chore: import clean Agent Desk snapshot (6/7)
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useEffect, type ReactNode } from "react"
|
||||
import { MailIcon, PencilIcon, UserRoundIcon } from "lucide-react"
|
||||
|
||||
import { useSupportAuth } from "@/app/(support)/support/_components/support-auth-provider"
|
||||
import { CommunityPostList } from "@/app/(support)/support/_components/community-post-list"
|
||||
import { SupportPageContent, SupportPageShell } from "@/app/(support)/support/_components/support-page-shell"
|
||||
import { PostListLoading } from "@/app/(support)/support/community/posts/_components/post-ui"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
import { useI18n } from "@/i18n/provider"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function SupportProfilePage() {
|
||||
const t = useI18n()
|
||||
const router = useRouter()
|
||||
const { ready, session } = useSupportAuth()
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && !session) {
|
||||
router.replace("/support/login?next=/support/profile")
|
||||
}
|
||||
}, [ready, router, session])
|
||||
|
||||
if (!ready || !session) {
|
||||
return (
|
||||
<SupportPageShell section="community">
|
||||
<SupportPageContent width="docs" className="py-8">
|
||||
<PostListLoading />
|
||||
</SupportPageContent>
|
||||
</SupportPageShell>
|
||||
)
|
||||
}
|
||||
|
||||
const user = session.user
|
||||
const displayName = user.nickname || user.username
|
||||
const fallback = displayName.slice(0, 1).toUpperCase() || "U"
|
||||
|
||||
return (
|
||||
<SupportPageShell section="community">
|
||||
<SupportPageContent width="docs" className="py-6 sm:py-8">
|
||||
<div className="grid gap-6 lg:grid-cols-[minmax(0,320px)_minmax(0,1fr)]">
|
||||
<aside className="h-fit rounded-md bg-card p-5">
|
||||
<div className="flex items-center gap-3">
|
||||
<Avatar className="size-14">
|
||||
<AvatarImage src={user.avatar} alt={displayName} />
|
||||
<AvatarFallback>{fallback}</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="min-w-0">
|
||||
<h1 className="truncate text-lg font-semibold">{displayName}</h1>
|
||||
<p className="truncate text-sm text-muted-foreground">{user.username}</p>
|
||||
</div>
|
||||
</div>
|
||||
<dl className="mt-5 grid gap-3 text-sm">
|
||||
<ProfileMeta icon={<UserRoundIcon className="size-4" />} label={t("supportPublic.profile.username")} value={user.username} />
|
||||
<ProfileMeta icon={<MailIcon className="size-4" />} label={t("supportPublic.profile.email")} value={user.email || t("supportPublic.profile.emailUnset")} />
|
||||
</dl>
|
||||
<Link className={cn(buttonVariants(), "mt-5 w-full")} href="/support/profile/edit">
|
||||
<PencilIcon />
|
||||
{t("supportPublic.profile.edit")}
|
||||
</Link>
|
||||
</aside>
|
||||
<section className="min-w-0 rounded-md bg-card">
|
||||
<div className="border-b px-5 py-4">
|
||||
<h2 className="text-base font-semibold">{t("supportPublic.profile.communityTitle")}</h2>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{t("supportPublic.profile.communityDescription")}</p>
|
||||
</div>
|
||||
<div className="px-5 py-3">
|
||||
<CommunityPostList
|
||||
emptyText={t("supportPublic.profile.noPosts")}
|
||||
limit={10}
|
||||
query={{ userId: user.id }}
|
||||
resetKey={`profile:${user.id}`}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</SupportPageContent>
|
||||
</SupportPageShell>
|
||||
)
|
||||
}
|
||||
|
||||
function ProfileMeta({ icon, label, value }: { icon: ReactNode; label: string; value: string }) {
|
||||
return (
|
||||
<div className="flex min-w-0 items-center gap-2 rounded-md bg-muted/45 px-3 py-2">
|
||||
<span className="shrink-0 text-muted-foreground">{icon}</span>
|
||||
<dt className="shrink-0 text-muted-foreground">{label}</dt>
|
||||
<dd className="min-w-0 flex-1 truncate text-right font-medium">{value}</dd>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
"use client"
|
||||
|
||||
import Link from "next/link"
|
||||
import { useRouter } from "next/navigation"
|
||||
import { useEffect, useState } from "react"
|
||||
import { toast } from "sonner"
|
||||
|
||||
import { useSupportAuth } from "@/app/(support)/support/_components/support-auth-provider"
|
||||
import { SupportPageContent, SupportPageShell } from "@/app/(support)/support/_components/support-page-shell"
|
||||
import { SupportFormField } from "@/app/(support)/support/_components/support-ui"
|
||||
import { ImageInput } from "@/components/image-input"
|
||||
import { Button, buttonVariants } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { useI18n } from "@/i18n/provider"
|
||||
import { updateProfile, uploadProfileAvatar } from "@/lib/api/auth"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
export function SupportProfileEditPage() {
|
||||
const t = useI18n()
|
||||
const router = useRouter()
|
||||
const { ready, session, refreshSession } = useSupportAuth()
|
||||
const [nickname, setNickname] = useState("")
|
||||
const [avatar, setAvatar] = useState("")
|
||||
const [email, setEmail] = useState("")
|
||||
const [error, setError] = useState("")
|
||||
const [submitting, setSubmitting] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (ready && !session) {
|
||||
router.replace("/support/login?next=/support/profile/edit")
|
||||
}
|
||||
}, [ready, router, session])
|
||||
|
||||
useEffect(() => {
|
||||
if (!session) return
|
||||
setNickname(session.user.nickname || session.user.username)
|
||||
setAvatar(session.user.avatar || "")
|
||||
setEmail(session.user.email || "")
|
||||
}, [session])
|
||||
|
||||
const submit = async () => {
|
||||
if (submitting) return
|
||||
const nextNickname = nickname.trim()
|
||||
if (!nextNickname) {
|
||||
setError(t("supportPublic.profile.nicknameRequired"))
|
||||
return
|
||||
}
|
||||
setSubmitting(true)
|
||||
setError("")
|
||||
try {
|
||||
await updateProfile({
|
||||
avatar: avatar.trim(),
|
||||
email: email.trim() || undefined,
|
||||
nickname: nextNickname,
|
||||
})
|
||||
await refreshSession()
|
||||
toast.success(t("supportPublic.toast.profileUpdated"))
|
||||
router.replace("/support/profile")
|
||||
} catch (err) {
|
||||
setError((err as Error).message || t("api.requestFailed"))
|
||||
} finally {
|
||||
setSubmitting(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<SupportPageShell section="community">
|
||||
<SupportPageContent className="py-6 sm:py-8">
|
||||
<div className="mx-auto max-w-2xl rounded-md bg-card">
|
||||
<div className="border-b px-5 py-4 sm:px-6">
|
||||
<h1 className="text-lg font-semibold">{t("supportPublic.profile.editTitle")}</h1>
|
||||
<p className="mt-1 text-sm text-muted-foreground">{t("supportPublic.profile.editDescription")}</p>
|
||||
</div>
|
||||
<div className="grid gap-5 p-5 sm:p-6">
|
||||
<SupportFormField label={t("supportPublic.profile.avatar")}>
|
||||
<ImageInput value={avatar} onChange={setAvatar} upload={uploadProfileAvatar} placeholder={t("supportPublic.profile.avatarPlaceholder")} />
|
||||
</SupportFormField>
|
||||
<SupportFormField label={t("supportPublic.profile.nickname")}>
|
||||
<Input value={nickname} onChange={(event) => setNickname(event.target.value)} placeholder={t("supportPublic.profile.nicknamePlaceholder")} />
|
||||
</SupportFormField>
|
||||
<SupportFormField label={t("supportPublic.profile.email")}>
|
||||
<Input value={email} onChange={(event) => setEmail(event.target.value)} placeholder={t("supportPublic.profile.emailPlaceholder")} />
|
||||
</SupportFormField>
|
||||
{error ? <p className="rounded-md border border-destructive/30 bg-destructive/10 px-3 py-2 text-sm text-destructive">{error}</p> : null}
|
||||
</div>
|
||||
<div className="flex flex-col-reverse gap-2 border-t px-5 py-4 sm:flex-row sm:justify-end sm:px-6">
|
||||
<Link className={cn(buttonVariants({ variant: "outline" }), "sm:w-auto")} href="/support/profile">
|
||||
{t("supportPublic.actions.cancel")}
|
||||
</Link>
|
||||
<Button disabled={submitting} onClick={() => void submit()}>
|
||||
{submitting ? t("supportPublic.actions.processing") : t("supportPublic.actions.save")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</SupportPageContent>
|
||||
</SupportPageShell>
|
||||
)
|
||||
}
|
||||
5
web/app/(support)/support/profile/edit/page.tsx
Normal file
5
web/app/(support)/support/profile/edit/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SupportProfileEditPage } from "./_components/profile-edit-page"
|
||||
|
||||
export default function ProfileEditPage() {
|
||||
return <SupportProfileEditPage />
|
||||
}
|
||||
5
web/app/(support)/support/profile/page.tsx
Normal file
5
web/app/(support)/support/profile/page.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { SupportProfilePage } from "./_components/profile-page"
|
||||
|
||||
export default function ProfilePage() {
|
||||
return <SupportProfilePage />
|
||||
}
|
||||
Reference in New Issue
Block a user