...
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { SubtitleEntry } from "../types/subtitle";
|
||||
import { logger } from "@/lib/logger";
|
||||
|
||||
export function parseSrt(data: string): SubtitleEntry[] {
|
||||
const lines = data.split(/\r?\n/);
|
||||
@@ -94,7 +93,7 @@ export async function loadSubtitle(url: string): Promise<SubtitleEntry[]> {
|
||||
const data = await response.text();
|
||||
return parseSrt(data);
|
||||
} catch (error) {
|
||||
logger.error('加载字幕失败', error);
|
||||
console.error('加载字幕失败', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@ import { SaveList } from "./SaveList";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { getLocalStorageOperator } from "@/lib/browser/localStorageOperators";
|
||||
import { genIPA, genLanguage } from "@/modules/translator/translator-action";
|
||||
import { logger } from "@/lib/logger";
|
||||
import { PageLayout } from "@/components/ui/PageLayout";
|
||||
import { getTTSUrl, TTS_SUPPORTED_LANGUAGES } from "@/lib/bigmodel/tts";
|
||||
|
||||
@@ -75,7 +74,7 @@ export default function TextSpeakerPage() {
|
||||
setIPA(data.ipa);
|
||||
})
|
||||
.catch((e) => {
|
||||
logger.error("生成 IPA 失败", e);
|
||||
console.error("生成 IPA 失败", e);
|
||||
setIPA("");
|
||||
});
|
||||
}
|
||||
@@ -120,7 +119,7 @@ export default function TextSpeakerPage() {
|
||||
load(objurlRef.current);
|
||||
play();
|
||||
} catch (e) {
|
||||
logger.error("播放音频失败", e);
|
||||
console.error("播放音频失败", e);
|
||||
setPause(true);
|
||||
setLanguage(null);
|
||||
setProcessing(false);
|
||||
@@ -212,7 +211,7 @@ export default function TextSpeakerPage() {
|
||||
}
|
||||
setIntoLocalStorage(save);
|
||||
} catch (e) {
|
||||
logger.error("保存到本地存储失败", e);
|
||||
console.error("保存到本地存储失败", e);
|
||||
setLanguage(null);
|
||||
} finally {
|
||||
setSaving(false);
|
||||
|
||||
@@ -1,49 +1,16 @@
|
||||
import Image from "next/image";
|
||||
import { PageLayout } from "@/components/ui/PageLayout";
|
||||
import { PageHeader } from "@/components/ui/PageHeader";
|
||||
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 async function ProfilePage() {
|
||||
const t = await getTranslations("profile");
|
||||
|
||||
const session = await auth.api.getSession({ headers: await headers() });
|
||||
|
||||
if (!session) {
|
||||
redirect("/auth?redirect=/profile");
|
||||
}
|
||||
|
||||
return (
|
||||
<PageLayout>
|
||||
<PageHeader title={t("myProfile")} />
|
||||
|
||||
{/* 用户信息区域 */}
|
||||
<div className="flex flex-col items-center gap-4">
|
||||
{/* 用户头像 */}
|
||||
{session.user.image && (
|
||||
<Image
|
||||
width={80}
|
||||
height={80}
|
||||
alt="User Avatar"
|
||||
src={session.user.image as string}
|
||||
className="rounded-full"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* 用户名和邮箱 */}
|
||||
<div className="text-center">
|
||||
<h2 className="text-xl font-semibold text-gray-800">
|
||||
{session.user.name}
|
||||
</h2>
|
||||
<p className="text-gray-600">{t("email", { email: session.user.email })}</p>
|
||||
</div>
|
||||
|
||||
{/* 登出按钮 */}
|
||||
<LogoutButton />
|
||||
</div>
|
||||
</PageLayout>
|
||||
);
|
||||
// 已登录,跳转到用户资料页面
|
||||
// 优先使用 username,如果没有则使用 email
|
||||
const username = (session.user.username as string) || (session.user.email as string);
|
||||
redirect(`/users/${username}`);
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
interface UserPageProps {
|
||||
params: Promise<{ username: string}>;
|
||||
}
|
||||
|
||||
export default async function UserPage({params}: UserPageProps) {
|
||||
const {username} = await params;
|
||||
|
||||
}
|
||||
126
src/app/users/[username]/page.tsx
Normal file
126
src/app/users/[username]/page.tsx
Normal file
@@ -0,0 +1,126 @@
|
||||
import Image from "next/image";
|
||||
import { Container } from "@/components/ui/Container";
|
||||
import { actionGetUserProfileByUsername } from "@/modules/auth/auth-action";
|
||||
import { notFound } from "next/navigation";
|
||||
import { getTranslations } from "next-intl/server";
|
||||
|
||||
interface UserPageProps {
|
||||
params: Promise<{ username: string; }>;
|
||||
}
|
||||
|
||||
export default async function UserPage({ params }: UserPageProps) {
|
||||
const { username } = await params;
|
||||
const t = await getTranslations("user_profile");
|
||||
|
||||
// Get user profile
|
||||
const result = await actionGetUserProfileByUsername({ username });
|
||||
|
||||
if (!result.success || !result.data) {
|
||||
notFound();
|
||||
}
|
||||
|
||||
const user = result.data;
|
||||
|
||||
return (
|
||||
<div className="min-h-[calc(100vh-64px)] bg-gray-50 py-8">
|
||||
<Container className="max-w-3xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<div className="flex items-center space-x-6">
|
||||
{/* Avatar */}
|
||||
{user.image ? (
|
||||
<div className="relative w-24 h-24 rounded-full border-4 border-[#35786f] overflow-hidden">
|
||||
<Image
|
||||
src={user.image}
|
||||
alt={user.displayUsername || user.username || user.email}
|
||||
fill
|
||||
className="object-cover"
|
||||
unoptimized
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-24 h-24 rounded-full bg-[#35786f] border-4 border-[#35786f] flex items-center justify-center">
|
||||
<span className="text-3xl font-bold text-white">
|
||||
{(user.displayUsername || user.username || user.email)[0].toUpperCase()}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* User Info */}
|
||||
<div className="flex-1">
|
||||
<h1 className="text-3xl font-bold text-gray-800 mb-2">
|
||||
{user.displayUsername || user.username || t("anonymous")}
|
||||
</h1>
|
||||
{user.username && (
|
||||
<p className="text-gray-600 text-sm mb-1">
|
||||
@{user.username}
|
||||
</p>
|
||||
)}
|
||||
<div className="flex items-center space-x-4 text-sm">
|
||||
<span className="text-gray-500">
|
||||
Joined: {new Date(user.createdAt).toLocaleDateString()}
|
||||
</span>
|
||||
{user.emailVerified && (
|
||||
<span className="flex items-center text-green-600">
|
||||
<svg className="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fillRule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 00016zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.293 12.293a1 1 0 101.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Verified
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email Section */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6 mb-6">
|
||||
<h2 className="text-xl font-semibold text-gray-800 mb-4">{t("email")}</h2>
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="text-gray-700">{user.email}</span>
|
||||
</div>
|
||||
{user.emailVerified ? (
|
||||
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-green-100 text-green-800">
|
||||
✓ {t("verified")}
|
||||
</span>
|
||||
) : (
|
||||
<span className="inline-flex items-center px-3 py-1 rounded-full text-sm font-medium bg-yellow-100 text-yellow-800">
|
||||
{t("unverified")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Account Info */}
|
||||
<div className="bg-white rounded-lg shadow-md p-6">
|
||||
<h2 className="text-xl font-semibold text-gray-800 mb-4">{t("accountInfo")}</h2>
|
||||
<dl className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">{t("userId")}</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900 font-mono break-all">{user.id}</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">{t("username")}</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{user.username || <span className="text-gray-400">{t("notSet")}</span>}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">{t("displayName")}</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{user.displayUsername || <span className="text-gray-400">{t("notSet")}</span>}
|
||||
</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt className="text-sm font-medium text-gray-500">{t("memberSince")}</dt>
|
||||
<dd className="mt-1 text-sm text-gray-900">
|
||||
{new Date(user.createdAt).toLocaleDateString()}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user