将next-auth替换为better-auth
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useState } from "react";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { useAudioPlayer } from "@/hooks/useAudioPlayer";
|
||||
import { getTTSAudioUrl } from "@/lib/browser/tts";
|
||||
import { VOICES } from "@/config/locales";
|
||||
import { useTranslations } from "next-intl";
|
||||
import localFont from "next/font/local";
|
||||
import { isNonNegativeInteger } from "@/lib/utils";
|
||||
import { isNonNegativeInteger, SeededRandom } from "@/lib/utils";
|
||||
import { Pair } from "../../../../generated/prisma/browser";
|
||||
|
||||
const myFont = localFont({
|
||||
@@ -27,20 +27,16 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
|
||||
const [show, setShow] = useState<"question" | "answer">("question");
|
||||
const { load, play } = useAudioPlayer();
|
||||
|
||||
const [disorderedTextPairs, setDisorderedTextPairs] = useState<Pair[]>(
|
||||
[],
|
||||
);
|
||||
if (textPairs.length === 0) {
|
||||
return <p>{t("noTextPairs")}</p>;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setDisorderedTextPairs(textPairs.toSorted(() => Math.random() - 0.5));
|
||||
}, [textPairs]);
|
||||
const rng = new SeededRandom(textPairs[0].folderId);
|
||||
const disorderedTextPairs = textPairs.toSorted(() => rng.next() - 0.5);
|
||||
|
||||
const getTextPairs = () => {
|
||||
if (disorder) {
|
||||
return disorderedTextPairs;
|
||||
}
|
||||
return textPairs.toSorted((a, b) => a.id - b.id);
|
||||
};
|
||||
textPairs.sort((a, b) => a.id - b.id);
|
||||
|
||||
const getTextPairs = () => disorder ? disorderedTextPairs : textPairs;
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -120,7 +116,7 @@ const Memorize: React.FC<MemorizeProps> = ({ textPairs }) => {
|
||||
(v) =>
|
||||
v.locale ===
|
||||
getTextPairs()[newIndex][
|
||||
reverse ? "locale2" : "locale1"
|
||||
reverse ? "locale2" : "locale1"
|
||||
],
|
||||
)!.short_name,
|
||||
).then((url) => {
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
"use server";
|
||||
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import {
|
||||
getFoldersWithTotalPairsByUserId,
|
||||
getUserIdByFolderId,
|
||||
} from "@/lib/actions/services/folderService";
|
||||
} from "@/lib/server/services/folderService";
|
||||
import { isNonNegativeInteger } from "@/lib/utils";
|
||||
import FolderSelector from "./FolderSelector";
|
||||
import Memorize from "./Memorize";
|
||||
import { getPairsByFolderId } from "@/lib/actions/services/pairService";
|
||||
import { getPairsByFolderId } from "@/lib/server/services/pairService";
|
||||
import { auth } from "@/auth";
|
||||
import { headers } from "next/headers";
|
||||
|
||||
export default async function MemorizePage({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: Promise<{ folder_id?: string }>;
|
||||
searchParams: Promise<{ folder_id?: string; }>;
|
||||
}) {
|
||||
const session = await auth();
|
||||
const userId = session?.user?.id;
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
const tParam = (await searchParams).folder_id;
|
||||
|
||||
if (!session) {
|
||||
redirect(
|
||||
`/login?redirect=/memorize${(await searchParams).folder_id
|
||||
? `?folder_id=${tParam}`
|
||||
: ""
|
||||
}`,
|
||||
);
|
||||
}
|
||||
|
||||
const t = await getTranslations("memorize.page");
|
||||
|
||||
const tParam = (await searchParams).folder_id;
|
||||
const folder_id = tParam
|
||||
? isNonNegativeInteger(tParam)
|
||||
? parseInt(tParam)
|
||||
: null
|
||||
: null;
|
||||
|
||||
if (!userId) {
|
||||
redirect(
|
||||
`/login?redirect=/memorize${folder_id ? `?folder_id=${folder_id}` : ""}`,
|
||||
);
|
||||
}
|
||||
|
||||
const uid = Number(userId);
|
||||
|
||||
if (!folder_id) {
|
||||
return (
|
||||
<FolderSelector
|
||||
folders={await getFoldersWithTotalPairsByUserId(uid)}
|
||||
folders={await getFoldersWithTotalPairsByUserId(session.user.id)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const owner = await getUserIdByFolderId(folder_id);
|
||||
if (owner !== uid) {
|
||||
if (owner !== session.user.id) {
|
||||
return <p>{t("unauthorized")}</p>;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useState, useRef, forwardRef, useEffect } from "react";
|
||||
import { useState, useRef, forwardRef, useEffect, useCallback } from "react";
|
||||
import SubtitleDisplay from "./SubtitleDisplay";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { getIndex, parseSrt, getNearistIndex } from "../subtitle";
|
||||
@@ -20,7 +20,7 @@ const VideoPanel = forwardRef<HTMLVideoElement, VideoPanelProps>(
|
||||
const [spanText, setSpanText] = useState<string>("");
|
||||
const [subtitle, setSubtitle] = useState<string>("");
|
||||
const parsedSrtRef = useRef<
|
||||
{ start: number; end: number; text: string }[] | null
|
||||
{ start: number; end: number; text: string; }[] | null
|
||||
>(null);
|
||||
const rafldRef = useRef<number>(0);
|
||||
const ready = useRef({
|
||||
@@ -31,7 +31,7 @@ const VideoPanel = forwardRef<HTMLVideoElement, VideoPanelProps>(
|
||||
},
|
||||
});
|
||||
|
||||
const togglePlayPause = () => {
|
||||
const togglePlayPause = useCallback(() => {
|
||||
if (!videoUrl) return;
|
||||
|
||||
const video = videoRef.current;
|
||||
@@ -42,7 +42,7 @@ const VideoPanel = forwardRef<HTMLVideoElement, VideoPanelProps>(
|
||||
video.pause();
|
||||
}
|
||||
setIsPlaying(!video.paused);
|
||||
}
|
||||
}, [videoRef, videoUrl]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDownEvent = (e: globalThis.KeyboardEvent) => {
|
||||
|
||||
@@ -16,7 +16,7 @@ import { VOICES } from "@/config/locales";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { getLocalStorageOperator } from "@/lib/browser/localStorageOperators";
|
||||
import { getTTSAudioUrl } from "@/lib/browser/tts";
|
||||
import { genIPA, genLocale } from "@/lib/actions/translatorActions";
|
||||
import { genIPA, genLocale } from "@/lib/server/translatorActions";
|
||||
|
||||
export default function TextSpeakerPage() {
|
||||
const t = useTranslations("text_speaker");
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
"use client";
|
||||
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import Container from "@/components/cards/Container";
|
||||
import { TranslationHistorySchema } from "@/lib/interfaces";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { Dispatch, useEffect, useState } from "react";
|
||||
import z from "zod";
|
||||
import { Folder } from "../../../../generated/prisma/browser";
|
||||
import { getFoldersByUserId } from "@/lib/actions/services/folderService";
|
||||
import { getFoldersByUserId } from "@/lib/server/services/folderService";
|
||||
import { Folder as Fd } from "lucide-react";
|
||||
import { createPair } from "@/lib/actions/services/pairService";
|
||||
import { createPair } from "@/lib/server/services/pairService";
|
||||
import { toast } from "sonner";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
|
||||
interface AddToFolderProps {
|
||||
item: z.infer<typeof TranslationHistorySchema>;
|
||||
@@ -17,19 +19,21 @@ interface AddToFolderProps {
|
||||
}
|
||||
|
||||
const AddToFolder: React.FC<AddToFolderProps> = ({ item, setShow }) => {
|
||||
const session = useSession();
|
||||
const { data: session } = authClient.useSession();
|
||||
const [folders, setFolders] = useState<Folder[]>([]);
|
||||
const t = useTranslations("translator.add_to_folder");
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
const userId = Number(session.data!.user!.id);
|
||||
if (!session) return;
|
||||
const userId = session.user.id;
|
||||
getFoldersByUserId(userId)
|
||||
.then(setFolders)
|
||||
.then(() => setLoading(false));
|
||||
}, [session.data]);
|
||||
}, [session]);
|
||||
|
||||
if (session.status !== "authenticated") {
|
||||
|
||||
if (!session) {
|
||||
return (
|
||||
<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">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import Container from "@/components/cards/Container";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Folder } from "../../../../generated/prisma/browser";
|
||||
import { getFoldersByUserId } from "@/lib/actions/services/folderService";
|
||||
import { getFoldersByUserId } from "@/lib/server/services/folderService";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { Folder as Fd } from "lucide-react";
|
||||
|
||||
interface FolderSelectorProps {
|
||||
setSelectedFolderId: (id: number) => void;
|
||||
userId: number;
|
||||
userId: string;
|
||||
cancel: () => void;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,18 +17,16 @@ import {
|
||||
genIPA,
|
||||
genLocale,
|
||||
genTranslation,
|
||||
} from "@/lib/actions/translatorActions";
|
||||
} from "@/lib/server/translatorActions";
|
||||
import { toast } from "sonner";
|
||||
import FolderSelector from "./FolderSelector";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { createPair } from "@/lib/actions/services/pairService";
|
||||
import { createPair } from "@/lib/server/services/pairService";
|
||||
import { shallowEqual } from "@/lib/utils";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
|
||||
export default function TranslatorPage() {
|
||||
const t = useTranslations("translator");
|
||||
|
||||
const session = useSession();
|
||||
|
||||
const taref = useRef<HTMLTextAreaElement>(null);
|
||||
const [lang, setLang] = useState<string>("chinese");
|
||||
const [tresult, setTresult] = useState<string>("");
|
||||
@@ -49,8 +47,9 @@ export default function TranslatorPage() {
|
||||
});
|
||||
const [autoSave, setAutoSave] = useState(false);
|
||||
const [autoSaveFolderId, setAutoSaveFolderId] = useState<number | null>(null);
|
||||
const { data: session } = authClient.useSession();
|
||||
|
||||
useEffect(()=>{
|
||||
useEffect(() => {
|
||||
setHistory(tlso.get());
|
||||
}, []);
|
||||
|
||||
@@ -310,7 +309,7 @@ export default function TranslatorPage() {
|
||||
checked={autoSave}
|
||||
onChange={(e) => {
|
||||
const checked = e.target.checked;
|
||||
if (checked === true && !(session.status === "authenticated")) {
|
||||
if (checked === true && !session) {
|
||||
toast.warning("Please login to enable auto-save");
|
||||
return;
|
||||
}
|
||||
@@ -368,7 +367,7 @@ export default function TranslatorPage() {
|
||||
)}
|
||||
{autoSave && !autoSaveFolderId && (
|
||||
<FolderSelector
|
||||
userId={Number(session.data!.user!.id)}
|
||||
userId={session!.user.id as string}
|
||||
cancel={() => setAutoSave(false)}
|
||||
setSelectedFolderId={(id) => setAutoSaveFolderId(id)}
|
||||
/>
|
||||
|
||||
4
src/app/api/auth/[...all]/route.ts
Normal file
4
src/app/api/auth/[...all]/route.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
import { auth } from "@/auth";
|
||||
import { toNextJsHandler } from "better-auth/next-js";
|
||||
|
||||
export const { POST, GET } = toNextJsHandler(auth);
|
||||
@@ -1,3 +0,0 @@
|
||||
import { handlers } from "../../../../auth";
|
||||
|
||||
export const { GET, POST } = handlers;
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
deleteFolderById,
|
||||
getFoldersWithTotalPairsByUserId,
|
||||
renameFolderById,
|
||||
} from "@/lib/actions/services/folderService";
|
||||
} from "@/lib/server/services/folderService";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -85,7 +85,7 @@ const FolderCard = ({ folder, refresh }: FolderProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default function FoldersClient({ userId }: { userId: number }) {
|
||||
export default function FoldersClient({ userId }: { userId: string }) {
|
||||
const t = useTranslations("folders");
|
||||
const [folders, setFolders] = useState<(Folder & { total: number })[]>(
|
||||
[],
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
createPair,
|
||||
deletePairById,
|
||||
getPairsByFolderId,
|
||||
} from "@/lib/actions/services/pairService";
|
||||
} from "@/lib/server/services/pairService";
|
||||
import AddTextPairModal from "./AddTextPairModal";
|
||||
import TextPairCard from "./TextPairCard";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Edit, Trash2 } from "lucide-react";
|
||||
import { TextPair } from "./InFolder";
|
||||
import { updatePairById } from "@/lib/actions/services/pairService";
|
||||
import { updatePairById } from "@/lib/server/services/pairService";
|
||||
import { useState } from "react";
|
||||
import UpdateTextPairModal from "./UpdateTextPairModal";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import InFolder from "./InFolder";
|
||||
import { getUserIdByFolderId } from "@/lib/actions/services/folderService";
|
||||
import { getUserIdByFolderId } from "@/lib/server/services/folderService";
|
||||
import { auth } from "@/auth";
|
||||
import { headers } from "next/headers";
|
||||
export default async function FoldersPage({
|
||||
params,
|
||||
}: {
|
||||
params: Promise<{ folder_id: number }>;
|
||||
params: Promise<{ folder_id: number; }>;
|
||||
}) {
|
||||
const session = await auth();
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
const { folder_id } = await params;
|
||||
const t = await getTranslations("folder_id");
|
||||
|
||||
if (!folder_id) {
|
||||
redirect("/folders");
|
||||
}
|
||||
if (!session?.user?.id) redirect(`/login?redirect=/folders/${folder_id}`);
|
||||
if ((await getUserIdByFolderId(Number(folder_id))) !== Number(session.user.id)) {
|
||||
if (!session) redirect(`/login?redirect=/folders/${folder_id}`);
|
||||
if ((await getUserIdByFolderId(folder_id)) !== session.user.id) {
|
||||
return <p>{t("unauthorized")}</p>;
|
||||
}
|
||||
return <InFolder folderId={Number(folder_id)} />;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import { auth } from "@/auth";
|
||||
import FoldersClient from "./FoldersClient";
|
||||
import { redirect } from "next/navigation";
|
||||
import { headers } from "next/headers";
|
||||
export default async function FoldersPage() {
|
||||
const session = await auth();
|
||||
if (!session?.user?.id) redirect(`/login?redirect=/folders`);
|
||||
return <FoldersClient userId={Number(session.user.id)} />;
|
||||
const session = await auth.api.getSession(
|
||||
{ headers: await headers() }
|
||||
);
|
||||
if (!session) redirect(`/signin?redirect=/folders`);
|
||||
return <FoldersClient userId={session.user.id} />;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ import type { Metadata } from "next";
|
||||
import "./globals.css";
|
||||
import type { Viewport } from "next";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import SessionWrapper from "@/components/SessionWrapper";
|
||||
import { Navbar } from "@/components/Navbar";
|
||||
import { Toaster } from "sonner";
|
||||
|
||||
@@ -22,16 +21,14 @@ export default async function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<SessionWrapper>
|
||||
<html lang="en">
|
||||
<body className={`antialiased`}>
|
||||
<NextIntlClientProvider>
|
||||
<Navbar></Navbar>
|
||||
{children}
|
||||
<Toaster />
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
</SessionWrapper>
|
||||
<html lang="en">
|
||||
<body className={`antialiased`}>
|
||||
<NextIntlClientProvider>
|
||||
<Navbar></Navbar>
|
||||
{children}
|
||||
<Toaster />
|
||||
</NextIntlClientProvider>
|
||||
</body>
|
||||
</html>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { Center } from "@/components/Center";
|
||||
import IMAGES from "@/config/images";
|
||||
import { signIn, useSession } from "next-auth/react";
|
||||
import Image from "next/image";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
|
||||
export default function LoginPage() {
|
||||
const session = useSession();
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
const t = useTranslations("login");
|
||||
|
||||
useEffect(() => {
|
||||
if (session.status === "authenticated") {
|
||||
router.push(searchParams.get("redirect") || "/");
|
||||
}
|
||||
}, [session.status, router, searchParams]);
|
||||
|
||||
return (
|
||||
<Center>
|
||||
{session.status === "loading" ? (
|
||||
<div>{t("loading")}</div>
|
||||
) : (
|
||||
<LightButton
|
||||
className="flex flex-row p-2 gap-2"
|
||||
onClick={() => signIn("github")}
|
||||
>
|
||||
<Image
|
||||
src={IMAGES.github_mark}
|
||||
alt="GitHub Logo"
|
||||
width={32}
|
||||
height={32}
|
||||
/>
|
||||
<span>{t("githubLogin")}</span>
|
||||
</LightButton>
|
||||
)}
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
@@ -1,10 +1,32 @@
|
||||
import { useTranslations } from "next-intl";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function HomePage() {
|
||||
const t = useTranslations("home");
|
||||
function TopArea() {
|
||||
return (
|
||||
interface LinkAreaProps {
|
||||
href: string;
|
||||
name: string;
|
||||
description: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
function LinkArea({ href, name, description, color }: LinkAreaProps) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
style={{ backgroundColor: color }}
|
||||
className={`h-32 md:h-64 flex md:justify-center items-center`}
|
||||
>
|
||||
<div className="text-white m-8">
|
||||
<h1 className="md:text-4xl text-3xl">{name}</h1>
|
||||
<p className="md:text-xl">{description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
export default async function HomePage() {
|
||||
const t = await getTranslations("home");
|
||||
return (
|
||||
<>
|
||||
<div className="bg-[#35786f] text-white w-full min-h-[75dvh] flex justify-center items-center">
|
||||
<div className="mb-16 mx-16 md:mx-0 md:max-w-[60dvw]">
|
||||
<h1 className="text-6xl md:text-9xl mb-8 font-extrabold">
|
||||
@@ -13,37 +35,19 @@ export default function HomePage() {
|
||||
<p className="text-2xl md:text-5xl font-medium">{t("description")}</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
interface LinkAreaProps {
|
||||
href: string;
|
||||
name: string;
|
||||
description: string;
|
||||
color: string;
|
||||
}
|
||||
function LinkArea({ href, name, description, color }: LinkAreaProps) {
|
||||
return (
|
||||
<Link
|
||||
href={href}
|
||||
style={{ backgroundColor: color }}
|
||||
className={`h-32 md:h-64 flex md:justify-center items-center`}
|
||||
>
|
||||
<div className="text-white m-8">
|
||||
<h1 className="md:text-4xl text-3xl">{name}</h1>
|
||||
<p className="md:text-xl">{description}</p>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
function LinkGrid() {
|
||||
return (
|
||||
<div className="w-full grid grid-cols-1 grid-rows-6 md:grid-cols-3">
|
||||
<LinkArea
|
||||
href="/translator"
|
||||
name={t("translator.name")}
|
||||
description={t("translator.description")}
|
||||
color="#a56068"
|
||||
></LinkArea>
|
||||
<div className="w-full flex justify-center font-serif items-center flex-col min-h-64 h-[25vdh]">
|
||||
<p className="text-3xl">{t("fortune.quote")}</p>
|
||||
<cite className="text-[#e9b353] text-xl">{t("fortune.author")}</cite>
|
||||
</div>
|
||||
<div className="bg-[#bbbbbb] w-full flex justify-center items-center flex-col h-32">
|
||||
<div className="w-0 h-0 border-l-40 border-r-40 border-t-30 border-l-transparent border-r-transparent border-t-white"></div>
|
||||
</div>
|
||||
<div className="w-full grid grid-cols-1 grid-rows-6 md:grid-cols-3"><LinkArea
|
||||
href="/translator"
|
||||
name={t("translator.name")}
|
||||
description={t("translator.description")}
|
||||
color="#a56068"
|
||||
></LinkArea>
|
||||
<LinkArea
|
||||
href="/text-speaker"
|
||||
name={t("textSpeaker.name")}
|
||||
@@ -75,29 +79,6 @@ export default function HomePage() {
|
||||
color="#cab48a"
|
||||
></LinkArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function Fortune() {
|
||||
return (
|
||||
<div className="w-full flex justify-center font-serif items-center flex-col min-h-64 h-[25vdh]">
|
||||
<p className="text-3xl">{t("fortune.quote")}</p>
|
||||
<cite className="text-[#e9b353] text-xl">{t("fortune.author")}</cite>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function Explore() {
|
||||
return (
|
||||
<div className="bg-[#bbbbbb] w-full flex justify-center items-center flex-col h-32">
|
||||
<div className="w-0 h-0 border-l-40 border-r-40 border-t-30 border-l-transparent border-r-transparent border-t-white"></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<TopArea></TopArea>
|
||||
<Fortune></Fortune>
|
||||
<Explore></Explore>
|
||||
<LinkGrid></LinkGrid>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
20
src/app/profile/LogoutButton.tsx
Normal file
20
src/app/profile/LogoutButton.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
export default function LogoutButton() {
|
||||
const t = useTranslations("profile");
|
||||
const router = useRouter();
|
||||
return <LightButton onClick={async () => {
|
||||
authClient.signOut({
|
||||
fetchOptions: {
|
||||
onSuccess: () => {
|
||||
router.push("/login?redirect=/profile");
|
||||
}
|
||||
}
|
||||
});
|
||||
}}> {t("logout")}</LightButton >;
|
||||
}
|
||||
@@ -1,43 +1,40 @@
|
||||
"use client";
|
||||
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
import Image from "next/image";
|
||||
import { useEffect } from "react";
|
||||
import { Center } from "@/components/Center";
|
||||
import Container from "@/components/cards/Container";
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { auth } from "@/auth";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import { redirect } from "next/navigation";
|
||||
import { headers } from "next/headers";
|
||||
import LogoutButton from "./LogoutButton";
|
||||
|
||||
export default function MePage() {
|
||||
const session = useSession();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const t = useTranslations("profile");
|
||||
export default async function ProfilePage() {
|
||||
const t = await getTranslations("profile");
|
||||
|
||||
useEffect(() => {
|
||||
if (session.status !== "authenticated") {
|
||||
router.push(`/login?redirect=${encodeURIComponent(pathname)}`);
|
||||
}
|
||||
}, [session.status, router, pathname]);
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
|
||||
if (!session) {
|
||||
redirect("/signin?redirect=/profile");
|
||||
}
|
||||
|
||||
console.log(JSON.stringify(session, null, 2));
|
||||
|
||||
return (
|
||||
<Center>
|
||||
<Container className="p-6">
|
||||
<h1>{t("myProfile")}</h1>
|
||||
{(session.data?.user?.image as string) && (
|
||||
{(session.user.image) && (
|
||||
<Image
|
||||
width={64}
|
||||
height={64}
|
||||
alt="User Avatar"
|
||||
src={session.data?.user?.image as string}
|
||||
src={session.user.image as string}
|
||||
className="rounded-4xl"
|
||||
></Image>
|
||||
)}
|
||||
<p>{session.data?.user?.name}</p>
|
||||
<p>{t("email", { email: session.data!.user!.email as string })}</p>
|
||||
<LightButton onClick={signOut}>{t("logout")}</LightButton>
|
||||
<p>{session.user.name}</p>
|
||||
<p>{t("email", { email: session.user.email })}</p>
|
||||
<LogoutButton />
|
||||
</Container>
|
||||
</Center>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
23
src/app/signin/page.tsx
Normal file
23
src/app/signin/page.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { signInAction } from "@/lib/actions/auth";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function SignInPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Sign In</h1>
|
||||
<form action={signInAction}>
|
||||
<input type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
required />
|
||||
<input type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
required />
|
||||
<LightButton type="submit">Sign In</LightButton>
|
||||
</form>
|
||||
<Link href={"/signup"}>Do not have an account? Sign up!</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
33
src/app/signup/page.tsx
Normal file
33
src/app/signup/page.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
"use client";
|
||||
|
||||
import LightButton from "@/components/buttons/LightButton";
|
||||
import { signUpAction } from "@/lib/actions/auth";
|
||||
import Link from "next/link";
|
||||
|
||||
export default function SignInPage() {
|
||||
return (
|
||||
<div>
|
||||
<h1>Sign Up</h1>
|
||||
<form action={signUpAction}>
|
||||
<input type="text"
|
||||
name="name"
|
||||
placeholder="Name"
|
||||
required
|
||||
/>
|
||||
<input type="email"
|
||||
name="email"
|
||||
placeholder="Email"
|
||||
required
|
||||
/>
|
||||
<input type="password"
|
||||
name="password"
|
||||
placeholder="Password"
|
||||
required
|
||||
/>
|
||||
<LightButton type="submit">Sign Up</LightButton>
|
||||
</form>
|
||||
|
||||
<Link href={"/signin"}>Already have an account? Sign in!</Link>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user