Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
51 changes: 19 additions & 32 deletions src/app/(admin)/page.tsx
Original file line number Diff line number Diff line change
@@ -1,42 +1,29 @@
import type { Metadata } from "next";
import { EcommerceMetrics } from "@/components/ecommerce/EcommerceMetrics";
import React from "react";
import MonthlyTarget from "@/components/ecommerce/MonthlyTarget";
import MonthlySalesChart from "@/components/ecommerce/MonthlySalesChart";
import StatisticsChart from "@/components/ecommerce/StatisticsChart";
import RecentOrders from "@/components/ecommerce/RecentOrders";
import DemographicCard from "@/components/ecommerce/DemographicCard";
import UserTable from "@/components/users/UserTable";
import { users } from "@/data/users";

export const metadata: Metadata = {
title:
"Next.js E-commerce Dashboard | TailAdmin - Next.js Dashboard Template",
description: "This is Next.js Home for TailAdmin Dashboard Template",
export const metadata = {
title: "Users | TailAdmin",
};

export default function Ecommerce() {
return (
<div className="grid grid-cols-12 gap-4 md:gap-6">
<div className="col-span-12 space-y-6 xl:col-span-7">
<EcommerceMetrics />

<MonthlySalesChart />
</div>
export default function UsersPage() {
const handleEdit = (user: any) => {
console.log("Edit user:", user);
};

<div className="col-span-12 xl:col-span-5">
<MonthlyTarget />
</div>
const handleDelete = (id: string) => {
console.log("Delete user:", id);
};

<div className="col-span-12">
<StatisticsChart />
</div>

<div className="col-span-12 xl:col-span-5">
<DemographicCard />
</div>
return (
<div className="space-y-6">
<h1 className="text-2xl font-semibold">Users</h1>
<UserTable
users={users}
onEdit={handleEdit}
onDelete={handleDelete}
/>

<div className="col-span-12 xl:col-span-7">
<RecentOrders />
</div>
</div>
);
}
26 changes: 26 additions & 0 deletions src/app/(admin)/users/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// user management ui page (mock example)

import React from "react";
import UserTable from "@/components/users/UserTable";
import { users } from "@/data/users";

export const metadata = {
title: "Users | TailAdmin",
};

export default function UsersPage() {
const handleEdit = (user: any) => {
console.log("Edit user:", user);
};

const handleDelete = (id: string) => {
console.log("Delete user:", id);
};

return (
<div className="space-y-6">
<h1 className="text-2xl font-semibold">Users</h1>
<UserTable users={users} onEdit={handleEdit} onDelete={handleDelete} />
</div>
);
}
148 changes: 148 additions & 0 deletions src/components/users/UserModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import React, { useState, useEffect } from "react";
import { User, Role } from "@/types";
import Button from "@/components/ui/button/Button";
import Input from "@/components/form/input/InputField";
import Label from "@/components/form/Label";
// import Modal from "@/components/ui/modal/Modal"; // Assuming a Modal component exists or we build a simple one

interface UserModalProps {
isOpen: boolean;
onClose: () => void;
onSubmit: (data: any) => Promise<void>;
user?: User | null; // If provided, we are editing
isLoading?: boolean;
}

export default function UserModal({
isOpen,
onClose,
onSubmit,
user,
isLoading = false,
}: UserModalProps) {
const [formData, setFormData] = useState({
name: "",
email: "",
password: "",
role: Role.USER,
});

useEffect(() => {
if (user) {
setFormData({
name: user.name || "",
email: user.email || "",
password: "", // Don't populate password for edit
role: user.role,
});
} else {
setFormData({
name: "",
email: "",
password: "",
role: Role.USER,
});
}
}, [user, isOpen]);

const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({ ...prev, [name]: value }));
};

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await onSubmit(formData);
};

if (!isOpen) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center p-4 bg-black/50 backdrop-blur-sm">
<div className="w-full max-w-md bg-white rounded-xl shadow-2xl dark:bg-gray-900 border border-gray-100 dark:border-gray-800 transform transition-all">
<div className="flex items-center justify-between p-6 border-b border-gray-100 dark:border-gray-800">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">
{user ? "Edit User" : "Add New User"}
</h3>
<button
onClick={onClose}
className="text-gray-400 hover:text-gray-500 dark:hover:text-gray-300 transition-colors"
>
<svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>

<form onSubmit={handleSubmit} className="p-6 space-y-4">
<div>
<Label>Name</Label>
<Input
name="name"
placeholder="Full Name"
value={formData.name}
onChange={handleChange}
required
/>
</div>

<div>
<Label>Email</Label>
<Input
type="email"
name="email"
placeholder="Email Address"
value={formData.email}
onChange={handleChange}
required
/>
</div>

<div>
<Label>{user ? "New Password (Optional)" : "Password"}</Label>
<Input
type="password"
name="password"
placeholder={user ? "Leave blank to keep current" : "Secure Password"}
value={formData.password}
onChange={handleChange}
required={!user}
min="8"
/>
</div>

<div>
<Label>Role</Label>
<select
name="role"
value={formData.role}
onChange={handleChange}
className="w-full h-11 px-4 py-2.5 rounded-lg border border-gray-300 bg-white text-gray-700 focus:border-brand-500 focus:ring-2 focus:ring-brand-500/20 outline-none dark:bg-gray-800 dark:border-gray-700 dark:text-gray-300 dark:focus:border-brand-500 transition-all"
>
<option value={Role.USER}>User</option>
<option value={Role.ADMIN}>Admin</option>
</select>
</div>

<div className="flex gap-3 mt-6 pt-4 border-t border-gray-100 dark:border-gray-800">
<Button
variant="outline"
onClick={onClose}
type="button"
className="w-full justify-center"
>
Cancel
</Button>
<Button
disabled={isLoading}
type="submit"
className="w-full justify-center"
>
{isLoading ? "Saving..." : (user ? "Update User" : "Create User")}
</Button>
</div>
</form>
</div>
</div>
);
}
81 changes: 81 additions & 0 deletions src/components/users/UserTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import React from "react";
import { User, Role } from "@/types";
import Button from "@/components/ui/button/Button";

interface UserTableProps {
users: User[];
onEdit: (user: User) => void;
onDelete: (user: User) => void;
}

export default function UserTable({ users, onEdit, onDelete }: UserTableProps) {
return (
<div className="overflow-x-auto rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-gray-50 dark:bg-gray-800">
<th className="px-6 py-4 text-sm font-medium text-gray-500 dark:text-gray-400">Name</th>
<th className="px-6 py-4 text-sm font-medium text-gray-500 dark:text-gray-400">Email</th>
<th className="px-6 py-4 text-sm font-medium text-gray-500 dark:text-gray-400">Role</th>
<th className="px-6 py-4 text-sm font-medium text-gray-500 dark:text-gray-400">Status</th>
<th className="px-6 py-4 text-sm font-medium text-right text-gray-500 dark:text-gray-400">Actions</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-gray-800">
{users.length === 0 ? (
<tr>
<td colSpan={5} className="px-6 py-8 text-center text-gray-500 dark:text-gray-400">
No users found
</td>
</tr>
) : (
users.map((user) => (
<tr key={user.id} className="hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">
<td className="px-6 py-4 text-sm text-gray-800 dark:text-gray-200">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-full bg-brand-100 flex items-center justify-center text-brand-600 font-bold text-xs uppercase dark:bg-brand-900/30 dark:text-brand-400">
{user.name ? user.name.slice(0, 2) : "NA"}
</div>
<span className="font-medium">{user.name || "N/A"}</span>
</div>
</td>
<td className="px-6 py-4 text-sm text-gray-600 dark:text-gray-400">{user.email}</td>
<td className="px-6 py-4 text-sm">
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${user.role === Role.ADMIN
? "bg-purple-100 text-purple-800 dark:bg-purple-900/30 dark:text-purple-400"
: "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400"
}`}
>
{user.role}
</span>
</td>
<td className="px-6 py-4 text-sm">
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400">
Active
</span>
</td>
<td className="px-6 py-4 text-right">
<div className="flex items-center justify-end gap-2">
<button
onClick={() => onEdit(user)}
className="text-gray-500 hover:text-brand-600 dark:text-gray-400 dark:hover:text-brand-400 transition-colors"
>
Edit
</button>
<button
onClick={() => onDelete(user)}
className="text-gray-500 hover:text-red-600 dark:text-gray-400 dark:hover:text-red-400 transition-colors"
>
Delete
</button>
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
);
}
16 changes: 16 additions & 0 deletions src/data/users.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const users = [
{
id: "1",
name: "Admin User",
email: "admin@example.com",
role: "Admin",
status: "Active",
},
{
id: "2",
name: "Regular User",
email: "user@example.com",
role: "User",
status: "Inactive",
},
];