마이페이지, 리포트, 메일인증
This commit is contained in:
@@ -11,12 +11,14 @@ export interface AccessTokenPayload extends JwtPayload {
|
||||
sub: string;
|
||||
email: string;
|
||||
tokenVersion: number;
|
||||
emailVerified?: boolean;
|
||||
}
|
||||
|
||||
export interface JwtUserLike {
|
||||
id: string;
|
||||
email: string;
|
||||
tokenVersion: number;
|
||||
emailVerified?: boolean;
|
||||
}
|
||||
|
||||
// 내부에서 사용할 JWT 시크릿을 가져온다.
|
||||
@@ -72,9 +74,10 @@ export async function signAccessToken(user: JwtUserLike): Promise<string> {
|
||||
sub: user.id,
|
||||
email: user.email,
|
||||
tokenVersion: user.tokenVersion,
|
||||
emailVerified: user.emailVerified ?? false,
|
||||
};
|
||||
|
||||
// 만료 시간은 기본 7일으로 설정한다.
|
||||
// 만료 시간은 기본 7일로 설정한다.
|
||||
const token = jwt.sign(payload, secret, {
|
||||
algorithm: "HS256",
|
||||
expiresIn: "7d",
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import nodemailer from "nodemailer";
|
||||
|
||||
export type SendVerificationEmailParams = {
|
||||
to: string;
|
||||
token: string;
|
||||
};
|
||||
|
||||
export async function sendVerificationEmail(params: SendVerificationEmailParams): Promise<void> {
|
||||
const host = process.env.EMAIL_SERVER_HOST;
|
||||
const portRaw = process.env.EMAIL_SERVER_PORT;
|
||||
const user = process.env.EMAIL_SERVER_USER;
|
||||
const pass = process.env.EMAIL_SERVER_PASSWORD;
|
||||
const from = process.env.EMAIL_FROM ?? user;
|
||||
|
||||
if (!host || !user || !pass) {
|
||||
throw new Error("Email server configuration is missing.");
|
||||
}
|
||||
|
||||
const port = portRaw ? Number(portRaw) : 587;
|
||||
|
||||
const baseUrl =
|
||||
process.env.NEXTAUTH_URL ?? process.env.APP_BASE_URL ?? "http://localhost:3000";
|
||||
const verifyUrl = `${baseUrl}/api/auth/verify-email?token=${encodeURIComponent(
|
||||
params.token,
|
||||
)}`;
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host,
|
||||
port,
|
||||
secure: port === 465,
|
||||
auth: {
|
||||
user,
|
||||
pass,
|
||||
},
|
||||
});
|
||||
|
||||
const subject = "이메일 인증을 완료해 주세요";
|
||||
const text = `아래 링크를 클릭해서 이메일 인증을 완료해 주세요.\n\n${verifyUrl}\n\n이 링크는 1시간 후 만료됩니다.`;
|
||||
const html = `<p>아래 링크를 클릭해서 이메일 인증을 완료해 주세요.</p><p><a href="${verifyUrl}">${verifyUrl}</a></p><p>이 링크는 1시간 후 만료됩니다.</p>`;
|
||||
|
||||
await transporter.sendMail({
|
||||
from: from ?? undefined,
|
||||
to: params.to,
|
||||
subject,
|
||||
text,
|
||||
html,
|
||||
});
|
||||
}
|
||||
@@ -12,10 +12,13 @@ export type DashboardMessages = {
|
||||
navDashboard: string;
|
||||
navProjects: string;
|
||||
navSubmissions: string;
|
||||
navReports?: string;
|
||||
// 헤더 오른쪽 액션 버튼/메뉴
|
||||
themeToggleLabel: string;
|
||||
menuLabel: string;
|
||||
logoutLabel: string;
|
||||
myPageLabel: string;
|
||||
emailNotVerifiedBanner: string;
|
||||
summaryTotalProjectsLabel: string;
|
||||
summaryTotalSubmissionsLabel: string;
|
||||
summaryTodaySubmissionsLabel: string;
|
||||
@@ -43,9 +46,12 @@ const DASHBOARD_MESSAGES: Record<AppLocale, DashboardMessages> = {
|
||||
navDashboard: "Dashboard",
|
||||
navProjects: "Projects",
|
||||
navSubmissions: "All submissions",
|
||||
navReports: "Reports",
|
||||
themeToggleLabel: "Toggle theme",
|
||||
menuLabel: "Menu",
|
||||
logoutLabel: "Log out",
|
||||
myPageLabel: "My page",
|
||||
emailNotVerifiedBanner: "Your email is not verified yet.",
|
||||
summaryTotalProjectsLabel: "Projects",
|
||||
summaryTotalSubmissionsLabel: "Total form submissions",
|
||||
summaryTodaySubmissionsLabel: "Submissions today",
|
||||
@@ -71,9 +77,12 @@ const DASHBOARD_MESSAGES: Record<AppLocale, DashboardMessages> = {
|
||||
navDashboard: "대시보드",
|
||||
navProjects: "프로젝트 목록",
|
||||
navSubmissions: "전체 제출 내역",
|
||||
navReports: "리포트",
|
||||
themeToggleLabel: "테마 전환",
|
||||
menuLabel: "메뉴",
|
||||
logoutLabel: "로그아웃",
|
||||
myPageLabel: "마이페이지",
|
||||
emailNotVerifiedBanner: "이메일 인증이 아직 완료되지 않았습니다.",
|
||||
summaryTotalProjectsLabel: "프로젝트 수",
|
||||
summaryTotalSubmissionsLabel: "전체 폼 제출 수",
|
||||
summaryTodaySubmissionsLabel: "오늘 제출 수",
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import type { AppLocale } from "../locale";
|
||||
import { DEFAULT_LOCALE } from "../locale";
|
||||
|
||||
// 마이페이지(/mypage) 전용 i18n 메시지
|
||||
|
||||
export type MyPageMessages = {
|
||||
title: string;
|
||||
description: string;
|
||||
loadError: string;
|
||||
loadingText: string;
|
||||
profileSectionTitle: string;
|
||||
profileEmailLabel: string;
|
||||
profileUserIdLabel: string;
|
||||
emailVerifiedBadge: string;
|
||||
backToDashboardLabel: string;
|
||||
emailVerificationNotice: string;
|
||||
emailVerificationButtonLabel: string;
|
||||
emailVerificationSuccess: string;
|
||||
emailVerificationError: string;
|
||||
changePasswordTitle: string;
|
||||
changePasswordCurrentLabel: string;
|
||||
changePasswordNewLabel: string;
|
||||
changePasswordConfirmLabel: string;
|
||||
changePasswordSubmitLabel: string;
|
||||
changePasswordErrorRequired: string;
|
||||
changePasswordErrorMismatch: string;
|
||||
changePasswordErrorNetwork: string;
|
||||
changePasswordSuccess: string;
|
||||
changeEmailTitle: string;
|
||||
changeEmailNewLabel: string;
|
||||
changeEmailCurrentPasswordLabel: string;
|
||||
changeEmailSubmitLabel: string;
|
||||
changeEmailErrorRequired: string;
|
||||
changeEmailErrorNetwork: string;
|
||||
changeEmailSuccess: string;
|
||||
};
|
||||
|
||||
const MYPAGE_MESSAGES: Record<AppLocale, MyPageMessages> = {
|
||||
en: {
|
||||
title: "My page",
|
||||
description: "Manage your account information, password, and email.",
|
||||
loadError: "An error occurred while loading your account information.",
|
||||
loadingText: "Loading account information...",
|
||||
profileSectionTitle: "Profile",
|
||||
profileEmailLabel: "Email:",
|
||||
profileUserIdLabel: "User ID:",
|
||||
emailVerifiedBadge: "Your email has been verified.",
|
||||
backToDashboardLabel: "Go to dashboard",
|
||||
emailVerificationNotice:
|
||||
"If your email is not verified yet, you can send a verification email from here.",
|
||||
emailVerificationButtonLabel: "Send verification email",
|
||||
emailVerificationSuccess: "Verification email has been sent.",
|
||||
emailVerificationError:
|
||||
"Failed to send verification email. Please try again.",
|
||||
changePasswordTitle: "Change password",
|
||||
changePasswordCurrentLabel: "Current password",
|
||||
changePasswordNewLabel: "New password",
|
||||
changePasswordConfirmLabel: "Confirm new password",
|
||||
changePasswordSubmitLabel: "Update password",
|
||||
changePasswordErrorRequired: "Please fill in all password fields.",
|
||||
changePasswordErrorMismatch: "New password and confirmation do not match.",
|
||||
changePasswordErrorNetwork:
|
||||
"A network error occurred while changing your password. Please try again.",
|
||||
changePasswordSuccess: "Your password has been changed.",
|
||||
changeEmailTitle: "Change email",
|
||||
changeEmailNewLabel: "New email",
|
||||
changeEmailCurrentPasswordLabel: "Current password",
|
||||
changeEmailSubmitLabel: "Update email",
|
||||
changeEmailErrorRequired: "Please enter your new email and current password.",
|
||||
changeEmailErrorNetwork:
|
||||
"A network error occurred while changing your email. Please try again.",
|
||||
changeEmailSuccess: "Your email has been changed.",
|
||||
},
|
||||
ko: {
|
||||
title: "마이페이지",
|
||||
description: "내 계정 정보, 비밀번호, 이메일을 관리할 수 있는 페이지입니다.",
|
||||
loadError: "계정 정보를 불러오는 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.",
|
||||
loadingText: "계정 정보를 불러오는 중입니다...",
|
||||
profileSectionTitle: "프로필",
|
||||
profileEmailLabel: "이메일:",
|
||||
profileUserIdLabel: "사용자 ID:",
|
||||
emailVerifiedBadge: "이메일 인증이 완료되었습니다.",
|
||||
backToDashboardLabel: "대시보드로 이동",
|
||||
emailVerificationNotice:
|
||||
"이메일 인증이 아직 완료되지 않았다면, 여기에서 인증 메일을 보낼 수 있습니다.",
|
||||
emailVerificationButtonLabel: "인증 메일 보내기",
|
||||
emailVerificationSuccess: "인증 메일을 보냈습니다.",
|
||||
emailVerificationError:
|
||||
"인증 메일 전송 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.",
|
||||
changePasswordTitle: "비밀번호 변경",
|
||||
changePasswordCurrentLabel: "현재 비밀번호",
|
||||
changePasswordNewLabel: "새 비밀번호",
|
||||
changePasswordConfirmLabel: "새 비밀번호 확인",
|
||||
changePasswordSubmitLabel: "비밀번호 업데이트",
|
||||
changePasswordErrorRequired: "모든 비밀번호 입력란을 채워주세요.",
|
||||
changePasswordErrorMismatch: "새 비밀번호와 확인 비밀번호가 일치하지 않습니다.",
|
||||
changePasswordErrorNetwork:
|
||||
"비밀번호 변경 중 네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.",
|
||||
changePasswordSuccess: "비밀번호가 변경되었습니다.",
|
||||
changeEmailTitle: "이메일 변경",
|
||||
changeEmailNewLabel: "새 이메일",
|
||||
changeEmailCurrentPasswordLabel: "현재 비밀번호",
|
||||
changeEmailSubmitLabel: "이메일 업데이트",
|
||||
changeEmailErrorRequired: "새 이메일과 현재 비밀번호를 모두 입력해주세요.",
|
||||
changeEmailErrorNetwork:
|
||||
"이메일 변경 중 네트워크 오류가 발생했습니다. 잠시 후 다시 시도해 주세요.",
|
||||
changeEmailSuccess: "이메일이 변경되었습니다.",
|
||||
},
|
||||
};
|
||||
|
||||
export function getMyPageMessages(locale: AppLocale | null | undefined): MyPageMessages {
|
||||
const key = (locale ?? DEFAULT_LOCALE) as AppLocale;
|
||||
return MYPAGE_MESSAGES[key] ?? MYPAGE_MESSAGES[DEFAULT_LOCALE];
|
||||
}
|
||||
Reference in New Issue
Block a user