This commit is contained in:
30
public/messages/en-US/folders-folder_id.json
Normal file
30
public/messages/en-US/folders-folder_id.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"unauthorized": "You are not the owner of this folder",
|
||||||
|
"back": "Back",
|
||||||
|
"backToFolders": "Back to folders",
|
||||||
|
"folderName": "Folder: {name}",
|
||||||
|
"textPairs": "Text Pairs",
|
||||||
|
"itemsCount": "{count} items",
|
||||||
|
"memorize": "Memorize",
|
||||||
|
"loadingTextPairs": "Loading text pairs...",
|
||||||
|
"noTextPairs": "No text pairs in this folder",
|
||||||
|
"addTextPair": "Add Text Pair",
|
||||||
|
"addNewTextPair": "Add New Text Pair",
|
||||||
|
"text1": "Text 1",
|
||||||
|
"text2": "Text 2",
|
||||||
|
"locale1": "Locale 1",
|
||||||
|
"locale2": "Locale 2",
|
||||||
|
"save": "Save",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"confirmDelete": "Are you sure you want to delete this text pair?",
|
||||||
|
"updateTextPair": "Update Text Pair",
|
||||||
|
"update": "Update",
|
||||||
|
"textPairSaved": "Text pair saved successfully",
|
||||||
|
"textPairUpdated": "Text pair updated successfully",
|
||||||
|
"textPairDeleted": "Text pair deleted successfully",
|
||||||
|
"errorSavingTextPair": "Failed to save text pair",
|
||||||
|
"errorUpdatingTextPair": "Failed to update text pair",
|
||||||
|
"errorDeletingTextPair": "Failed to delete text pair"
|
||||||
|
}
|
||||||
14
public/messages/en-US/folders.json
Normal file
14
public/messages/en-US/folders.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"title": "Folders",
|
||||||
|
"subtitle": "Manage your collections",
|
||||||
|
"newFolder": "New Folder",
|
||||||
|
"creating": "Creating...",
|
||||||
|
"noFoldersYet": "No folders yet",
|
||||||
|
"folderInfo": "{id}. {name} ({totalPairs})",
|
||||||
|
"enterFolderName": "Enter folder name:",
|
||||||
|
"confirmDelete": "Type \"{name}\" to delete:",
|
||||||
|
"createFolderError": "Failed to create folder",
|
||||||
|
"deleteFolderError": "Failed to delete folder",
|
||||||
|
"createFolderSuccess": "Folder created successfully",
|
||||||
|
"deleteFolderSuccess": "Folder deleted successfully"
|
||||||
|
}
|
||||||
4
public/messages/en-US/login.json
Normal file
4
public/messages/en-US/login.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"loading": "Loading...",
|
||||||
|
"githubLogin": "GitHub Login"
|
||||||
|
}
|
||||||
5
public/messages/en-US/memorize/folder-selector.json
Normal file
5
public/messages/en-US/memorize/folder-selector.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "Select a folder",
|
||||||
|
"noFolders": "No folders found",
|
||||||
|
"folderInfo": "{id}. {name} ({count})"
|
||||||
|
}
|
||||||
8
public/messages/en-US/memorize/memorize.json
Normal file
8
public/messages/en-US/memorize/memorize.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"showAnswer": "Show Answer",
|
||||||
|
"next": "Next",
|
||||||
|
"reverse": "Reverse",
|
||||||
|
"dictation": "Dictation",
|
||||||
|
"noTextPairs": "No text pairs available",
|
||||||
|
"progress": "{index}/{total}"
|
||||||
|
}
|
||||||
3
public/messages/en-US/memorize/page.json
Normal file
3
public/messages/en-US/memorize/page.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"unauthorized": "Unauthorized access to this folder"
|
||||||
|
}
|
||||||
5
public/messages/en-US/profile.json
Normal file
5
public/messages/en-US/profile.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"myProfile": "My Profile",
|
||||||
|
"email": "Email: {email}",
|
||||||
|
"logout": "Logout"
|
||||||
|
}
|
||||||
@@ -8,5 +8,7 @@
|
|||||||
"other": "Other",
|
"other": "Other",
|
||||||
"translating": "translating...",
|
"translating": "translating...",
|
||||||
"translate": "translate",
|
"translate": "translate",
|
||||||
"inputLanguage": "Input a language."
|
"inputLanguage": "Input a language.",
|
||||||
|
"history": "History",
|
||||||
|
"enterLanguage": "Enter language"
|
||||||
}
|
}
|
||||||
|
|||||||
8
public/messages/en-US/translator/add-to-folder.json
Normal file
8
public/messages/en-US/translator/add-to-folder.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"chooseFolder": "Choose a Folder to Add to",
|
||||||
|
"notAuthenticated": "You are not authenticated",
|
||||||
|
"noFoldersFound": "No folders found",
|
||||||
|
"close": "Close",
|
||||||
|
"addToFolderSuccess": "Text pair added to folder",
|
||||||
|
"addToFolderError": "Failed to add text pair to folder"
|
||||||
|
}
|
||||||
31
public/messages/zh-CN/folders-folder_id.json
Normal file
31
public/messages/zh-CN/folders-folder_id.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"unauthorized": "您不是此文件夹的所有者",
|
||||||
|
"back": "返回",
|
||||||
|
"backToFolders": "返回文件夹",
|
||||||
|
"folderName": "文件夹:{name}",
|
||||||
|
"textPairs": "文本对",
|
||||||
|
"itemsCount": "{count} 项",
|
||||||
|
"memorize": "记忆",
|
||||||
|
"loadingTextPairs": "正在加载文本对...",
|
||||||
|
"noTextPairs": "此文件夹中没有文本对",
|
||||||
|
"addTextPair": "添加文本对",
|
||||||
|
"addNewTextPair": "添加新文本对",
|
||||||
|
"text1": "文本1",
|
||||||
|
"text2": "文本2",
|
||||||
|
"locale1": "语言1",
|
||||||
|
"locale2": "语言2",
|
||||||
|
"save": "保存",
|
||||||
|
"cancel": "取消",
|
||||||
|
"add": "添加",
|
||||||
|
"edit": "编辑",
|
||||||
|
"delete": "删除",
|
||||||
|
"confirmDelete": "确定要删除这个文本对吗?",
|
||||||
|
"updateTextPair": "更新文本对",
|
||||||
|
"update": "更新",
|
||||||
|
"textPairSaved": "文本对保存成功",
|
||||||
|
"textPairUpdated": "文本对更新成功",
|
||||||
|
"textPairDeleted": "文本对删除成功",
|
||||||
|
"errorSavingTextPair": "保存文本对失败",
|
||||||
|
"errorUpdatingTextPair": "更新文本对失败",
|
||||||
|
"errorDeletingTextPair": "删除文本对失败"
|
||||||
|
}
|
||||||
14
public/messages/zh-CN/folders.json
Normal file
14
public/messages/zh-CN/folders.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"title": "文件夹",
|
||||||
|
"subtitle": "管理您的集合",
|
||||||
|
"newFolder": "新建文件夹",
|
||||||
|
"creating": "创建中...",
|
||||||
|
"noFoldersYet": "还没有文件夹",
|
||||||
|
"folderInfo": "{id}. {name} ({totalPairs})",
|
||||||
|
"enterFolderName": "输入文件夹名称:",
|
||||||
|
"confirmDelete": "输入 \"{name}\" 以删除:",
|
||||||
|
"createFolderError": "创建文件夹失败",
|
||||||
|
"deleteFolderError": "删除文件夹失败",
|
||||||
|
"createFolderSuccess": "文件夹创建成功",
|
||||||
|
"deleteFolderSuccess": "文件夹删除成功"
|
||||||
|
}
|
||||||
4
public/messages/zh-CN/login.json
Normal file
4
public/messages/zh-CN/login.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"loading": "加载中...",
|
||||||
|
"githubLogin": "GitHub 登录"
|
||||||
|
}
|
||||||
5
public/messages/zh-CN/memorize/folder-selector.json
Normal file
5
public/messages/zh-CN/memorize/folder-selector.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"title": "选择文件夹",
|
||||||
|
"noFolders": "未找到文件夹",
|
||||||
|
"folderInfo": "{id}. {name} ({count})"
|
||||||
|
}
|
||||||
8
public/messages/zh-CN/memorize/memorize.json
Normal file
8
public/messages/zh-CN/memorize/memorize.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"showAnswer": "显示答案",
|
||||||
|
"next": "下一个",
|
||||||
|
"reverse": "反向",
|
||||||
|
"dictation": "听写",
|
||||||
|
"noTextPairs": "没有可用的文本对",
|
||||||
|
"progress": "{index}/{total}"
|
||||||
|
}
|
||||||
3
public/messages/zh-CN/memorize/page.json
Normal file
3
public/messages/zh-CN/memorize/page.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"unauthorized": "您无权访问该文件夹"
|
||||||
|
}
|
||||||
5
public/messages/zh-CN/profile.json
Normal file
5
public/messages/zh-CN/profile.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"myProfile": "我的个人资料",
|
||||||
|
"email": "邮箱:{email}",
|
||||||
|
"logout": "退出登录"
|
||||||
|
}
|
||||||
@@ -8,5 +8,7 @@
|
|||||||
"other": "其他",
|
"other": "其他",
|
||||||
"translating": "翻译中...",
|
"translating": "翻译中...",
|
||||||
"translate": "翻译",
|
"translate": "翻译",
|
||||||
"inputLanguage": "请输入语言。"
|
"inputLanguage": "请输入语言。",
|
||||||
|
"history": "历史记录",
|
||||||
|
"enterLanguage": "输入语言"
|
||||||
}
|
}
|
||||||
|
|||||||
8
public/messages/zh-CN/translator/add-to-folder.json
Normal file
8
public/messages/zh-CN/translator/add-to-folder.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"chooseFolder": "选择要添加到的文件夹",
|
||||||
|
"notAuthenticated": "您未登录",
|
||||||
|
"noFoldersFound": "未找到文件夹",
|
||||||
|
"close": "关闭",
|
||||||
|
"addToFolderSuccess": "文本对已添加到文件夹",
|
||||||
|
"addToFolderError": "添加文本对到文件夹失败"
|
||||||
|
}
|
||||||
@@ -5,24 +5,26 @@ import { folder } from "../../../../generated/prisma/client";
|
|||||||
import { Folder } from "lucide-react";
|
import { Folder } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Center } from "@/components/Center";
|
import { Center } from "@/components/Center";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface FolderSelectorProps {
|
interface FolderSelectorProps {
|
||||||
folders: (folder & { total_pairs: number })[];
|
folders: (folder & { total_pairs: number })[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const FolderSelector: React.FC<FolderSelectorProps> = ({ folders }) => {
|
const FolderSelector: React.FC<FolderSelectorProps> = ({ folders }) => {
|
||||||
|
const t = useTranslations("memorize/folder-selector");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
return (
|
return (
|
||||||
<Center>
|
<Center>
|
||||||
<Container className="p-6 gap-4 flex flex-col">
|
<Container className="p-6 gap-4 flex flex-col">
|
||||||
{(folders.length === 0 && (
|
{(folders.length === 0 && (
|
||||||
<h1 className="text-2xl text-gray-900 font-light">
|
<h1 className="text-2xl text-gray-900 font-light">
|
||||||
No folders found.
|
{t("noFolders")}
|
||||||
</h1>
|
</h1>
|
||||||
)) || (
|
)) || (
|
||||||
<>
|
<>
|
||||||
<h1 className="text-2xl text-gray-900 font-light">
|
<h1 className="text-2xl text-gray-900 font-light">
|
||||||
Select a folder:
|
{t("selectFolder")}
|
||||||
</h1>
|
</h1>
|
||||||
<div className="text-gray-900 border border-gray-200 rounded-2xl max-h-96 overflow-y-auto">
|
<div className="text-gray-900 border border-gray-200 rounded-2xl max-h-96 overflow-y-auto">
|
||||||
{folders.map((folder) => (
|
{folders.map((folder) => (
|
||||||
@@ -36,9 +38,12 @@ const FolderSelector: React.FC<FolderSelectorProps> = ({ folders }) => {
|
|||||||
<Folder />
|
<Folder />
|
||||||
<div className="flex-1 flex gap-2">
|
<div className="flex-1 flex gap-2">
|
||||||
<span className="group-hover:text-blue-500">
|
<span className="group-hover:text-blue-500">
|
||||||
{folder.id}. {folder.name}
|
{t("folderInfo", {
|
||||||
|
id: folder.id,
|
||||||
|
name: folder.name,
|
||||||
|
count: folder.total_pairs,
|
||||||
|
})}
|
||||||
</span>
|
</span>
|
||||||
<span>({folder.total_pairs})</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -8,12 +8,14 @@ import LightButton from "@/components/buttons/LightButton";
|
|||||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||||
import { getTTSAudioUrl } from "@/lib/tts";
|
import { getTTSAudioUrl } from "@/lib/tts";
|
||||||
import { VOICES } from "@/config/locales";
|
import { VOICES } from "@/config/locales";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface MemorizeProps {
|
interface MemorizeProps {
|
||||||
textPairs: text_pair[];
|
textPairs: text_pair[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
|
const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
|
||||||
|
const t = useTranslations("memorize.memorize");
|
||||||
const [reverse, setReverse] = useState(false);
|
const [reverse, setReverse] = useState(false);
|
||||||
const [dictation, setDictation] = useState(false);
|
const [dictation, setDictation] = useState(false);
|
||||||
const [index, setIndex] = useState(0);
|
const [index, setIndex] = useState(0);
|
||||||
@@ -27,7 +29,7 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
|
|||||||
<>
|
<>
|
||||||
<div className="h-36 flex flex-col gap-2 justify-start items-center font-serif text-3xl">
|
<div className="h-36 flex flex-col gap-2 justify-start items-center font-serif text-3xl">
|
||||||
<div className="text-sm text-gray-500">
|
<div className="text-sm text-gray-500">
|
||||||
{index + 1}/{textPairs.length}
|
{t("progress", { current: index + 1, total: textPairs.length })}
|
||||||
</div>
|
</div>
|
||||||
{dictation ? (
|
{dictation ? (
|
||||||
show === "question" ? (
|
show === "question" ? (
|
||||||
@@ -86,7 +88,7 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
|
|||||||
setShow(show === "question" ? "answer" : "question");
|
setShow(show === "question" ? "answer" : "question");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{show === "question" ? "Show Answer" : "Next"}
|
{show === "question" ? t("showAnswer") : t("next")}
|
||||||
</LightButton>
|
</LightButton>
|
||||||
<LightButton
|
<LightButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -94,7 +96,7 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
|
|||||||
}}
|
}}
|
||||||
selected={reverse}
|
selected={reverse}
|
||||||
>
|
>
|
||||||
Reverse
|
{t("reverse")}
|
||||||
</LightButton>
|
</LightButton>
|
||||||
<LightButton
|
<LightButton
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -102,11 +104,11 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
|
|||||||
}}
|
}}
|
||||||
selected={dictation}
|
selected={dictation}
|
||||||
>
|
>
|
||||||
Dictation
|
{t("dictation")}
|
||||||
</LightButton>
|
</LightButton>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)) || <p>No text pairs available</p>}
|
)) || <p>{t("noTextPairs")}</p>}
|
||||||
</Container>
|
</Container>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
import {
|
import {
|
||||||
getFoldersWithTotalPairsByOwner,
|
getFoldersWithTotalPairsByOwner,
|
||||||
getOwnerByFolderId,
|
getOwnerByFolderId,
|
||||||
@@ -18,9 +19,14 @@ export default async function MemorizePage({
|
|||||||
}) {
|
}) {
|
||||||
const session = await getServerSession();
|
const session = await getServerSession();
|
||||||
const username = session?.user?.name;
|
const username = session?.user?.name;
|
||||||
|
const t = await getTranslations("memorize.page");
|
||||||
|
|
||||||
const t = (await searchParams).folder_id;
|
const tParam = (await searchParams).folder_id;
|
||||||
const folder_id = t ? (isNonNegativeInteger(t) ? parseInt(t) : null) : null;
|
const folder_id = tParam
|
||||||
|
? isNonNegativeInteger(tParam)
|
||||||
|
? parseInt(tParam)
|
||||||
|
: null
|
||||||
|
: null;
|
||||||
|
|
||||||
if (!username)
|
if (!username)
|
||||||
redirect(
|
redirect(
|
||||||
@@ -37,7 +43,7 @@ export default async function MemorizePage({
|
|||||||
|
|
||||||
const owner = await getOwnerByFolderId(folder_id);
|
const owner = await getOwnerByFolderId(folder_id);
|
||||||
if (owner !== username) {
|
if (owner !== username) {
|
||||||
return <p>无权访问该文件夹</p>;
|
return <p>{t("unauthorized")}</p>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Memorize textPairs={await getTextPairsByFolderId(folder_id)} />;
|
return <Memorize textPairs={await getTextPairsByFolderId(folder_id)} />;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { getFoldersByOwner } from "@/lib/services/folderService";
|
|||||||
import { Folder } from "lucide-react";
|
import { Folder } from "lucide-react";
|
||||||
import { createTextPair } from "@/lib/services/textPairService";
|
import { createTextPair } from "@/lib/services/textPairService";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface AddToFolderProps {
|
interface AddToFolderProps {
|
||||||
item: z.infer<typeof TranslationHistorySchema>;
|
item: z.infer<typeof TranslationHistorySchema>;
|
||||||
@@ -18,6 +19,7 @@ interface AddToFolderProps {
|
|||||||
const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
|
const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const [folders, setFolders] = useState<folder[]>([]);
|
const [folders, setFolders] = useState<folder[]>([]);
|
||||||
|
const t = useTranslations("translator.add-to-folder");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const username = session.data!.user!.name as string;
|
const username = session.data!.user!.name as string;
|
||||||
@@ -28,7 +30,7 @@ const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="fixed left-0 top-0 z-50 w-screen h-screen bg-black/50 flex justify-center items-center">
|
<div className="fixed left-0 top-0 z-50 w-screen h-screen bg-black/50 flex justify-center items-center">
|
||||||
<Container className="p-6">
|
<Container className="p-6">
|
||||||
<div>You are not authenticated</div>;
|
<div>{t("notAuthenticated")}</div>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -36,7 +38,7 @@ const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
|
|||||||
return (
|
return (
|
||||||
<div className="fixed left-0 top-0 z-50 w-screen h-screen bg-black/50 flex justify-center items-center">
|
<div className="fixed left-0 top-0 z-50 w-screen h-screen bg-black/50 flex justify-center items-center">
|
||||||
<Container className="p-6">
|
<Container className="p-6">
|
||||||
<h1>Choose a Folder to Add to</h1>
|
<h1>{t("chooseFolder")}</h1>
|
||||||
<div className="border border-gray-200 rounded-2xl">
|
<div className="border border-gray-200 rounded-2xl">
|
||||||
{(folders.length > 0 &&
|
{(folders.length > 0 &&
|
||||||
folders.map((folder) => (
|
folders.map((folder) => (
|
||||||
@@ -56,20 +58,20 @@ const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
toast.success("Text pair added to folder");
|
toast.success(t("success"));
|
||||||
setShow(false);
|
setShow(false);
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
toast.error("Failed to add text pair to folder");
|
toast.error(t("error"));
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Folder />
|
<Folder />
|
||||||
{folder.id}. {folder.name}
|
{t("folderInfo", { id: folder.id, name: folder.name })}
|
||||||
</button>
|
</button>
|
||||||
))) || <div>No folders found</div>}
|
))) || <div>{t("noFolders")}</div>}
|
||||||
</div>
|
</div>
|
||||||
<LightButton onClick={() => setShow(false)}>Close</LightButton>
|
<LightButton onClick={() => setShow(false)}>{t("close")}</LightButton>
|
||||||
</Container>
|
</Container>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -238,7 +238,7 @@ export default function TranslatorPage() {
|
|||||||
<LightButton
|
<LightButton
|
||||||
selected={!["chinese", "english", "italian"].includes(lang)}
|
selected={!["chinese", "english", "italian"].includes(lang)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const newLang = prompt("Enter language");
|
const newLang = prompt(t("enterLanguage"));
|
||||||
if (newLang) {
|
if (newLang) {
|
||||||
setLang(newLang);
|
setLang(newLang);
|
||||||
}
|
}
|
||||||
@@ -261,7 +261,7 @@ export default function TranslatorPage() {
|
|||||||
</div>
|
</div>
|
||||||
{history.length > 0 && (
|
{history.length > 0 && (
|
||||||
<div className="m-6 flex flex-col items-center">
|
<div className="m-6 flex flex-col items-center">
|
||||||
<h1 className="text-2xl font-light">History</h1>
|
<h1 className="text-2xl font-light">{t("history")}</h1>
|
||||||
<div className="border border-gray-200 rounded-2xl m-4">
|
<div className="border border-gray-200 rounded-2xl m-4">
|
||||||
{history.map((item, index) => (
|
{history.map((item, index) => (
|
||||||
<div key={index}>
|
<div key={index}>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
deleteFolderById,
|
deleteFolderById,
|
||||||
getFoldersWithTotalPairsByOwner,
|
getFoldersWithTotalPairsByOwner,
|
||||||
} from "@/lib/services/folderService";
|
} from "@/lib/services/folderService";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface FolderProps {
|
interface FolderProps {
|
||||||
folder: folder & { total_pairs: number };
|
folder: folder & { total_pairs: number };
|
||||||
@@ -18,6 +19,7 @@ interface FolderProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const FolderCard = ({ folder, deleteCallback, openCallback }: FolderProps) => {
|
const FolderCard = ({ folder, deleteCallback, openCallback }: FolderProps) => {
|
||||||
|
const t = useTranslations("folders");
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="flex justify-between items-center group p-4 border-b border-gray-100 hover:bg-gray-50 cursor-pointer transition-colors"
|
className="flex justify-between items-center group p-4 border-b border-gray-100 hover:bg-gray-50 cursor-pointer transition-colors"
|
||||||
@@ -30,7 +32,11 @@ const FolderCard = ({ folder, deleteCallback, openCallback }: FolderProps) => {
|
|||||||
|
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<h3 className="font-medium text-gray-900">
|
<h3 className="font-medium text-gray-900">
|
||||||
{folder.id}. {folder.name} ({folder.total_pairs})
|
{t("folderInfo", {
|
||||||
|
id: folder.id,
|
||||||
|
name: folder.name,
|
||||||
|
totalPairs: folder.total_pairs,
|
||||||
|
})}
|
||||||
</h3>
|
</h3>
|
||||||
{/*<p className="text-sm text-gray-500">{} items</p>*/}
|
{/*<p className="text-sm text-gray-500">{} items</p>*/}
|
||||||
</div>
|
</div>
|
||||||
@@ -55,6 +61,7 @@ const FolderCard = ({ folder, deleteCallback, openCallback }: FolderProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default function FoldersClient({ username }: { username: string }) {
|
export default function FoldersClient({ username }: { username: string }) {
|
||||||
|
const t = useTranslations("folders");
|
||||||
const [folders, setFolders] = useState<(folder & { total_pairs: number })[]>(
|
const [folders, setFolders] = useState<(folder & { total_pairs: number })[]>(
|
||||||
[],
|
[],
|
||||||
);
|
);
|
||||||
@@ -80,13 +87,13 @@ export default function FoldersClient({ username }: { username: string }) {
|
|||||||
<Center>
|
<Center>
|
||||||
<div className="w-full max-w-2xl mx-auto bg-white border border-gray-200 rounded-2xl p-6">
|
<div className="w-full max-w-2xl mx-auto bg-white border border-gray-200 rounded-2xl p-6">
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<h1 className="text-2xl font-light text-gray-900">Folders</h1>
|
<h1 className="text-2xl font-light text-gray-900">{t("title")}</h1>
|
||||||
<p className="text-sm text-gray-500 mt-1">Manage your collections</p>
|
<p className="text-sm text-gray-500 mt-1">{t("subtitle")}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<button
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const folderName = prompt("Enter folder name:");
|
const folderName = prompt(t("enterFolderName"));
|
||||||
if (!folderName) return;
|
if (!folderName) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -103,7 +110,7 @@ export default function FoldersClient({ username }: { username: string }) {
|
|||||||
className="w-full p-3 border-2 border-dashed border-gray-300 rounded-xl text-gray-500 hover:border-gray-400 hover:text-gray-600 transition-colors flex items-center justify-center gap-2"
|
className="w-full p-3 border-2 border-dashed border-gray-300 rounded-xl text-gray-500 hover:border-gray-400 hover:text-gray-600 transition-colors flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
<FolderPlus size={18} />
|
<FolderPlus size={18} />
|
||||||
<span>{loading ? "Creating..." : "New Folder"}</span>
|
<span>{loading ? t("creating") : t("newFolder")}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="mt-4 max-h-96 overflow-y-auto">
|
<div className="mt-4 max-h-96 overflow-y-auto">
|
||||||
@@ -112,7 +119,7 @@ export default function FoldersClient({ username }: { username: string }) {
|
|||||||
<div className="w-16 h-16 mx-auto mb-3 rounded-lg bg-gray-100 flex items-center justify-center">
|
<div className="w-16 h-16 mx-auto mb-3 rounded-lg bg-gray-100 flex items-center justify-center">
|
||||||
<FolderPlus size={24} className="text-gray-400" />
|
<FolderPlus size={24} className="text-gray-400" />
|
||||||
</div>
|
</div>
|
||||||
<p className="text-sm">No folders yet</p>
|
<p className="text-sm">{t("noFoldersYet")}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="rounded-xl border border-gray-200 overflow-hidden">
|
<div className="rounded-xl border border-gray-200 overflow-hidden">
|
||||||
@@ -121,7 +128,9 @@ export default function FoldersClient({ username }: { username: string }) {
|
|||||||
key={folder.id}
|
key={folder.id}
|
||||||
folder={folder}
|
folder={folder}
|
||||||
deleteCallback={() => {
|
deleteCallback={() => {
|
||||||
const confirm = prompt(`Type "${folder.name}" to delete:`);
|
const confirm = prompt(
|
||||||
|
t("confirmDelete", { name: folder.name }),
|
||||||
|
);
|
||||||
if (confirm === folder.name) {
|
if (confirm === folder.name) {
|
||||||
deleteFolderById(folder.id).then(updateFolders);
|
deleteFolderById(folder.id).then(updateFolders);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import LightButton from "@/components/buttons/LightButton";
|
|||||||
import Input from "@/components/Input";
|
import Input from "@/components/Input";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface AddTextPairModalProps {
|
interface AddTextPairModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -19,6 +20,7 @@ export default function AddTextPairModal({
|
|||||||
onClose,
|
onClose,
|
||||||
onAdd,
|
onAdd,
|
||||||
}: AddTextPairModalProps) {
|
}: AddTextPairModalProps) {
|
||||||
|
const t = useTranslations("folders.folder_id");
|
||||||
const input1Ref = useRef<HTMLInputElement>(null);
|
const input1Ref = useRef<HTMLInputElement>(null);
|
||||||
const input2Ref = useRef<HTMLInputElement>(null);
|
const input2Ref = useRef<HTMLInputElement>(null);
|
||||||
const input3Ref = useRef<HTMLInputElement>(null);
|
const input3Ref = useRef<HTMLInputElement>(null);
|
||||||
@@ -66,25 +68,29 @@ export default function AddTextPairModal({
|
|||||||
<div className="bg-white rounded-xl p-6 w-full max-w-md mx-4">
|
<div className="bg-white rounded-xl p-6 w-full max-w-md mx-4">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<h2 className="flex-1 text-xl font-light mb-4 text-center">
|
<h2 className="flex-1 text-xl font-light mb-4 text-center">
|
||||||
Add New Text Pair
|
{t("addNewTextPair")}
|
||||||
</h2>
|
</h2>
|
||||||
<X onClick={onClose} className="hover:cursor-pointer"></X>
|
<X onClick={onClose} className="hover:cursor-pointer"></X>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
text1<Input ref={input1Ref} className="w-full"></Input>
|
{t("text1")}
|
||||||
|
<Input ref={input1Ref} className="w-full"></Input>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
text2<Input ref={input2Ref} className="w-full"></Input>
|
{t("text2")}
|
||||||
|
<Input ref={input2Ref} className="w-full"></Input>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
locale1<Input ref={input3Ref} className="w-full"></Input>
|
{t("locale1")}
|
||||||
|
<Input ref={input3Ref} className="w-full"></Input>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
locale2<Input ref={input4Ref} className="w-full"></Input>
|
{t("locale2")}
|
||||||
|
<Input ref={input4Ref} className="w-full"></Input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<LightButton onClick={handleAdd}>Add</LightButton>
|
<LightButton onClick={handleAdd}>{t("add")}</LightButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ import {
|
|||||||
import AddTextPairModal from "./AddTextPairModal";
|
import AddTextPairModal from "./AddTextPairModal";
|
||||||
import TextPairCard from "./TextPairCard";
|
import TextPairCard from "./TextPairCard";
|
||||||
import LightButton from "@/components/buttons/LightButton";
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export interface TextPair {
|
export interface TextPair {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -27,6 +29,7 @@ export default function InFolder({ folderId }: { folderId: number }) {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [openAddModal, setAddModal] = useState(false);
|
const [openAddModal, setAddModal] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const t = useTranslations("folders.folder_id");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchTextPairs = async () => {
|
const fetchTextPairs = async () => {
|
||||||
@@ -64,14 +67,16 @@ export default function InFolder({ folderId }: { folderId: number }) {
|
|||||||
className="flex items-center gap-2 text-gray-500 hover:text-gray-700 transition-colors mb-4"
|
className="flex items-center gap-2 text-gray-500 hover:text-gray-700 transition-colors mb-4"
|
||||||
>
|
>
|
||||||
<ArrowLeft size={16} />
|
<ArrowLeft size={16} />
|
||||||
<span className="text-sm">Back to folders</span>
|
<span className="text-sm">{t("back")}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-light text-gray-900">Text Pairs</h1>
|
<h1 className="text-2xl font-light text-gray-900">
|
||||||
|
{t("textPairs")}
|
||||||
|
</h1>
|
||||||
<p className="text-sm text-gray-500 mt-1">
|
<p className="text-sm text-gray-500 mt-1">
|
||||||
{textPairs.length} items
|
{t("itemsCount", { count: textPairs.length })}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -81,7 +86,7 @@ export default function InFolder({ folderId }: { folderId: number }) {
|
|||||||
redirect(`/memorize?folder_id=${folderId}`);
|
redirect(`/memorize?folder_id=${folderId}`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Memorize
|
{t("memorize")}
|
||||||
</LightButton>
|
</LightButton>
|
||||||
<button
|
<button
|
||||||
className="p-2 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
|
className="p-2 border border-gray-200 rounded-lg hover:bg-gray-50 transition-colors"
|
||||||
@@ -102,11 +107,11 @@ export default function InFolder({ folderId }: { folderId: number }) {
|
|||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-8 text-center">
|
<div className="p-8 text-center">
|
||||||
<div className="w-8 h-8 border-2 border-gray-200 border-t-gray-400 rounded-full animate-spin mx-auto mb-3"></div>
|
<div className="w-8 h-8 border-2 border-gray-200 border-t-gray-400 rounded-full animate-spin mx-auto mb-3"></div>
|
||||||
<p className="text-sm text-gray-500">Loading text pairs...</p>
|
<p className="text-sm text-gray-500">{t("loadingTextPairs")}</p>
|
||||||
</div>
|
</div>
|
||||||
) : textPairs.length === 0 ? (
|
) : textPairs.length === 0 ? (
|
||||||
<div className="p-12 text-center">
|
<div className="p-12 text-center">
|
||||||
<p className="text-sm text-gray-500 mb-2">No text pair items</p>
|
<p className="text-sm text-gray-500 mb-2">{t("noTextPairs")}</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="divide-y divide-gray-100">
|
<div className="divide-y divide-gray-100">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { updateTextPairById } from "@/lib/services/textPairService";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { text_pairUpdateInput } from "../../../../generated/prisma/models";
|
import { text_pairUpdateInput } from "../../../../generated/prisma/models";
|
||||||
import UpdateTextPairModal from "./UpdateTextPairModal";
|
import UpdateTextPairModal from "./UpdateTextPairModal";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface TextPairCardProps {
|
interface TextPairCardProps {
|
||||||
textPair: TextPair;
|
textPair: TextPair;
|
||||||
@@ -16,6 +17,7 @@ export default function TextPairCard({
|
|||||||
onDel,
|
onDel,
|
||||||
refreshTextPairs,
|
refreshTextPairs,
|
||||||
}: TextPairCardProps) {
|
}: TextPairCardProps) {
|
||||||
|
const t = useTranslations("folders.folder_id");
|
||||||
const [openUpdateModal, setOpenUpdateModal] = useState(false);
|
const [openUpdateModal, setOpenUpdateModal] = useState(false);
|
||||||
return (
|
return (
|
||||||
<div className="group border-b border-gray-100 hover:bg-gray-50 transition-colors">
|
<div className="group border-b border-gray-100 hover:bg-gray-50 transition-colors">
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { X } from "lucide-react";
|
|||||||
import { useRef } from "react";
|
import { useRef } from "react";
|
||||||
import { text_pairUpdateInput } from "../../../../generated/prisma/models";
|
import { text_pairUpdateInput } from "../../../../generated/prisma/models";
|
||||||
import { TextPair } from "./InFolder";
|
import { TextPair } from "./InFolder";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
interface UpdateTextPairModalProps {
|
interface UpdateTextPairModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@@ -18,6 +19,7 @@ export default function UpdateTextPairModal({
|
|||||||
onUpdate,
|
onUpdate,
|
||||||
textPair,
|
textPair,
|
||||||
}: UpdateTextPairModalProps) {
|
}: UpdateTextPairModalProps) {
|
||||||
|
const t = useTranslations("folders.folder_id");
|
||||||
const input1Ref = useRef<HTMLInputElement>(null);
|
const input1Ref = useRef<HTMLInputElement>(null);
|
||||||
const input2Ref = useRef<HTMLInputElement>(null);
|
const input2Ref = useRef<HTMLInputElement>(null);
|
||||||
const input3Ref = useRef<HTMLInputElement>(null);
|
const input3Ref = useRef<HTMLInputElement>(null);
|
||||||
@@ -65,25 +67,45 @@ export default function UpdateTextPairModal({
|
|||||||
<div className="bg-white rounded-xl p-6 w-full max-w-md mx-4">
|
<div className="bg-white rounded-xl p-6 w-full max-w-md mx-4">
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<h2 className="flex-1 text-xl font-light mb-4 text-center">
|
<h2 className="flex-1 text-xl font-light mb-4 text-center">
|
||||||
Update Text Pair
|
{t("updateTextPair")}
|
||||||
</h2>
|
</h2>
|
||||||
<X onClick={onClose} className="hover:cursor-pointer"></X>
|
<X onClick={onClose} className="hover:cursor-pointer"></X>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
text1<Input defaultValue={textPair.text1} ref={input1Ref} className="w-full"></Input>
|
{t("text1")}
|
||||||
|
<Input
|
||||||
|
defaultValue={textPair.text1}
|
||||||
|
ref={input1Ref}
|
||||||
|
className="w-full"
|
||||||
|
></Input>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
text2<Input defaultValue={textPair.text2} ref={input2Ref} className="w-full"></Input>
|
{t("text2")}
|
||||||
|
<Input
|
||||||
|
defaultValue={textPair.text2}
|
||||||
|
ref={input2Ref}
|
||||||
|
className="w-full"
|
||||||
|
></Input>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
locale1<Input defaultValue={textPair.locale1} ref={input3Ref} className="w-full"></Input>
|
{t("locale1")}
|
||||||
|
<Input
|
||||||
|
defaultValue={textPair.locale1}
|
||||||
|
ref={input3Ref}
|
||||||
|
className="w-full"
|
||||||
|
></Input>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
locale2<Input defaultValue={textPair.locale2} ref={input4Ref} className="w-full"></Input>
|
{t("locale2")}
|
||||||
|
<Input
|
||||||
|
defaultValue={textPair.locale2}
|
||||||
|
ref={input4Ref}
|
||||||
|
className="w-full"
|
||||||
|
></Input>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<LightButton onClick={handleUpdate}>Update</LightButton>
|
<LightButton onClick={handleUpdate}>{t("update")}</LightButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
import { getServerSession } from "next-auth";
|
import { getServerSession } from "next-auth";
|
||||||
|
import { getTranslations } from "next-intl/server";
|
||||||
import InFolder from "./InFolder";
|
import InFolder from "./InFolder";
|
||||||
import { getOwnerByFolderId } from "@/lib/services/folderService";
|
import { getOwnerByFolderId } from "@/lib/services/folderService";
|
||||||
export default async function FoldersPage({
|
export default async function FoldersPage({
|
||||||
@@ -10,12 +11,14 @@ export default async function FoldersPage({
|
|||||||
const session = await getServerSession();
|
const session = await getServerSession();
|
||||||
const { folder_id } = await params;
|
const { folder_id } = await params;
|
||||||
const id = Number(folder_id);
|
const id = Number(folder_id);
|
||||||
|
const t = await getTranslations("folders.folder_id");
|
||||||
|
|
||||||
if (!id) {
|
if (!id) {
|
||||||
redirect("/folders");
|
redirect("/folders");
|
||||||
}
|
}
|
||||||
if (!session?.user?.name) redirect(`/login?redirect=/folders/${id}`);
|
if (!session?.user?.name) redirect(`/login?redirect=/folders/${id}`);
|
||||||
if ((await getOwnerByFolderId(id)) !== session.user.name) {
|
if ((await getOwnerByFolderId(id)) !== session.user.name) {
|
||||||
return "you are not the owner of this folder";
|
return <p>{t("unauthorized")}</p>;
|
||||||
}
|
}
|
||||||
return <InFolder folderId={id} />;
|
return <InFolder folderId={id} />;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import { signIn, useSession } from "next-auth/react";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
|
const t = useTranslations("login");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session.status === "authenticated") {
|
if (session.status === "authenticated") {
|
||||||
@@ -22,7 +24,7 @@ export default function LoginPage() {
|
|||||||
return (
|
return (
|
||||||
<Center>
|
<Center>
|
||||||
{session.status === "loading" ? (
|
{session.status === "loading" ? (
|
||||||
<div>Loading...</div>
|
<div>{t("loading")}</div>
|
||||||
) : (
|
) : (
|
||||||
<LightButton
|
<LightButton
|
||||||
className="flex flex-row p-2 gap-2"
|
className="flex flex-row p-2 gap-2"
|
||||||
@@ -34,7 +36,7 @@ export default function LoginPage() {
|
|||||||
width={32}
|
width={32}
|
||||||
height={32}
|
height={32}
|
||||||
/>
|
/>
|
||||||
<span>GitHub Login</span>
|
<span>{t("githubLogin")}</span>
|
||||||
</LightButton>
|
</LightButton>
|
||||||
)}
|
)}
|
||||||
</Center>
|
</Center>
|
||||||
|
|||||||
@@ -7,11 +7,13 @@ import { useEffect } from "react";
|
|||||||
import { Center } from "@/components/Center";
|
import { Center } from "@/components/Center";
|
||||||
import Container from "@/components/cards/Container";
|
import Container from "@/components/cards/Container";
|
||||||
import LightButton from "@/components/buttons/LightButton";
|
import LightButton from "@/components/buttons/LightButton";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
export default function MePage() {
|
export default function MePage() {
|
||||||
const session = useSession();
|
const session = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
|
const t = useTranslations("profile");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session.status !== "authenticated") {
|
if (session.status !== "authenticated") {
|
||||||
@@ -22,7 +24,7 @@ export default function MePage() {
|
|||||||
return (
|
return (
|
||||||
<Center>
|
<Center>
|
||||||
<Container className="p-6">
|
<Container className="p-6">
|
||||||
<h1>My Profile</h1>
|
<h1>{t("myProfile")}</h1>
|
||||||
{(session.data?.user?.image as string) && (
|
{(session.data?.user?.image as string) && (
|
||||||
<Image
|
<Image
|
||||||
width={64}
|
width={64}
|
||||||
@@ -33,8 +35,8 @@ export default function MePage() {
|
|||||||
></Image>
|
></Image>
|
||||||
)}
|
)}
|
||||||
<p>{session.data?.user?.name}</p>
|
<p>{session.data?.user?.name}</p>
|
||||||
<p>Email: {session.data?.user?.email}</p>
|
<p>{t("email", { email: session.data?.user?.email })}</p>
|
||||||
<LightButton onClick={signOut}>Logout</LightButton>
|
<LightButton onClick={signOut}>{t("logout")}</LightButton>
|
||||||
</Container>
|
</Container>
|
||||||
</Center>
|
</Center>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user