First Commit
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Executable
+29
@@ -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>
|
||||
);
|
||||
}
|
||||
Executable
+20
@@ -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>
|
||||
);
|
||||
}
|
||||
Executable
+34
@@ -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>
|
||||
);
|
||||
}
|
||||
Executable
+40
@@ -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 />
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Executable
+71
@@ -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>
|
||||
);
|
||||
}
|
||||
Executable
+58
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user