Member page
This commit is contained in:
@@ -1,12 +1,15 @@
|
|||||||
FROM golang:alpine
|
FROM golang:alpine AS build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
RUN go mod tidy
|
|
||||||
|
|
||||||
RUN go build main.go
|
RUN go build -o /app .
|
||||||
|
|
||||||
CMD ["./main"]
|
FROM scratch AS final
|
||||||
|
|
||||||
|
COPY --from=build /app /app
|
||||||
|
|
||||||
|
CMD ["/app"]
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ services:
|
|||||||
build:
|
build:
|
||||||
context: ./frontend/
|
context: ./frontend/
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
target: final
|
||||||
depends_on:
|
depends_on:
|
||||||
- latosa-escrima.fr-backend
|
- latosa-escrima.fr-backend
|
||||||
env_file: .env
|
env_file: .env
|
||||||
|
|||||||
221
frontend/app/(auth)/dashboard/members/[uuid]/page.tsx
Normal file
221
frontend/app/(auth)/dashboard/members/[uuid]/page.tsx
Normal file
@@ -0,0 +1,221 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { UserIcon, Building, X } from "lucide-react";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@/components/ui/select";
|
||||||
|
import { Role, User } from "@/types/types";
|
||||||
|
import { useParams } from "next/navigation";
|
||||||
|
import { useApi } from "@/hooks/use-api";
|
||||||
|
import { useState } from "react";
|
||||||
|
import request from "@/lib/request";
|
||||||
|
|
||||||
|
export default function UserDetailsPage() {
|
||||||
|
const { uuid } = useParams<{ uuid: string }>();
|
||||||
|
const user = useApi<User>(`/users/${uuid}`, {}, true);
|
||||||
|
|
||||||
|
const availableRoles = useApi<Role[]>("/roles", {}, true);
|
||||||
|
availableRoles.data ??= [];
|
||||||
|
const [selectedRole, setSelectedRole] = useState<Role | null>(null);
|
||||||
|
// const [selectedOrg, setSelectedOrg] = useState("");
|
||||||
|
|
||||||
|
const addRole = async (role: Role) => {
|
||||||
|
const res = await request(
|
||||||
|
`/users/${user.data?.userId}/roles/${role.id}/add`,
|
||||||
|
{ method: "PATCH", requiresAuth: true },
|
||||||
|
);
|
||||||
|
if (res.status === "Success") {
|
||||||
|
setSelectedRole(null);
|
||||||
|
user.mutate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeRole = async (role: Role) => {
|
||||||
|
const res = await request(
|
||||||
|
`/users/${user.data?.userId}/roles/${role.id}/remove`,
|
||||||
|
{ method: "PATCH", requiresAuth: true },
|
||||||
|
);
|
||||||
|
if (res.status === "Success") user.mutate();
|
||||||
|
};
|
||||||
|
|
||||||
|
const addOrganization = () => {
|
||||||
|
// if (selectedOrg && !user.organizations.includes(selectedOrg)) {
|
||||||
|
// setUser((prevUser) => ({
|
||||||
|
// ...prevUser,
|
||||||
|
// organizations: [...prevUser.organizations, selectedOrg],
|
||||||
|
// }));
|
||||||
|
// setSelectedOrg("");
|
||||||
|
// }
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeOrganization = (orgToRemove: string) => {
|
||||||
|
// setUser((prevUser) => ({
|
||||||
|
// ...prevUser,
|
||||||
|
// organizations: prevUser.organizations.filter(
|
||||||
|
// (org) => org !== orgToRemove,
|
||||||
|
// ),
|
||||||
|
// }));
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!user.data || !user.success) return <p>Error</p>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container mx-auto py-10">
|
||||||
|
<Card>
|
||||||
|
<CardContent className="pt-6">
|
||||||
|
<div className="grid gap-6">
|
||||||
|
<div className="flex items-center space-x-4">
|
||||||
|
<UserIcon className="h-12 w-12 text-gray-400" />
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold">
|
||||||
|
{user.data.firstname} {user.data.lastname}
|
||||||
|
</h2>
|
||||||
|
<p className="text-sm text-gray-500">
|
||||||
|
{user.data.email}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-2">
|
||||||
|
Rôles
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{user.data.roles?.map((role) => (
|
||||||
|
<Badge
|
||||||
|
key={role.id}
|
||||||
|
variant="secondary"
|
||||||
|
className="text-sm py-1 px-2"
|
||||||
|
>
|
||||||
|
{role.name}
|
||||||
|
<button
|
||||||
|
onClick={() => removeRole(role)}
|
||||||
|
className="ml-2 text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mt-2 flex space-x-2">
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
selectedRole
|
||||||
|
? selectedRole.name
|
||||||
|
: ""
|
||||||
|
}
|
||||||
|
onValueChange={(s) => {
|
||||||
|
const r = availableRoles.data?.find(
|
||||||
|
(r) => r.name === s,
|
||||||
|
);
|
||||||
|
console.log(r);
|
||||||
|
if (r) setSelectedRole(r);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[180px]">
|
||||||
|
<SelectValue placeholder="Select an organization" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{availableRoles.data
|
||||||
|
.filter(
|
||||||
|
(org) =>
|
||||||
|
!user.data?.roles?.includes(
|
||||||
|
org,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map((role) => (
|
||||||
|
<SelectItem
|
||||||
|
key={role.id}
|
||||||
|
value={role.name}
|
||||||
|
>
|
||||||
|
{role.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Button
|
||||||
|
disabled={!user.data || !selectedRole}
|
||||||
|
onClick={() => addRole(selectedRole!)}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
<Building className="mr-2 h-4 w-4" />
|
||||||
|
Ajouter le rôle
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/*<div>
|
||||||
|
<h3 className="text-lg font-semibold mb-2">
|
||||||
|
Organizations
|
||||||
|
</h3>
|
||||||
|
<div className="flex flex-wrap gap-2">
|
||||||
|
{user.data.organizations.map((org) => (
|
||||||
|
<Badge
|
||||||
|
key={org}
|
||||||
|
variant="outline"
|
||||||
|
className="text-sm py-1 px-2"
|
||||||
|
>
|
||||||
|
{org}
|
||||||
|
<button
|
||||||
|
onClick={() =>
|
||||||
|
removeOrganization(org)
|
||||||
|
}
|
||||||
|
className="ml-2 text-gray-500 hover:text-gray-700"
|
||||||
|
>
|
||||||
|
<X className="h-3 w-3" />
|
||||||
|
</button>
|
||||||
|
</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
<div className="mt-2 flex space-x-2">
|
||||||
|
<Select
|
||||||
|
value={selectedOrg}
|
||||||
|
onValueChange={setSelectedOrg}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[180px]">
|
||||||
|
<SelectValue placeholder="Select an organization" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{availableOrganizations
|
||||||
|
.filter(
|
||||||
|
(org) =>
|
||||||
|
!user.organizations.includes(
|
||||||
|
org,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.map((org) => (
|
||||||
|
<SelectItem
|
||||||
|
key={org}
|
||||||
|
value={org}
|
||||||
|
>
|
||||||
|
{org}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<Button
|
||||||
|
onClick={addOrganization}
|
||||||
|
className="flex items-center"
|
||||||
|
>
|
||||||
|
<Building className="mr-2 h-4 w-4" />
|
||||||
|
Add Org
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -16,7 +16,8 @@ import { PhotoDialog } from "@/components/photo-dialog";
|
|||||||
import useFileUpload from "@/hooks/use-file-upload";
|
import useFileUpload from "@/hooks/use-file-upload";
|
||||||
import useMedia from "@/hooks/use-media";
|
import useMedia from "@/hooks/use-media";
|
||||||
import Media from "@/interfaces/Media";
|
import Media from "@/interfaces/Media";
|
||||||
import useApiMutation, { request } from "@/hooks/use-api";
|
import useApiMutation from "@/hooks/use-api";
|
||||||
|
import request from "@/lib/request";
|
||||||
|
|
||||||
export default function PhotoGallery() {
|
export default function PhotoGallery() {
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ import {
|
|||||||
} from "@/components/ui/dialog";
|
} from "@/components/ui/dialog";
|
||||||
import { ChevronDown, ChevronRight, Plus, Trash2 } from "lucide-react";
|
import { ChevronDown, ChevronRight, Plus, Trash2 } from "lucide-react";
|
||||||
import { toTitleCase } from "@/lib/utils";
|
import { toTitleCase } from "@/lib/utils";
|
||||||
import { request, useApi } from "@/hooks/use-api";
|
import { useApi } from "@/hooks/use-api";
|
||||||
|
import request from "@/lib/request";
|
||||||
|
|
||||||
type Action = string;
|
type Action = string;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { ShortcodeTable } from "@/components/shortcodes-table";
|
import { ShortcodeTable } from "@/components/shortcodes-table";
|
||||||
import type IShortcode from "@/interfaces/IShortcode";
|
import type IShortcode from "@/interfaces/IShortcode";
|
||||||
import { request, useApi } from "@/hooks/use-api";
|
import { useApi } from "@/hooks/use-api";
|
||||||
|
import request from "@/lib/request";
|
||||||
import { Loader2 } from "lucide-react";
|
import { Loader2 } from "lucide-react";
|
||||||
|
|
||||||
export default function ShortcodesPage() {
|
export default function ShortcodesPage() {
|
||||||
|
|||||||
@@ -2,12 +2,25 @@ import { ExternalLink } from "lucide-react";
|
|||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { API_URL } from "@/lib/constants";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
const Hero = () => {
|
const Hero = () => {
|
||||||
|
const background = `${API_URL}/media/591ab183-c72d-46ff-905c-ec04fed1bb34/file`;
|
||||||
return (
|
return (
|
||||||
<section className="relative flex h-[calc(100vh-68px)] items-center justify-center overflow-hidden py-32">
|
<section className="relative flex h-[calc(100vh-68px)] items-center justify-center overflow-hidden py-32">
|
||||||
<div className="">
|
<div className="">
|
||||||
<div className="magicpattern absolute inset-x-0 top-0 -z-10 flex h-full w-full items-center justify-center bg-blue-50 opacity-100" />
|
<Image
|
||||||
|
src={background}
|
||||||
|
layout="fill"
|
||||||
|
objectFit="cover"
|
||||||
|
priority
|
||||||
|
alt="Hero image"
|
||||||
|
unoptimized
|
||||||
|
className="grayscale"
|
||||||
|
/>
|
||||||
|
{/* Gradient and Blur Overlay */}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-transparent to-transparent bg-opacity-30 backdrop-blur-sm"></div>
|
||||||
<div className="mx-auto flex max-w-5xl flex-col items-center">
|
<div className="mx-auto flex max-w-5xl flex-col items-center">
|
||||||
<div className="z-10 flex flex-col items-center gap-6 text-center">
|
<div className="z-10 flex flex-col items-center gap-6 text-center">
|
||||||
<img
|
<img
|
||||||
@@ -17,9 +30,9 @@ const Hero = () => {
|
|||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="mb-6 text-pretty text-2xl font-bold text-primary lg:text-5xl">
|
<h1 className="mb-6 text-pretty text-2xl font-bold text-primary lg:text-5xl">
|
||||||
Trouvez votre équilibre
|
Trouvez votre équilibre avec
|
||||||
<br />
|
<br />
|
||||||
avec Latosa-Escrima
|
Latosa-Escrima
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground lg:text-xl">
|
<p className="text-muted-foreground lg:text-xl">
|
||||||
Une évolution des arts martiaux Philippins
|
Une évolution des arts martiaux Philippins
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ import { Button } from "@/components/ui/button";
|
|||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { ScrollArea } from "@/components/ui/scroll-area";
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
||||||
import MemberDialog, { Member } from "./member-dialog";
|
import MemberDialog, { Member } from "./member-dialog";
|
||||||
import * as z from "zod";
|
import { useApi } from "@/hooks/use-api";
|
||||||
import { request, useApi } from "@/hooks/use-api";
|
import request from "@/lib/request";
|
||||||
import {
|
import {
|
||||||
CircleX,
|
CircleX,
|
||||||
Loader2,
|
Loader2,
|
||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
UserRoundPen,
|
UserRoundPen,
|
||||||
UserRoundPlus,
|
UserRoundPlus,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function MembersTable() {
|
export default function MembersTable() {
|
||||||
const {
|
const {
|
||||||
@@ -107,7 +108,7 @@ export default function MembersTable() {
|
|||||||
<TableRow>
|
<TableRow>
|
||||||
{selectMode && (
|
{selectMode && (
|
||||||
<TableHead className="w-[50px]">
|
<TableHead className="w-[50px]">
|
||||||
Selectionner
|
Sélectionner
|
||||||
</TableHead>
|
</TableHead>
|
||||||
)}
|
)}
|
||||||
<TableHead>Prénom</TableHead>
|
<TableHead>Prénom</TableHead>
|
||||||
@@ -140,9 +141,23 @@ export default function MembersTable() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
)}
|
)}
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{member.firstname}
|
<Link
|
||||||
|
href={`/dashboard/members/${member.userId}`}
|
||||||
|
>
|
||||||
|
<span className="underline">
|
||||||
|
{member.firstname}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Link
|
||||||
|
href={`/dashboard/members/${member.userId}`}
|
||||||
|
>
|
||||||
|
<span className="underline">
|
||||||
|
{member.lastname}
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{member.lastname}</TableCell>
|
|
||||||
<TableCell>{member.email}</TableCell>
|
<TableCell>{member.email}</TableCell>
|
||||||
<TableCell>{member.phone}</TableCell>
|
<TableCell>{member.phone}</TableCell>
|
||||||
<TableCell>{member.role}</TableCell>
|
<TableCell>{member.role}</TableCell>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { ApiResponse, request } from "@/hooks/use-api";
|
import { ApiResponse } from "@/types/types";
|
||||||
|
import request from "@/lib/request";
|
||||||
import "@schedule-x/theme-shadcn/dist/index.css";
|
import "@schedule-x/theme-shadcn/dist/index.css";
|
||||||
import { useNextCalendarApp, ScheduleXCalendar } from "@schedule-x/react";
|
import { useNextCalendarApp, ScheduleXCalendar } from "@schedule-x/react";
|
||||||
import { createEventsServicePlugin } from "@schedule-x/events-service";
|
import { createEventsServicePlugin } from "@schedule-x/events-service";
|
||||||
|
|||||||
@@ -1,60 +1,9 @@
|
|||||||
"use client";
|
"use client";
|
||||||
import { API_URL } from "@/lib/constants";
|
import request from "@/lib/request";
|
||||||
import { getCookie } from "cookies-next";
|
import { ApiResponse } from "@/types/types";
|
||||||
import useSWR, { SWRConfiguration } from "swr";
|
import useSWR, { SWRConfiguration } from "swr";
|
||||||
import useSWRMutation, { type SWRMutationConfiguration } from "swr/mutation";
|
import useSWRMutation, { type SWRMutationConfiguration } from "swr/mutation";
|
||||||
|
|
||||||
export interface ApiResponse<T> {
|
|
||||||
status: "Error" | "Success";
|
|
||||||
message: string;
|
|
||||||
data?: T;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function request<T>(
|
|
||||||
endpoint: string,
|
|
||||||
options: {
|
|
||||||
method?: "GET" | "POST" | "PATCH" | "DELETE";
|
|
||||||
body?: any;
|
|
||||||
requiresAuth?: boolean;
|
|
||||||
csrfToken?: boolean;
|
|
||||||
} = {},
|
|
||||||
): Promise<ApiResponse<T>> {
|
|
||||||
const { method = "GET", body, requiresAuth = true } = options;
|
|
||||||
const headers: Record<string, string> = {
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.csrfToken) {
|
|
||||||
const res: ApiResponse<{ csrf: string }> = await (
|
|
||||||
await fetch(`${API_URL}/csrf-token`)
|
|
||||||
).json();
|
|
||||||
if (res.data) headers["X-CSRF-Token"] = res.data.csrf;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (requiresAuth) {
|
|
||||||
const authToken = getCookie("auth_token");
|
|
||||||
if (!authToken) {
|
|
||||||
throw new Error("User is not authenticated");
|
|
||||||
}
|
|
||||||
headers.Authorization = `Bearer ${authToken}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${API_URL}${endpoint}`, {
|
|
||||||
method,
|
|
||||||
headers,
|
|
||||||
body: body ? JSON.stringify(body) : undefined,
|
|
||||||
credentials: options.csrfToken ? "include" : "omit",
|
|
||||||
});
|
|
||||||
|
|
||||||
const apiResponse: ApiResponse<T> = await response.json();
|
|
||||||
|
|
||||||
if (apiResponse.status === "Error") {
|
|
||||||
throw new Error(apiResponse.message || "An unexpected error occurred");
|
|
||||||
}
|
|
||||||
|
|
||||||
return apiResponse;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetcher<T>(
|
async function fetcher<T>(
|
||||||
url: string,
|
url: string,
|
||||||
requiresAuth: boolean = true,
|
requiresAuth: boolean = true,
|
||||||
|
|||||||
56
frontend/lib/request.ts
Normal file
56
frontend/lib/request.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import { API_URL } from "@/lib/constants";
|
||||||
|
import { ApiResponse } from "@/types/types";
|
||||||
|
import { getCookie } from "cookies-next";
|
||||||
|
import { ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
|
||||||
|
export default async function request<T>(
|
||||||
|
endpoint: string,
|
||||||
|
options: {
|
||||||
|
method?: "GET" | "POST" | "PATCH" | "DELETE";
|
||||||
|
body?: any;
|
||||||
|
requiresAuth?: boolean;
|
||||||
|
csrfToken?: boolean;
|
||||||
|
cookies?: () => Promise<ReadonlyRequestCookies>;
|
||||||
|
} = {},
|
||||||
|
): Promise<ApiResponse<T>> {
|
||||||
|
console.log("Hello everyone");
|
||||||
|
const { method = "GET", body, requiresAuth = true } = options;
|
||||||
|
const headers: Record<string, string> = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
};
|
||||||
|
|
||||||
|
if (options.csrfToken) {
|
||||||
|
const res: ApiResponse<{ csrf: string }> = await (
|
||||||
|
await fetch(`${API_URL}/csrf-token`)
|
||||||
|
).json();
|
||||||
|
if (res.data) headers["X-CSRF-Token"] = res.data.csrf;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiresAuth) {
|
||||||
|
let authToken;
|
||||||
|
if (!options.cookies) {
|
||||||
|
authToken = getCookie("auth_token");
|
||||||
|
} else {
|
||||||
|
authToken = (await options.cookies()).get("auth_token")?.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authToken) {
|
||||||
|
throw new Error("User is not authenticated");
|
||||||
|
}
|
||||||
|
headers.Authorization = `Bearer ${authToken}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await fetch(`${API_URL}${endpoint}`, {
|
||||||
|
method,
|
||||||
|
headers,
|
||||||
|
body: body ? JSON.stringify(body) : undefined,
|
||||||
|
credentials: options.csrfToken ? "include" : "omit",
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiResponse: ApiResponse<T> = await response.json();
|
||||||
|
|
||||||
|
if (apiResponse.status === "Error") {
|
||||||
|
throw new Error(apiResponse.message || "An unexpected error occurred");
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiResponse;
|
||||||
|
}
|
||||||
@@ -1,43 +1,58 @@
|
|||||||
|
export interface Permission {
|
||||||
|
resource: string;
|
||||||
|
action: string;
|
||||||
|
}
|
||||||
// Role type as a string literal
|
// Role type as a string literal
|
||||||
export type Role = 'admin' | 'user';
|
export interface Role {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
permissions: Permission[];
|
||||||
|
}
|
||||||
|
|
||||||
// Status type as a string literal
|
// Status type as a string literal
|
||||||
export type Status = 'Active' | 'Inactive';
|
export type Status = "Active" | "Inactive";
|
||||||
|
|
||||||
// Event type (you can expand this type as needed based on your schema)
|
// Event type (you can expand this type as needed based on your schema)
|
||||||
export interface Event {
|
export interface Event {
|
||||||
eventID: string;
|
eventID: string;
|
||||||
title: string;
|
title: string;
|
||||||
date: string; // Assuming ISO date string
|
date: string; // Assuming ISO date string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blog type (you may already have this defined as shown in your previous example)
|
// Blog type (you may already have this defined as shown in your previous example)
|
||||||
export interface Blog {
|
export interface Blog {
|
||||||
blogID: string;
|
blogID: string;
|
||||||
slug: string;
|
slug: string;
|
||||||
content: string;
|
content: string;
|
||||||
label?: string;
|
label?: string;
|
||||||
authorID: string;
|
authorID: string;
|
||||||
published: string;
|
published: string;
|
||||||
summary?: string;
|
summary?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
href?: string;
|
href?: string;
|
||||||
|
|
||||||
author: User; // Relation to User
|
author: User; // Relation to User
|
||||||
}
|
}
|
||||||
|
|
||||||
// User type definition
|
// User type definition
|
||||||
export interface User {
|
export interface User {
|
||||||
userID: string; // UUID represented as a string
|
userId: string; // UUID represented as a string
|
||||||
firstName: string;
|
firstname: string;
|
||||||
lastName: string;
|
lastname: string;
|
||||||
email: string;
|
email: string;
|
||||||
password?: string; // Optional field, since it's omitted in the JSON
|
password?: string; // Optional field, since it's omitted in the JSON
|
||||||
phone: string;
|
phone: string;
|
||||||
role: Role; // 'admin' or 'user'
|
role: Role; // 'admin' or 'user'
|
||||||
createdAt: string; // ISO date string
|
createdAt: string; // ISO date string
|
||||||
updatedAt: string; // ISO date string
|
updatedAt: string; // ISO date string
|
||||||
|
|
||||||
events?: Event[]; // Many-to-many relation with Event (optional)
|
events?: Event[]; // Many-to-many relation with Event (optional)
|
||||||
articles?: Blog[]; // One-to-many relation with Blog (optional)
|
articles?: Blog[]; // One-to-many relation with Blog (optional)
|
||||||
|
roles?: Role[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiResponse<T> {
|
||||||
|
status: "Error" | "Success";
|
||||||
|
message: string;
|
||||||
|
data?: T;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user