working on onboarding flow

This commit is contained in:
dimitar committed 2025-12-13 06:26:23 +01:00
1 parent 868eaa5e3d
commit d3a36b6103
19 files changed
+2062 -393

No files matched your search

@@ -5,6 +5,7 @@ import { UserGrid } from "@/components/users/UserGrid";
// import { Button } from "@/components/ui/button";
import { Card, CardHeader, CardContent } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { useUser } from "@clerk/nextjs";
interface User {
id: string;
@@ -13,6 +14,7 @@ interface User {
lastName: string;
role: string;
phone?: string;
gymId?: string;
createdAt: Date;
isCheckedIn?: boolean;
checkInTime?: Date;
@@ -29,6 +31,7 @@ interface User {
}
export function UserManagement() {
const { user } = useUser();
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState<string>("all");
@@ -41,8 +44,32 @@ export function UserManagement() {
email: string;
role: string;
phone: string;
gymId: string;
} | null>(null);
// Active gyms for dropdown
const [gyms, setGyms] = useState<Array<{ id: string; name: string }>>([]);
// Load gyms when modal opens or refreshes
useEffect(() => {
if (isEditing) {
(async () => {
try {
const res = await fetch("/api/gyms");
const data = await res.json();
if (Array.isArray(data)) {
// map down to id and name to avoid extra payload use here
setGyms(data.map((g: any) => ({ id: g.id, name: g.name })));
} else {
setGyms([]);
}
} catch {
setGyms([]);
}
})();
}
}, [isEditing]);
useEffect(() => {
fetchUsers();
}, [filter]);
@@ -74,6 +101,7 @@ export function UserManagement() {
email: user.email,
role: user.role,
phone: user.phone || "",
gymId: user.gymId || "",
});
setIsEditing(true);
};
@@ -150,10 +178,14 @@ export function UserManagement() {
try {
if (selectedUser) {
// Update existing user
const response = await fetch("/api/users", {
method: "PUT",
const response = await fetch("/api/admin/set-user-metadata", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ id: selectedUser.id, ...editForm }),
body: JSON.stringify({
targetUserId: selectedUser.id,
role: editForm.role,
gymId: editForm.gymId === "" ? null : editForm.gymId,
}),
});
if (response.ok) {
setIsEditing(false);
@@ -164,10 +196,14 @@ export function UserManagement() {
}
} else {
// Create (Invite) new user
const response = await fetch("/api/users", {
const response = await fetch("/api/invitations", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(editForm),
body: JSON.stringify({
inviteeEmail: editForm.email,
roleAssigned: editForm.role,
gymId: editForm.gymId || undefined,
}),
});
if (response.ok) {
@@ -232,6 +268,7 @@ export function UserManagement() {
email: "",
role: "client",
phone: "",
gymId: String((user?.publicMetadata as any)?.gymId ?? ""),
});
setSelectedUser(null);
setIsEditing(true);
@@ -308,7 +345,9 @@ export function UserManagement() {
{isEditing && editForm && (
<div className="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div className="bg-white p-6 rounded-lg shadow-lg max-w-md w-full">
<h3 className="text-lg font-semibold mb-4">{selectedUser ? 'Edit User' : 'Invite New User'}</h3>
<h3 className="text-lg font-semibold mb-4">
{selectedUser ? "Edit User" : "Invite New User"}
</h3>
<form
onSubmit={(e) => {
e.preventDefault();
@@ -366,8 +405,8 @@ export function UserManagement() {
className="w-full border border-gray-300 rounded px-3 py-2"
required
>
{/* Ideally we fetch current user role to filter these.
For now, we show all but the API will enforce it.
{/* Ideally we fetch current user role to filter these.
For now, we show all but the API will enforce it.
We can add a visual indicator or fetch "me" to filter. */}
<option value="client">Client</option>
<option value="trainer">Trainer</option>
@@ -389,6 +428,26 @@ export function UserManagement() {
className="w-full border border-gray-300 rounded px-3 py-2"
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">Gym</label>
<select
value={editForm.gymId}
onChange={(e) =>
setEditForm({ ...editForm, gymId: e.target.value })
}
className="w-full border border-gray-300 rounded px-3 py-2"
>
<option value="">Proceed without gym</option>
{gyms.map((g) => (
<option key={g.id} value={g.id}>
{g.name}
</option>
))}
</select>
<p className="text-xs text-gray-500 mt-1">
Select an active gym or proceed without a gym.
</p>
</div>
<div className="flex justify-end gap-2">
<button
type="button"
@@ -404,7 +463,7 @@ export function UserManagement() {
type="submit"
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
{selectedUser ? 'Save Changes' : 'Send Invitation'}
{selectedUser ? "Save Changes" : "Send Invitation"}
</button>
</div>
</form>
@@ -499,8 +558,8 @@ export function UserManagement() {
<span className="font-medium">Last Visit:</span>{" "}
{selectedUser.client.lastVisit
? new Date(
selectedUser.client.lastVisit,
).toLocaleDateString()
selectedUser.client.lastVisit,
).toLocaleDateString()
: "Never"}
</p>
</div>
@@ -513,9 +572,7 @@ export function UserManagement() {
<p>
<span className="font-medium">Last Check-In:</span>{" "}
{selectedUser.lastCheckInTime
? new Date(
selectedUser.lastCheckInTime,
).toLocaleString()
? new Date(selectedUser.lastCheckInTime).toLocaleString()
: "Never"}
</p>
<p>