diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 3afe659..255df63 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,7 +1,70 @@ +import { Link } from "react-router-dom"; +import { Button, Container, Stack, Title, Text, Group, Card, Divider } from "@mantine/core"; +import { IconArrowRight, IconRocket, IconTerminal2 } from "@tabler/icons-react"; +import clientRoutes from "@/clientRoutes"; + export default function Home() { return ( -
-

Home

-
+ + + + + + + Welcome to Jenna + + + + A futuristic dashboard experience built with Mantine and Bun — designed for speed, + precision, and modern elegance. Navigate to your dashboard and start exploring. + + + + + + + Built for developers — optimized for 2025 workflows. + + + + + ); } diff --git a/src/pages/scr/dashboard/apikey/apikey_page.tsx b/src/pages/scr/dashboard/apikey/apikey_page.tsx index 7d11393..2375e66 100644 --- a/src/pages/scr/dashboard/apikey/apikey_page.tsx +++ b/src/pages/scr/dashboard/apikey/apikey_page.tsx @@ -7,16 +7,39 @@ import { Table, Text, TextInput, + Title, + ScrollArea, + ActionIcon, + Tooltip, + Divider, + Loader, + Badge, + useMantineTheme, } from "@mantine/core"; import { useEffect, useState } from "react"; -import apiFetch from "@/lib/apiFetch"; +import { + IconPlus, + IconCopy, + IconTrash, + IconKey, + IconDatabase, + IconLock, +} from "@tabler/icons-react"; import { showNotification } from "@mantine/notifications"; +import apiFetch from "@/lib/apiFetch"; export default function ApiKeyPage() { return ( - - - API Key + + + + + + + API Key Management + + + @@ -24,6 +47,7 @@ export default function ApiKeyPage() { } function CreateApiKey() { + const theme = useMantineTheme(); const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [expiredAt, setExpiredAt] = useState(""); @@ -31,6 +55,14 @@ function CreateApiKey() { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); + if (!name || !expiredAt) { + showNotification({ + color: "red", + title: "Missing Information", + message: "Please provide a name and expiration date.", + }); + return; + } setLoading(true); const res = await apiFetch.api.apikey.create.post({ name, @@ -42,52 +74,123 @@ function CreateApiKey() { setDescription(""); setExpiredAt(""); showNotification({ - title: "Success", - message: "API key created successfully", - color: "green", + title: "API Key Created", + message: "Your new API key is now active and ready to use.", + color: "teal", + }); + } else { + showNotification({ + title: "Error", + message: "Failed to create API key. Please try again.", + color: "red", }); } setLoading(false); }; - return ( - - - API Create - setName(e.target.value)} - /> - setDescription(e.target.value)} - /> - setExpiredAt(e.target.value)} - /> - - - - + return ( + + + + + Create New API Key + + + + + + + + +
+ + setName(e.target.value)} + styles={{ + input: { + backgroundColor: "rgba(255,255,255,0.05)", + borderColor: "rgba(255,255,255,0.1)", + }, + }} + /> + setDescription(e.target.value)} + styles={{ + input: { + backgroundColor: "rgba(255,255,255,0.05)", + borderColor: "rgba(255,255,255,0.1)", + }, + }} + /> + setExpiredAt(e.target.value)} + styles={{ + input: { + backgroundColor: "rgba(255,255,255,0.05)", + borderColor: "rgba(255,255,255,0.1)", + }, + }} + /> + + + + + +
+
@@ -95,69 +198,169 @@ function CreateApiKey() { } function ListApiKey() { + const theme = useMantineTheme(); const [apiKeys, setApiKeys] = useState([]); + const [loading, setLoading] = useState(true); + useEffect(() => { const fetchApiKeys = async () => { const res = await apiFetch.api.apikey.list.get(); if (res.status === 200) { setApiKeys(res.data?.apiKeys || []); } + setLoading(false); }; fetchApiKeys(); }, []); + + const handleDelete = async (id: string) => { + const res = await apiFetch.api.apikey.delete.delete({ id }); + if (res.status === 200) { + setApiKeys(apiKeys.filter((api: any) => api.id !== id)); + showNotification({ + title: "API Key Deleted", + message: "The API key has been successfully removed.", + color: "teal", + }); + } + }; + + const handleCopy = (key: string) => { + navigator.clipboard.writeText(key); + showNotification({ + title: "Copied", + message: "API key copied to clipboard.", + color: "cyan", + }); + }; + return ( - - - API List - - - - - - - - - - - - - {apiKeys.map((apiKey: any, index: number) => ( - - - - - - - + + + + + + + ))} + +
NameDescriptionExpired AtCreated AtUpdated AtActions
{apiKey.name}{apiKey.description}{apiKey.expiredAt.toISOString().split("T")[0]}{apiKey.createdAt.toISOString().split("T")[0]}{apiKey.updatedAt.toISOString().split("T")[0]} - {apiKey.name}{apiKey.description || "-"} + + {new Date(apiKey.expiredAt).toLocaleDateString()} + + {new Date(apiKey.createdAt).toLocaleDateString()}{new Date(apiKey.updatedAt).toLocaleDateString()} + + + handleCopy(apiKey.key)} + radius="md" + > + + + + + handleDelete(apiKey.id)} + radius="md" + > + + + + +
+ + )}
); diff --git a/src/pages/scr/dashboard/credential/credential_page.tsx b/src/pages/scr/dashboard/credential/credential_page.tsx index 6959d78..71db342 100644 --- a/src/pages/scr/dashboard/credential/credential_page.tsx +++ b/src/pages/scr/dashboard/credential/credential_page.tsx @@ -1,33 +1,42 @@ import apiFetch from "@/lib/apiFetch"; import { + ActionIcon, Button, Card, Container, + Divider, Flex, Group, Stack, Text, TextInput, Title, + Tooltip, + LoadingOverlay, + Badge, } from "@mantine/core"; import { useShallowEffect } from "@mantine/hooks"; import { showNotification } from "@mantine/notifications"; +import { + IconKey, + IconTrash, + IconPlus, + IconRefresh, + IconShieldLock, +} from "@tabler/icons-react"; import { useState } from "react"; import useSwr from "swr"; import { proxy, subscribe } from "valtio"; -const state = proxy({ - reload: "", -}); - +const state = proxy({ reload: "" }); function reloadState() { state.reload = Math.random().toString(); } export default function CredentialPage() { return ( - - + + @@ -38,38 +47,115 @@ export default function CredentialPage() { function CredentialCreate() { const [name, setName] = useState(""); const [apikey, setApikey] = useState(""); + const [loading, setLoading] = useState(false); async function handleSubmit() { - const { data } = await apiFetch.api.credential.create.post({ - name: name, - value: apikey, - }); - - setName(""); - setApikey(""); - - showNotification({ - message: data?.message, - }); - - reloadState(); + if (!name || !apikey) { + showNotification({ + color: "red", + title: "Missing Information", + message: "Please fill in all required fields before saving.", + }); + return; + } + setLoading(true); + try { + const { data } = await apiFetch.api.credential.create.post({ + name, + value: apikey, + }); + setName(""); + setApikey(""); + showNotification({ + color: "teal", + title: "Credential Saved", + message: data?.message || "Your credential has been successfully added.", + }); + reloadState(); + } catch { + showNotification({ + color: "red", + title: "Error", + message: "Failed to create credential. Please try again.", + }); + } finally { + setLoading(false); + } } + return ( - - - Credential Create + + + + + + Create New Credential + + + + + + + + setName(e.target.value)} + onChange={(e) => setName(e.currentTarget.value)} + styles={{ + input: { + backgroundColor: "rgba(255,255,255,0.05)", + borderColor: "rgba(255,255,255,0.1)", + }, + }} /> setApikey(e.target.value)} + onChange={(e) => setApikey(e.currentTarget.value)} + leftSection={} + styles={{ + input: { + backgroundColor: "rgba(255,255,255,0.05)", + borderColor: "rgba(255,255,255,0.1)", + }, + }} /> - - + + @@ -77,39 +163,133 @@ function CredentialCreate() { } function CredentialList() { - const { data, mutate } = useSwr("/", () => - apiFetch.api.credential.list.get(), + const { data, mutate, isLoading } = useSwr("/", () => + apiFetch.api.credential.list.get() ); useShallowEffect(() => { - const unsubscribe = subscribe(state, async () => { - console.log("state has changed to", state); - mutate(); - }); - + const unsubscribe = subscribe(state, () => mutate()); return () => unsubscribe(); }, []); - async function handleRm(id: string) { - await apiFetch.api.credential.rm.delete({ - id: id, - }); - - reloadState(); + async function handleRemove(id: string) { + try { + await apiFetch.api.credential.rm.delete({ id }); + showNotification({ + color: "teal", + title: "Credential Deleted", + message: "The credential was successfully removed.", + }); + reloadState(); + } catch { + showNotification({ + color: "red", + title: "Error", + message: "Failed to delete credential. Please try again.", + }); + } } + + if (isLoading) + return ( + + + Loading credentials... + + + ); + + const list = data?.data?.list || []; + return ( - - - {data?.data?.list.map((v, k) => ( - - - {v.name} - - - - - - ))} + + + + + Saved Credentials + + + {list.length} Active + + + + {list.length === 0 ? ( + + + + + No credentials have been added yet. + + + + ) : ( + list.map((v: any) => ( + + (e.currentTarget.style.boxShadow = + "0 0 10px rgba(0,255,200,0.2)") + } + onMouseLeave={(e) => + (e.currentTarget.style.boxShadow = "none") + } + > + + + + {v.name} + + + ID: {v.id} + + + + handleRemove(v.id)} + > + + + + + + )) + )} ); diff --git a/src/pages/scr/dashboard/dashboard_layout.tsx b/src/pages/scr/dashboard/dashboard_layout.tsx index f91c9a6..bf371f8 100644 --- a/src/pages/scr/dashboard/dashboard_layout.tsx +++ b/src/pages/scr/dashboard/dashboard_layout.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from "react"; - import { ActionIcon, AppShell, Avatar, + Badge, Button, Card, Divider, @@ -24,29 +24,32 @@ import { IconDashboard, IconKey, IconLock, + IconUser, } from "@tabler/icons-react"; import type { User } from "generated/prisma"; import { Outlet, useLocation, useNavigate } from "react-router-dom"; - -import { - default as clientRoute, - default as clientRoutes, -} from "@/clientRoutes"; +import { default as clientRoute, default as clientRoutes } from "@/clientRoutes"; import apiFetch from "@/lib/apiFetch"; function Logout() { return ( - + ); @@ -60,14 +63,24 @@ export default function DashboardLayout() { return ( - + setOpened((v) => !v)} - aria-label="Toggle navigation" radius="xl" + size="lg" + style={{ + backgroundColor: "rgba(255,255,255,0.05)", + boxShadow: "0 0 6px rgba(0,255,200,0.2)", + }} > {opened ? : } @@ -97,8 +114,17 @@ export default function DashboardLayout() { - - + + {!opened && ( @@ -106,15 +132,14 @@ export default function DashboardLayout() { variant="light" color="gray" onClick={() => setOpened(true)} - aria-label="Open navigation" radius="xl" > )} - - App Dashboard + <Title order={3} fw={600} c="gray.0"> + Dashboard Control Panel @@ -125,7 +150,6 @@ export default function DashboardLayout() { ); } -/* ----------------------- Host Info ----------------------- */ function HostView() { const [host, setHost] = useState(null); @@ -138,33 +162,46 @@ function HostView() { }, []); return ( - + {host ? ( - + - - {host.name?.[0]} + + {host.name?.[0]?.toUpperCase()} - {host.name} + + {host.name} + {host.email} - + ) : ( - - No host information available - + + + + No user information available + + )} ); } -/* ----------------------- Navigation ----------------------- */ function NavigationDashboard() { const navigate = useNavigate(); const location = useLocation(); @@ -172,31 +209,66 @@ function NavigationDashboard() { const isActive = (path: keyof typeof clientRoute) => location.pathname.startsWith(clientRoute[path]); + const navItems = [ + { + path: "/scr/dashboard/dashboard-home", + icon: , + label: "Dashboard Overview", + description: "Quick summary and insights", + }, + { + path: "/scr/dashboard/apikey/apikey", + icon: , + label: "API Key Manager", + description: "Create and manage API keys", + }, + { + path: "/scr/dashboard/credential/credential", + icon: , + label: "Credentials", + description: "Manage service credentials", + }, + ]; + return ( - } - label="Dashboard Overview" - description="Quick summary and activity highlights" - onClick={() => navigate(clientRoutes["/scr/dashboard/dashboard-home"])} - /> - } - label="API Key" - description="API Key Management and Generation" - onClick={() => navigate(clientRoutes["/scr/dashboard/apikey/apikey"])} - /> - } - label="Credential" - description="Credential Management" - onClick={() => - navigate(clientRoutes["/scr/dashboard/credential/credential"]) - } - /> + {navItems.map((item) => ( + + {item.label} + {isActive(item.path as keyof typeof clientRoute) && ( + + Active + + )} + + } + description={item.description} + onClick={() => navigate(clientRoutes[item.path as keyof typeof clientRoute])} + style={{ + backgroundColor: isActive(item.path as keyof typeof clientRoute) + ? "rgba(0,255,200,0.1)" + : "transparent", + borderRadius: "8px", + transition: "all 0.2s ease", + }} + styles={{ + label: { color: "white" }, + description: { color: "#aaa" }, + section: { color: "teal" }, + }} + /> + ))} ); }