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/releases
|
||||
!.yarn/versions
|
||||
/package-lock.json
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
@ -31,8 +32,7 @@ yarn-error.log*
|
|||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env
|
||||
.env.local
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
@ -45,3 +45,6 @@ next-env.d.ts
|
|||
/.vscode/
|
||||
/.trae/
|
||||
/.vs/
|
||||
|
||||
# config
|
||||
/peas.config.ts
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
<center>
|
||||
|
||||
# Peas
|
||||
|
||||
<img width="140" style="border-radius: 100%" src="./src/asset/peas.svg">
|
||||
<img width="140" src="./src/asset/peas.svg">
|
||||
|
||||
## Technologys
|
||||
|
||||
<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/js.svg">
|
||||
|
@ -20,11 +19,12 @@
|
|||
<img width="30" src="./src/asset/simpleicons/svg.svg">
|
||||
|
||||
## About Peas
|
||||
|
||||
Peas is a SNS designed for sharing profiles.
|
||||
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.
|
||||
|
||||
## LICENSE
|
||||
|
||||
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",
|
||||
"version": "1.0",
|
||||
"version": "nonerelease",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "peas",
|
||||
"version": "1.0",
|
||||
"license": "ISC",
|
||||
"version": "nonerelease",
|
||||
"license": "AGPL-3.0-later",
|
||||
"dependencies": {
|
||||
"@iconify/react": "^6.0.0",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
|
@ -19,7 +20,7 @@
|
|||
"dotenv": "^16.5.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"mysql2": "^3.14.1",
|
||||
"next": "^15.3.2",
|
||||
"next": "^15.3.4",
|
||||
"nodemailer": "^7.0.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
@ -289,6 +290,27 @@
|
|||
"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": {
|
||||
"version": "0.34.2",
|
||||
"resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.2.tgz",
|
||||
|
@ -724,9 +746,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/env": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.2.tgz",
|
||||
"integrity": "sha512-xURk++7P7qR9JG1jJtLzPzf0qEvqCN0A/T3DXf8IPMKo9/6FfjxtEffRJIIew/bIL4T3C2jLLqBor8B/zVlx6g==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/env/-/env-15.3.4.tgz",
|
||||
"integrity": "sha512-ZkdYzBseS6UjYzz6ylVKPOK+//zLWvD6Ta+vpoye8cW11AjiQjGYVibF0xuvT4L0iJfAPfZLFidaEzAOywyOAQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@next/eslint-plugin-next": {
|
||||
|
@ -740,9 +762,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-arm64": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.2.tgz",
|
||||
"integrity": "sha512-2DR6kY/OGcokbnCsjHpNeQblqCZ85/1j6njYSkzRdpLn5At7OkSdmk7WyAmB9G0k25+VgqVZ/u356OSoQZ3z0g==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.3.4.tgz",
|
||||
"integrity": "sha512-z0qIYTONmPRbwHWvpyrFXJd5F9YWLCsw3Sjrzj2ZvMYy9NPQMPZ1NjOJh4ojr4oQzcGYwgJKfidzehaNa1BpEg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -756,9 +778,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-darwin-x64": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.2.tgz",
|
||||
"integrity": "sha512-ro/fdqaZWL6k1S/5CLv1I0DaZfDVJkWNaUU3un8Lg6m0YENWlDulmIWzV96Iou2wEYyEsZq51mwV8+XQXqMp3w==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.3.4.tgz",
|
||||
"integrity": "sha512-Z0FYJM8lritw5Wq+vpHYuCIzIlEMjewG2aRkc3Hi2rcbULknYL/xqfpBL23jQnCSrDUGAo/AEv0Z+s2bff9Zkw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -772,9 +794,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-gnu": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.2.tgz",
|
||||
"integrity": "sha512-covwwtZYhlbRWK2HlYX9835qXum4xYZ3E2Mra1mdQ+0ICGoMiw1+nVAn4d9Bo7R3JqSmK1grMq/va+0cdh7bJA==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.3.4.tgz",
|
||||
"integrity": "sha512-l8ZQOCCg7adwmsnFm8m5q9eIPAHdaB2F3cxhufYtVo84pymwKuWfpYTKcUiFcutJdp9xGHC+F1Uq3xnFU1B/7g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -788,9 +810,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-arm64-musl": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.2.tgz",
|
||||
"integrity": "sha512-KQkMEillvlW5Qk5mtGA/3Yz0/tzpNlSw6/3/ttsV1lNtMuOHcGii3zVeXZyi4EJmmLDKYcTcByV2wVsOhDt/zg==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.3.4.tgz",
|
||||
"integrity": "sha512-wFyZ7X470YJQtpKot4xCY3gpdn8lE9nTlldG07/kJYexCUpX1piX+MBfZdvulo+t1yADFVEuzFfVHfklfEx8kw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -804,9 +826,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-gnu": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.2.tgz",
|
||||
"integrity": "sha512-uRBo6THWei0chz+Y5j37qzx+BtoDRFIkDzZjlpCItBRXyMPIg079eIkOCl3aqr2tkxL4HFyJ4GHDes7W8HuAUg==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.3.4.tgz",
|
||||
"integrity": "sha512-gEbH9rv9o7I12qPyvZNVTyP/PWKqOp8clvnoYZQiX800KkqsaJZuOXkWgMa7ANCCh/oEN2ZQheh3yH8/kWPSEg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -820,9 +842,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-linux-x64-musl": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.2.tgz",
|
||||
"integrity": "sha512-+uxFlPuCNx/T9PdMClOqeE8USKzj8tVz37KflT3Kdbx/LOlZBRI2yxuIcmx1mPNK8DwSOMNCr4ureSet7eyC0w==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.3.4.tgz",
|
||||
"integrity": "sha512-Cf8sr0ufuC/nu/yQ76AnarbSAXcwG/wj+1xFPNbyNo8ltA6kw5d5YqO8kQuwVIxk13SBdtgXrNyom3ZosHAy4A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -836,9 +858,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-arm64-msvc": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.2.tgz",
|
||||
"integrity": "sha512-LLTKmaI5cfD8dVzh5Vt7+OMo+AIOClEdIU/TSKbXXT2iScUTSxOGoBhfuv+FU8R9MLmrkIL1e2fBMkEEjYAtPQ==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.3.4.tgz",
|
||||
"integrity": "sha512-ay5+qADDN3rwRbRpEhTOreOn1OyJIXS60tg9WMYTWCy3fB6rGoyjLVxc4dR9PYjEdR2iDYsaF5h03NA+XuYPQQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
|
@ -852,9 +874,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@next/swc-win32-x64-msvc": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.2.tgz",
|
||||
"integrity": "sha512-aW5B8wOPioJ4mBdMDXkt5f3j8pUr9W8AnlX0Df35uRWNT1Y6RIybxjnSUe+PhM+M1bwgyY8PHLmXZC6zT1o5tA==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.3.4.tgz",
|
||||
"integrity": "sha512-4kDt31Bc9DGyYs41FTL1/kNpDeHyha2TC0j5sRRoKCyrhNcfZ/nRQkAUlF27mETwm8QyHqIjHJitfcza2Iykfg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
|
@ -4349,12 +4371,12 @@
|
|||
"license": "MIT"
|
||||
},
|
||||
"node_modules/next": {
|
||||
"version": "15.3.2",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.2.tgz",
|
||||
"integrity": "sha512-CA3BatMyHkxZ48sgOCLdVHjFU36N7TF1HhqAHLFOkV6buwZnvMI84Cug8xD56B9mCuKrqXnLn94417GrZ/jjCQ==",
|
||||
"version": "15.3.4",
|
||||
"resolved": "https://registry.npmjs.org/next/-/next-15.3.4.tgz",
|
||||
"integrity": "sha512-mHKd50C+mCjam/gcnwqL1T1vPx/XQNFlXqFIVdgQdVAFY9iIQtY0IfaVflEYzKiqjeA7B0cYYMaCrmAYFjs4rA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@next/env": "15.3.2",
|
||||
"@next/env": "15.3.4",
|
||||
"@swc/counter": "0.1.3",
|
||||
"@swc/helpers": "0.5.15",
|
||||
"busboy": "1.6.0",
|
||||
|
@ -4369,14 +4391,14 @@
|
|||
"node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@next/swc-darwin-arm64": "15.3.2",
|
||||
"@next/swc-darwin-x64": "15.3.2",
|
||||
"@next/swc-linux-arm64-gnu": "15.3.2",
|
||||
"@next/swc-linux-arm64-musl": "15.3.2",
|
||||
"@next/swc-linux-x64-gnu": "15.3.2",
|
||||
"@next/swc-linux-x64-musl": "15.3.2",
|
||||
"@next/swc-win32-arm64-msvc": "15.3.2",
|
||||
"@next/swc-win32-x64-msvc": "15.3.2",
|
||||
"@next/swc-darwin-arm64": "15.3.4",
|
||||
"@next/swc-darwin-x64": "15.3.4",
|
||||
"@next/swc-linux-arm64-gnu": "15.3.4",
|
||||
"@next/swc-linux-arm64-musl": "15.3.4",
|
||||
"@next/swc-linux-x64-gnu": "15.3.4",
|
||||
"@next/swc-linux-x64-musl": "15.3.4",
|
||||
"@next/swc-win32-arm64-msvc": "15.3.4",
|
||||
"@next/swc-win32-x64-msvc": "15.3.4",
|
||||
"sharp": "^0.34.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"type": "module",
|
||||
"name": "peas",
|
||||
"version": "1.0",
|
||||
"version": "nonerelease",
|
||||
"license": "AGPL-3.0-later",
|
||||
"author": {
|
||||
"name": "Last2014",
|
||||
|
@ -15,6 +15,7 @@
|
|||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iconify/react": "^6.0.0",
|
||||
"@types/dotenv": "^6.1.1",
|
||||
"@types/eslint": "^9.6.1",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
|
@ -25,7 +26,7 @@
|
|||
"dotenv": "^16.5.0",
|
||||
"js-cookie": "^3.0.5",
|
||||
"mysql2": "^3.14.1",
|
||||
"next": "^15.3.2",
|
||||
"next": "^15.3.4",
|
||||
"nodemailer": "^7.0.3",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
|
|
|
@ -1,27 +1,33 @@
|
|||
import pool from '@/lib/database';
|
||||
import type { RowDataPacket } from 'mysql2';
|
||||
import pool from "@/lib/database";
|
||||
import type { RowDataPacket } from "mysql2";
|
||||
|
||||
import { NextResponse, NextRequest } from 'next/server';
|
||||
import { NextResponse, NextRequest } from "next/server";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
const body = await request.json();
|
||||
const { code } = body;
|
||||
|
||||
const [rows] = await pool.query<RowDataPacket[]>(
|
||||
'SELECT * FROM mailverifiedcode WHERE code = ?',
|
||||
[code]
|
||||
"SELECT * FROM mailverifiedcode WHERE code = ?",
|
||||
[code],
|
||||
);
|
||||
|
||||
if (rows.length === 0) {
|
||||
return NextResponse.json({status: "error", message: 'Code not found' }, { status: 401 });
|
||||
}else{
|
||||
if(rows[0].code === code){
|
||||
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]
|
||||
"UPDATE `users` SET `mailverified` = 1 WHERE `users`.`id` = ?",
|
||||
[rows[0].user],
|
||||
);
|
||||
|
||||
return NextResponse.json({status: "success", message: 'Code verified' }, { status: 200 });
|
||||
return NextResponse.json(
|
||||
{ status: "success", message: "Code verified" },
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
import pool from '@/lib/database';
|
||||
import type { RowDataPacket } from 'mysql2';
|
||||
import pool from "@/lib/database";
|
||||
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) {
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
const code = Math.floor(100000 + Math.random() * 900000).toString();
|
||||
const [existingCodes] = await pool.execute<RowDataPacket[]>(
|
||||
"SELECT * FROM mailverifiedcode WHERE code = ?",[code]
|
||||
"SELECT * FROM mailverifiedcode WHERE code = ?",
|
||||
[code],
|
||||
);
|
||||
if (existingCodes.length === 0) {
|
||||
return code;
|
||||
|
@ -28,44 +30,49 @@ export async function POST(request: NextRequest) {
|
|||
|
||||
// コードが存在する場合
|
||||
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){
|
||||
if (existingCodes.length !== 0) {
|
||||
[row] = await pool.execute<RowDataPacket[]>(
|
||||
"UPDATE `mailverifiedcode` SET `code` = ?, `time` = current_timestamp(3) WHERE `mailverifiedcode`.`user` = ?",
|
||||
[code, user]
|
||||
)
|
||||
}else{
|
||||
[code, user],
|
||||
);
|
||||
} else {
|
||||
// コードが存在しない場合
|
||||
[row] = await pool.execute<RowDataPacket[]>(
|
||||
"INSERT INTO `mailverifiedcode` (`code`, `user`, `email`, `time`, `num`) VALUES (?, ?, ?, current_timestamp(3), NULL);",
|
||||
[code, user, email]
|
||||
)
|
||||
[code, user, email],
|
||||
);
|
||||
}
|
||||
|
||||
// メール送信
|
||||
if ((row as RowDataPacket).affectedRows === 1) {
|
||||
await sendMail({
|
||||
to: email,
|
||||
subject: "【Peas】メール認証",
|
||||
text: `${user}さんこんにちは。\n
|
||||
Peasではメール認証が必要となっています。\n
|
||||
メール認証をすることでPeasの機能を利用することができます。\n
|
||||
${request.nextUrl.origin}/mailverify?code=${code} にアクセスしてください。`
|
||||
})
|
||||
subject: `【${PeasConfig.serverName}】メール認証`,
|
||||
text: `
|
||||
${user}さんこんにちは。\n
|
||||
${PeasConfig.serverName}ではメール認証が必要となっています。\n
|
||||
メール認証をすることで${PeasConfig.serverName}の機能を利用することができます。\n
|
||||
${request.nextUrl.origin}/mailverify?code=${code} にアクセスしてください。`,
|
||||
});
|
||||
|
||||
return NextResponse.json({
|
||||
status: "success",
|
||||
code: code,
|
||||
})
|
||||
}else{
|
||||
});
|
||||
} else {
|
||||
// コード保存失敗
|
||||
return NextResponse.json({
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "error",
|
||||
error: "Failed to Save Code"
|
||||
}, { status: 400 });
|
||||
error: "Failed to Save Code",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,47 +1,57 @@
|
|||
import pool from '@/lib/database';
|
||||
import type { RowDataPacket } from 'mysql2';
|
||||
import pool from "@/lib/database";
|
||||
import type { RowDataPacket } from "mysql2";
|
||||
|
||||
import bcrypt from 'bcrypt';
|
||||
import bcrypt from "bcrypt";
|
||||
|
||||
import { NextResponse, NextRequest } from 'next/server';
|
||||
import { cookies } from 'next/headers'
|
||||
import { NextResponse, NextRequest } from "next/server";
|
||||
import { cookies } from "next/headers";
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
// body取得
|
||||
// body取得
|
||||
const body = await request.json();
|
||||
const { email, password } = body;
|
||||
|
||||
// ユーザー取得
|
||||
// ユーザー取得
|
||||
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({
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "error",
|
||||
error: "User not found"
|
||||
}, { status: 404 });
|
||||
error: "User not found",
|
||||
},
|
||||
{ status: 404 },
|
||||
);
|
||||
}
|
||||
|
||||
const user = existingUsers[0];
|
||||
const passwordMatch = await bcrypt.compare(password, user.password);
|
||||
|
||||
// パスワード確認
|
||||
// パスワード確認
|
||||
if (!passwordMatch) {
|
||||
return NextResponse.json({
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "error",
|
||||
error: "Incorrect password"
|
||||
}, { status: 401 });
|
||||
error: "Incorrect password",
|
||||
},
|
||||
{ status: 401 },
|
||||
);
|
||||
} else {
|
||||
// 成功
|
||||
// 成功
|
||||
const sessionCookie = await cookies();
|
||||
sessionCookie.set('user', user.id);
|
||||
sessionCookie.set('password', password);
|
||||
sessionCookie.set("user", user.id);
|
||||
sessionCookie.set("password", password);
|
||||
|
||||
return NextResponse.json({
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "success",
|
||||
message: "Login successful"
|
||||
}, { status: 200 });
|
||||
message: "Login successful",
|
||||
},
|
||||
{ status: 200 },
|
||||
);
|
||||
}
|
||||
}
|
|
@ -1,87 +1,114 @@
|
|||
import pool from '@/lib/database';
|
||||
import type { RowDataPacket } from 'mysql2';
|
||||
import pool from "@/lib/database";
|
||||
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) {
|
||||
const body = await request.json();
|
||||
const { email, password, id, rule } = body;
|
||||
|
||||
try{
|
||||
if(!rule || rule === false){
|
||||
return NextResponse.json({
|
||||
try {
|
||||
if (!rule || rule === false) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "error",
|
||||
error: "Please read and accept the terms and conditions"
|
||||
}, { status: 400 });
|
||||
error: "Please read and accept the terms and conditions",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
}catch(e){
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
// bodyが空になっていないか
|
||||
if(!email || !password || !id || !rule){
|
||||
return NextResponse.json({
|
||||
if (!email || !password || !id || !rule) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "error",
|
||||
error: "Body Required"
|
||||
}, { status: 400 });
|
||||
error: "Body Required",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// パスワードが八文字以上か
|
||||
if(password.length < 8){
|
||||
return NextResponse.json({
|
||||
// パスワードが8文字以上か
|
||||
if (password.length < 8) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "error",
|
||||
error: "Password must be at least 8 characters long"
|
||||
}, { status: 400 });
|
||||
error: "Password must be at least 8 characters long",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
// ユーザー名が英数字3文字以上か
|
||||
if(!/^[a-zA-Z0-9]{3,}$/.test(id)){
|
||||
return NextResponse.json({
|
||||
if (!/^[a-zA-Z0-9]{3,}$/.test(id)) {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "error",
|
||||
error: "Username must be at least 3 alphanumeric characters"
|
||||
}, { status: 400 });
|
||||
error: "Username must be at least 3 alphanumeric characters",
|
||||
},
|
||||
{ status: 400 },
|
||||
);
|
||||
}
|
||||
|
||||
const [existingUsers] = await pool.execute<RowDataPacket[]>(
|
||||
"SELECT * FROM users WHERE id = ?",[id]
|
||||
"SELECT * FROM users WHERE id = ?",
|
||||
[id],
|
||||
);
|
||||
|
||||
if(existingUsers.length === 0){
|
||||
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]
|
||||
[id, passwordHash, email],
|
||||
);
|
||||
|
||||
if ((result as RowDataPacket).affectedRows === 1) {
|
||||
await fetch(request.nextUrl.protocol + request.nextUrl.host + "/api/mailverified/send", {
|
||||
await fetch(
|
||||
request.nextUrl.protocol +
|
||||
request.nextUrl.host +
|
||||
"/api/mailverified/send",
|
||||
{
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email: email,
|
||||
user: id,
|
||||
})
|
||||
});
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
return NextResponse.json({
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "success",
|
||||
message: "User created successfully"
|
||||
}, { status: 201 });
|
||||
message: "User created successfully",
|
||||
},
|
||||
{ status: 201 },
|
||||
);
|
||||
} else {
|
||||
return NextResponse.json({
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "error",
|
||||
error: "Failed to create user"
|
||||
}, { status: 500 });
|
||||
error: "Failed to create user",
|
||||
},
|
||||
{ status: 500 },
|
||||
);
|
||||
}
|
||||
}else{
|
||||
return NextResponse.json({
|
||||
} else {
|
||||
return NextResponse.json(
|
||||
{
|
||||
status: "error",
|
||||
error: "User already exists"
|
||||
}, { status: 400 });
|
||||
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 "./global.css";
|
||||
|
||||
import PeasConfig from "@/../peas.config";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Peas",
|
||||
description: "Peas SNS",
|
||||
title: PeasConfig.serverName,
|
||||
description: PeasConfig.serverDescription,
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
@ -14,13 +16,7 @@ export default function RootLayout({
|
|||
}>) {
|
||||
return (
|
||||
<html>
|
||||
<body>
|
||||
<header className="header">
|
||||
<span className="title">Peas</span>
|
||||
</header>
|
||||
|
||||
{children}
|
||||
</body>
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,28 +1,38 @@
|
|||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
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() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const URLCode = searchParams.get("code");
|
||||
|
||||
let code: string = "";
|
||||
|
||||
if(URLCode){
|
||||
if (URLCode !== null) {
|
||||
code = URLCode;
|
||||
}
|
||||
|
||||
const verify = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
if(code === ""){
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
if (code === "") {
|
||||
code = e.currentTarget.code.value;
|
||||
}
|
||||
|
||||
if (code.length !== 6) {
|
||||
alert("コードは6桁で入力してください。");
|
||||
alert(MessageLangVar({text: "codeCharacterCount"}));
|
||||
return;
|
||||
}
|
||||
|
||||
const res = await fetch(`${location.origin}/api/mailverify/check`, {
|
||||
try {
|
||||
const res = await fetch(`${location.origin}/api/mailverified/check`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
|
@ -31,23 +41,50 @@ export default function MailVerify() {
|
|||
code,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.text();
|
||||
const result = JSON.parse(data);
|
||||
|
||||
if (result.status === "success") {
|
||||
alert("メール認証に成功しました。\nログインしてください。");
|
||||
window.location.href = "/login";
|
||||
alert(MessageLangVar({ text: "emailVerificationSuccess" }));
|
||||
window.location.href = "/signin";
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1 className="title">メール認証</h1>
|
||||
<h1 className="title has-text-centered"><MessageLang text="mailVerify" /></h1>
|
||||
|
||||
<form onSubmit={verify}>
|
||||
<label className="label">コード:</label>
|
||||
<input type="text" name="code" defaultValue={code} className="input" placeholder="123456" style={{width: "10rem"}} maxLength={6} />
|
||||
{loading && (
|
||||
<div style={{ marginBottom: "1rem" }}>
|
||||
<Icon icon="svg-spinners:6-dots-scale" width={32} height={32} />
|
||||
</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";
|
||||
|
||||
import "./style.css"
|
||||
import "./style.css";
|
||||
|
||||
import Link from "next/link";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
import PeasConfig from "@/../peas.config";
|
||||
import { MessageLang } from "@/lang/component/client";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<>
|
||||
<h1 className="has-text-centered title">{PeasConfig.serverName}</h1>
|
||||
|
||||
<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">
|
||||
<MessageLang text="PeasAboutText" />
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style={{width: "15rem"}}>
|
||||
<Link href="/signup" 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 className="has-text-centered">
|
||||
<Link
|
||||
href="/signup"
|
||||
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>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -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,13 +1,20 @@
|
|||
"use client";
|
||||
|
||||
import "./style.css";
|
||||
import { useState } from "react";
|
||||
|
||||
import { MessageLang } from "@/lang/component/client";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
export default function SignIn() {
|
||||
const [error, setError] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const FormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/signin", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
@ -23,24 +30,64 @@ export default function SignIn() {
|
|||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
useState(() => {
|
||||
const locationURL = new URL(window.location.href);
|
||||
if (locationURL.searchParams.get("error")) {
|
||||
setError(true);
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<form onSubmit={FormSubmit}>
|
||||
<h1 className="title"><MessageLang text="Signin" /></h1>
|
||||
<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>
|
||||
)}
|
||||
|
||||
<label className="label"><MessageLang text="email" /></label>
|
||||
<h1 className="title">
|
||||
<MessageLang text="Signin" />
|
||||
</h1>
|
||||
|
||||
{loading && (
|
||||
<div style={{ marginBottom: "1rem" }}>
|
||||
<Icon icon="svg-spinners:6-dots-scale" width={32} height={32} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<label className="label">
|
||||
<MessageLang text="email" />
|
||||
</label>
|
||||
<input type="email" name="email" className="input" required />
|
||||
|
||||
<br />
|
||||
|
||||
<label className="label"><MessageLang text="password" /></label>
|
||||
<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>
|
||||
<button type="submit" className="button">
|
||||
<MessageLang text="Signin" />
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
);
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
.input{
|
||||
.input {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.button{
|
||||
.button {
|
||||
margin-top: 5px;
|
||||
}
|
|
@ -1,15 +1,22 @@
|
|||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { useState } from "react";
|
||||
import { Icon } from "@iconify/react";
|
||||
|
||||
import "./style.css";
|
||||
|
||||
import { MessageLang } from "@/lang/component/client";
|
||||
import { MessageLangVar } from "@/lang/component/variable";
|
||||
|
||||
export default function SignUp() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const FormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
const res = await fetch("/api/signup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
@ -22,56 +29,146 @@ export default function SignUp() {
|
|||
rule: e.currentTarget.ruleCheck.checked,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await res.text();
|
||||
const json = JSON.parse(data);
|
||||
|
||||
if (json.status === "success") {
|
||||
alert("アカウントの作成に成功しました。\nご登録いただいたメールアドレスに確認用メールを送信しました。\n確認メールをクリックしてアカウントを有効化してください。")
|
||||
alert(MessageLangVar({ text: "accountCreateSuccess" }));
|
||||
window.location.href = "/";
|
||||
}else{
|
||||
if(json.error === "User already exists"){
|
||||
alert("同じユーザー名が既に使用されています")
|
||||
}else if(json.error === "Please read and accept the terms and conditions"){
|
||||
alert("利用規約とプライバシーポリシーに同意してください")
|
||||
}else if(json.error === "Body Required"){
|
||||
alert("Bodyは必須です")
|
||||
}else if(json.error === "Password must be at least 8 characters long"){
|
||||
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"){
|
||||
alert("ユーザーの作成に失敗しました")
|
||||
} 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 (
|
||||
<form onSubmit={FormSubmit}>
|
||||
<h1 className="title"><MessageLang text="Signup" /></h1>
|
||||
<div
|
||||
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>
|
||||
<div className="field" style={{ position: 'relative' }}>
|
||||
<span className="input-out">@</span>
|
||||
<input type="text" name="username" placeholder="username" className="input" style={{ paddingLeft: '25px' }} required />
|
||||
{loading && (
|
||||
<div style={{ marginBottom: "1rem" }}>
|
||||
<Icon icon="svg-spinners:6-dots-scale" width={32} height={32} />
|
||||
</div>
|
||||
<p className="help"><MessageLang text="idHelp" /></p>
|
||||
)}
|
||||
|
||||
<label className="label"><MessageLang text="email" /></label>
|
||||
<input className="input" type="email" name="email" placeholder="info@example.com" required />
|
||||
<p className="help"><MessageLang text="mailHelp" /></p>
|
||||
<label className="label">
|
||||
<MessageLang text="username" />
|
||||
</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>
|
||||
<input type="password" name="password" className="input" placeholder="password" required />
|
||||
<p className="help"><MessageLang text="passwordHelp" /></p>
|
||||
<label className="label">
|
||||
<MessageLang text="email" />
|
||||
</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">
|
||||
<input type="checkbox" name="ruleCheck" required />
|
||||
<Link href="/terms">利用規約</Link>と<Link href="/privacypolicy">プライバシーポリシー</Link>に同意する
|
||||
<span style={{ margin: "5px" }} />
|
||||
|
||||
<label className="label checkbox" style={{ justifyContent: "center" }}>
|
||||
<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">サインアップ</button>
|
||||
<button
|
||||
type="submit"
|
||||
className="button is-primary"
|
||||
style={{ width: "100%" }}
|
||||
id="signup"
|
||||
disabled={loading}
|
||||
>
|
||||
<MessageLang text="Signup" />
|
||||
</button>
|
||||
</form>
|
||||
)
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
.input{
|
||||
.input {
|
||||
width: 20rem;
|
||||
}
|
||||
|
||||
.button{
|
||||
.button {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.input-out{
|
||||
.input-out {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
|
@ -18,6 +18,6 @@
|
|||
font-weight: normal;
|
||||
}
|
||||
|
||||
.inputout-email{
|
||||
.inputout-email {
|
||||
z-index: 1;
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import type { MetadataRoute } from "next";
|
||||
|
||||
export default function sitemap(): MetadataRoute.Sitemap {
|
||||
return [];
|
||||
}
|
|
@ -1,7 +1,16 @@
|
|||
.about{
|
||||
margin: 10px;
|
||||
.about {
|
||||
margin: 0 auto;
|
||||
margin-top: 10px;
|
||||
max-width: 40rem;
|
||||
}
|
||||
|
||||
.signup,.signin{
|
||||
.signup,
|
||||
.signin {
|
||||
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';
|
||||
import Cookies from 'js-cookie';
|
||||
import { messages } from '../messages';
|
||||
import { MessageLangProps, SupportedLanguage, isValidLanguage } from '../types';
|
||||
interface MessageLangPropsWithStyle
|
||||
extends MessageLangProps,
|
||||
React.HTMLAttributes<HTMLSpanElement> {}
|
||||
|
||||
export function MessageLang({ text, data = [] }: MessageLangProps) {
|
||||
const [lang, setLang] = useState<SupportedLanguage>('ja');
|
||||
export function MessageLang({
|
||||
text,
|
||||
data = [],
|
||||
...htmlProps
|
||||
}: MessageLangPropsWithStyle) {
|
||||
const [lang, setLang] = useState<SupportedLanguage>("ja");
|
||||
|
||||
useEffect(() => {
|
||||
const browserLang = navigator.language.split('-')[0];
|
||||
const savedLang = Cookies.get('lang') || browserLang || 'ja';
|
||||
const browserLang = navigator.language.split("-")[0];
|
||||
const savedLang = Cookies.get("lang") || browserLang || "ja";
|
||||
if (isValidLanguage(savedLang)) {
|
||||
setLang(savedLang);
|
||||
Cookies.set('lang', savedLang);
|
||||
Cookies.set("lang", savedLang);
|
||||
}
|
||||
}, []);
|
||||
|
||||
let message = messages[lang][text];
|
||||
|
||||
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 }} {...htmlProps} />;
|
||||
}
|
|
@ -1,7 +1,12 @@
|
|||
import { messages } from '../messages';
|
||||
import { MessageLangProps, SupportedLanguage } from '../types';
|
||||
|
||||
export function MessageLangVar({ text, data = [], variables = {} }: MessageLangProps & { variables?: Record<string, string>; }, langFunc?: string) {
|
||||
export function MessageLangVar({
|
||||
text, data = [],
|
||||
variables = {}
|
||||
}: MessageLangProps & {
|
||||
variables?: Record<string, string>;
|
||||
}, langFunc?: string) {
|
||||
const lang: SupportedLanguage = (langFunc as SupportedLanguage) || 'ja';
|
||||
|
||||
let message = messages[lang][text];
|
||||
|
|
|
@ -7,7 +7,10 @@ export const messages = {
|
|||
学校や職場で使用できるようなプロフィール欄も自由に作成でき、公開設定も変更できます。<br />
|
||||
QRコードの印刷にも対応していて共有にも最適です。
|
||||
`,
|
||||
ServerAboutTitle: "このサーバーについて",
|
||||
or: "または",
|
||||
and: "と",
|
||||
agree: "同意",
|
||||
Start: "今すぐ始める",
|
||||
Signin: "サインイン",
|
||||
Signup: "サインアップ",
|
||||
|
@ -16,7 +19,30 @@ export const messages = {
|
|||
username: "ユーザー名",
|
||||
idHelp: "@から始まる英数字のID(3文字以上)",
|
||||
mailHelp: "メールアドレスは受信できるものを使用してください",
|
||||
passwordHelp: "8文字以上のパスワードを使用してください"
|
||||
passwordHelp: "8文字以上のパスワードを使用してください",
|
||||
signinError: "サインインに失敗しました",
|
||||
mailVerify: "メール認証",
|
||||
code: "コード",
|
||||
submit: "送信",
|
||||
codeCharacterCount: "コードは6桁で入力してください",
|
||||
emailVerificationSuccess: `
|
||||
メール認証に成功しました。
|
||||
ログインしてください。
|
||||
`,
|
||||
connectionError: "通信エラー",
|
||||
accountCreateSuccess: `
|
||||
アカウントの作成に成功しました。
|
||||
ご登録いただいたメールアドレスに確認用メールを送信しました。
|
||||
確認メールをクリックしてアカウントを有効化してください。
|
||||
`,
|
||||
sameUsername: "同じユーザー名が既に使用されています",
|
||||
termsAgreement: "利用規約とプライバシーポリシーに同意してください",
|
||||
bodyRequired: "フォーム内容が送信されていません",
|
||||
passwordCharacterLimit: "パスワードは8文字以上にしてください",
|
||||
usernameRestriction: "ユーザー名は3文字以上の英数字のIDにしてください",
|
||||
userCreationFailed: "ユーザーの作成に失敗しました",
|
||||
terms: "利用規約",
|
||||
privacypolicy: "プライバシーポリシー",
|
||||
},
|
||||
en: {
|
||||
PeasAboutTitle: "About Peas",
|
||||
|
@ -26,15 +52,44 @@ export const messages = {
|
|||
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",
|
||||
Start: "Get Started Now",
|
||||
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)",
|
||||
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"
|
||||
}
|
||||
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,11 +1,12 @@
|
|||
import mysql from 'mysql2/promise';
|
||||
import mysql from "mysql2/promise";
|
||||
import PeasConfig from "@/../peas.config";
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
user: process.env.DB_USER,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_DATABASE,
|
||||
host: PeasConfig.databaseHost,
|
||||
port: PeasConfig.databasePort,
|
||||
user: PeasConfig.databaseUser,
|
||||
password: PeasConfig.databasePassword,
|
||||
database: PeasConfig.databaseDB,
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10,
|
||||
});
|
||||
|
|
|
@ -1,41 +1,32 @@
|
|||
import * as nodemailer from 'nodemailer';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
dotenv.config();
|
||||
import * as nodemailer from "nodemailer";
|
||||
import type SMTPTransport from "nodemailer/lib/smtp-transport";
|
||||
import PeasConfig from "@/../peas.config";
|
||||
|
||||
export interface EmailMessage {
|
||||
to: string | string[],
|
||||
subject: string,
|
||||
text?: string,
|
||||
html?: string,
|
||||
to: string | string[];
|
||||
subject: string;
|
||||
text?: string;
|
||||
html?: string;
|
||||
}
|
||||
|
||||
export async function createTransporter() {
|
||||
// 環境変数の値をログ出力して確認
|
||||
console.log('SMTP設定:', {
|
||||
host: process.env.SMTP_HOST,
|
||||
port: process.env.SMTP_PORT,
|
||||
secure: process.env.SMTP_SECURE,
|
||||
user: process.env.SMTP_USER
|
||||
});
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: Number(process.env.SMTP_PORT),
|
||||
secure: process.env.SMTP_SECURE === 'true',
|
||||
host: PeasConfig.smtpHost,
|
||||
port: PeasConfig.smtpPort,
|
||||
secure: PeasConfig.smtpSecure,
|
||||
auth: {
|
||||
user: process.env.SMTP_USER,
|
||||
pass: process.env.SMTP_PASSWORD,
|
||||
user: PeasConfig.smtpUser,
|
||||
pass: PeasConfig.smtpPassword,
|
||||
},
|
||||
debug: process.env.DEBUG === 'true',
|
||||
});
|
||||
debug: process.env.DEBUG === "true",
|
||||
} as SMTPTransport.Options);
|
||||
|
||||
// 接続テスト
|
||||
try {
|
||||
await transporter.verify();
|
||||
console.log('SMTPサーバーに接続できました');
|
||||
console.log("SMTPサーバーに接続できました");
|
||||
} catch (error) {
|
||||
console.error('SMTP接続テストに失敗:', error);
|
||||
console.error("SMTP接続テストに失敗:", error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
@ -43,18 +34,18 @@ export async function createTransporter() {
|
|||
}
|
||||
|
||||
export async function sendMail(message: EmailMessage): Promise<void> {
|
||||
try{
|
||||
try {
|
||||
const transporter = await createTransporter();
|
||||
await transporter.sendMail({
|
||||
from: process.env.SMTP_USER,
|
||||
to: Array.isArray(message.to) ? message.to.join(',') : message.to,
|
||||
from: PeasConfig.smtpUser,
|
||||
to: Array.isArray(message.to) ? message.to.join(",") : message.to,
|
||||
subject: message.subject,
|
||||
text: message.text,
|
||||
html: message.html
|
||||
html: message.html,
|
||||
});
|
||||
console.log('メール送信成功');
|
||||
}catch (error){
|
||||
console.error('メール送信に失敗しました:', error);
|
||||
console.log("メール送信成功");
|
||||
} catch (error) {
|
||||
console.error("メール送信に失敗しました:", 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