Skip to content

Commit a4681f0

Browse files
committed
refactor(profiles): remove profile-table and add implementation using table component, update actions for improved readability
1 parent 4106e8a commit a4681f0

File tree

8 files changed

+170
-206
lines changed

8 files changed

+170
-206
lines changed

src/actions/profiles.ts

Lines changed: 0 additions & 28 deletions
This file was deleted.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"use server";
2+
3+
import { createClient } from "@/lib/supabase/server";
4+
5+
export async function deleteProfileByAdmin(profileId: string) {
6+
const supabase = await createClient();
7+
const { error } = await supabase
8+
.from("profiles")
9+
.delete()
10+
.eq("id", profileId);
11+
12+
if (error) throw new Error("Failed to delete post");
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
"use server";
2+
3+
import { createClient } from "@/lib/supabase/server";
4+
import type { Profile } from "@/types";
5+
6+
export const getProfilesByAdmin = async (): Promise<Profile[]> => {
7+
const supabase = await createClient();
8+
9+
const { data, error } = await supabase.from("profiles").select("*");
10+
11+
if (error) throw new Error("Failed to fetch profiles");
12+
return data;
13+
};

src/actions/profiles/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "./delete-profile-by-admin";
2+
export * from "./get-profiles-by-admin";
3+
export * from "./update-profile-role-by-admin";
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"use server";
2+
3+
import { createClient } from "@/lib/supabase/server";
4+
5+
export const updateProfileRole = async (id: string, newRole: string) => {
6+
const supabase = await createClient();
7+
8+
const { error } = await supabase
9+
.from("profiles")
10+
.update({ role: newRole })
11+
.eq("id", id);
12+
13+
if (error) throw new Error("Failed to update profile role");
14+
};

src/app/(private)/dashboard/profiles/client.tsx

Lines changed: 127 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,41 @@
22

33
import { useState } from "react";
44
import { useRouter } from "next/navigation";
5-
import { useDisclosure, addToast } from "@heroui/react";
6-
7-
import { deleteCategory } from "@/actions/categories";
8-
import { DeleteModal, HeadingDashboard } from "@/components/ui";
9-
10-
import type { Profile } from "@/types";
11-
import { ProfileTable } from "@/components/profiles";
12-
import { updateProfileRole } from "@/actions/profiles";
5+
import {
6+
useDisclosure,
7+
addToast,
8+
Image,
9+
Chip,
10+
Dropdown,
11+
DropdownTrigger,
12+
Button,
13+
DropdownMenu,
14+
DropdownItem,
15+
} from "@heroui/react";
16+
import { deleteProfileByAdmin, updateProfileRole } from "@/actions/profiles";
17+
import { DeleteModal, HeadingDashboard, Table } from "@/components/ui";
18+
import { ChevronDown, Trash2 } from "lucide-react";
19+
import { capitalize } from "@/lib/utils";
20+
import { ROLE_OPTIONS } from "@/config/constants";
21+
import type { Profile, TableAction, TableColumn } from "@/types";
1322

1423
type Props = { profiles: Profile[] };
1524

1625
export default function ProfilesPageClient({ profiles }: Props) {
1726
const router = useRouter();
27+
28+
// States
1829
const [profileToDelete, setProfileToDelete] = useState<string | null>(null);
1930
const [loading, setLoading] = useState<boolean>(false);
2031

32+
// Hooks
2133
const {
2234
isOpen: isConfirmOpen,
2335
onOpen: onConfirmOpen,
2436
onClose: onConfirmClose,
2537
} = useDisclosure();
2638

39+
// Functions
2740
const handleRoleChange = async (id: string, newRole: string) => {
2841
try {
2942
await updateProfileRole(id, newRole);
@@ -50,7 +63,7 @@ export default function ProfilesPageClient({ profiles }: Props) {
5063
if (!profileToDelete) return;
5164
setLoading(true);
5265
try {
53-
await deleteCategory(profileToDelete);
66+
await deleteProfileByAdmin(profileToDelete);
5467
addToast({
5568
title: "Deleted",
5669
description: "Profile deleted successfully.",
@@ -66,16 +79,117 @@ export default function ProfilesPageClient({ profiles }: Props) {
6679
}
6780
};
6881

82+
// Constants
83+
const columns: TableColumn[] = [
84+
{ name: "Avatar", uid: "avatar_url" },
85+
{ name: "Full Name", uid: "full_name", sortable: true },
86+
{ name: "Username", uid: "username", sortable: true },
87+
{ name: "Created At", uid: "created_at", sortable: true },
88+
{ name: "Role", uid: "role" },
89+
{ name: "Status", uid: "status" },
90+
];
91+
92+
const roleOptions = [
93+
{ uid: "user", name: "User" },
94+
{ uid: "admin", name: "Admin" },
95+
];
96+
97+
const actions: TableAction[] = [
98+
{
99+
key: "delete",
100+
label: "Delete profile",
101+
icon: <Trash2 size={18} />,
102+
color: "danger",
103+
shortcut: "⌘D",
104+
onAction: (profile) => handleDelete(profile.id),
105+
},
106+
];
107+
69108
return (
70109
<div className="flex flex-col gap-4">
71110
{/* Heading */}
72111
<HeadingDashboard title="Profiles" count={profiles.length} />
73112

74113
{/* Table */}
75-
<ProfileTable
76-
profiles={profiles}
77-
handleRoleChange={handleRoleChange}
78-
handleDelete={handleDelete}
114+
<Table
115+
title="Profiles table"
116+
data={profiles}
117+
columns={columns}
118+
actions={actions}
119+
searchPlaceholder="Search by full name or username..."
120+
searchKeys={["full_name", "username"]}
121+
defaultRowsPerPage={10}
122+
rowsPerPageOptions={[10, 25, 50, 100]}
123+
filterKey="role"
124+
filterLabel="Role"
125+
filterOptions={roleOptions}
126+
cellRenderer={({ item, columnKey, value }) => {
127+
switch (columnKey) {
128+
case "avatar_url":
129+
return (
130+
<Image
131+
radius="sm"
132+
src={value}
133+
alt="Current image"
134+
width={24}
135+
height={24}
136+
className="border-2 border-neutral-200 dark:border-neutral-800"
137+
/>
138+
);
139+
case "created_at":
140+
return new Date(value).toLocaleDateString();
141+
case "role":
142+
return (
143+
<Dropdown
144+
classNames={{
145+
content:
146+
"bg-white dark:bg-neutral-950 border-2 border-neutral-200 dark:border-neutral-800",
147+
}}
148+
>
149+
<DropdownTrigger>
150+
<Button
151+
size="sm"
152+
variant="bordered"
153+
className="min-w-[110px] border-2 border-neutral-200 dark:border-neutral-800"
154+
endContent={<ChevronDown size={16} />}
155+
>
156+
{capitalize(value)}
157+
</Button>
158+
</DropdownTrigger>
159+
<DropdownMenu
160+
aria-label="Select Role"
161+
disallowEmptySelection
162+
selectionMode="single"
163+
onAction={(key) =>
164+
handleRoleChange(item.id, key.toString())
165+
}
166+
>
167+
{ROLE_OPTIONS.map((role) => (
168+
<DropdownItem key={role.key}>{role.label}</DropdownItem>
169+
))}
170+
</DropdownMenu>
171+
</Dropdown>
172+
);
173+
case "status":
174+
return (
175+
<Chip
176+
size="sm"
177+
color={
178+
value === "active"
179+
? "success"
180+
: value === "inactive"
181+
? "danger"
182+
: "default"
183+
}
184+
variant="flat"
185+
>
186+
{capitalize(value)}
187+
</Chip>
188+
);
189+
default:
190+
return value;
191+
}
192+
}}
79193
/>
80194

81195
{/* Delete Modal */}

src/components/profiles/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)