Started to work the shortcodes
This commit is contained in:
65
frontend/app/(auth)/dashboard/settings/shortcodes/page.tsx
Normal file
65
frontend/app/(auth)/dashboard/settings/shortcodes/page.tsx
Normal file
@@ -0,0 +1,65 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { ShortcodeTable } from "@/components/shortcodes-table";
|
||||
import type IShortcode from "@/interfaces/IShortcode";
|
||||
import { request, useApi } from "@/hooks/use-api";
|
||||
import { Loader2 } from "lucide-react";
|
||||
|
||||
export default function ShortcodesPage() {
|
||||
const {
|
||||
data: shortcodes,
|
||||
error,
|
||||
isLoading,
|
||||
mutate,
|
||||
success,
|
||||
} = useApi<IShortcode[]>("/shortcodes", undefined, true);
|
||||
|
||||
const handleUpdate = async (updatedShortcode: IShortcode) => {
|
||||
const res = await request<IShortcode>(
|
||||
`/shortcodes/${updatedShortcode.code}/update`,
|
||||
{
|
||||
method: "PATCH",
|
||||
requiresAuth: true,
|
||||
body: updatedShortcode,
|
||||
},
|
||||
);
|
||||
mutate();
|
||||
// Implement update logic here
|
||||
console.log("Update shortcode:", updatedShortcode);
|
||||
};
|
||||
|
||||
const handleDelete = async (code: string) => {
|
||||
const res = await request<undefined>(`/shortcodes/${code}/delete`, {
|
||||
requiresAuth: true,
|
||||
method: "DELETE",
|
||||
});
|
||||
mutate();
|
||||
};
|
||||
|
||||
const handleAdd = async (newShortcode: Omit<IShortcode, "id">) => {
|
||||
const res = await request<IShortcode>(`/shortcodes/new`, {
|
||||
body: newShortcode,
|
||||
method: "POST",
|
||||
requiresAuth: true,
|
||||
});
|
||||
console.log(res);
|
||||
mutate();
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="container mx-auto py-10">
|
||||
<h1 className="text-2xl font-bold mb-5">Shortcodes</h1>
|
||||
{isLoading && (
|
||||
<Loader2 className="flex w-full min-w-0 flex-col gap-1 justify-center animate-spin" />
|
||||
)}
|
||||
{error && <p>{error}</p>}
|
||||
<ShortcodeTable
|
||||
shortcodes={shortcodes ?? []}
|
||||
onUpdate={handleUpdate}
|
||||
onDelete={handleDelete}
|
||||
onAdd={handleAdd}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -95,7 +95,12 @@ const data = {
|
||||
items: [
|
||||
{
|
||||
title: "Media",
|
||||
url: "/dashboard/media",
|
||||
url: "/dashboard/settings/media",
|
||||
icon: Camera,
|
||||
},
|
||||
{
|
||||
title: "Shortcodes",
|
||||
url: "/dashboard/settings/shortcodes",
|
||||
icon: Camera,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
"use client";
|
||||
import { Book, Menu, Sunset, Trees, Zap } from "lucide-react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
@@ -18,6 +19,8 @@ import {
|
||||
SheetTrigger,
|
||||
} from "@/components/ui/sheet";
|
||||
import Link from "next/link";
|
||||
import { deleteCookie, getCookie } from "cookies-next";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
const subMenuItemsOne = [
|
||||
{
|
||||
@@ -67,6 +70,11 @@ const subMenuItemsTwo = [
|
||||
];
|
||||
|
||||
const Navbar = () => {
|
||||
const [cookie, setCookie] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
const _cookie = getCookie("auth_token");
|
||||
setCookie(_cookie?.toString() ?? null);
|
||||
}, []);
|
||||
return (
|
||||
<section className="sticky top-0 z-50 bg-background p-4">
|
||||
<div>
|
||||
@@ -133,11 +141,26 @@ const Navbar = () => {
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<div className="flex gap-2 animate-in ease-in-out">
|
||||
<Button variant="outline">
|
||||
<Link href="/login">Se connecter</Link>
|
||||
{cookie ? (
|
||||
<Link href="/dashboard">Compte</Link>
|
||||
) : (
|
||||
<Link href="/login">Se connecter</Link>
|
||||
)}
|
||||
</Button>
|
||||
<Button>Sign up</Button>
|
||||
{cookie ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
deleteCookie("auth_token");
|
||||
setCookie(null);
|
||||
}}
|
||||
>
|
||||
Se déconnecter
|
||||
</Button>
|
||||
) : (
|
||||
<Button>Créer un compte</Button>
|
||||
)}
|
||||
</div>
|
||||
</nav>
|
||||
<div className="block lg:hidden">
|
||||
|
||||
120
frontend/components/shortcode-dialogue.tsx
Normal file
120
frontend/components/shortcode-dialogue.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
"use client";
|
||||
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@/components/ui/select";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import type IShortcode from "@/interfaces/IShortcode";
|
||||
|
||||
interface ShortcodeDialogProps {
|
||||
onSave: (shortcode: Omit<IShortcode, "id">) => void;
|
||||
}
|
||||
|
||||
export default function ShortcodeDialog({ onSave }: ShortcodeDialogProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [code, setCode] = useState("");
|
||||
const [type, setType] = useState<"value" | "media">("value");
|
||||
const [value, setValue] = useState("");
|
||||
const [mediaId, setMediaId] = useState("");
|
||||
|
||||
const handleSave = () => {
|
||||
onSave({ code, type, value, media_id: mediaId });
|
||||
setOpen(false);
|
||||
resetForm();
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setCode("");
|
||||
setType("value");
|
||||
setValue("");
|
||||
setMediaId("");
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={setOpen}>
|
||||
<DialogTrigger asChild>
|
||||
<Button variant="outline">Add New Shortcode</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Add New Shortcode</DialogTitle>
|
||||
<DialogDescription>
|
||||
Create a new shortcode here. Click save when you're
|
||||
done.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div className="grid gap-4 py-4">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="code" className="text-right">
|
||||
Code
|
||||
</Label>
|
||||
<Input
|
||||
id="code"
|
||||
value={code}
|
||||
onChange={(e) => setCode(e.target.value)}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
<Tabs
|
||||
defaultValue={type}
|
||||
onValueChange={(v) => setType(v as "value" | "media")}
|
||||
className="w-full"
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="value">Value</TabsTrigger>
|
||||
<TabsTrigger value="media">Media</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="value">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="value" className="text-right">
|
||||
Value
|
||||
</Label>
|
||||
<Input
|
||||
id="value"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent value="media">
|
||||
<div className="grid grid-cols-4 items-center gap-4">
|
||||
<Label htmlFor="mediaId" className="text-right">
|
||||
Media ID
|
||||
</Label>
|
||||
<Input
|
||||
id="mediaId"
|
||||
value={mediaId}
|
||||
onChange={(e) => setMediaId(e.target.value)}
|
||||
className="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit" onClick={handleSave}>
|
||||
Save shortcode
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
102
frontend/components/shortcodes-table.tsx
Normal file
102
frontend/components/shortcodes-table.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from "@/components/ui/dropdown-menu";
|
||||
import { MoreHorizontal } from "lucide-react";
|
||||
import type IShortcode from "@/interfaces/IShortcode";
|
||||
import ShortcodeDialog from "@/components/shortcode-dialogue";
|
||||
|
||||
interface ShortcodeTableProps {
|
||||
shortcodes: IShortcode[];
|
||||
onUpdate: (shortcode: IShortcode) => void;
|
||||
onDelete: (id: string) => void;
|
||||
onAdd: (shortcode: Omit<IShortcode, "id">) => void;
|
||||
}
|
||||
|
||||
export function ShortcodeTable({
|
||||
shortcodes,
|
||||
onUpdate,
|
||||
onDelete,
|
||||
onAdd,
|
||||
}: ShortcodeTableProps) {
|
||||
return (
|
||||
<div>
|
||||
<div className="mb-4">
|
||||
<ShortcodeDialog onSave={onAdd} />
|
||||
</div>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>ID</TableHead>
|
||||
<TableHead>Code</TableHead>
|
||||
<TableHead>Type</TableHead>
|
||||
<TableHead>Value</TableHead>
|
||||
<TableHead>Media ID</TableHead>
|
||||
<TableHead className="w-[80px]">Actions</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{shortcodes.map((shortcode) => (
|
||||
<TableRow key={shortcode.id}>
|
||||
<TableCell>{shortcode.id}</TableCell>
|
||||
<TableCell>{shortcode.code}</TableCell>
|
||||
<TableCell>{shortcode.type}</TableCell>
|
||||
<TableCell>
|
||||
{shortcode.value || "N/A"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{shortcode.media_id || "N/A"}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="h-8 w-8 p-0"
|
||||
>
|
||||
<span className="sr-only">
|
||||
Open menu
|
||||
</span>
|
||||
<MoreHorizontal className="h-4 w-4" />
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
onUpdate(shortcode)
|
||||
}
|
||||
>
|
||||
Update
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem
|
||||
onClick={() =>
|
||||
onDelete(shortcode.code)
|
||||
}
|
||||
>
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
120
frontend/components/ui/table.tsx
Normal file
120
frontend/components/ui/table.tsx
Normal file
@@ -0,0 +1,120 @@
|
||||
import * as React from "react";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const Table = React.forwardRef<
|
||||
HTMLTableElement,
|
||||
React.HTMLAttributes<HTMLTableElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div className="relative w-full overflow-auto">
|
||||
<table
|
||||
ref={ref}
|
||||
className={cn("w-full caption-bottom text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
));
|
||||
Table.displayName = "Table";
|
||||
|
||||
const TableHeader = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<thead ref={ref} className={cn("[&_tr]:border-b", className)} {...props} />
|
||||
));
|
||||
TableHeader.displayName = "TableHeader";
|
||||
|
||||
const TableBody = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tbody
|
||||
ref={ref}
|
||||
className={cn("[&_tr:last-child]:border-0", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableBody.displayName = "TableBody";
|
||||
|
||||
const TableFooter = React.forwardRef<
|
||||
HTMLTableSectionElement,
|
||||
React.HTMLAttributes<HTMLTableSectionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tfoot
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-t bg-muted/50 font-medium [&>tr]:last:border-b-0",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableFooter.displayName = "TableFooter";
|
||||
|
||||
const TableRow = React.forwardRef<
|
||||
HTMLTableRowElement,
|
||||
React.HTMLAttributes<HTMLTableRowElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<tr
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableRow.displayName = "TableRow";
|
||||
|
||||
const TableHead = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.ThHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<th
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableHead.displayName = "TableHead";
|
||||
|
||||
const TableCell = React.forwardRef<
|
||||
HTMLTableCellElement,
|
||||
React.TdHTMLAttributes<HTMLTableCellElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<td
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCell.displayName = "TableCell";
|
||||
|
||||
const TableCaption = React.forwardRef<
|
||||
HTMLTableCaptionElement,
|
||||
React.HTMLAttributes<HTMLTableCaptionElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<caption
|
||||
ref={ref}
|
||||
className={cn("mt-4 text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
TableCaption.displayName = "TableCaption";
|
||||
|
||||
export {
|
||||
Table,
|
||||
TableHeader,
|
||||
TableBody,
|
||||
TableFooter,
|
||||
TableHead,
|
||||
TableRow,
|
||||
TableCell,
|
||||
TableCaption,
|
||||
};
|
||||
@@ -11,7 +11,7 @@ export interface ApiResponse<T> {
|
||||
}
|
||||
|
||||
export async function request<T>(
|
||||
url: string,
|
||||
endpoint: string,
|
||||
options: {
|
||||
method?: "GET" | "POST" | "PATCH" | "DELETE";
|
||||
body?: any;
|
||||
@@ -39,7 +39,7 @@ export async function request<T>(
|
||||
headers.Authorization = `Bearer ${authToken}`;
|
||||
}
|
||||
|
||||
const response = await fetch(`${API_URL}${url}`, {
|
||||
const response = await fetch(`${API_URL}${endpoint}`, {
|
||||
method,
|
||||
headers,
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
|
||||
10
frontend/interfaces/IShortcode.ts
Normal file
10
frontend/interfaces/IShortcode.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import Media from "./Media";
|
||||
|
||||
export default interface IShortcode {
|
||||
id: number;
|
||||
code: string;
|
||||
type: string;
|
||||
value?: string;
|
||||
media_id?: string;
|
||||
media?: Media;
|
||||
}
|
||||
Reference in New Issue
Block a user