Feat: started admin panel
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -41,4 +41,8 @@ yarn-error.log*
|
|||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
# repomix
|
# repomix
|
||||||
repomix-output.txt
|
repomix-output.txt
|
||||||
|
|
||||||
|
# vscode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"scss.lint.unknownAtRules": "ignore"
|
||||||
|
}
|
||||||
@@ -10,12 +10,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@mantine/core": "^7.16.1",
|
"@mantine/core": "^7.16.1",
|
||||||
|
"@mantine/form": "^7.16.1",
|
||||||
"@mantine/hooks": "^7.16.1",
|
"@mantine/hooks": "^7.16.1",
|
||||||
"@mantine/modals": "^7.16.1",
|
"@mantine/modals": "^7.16.1",
|
||||||
"next": "15.1.5",
|
"next": "15.1.5",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-icons": "^5.4.0"
|
"react-icons": "^5.4.0",
|
||||||
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@eslint/eslintrc": "^3",
|
||||||
|
|||||||
9
src/app/admin/actions.ts
Normal file
9
src/app/admin/actions.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
"use server";
|
||||||
|
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export async function login(formData: { name: string; password: string }) {
|
||||||
|
console.log("data");
|
||||||
|
console.log(formData);
|
||||||
|
redirect("/admin/panel");
|
||||||
|
}
|
||||||
0
src/app/admin/admin.module.scss
Normal file
0
src/app/admin/admin.module.scss
Normal file
60
src/app/admin/page.tsx
Normal file
60
src/app/admin/page.tsx
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Button, Card, Flex, PasswordInput, Text, TextInput } from "@mantine/core";
|
||||||
|
import { hasLength, useForm } from "@mantine/form";
|
||||||
|
import { login } from "./actions";
|
||||||
|
|
||||||
|
interface FormValues {
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AdminPage = () => {
|
||||||
|
const form = useForm<FormValues>({
|
||||||
|
mode: "uncontrolled",
|
||||||
|
initialValues: {
|
||||||
|
name: "",
|
||||||
|
password: "",
|
||||||
|
},
|
||||||
|
validate: {
|
||||||
|
name: hasLength({ min: 5 }, "Too short"),
|
||||||
|
password: hasLength({ min: 5 }, "Too short"),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Flex w="100vw" h="100vh" justify="center" align="center">
|
||||||
|
<Card shadow="md" w="400" radius="md">
|
||||||
|
<form onSubmit={form.onSubmit(login)}>
|
||||||
|
<Text c="dimmed" size="xl" ta="center">
|
||||||
|
Admin
|
||||||
|
</Text>
|
||||||
|
<TextInput
|
||||||
|
label="Name"
|
||||||
|
placeholder="Username"
|
||||||
|
key={form.key("name")}
|
||||||
|
withAsterisk
|
||||||
|
{...form.getInputProps("name")}
|
||||||
|
name="name"
|
||||||
|
autoComplete="username"
|
||||||
|
/>
|
||||||
|
<PasswordInput
|
||||||
|
label="Password"
|
||||||
|
mt="md"
|
||||||
|
withAsterisk
|
||||||
|
placeholder="password"
|
||||||
|
key={form.key("password")}
|
||||||
|
name="password"
|
||||||
|
autoComplete="current-password"
|
||||||
|
{...form.getInputProps("password")}
|
||||||
|
/>
|
||||||
|
<Button radius="md" type="submit" mt="md" w="100%">
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</Card>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AdminPage;
|
||||||
35
src/app/admin/panel/page.tsx
Normal file
35
src/app/admin/panel/page.tsx
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
"use client";
|
||||||
|
import { ActionIcon, AppShell, Burger, Flex, Group, Skeleton, useMantineColorScheme } from "@mantine/core";
|
||||||
|
import { useDisclosure } from "@mantine/hooks";
|
||||||
|
import { LuMoon, LuSun } from "react-icons/lu";
|
||||||
|
|
||||||
|
const PanelPage = () => {
|
||||||
|
const [opened, { toggle }] = useDisclosure();
|
||||||
|
const { setColorScheme, colorScheme } = useMantineColorScheme();
|
||||||
|
const changeColorScheme = () => {
|
||||||
|
setColorScheme(colorScheme === "light" ? "dark" : "light");
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
<AppShell header={{ height: 60 }} navbar={{ width: 300, breakpoint: "sm", collapsed: { mobile: !opened } }}>
|
||||||
|
<AppShell.Header>
|
||||||
|
<Flex h="100%" px="md" justify="space-between" align="center">
|
||||||
|
<Group h="100%">
|
||||||
|
<Burger opened={opened} onClick={toggle} size="sm" hiddenFrom="sm" />
|
||||||
|
<div>Logo</div>
|
||||||
|
</Group>
|
||||||
|
<ActionIcon onClick={changeColorScheme} variant="default" size="md" aria-label="Toggle color scheme">
|
||||||
|
{colorScheme === "light" ? <LuMoon /> : <LuSun />}
|
||||||
|
</ActionIcon>
|
||||||
|
</Flex>
|
||||||
|
</AppShell.Header>
|
||||||
|
<AppShell.Navbar p="md">
|
||||||
|
{Array(15)
|
||||||
|
.fill(0)
|
||||||
|
.map((_, index) => (
|
||||||
|
<Skeleton key={index} h={28} mt="sm" animate={false} />
|
||||||
|
))}
|
||||||
|
</AppShell.Navbar>
|
||||||
|
</AppShell>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export default PanelPage;
|
||||||
3
src/app/api/route.ts
Normal file
3
src/app/api/route.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export async function GET() {
|
||||||
|
return Response.json({ test: 1 });
|
||||||
|
}
|
||||||
3
src/lib/auth.ts
Normal file
3
src/lib/auth.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export function isAuthenticated() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
14
src/middleware.ts
Normal file
14
src/middleware.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { isAuthenticated } from "@/lib/auth";
|
||||||
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: "/admin/:path*",
|
||||||
|
};
|
||||||
|
|
||||||
|
export function middleware(request: NextRequest) {
|
||||||
|
const { pathname } = request.nextUrl;
|
||||||
|
if (pathname === "/admin" || isAuthenticated()) {
|
||||||
|
return NextResponse.next();
|
||||||
|
}
|
||||||
|
return NextResponse.redirect(new URL("/admin", request.url));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user