fixed bug scroll area pada setting components
This commit is contained in:
parent
258205ef49
commit
1f8a6b18df
|
@ -61,14 +61,8 @@ const ImportData = () => {
|
|||
}, []);
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
className={`h-[${scrollAreaHeight}] w-full`}
|
||||
scrollable={isScrollable}
|
||||
>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className="min-h-screen p-8 max-w-4xl mx-auto space-y-8"
|
||||
>
|
||||
<div className="space-y-14 w-full max-w-4xl mx-auto">
|
||||
<div className="space-y-14 p-8 max-w-4xl mx-auto">
|
||||
<div className="space-y-4 mb-4">
|
||||
<h1 className="font-medium">Import data</h1>
|
||||
<Separator />
|
||||
|
@ -134,7 +128,7 @@ const ImportData = () => {
|
|||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -93,14 +93,8 @@ export default function NotificationsSetting() {
|
|||
};
|
||||
|
||||
return (
|
||||
<ScrollArea
|
||||
className="h-[calc(100vh-140px)] w-full"
|
||||
scrollable={isScrollable}
|
||||
>
|
||||
<div
|
||||
ref={contentRef}
|
||||
className="min-h-screen p-8 max-w-4xl mx-auto space-y-14"
|
||||
>
|
||||
<div className="space-y-14 w-full max-w-4xl mx-auto">
|
||||
<div className="space-y-14 p-8 max-w-4xl mx-auto">
|
||||
<div>
|
||||
<div className="space-y-4 mb-4">
|
||||
<h1 className="font-medium">Notifications</h1>
|
||||
|
@ -174,6 +168,6 @@ export default function NotificationsSetting() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -241,8 +241,8 @@ export default function PreferencesSettings() {
|
|||
}
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-[calc(100vh-140px)] w-full">
|
||||
<div className="min-h-screen p-8 max-w-4xl mx-auto space-y-14">
|
||||
<div className="space-y-14 w-full max-w-4xl mx-auto">
|
||||
<div className="space-y-14 p-8 max-w-4xl mx-auto">
|
||||
<div>
|
||||
<div className="space-y-4 mb-4">
|
||||
<h1 className="font-medium">Preferences</h1>
|
||||
|
@ -428,6 +428,6 @@ export default function PreferencesSettings() {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,67 +1,25 @@
|
|||
"use client";
|
||||
"use client"
|
||||
import { Loader2, ImageIcon } from "lucide-react"
|
||||
|
||||
import type React from "react";
|
||||
import { Form, FormControl, FormField, FormItem, FormMessage } from "@/app/_components/ui/form"
|
||||
import { Input } from "@/app/_components/ui/input"
|
||||
import { Button } from "@/app/_components/ui/button"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/app/_components/ui/avatar"
|
||||
import { Label } from "@/app/_components/ui/label"
|
||||
import { Separator } from "@/app/_components/ui/separator"
|
||||
import { Switch } from "@/app/_components/ui/switch"
|
||||
import { useProfileFormHandlers } from "../../dashboard/user-management/_handlers/use-profile-form"
|
||||
import { CTexts } from "@/app/_utils/const/texts"
|
||||
|
||||
import type { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||
|
||||
import { useForm } from "react-hook-form";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import { z } from "zod";
|
||||
import { Loader2, ImageIcon } from "lucide-react";
|
||||
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormMessage,
|
||||
} from "@/app/_components/ui/form";
|
||||
import { Input } from "@/app/_components/ui/input";
|
||||
import { Button } from "@/app/_components/ui/button";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/app/_components/ui/avatar";
|
||||
import { Label } from "@/app/_components/ui/label";
|
||||
import { Separator } from "@/app/_components/ui/separator";
|
||||
import { Switch } from "@/app/_components/ui/switch";
|
||||
import { useRef, useState } from "react";
|
||||
import { ScrollArea } from "@/app/_components/ui/scroll-area";
|
||||
import {
|
||||
updateUser,
|
||||
} from "@/app/(pages)/(admin)/dashboard/user-management/action";
|
||||
import { useProfileFormHandlers } from "../../dashboard/user-management/_handlers/use-profile-form";
|
||||
import { CTexts } from "@/app/_utils/const/texts";
|
||||
|
||||
const profileFormSchema = z.object({
|
||||
username: z.string().nullable().optional(),
|
||||
avatar: z.string().nullable().optional(),
|
||||
});
|
||||
|
||||
type ProfileFormValues = z.infer<typeof profileFormSchema>;
|
||||
|
||||
interface ProfileSettingsProps {
|
||||
user: IUserSchema | null;
|
||||
}
|
||||
|
||||
export function ProfileSettings({ user }: ProfileSettingsProps) {
|
||||
const email = user?.email || "";
|
||||
const username = user?.profile?.username || "";
|
||||
|
||||
const {
|
||||
form,
|
||||
fileInputRef,
|
||||
handleFileChange,
|
||||
handleAvatarClick,
|
||||
isPending,
|
||||
onSubmit,
|
||||
} = useProfileFormHandlers({ user });
|
||||
export function ProfileSettings() {
|
||||
const { form, fileInputRef, handleFileChange, handleAvatarClick, isPending, user } = useProfileFormHandlers()
|
||||
|
||||
const email = user?.email || ""
|
||||
const username = user?.profile?.username || ""
|
||||
|
||||
return (
|
||||
<ScrollArea className="h-[calc(100vh-140px)] w-full ">
|
||||
<div className="space-y-14 min-h-screen p-8 max-w-4xl mx-auto">
|
||||
<div className="space-y-14 w-full max-w-4xl mx-auto">
|
||||
<div className="space-y-14 p-8 max-w-4xl mx-auto">
|
||||
<Form {...form}>
|
||||
<form onSubmit={() => { }} className="space-y-8">
|
||||
<div className="space-y-4">
|
||||
|
@ -70,22 +28,14 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
|
|||
<Separator className="" />
|
||||
</div>
|
||||
<div className="flex items-start gap-4">
|
||||
<div
|
||||
className="relative cursor-pointer group"
|
||||
onClick={handleAvatarClick}
|
||||
>
|
||||
<div className="relative cursor-pointer group" onClick={handleAvatarClick}>
|
||||
<Avatar className="h-16 w-16">
|
||||
{isPending ? (
|
||||
<div className="h-full w-full bg-muted animate-pulse rounded-full" />
|
||||
) : (
|
||||
<>
|
||||
<AvatarImage
|
||||
src={user?.profile?.avatar || ""}
|
||||
alt={username}
|
||||
/>
|
||||
<AvatarFallback>
|
||||
{username?.[0]?.toUpperCase() || email?.[0]?.toUpperCase()}
|
||||
</AvatarFallback>
|
||||
<AvatarImage src={user?.profile?.avatar || ""} alt={username} />
|
||||
<AvatarFallback>{username?.[0]?.toUpperCase() || email?.[0]?.toUpperCase()}</AvatarFallback>
|
||||
</>
|
||||
)}
|
||||
<div className="absolute inset-0 flex items-center justify-center bg-black/50 rounded-full opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
|
@ -166,9 +116,7 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
|
|||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<Label>Password</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Set a permanent password to login to your account.
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground">Set a permanent password to login to your account.</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
Change password
|
||||
|
@ -179,8 +127,7 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
|
|||
<div>
|
||||
<Label>2-step verification</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Add an additional layer of security to your account during
|
||||
login.
|
||||
Add an additional layer of security to your account during login.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm">
|
||||
|
@ -222,8 +169,7 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
|
|||
<div>
|
||||
<Label className="text-destructive">Delete account</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Permanently delete the account and remove access from all
|
||||
workspaces.
|
||||
Permanently delete the account and remove access from all workspaces.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="outline" size="sm" className="text-destructive">
|
||||
|
@ -233,6 +179,6 @@ export function ProfileSettings({ user }: ProfileSettingsProps) {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ScrollArea>
|
||||
);
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
import { Button } from "@/app/_components/ui/button";
|
||||
import { Separator } from "@/app/_components/ui/separator";
|
||||
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||
import { useGetCurrentUserQuery } from "../../dashboard/user-management/_queries/queries";
|
||||
|
||||
interface SecuritySettingsProps {
|
||||
user: IUserSchema | null;
|
||||
}
|
||||
export function SecuritySettings() {
|
||||
|
||||
const { data: user } = useGetCurrentUserQuery();
|
||||
|
||||
export function SecuritySettings({ user }: SecuritySettingsProps) {
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
<div>
|
||||
|
|
|
@ -1,78 +1,52 @@
|
|||
"use client";
|
||||
"use client"
|
||||
|
||||
import type React from "react"
|
||||
|
||||
import { cn } from "@/app/_lib/utils";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogTrigger,
|
||||
} from "@/app/_components/ui/dialog";
|
||||
import { ScrollArea } from "@/app/_components/ui/scroll-area";
|
||||
import { Separator } from "@/app/_components/ui/separator";
|
||||
import {
|
||||
Avatar,
|
||||
AvatarFallback,
|
||||
AvatarImage,
|
||||
} from "@/app/_components/ui/avatar";
|
||||
import {
|
||||
IconAdjustmentsHorizontal,
|
||||
IconBaselineDensityLarge,
|
||||
IconBell,
|
||||
IconFileExport,
|
||||
IconFileImport,
|
||||
IconFingerprint,
|
||||
IconLock,
|
||||
IconPlugConnected,
|
||||
IconSettings,
|
||||
IconUser,
|
||||
IconUsers,
|
||||
IconWorld,
|
||||
} from "@tabler/icons-react";
|
||||
import { ProfileSettings } from "./profile-settings";
|
||||
import { DialogTitle } from "@radix-ui/react-dialog";
|
||||
import { useState } from "react";
|
||||
import { cn } from "@/app/_lib/utils"
|
||||
import { Dialog, DialogContent, DialogTrigger } from "@/app/_components/ui/dialog"
|
||||
import { ScrollArea } from "@/app/_components/ui/scroll-area"
|
||||
import { Separator } from "@/app/_components/ui/separator"
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/app/_components/ui/avatar"
|
||||
import { IconAdjustmentsHorizontal, IconBell, IconFileExport, IconFileImport, IconUser } from "@tabler/icons-react"
|
||||
import { ProfileSettings } from "./profile-settings"
|
||||
import { DialogTitle } from "@radix-ui/react-dialog"
|
||||
import { useState } from "react"
|
||||
|
||||
import NotificationsSetting from "./notification-settings";
|
||||
import PreferencesSettings from "./preference-settings";
|
||||
import ImportData from "./import-data";
|
||||
import { IUserSchema } from "@/src/entities/models/users/users.model";
|
||||
import { useUserStore } from "@/app/_lib/zustand/stores/user";
|
||||
import NotificationsSetting from "./notification-settings"
|
||||
import PreferencesSettings from "./preference-settings"
|
||||
import ImportData from "./import-data"
|
||||
|
||||
import { useGetCurrentUserQuery } from "../../dashboard/user-management/_queries/queries"
|
||||
|
||||
interface SettingsDialogProps {
|
||||
trigger: React.ReactNode;
|
||||
defaultTab?: string;
|
||||
open?: boolean;
|
||||
onOpenChange?: (open: boolean) => void;
|
||||
trigger: React.ReactNode
|
||||
defaultTab?: string
|
||||
open?: boolean
|
||||
onOpenChange?: (open: boolean) => void
|
||||
}
|
||||
|
||||
interface SettingsTab {
|
||||
id: string;
|
||||
icon: typeof IconUser;
|
||||
title: string;
|
||||
content: React.ReactNode;
|
||||
id: string
|
||||
icon: typeof IconUser
|
||||
title: string
|
||||
content: React.ReactNode
|
||||
}
|
||||
|
||||
interface SettingsSection {
|
||||
title: string;
|
||||
tabs: SettingsTab[];
|
||||
title: string
|
||||
tabs: SettingsTab[]
|
||||
}
|
||||
|
||||
export function SettingsDialog({
|
||||
trigger,
|
||||
defaultTab = "account",
|
||||
open,
|
||||
onOpenChange,
|
||||
}: SettingsDialogProps) {
|
||||
export function SettingsDialog({ trigger, defaultTab = "account", open, onOpenChange }: SettingsDialogProps) {
|
||||
const { data: user, isPending } = useGetCurrentUserQuery()
|
||||
|
||||
const { user, isPending } = useUserStore();
|
||||
|
||||
const [selectedTab, setSelectedTab] = useState(defaultTab);
|
||||
const [selectedTab, setSelectedTab] = useState(defaultTab)
|
||||
|
||||
// Get user display name
|
||||
const preferredName = user?.profile?.username || "";
|
||||
const userEmail = user?.email || "";
|
||||
const displayName = preferredName || userEmail?.split("@")[0] || "User";
|
||||
const userAvatar = user?.profile?.avatar || "";
|
||||
const preferredName = user?.profile?.username || ""
|
||||
const userEmail = user?.email || ""
|
||||
const displayName = preferredName || userEmail?.split("@")[0] || "User"
|
||||
const userAvatar = user?.profile?.avatar || ""
|
||||
|
||||
const sections: SettingsSection[] = [
|
||||
{
|
||||
|
@ -82,7 +56,7 @@ export function SettingsDialog({
|
|||
id: "account",
|
||||
icon: IconUser,
|
||||
title: "My Account",
|
||||
content: <ProfileSettings user={user} />,
|
||||
content: <ProfileSettings />,
|
||||
},
|
||||
{
|
||||
id: "preferences",
|
||||
|
@ -115,17 +89,15 @@ export function SettingsDialog({
|
|||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
]
|
||||
|
||||
const currentTab = sections
|
||||
.flatMap((section) => section.tabs)
|
||||
.find((tab) => tab.id === selectedTab);
|
||||
const currentTab = sections.flatMap((section) => section.tabs).find((tab) => tab.id === selectedTab)
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogTitle></DialogTitle>
|
||||
<DialogTrigger asChild>{trigger}</DialogTrigger>
|
||||
<DialogContent className="max-w-[1200px] gap-0 p-0">
|
||||
<DialogContent className="max-w-[1200px] gap-0 p-0 h-[600px]">
|
||||
<div className="grid h-[600px] grid-cols-[250px,1fr]">
|
||||
{/* Sidebar */}
|
||||
<div className="border-r bg-muted/50">
|
||||
|
@ -137,25 +109,17 @@ export function SettingsDialog({
|
|||
) : (
|
||||
<Avatar className="h-8 w-8">
|
||||
<AvatarImage src={userAvatar} alt={displayName} />
|
||||
<AvatarFallback>
|
||||
{displayName[0].toUpperCase()}
|
||||
</AvatarFallback>
|
||||
<AvatarFallback>{displayName[0].toUpperCase()}</AvatarFallback>
|
||||
</Avatar>
|
||||
)}
|
||||
<span className="text-sm font-medium">
|
||||
{isPending ? (
|
||||
<div className="h-4 w-24 rounded bg-muted animate-pulse" />
|
||||
) : (
|
||||
displayName
|
||||
)}
|
||||
{isPending ? <div className="h-4 w-24 rounded bg-muted animate-pulse" /> : displayName}
|
||||
</span>
|
||||
</div>
|
||||
{sections.map((section, index) => (
|
||||
<div key={section.title} className="py-2">
|
||||
<div className="px-3 py-2">
|
||||
<h3 className="text-sm font-medium text-muted-foreground">
|
||||
{section.title}
|
||||
</h3>
|
||||
<h3 className="text-sm font-medium text-muted-foreground">{section.title}</h3>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
{section.tabs.map((tab) => (
|
||||
|
@ -166,7 +130,7 @@ export function SettingsDialog({
|
|||
"flex w-full items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium",
|
||||
tab.id === selectedTab
|
||||
? "bg-accent text-accent-foreground"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground"
|
||||
: "text-muted-foreground hover:bg-accent hover:text-accent-foreground",
|
||||
)}
|
||||
>
|
||||
<tab.icon className="h-4 w-4" />
|
||||
|
@ -174,9 +138,7 @@ export function SettingsDialog({
|
|||
</button>
|
||||
))}
|
||||
</div>
|
||||
{index < sections.length - 1 && (
|
||||
<Separator className="mx-3 my-2" />
|
||||
)}
|
||||
{index < sections.length - 1 && <Separator className="mx-3 my-2" />}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
@ -184,17 +146,17 @@ export function SettingsDialog({
|
|||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="flex flex-col">
|
||||
<div className="flex-1">
|
||||
{isPending ? (
|
||||
<div className="h-full w-full animate-pulse bg-muted" />
|
||||
) : (
|
||||
currentTab?.content
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col h-full">
|
||||
{isPending ? (
|
||||
<div className="h-full w-full animate-pulse bg-muted" />
|
||||
) : (
|
||||
<ScrollArea className="h-[600px]">
|
||||
<div className="p-6">{currentTab?.content}</div>
|
||||
</ScrollArea>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
)
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { toast } from "sonner"
|
|||
import { CNumbers } from "@/app/_utils/const/numbers"
|
||||
import { CTexts } from "@/app/_utils/const/texts"
|
||||
import { useUserActionsHandler } from "./actions/use-user-actions"
|
||||
import { useGetCurrentUserQuery } from "../_queries/queries"
|
||||
|
||||
// Profile update form schema
|
||||
const profileFormSchema = z.object({
|
||||
|
@ -26,14 +27,11 @@ const profileFormSchema = z.object({
|
|||
|
||||
type ProfileFormValues = z.infer<typeof profileFormSchema>
|
||||
|
||||
interface ProfileFormProps {
|
||||
user: IUserSchema | null
|
||||
onSuccess?: () => void
|
||||
}
|
||||
|
||||
export const useProfileFormHandlers = ({ user, onSuccess }: ProfileFormProps) => {
|
||||
export const useProfileFormHandlers = () => {
|
||||
const { invalidateUsers, invalidateCurrentUser, invalidateUser } = useUserActionsHandler()
|
||||
|
||||
const { data: user } = useGetCurrentUserQuery()
|
||||
|
||||
const {
|
||||
mutateAsync: updateUser,
|
||||
isPending,
|
||||
|
@ -176,7 +174,7 @@ export const useProfileFormHandlers = ({ user, onSuccess }: ProfileFormProps) =>
|
|||
invalidateUser(user.id)
|
||||
|
||||
// Call success callback
|
||||
onSuccess?.()
|
||||
// onSuccess?.()
|
||||
} catch (error) {
|
||||
console.error("Error updating profile:", error)
|
||||
toast.error("Error updating profile")
|
||||
|
@ -191,6 +189,7 @@ export const useProfileFormHandlers = ({ user, onSuccess }: ProfileFormProps) =>
|
|||
isPending,
|
||||
avatarPreview,
|
||||
fileInputRef,
|
||||
user,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue