First Commit

This commit is contained in:
2026-09-12 13:56:36 +01:00
commit 4d75af0418
426 changed files with 36452 additions and 0 deletions
@@ -0,0 +1,89 @@
import React from "react";
import { Menu, X } from "lucide-react";
import AdminAsideLinks from "./admin-aside-links-dict";
import Row from "@/src/components/twui/layout/Row";
import LogoIcon from "@/src/components/general/logo-icon";
import Divider from "@/src/components/twui/layout/Divider";
import { twMerge } from "tailwind-merge";
import Stack from "@/src/components/twui/layout/Stack";
export default function MobileNav() {
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
if (!open) return;
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
document.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = prev;
document.removeEventListener("keydown", onKey);
};
}, [open]);
return (
<div className="flex xl:hidden">
<button
type="button"
aria-label="Open menu"
aria-expanded={open}
className="cursor-pointer p-1"
onClick={() => setOpen(true)}
>
<Menu size={28} />
</button>
{open ? (
<>
<div
className="fixed inset-0 z-200 bg-dark/50"
onClick={() => setOpen(false)}
aria-hidden
/>
<aside
className={twMerge(
"fixed top-0 left-0 bottom-0 z-210",
"w-[min(280px,85vw)] bg-background-light dark:bg-background-dark",
"flex flex-col shadow-xl",
)}
role="dialog"
aria-modal="true"
aria-label="Admin navigation"
>
<Row className="w-full justify-between items-center py-2 px-4 h-20 flex-nowrap">
<a href="/" onClick={() => setOpen(false)}>
<LogoIcon icon_size={60} />
</a>
<button
type="button"
aria-label="Close menu"
className="cursor-pointer p-1"
onClick={() => setOpen(false)}
>
<X size={28} />
</button>
</Row>
<Divider />
<Stack
className="w-full grow overflow-y-auto"
onClick={(e) => {
if ((e.target as HTMLElement).closest("a")) {
setOpen(false);
}
}}
>
<AdminAsideLinks />
</Stack>
</aside>
</>
) : null}
</div>
);
}
@@ -0,0 +1,66 @@
import type { TWUILink } from "@/src/components/twui/elements/LinkList";
import LinkList from "@/src/components/twui/elements/LinkList";
import Stack from "@/src/components/twui/layout/Stack";
import { AppContext } from "@/src/pages/__root";
import { useContext } from "react";
export default function AdminAsideLinks() {
const { pageProps } = useContext(AppContext);
const { user, user_types } = pageProps || {};
if (!user?.logged_in_status) {
return null;
}
const links: TWUILink[] = [
{
title: "Dashboard",
url: "/admin",
strict: true,
},
{
title: "People",
url: "/admin/people",
},
{
title: "Events",
url: "/admin/events",
},
{
title: "Media",
url: "/admin/media",
},
{
title: "Board",
url: "/admin/board",
},
{
title: "Locations",
url: "/admin/locations",
},
{
component: <div className="h-10"></div>,
},
{
title: "Settings",
url: "/admin/settings",
},
{
title: "Logout",
url: "/auth/logout",
},
];
return (
<Stack className="w-full items-stretch">
<LinkList
links={links}
className="gap-1 flex-col items-start"
linkProps={{
className: "px-4 py-3 w-full",
}}
divider
/>
</Stack>
);
}
+29
View File
@@ -0,0 +1,29 @@
import Row from "@/src/components/twui/layout/Row";
import Stack from "@/src/components/twui/layout/Stack";
import LogoIcon from "@/src/components/general/logo-icon";
import Divider from "@/src/components/twui/layout/Divider";
import AdminAsideLinks from "./admin-aside-links-dict";
import { twMerge } from "tailwind-merge";
type Props = {
className?: string;
};
export default function AdminAside({ className }: Props) {
return (
<aside className={twMerge("min-w-[180px] mb-20", className)}>
<Stack className="w-full items-stretch gap-0!">
<Row className="py-2 px-4 h-20">
<a href="/">
<LogoIcon icon_size={60} />
</a>
</Row>
<Divider />
<nav className="w-full">
<AdminAsideLinks />
</nav>
<Divider />
</Stack>
</aside>
);
}
+20
View File
@@ -0,0 +1,20 @@
import Row from "@/src/components/twui/layout/Row";
import { AppContext } from "@/src/pages/__root";
import type { AppContextObject } from "@/src/types";
import React from "react";
export default function Footer() {
const appContext: AppContextObject = React.useContext(AppContext);
const { user, appState } = appContext;
/**
* Render component
*/
return (
<footer className="w-full p-4 h-20">
<Row className="h-full">
<span>Copyright {new Date().getFullYear()}</span>
</Row>
</footer>
);
}
+34
View File
@@ -0,0 +1,34 @@
import Row from "@/src/components/twui/layout/Row";
import HeaderUser from "../../main/(partials)/header-user";
import LogoIcon from "@/src/components/general/logo-icon";
import MobileNav from "../(partials)/MobileNav";
import { twMerge } from "tailwind-merge";
type Props = {};
export default function Header({}: Props) {
/**
* Render component
*/
return (
<header className="h-20">
<Row
className={twMerge(
"w-full justify-between h-full py-4 px-4 xl:px-10",
"flex-nowrap",
)}
>
<Row className="flex-nowrap">
<LogoIcon
icon_size={60}
img_props={{ className: "flex xl:hidden" }}
/>
<MobileNav />
</Row>
<Row className="flex-nowrap">
<HeaderUser />
</Row>
</Row>
</header>
);
}
+40
View File
@@ -0,0 +1,40 @@
import React from "react";
import Header from "./(sections)/Header";
import Footer from "./(sections)/Footer";
import { AppContext } from "@/src/pages/__root";
import type { AppContextObject } from "@/src/types";
import Row from "@/src/components/twui/layout/Row";
import Stack from "@/src/components/twui/layout/Stack";
import LogoIcon from "@/src/components/general/logo-icon";
import Divider from "@/src/components/twui/layout/Divider";
import AdminAsideLinks from "./(partials)/admin-aside-links-dict";
import AdminAside from "./(partials)/admin-aside";
/* -------------------------------------------------------------------------- */
/* Main Layout Functional Component */
/* -------------------------------------------------------------------------- */
// Main Layout Component
export default function Layout({ children }: { children: React.ReactNode }) {
const appContext = React.useContext<AppContextObject>(AppContext);
const { user } = appContext;
return (
<>
<Row className="w-full items-stretch gap-0! flex-nowrap">
<AdminAside className="hidden xl:flex" />
<Divider vertical className="h-auto" />
<Stack className="grow items-stretch gap-0!">
<Header />
<Divider />
<main className="grow items-start" id="admin-main">
{children}
</main>
<Divider />
<Footer />
</Stack>
</Row>
<Divider />
</>
);
}
+95
View File
@@ -0,0 +1,95 @@
import React from "react";
import { Menu, X } from "lucide-react";
import Stack from "@/src/components/twui/layout/Stack";
import { MainLinks } from "@/src/dict/main-links-dict";
import LinkList from "@/src/components/twui/elements/LinkList";
import HeaderCTA from "./header-cta";
import Row from "@/src/components/twui/layout/Row";
import Logo from "@/src/components/general/logo";
import { twMerge } from "tailwind-merge";
export default function MobileNav() {
const [open, setOpen] = React.useState(false);
React.useEffect(() => {
if (!open) return;
const prev = document.body.style.overflow;
document.body.style.overflow = "hidden";
const onKey = (e: KeyboardEvent) => {
if (e.key === "Escape") setOpen(false);
};
document.addEventListener("keydown", onKey);
return () => {
document.body.style.overflow = prev;
document.removeEventListener("keydown", onKey);
};
}, [open]);
return (
<div className="flex xl:hidden relative">
<button
type="button"
aria-label="Open menu"
aria-expanded={open}
className="cursor-pointer text-white p-1"
onClick={() => setOpen(true)}
>
<Menu size={30} />
</button>
{open ? (
<div
className={twMerge(
"fixed inset-0 z-200 flex flex-col",
"bg-dark text-white",
)}
role="dialog"
aria-modal="true"
aria-label="Mobile navigation"
onClick={(e) => {
if ((e.target as HTMLElement).closest("a")) {
setOpen(false);
}
}}
>
<Row className="w-full justify-between items-center px-4 py-6 flex-nowrap">
<Logo
icon_size={60}
text_wrapper_props={{
className: "max-[400px]:hidden",
}}
/>
<button
type="button"
aria-label="Close menu"
className="cursor-pointer text-white p-1"
onClick={() => setOpen(false)}
>
<X size={30} />
</button>
</Row>
<Stack className="grow w-full items-stretch px-6 py-4 gap-8! overflow-y-auto">
<nav className="w-full">
<LinkList
links={MainLinks}
className="flex-col items-stretch gap-1 w-full"
linkProps={{
className: twMerge(
"px-2 py-4 w-full text-xl border-0",
"text-white/80 hover:text-white",
),
}}
/>
</nav>
<HeaderCTA className="flex flex-col w-full items-stretch" />
</Stack>
</div>
) : null}
</div>
);
}
+28
View File
@@ -0,0 +1,28 @@
export default function NavList({ transparent }: { transparent?: boolean }) {
const className = transparent ? "light" : "";
/**
* Render component
*/
return (
<div className="gap-10 flex-col xl:flex-row">
<a className={className} href="/">
Home
</a>
<a className={className} href="/subsidiaries">
Subsidiaries
</a>
<a className={className} href="/about">
About
</a>
<a className={className} href="/events">
Events
</a>
<a className={className} href="/gallery">
Gallery
</a>
<a className={className} href="/contact">
Contact Us
</a>
</div>
);
}
@@ -0,0 +1,17 @@
import Row from "@/src/components/twui/layout/Row";
import Button from "@/src/components/twui/layout/Button";
import { twMerge } from "tailwind-merge";
type Props = {
className?: string;
};
export default function HeaderCTA({ className }: Props) {
return (
<Row className={twMerge("hidden xl:flex", className)}>
<Button title="Login" className="w-full" href="/auth/login">
Login
</Button>
</Row>
);
}
@@ -0,0 +1,61 @@
import { useContext } from "react";
import Row from "@/src/components/twui/layout/Row";
import { AppContext } from "@/src/pages/__root";
import Dropdown from "@/src/components/twui/elements/Dropdown";
import Img from "@/src/components/twui/layout/Img";
import Paper from "@/src/components/twui/elements/Paper";
import LinkList from "@/src/components/twui/elements/LinkList";
import Divider from "@/src/components/twui/layout/Divider";
export default function HeaderUser() {
const { pageProps } = useContext(AppContext);
const { user, user_types } = pageProps || {};
if (!user?.logged_in_status) {
return null;
}
return (
<Row className="">
<Dropdown
target={
<Img
alt="Logged in user image"
src={user.image_thumbnail ?? "/icons/man.png"}
size={45}
circle
className="bg-slate-400"
title={
user.first_name +
(user.last_name ? ` ${user.last_name}` : "")
}
/>
}
position="bottom-right"
>
<Paper className="min-w-[200px] px-0! py-2 header-user-dropdown">
<LinkList
links={[
{
title: "Dashboard",
url: "/admin",
},
{
component: <Divider />,
},
{
title: "Settings",
url: "/admin/settings",
},
{
title: "Logout",
url: "/auth/logout",
},
]}
className="flex-col items-stretch w-full"
/>
</Paper>
</Dropdown>
</Row>
);
}
+71
View File
@@ -0,0 +1,71 @@
import React from "react";
import { AppContext } from "@/src/pages/__root";
import type { AppContextObject } from "@/src/types";
import Divider from "@/src/components/twui/layout/Divider";
import Stack from "@/src/components/twui/layout/Stack";
import Section from "@/src/components/twui/layout/Section";
import Container from "@/src/components/twui/layout/Container";
import Row from "@/src/components/twui/layout/Row";
import Logo from "@/src/components/general/logo";
import Span from "@/src/components/twui/layout/Span";
import H4 from "@/src/components/twui/layout/H4";
import { SiteData } from "@/src/data/site-data";
export default function Footer() {
const appContext: AppContextObject = React.useContext(AppContext);
/**
* Render component
*/
return (
<footer className="bg-dark text-white">
<Section className="pt-20! pb-0!">
<Container>
<Stack className="w-full gap-10! items-center">
<Row className="w-full items-start justify-start xl:justify-between gap-20!">
<div className="flex flex-col items-start max-w-[380px] gap-4">
<Logo
text_props={{ className: "text-white" }}
white
/>
<Span className="opacity-70">
Wireguard UI is the ultimate dashboard for
managing raw Wireguard
</Span>
</div>
<Row className="gap-20! justify-start flex-wrap items-start">
<div className="flex items-start gap-10 justify-between flex-wrap xl:ml-auto">
<Stack>
<H4>Company</H4>
<Divider className="opacity-20" />
<a href="/about">Company</a>
<a href="/terms">
Terms and Conditions
</a>
<a href="/privacy">Privacy Policy</a>
</Stack>
</div>
</Row>
</Row>
<Divider className="opacity-20" />
<div
className="container px-4 sm:px-6 flex w-full items-start justify-between flex-wrap"
style={{
gap: "40px 100px",
}}
>
<span className="text-sm opacity-60">
Copyright {new Date().getFullYear()}{" "}
{SiteData["SiteName"]}. All rights reserved
</span>
</div>
</Stack>
</Container>
</Section>
<div className="w-full h-20 relative z-100 overflow-hidden mt-20"></div>
</footer>
);
}
+58
View File
@@ -0,0 +1,58 @@
import MobileNav from "../(partials)/MobileNav";
import Section from "@/src/components/twui/layout/Section";
import Container from "@/src/components/twui/layout/Container";
import Row from "@/src/components/twui/layout/Row";
import Logo from "@/src/components/general/logo";
import LinkList from "@/src/components/twui/elements/LinkList";
import { MainLinks } from "@/src/dict/main-links-dict";
import Divider from "@/src/components/twui/layout/Divider";
import HeaderCTA from "../(partials)/header-cta";
import HeaderUser from "../(partials)/header-user";
import { twMerge } from "tailwind-merge";
type Props = {
no_header_divider?: boolean;
};
export default function Header({ no_header_divider }: Props) {
/**
* Render component
*/
return (
<header className={"px-4 py-6 w-full z-100 -mb-[160px] relative"}>
<Section className="p-0! relative z-10">
<Container className="w-full justify-between">
<Row className="w-full items-center gap-10 justify-between flex-nowrap">
<Row className="flex xl:hidden">
<Logo
text_wrapper_props={{
className: "max-[400px]:hidden",
}}
/>
</Row>
<Row className="gap-5! hidden xl:flex">
<Logo />
{/* <Row className="hidden xl:flex gap-3!">
<Divider vertical className="-skew-12 h-10" />
<nav>
<LinkList
links={MainLinks}
className="gap-1"
/>
</nav>
</Row> */}
</Row>
<Row className="flex-nowrap">
<HeaderCTA />
<MobileNav />
<HeaderUser />
</Row>
</Row>
</Container>
</Section>
{no_header_divider ? null : <Divider className="mt-6 opacity-20" />}
</header>
);
}
+31
View File
@@ -0,0 +1,31 @@
import React from "react";
import Header from "./(sections)/Header";
import Footer from "./(sections)/Footer";
import { AppContext } from "@/src/pages/__root";
import Stack from "@/src/components/twui/layout/Stack";
type Props = {
children: React.ReactNode;
no_header_divider?: boolean;
blank?: boolean;
};
export default function Layout({
children,
no_header_divider,
blank,
}: Props): React.JSX.Element {
const {} = React.useContext(AppContext);
return (
<React.Fragment>
<Stack className="w-full items-stretch gap-0">
{blank ? null : (
<Header no_header_divider={no_header_divider} />
)}
<main>{children}</main>
{blank ? null : <Footer />}
</Stack>
</React.Fragment>
);
}