feat: implement dark/light mode toggle across dashboard and profile
This commit is contained in:
34
src/components/ColorSchemeToggle.tsx
Normal file
34
src/components/ColorSchemeToggle.tsx
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import {
|
||||||
|
ActionIcon,
|
||||||
|
Group,
|
||||||
|
rem,
|
||||||
|
useComputedColorScheme,
|
||||||
|
useMantineColorScheme,
|
||||||
|
} from "@mantine/core";
|
||||||
|
import { IconMoon, IconSun } from "@tabler/icons-react";
|
||||||
|
|
||||||
|
export function ColorSchemeToggle() {
|
||||||
|
const { setColorScheme } = useMantineColorScheme();
|
||||||
|
const computedColorScheme = useComputedColorScheme("light", {
|
||||||
|
getInitialValueInEffect: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group justify="center">
|
||||||
|
<ActionIcon
|
||||||
|
onClick={() =>
|
||||||
|
setColorScheme(computedColorScheme === "light" ? "dark" : "light")
|
||||||
|
}
|
||||||
|
variant="default"
|
||||||
|
size="lg"
|
||||||
|
aria-label="Toggle color scheme"
|
||||||
|
>
|
||||||
|
{computedColorScheme === "light" ? (
|
||||||
|
<IconMoon style={{ width: rem(22), height: rem(22) }} stroke={1.5} />
|
||||||
|
) : (
|
||||||
|
<IconSun style={{ width: rem(22), height: rem(22) }} stroke={1.5} />
|
||||||
|
)}
|
||||||
|
</ActionIcon>
|
||||||
|
</Group>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -56,7 +56,7 @@ const app = (
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<MantineProvider theme={theme}>
|
<MantineProvider theme={theme} defaultColorScheme="dark">
|
||||||
<ModalsProvider>
|
<ModalsProvider>
|
||||||
<RouterProvider router={router} />
|
<RouterProvider router={router} />
|
||||||
</ModalsProvider>
|
</ModalsProvider>
|
||||||
|
|||||||
@@ -171,8 +171,8 @@ if (!isProduction) {
|
|||||||
const file = Bun.file(filePath);
|
const file = Bun.file(filePath);
|
||||||
return new Response(file, {
|
return new Response(file, {
|
||||||
headers: {
|
headers: {
|
||||||
"Vary": "Accept-Encoding",
|
Vary: "Accept-Encoding",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,8 +181,8 @@ if (!isProduction) {
|
|||||||
if (fs.existsSync(indexHtml)) {
|
if (fs.existsSync(indexHtml)) {
|
||||||
return new Response(Bun.file(indexHtml), {
|
return new Response(Bun.file(indexHtml), {
|
||||||
headers: {
|
headers: {
|
||||||
"Vary": "Accept-Encoding",
|
Vary: "Accept-Encoding",
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,7 +56,11 @@ function DashboardComponent() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Container size="lg" py="xl">
|
<Container size="lg" py="xl">
|
||||||
<Title order={1} ta="center" className=" text-blue-600 p-4 rounded-lg mt-10 shadow-lg">
|
<Title
|
||||||
|
order={1}
|
||||||
|
ta="center"
|
||||||
|
className=" text-blue-600 p-4 rounded-lg mt-10 shadow-lg"
|
||||||
|
>
|
||||||
Dashboard Overview
|
Dashboard Overview
|
||||||
</Title>
|
</Title>
|
||||||
|
|
||||||
@@ -66,8 +70,7 @@ function DashboardComponent() {
|
|||||||
p="xl"
|
p="xl"
|
||||||
radius="md"
|
radius="md"
|
||||||
mb="xl"
|
mb="xl"
|
||||||
bg="rgba(251, 240, 223, 0.05)"
|
style={{ border: "1px solid var(--mantine-color-default-border)" }}
|
||||||
style={{ border: "1px solid rgba(251, 240, 223, 0.1)" }}
|
|
||||||
>
|
>
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Group>
|
<Group>
|
||||||
@@ -77,14 +80,14 @@ function DashboardComponent() {
|
|||||||
radius="xl"
|
radius="xl"
|
||||||
style={{
|
style={{
|
||||||
cursor: "pointer",
|
cursor: "pointer",
|
||||||
border: "2px solid rgba(251, 240, 223, 0.3)",
|
border: "2px solid var(--mantine-color-orange-filled)",
|
||||||
}}
|
}}
|
||||||
onClick={() => navigate({ to: "/profile" })}
|
onClick={() => navigate({ to: "/profile" })}
|
||||||
>
|
>
|
||||||
{snap.user?.name?.charAt(0).toUpperCase()}
|
{snap.user?.name?.charAt(0).toUpperCase()}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<div>
|
||||||
<Text size="lg" fw={600} c="#fbf0df">
|
<Text size="lg" fw={600}>
|
||||||
{snap.user?.name}
|
{snap.user?.name}
|
||||||
</Text>
|
</Text>
|
||||||
<Text c="dimmed" size="sm">
|
<Text c="dimmed" size="sm">
|
||||||
@@ -104,23 +107,17 @@ function DashboardComponent() {
|
|||||||
{/* Stats Grid */}
|
{/* Stats Grid */}
|
||||||
<SimpleGrid cols={{ base: 1, sm: 2, md: 4 }} spacing="lg" mb="xl">
|
<SimpleGrid cols={{ base: 1, sm: 2, md: 4 }} spacing="lg" mb="xl">
|
||||||
{statsData.map((stat, index) => (
|
{statsData.map((stat, index) => (
|
||||||
<Card
|
<Card key={index.toString()} withBorder p="lg" radius="md">
|
||||||
key={index.toString()}
|
|
||||||
withBorder
|
|
||||||
p="lg"
|
|
||||||
radius="md"
|
|
||||||
bg="rgba(251, 240, 223, 0.05)"
|
|
||||||
>
|
|
||||||
<Group justify="space-between">
|
<Group justify="space-between">
|
||||||
<Box>
|
<Box>
|
||||||
<Text size="sm" c="dimmed">
|
<Text size="sm" c="dimmed">
|
||||||
{stat.title}
|
{stat.title}
|
||||||
</Text>
|
</Text>
|
||||||
<Text size="lg" fw={700} c="#fbf0df">
|
<Text size="lg" fw={700}>
|
||||||
{stat.value}
|
{stat.value}
|
||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Box c="#f3d5a3">{stat.icon}</Box>
|
<Box c="orange.6">{stat.icon}</Box>
|
||||||
</Group>
|
</Group>
|
||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
@@ -128,13 +125,7 @@ function DashboardComponent() {
|
|||||||
|
|
||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
<Grid.Col span={{ base: 12, md: 8 }}>
|
<Grid.Col span={{ base: 12, md: 8 }}>
|
||||||
<Card
|
<Card withBorder p="lg" radius="md" mb="lg">
|
||||||
withBorder
|
|
||||||
p="lg"
|
|
||||||
radius="md"
|
|
||||||
mb="lg"
|
|
||||||
bg="rgba(251, 240, 223, 0.05)"
|
|
||||||
>
|
|
||||||
<Title order={3} mb="md">
|
<Title order={3} mb="md">
|
||||||
System Performance
|
System Performance
|
||||||
</Title>
|
</Title>
|
||||||
@@ -171,7 +162,7 @@ function DashboardComponent() {
|
|||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
|
|
||||||
<Grid.Col span={{ base: 12, md: 4 }}>
|
<Grid.Col span={{ base: 12, md: 4 }}>
|
||||||
<Card withBorder p="lg" radius="md" bg="rgba(251, 240, 223, 0.05)">
|
<Card withBorder p="lg" radius="md">
|
||||||
<Title order={3} mb="md">
|
<Title order={3} mb="md">
|
||||||
Server Status
|
Server Status
|
||||||
</Title>
|
</Title>
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import {
|
|||||||
useNavigate,
|
useNavigate,
|
||||||
} from "@tanstack/react-router";
|
} from "@tanstack/react-router";
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
|
import { ColorSchemeToggle } from "@/components/ColorSchemeToggle";
|
||||||
import { authClient } from "@/utils/auth-client";
|
import { authClient } from "@/utils/auth-client";
|
||||||
import { authStore } from "../../store/auth";
|
import { authStore } from "../../store/auth";
|
||||||
|
|
||||||
@@ -101,7 +102,9 @@ function DashboardLayout() {
|
|||||||
header={{ height: 70 }}
|
header={{ height: 70 }}
|
||||||
navbar={{
|
navbar={{
|
||||||
width: 280,
|
width: 280,
|
||||||
|
|
||||||
breakpoint: "sm",
|
breakpoint: "sm",
|
||||||
|
|
||||||
collapsed: { mobile: !mobileOpened, desktop: !desktopOpened },
|
collapsed: { mobile: !mobileOpened, desktop: !desktopOpened },
|
||||||
}}
|
}}
|
||||||
padding="md"
|
padding="md"
|
||||||
@@ -109,10 +112,10 @@ function DashboardLayout() {
|
|||||||
transitionTimingFunction="ease"
|
transitionTimingFunction="ease"
|
||||||
>
|
>
|
||||||
<AppShell.Header
|
<AppShell.Header
|
||||||
bg="rgba(26, 26, 26, 0.8)"
|
|
||||||
style={{
|
style={{
|
||||||
backdropFilter: "blur(10px)",
|
backdropFilter: "blur(10px)",
|
||||||
borderBottom: "1px solid rgba(251, 240, 223, 0.1)",
|
|
||||||
|
borderBottom: "1px solid var(--mantine-color-default-border)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Group h="100%" px="md" justify="space-between">
|
<Group h="100%" px="md" justify="space-between">
|
||||||
@@ -122,24 +125,24 @@ function DashboardLayout() {
|
|||||||
onClick={toggleMobile}
|
onClick={toggleMobile}
|
||||||
hiddenFrom="sm"
|
hiddenFrom="sm"
|
||||||
size="sm"
|
size="sm"
|
||||||
color="#f3d5a3"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Burger
|
<Burger
|
||||||
opened={desktopOpened}
|
opened={desktopOpened}
|
||||||
onClick={toggleDesktop}
|
onClick={toggleDesktop}
|
||||||
visibleFrom="sm"
|
visibleFrom="sm"
|
||||||
size="sm"
|
size="sm"
|
||||||
color="#f3d5a3"
|
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Box visibleFrom="xs" ml="xs">
|
<Box visibleFrom="xs" ml="xs">
|
||||||
<Text
|
<Text
|
||||||
fw={800}
|
fw={800}
|
||||||
size="xl"
|
size="xl"
|
||||||
c="#f3d5a3"
|
c="orange.6"
|
||||||
style={{ letterSpacing: "-0.5px" }}
|
style={{ letterSpacing: "-0.5px" }}
|
||||||
>
|
>
|
||||||
ADMIN
|
ADMIN
|
||||||
<Text span c="#fbf0df">
|
<Text span c="var(--mantine-color-text)">
|
||||||
PANEL
|
PANEL
|
||||||
</Text>
|
</Text>
|
||||||
</Text>
|
</Text>
|
||||||
@@ -147,6 +150,8 @@ function DashboardLayout() {
|
|||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Group gap="md">
|
<Group gap="md">
|
||||||
|
<ColorSchemeToggle />
|
||||||
|
|
||||||
<Menu
|
<Menu
|
||||||
shadow="md"
|
shadow="md"
|
||||||
width={200}
|
width={200}
|
||||||
@@ -158,24 +163,28 @@ function DashboardLayout() {
|
|||||||
gap="xs"
|
gap="xs"
|
||||||
style={{ cursor: "pointer" }}
|
style={{ cursor: "pointer" }}
|
||||||
p="xs"
|
p="xs"
|
||||||
hover-bg="rgba(255,255,255,0.05)"
|
className="hover:bg-gray-50 dark:hover:bg-white/5 rounded-md"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
style={{ textAlign: "right" }}
|
style={{ textAlign: "right" }}
|
||||||
className="visible-from-sm"
|
className="visible-from-sm"
|
||||||
>
|
>
|
||||||
<Text size="sm" fw={600} c="#fbf0df">
|
<Text size="sm" fw={600}>
|
||||||
{snap.user?.name}
|
{snap.user?.name}
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
<Text size="xs" c="dimmed">
|
<Text size="xs" c="dimmed">
|
||||||
Administrator
|
Administrator
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Avatar
|
<Avatar
|
||||||
src={snap.user?.image}
|
src={snap.user?.image}
|
||||||
radius="xl"
|
radius="xl"
|
||||||
size="md"
|
size="md"
|
||||||
style={{ border: "2px solid #f3d5a3" }}
|
style={{
|
||||||
|
border: "2px solid var(--mantine-color-orange-filled)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{snap.user?.name?.charAt(0)}
|
{snap.user?.name?.charAt(0)}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
@@ -184,6 +193,7 @@ function DashboardLayout() {
|
|||||||
|
|
||||||
<Menu.Dropdown>
|
<Menu.Dropdown>
|
||||||
<Menu.Label>Akun</Menu.Label>
|
<Menu.Label>Akun</Menu.Label>
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={
|
leftSection={
|
||||||
<IconUser style={{ width: rem(14), height: rem(14) }} />
|
<IconUser style={{ width: rem(14), height: rem(14) }} />
|
||||||
@@ -192,6 +202,7 @@ function DashboardLayout() {
|
|||||||
>
|
>
|
||||||
Profil Saya
|
Profil Saya
|
||||||
</Menu.Item>
|
</Menu.Item>
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
leftSection={
|
leftSection={
|
||||||
<IconSettings style={{ width: rem(14), height: rem(14) }} />
|
<IconSettings style={{ width: rem(14), height: rem(14) }} />
|
||||||
@@ -204,6 +215,7 @@ function DashboardLayout() {
|
|||||||
<Menu.Divider />
|
<Menu.Divider />
|
||||||
|
|
||||||
<Menu.Label>Bahaya</Menu.Label>
|
<Menu.Label>Bahaya</Menu.Label>
|
||||||
|
|
||||||
<Menu.Item
|
<Menu.Item
|
||||||
color="red"
|
color="red"
|
||||||
leftSection={
|
leftSection={
|
||||||
@@ -221,8 +233,7 @@ function DashboardLayout() {
|
|||||||
|
|
||||||
<AppShell.Navbar
|
<AppShell.Navbar
|
||||||
p="md"
|
p="md"
|
||||||
bg="rgba(20, 20, 20, 1)"
|
style={{ borderRight: "1px solid var(--mantine-color-default-border)" }}
|
||||||
style={{ borderRight: "1px solid rgba(251, 240, 223, 0.1)" }}
|
|
||||||
>
|
>
|
||||||
<AppShell.Section grow component={ScrollArea} mx="-md" px="md">
|
<AppShell.Section grow component={ScrollArea} mx="-md" px="md">
|
||||||
<Stack gap="xs" mt="md">
|
<Stack gap="xs" mt="md">
|
||||||
@@ -237,6 +248,7 @@ function DashboardLayout() {
|
|||||||
<NavLink
|
<NavLink
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate({ to: item.to });
|
navigate({ to: item.to });
|
||||||
|
|
||||||
if (mobileOpened) toggleMobile();
|
if (mobileOpened) toggleMobile();
|
||||||
}}
|
}}
|
||||||
leftSection={
|
leftSection={
|
||||||
@@ -254,20 +266,15 @@ function DashboardLayout() {
|
|||||||
}
|
}
|
||||||
rightSection={<IconChevronRight size="0.8rem" stroke={1.5} />}
|
rightSection={<IconChevronRight size="0.8rem" stroke={1.5} />}
|
||||||
active={isActive(item.to)}
|
active={isActive(item.to)}
|
||||||
variant="filled"
|
variant="light"
|
||||||
color="orange"
|
color="orange"
|
||||||
styles={{
|
styles={{
|
||||||
root: {
|
root: {
|
||||||
borderRadius: rem(8),
|
borderRadius: rem(8),
|
||||||
|
|
||||||
marginBottom: rem(4),
|
marginBottom: rem(4),
|
||||||
backgroundColor: isActive(item.to)
|
|
||||||
? "rgba(243, 213, 163, 0.1)"
|
|
||||||
: "transparent",
|
|
||||||
color: isActive(item.to) ? "#f3d5a3" : "#fbf0df",
|
|
||||||
"&:hover": {
|
|
||||||
backgroundColor: "rgba(243, 213, 163, 0.05)",
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
label: {
|
label: {
|
||||||
fontSize: rem(14),
|
fontSize: rem(14),
|
||||||
},
|
},
|
||||||
@@ -279,7 +286,7 @@ function DashboardLayout() {
|
|||||||
</AppShell.Section>
|
</AppShell.Section>
|
||||||
|
|
||||||
<AppShell.Section
|
<AppShell.Section
|
||||||
style={{ borderTop: "1px solid rgba(251, 240, 223, 0.1)" }}
|
style={{ borderTop: "1px solid var(--mantine-color-default-border)" }}
|
||||||
pt="md"
|
pt="md"
|
||||||
>
|
>
|
||||||
<NavLink
|
<NavLink
|
||||||
@@ -292,6 +299,7 @@ function DashboardLayout() {
|
|||||||
}
|
}
|
||||||
styles={{ root: { borderRadius: rem(8) } }}
|
styles={{ root: { borderRadius: rem(8) } }}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
label="Keluar"
|
label="Keluar"
|
||||||
onClick={handleLogout}
|
onClick={handleLogout}
|
||||||
@@ -308,7 +316,7 @@ function DashboardLayout() {
|
|||||||
</AppShell.Section>
|
</AppShell.Section>
|
||||||
</AppShell.Navbar>
|
</AppShell.Navbar>
|
||||||
|
|
||||||
<AppShell.Main bg="rgba(15, 15, 15, 1)">
|
<AppShell.Main>
|
||||||
<Box p="lg" style={{ minHeight: "calc(100vh - 100px)" }}>
|
<Box p="lg" style={{ minHeight: "calc(100vh - 100px)" }}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ function EditProfile() {
|
|||||||
<Stack gap="xl">
|
<Stack gap="xl">
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Box>
|
<Box>
|
||||||
<Title order={1} c="#f3d5a3">
|
<Title order={1} c="orange.6">
|
||||||
Edit Profil
|
Edit Profil
|
||||||
</Title>
|
</Title>
|
||||||
<Text c="dimmed" size="sm">
|
<Text c="dimmed" size="sm">
|
||||||
@@ -88,14 +88,13 @@ function EditProfile() {
|
|||||||
</Button>
|
</Button>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Divider color="rgba(251, 240, 223, 0.1)" />
|
<Divider style={{ opacity: 0.1 }} />
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
withBorder
|
withBorder
|
||||||
radius="md"
|
radius="md"
|
||||||
p="xl"
|
p="xl"
|
||||||
bg="rgba(26, 26, 26, 0.5)"
|
style={{ border: "1px solid var(--mantine-color-default-border)" }}
|
||||||
style={{ border: "1px solid rgba(251, 240, 223, 0.1)" }}
|
|
||||||
>
|
>
|
||||||
<form onSubmit={form.onSubmit(handleUpdateProfile)}>
|
<form onSubmit={form.onSubmit(handleUpdateProfile)}>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
@@ -104,10 +103,9 @@ function EditProfile() {
|
|||||||
placeholder="Masukkan nama lengkap Anda"
|
placeholder="Masukkan nama lengkap Anda"
|
||||||
{...form.getInputProps("name")}
|
{...form.getInputProps("name")}
|
||||||
styles={{
|
styles={{
|
||||||
label: { color: "#fbf0df", marginBottom: 8 },
|
label: { marginBottom: 8 },
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: "rgba(0,0,0,0.2)",
|
backgroundColor: "var(--mantine-color-default-soft)",
|
||||||
color: "#fbf0df",
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -116,10 +114,9 @@ function EditProfile() {
|
|||||||
placeholder="https://example.com/photo.jpg"
|
placeholder="https://example.com/photo.jpg"
|
||||||
{...form.getInputProps("image")}
|
{...form.getInputProps("image")}
|
||||||
styles={{
|
styles={{
|
||||||
label: { color: "#fbf0df", marginBottom: 8 },
|
label: { marginBottom: 8 },
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: "rgba(0,0,0,0.2)",
|
backgroundColor: "var(--mantine-color-default-soft)",
|
||||||
color: "#fbf0df",
|
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ import {
|
|||||||
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
import { createFileRoute, useNavigate } from "@tanstack/react-router";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useSnapshot } from "valtio";
|
import { useSnapshot } from "valtio";
|
||||||
|
import { ColorSchemeToggle } from "@/components/ColorSchemeToggle";
|
||||||
import { protectedRouteMiddleware } from "@/middleware/authMiddleware";
|
import { protectedRouteMiddleware } from "@/middleware/authMiddleware";
|
||||||
import { authClient } from "@/utils/auth-client";
|
import { authClient } from "@/utils/auth-client";
|
||||||
import { authStore } from "../../store/auth";
|
import { authStore } from "../../store/auth";
|
||||||
@@ -92,25 +93,22 @@ function Profile() {
|
|||||||
withBorder
|
withBorder
|
||||||
p="md"
|
p="md"
|
||||||
radius="md"
|
radius="md"
|
||||||
bg="rgba(251, 240, 223, 0.03)"
|
style={{ border: "1px solid var(--mantine-color-default-border)" }}
|
||||||
style={{ border: "1px solid rgba(251, 240, 223, 0.1)" }}
|
|
||||||
>
|
>
|
||||||
<Group wrap="nowrap" align="flex-start">
|
<Group wrap="nowrap" align="flex-start">
|
||||||
<Box mt={3}>
|
<Box mt={3}>
|
||||||
<Icon size={20} stroke={1.5} color="#f3d5a3" />
|
<Icon
|
||||||
|
size={20}
|
||||||
|
stroke={1.5}
|
||||||
|
color="var(--mantine-color-orange-filled)"
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
<Box style={{ flex: 1 }}>
|
<Box style={{ flex: 1 }}>
|
||||||
<Text size="xs" c="dimmed" tt="uppercase" fw={700} lts={0.5}>
|
<Text size="xs" c="dimmed" tt="uppercase" fw={700} lts={0.5}>
|
||||||
{label}
|
{label}
|
||||||
</Text>
|
</Text>
|
||||||
<Group gap="xs" mt={4} wrap="nowrap">
|
<Group gap="xs" mt={4} wrap="nowrap">
|
||||||
<Text
|
<Text fw={500} size="sm" truncate="end" style={{ flex: 1 }}>
|
||||||
fw={500}
|
|
||||||
size="sm"
|
|
||||||
c="#fbf0df"
|
|
||||||
truncate="end"
|
|
||||||
style={{ flex: 1 }}
|
|
||||||
>
|
|
||||||
{value || "N/A"}
|
{value || "N/A"}
|
||||||
</Text>
|
</Text>
|
||||||
{copyable && value && (
|
{copyable && value && (
|
||||||
@@ -145,7 +143,7 @@ function Profile() {
|
|||||||
{/* Header Section */}
|
{/* Header Section */}
|
||||||
<Group justify="space-between" align="center">
|
<Group justify="space-between" align="center">
|
||||||
<Box>
|
<Box>
|
||||||
<Title order={1} c="#f3d5a3">
|
<Title order={1} c="orange.6">
|
||||||
Profil Saya
|
Profil Saya
|
||||||
</Title>
|
</Title>
|
||||||
<Text c="dimmed" size="sm">
|
<Text c="dimmed" size="sm">
|
||||||
@@ -153,6 +151,7 @@ function Profile() {
|
|||||||
</Text>
|
</Text>
|
||||||
</Box>
|
</Box>
|
||||||
<Group>
|
<Group>
|
||||||
|
<ColorSchemeToggle />
|
||||||
{snap.user?.role === "admin" && (
|
{snap.user?.role === "admin" && (
|
||||||
<Button
|
<Button
|
||||||
variant="light"
|
variant="light"
|
||||||
@@ -182,20 +181,17 @@ function Profile() {
|
|||||||
</Group>
|
</Group>
|
||||||
</Group>
|
</Group>
|
||||||
|
|
||||||
<Divider color="rgba(251, 240, 223, 0.1)" />
|
<Divider style={{ opacity: 0.1 }} />
|
||||||
|
|
||||||
{/* Profile Overview Card */}
|
{/* Profile Overview Card */}
|
||||||
<Card
|
<Card withBorder radius="lg" p={0} style={{ overflow: "hidden" }}>
|
||||||
withBorder
|
|
||||||
radius="lg"
|
|
||||||
p={0}
|
|
||||||
bg="rgba(26, 26, 26, 0.5)"
|
|
||||||
style={{ overflow: "hidden" }}
|
|
||||||
>
|
|
||||||
<Box
|
<Box
|
||||||
h={120}
|
h={120}
|
||||||
bg="linear-gradient(45deg, #2c2c2c 0%, #1a1a1a 100%)"
|
style={{
|
||||||
style={{ borderBottom: "1px solid rgba(251, 240, 223, 0.1)" }}
|
background:
|
||||||
|
"linear-gradient(45deg, var(--mantine-color-gray-filled) 0%, var(--mantine-color-dark-filled) 100%)",
|
||||||
|
borderBottom: "1px solid var(--mantine-color-default-border)",
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
<Box px="xl" pb="xl" style={{ marginTop: rem(-60) }}>
|
<Box px="xl" pb="xl" style={{ marginTop: rem(-60) }}>
|
||||||
<Group align="flex-end" gap="xl" mb="md">
|
<Group align="flex-end" gap="xl" mb="md">
|
||||||
@@ -204,16 +200,14 @@ function Profile() {
|
|||||||
size={120}
|
size={120}
|
||||||
radius={120}
|
radius={120}
|
||||||
style={{
|
style={{
|
||||||
border: "4px solid #1a1a1a",
|
border: "4px solid var(--mantine-color-body)",
|
||||||
boxShadow: "0 4px 10px rgba(0,0,0,0.3)",
|
boxShadow: "var(--mantine-shadow-md)",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{snap.user?.name?.charAt(0).toUpperCase()}
|
{snap.user?.name?.charAt(0).toUpperCase()}
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Stack gap={0} pb="md">
|
<Stack gap={0} pb="md">
|
||||||
<Title order={2} c="#fbf0df">
|
<Title order={2}>{snap.user?.name}</Title>
|
||||||
{snap.user?.name}
|
|
||||||
</Title>
|
|
||||||
<Group gap="xs">
|
<Group gap="xs">
|
||||||
<Text c="dimmed" size="sm">
|
<Text c="dimmed" size="sm">
|
||||||
{snap.user?.email}
|
{snap.user?.email}
|
||||||
@@ -237,7 +231,7 @@ function Profile() {
|
|||||||
<Grid gutter="lg">
|
<Grid gutter="lg">
|
||||||
<Grid.Col span={{ base: 12, md: 7 }}>
|
<Grid.Col span={{ base: 12, md: 7 }}>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Title order={4} c="#f3d5a3">
|
<Title order={4} c="orange.6">
|
||||||
Informasi Identitas
|
Informasi Identitas
|
||||||
</Title>
|
</Title>
|
||||||
<Grid gutter="sm">
|
<Grid gutter="sm">
|
||||||
@@ -279,15 +273,16 @@ function Profile() {
|
|||||||
|
|
||||||
<Grid.Col span={{ base: 12, md: 5 }}>
|
<Grid.Col span={{ base: 12, md: 5 }}>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Title order={4} c="#f3d5a3">
|
<Title order={4} c="orange.6">
|
||||||
Keamanan & Sesi
|
Keamanan & Sesi
|
||||||
</Title>
|
</Title>
|
||||||
<Card
|
<Card
|
||||||
withBorder
|
withBorder
|
||||||
radius="md"
|
radius="md"
|
||||||
p="lg"
|
p="lg"
|
||||||
bg="rgba(251, 240, 223, 0.03)"
|
style={{
|
||||||
style={{ border: "1px solid rgba(251, 240, 223, 0.1)" }}
|
border: "1px solid var(--mantine-color-default-border)",
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Stack gap="md">
|
<Stack gap="md">
|
||||||
<Box>
|
<Box>
|
||||||
@@ -312,9 +307,6 @@ function Profile() {
|
|||||||
<Code
|
<Code
|
||||||
block
|
block
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: "rgba(0,0,0,0.3)",
|
|
||||||
color: "#f3d5a3",
|
|
||||||
border: "1px solid rgba(251, 240, 223, 0.1)",
|
|
||||||
fontSize: rem(11),
|
fontSize: rem(11),
|
||||||
flex: 1,
|
flex: 1,
|
||||||
}}
|
}}
|
||||||
|
|||||||
25
src/sw.js
25
src/sw.js
@@ -1,25 +1,24 @@
|
|||||||
const CACHE_NAME = 'makuro-v1';
|
const CACHE_NAME = "makuro-v1";
|
||||||
const ASSETS = [
|
const ASSETS = ["/", "/index.html", "/logo.svg"];
|
||||||
'/',
|
|
||||||
'/index.html',
|
|
||||||
'/logo.svg'
|
|
||||||
];
|
|
||||||
|
|
||||||
self.addEventListener('install', (event) => {
|
self.addEventListener("install", (event) => {
|
||||||
event.waitUntil(
|
event.waitUntil(
|
||||||
caches.open(CACHE_NAME).then((cache) => {
|
caches.open(CACHE_NAME).then((cache) => {
|
||||||
console.log('SW: Pre-caching assets');
|
console.log("SW: Pre-caching assets");
|
||||||
return cache.addAll(ASSETS).catch(err => {
|
return cache.addAll(ASSETS).catch((err) => {
|
||||||
console.error('SW: Pre-cache failed (likely due to Vary header or missing file):', err);
|
console.error(
|
||||||
|
"SW: Pre-cache failed (likely due to Vary header or missing file):",
|
||||||
|
err,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
self.addEventListener('fetch', (event) => {
|
self.addEventListener("fetch", (event) => {
|
||||||
event.respondWith(
|
event.respondWith(
|
||||||
caches.match(event.request).then((response) => {
|
caches.match(event.request).then((response) => {
|
||||||
return response || fetch(event.request);
|
return response || fetch(event.request);
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
10
src/vite.ts
10
src/vite.ts
@@ -1,7 +1,7 @@
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { inspectorServer } from "@react-dev-inspector/vite-plugin";
|
import { inspectorServer } from "@react-dev-inspector/vite-plugin";
|
||||||
import { tanstackRouter } from "@tanstack/router-vite-plugin";
|
|
||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
import { tanstackRouter } from "@tanstack/router-vite-plugin";
|
||||||
import react from "@vitejs/plugin-react";
|
import react from "@vitejs/plugin-react";
|
||||||
import { createServer as createViteServer } from "vite";
|
import { createServer as createViteServer } from "vite";
|
||||||
|
|
||||||
@@ -36,7 +36,13 @@ export async function createVite() {
|
|||||||
},
|
},
|
||||||
appType: "custom",
|
appType: "custom",
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
include: ["react", "react-dom", "@mantine/core", "manifest.json", "sw.js"],
|
include: [
|
||||||
|
"react",
|
||||||
|
"react-dom",
|
||||||
|
"@mantine/core",
|
||||||
|
"manifest.json",
|
||||||
|
"sw.js",
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user