configファイルに移行・多言語化完全対応・manifest系対応・PWA対応・ロード表示・アカウント初期化処理完成・mailsend.tsの型を修正
This commit is contained in:
parent
8d7a968c75
commit
ebacf6e1af
13
.env.example
13
.env.example
|
@ -1,13 +0,0 @@
|
||||||
// DATABASE(MySQL/MariaDB)
|
|
||||||
DB_HOST=
|
|
||||||
DB_PORT=
|
|
||||||
DB_USER=
|
|
||||||
DB_PASSWORD=
|
|
||||||
DB_DATABASE=
|
|
||||||
|
|
||||||
// MAIL(SMTP)
|
|
||||||
SMTP_HOST=
|
|
||||||
SMTP_PORT=
|
|
||||||
SMTP_SECURE=
|
|
||||||
SMTP_USER=
|
|
||||||
SMTP_PASSWORD=
|
|
|
@ -9,6 +9,7 @@
|
||||||
!.yarn/plugins
|
!.yarn/plugins
|
||||||
!.yarn/releases
|
!.yarn/releases
|
||||||
!.yarn/versions
|
!.yarn/versions
|
||||||
|
/package-lock.json
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
@ -31,8 +32,7 @@ yarn-error.log*
|
||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
|
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env
|
.env*
|
||||||
.env.local
|
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
@ -44,4 +44,7 @@ next-env.d.ts
|
||||||
# editor
|
# editor
|
||||||
/.vscode/
|
/.vscode/
|
||||||
/.trae/
|
/.trae/
|
||||||
/.vs/
|
/.vs/
|
||||||
|
|
||||||
|
# config
|
||||||
|
/peas.config.ts
|
||||||
|
|
14
README.md
14
README.md
|
@ -1,10 +1,9 @@
|
||||||
<center>
|
|
||||||
|
|
||||||
# Peas
|
# Peas
|
||||||
|
|
||||||
<img width="140" style="border-radius: 100%" src="./src/asset/peas.svg">
|
<img width="140" src="./src/asset/peas.svg">
|
||||||
|
|
||||||
## Technologys
|
## Technologys
|
||||||
|
|
||||||
<img width="30" src="./src/asset/simpleicons/html5.svg">
|
<img width="30" src="./src/asset/simpleicons/html5.svg">
|
||||||
<img width="30" src="./src/asset/simpleicons/css.svg">
|
<img width="30" src="./src/asset/simpleicons/css.svg">
|
||||||
<img width="30" src="./src/asset/simpleicons/js.svg">
|
<img width="30" src="./src/asset/simpleicons/js.svg">
|
||||||
|
@ -20,11 +19,12 @@
|
||||||
<img width="30" src="./src/asset/simpleicons/svg.svg">
|
<img width="30" src="./src/asset/simpleicons/svg.svg">
|
||||||
|
|
||||||
## About Peas
|
## About Peas
|
||||||
Peas is a SNS designed for sharing profiles.
|
|
||||||
You can register and share your user profile.
|
Peas is a SNS designed for sharing profiles.
|
||||||
You can freely create profile sections suitable for use at school or work, and also change the privacy settings.
|
You can register and share your user profile.
|
||||||
|
You can freely create profile sections suitable for use at school or work, and also change the privacy settings.
|
||||||
It also supports printing QR codes, making sharing easy.
|
It also supports printing QR codes, making sharing easy.
|
||||||
|
|
||||||
## LICENSE
|
## LICENSE
|
||||||
|
|
||||||
AGPL-v3.0
|
AGPL-v3.0
|
||||||
</center>
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
import { PeasConfigType } from "@/lib/peas.config";
|
||||||
|
|
||||||
|
const PeasConfig: PeasConfigType = {
|
||||||
|
// Server Config
|
||||||
|
serverName: "Peas Server",
|
||||||
|
serverDescription: "Peas SNS",
|
||||||
|
serverHost: "peas.example.com",
|
||||||
|
|
||||||
|
// SSL Config
|
||||||
|
ssl: {
|
||||||
|
ssl: false,
|
||||||
|
},
|
||||||
|
|
||||||
|
// SMTP Config
|
||||||
|
smtpHost: "mail.example.com",
|
||||||
|
smtpPort: 587,
|
||||||
|
smtpSecure: false,
|
||||||
|
smtpUser: "info@mail.example.com",
|
||||||
|
smtpPassword: "SMTPMailPassword",
|
||||||
|
|
||||||
|
// Database Config
|
||||||
|
databaseHost: "db.example.com",
|
||||||
|
databasePort: 3306,
|
||||||
|
databaseUser: "peas",
|
||||||
|
databasePassword: "DatabasePassword",
|
||||||
|
databaseDB: "peas",
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PeasConfig;
|
|
@ -1,14 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "peas",
|
"name": "peas",
|
||||||
"version": "1.0",
|
"version": "nonerelease",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "peas",
|
"name": "peas",
|
||||||
"version": "1.0",
|
"version": "nonerelease",
|
||||||
"license": "ISC",
|
"license": "AGPL-3.0-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iconify/react": "^6.0.0",
|
||||||
"@types/dotenv": "^6.1.1",
|
"@types/dotenv": "^6.1.1",
|
||||||
"@types/eslint": "^9.6.1",
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
@ -19,7 +20,7 @@
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"mysql2": "^3.14.1",
|
"mysql2": "^3.14.1",
|
||||||
"next": "^15.3.2",
|
"next": "^15.3.4",
|
||||||
"nodemailer": "^7.0.3",
|
"nodemailer": "^7.0.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
@ -289,6 +290,27 @@
|
||||||
"url": "https://github.com/sponsors/nzakas"
|
"url": "https://github.com/sponsors/nzakas"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@iconify/react": {
|
||||||
|
"version": "6.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify/react/-/react-6.0.0.tgz",
|
||||||
|
"integrity": "sha512-eqNscABVZS8eCpZLU/L5F5UokMS9mnCf56iS1nM9YYHdH8ZxqZL9zyjSwW60IOQFsXZkilbBiv+1paMXBhSQnw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@iconify/types": "^2.0.0"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/cyberalien"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@iconify/types": {
|
||||||
|
"version": "2.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz",
|
||||||
|
"integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@img/sharp-darwin-arm64": {
|
"node_modules/@img/sharp-darwin-arm64": {
|
||||||
"version": "0.34.2",
|
"version": "0.34.2",
|
||||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz",
|
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz",
|
||||||
|
@ -724,9 +746,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/env": {
|
"node_modules/@next/env": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.4.tgz",
|
||||||
"integrity": "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==",
|
"integrity": "sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@next/eslint-plugin-next": {
|
"node_modules/@next/eslint-plugin-next": {
|
||||||
|
@ -740,9 +762,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-arm64": {
|
"node_modules/@next/swc-darwin-arm64": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.4.tgz",
|
||||||
"integrity": "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==",
|
"integrity": "sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
@ -756,9 +778,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-darwin-x64": {
|
"node_modules/@next/swc-darwin-x64": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.4.tgz",
|
||||||
"integrity": "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==",
|
"integrity": "sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
@ -772,9 +794,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.4.tgz",
|
||||||
"integrity": "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==",
|
"integrity": "sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
@ -788,9 +810,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-arm64-musl": {
|
"node_modules/@next/swc-linux-arm64-musl": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.4.tgz",
|
||||||
"integrity": "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==",
|
"integrity": "sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
@ -804,9 +826,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-gnu": {
|
"node_modules/@next/swc-linux-x64-gnu": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.4.tgz",
|
||||||
"integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==",
|
"integrity": "sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
@ -820,9 +842,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-linux-x64-musl": {
|
"node_modules/@next/swc-linux-x64-musl": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.4.tgz",
|
||||||
"integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==",
|
"integrity": "sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
@ -836,9 +858,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.4.tgz",
|
||||||
"integrity": "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==",
|
"integrity": "sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
],
|
],
|
||||||
|
@ -852,9 +874,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@next/swc-win32-x64-msvc": {
|
"node_modules/@next/swc-win32-x64-msvc": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.4.tgz",
|
||||||
"integrity": "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==",
|
"integrity": "sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
],
|
],
|
||||||
|
@ -4349,12 +4371,12 @@
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/next": {
|
"node_modules/next": {
|
||||||
"version": "15.3.2",
|
"version": "15.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/next/-/next-15.3.4.tgz",
|
||||||
"integrity": "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==",
|
"integrity": "sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@next/env": "15.3.2",
|
"@next/env": "15.3.4",
|
||||||
"@swc/counter": "0.1.3",
|
"@swc/counter": "0.1.3",
|
||||||
"@swc/helpers": "0.5.15",
|
"@swc/helpers": "0.5.15",
|
||||||
"busboy": "1.6.0",
|
"busboy": "1.6.0",
|
||||||
|
@ -4369,14 +4391,14 @@
|
||||||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@next/swc-darwin-arm64": "15.3.2",
|
"@next/swc-darwin-arm64": "15.3.4",
|
||||||
"@next/swc-darwin-x64": "15.3.2",
|
"@next/swc-darwin-x64": "15.3.4",
|
||||||
"@next/swc-linux-arm64-gnu": "15.3.2",
|
"@next/swc-linux-arm64-gnu": "15.3.4",
|
||||||
"@next/swc-linux-arm64-musl": "15.3.2",
|
"@next/swc-linux-arm64-musl": "15.3.4",
|
||||||
"@next/swc-linux-x64-gnu": "15.3.2",
|
"@next/swc-linux-x64-gnu": "15.3.4",
|
||||||
"@next/swc-linux-x64-musl": "15.3.2",
|
"@next/swc-linux-x64-musl": "15.3.4",
|
||||||
"@next/swc-win32-arm64-msvc": "15.3.2",
|
"@next/swc-win32-arm64-msvc": "15.3.4",
|
||||||
"@next/swc-win32-x64-msvc": "15.3.2",
|
"@next/swc-win32-x64-msvc": "15.3.4",
|
||||||
"sharp": "^0.34.1"
|
"sharp": "^0.34.1"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "peas",
|
"name": "peas",
|
||||||
"version": "1.0",
|
"version": "nonerelease",
|
||||||
"license": "AGPL-3.0-later",
|
"license": "AGPL-3.0-later",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Last2014",
|
"name": "Last2014",
|
||||||
|
@ -15,6 +15,7 @@
|
||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@iconify/react": "^6.0.0",
|
||||||
"@types/dotenv": "^6.1.1",
|
"@types/dotenv": "^6.1.1",
|
||||||
"@types/eslint": "^9.6.1",
|
"@types/eslint": "^9.6.1",
|
||||||
"@types/js-cookie": "^3.0.6",
|
"@types/js-cookie": "^3.0.6",
|
||||||
|
@ -25,7 +26,7 @@
|
||||||
"dotenv": "^16.5.0",
|
"dotenv": "^16.5.0",
|
||||||
"js-cookie": "^3.0.5",
|
"js-cookie": "^3.0.5",
|
||||||
"mysql2": "^3.14.1",
|
"mysql2": "^3.14.1",
|
||||||
"next": "^15.3.2",
|
"next": "^15.3.4",
|
||||||
"nodemailer": "^7.0.3",
|
"nodemailer": "^7.0.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
import pool from '@/lib/database';
|
import pool from "@/lib/database";
|
||||||
import type { RowDataPacket } from 'mysql2';
|
import type { RowDataPacket } from "mysql2";
|
||||||
|
|
||||||
import { NextResponse, NextRequest } from 'next/server';
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { code } = body;
|
const { code } = body;
|
||||||
|
|
||||||
const [rows] = await pool.query<RowDataPacket[]>(
|
const [rows] = await pool.query<RowDataPacket[]>(
|
||||||
'SELECT * FROM mailverifiedcode WHERE code = ?',
|
"SELECT * FROM mailverifiedcode WHERE code = ?",
|
||||||
[code]
|
[code],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (rows.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ status: "error", message: "Code not found" },
|
||||||
|
{ status: 401 },
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
if (rows[0].code === code) {
|
||||||
|
await pool.query<RowDataPacket[]>(
|
||||||
|
"UPDATE `users` SET `mailverified` = 1 WHERE `users`.`id` = ?",
|
||||||
|
[rows[0].user],
|
||||||
|
);
|
||||||
|
|
||||||
if (rows.length === 0) {
|
return NextResponse.json(
|
||||||
return NextResponse.json({status: "error", message: 'Code not found' }, { status: 401 });
|
{ status: "success", message: "Code verified" },
|
||||||
}else{
|
{ status: 200 },
|
||||||
if(rows[0].code === code){
|
);
|
||||||
await pool.query<RowDataPacket[]>(
|
|
||||||
'UPDATE `users` SET `mailverified` = 1 WHERE `users`.`id` = ?',
|
|
||||||
[rows[0].user]
|
|
||||||
);
|
|
||||||
|
|
||||||
return NextResponse.json({status: "success", message: 'Code verified' }, { status: 200 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,71 +1,78 @@
|
||||||
import pool from '@/lib/database';
|
import pool from "@/lib/database";
|
||||||
import type { RowDataPacket } from 'mysql2';
|
import type { RowDataPacket } from "mysql2";
|
||||||
|
|
||||||
import { sendMail } from '@/lib/mailsend';
|
import { sendMail } from "@/lib/mailsend";
|
||||||
|
import PeasConfig from "@/../peas.config";
|
||||||
|
|
||||||
import { NextResponse, NextRequest } from 'next/server';
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
|
|
||||||
// コード生成
|
// コード生成
|
||||||
async function createCode(maxAttempts = 10) {
|
async function createCode(maxAttempts = 10) {
|
||||||
for (let i = 0; i < maxAttempts; i++) {
|
for (let i = 0; i < maxAttempts; i++) {
|
||||||
const code = Math.floor(100000 + Math.random() * 900000).toString();
|
const code = Math.floor(100000 + Math.random() * 900000).toString();
|
||||||
const [existingCodes] = await pool.execute<RowDataPacket[]>(
|
const [existingCodes] = await pool.execute<RowDataPacket[]>(
|
||||||
"SELECT * FROM mailverifiedcode WHERE code = ?",[code]
|
"SELECT * FROM mailverifiedcode WHERE code = ?",
|
||||||
);
|
[code],
|
||||||
if (existingCodes.length === 0) {
|
);
|
||||||
return code;
|
if (existingCodes.length === 0) {
|
||||||
}
|
return code;
|
||||||
}
|
}
|
||||||
throw new Error("Failed to generate unique code after maximum attempts");
|
}
|
||||||
|
throw new Error("Failed to generate unique code after maximum attempts");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
// body取得
|
// body取得
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { email, user } = body;
|
const { email, user } = body;
|
||||||
|
|
||||||
const code = await createCode();
|
const code = await createCode();
|
||||||
|
|
||||||
// コードが存在する場合
|
// コードが存在する場合
|
||||||
const [existingCodes] = await pool.execute<RowDataPacket[]>(
|
const [existingCodes] = await pool.execute<RowDataPacket[]>(
|
||||||
"SELECT * FROM mailverifiedcode WHERE user = ?",[user]
|
"SELECT * FROM mailverifiedcode WHERE user = ?",
|
||||||
|
[user],
|
||||||
|
);
|
||||||
|
|
||||||
|
let row: RowDataPacket[] = [];
|
||||||
|
|
||||||
|
if (existingCodes.length !== 0) {
|
||||||
|
[row] = await pool.execute<RowDataPacket[]>(
|
||||||
|
"UPDATE `mailverifiedcode` SET `code` = ?, `time` = current_timestamp(3) WHERE `mailverifiedcode`.`user` = ?",
|
||||||
|
[code, user],
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
let row: RowDataPacket[] = [];
|
// コードが存在しない場合
|
||||||
|
[row] = await pool.execute<RowDataPacket[]>(
|
||||||
|
"INSERT INTO `mailverifiedcode` (`code`, `user`, `email`, `time`, `num`) VALUES (?, ?, ?, current_timestamp(3), NULL);",
|
||||||
|
[code, user, email],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if(existingCodes.length !== 0){
|
// メール送信
|
||||||
[row] = await pool.execute<RowDataPacket[]>(
|
if ((row as RowDataPacket).affectedRows === 1) {
|
||||||
"UPDATE `mailverifiedcode` SET `code` = ?, `time` = current_timestamp(3) WHERE `mailverifiedcode`.`user` = ?",
|
await sendMail({
|
||||||
[code, user]
|
to: email,
|
||||||
)
|
subject: `【${PeasConfig.serverName}】メール認証`,
|
||||||
}else{
|
text: `
|
||||||
// コードが存在しない場合
|
${user}さんこんにちは。\n
|
||||||
[row] = await pool.execute<RowDataPacket[]>(
|
${PeasConfig.serverName}ではメール認証が必要となっています。\n
|
||||||
"INSERT INTO `mailverifiedcode` (`code`, `user`, `email`, `time`, `num`) VALUES (?, ?, ?, current_timestamp(3), NULL);",
|
メール認証をすることで${PeasConfig.serverName}の機能を利用することができます。\n
|
||||||
[code, user, email]
|
${request.nextUrl.origin}/mailverify?code=${code} にアクセスしてください。`,
|
||||||
)
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// メール送信
|
return NextResponse.json({
|
||||||
if ((row as RowDataPacket).affectedRows === 1) {
|
status: "success",
|
||||||
await sendMail({
|
code: code,
|
||||||
to: email,
|
});
|
||||||
subject: "【Peas】メール認証",
|
} else {
|
||||||
text: `${user}さんこんにちは。\n
|
// コード保存失敗
|
||||||
Peasではメール認証が必要となっています。\n
|
return NextResponse.json(
|
||||||
メール認証をすることでPeasの機能を利用することができます。\n
|
{
|
||||||
${request.nextUrl.origin}/mailverify?code=${code} にアクセスしてください。`
|
status: "error",
|
||||||
})
|
error: "Failed to Save Code",
|
||||||
|
},
|
||||||
return NextResponse.json({
|
{ status: 400 },
|
||||||
status: "success",
|
);
|
||||||
code: code,
|
}
|
||||||
})
|
}
|
||||||
}else{
|
|
||||||
// コード保存失敗
|
|
||||||
return NextResponse.json({
|
|
||||||
status: "error",
|
|
||||||
error: "Failed to Save Code"
|
|
||||||
}, { status: 400 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,47 +1,57 @@
|
||||||
import pool from '@/lib/database';
|
import pool from "@/lib/database";
|
||||||
import type { RowDataPacket } from 'mysql2';
|
import type { RowDataPacket } from "mysql2";
|
||||||
|
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
import { NextResponse, NextRequest } from 'next/server';
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
import { cookies } from 'next/headers'
|
import { cookies } from "next/headers";
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
// body取得
|
// body取得
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { email, password } = body;
|
const { email, password } = body;
|
||||||
|
|
||||||
// ユーザー取得
|
// ユーザー取得
|
||||||
const [existingUsers] = await pool.execute<RowDataPacket[]>(
|
const [existingUsers] = await pool.execute<RowDataPacket[]>(
|
||||||
"SELECT * FROM users WHERE email = ?", [email]
|
"SELECT * FROM users WHERE email = ?",
|
||||||
|
[email],
|
||||||
|
);
|
||||||
|
|
||||||
|
// ユーザーが存在しない場合
|
||||||
|
if (existingUsers.length === 0) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
status: "error",
|
||||||
|
error: "User not found",
|
||||||
|
},
|
||||||
|
{ status: 404 },
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ユーザーが存在しない場合
|
const user = existingUsers[0];
|
||||||
if (existingUsers.length === 0) {
|
const passwordMatch = await bcrypt.compare(password, user.password);
|
||||||
return NextResponse.json({
|
|
||||||
status: "error",
|
|
||||||
error: "User not found"
|
|
||||||
}, { status: 404 });
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = existingUsers[0];
|
// パスワード確認
|
||||||
const passwordMatch = await bcrypt.compare(password, user.password);
|
if (!passwordMatch) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
status: "error",
|
||||||
|
error: "Incorrect password",
|
||||||
|
},
|
||||||
|
{ status: 401 },
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 成功
|
||||||
|
const sessionCookie = await cookies();
|
||||||
|
sessionCookie.set("user", user.id);
|
||||||
|
sessionCookie.set("password", password);
|
||||||
|
|
||||||
// パスワード確認
|
return NextResponse.json(
|
||||||
if (!passwordMatch) {
|
{
|
||||||
return NextResponse.json({
|
status: "success",
|
||||||
status: "error",
|
message: "Login successful",
|
||||||
error: "Incorrect password"
|
},
|
||||||
}, { status: 401 });
|
{ status: 200 },
|
||||||
} else {
|
);
|
||||||
// 成功
|
}
|
||||||
const sessionCookie = await cookies();
|
}
|
||||||
sessionCookie.set('user', user.id);
|
|
||||||
sessionCookie.set('password', password);
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
status: "success",
|
|
||||||
message: "Login successful"
|
|
||||||
}, { status: 200 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,87 +1,114 @@
|
||||||
import pool from '@/lib/database';
|
import pool from "@/lib/database";
|
||||||
import type { RowDataPacket } from 'mysql2';
|
import type { RowDataPacket } from "mysql2";
|
||||||
|
|
||||||
import bcrypt from 'bcrypt';
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
import { NextResponse, NextRequest } from 'next/server';
|
import { NextResponse, NextRequest } from "next/server";
|
||||||
|
|
||||||
export async function POST(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
const body = await request.json();
|
const body = await request.json();
|
||||||
const { email, password, id, rule } = body;
|
const { email, password, id, rule } = body;
|
||||||
|
|
||||||
try{
|
try {
|
||||||
if(!rule || rule === false){
|
if (!rule || rule === false) {
|
||||||
return NextResponse.json({
|
return NextResponse.json(
|
||||||
status: "error",
|
{
|
||||||
error: "Please read and accept the terms and conditions"
|
status: "error",
|
||||||
}, { status: 400 });
|
error: "Please read and accept the terms and conditions",
|
||||||
}
|
},
|
||||||
}catch(e){
|
{ status: 400 },
|
||||||
console.log(e);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
// bodyが空になっていないか
|
// bodyが空になっていないか
|
||||||
if(!email || !password || !id || !rule){
|
if (!email || !password || !id || !rule) {
|
||||||
return NextResponse.json({
|
return NextResponse.json(
|
||||||
status: "error",
|
{
|
||||||
error: "Body Required"
|
status: "error",
|
||||||
}, { status: 400 });
|
error: "Body Required",
|
||||||
}
|
},
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// パスワードが八文字以上か
|
// パスワードが8文字以上か
|
||||||
if(password.length < 8){
|
if (password.length < 8) {
|
||||||
return NextResponse.json({
|
return NextResponse.json(
|
||||||
status: "error",
|
{
|
||||||
error: "Password must be at least 8 characters long"
|
status: "error",
|
||||||
}, { status: 400 });
|
error: "Password must be at least 8 characters long",
|
||||||
}
|
},
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// ユーザー名が英数字3文字以上か
|
// ユーザー名が英数字3文字以上か
|
||||||
if(!/^[a-zA-Z0-9]{3,}$/.test(id)){
|
if (!/^[a-zA-Z0-9]{3,}$/.test(id)) {
|
||||||
return NextResponse.json({
|
return NextResponse.json(
|
||||||
status: "error",
|
{
|
||||||
error: "Username must be at least 3 alphanumeric characters"
|
status: "error",
|
||||||
}, { status: 400 });
|
error: "Username must be at least 3 alphanumeric characters",
|
||||||
}
|
},
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const [existingUsers] = await pool.execute<RowDataPacket[]>(
|
const [existingUsers] = await pool.execute<RowDataPacket[]>(
|
||||||
"SELECT * FROM users WHERE id = ?",[id]
|
"SELECT * FROM users WHERE id = ?",
|
||||||
|
[id],
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingUsers.length === 0) {
|
||||||
|
const passwordHash = await bcrypt.hash(password, 10);
|
||||||
|
|
||||||
|
const [result] = await pool.execute<RowDataPacket[]>(
|
||||||
|
"INSERT INTO `users` (`id`, `password`, `email`, `mailverified`, `time`, `num`) VALUES (?, ?, ?, '0', current_timestamp(3), NULL)",
|
||||||
|
[id, passwordHash, email],
|
||||||
);
|
);
|
||||||
|
|
||||||
if(existingUsers.length === 0){
|
if ((result as RowDataPacket).affectedRows === 1) {
|
||||||
const passwordHash = await bcrypt.hash(password, 10);
|
await fetch(
|
||||||
|
request.nextUrl.protocol +
|
||||||
|
request.nextUrl.host +
|
||||||
|
"/api/mailverified/send",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: email,
|
||||||
|
user: id,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const [result] = await pool.execute<RowDataPacket[]>(
|
return NextResponse.json(
|
||||||
"INSERT INTO `users` (`id`, `password`, `email`, `mailverified`, `time`, `num`) VALUES (?, ?, ?, '0', current_timestamp(3), NULL)",
|
{
|
||||||
[id, passwordHash, email]
|
status: "success",
|
||||||
);
|
message: "User created successfully",
|
||||||
|
},
|
||||||
if ((result as RowDataPacket).affectedRows === 1) {
|
{ status: 201 },
|
||||||
await fetch(request.nextUrl.protocol + request.nextUrl.host + "/api/mailverified/send", {
|
);
|
||||||
method: "POST",
|
} else {
|
||||||
headers: {
|
return NextResponse.json(
|
||||||
"Content-Type": "application/json"
|
{
|
||||||
},
|
status: "error",
|
||||||
body: JSON.stringify({
|
error: "Failed to create user",
|
||||||
email: email,
|
},
|
||||||
user: id,
|
{ status: 500 },
|
||||||
})
|
);
|
||||||
});
|
|
||||||
|
|
||||||
return NextResponse.json({
|
|
||||||
status: "success",
|
|
||||||
message: "User created successfully"
|
|
||||||
}, { status: 201 });
|
|
||||||
} else {
|
|
||||||
return NextResponse.json({
|
|
||||||
status: "error",
|
|
||||||
error: "Failed to create user"
|
|
||||||
}, { status: 500 });
|
|
||||||
}
|
|
||||||
}else{
|
|
||||||
return NextResponse.json({
|
|
||||||
status: "error",
|
|
||||||
error: "User already exists"
|
|
||||||
}, { status: 400 });
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
status: "error",
|
||||||
|
error: "User already exists",
|
||||||
|
},
|
||||||
|
{ status: 400 },
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
@import 'bulma/css/bulma.css';
|
@import "bulma/css/bulma.min.css";
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 15 KiB |
|
@ -2,9 +2,11 @@ import type { Metadata } from "next";
|
||||||
import "./bulma.css";
|
import "./bulma.css";
|
||||||
import "./global.css";
|
import "./global.css";
|
||||||
|
|
||||||
|
import PeasConfig from "@/../peas.config";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Peas",
|
title: PeasConfig.serverName,
|
||||||
description: "Peas SNS",
|
description: PeasConfig.serverDescription,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
|
@ -14,13 +16,7 @@ export default function RootLayout({
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html>
|
<html>
|
||||||
<body>
|
<body>{children}</body>
|
||||||
<header className="header">
|
|
||||||
<span className="title">Peas</span>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
{children}
|
|
||||||
</body>
|
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,53 +1,90 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
|
import { Icon } from "@iconify/react";
|
||||||
|
import { MessageLang } from "@/lang/component/client";
|
||||||
|
import { MessageLangVar } from "@/lang/component/variable";
|
||||||
|
|
||||||
export default function MailVerify() {
|
export default function MailVerify() {
|
||||||
const searchParams = useSearchParams();
|
const [loading, setLoading] = useState(false);
|
||||||
const URLCode = searchParams.get("code");
|
|
||||||
|
|
||||||
let code: string = "";
|
const searchParams = useSearchParams();
|
||||||
|
const URLCode = searchParams.get("code");
|
||||||
|
|
||||||
if(URLCode){
|
let code: string = "";
|
||||||
code = URLCode;
|
|
||||||
|
if (URLCode !== null) {
|
||||||
|
code = URLCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const verify = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setLoading(true);
|
||||||
|
|
||||||
|
if (code === "") {
|
||||||
|
code = e.currentTarget.code.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
const verify = async (e: React.FormEvent<HTMLFormElement>) => {
|
if (code.length !== 6) {
|
||||||
if(code === ""){
|
alert(MessageLangVar({text: "codeCharacterCount"}));
|
||||||
code = e.currentTarget.code.value;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (code.length !== 6) {
|
try {
|
||||||
alert("コードは6桁で入力してください。");
|
const res = await fetch(`${location.origin}/api/mailverified/check`, {
|
||||||
return;
|
method: "POST",
|
||||||
}
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
code,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
const res = await fetch(`${location.origin}/api/mailverify/check`, {
|
const data = await res.text();
|
||||||
method: "POST",
|
const result = JSON.parse(data);
|
||||||
headers: {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
code,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const data = await res.text();
|
|
||||||
const result = JSON.parse(data);
|
|
||||||
|
|
||||||
if (result.status === "success") {
|
if (result.status === "success") {
|
||||||
alert("メール認証に成功しました。\nログインしてください。");
|
alert(MessageLangVar({ text: "emailVerificationSuccess" }));
|
||||||
window.location.href = "/login";
|
window.location.href = "/signin";
|
||||||
}
|
}
|
||||||
};
|
} catch (err) {
|
||||||
|
console.error(err)
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className="title">メール認証</h1>
|
<h1 className="title has-text-centered"><MessageLang text="mailVerify" /></h1>
|
||||||
|
|
||||||
<form onSubmit={verify}>
|
{loading && (
|
||||||
<label className="label">コード:</label>
|
<div style={{ marginBottom: "1rem" }}>
|
||||||
<input type="text" name="code" defaultValue={code} className="input" placeholder="123456" style={{width: "10rem"}} maxLength={6} />
|
<Icon icon="svg-spinners:6-dots-scale" width={32} height={32} />
|
||||||
</form>
|
</div>
|
||||||
</>
|
)}
|
||||||
)
|
|
||||||
}
|
<form onSubmit={verify} className="has-text-centered">
|
||||||
|
<label className="label"><MessageLang text="code" />:</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="code"
|
||||||
|
defaultValue={code}
|
||||||
|
className="input"
|
||||||
|
placeholder="123456"
|
||||||
|
style={{
|
||||||
|
width: "10rem",
|
||||||
|
marginBottom: "10px"
|
||||||
|
}}
|
||||||
|
maxLength={6}
|
||||||
|
/>
|
||||||
|
<br />
|
||||||
|
<button className="button" type="submit">
|
||||||
|
<MessageLang text="submit" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import type { MetadataRoute } from "next";
|
||||||
|
import PeasConfig from "@/../peas.config";
|
||||||
|
|
||||||
|
export default function manifest(): MetadataRoute.Manifest {
|
||||||
|
return {
|
||||||
|
name: PeasConfig.serverName,
|
||||||
|
description: PeasConfig.serverDescription,
|
||||||
|
start_url: "/",
|
||||||
|
display: "standalone",
|
||||||
|
background_color: "#fff",
|
||||||
|
theme_color: "#fff",
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: "/favicon.ico",
|
||||||
|
sizes: "any",
|
||||||
|
type: "image/x-icon",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,26 +1,54 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import "./style.css"
|
import "./style.css";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { Icon } from "@iconify/react";
|
||||||
|
|
||||||
|
import PeasConfig from "@/../peas.config";
|
||||||
import { MessageLang } from "@/lang/component/client";
|
import { MessageLang } from "@/lang/component/client";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<h1 className="has-text-centered title">{PeasConfig.serverName}</h1>
|
||||||
|
|
||||||
<div className="message about">
|
<div className="message about">
|
||||||
<h1 className="message-header"><MessageLang text="PeasAboutTitle" /></h1>
|
<h1 className="message-header">
|
||||||
|
<Icon icon="ix:about-filled" />
|
||||||
|
<MessageLang className="peasAboutTitle" text="ServerAboutTitle" />
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p className="message-body">{PeasConfig.serverDescription}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="message about">
|
||||||
|
<h1 className="message-header">
|
||||||
|
<Icon icon="ix:about-filled" />
|
||||||
|
<MessageLang className="peasAboutTitle" text="PeasAboutTitle" />
|
||||||
|
</h1>
|
||||||
|
|
||||||
<p className="message-body">
|
<p className="message-body">
|
||||||
<MessageLang text="PeasAboutText" />
|
<MessageLang text="PeasAboutText" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{width: "15rem"}}>
|
<div className="has-text-centered">
|
||||||
<Link href="/signup" className="card card-footer card-footer-item signup"><MessageLang text="Start" /></Link>
|
<Link
|
||||||
<p className="has-text-centered"><MessageLang text="or" /></p>
|
href="/signup"
|
||||||
<Link href="/signin" className="card card-footer card-footer-item signin"><MessageLang text="Signin" /></Link>
|
className="card card-footer card-footer-item signup"
|
||||||
|
>
|
||||||
|
<MessageLang text="Start" />
|
||||||
|
</Link>
|
||||||
|
<p className="has-text-centered">
|
||||||
|
<MessageLang text="or" />
|
||||||
|
</p>
|
||||||
|
<Link
|
||||||
|
href="/signin"
|
||||||
|
className="card card-footer card-footer-item signin"
|
||||||
|
>
|
||||||
|
<MessageLang text="Signin" />
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -0,0 +1,20 @@
|
||||||
|
import type { MetadataRoute } from "next";
|
||||||
|
import PeasConfig from "../../peas.config";
|
||||||
|
|
||||||
|
let protocol: string;
|
||||||
|
|
||||||
|
if (PeasConfig.ssl.ssl) {
|
||||||
|
protocol = "https";
|
||||||
|
} else {
|
||||||
|
protocol = "http";
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function robots(): MetadataRoute.Robots {
|
||||||
|
return {
|
||||||
|
rules: {
|
||||||
|
userAgent: "*",
|
||||||
|
allow: "/",
|
||||||
|
},
|
||||||
|
sitemap: `${protocol}://${PeasConfig.serverHost}/sitemap.xml`,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,46 +1,93 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
import { MessageLang } from "@/lang/component/client";
|
import { MessageLang } from "@/lang/component/client";
|
||||||
|
import { Icon } from "@iconify/react";
|
||||||
|
|
||||||
export default function SignIn() {
|
export default function SignIn() {
|
||||||
const FormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
const [error, setError] = useState(false);
|
||||||
e.preventDefault();
|
const [loading, setLoading] = useState(false);
|
||||||
|
|
||||||
const res = await fetch("/api/signin", {
|
const FormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
method: "POST",
|
e.preventDefault();
|
||||||
headers: {
|
setLoading(true);
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: e.currentTarget.email.value,
|
|
||||||
password: e.currentTarget.password.value,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const data = await res.text();
|
|
||||||
const json = JSON.parse(data);
|
|
||||||
|
|
||||||
if (json.status === "success") {
|
try {
|
||||||
window.location.href = "/";
|
const res = await fetch("/api/signin", {
|
||||||
}
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
email: e.currentTarget.email.value,
|
||||||
|
password: e.currentTarget.password.value,
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
const data = await res.text();
|
||||||
|
const json = JSON.parse(data);
|
||||||
|
|
||||||
|
if (json.status === "success") {
|
||||||
|
window.location.href = "/";
|
||||||
|
} else {
|
||||||
|
setError(true);
|
||||||
|
window.location.replace("/signin?error=true");
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
setError(true);
|
||||||
|
window.location.replace("/signin?error=true");
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
useState(() => {
|
||||||
<form onSubmit={FormSubmit}>
|
const locationURL = new URL(window.location.href);
|
||||||
<h1 className="title"><MessageLang text="Signin" /></h1>
|
if (locationURL.searchParams.get("error")) {
|
||||||
|
setError(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
<label className="label"><MessageLang text="email" /></label>
|
return (
|
||||||
<input type="email" name="email" className="input" required />
|
<form onSubmit={FormSubmit} className="has-text-centered">
|
||||||
|
{error && (
|
||||||
|
<div className="message is-danger">
|
||||||
|
<div className="message-body">
|
||||||
|
<Icon icon="iconoir:warning-circle-solid" />
|
||||||
|
<MessageLang text="signinError" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<br />
|
<h1 className="title">
|
||||||
|
<MessageLang text="Signin" />
|
||||||
|
</h1>
|
||||||
|
|
||||||
<label className="label"><MessageLang text="password" /></label>
|
{loading && (
|
||||||
<input type="password" name="password" className="input" required />
|
<div style={{ marginBottom: "1rem" }}>
|
||||||
|
<Icon icon="svg-spinners:6-dots-scale" width={32} height={32} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<br />
|
<label className="label">
|
||||||
|
<MessageLang text="email" />
|
||||||
|
</label>
|
||||||
|
<input type="email" name="email" className="input" required />
|
||||||
|
|
||||||
<button type="submit" className="button"><MessageLang text="Signin" /></button>
|
<br />
|
||||||
</form>
|
|
||||||
)
|
<label className="label">
|
||||||
}
|
<MessageLang text="password" />
|
||||||
|
</label>
|
||||||
|
<input type="password" name="password" className="input" required />
|
||||||
|
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<button type="submit" className="button">
|
||||||
|
<MessageLang text="Signin" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
.input{
|
.input {
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button{
|
.button {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,77 +1,174 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Icon } from "@iconify/react";
|
||||||
|
|
||||||
import "./style.css";
|
import "./style.css";
|
||||||
|
|
||||||
import { MessageLang } from "@/lang/component/client";
|
import { MessageLang } from "@/lang/component/client";
|
||||||
|
import { MessageLangVar } from "@/lang/component/variable";
|
||||||
|
|
||||||
export default function SignUp() {
|
export default function SignUp() {
|
||||||
const FormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
const [loading, setLoading] = useState(false);
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const res = await fetch("/api/signup", {
|
const FormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||||
method: "POST",
|
e.preventDefault();
|
||||||
headers: {
|
setLoading(true);
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
email: e.currentTarget.email.value,
|
|
||||||
password: e.currentTarget.password.value,
|
|
||||||
id: e.currentTarget.username.value,
|
|
||||||
rule: e.currentTarget.ruleCheck.checked,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
const data = await res.text();
|
|
||||||
const json = JSON.parse(data);
|
|
||||||
|
|
||||||
if (json.status === "success") {
|
try {
|
||||||
alert("アカウントの作成に成功しました。\nご登録いただいたメールアドレスに確認用メールを送信しました。\n確認メールをクリックしてアカウントを有効化してください。")
|
const res = await fetch("/api/signup", {
|
||||||
window.location.href = "/";
|
method: "POST",
|
||||||
}else{
|
headers: {
|
||||||
if(json.error === "User already exists"){
|
"Content-Type": "application/json",
|
||||||
alert("同じユーザー名が既に使用されています")
|
},
|
||||||
}else if(json.error === "Please read and accept the terms and conditions"){
|
body: JSON.stringify({
|
||||||
alert("利用規約とプライバシーポリシーに同意してください")
|
email: e.currentTarget.email.value,
|
||||||
}else if(json.error === "Body Required"){
|
password: e.currentTarget.password.value,
|
||||||
alert("Bodyは必須です")
|
id: e.currentTarget.username.value,
|
||||||
}else if(json.error === "Password must be at least 8 characters long"){
|
rule: e.currentTarget.ruleCheck.checked,
|
||||||
alert("パスワードは8文字以上にしてください")
|
}),
|
||||||
}else if(json.error === "Username must be at least 3 alphanumeric characters"){
|
});
|
||||||
alert("ユーザー名は3文字以上の英数字のIDにしてください")
|
|
||||||
}else if(json.error === "Failed to create user"){
|
const data = await res.text();
|
||||||
alert("ユーザーの作成に失敗しました")
|
const json = JSON.parse(data);
|
||||||
}
|
|
||||||
|
if (json.status === "success") {
|
||||||
|
alert(MessageLangVar({ text: "accountCreateSuccess" }));
|
||||||
|
window.location.href = "/";
|
||||||
|
} else {
|
||||||
|
if (json.error === "User already exists") {
|
||||||
|
alert(MessageLangVar({ text: "sameUsername" }));
|
||||||
|
} else if (
|
||||||
|
json.error === "Please read and accept the terms and conditions"
|
||||||
|
) {
|
||||||
|
alert(MessageLangVar({ text: "termsAgreement" }));
|
||||||
|
} else if (json.error === "Body Required") {
|
||||||
|
alert(MessageLangVar({ text: "bodyRequired" }));
|
||||||
|
} else if (
|
||||||
|
json.error === "Password must be at least 8 characters long"
|
||||||
|
) {
|
||||||
|
alert(MessageLangVar({ text: "passwordCharacterLimit" }));
|
||||||
|
} else if (
|
||||||
|
json.error === "Username must be at least 3 alphanumeric characters"
|
||||||
|
) {
|
||||||
|
alert(MessageLangVar({ text: "usernameRestriction" }));
|
||||||
|
} else if (json.error === "Failed to create user") {
|
||||||
|
alert(MessageLangVar({ text: "userCreationFailed" }));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert(`${MessageLangVar({ text: "connectionError" })}: ${err}`);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={FormSubmit}>
|
<div
|
||||||
<h1 className="title"><MessageLang text="Signup" /></h1>
|
className="has-text-centered"
|
||||||
|
style={{ maxWidth: "400px", margin: "2rem auto" }}
|
||||||
|
>
|
||||||
|
<form onSubmit={FormSubmit} style={{ width: "100%" }}>
|
||||||
|
<h1 className="title">
|
||||||
|
<MessageLang text="Signup" />
|
||||||
|
</h1>
|
||||||
|
|
||||||
<label className="label"><MessageLang text="username" /></label>
|
{loading && (
|
||||||
<div className="field" style={{ position: 'relative' }}>
|
<div style={{ marginBottom: "1rem" }}>
|
||||||
<span className="input-out">@</span>
|
<Icon icon="svg-spinners:6-dots-scale" width={32} height={32} />
|
||||||
<input type="text" name="username" placeholder="username" className="input" style={{ paddingLeft: '25px' }} required />
|
</div>
|
||||||
</div>
|
)}
|
||||||
<p className="help"><MessageLang text="idHelp" /></p>
|
|
||||||
|
|
||||||
<label className="label"><MessageLang text="email" /></label>
|
<label className="label">
|
||||||
<input className="input" type="email" name="email" placeholder="info@example.com" required />
|
<MessageLang text="username" />
|
||||||
<p className="help"><MessageLang text="mailHelp" /></p>
|
</label>
|
||||||
|
<div className="field" style={{ position: "relative", width: "100%" }}>
|
||||||
|
<span
|
||||||
|
className="input-out"
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
left: "10px",
|
||||||
|
top: "50%",
|
||||||
|
transform: "translateY(-50%)",
|
||||||
|
pointerEvents: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
@
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="username"
|
||||||
|
placeholder="username"
|
||||||
|
className="input"
|
||||||
|
style={{ paddingLeft: "30px", width: "100%" }}
|
||||||
|
required
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p className="help">
|
||||||
|
<MessageLang text="idHelp" />
|
||||||
|
</p>
|
||||||
|
|
||||||
<label className="label"><MessageLang text="password" /></label>
|
<label className="label">
|
||||||
<input type="password" name="password" className="input" placeholder="password" required />
|
<MessageLang text="email" />
|
||||||
<p className="help"><MessageLang text="passwordHelp" /></p>
|
</label>
|
||||||
|
<input
|
||||||
|
className="input"
|
||||||
|
type="email"
|
||||||
|
name="email"
|
||||||
|
placeholder="info@example.com"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
required
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
<p className="help">
|
||||||
|
<MessageLang text="mailHelp" />
|
||||||
|
</p>
|
||||||
|
|
||||||
<span style={{margin: "5px"}} />
|
<label className="label">
|
||||||
|
<MessageLang text="password" />
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
name="password"
|
||||||
|
className="input"
|
||||||
|
placeholder="password"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
required
|
||||||
|
disabled={loading}
|
||||||
|
/>
|
||||||
|
<p className="help">
|
||||||
|
<MessageLang text="passwordHelp" />
|
||||||
|
</p>
|
||||||
|
|
||||||
<label className="label checkbox">
|
<span style={{ margin: "5px" }} />
|
||||||
<input type="checkbox" name="ruleCheck" required />
|
|
||||||
<Link href="/terms">利用規約</Link>と<Link href="/privacypolicy">プライバシーポリシー</Link>に同意する
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<button type="submit" className="button">サインアップ</button>
|
<label className="label checkbox" style={{ justifyContent: "center" }}>
|
||||||
</form>
|
<input type="checkbox" name="ruleCheck" required disabled={loading} />
|
||||||
)
|
|
||||||
}
|
<MessageLang text="agree" />
|
||||||
|
{": "}
|
||||||
|
|
||||||
|
<Link href="/terms">
|
||||||
|
<MessageLang text="terms" />
|
||||||
|
</Link>
|
||||||
|
<MessageLang text="and" />
|
||||||
|
<Link href="/privacypolicy">
|
||||||
|
<MessageLang text="privacypolicy" />
|
||||||
|
</Link>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
className="button is-primary"
|
||||||
|
style={{ width: "100%" }}
|
||||||
|
id="signup"
|
||||||
|
disabled={loading}
|
||||||
|
>
|
||||||
|
<MessageLang text="Signup" />
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
.input{
|
.input {
|
||||||
width: 20rem;
|
width: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button{
|
.button {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-out{
|
.input-out {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 10px;
|
left: 10px;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
@ -18,6 +18,6 @@
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inputout-email{
|
.inputout-email {
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
import type { MetadataRoute } from "next";
|
||||||
|
|
||||||
|
export default function sitemap(): MetadataRoute.Sitemap {
|
||||||
|
return [];
|
||||||
|
}
|
|
@ -1,7 +1,16 @@
|
||||||
.about{
|
.about {
|
||||||
margin: 10px;
|
margin: 0 auto;
|
||||||
|
margin-top: 10px;
|
||||||
|
max-width: 40rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.signup,.signin{
|
.signup,
|
||||||
|
.signin {
|
||||||
margin: 10px;
|
margin: 10px;
|
||||||
}
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.peasAboutTitle {
|
||||||
|
position: absolute;
|
||||||
|
margin-left: 16px;
|
||||||
|
}
|
||||||
|
|
|
@ -1,27 +1,33 @@
|
||||||
'use client';
|
"use client";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import Cookies from "js-cookie";
|
||||||
|
import { messages } from "../messages";
|
||||||
|
import { MessageLangProps, SupportedLanguage, isValidLanguage } from "../types";
|
||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
interface MessageLangPropsWithStyle
|
||||||
import Cookies from 'js-cookie';
|
extends MessageLangProps,
|
||||||
import { messages } from '../messages';
|
React.HTMLAttributes<HTMLSpanElement> {}
|
||||||
import { MessageLangProps, SupportedLanguage, isValidLanguage } from '../types';
|
|
||||||
|
|
||||||
export function MessageLang({ text, data = [] }: MessageLangProps) {
|
export function MessageLang({
|
||||||
const [lang, setLang] = useState<SupportedLanguage>('ja');
|
text,
|
||||||
|
data = [],
|
||||||
|
...htmlProps
|
||||||
|
}: MessageLangPropsWithStyle) {
|
||||||
|
const [lang, setLang] = useState<SupportedLanguage>("ja");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const browserLang = navigator.language.split('-')[0];
|
const browserLang = navigator.language.split("-")[0];
|
||||||
const savedLang = Cookies.get('lang') || browserLang || 'ja';
|
const savedLang = Cookies.get("lang") || browserLang || "ja";
|
||||||
if (isValidLanguage(savedLang)) {
|
if (isValidLanguage(savedLang)) {
|
||||||
setLang(savedLang);
|
setLang(savedLang);
|
||||||
Cookies.set('lang', savedLang);
|
Cookies.set("lang", savedLang);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
let message = messages[lang][text];
|
let message = messages[lang][text];
|
||||||
|
data.forEach((value, index) => {
|
||||||
|
message = message.replace(new RegExp("\\{" + index + "\\}", "g"), value);
|
||||||
|
});
|
||||||
|
|
||||||
data.forEach((value, index) => {
|
return <span dangerouslySetInnerHTML={{ __html: message }} {...htmlProps} />;
|
||||||
message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value);
|
}
|
||||||
});
|
|
||||||
|
|
||||||
return <span dangerouslySetInnerHTML={{__html: message}} />;
|
|
||||||
}
|
|
||||||
|
|
|
@ -3,15 +3,15 @@ import { messages } from '../messages';
|
||||||
import { MessageLangProps, isValidLanguage } from '../types';
|
import { MessageLangProps, isValidLanguage } from '../types';
|
||||||
|
|
||||||
export async function MessageLang({ text, data = [] }: MessageLangProps) {
|
export async function MessageLang({ text, data = [] }: MessageLangProps) {
|
||||||
const cookieStore = await cookies();
|
const cookieStore = await cookies();
|
||||||
const savedLang = cookieStore.get('lang')?.value || 'ja';
|
const savedLang = cookieStore.get('lang')?.value || 'ja';
|
||||||
const lang = isValidLanguage(savedLang) ? savedLang : 'ja';
|
const lang = isValidLanguage(savedLang) ? savedLang : 'ja';
|
||||||
|
|
||||||
let message = messages[lang][text];
|
let message = messages[lang][text];
|
||||||
|
|
||||||
data.forEach((value, index) => {
|
data.forEach((value, index) => {
|
||||||
message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value);
|
message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return <span dangerouslySetInnerHTML={{__html: message}} />;
|
return <span dangerouslySetInnerHTML={{__html: message}} />;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
import { messages } from '../messages';
|
import { messages } from '../messages';
|
||||||
import { MessageLangProps, SupportedLanguage } from '../types';
|
import { MessageLangProps, SupportedLanguage } from '../types';
|
||||||
|
|
||||||
export function MessageLangVar({ text, data = [], variables = {} }: MessageLangProps & { variables?: Record<string, string>; }, langFunc?: string) {
|
export function MessageLangVar({
|
||||||
const lang: SupportedLanguage = (langFunc as SupportedLanguage) || 'ja';
|
text, data = [],
|
||||||
|
variables = {}
|
||||||
|
}: MessageLangProps & {
|
||||||
|
variables?: Record<string, string>;
|
||||||
|
}, langFunc?: string) {
|
||||||
|
const lang: SupportedLanguage = (langFunc as SupportedLanguage) || 'ja';
|
||||||
|
|
||||||
let message = messages[lang][text];
|
let message = messages[lang][text];
|
||||||
|
|
||||||
data.forEach((value, index) => {
|
data.forEach((value, index) => {
|
||||||
message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value);
|
message = message.replace(new RegExp('\\{' + index + '\\}', 'g'), value);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (variables) {
|
||||||
|
Object.keys(variables).forEach(key => {
|
||||||
|
message = message.replace(new RegExp('\\{' + key + '\\}', 'g'), variables[key]);
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (variables) {
|
return message;
|
||||||
Object.keys(variables).forEach(key => {
|
|
||||||
message = message.replace(new RegExp('\\{' + key + '\\}', 'g'), variables[key]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,95 @@
|
||||||
export const messages = {
|
export const messages = {
|
||||||
ja: {
|
ja: {
|
||||||
PeasAboutTitle: "Peasについて",
|
PeasAboutTitle: "Peasについて",
|
||||||
PeasAboutText: `
|
PeasAboutText: `
|
||||||
Peasはプロフィール共有を目的としたSNSです。<br />
|
Peasはプロフィール共有を目的としたSNSです。<br />
|
||||||
ユーザーのプロフィールを登録し、共有することが出来ます。<br />
|
ユーザーのプロフィールを登録し、共有することが出来ます。<br />
|
||||||
学校や職場で使用できるようなプロフィール欄も自由に作成でき、公開設定も変更できます。<br />
|
学校や職場で使用できるようなプロフィール欄も自由に作成でき、公開設定も変更できます。<br />
|
||||||
QRコードの印刷にも対応していて共有にも最適です。
|
QRコードの印刷にも対応していて共有にも最適です。
|
||||||
`,
|
`,
|
||||||
or: "または",
|
ServerAboutTitle: "このサーバーについて",
|
||||||
Start: "今すぐ始める",
|
or: "または",
|
||||||
Signin: "サインイン",
|
and: "と",
|
||||||
Signup: "サインアップ",
|
agree: "同意",
|
||||||
email: "メールアドレス",
|
Start: "今すぐ始める",
|
||||||
password: "パスワード",
|
Signin: "サインイン",
|
||||||
username: "ユーザー名",
|
Signup: "サインアップ",
|
||||||
idHelp: "@から始まる英数字のID(3文字以上)",
|
email: "メールアドレス",
|
||||||
mailHelp: "メールアドレスは受信できるものを使用してください",
|
password: "パスワード",
|
||||||
passwordHelp: "8文字以上のパスワードを使用してください"
|
username: "ユーザー名",
|
||||||
},
|
idHelp: "@から始まる英数字のID(3文字以上)",
|
||||||
en: {
|
mailHelp: "メールアドレスは受信できるものを使用してください",
|
||||||
PeasAboutTitle: "About Peas",
|
passwordHelp: "8文字以上のパスワードを使用してください",
|
||||||
PeasAboutText: `
|
signinError: "サインインに失敗しました",
|
||||||
Peas is a SNS designed for sharing profiles.<br />
|
mailVerify: "メール認証",
|
||||||
You can register and share your user profile.<br />
|
code: "コード",
|
||||||
You can freely create profile sections suitable for use at school or work, and also change the privacy settings.<br />
|
submit: "送信",
|
||||||
It also supports printing QR codes, making sharing easy.
|
codeCharacterCount: "コードは6桁で入力してください",
|
||||||
`,
|
emailVerificationSuccess: `
|
||||||
or: "or",
|
メール認証に成功しました。
|
||||||
Start: "Get Started Now",
|
ログインしてください。
|
||||||
Signin: "Sign In",
|
`,
|
||||||
Signup: "Sign Up",
|
connectionError: "通信エラー",
|
||||||
email: "Email",
|
accountCreateSuccess: `
|
||||||
password: "Password",
|
アカウントの作成に成功しました。
|
||||||
username: "Username",
|
ご登録いただいたメールアドレスに確認用メールを送信しました。
|
||||||
idHelp: "ID starting with @ and consisting of English numbers (at least 3 characters)",
|
確認メールをクリックしてアカウントを有効化してください。
|
||||||
mailHelp: "Use an email address that can be received",
|
`,
|
||||||
passwordHelp: "Use a password of at least 8 characters"
|
sameUsername: "同じユーザー名が既に使用されています",
|
||||||
}
|
termsAgreement: "利用規約とプライバシーポリシーに同意してください",
|
||||||
};
|
bodyRequired: "フォーム内容が送信されていません",
|
||||||
|
passwordCharacterLimit: "パスワードは8文字以上にしてください",
|
||||||
|
usernameRestriction: "ユーザー名は3文字以上の英数字のIDにしてください",
|
||||||
|
userCreationFailed: "ユーザーの作成に失敗しました",
|
||||||
|
terms: "利用規約",
|
||||||
|
privacypolicy: "プライバシーポリシー",
|
||||||
|
},
|
||||||
|
en: {
|
||||||
|
PeasAboutTitle: "About Peas",
|
||||||
|
PeasAboutText: `
|
||||||
|
Peas is a SNS designed for sharing profiles.<br />
|
||||||
|
You can register and share your user profile.<br />
|
||||||
|
You can freely create profile sections suitable for use at school or work, and also change the privacy settings.<br />
|
||||||
|
It also supports printing QR codes, making sharing easy.
|
||||||
|
`,
|
||||||
|
ServerAboutTitle: "About this server",
|
||||||
|
or: "or",
|
||||||
|
and: "and",
|
||||||
|
agree: "Agree",
|
||||||
|
Start: "Get started now",
|
||||||
|
Signin: "Sign In",
|
||||||
|
Signup: "Sign Up",
|
||||||
|
email: "Email",
|
||||||
|
password: "Password",
|
||||||
|
username: "Username",
|
||||||
|
idHelp:
|
||||||
|
"ID starting with @ and consisting of English numbers (at least 3 characters)",
|
||||||
|
mailHelp: "Use an email address that can be received",
|
||||||
|
passwordHelp: "Use a password of at least 8 characters",
|
||||||
|
signinError: "Sign in failed",
|
||||||
|
mailVerify: "Mail Verify",
|
||||||
|
code: "Code",
|
||||||
|
submit: "Submit",
|
||||||
|
codeCharacterCount: "Please enter a 6-digit code",
|
||||||
|
emailVerificationSuccess: `
|
||||||
|
Email verification successful.
|
||||||
|
Please log in.
|
||||||
|
`,
|
||||||
|
connectionError: "Connection Error",
|
||||||
|
accountCreateSuccess: `
|
||||||
|
Your account has been successfully created.
|
||||||
|
We have sent a confirmation email to the email address you registered.
|
||||||
|
Please click the confirmation email to activate your account.
|
||||||
|
`,
|
||||||
|
sameUsername: "That username is already taken",
|
||||||
|
termsAgreement: "Please agree to the Terms of Service and Privacy Policy",
|
||||||
|
bodyRequired: "Form contents have not been submitted",
|
||||||
|
passwordCharacterLimit:
|
||||||
|
"Please make your password at least 8 characters long.",
|
||||||
|
usernameRestriction:
|
||||||
|
"Please make your username an alphanumeric ID with at least 3 characters.",
|
||||||
|
userCreationFailed: "Failed to create the user.",
|
||||||
|
terms: "Terms of Service",
|
||||||
|
privacypolicy: "Privacy Policy",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
import mysql from 'mysql2/promise';
|
import mysql from "mysql2/promise";
|
||||||
|
import PeasConfig from "@/../peas.config";
|
||||||
|
|
||||||
const pool = mysql.createPool({
|
const pool = mysql.createPool({
|
||||||
host: process.env.DB_HOST,
|
host: PeasConfig.databaseHost,
|
||||||
port: Number(process.env.DB_PORT),
|
port: PeasConfig.databasePort,
|
||||||
user: process.env.DB_USER,
|
user: PeasConfig.databaseUser,
|
||||||
password: process.env.DB_PASSWORD,
|
password: PeasConfig.databasePassword,
|
||||||
database: process.env.DB_DATABASE,
|
database: PeasConfig.databaseDB,
|
||||||
waitForConnections: true,
|
waitForConnections: true,
|
||||||
connectionLimit: 10,
|
connectionLimit: 10,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default pool;
|
export default pool;
|
||||||
|
|
|
@ -1,60 +1,51 @@
|
||||||
import * as nodemailer from 'nodemailer';
|
import * as nodemailer from "nodemailer";
|
||||||
import * as dotenv from 'dotenv';
|
import type SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||||
|
import PeasConfig from "@/../peas.config";
|
||||||
dotenv.config();
|
|
||||||
|
|
||||||
export interface EmailMessage {
|
export interface EmailMessage {
|
||||||
to: string | string[],
|
to: string | string[];
|
||||||
subject: string,
|
subject: string;
|
||||||
text?: string,
|
text?: string;
|
||||||
html?: string,
|
html?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createTransporter() {
|
export async function createTransporter() {
|
||||||
// 環境変数の値をログ出力して確認
|
const transporter = nodemailer.createTransport({
|
||||||
console.log('SMTP設定:', {
|
host: PeasConfig.smtpHost,
|
||||||
host: process.env.SMTP_HOST,
|
port: PeasConfig.smtpPort,
|
||||||
port: process.env.SMTP_PORT,
|
secure: PeasConfig.smtpSecure,
|
||||||
secure: process.env.SMTP_SECURE,
|
auth: {
|
||||||
user: process.env.SMTP_USER
|
user: PeasConfig.smtpUser,
|
||||||
});
|
pass: PeasConfig.smtpPassword,
|
||||||
|
},
|
||||||
|
debug: process.env.DEBUG === "true",
|
||||||
|
} as SMTPTransport.Options);
|
||||||
|
|
||||||
const transporter = nodemailer.createTransport({
|
// 接続テスト
|
||||||
host: process.env.SMTP_HOST,
|
try {
|
||||||
port: Number(process.env.SMTP_PORT),
|
await transporter.verify();
|
||||||
secure: process.env.SMTP_SECURE === 'true',
|
console.log("SMTPサーバーに接続できました");
|
||||||
auth: {
|
} catch (error) {
|
||||||
user: process.env.SMTP_USER,
|
console.error("SMTP接続テストに失敗:", error);
|
||||||
pass: process.env.SMTP_PASSWORD,
|
throw error;
|
||||||
},
|
}
|
||||||
debug: process.env.DEBUG === 'true',
|
|
||||||
});
|
|
||||||
|
|
||||||
// 接続テスト
|
return transporter;
|
||||||
try {
|
|
||||||
await transporter.verify();
|
|
||||||
console.log('SMTPサーバーに接続できました');
|
|
||||||
} catch (error) {
|
|
||||||
console.error('SMTP接続テストに失敗:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return transporter;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sendMail(message: EmailMessage): Promise<void> {
|
export async function sendMail(message: EmailMessage): Promise<void> {
|
||||||
try{
|
try {
|
||||||
const transporter = await createTransporter();
|
const transporter = await createTransporter();
|
||||||
await transporter.sendMail({
|
await transporter.sendMail({
|
||||||
from: process.env.SMTP_USER,
|
from: PeasConfig.smtpUser,
|
||||||
to: Array.isArray(message.to) ? message.to.join(',') : message.to,
|
to: Array.isArray(message.to) ? message.to.join(",") : message.to,
|
||||||
subject: message.subject,
|
subject: message.subject,
|
||||||
text: message.text,
|
text: message.text,
|
||||||
html: message.html
|
html: message.html,
|
||||||
});
|
});
|
||||||
console.log('メール送信成功');
|
console.log("メール送信成功");
|
||||||
}catch (error){
|
} catch (error) {
|
||||||
console.error('メール送信に失敗しました:', error);
|
console.error("メール送信に失敗しました:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
interface SSLConfig {
|
||||||
|
ssl: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PeasConfigType {
|
||||||
|
// Server Config
|
||||||
|
serverName: string;
|
||||||
|
serverDescription: string;
|
||||||
|
serverHost: string;
|
||||||
|
// SSL Config
|
||||||
|
ssl: SSLConfig;
|
||||||
|
// SMTP Config
|
||||||
|
smtpHost: string;
|
||||||
|
smtpPort: number;
|
||||||
|
smtpSecure: boolean;
|
||||||
|
smtpUser: string;
|
||||||
|
smtpPassword: string;
|
||||||
|
// Database Config
|
||||||
|
databaseHost: string;
|
||||||
|
databasePort: number;
|
||||||
|
databaseUser: string;
|
||||||
|
databasePassword: string;
|
||||||
|
databaseDB: string;
|
||||||
|
}
|
Loading…
Reference in New Issue