Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 41 additions & 7 deletions client/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@stripe/react-stripe-js": "^6.3.0",
"@stripe/stripe-js": "^9.5.0",
"@tailwindcss/vite": "^4.3.0",
"@tanstack/react-table": "^8.21.3",
"axios": "^1.16.0",
"lucide-react": "^1.11.0",
"react": "^19.2.0",
Expand Down
17 changes: 17 additions & 0 deletions client/src/api/contactApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,20 @@ export async function fetchContacts() {
const res = api.get("/contacts");
return res.then((response) => response.data);
}

export async function updateContact(
id: string,
contactData: {
name: string;
email: string;
message: string;
}
) {
const res = api.put(`/contacts/${id}`, contactData);
return res.then((response) => response.data);
}

export async function deleteContact(id: string) {
const res = api.delete(`/contacts/${id}`);
return res.then((response) => response.data);
}
16 changes: 16 additions & 0 deletions client/src/api/usersApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import api from "./index";

export async function fetchMembers() {
const response = await api.get("/users");
return response.data;
}

export async function updateMember(id: string, member: object) {
const response = await api.put(`/users/${id}`, member);
return response.data;
}

export async function deleteMember(id: string) {
const response = await api.delete(`/users/${id}`);
return response.data;
}
70 changes: 70 additions & 0 deletions client/src/components/AdminComponents/AdminDashboard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import type { AdminSection } from "../../pages/Admin.tsx";
import MembersSection from "./MembersSection.tsx";
import ResponsesSection from "./ResponseSection.tsx";
import { Inbox, UserRoundCheck } from "lucide-react";

type AdminDashboardProps = {
activeSection: AdminSection;
};

export default function AdminDashboard({ activeSection }: AdminDashboardProps) {
const section =
activeSection === "members"
? {
description:
"Search membership records, check payment year, and find student details quickly.",
icon: UserRoundCheck,
label: "Members",
title: "Member Details",
}
: {
description:
"Review contact form messages and reply to recent enquiries.",
icon: Inbox,
label: "Inbox",
title: "Contact Form Responses",
};

const Icon = section.icon;

return (
<main className="flex min-w-0 flex-1 flex-col gap-5">
<header className="rounded-lg border border-slate-200 bg-white shadow-sm">
<div className="grid gap-4 px-5 py-5 md:grid-cols-[minmax(0,1fr)_auto] md:items-start">
<div className="flex min-w-0 items-start gap-4">
<span className="mt-1 inline-flex h-12 w-12 shrink-0 items-center justify-center rounded-md bg-blue-medium text-yellow-light shadow-sm">
<Icon aria-hidden="true" className="h-6 w-6" />
</span>

<div className="min-w-0">
<div className="flex flex-wrap items-center gap-2">
<p className="!m-0 !text-sm font-semibold uppercase leading-none tracking-normal text-slate-500">
Admin Dashboard
</p>
<span className="inline-flex rounded-full bg-yellow-light px-3 py-1 text-sm font-semibold leading-none text-blue-medium md:hidden">
{section.label}
</span>
</div>

<h1 className="!mb-0 !mt-2 !text-4xl font-bold leading-tight text-slate-950">
{section.title}
</h1>

<p className="!mb-0 !mt-3 max-w-3xl !text-base leading-6 text-slate-600">
{section.description}
</p>
</div>
</div>

<span className="hidden rounded-full bg-yellow-light px-3 py-1 text-sm font-semibold leading-none text-blue-medium md:inline-flex">
{section.label}
</span>
</div>
</header>

{activeSection === "members" && <MembersSection />}

{activeSection === "responses" && <ResponsesSection />}
</main>
);
}
60 changes: 60 additions & 0 deletions client/src/components/AdminComponents/AdminSidebar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { AdminSection } from "../../pages/Admin.tsx";
import { Inbox, UsersRound } from "lucide-react";
import type { LucideIcon } from "lucide-react";

type AdminSidebarProps = {
activeSection: AdminSection;
onSectionChange: (section: AdminSection) => void;
};

export default function AdminSidebar({
activeSection,
onSectionChange,
}: AdminSidebarProps) {
const navItems: { icon: LucideIcon; label: string; value: AdminSection }[] = [
{ icon: UsersRound, label: "Members", value: "members" },
{ icon: Inbox, label: "Form Responses", value: "responses" },
];

return (
<aside className="w-full shrink-0 overflow-hidden rounded-lg border border-slate-200 bg-white shadow-sm lg:sticky lg:top-4 lg:w-64 lg:self-start">
<div className="border-b border-slate-100 bg-blue-medium px-4 py-4 text-white">
<p className="text-xs font-semibold uppercase tracking-normal text-yellow-dark">
KAC
</p>
<h2 className="mt-1 text-xl font-bold leading-none">Admin</h2>
</div>

<nav className="grid gap-2 p-3 sm:grid-cols-2 lg:grid-cols-1">
{navItems.map((item) => {
const isActive = activeSection === item.value;
const Icon = item.icon;

return (
<button
key={item.value}
type="button"
onClick={() => onSectionChange(item.value)}
className={`group flex h-12 w-full items-center gap-3 rounded-md px-3 text-left text-sm font-semibold transition ${
isActive
? "bg-yellow-light text-blue-medium shadow-sm ring-1 ring-yellow-dark/40"
: "bg-white text-slate-700 hover:bg-slate-50 hover:text-blue-medium"
}`}
>
<span
className={`inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md transition ${
isActive
? "bg-blue-medium text-white"
: "bg-slate-100 text-slate-500 group-hover:bg-yellow-light group-hover:text-blue-medium"
}`}
>
<Icon aria-hidden="true" className="h-4 w-4" />
</span>
<span>{item.label}</span>
</button>
);
})}
</nav>
</aside>
);
}
Loading
Loading