Checkpoint: Initial stable CheckFlow base before i18n and demo mode
This commit is contained in:
@@ -0,0 +1,41 @@
|
||||
import { NextAuthOptions } from "next-auth";
|
||||
import CredentialsProvider from "next-auth/providers/credentials";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
export const authOptions: NextAuthOptions = {
|
||||
session: { strategy: "jwt" },
|
||||
pages: {
|
||||
signIn: "/login",
|
||||
newUser: "/register",
|
||||
},
|
||||
providers: [
|
||||
CredentialsProvider({
|
||||
name: "credentials",
|
||||
credentials: {
|
||||
email: { label: "Email", type: "email" },
|
||||
password: { label: "Password", type: "password" },
|
||||
},
|
||||
async authorize(credentials) {
|
||||
if (!credentials?.email || !credentials?.password) return null;
|
||||
const user = await prisma.user.findUnique({
|
||||
where: { email: credentials.email },
|
||||
});
|
||||
if (!user) return null;
|
||||
const valid = await bcrypt.compare(credentials.password, user.passwordHash);
|
||||
if (!valid) return null;
|
||||
return { id: user.id, email: user.email, name: user.name };
|
||||
},
|
||||
}),
|
||||
],
|
||||
callbacks: {
|
||||
jwt({ token, user }) {
|
||||
if (user) token.id = user.id;
|
||||
return token;
|
||||
},
|
||||
session({ session, token }) {
|
||||
if (session.user) session.user.id = token.id as string;
|
||||
return session;
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,288 @@
|
||||
"use client";
|
||||
import React, { createContext, useContext, useState, useEffect } from "react";
|
||||
|
||||
export type Locale = "en" | "ko" | "ja";
|
||||
|
||||
// ============================================================
|
||||
// TRANSLATIONS
|
||||
// ============================================================
|
||||
const translations = {
|
||||
en: {
|
||||
auth: {
|
||||
welcome: "Welcome back",
|
||||
signIn: "Sign in",
|
||||
signInSub: "Sign in to your account",
|
||||
createAccount: "Create account",
|
||||
createAccountSub: "Start organizing your tasks today",
|
||||
email: "Email",
|
||||
password: "Password",
|
||||
passwordHint: "Min. 8 characters",
|
||||
displayName: "Display Name",
|
||||
namePlaceholder: "Your name",
|
||||
noAccount: "Don't have an account?",
|
||||
hasAccount: "Already have an account?",
|
||||
createOne: "Create one",
|
||||
signInLink: "Sign in",
|
||||
signingIn: "Signing in...",
|
||||
creating: "Creating account...",
|
||||
invalidCredentials: "Invalid email or password.",
|
||||
registrationFailed: "Registration failed.",
|
||||
},
|
||||
sidebar: {
|
||||
lists: "Lists",
|
||||
newList: "New List",
|
||||
create: "Create",
|
||||
cancel: "Cancel",
|
||||
listName: "List name",
|
||||
importTasks: "Import Tasks",
|
||||
signOut: "Sign out",
|
||||
deleteListConfirm: "Delete this list and all its tasks?",
|
||||
},
|
||||
tasks: {
|
||||
addTask: "Add a task...",
|
||||
add: "Add",
|
||||
showDone: "Show done",
|
||||
hideDone: "Hide done",
|
||||
noTasks: "No tasks yet. Add one below!",
|
||||
selectList: "Select a list to get started",
|
||||
completed: "Completed",
|
||||
loading: "Loading...",
|
||||
},
|
||||
detail: {
|
||||
priority: "Priority",
|
||||
dueDate: "Due Date",
|
||||
notes: "Notes",
|
||||
notesPlaceholder: "Add notes, details, or anything you need to remember\u2026\n\nMarkdown: **bold**, *italic*, # heading, - list, - [ ] checkbox",
|
||||
subtasks: "Sub-tasks",
|
||||
addSubtask: "Add sub-task\u2026",
|
||||
autoSaved: "Auto-saved",
|
||||
saving: "Saving\u2026",
|
||||
delete: "Delete task",
|
||||
deleteConfirm: "Delete this task?",
|
||||
close: "Close",
|
||||
clear: "Clear",
|
||||
created: "Created",
|
||||
},
|
||||
priority: { none: "None", low: "Low", medium: "Medium", high: "High" },
|
||||
settings: {
|
||||
theme: "Theme",
|
||||
language: "Language",
|
||||
system: "System",
|
||||
light: "Light",
|
||||
dark: "Dark",
|
||||
},
|
||||
import: {
|
||||
title: "Import Tasks",
|
||||
targetList: "Target List",
|
||||
file: "File (CSV or ICS)",
|
||||
tip: "TickTick: Settings \u2192 Export \u2192 Export as CSV or iCalendar",
|
||||
cancel: "Cancel",
|
||||
import: "Import",
|
||||
importing: "Importing...",
|
||||
error: "Error",
|
||||
},
|
||||
list: { create: "Create list", color: "Color" },
|
||||
},
|
||||
|
||||
ko: {
|
||||
auth: {
|
||||
welcome: "다시 오셨군요",
|
||||
signIn: "로그인",
|
||||
signInSub: "계정에 로그인하세요",
|
||||
createAccount: "계정 만들기",
|
||||
createAccountSub: "오늘부터 할 일을 정리해보세요",
|
||||
email: "이메일",
|
||||
password: "비밀번호",
|
||||
passwordHint: "최소 8자",
|
||||
displayName: "이름",
|
||||
namePlaceholder: "표시될 이름",
|
||||
noAccount: "계정이 없으신가요?",
|
||||
hasAccount: "이미 계정이 있으신가요?",
|
||||
createOne: "만들기",
|
||||
signInLink: "로그인",
|
||||
signingIn: "로그인 중...",
|
||||
creating: "계정 생성 중...",
|
||||
invalidCredentials: "이메일 또는 비밀번호가 올바르지 않습니다.",
|
||||
registrationFailed: "회원가입에 실패했습니다.",
|
||||
},
|
||||
sidebar: {
|
||||
lists: "목록",
|
||||
newList: "새 목록",
|
||||
create: "만들기",
|
||||
cancel: "취소",
|
||||
listName: "목록 이름",
|
||||
importTasks: "가져오기",
|
||||
signOut: "로그아웃",
|
||||
deleteListConfirm: "이 목록과 모든 할 일을 삭제할까요?",
|
||||
},
|
||||
tasks: {
|
||||
addTask: "할 일 추가...",
|
||||
add: "추가",
|
||||
showDone: "완료 표시",
|
||||
hideDone: "완료 숨기기",
|
||||
noTasks: "할 일이 없습니다. 아래에서 추가하세요!",
|
||||
selectList: "목록을 선택하여 시작하세요",
|
||||
completed: "완료됨",
|
||||
loading: "불러오는 중...",
|
||||
},
|
||||
detail: {
|
||||
priority: "우선순위",
|
||||
dueDate: "마감일",
|
||||
notes: "메모",
|
||||
notesPlaceholder: "메모, 세부 정보, 기억해야 할 내용을 추가하세요\u2026\n\nMarkdown: **굵게**, *기울임*, # 제목, - 목록, - [ ] 체크박스",
|
||||
subtasks: "하위 할 일",
|
||||
addSubtask: "하위 항목 추가\u2026",
|
||||
autoSaved: "자동 저장됨",
|
||||
saving: "저장 중\u2026",
|
||||
delete: "삭제",
|
||||
deleteConfirm: "이 할 일을 삭제할까요?",
|
||||
close: "닫기",
|
||||
clear: "초기화",
|
||||
created: "생성일",
|
||||
},
|
||||
priority: { none: "없음", low: "낮음", medium: "보통", high: "높음" },
|
||||
settings: {
|
||||
theme: "테마",
|
||||
language: "언어",
|
||||
system: "시스템",
|
||||
light: "라이트",
|
||||
dark: "다크",
|
||||
},
|
||||
import: {
|
||||
title: "할 일 가져오기",
|
||||
targetList: "대상 목록",
|
||||
file: "파일 (CSV 또는 ICS)",
|
||||
tip: "TickTick: 설정 \u2192 내보내기 \u2192 CSV 또는 iCalendar로 내보내기",
|
||||
cancel: "취소",
|
||||
import: "가져오기",
|
||||
importing: "가져오는 중...",
|
||||
error: "오류",
|
||||
},
|
||||
list: { create: "목록 만들기", color: "색상" },
|
||||
},
|
||||
|
||||
ja: {
|
||||
auth: {
|
||||
welcome: "おかえりなさい",
|
||||
signIn: "サインイン",
|
||||
signInSub: "アカウントにサインイン",
|
||||
createAccount: "アカウント作成",
|
||||
createAccountSub: "今日からタスクを整理しましょう",
|
||||
email: "メールアドレス",
|
||||
password: "パスワード",
|
||||
passwordHint: "8文字以上",
|
||||
displayName: "表示名",
|
||||
namePlaceholder: "あなたの名前",
|
||||
noAccount: "アカウントをお持ちでない方は",
|
||||
hasAccount: "すでにアカウントをお持ちですか?",
|
||||
createOne: "作成する",
|
||||
signInLink: "サインイン",
|
||||
signingIn: "サインイン中...",
|
||||
creating: "アカウント作成中...",
|
||||
invalidCredentials: "メールアドレスまたはパスワードが正しくありません。",
|
||||
registrationFailed: "登録に失敗しました。",
|
||||
},
|
||||
sidebar: {
|
||||
lists: "リスト",
|
||||
newList: "新しいリスト",
|
||||
create: "作成",
|
||||
cancel: "キャンセル",
|
||||
listName: "リスト名",
|
||||
importTasks: "インポート",
|
||||
signOut: "サインアウト",
|
||||
deleteListConfirm: "このリストとすべてのタスクを削除しますか?",
|
||||
},
|
||||
tasks: {
|
||||
addTask: "タスクを追加...",
|
||||
add: "追加",
|
||||
showDone: "完了を表示",
|
||||
hideDone: "完了を非表示",
|
||||
noTasks: "タスクがありません。下から追加してください!",
|
||||
selectList: "リストを選択して始めましょう",
|
||||
completed: "完了済み",
|
||||
loading: "読み込み中...",
|
||||
},
|
||||
detail: {
|
||||
priority: "優先度",
|
||||
dueDate: "期限",
|
||||
notes: "メモ",
|
||||
notesPlaceholder: "メモ、詳細、覚えておくべきことを追加\u2026\n\nMarkdown: **太字**, *斜体*, # 見出し, - リスト, - [ ] チェックボックス",
|
||||
subtasks: "サブタスク",
|
||||
addSubtask: "サブタスクを追加\u2026",
|
||||
autoSaved: "自動保存済み",
|
||||
saving: "保存中\u2026",
|
||||
delete: "削除",
|
||||
deleteConfirm: "このタスクを削除しますか?",
|
||||
close: "閉じる",
|
||||
clear: "クリア",
|
||||
created: "作成日",
|
||||
},
|
||||
priority: { none: "なし", low: "低", medium: "中", high: "高" },
|
||||
settings: {
|
||||
theme: "テーマ",
|
||||
language: "言語",
|
||||
system: "システム",
|
||||
light: "ライト",
|
||||
dark: "ダーク",
|
||||
},
|
||||
import: {
|
||||
title: "タスクをインポート",
|
||||
targetList: "対象リスト",
|
||||
file: "ファイル (CSV または ICS)",
|
||||
tip: "TickTick: 設定 \u2192 エクスポート \u2192 CSVまたはiCalendarでエクスポート",
|
||||
cancel: "キャンセル",
|
||||
import: "インポート",
|
||||
importing: "インポート中...",
|
||||
error: "エラー",
|
||||
},
|
||||
list: { create: "リスト作成", color: "カラー" },
|
||||
},
|
||||
} as const;
|
||||
|
||||
export type Translations = typeof translations.en;
|
||||
export type TranslationKey = keyof Translations;
|
||||
|
||||
// ============================================================
|
||||
// CONTEXT
|
||||
// ============================================================
|
||||
interface I18nContextValue {
|
||||
locale: Locale;
|
||||
setLocale: (l: Locale) => void;
|
||||
t: Translations;
|
||||
}
|
||||
|
||||
const I18nContext = createContext<I18nContextValue>({
|
||||
locale: "en",
|
||||
setLocale: () => {},
|
||||
t: translations.en,
|
||||
});
|
||||
|
||||
export function I18nProvider({ children }: { children: React.ReactNode }) {
|
||||
const [locale, setLocaleState] = useState<Locale>("en");
|
||||
|
||||
useEffect(() => {
|
||||
const saved = localStorage.getItem("locale") as Locale | null;
|
||||
if (saved && saved in translations) setLocaleState(saved);
|
||||
}, []);
|
||||
|
||||
const setLocale = (l: Locale) => {
|
||||
setLocaleState(l);
|
||||
localStorage.setItem("locale", l);
|
||||
};
|
||||
|
||||
return (
|
||||
<I18nContext.Provider value={{ locale, setLocale, t: translations[locale] }}>
|
||||
{children}
|
||||
</I18nContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export function useI18n() {
|
||||
return useContext(I18nContext);
|
||||
}
|
||||
|
||||
export const LOCALES: { value: Locale; label: string; flag: string }[] = [
|
||||
{ value: "en", label: "English", flag: "🇺🇸" },
|
||||
{ value: "ko", label: "한국어", flag: "🇰🇷" },
|
||||
{ value: "ja", label: "日本語", flag: "🇯🇵" },
|
||||
];
|
||||
@@ -0,0 +1,13 @@
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined;
|
||||
};
|
||||
|
||||
export const prisma =
|
||||
globalForPrisma.prisma ??
|
||||
new PrismaClient({
|
||||
log: process.env.NODE_ENV === "development" ? ["error", "warn"] : ["error"],
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;
|
||||
Reference in New Issue
Block a user