refactor(profile): split view/container and update nav state
Signed-off-by: Noa Virellia <noa@requiem.garden>
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import type { ServiceUserUserInfoData } from '@/client';
|
||||
import { useUpdateUser } from '@/hooks/data/useUpdateUser';
|
||||
import { EditProfileDialogView } from './edit-profile.dialog.view';
|
||||
|
||||
export function EditProfileDialogContainer({ data }: { data: ServiceUserUserInfoData }) {
|
||||
const { mutateAsync } = useUpdateUser();
|
||||
return (
|
||||
<EditProfileDialogView
|
||||
user={data}
|
||||
updateProfile={async (data) => {
|
||||
await mutateAsync({ body: data });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
import type { ServiceUserUserInfoData } from '@/client';
|
||||
import { useForm } from '@tanstack/react-form';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { toast } from 'sonner';
|
||||
import z from 'zod';
|
||||
import { Button } from '@/components/ui/button';
|
||||
@@ -21,7 +24,6 @@ import {
|
||||
import {
|
||||
Input,
|
||||
} from '@/components/ui/input';
|
||||
import { useUpdateUser } from '@/hooks/data/useUpdateUser';
|
||||
import { Switch } from '../ui/switch';
|
||||
|
||||
const formSchema = z.object({
|
||||
@@ -31,9 +33,7 @@ const formSchema = z.object({
|
||||
avatar: z.url().or(z.literal('')),
|
||||
allow_public: z.boolean(),
|
||||
});
|
||||
export function EditProfileDialog({ user }: { user: ServiceUserUserInfoData }) {
|
||||
const { mutateAsync } = useUpdateUser();
|
||||
|
||||
export function EditProfileDialogView({ user, updateProfile }: { user: ServiceUserUserInfoData; updateProfile: (data: ServiceUserUserInfoData) => Promise<void> }) {
|
||||
const form = useForm({
|
||||
defaultValues: {
|
||||
avatar: user.avatar,
|
||||
@@ -49,7 +49,7 @@ export function EditProfileDialog({ user }: { user: ServiceUserUserInfoData }) {
|
||||
value,
|
||||
}) => {
|
||||
try {
|
||||
await mutateAsync({ body: value });
|
||||
await updateProfile(value);
|
||||
toast.success('个人资料更新成功');
|
||||
}
|
||||
catch (error) {
|
||||
@@ -61,11 +61,14 @@ export function EditProfileDialog({ user }: { user: ServiceUserUserInfoData }) {
|
||||
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
if (!open) {
|
||||
setTimeout(() => {
|
||||
form.reset();
|
||||
}, 200);
|
||||
}
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
const id = setTimeout(() => {
|
||||
form.reset();
|
||||
}, 200);
|
||||
return () => clearTimeout(id);
|
||||
}
|
||||
}, [open, form]);
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
17
client/cms/src/components/profile/profile.container.tsx
Normal file
17
client/cms/src/components/profile/profile.container.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { useUpdateUser } from '@/hooks/data/useUpdateUser';
|
||||
import { useOtherUserInfo } from '@/hooks/data/useUserInfo';
|
||||
import { utf8ToBase64 } from '@/lib/utils';
|
||||
import { ProfileView } from './profile.view';
|
||||
|
||||
export function ProfileContainer({ userId }: { userId: string }) {
|
||||
const { data } = useOtherUserInfo(userId);
|
||||
const { mutateAsync } = useUpdateUser();
|
||||
return (
|
||||
<ProfileView
|
||||
user={data.data!}
|
||||
onSaveBio={async (bio) => {
|
||||
await mutateAsync({ body: { bio: utf8ToBase64(bio) } });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -11,15 +11,13 @@ import { useMemo, useState } from 'react';
|
||||
import Markdown from 'react-markdown';
|
||||
import { toast } from 'sonner';
|
||||
import { Avatar, AvatarImage } from '@/components/ui/avatar';
|
||||
import { useUpdateUser } from '@/hooks/data/useUpdateUser';
|
||||
import { base64ToUtf8, utf8ToBase64 } from '@/lib/utils';
|
||||
import { base64ToUtf8 } from '@/lib/utils';
|
||||
import { Button } from '../ui/button';
|
||||
import { EditProfileDialog } from './edit-profile-dialog';
|
||||
import { EditProfileDialogContainer } from './edit-profile.dialog.container';
|
||||
|
||||
export function Profile({ user }: { user: ServiceUserUserInfoData }) {
|
||||
export function ProfileView({ user, onSaveBio }: { user: ServiceUserUserInfoData; onSaveBio: (bio: string) => Promise<void> }) {
|
||||
const [bio, setBio] = useState<string | undefined>(() => base64ToUtf8(user.bio ?? ''));
|
||||
const [enableBioEdit, setEnableBioEdit] = useState(false);
|
||||
const { mutateAsync } = useUpdateUser();
|
||||
|
||||
const IdentIcon = useMemo(() => {
|
||||
const avatar = createAvatar(identicon, {
|
||||
@@ -48,7 +46,7 @@ export function Profile({ user }: { user: ServiceUserUserInfoData }) {
|
||||
{user.email}
|
||||
</div>
|
||||
</div>
|
||||
<EditProfileDialog user={user} />
|
||||
<EditProfileDialogContainer data={user} />
|
||||
</div>
|
||||
</div>
|
||||
<section className="relative rounded-md border border-muted w-full flex-1 lg:flex-auto min-h-72 lg:h-full mt-4 lg:mt-0 prose dark:prose-invert max-w-[1012px] self-center">
|
||||
@@ -72,7 +70,7 @@ export function Profile({ user }: { user: ServiceUserUserInfoData }) {
|
||||
else {
|
||||
if (!isNil(bio)) {
|
||||
try {
|
||||
await mutateAsync({ body: { bio: utf8ToBase64(bio) } });
|
||||
await onSaveBio(bio);
|
||||
setEnableBioEdit(false);
|
||||
}
|
||||
catch (error) {
|
||||
@@ -1,3 +1,4 @@
|
||||
import type { NavData } from '@/lib/navData';
|
||||
import * as React from 'react';
|
||||
import NixOSLogo from '@/assets/nixos.svg?react';
|
||||
import { NavMain } from '@/components/sidebar/nav-main';
|
||||
@@ -11,10 +12,9 @@ import {
|
||||
SidebarMenuButton,
|
||||
SidebarMenuItem,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { navData } from '@/lib/navData';
|
||||
import { NavUser } from './nav-user';
|
||||
|
||||
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
|
||||
export function AppSidebar({ navData, ...props }: React.ComponentProps<typeof Sidebar> & { navData: NavData }) {
|
||||
return (
|
||||
<Sidebar collapsible="offcanvas" {...props}>
|
||||
<SidebarHeader>
|
||||
|
||||
@@ -26,15 +26,14 @@ import {
|
||||
useSidebar,
|
||||
} from '@/components/ui/sidebar';
|
||||
import { useUserInfo } from '@/hooks/data/useUserInfo';
|
||||
import { useLogout } from '@/hooks/useLogout';
|
||||
import { logout } from '@/lib/token';
|
||||
import { withFallback } from '../hoc/with-fallback';
|
||||
import { Skeleton } from '../ui/skeleton';
|
||||
|
||||
function NavUser_() {
|
||||
export function NavUser_() {
|
||||
const { isMobile } = useSidebar();
|
||||
const { data } = useUserInfo();
|
||||
const user = data.data!;
|
||||
const { logout } = useLogout();
|
||||
|
||||
const IdentIcon = useMemo(() => {
|
||||
const avatar = createAvatar(identicon, {
|
||||
@@ -85,7 +84,7 @@ function NavUser_() {
|
||||
</div>
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={logout}>
|
||||
<DropdownMenuItem onClick={_e => logout()}>
|
||||
<IconLogout />
|
||||
登出
|
||||
</DropdownMenuItem>
|
||||
|
||||
@@ -1,18 +1,7 @@
|
||||
import { useRouterState } from '@tanstack/react-router';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { SidebarTrigger } from '@/components/ui/sidebar';
|
||||
import { navData } from '@/lib/navData';
|
||||
|
||||
export function SiteHeader() {
|
||||
const pathname = useRouterState({ select: state => state.location.pathname });
|
||||
const allNavItems = [...navData.navMain, ...navData.navSecondary];
|
||||
const currentTitle
|
||||
= allNavItems.find(item =>
|
||||
item.url === '/'
|
||||
? pathname === '/'
|
||||
: pathname.startsWith(item.url),
|
||||
)?.title ?? '工作台';
|
||||
|
||||
export function SiteHeader({ title }: { title: string }) {
|
||||
return (
|
||||
<header className="flex h-(--header-height) shrink-0 items-center gap-2 border-b transition-[width,height] ease-linear group-has-data-[collapsible=icon]/sidebar-wrapper:h-(--header-height)">
|
||||
<div className="flex w-full items-center gap-1 px-4 lg:gap-2 lg:px-6">
|
||||
@@ -21,7 +10,7 @@ export function SiteHeader() {
|
||||
orientation="vertical"
|
||||
className="mx-2 data-[orientation=vertical]:h-4"
|
||||
/>
|
||||
<h1 className="text-base font-medium">{currentTitle}</h1>
|
||||
<h1 className="text-base font-medium">{title}</h1>
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user