-
+
+
{output}
-
+
);
-}
\ No newline at end of file
+}
diff --git a/src/pages/admin/setup/(partials)/setup-stat-cell.tsx b/src/pages/admin/setup/(partials)/setup-stat-cell.tsx
deleted file mode 100644
index 788014e..0000000
--- a/src/pages/admin/setup/(partials)/setup-stat-cell.tsx
+++ /dev/null
@@ -1,39 +0,0 @@
-import Span from "@/src/components/twui/layout/Span";
-import Stack from "@/src/components/twui/layout/Stack";
-import { twMerge } from "tailwind-merge";
-
-export type SetupStatCellTone = "success" | "error" | "warning" | "muted";
-
-const toneClassName: Record = {
- success: "text-success",
- error: "text-error",
- warning: "text-warning",
- muted: "text-foreground-light/40 dark:text-foreground-dark/40",
-};
-
-type Props = {
- label: string;
- value: string;
- tone?: SetupStatCellTone;
-};
-
-export default function SetupStatCell({ label, value, tone }: Props) {
- return (
-
-
- {label}
-
-
- {value}
-
-
- );
-}
diff --git a/src/pages/admin/setup/(partials)/setup-update-wg-ui-action.tsx b/src/pages/admin/setup/(partials)/setup-update-wg-ui-action.tsx
index b473590..0de8973 100644
--- a/src/pages/admin/setup/(partials)/setup-update-wg-ui-action.tsx
+++ b/src/pages/admin/setup/(partials)/setup-update-wg-ui-action.tsx
@@ -1,14 +1,13 @@
+import { useState } from "react";
+import { CloudDownload } from "lucide-react";
import fetchApi from "@/src/components/twui/utils/fetch/fetchApi";
import Input from "@/src/components/twui/form/Input";
import Stack from "@/src/components/twui/layout/Stack";
-import Span from "@/src/components/twui/layout/Span";
-import type useSystemSetupStatus from "../(hooks)/use-system-setup-status";
-import SetupActionCard from "./setup-action-card";
import type { ApiReqParams } from "@/src/types";
import type { APIResponseObject } from "@moduletrace/bunext/types";
-import { CloudDownload } from "lucide-react";
-import { useState } from "react";
import { SiteData } from "@/src/data/site-data";
+import type useSystemSetupStatus from "../(hooks)/use-system-setup-status";
+import SetupActionRow from "./setup-action-row";
type Props = {
setup: ReturnType;
@@ -17,87 +16,68 @@ type Props = {
export default function SetupUpdateWgUiAction({ setup }: Props) {
const setup_status = setup.setup_status;
- const [repoUrl, setRepoUrl] = useState(
- setup_status?.repo_url || "",
- );
- const [branch, setBranch] = useState(
- setup_status?.repo_branch || "main",
- );
+ const [repoUrl, setRepoUrl] = useState(setup_status?.repo_url || "");
+ const [branch, setBranch] = useState(setup_status?.repo_branch || "main");
const needs_repo_url = !setup_status?.is_dev && !setup_status?.repo_url;
+ const service_name = setup_status?.service_name || "wgui";
+
+ const description = needs_repo_url
+ ? "wg-ui isn't installed yet. Provide the git repository so the installer can clone it."
+ : setup_status?.is_dev
+ ? "Development mode: reinstalls dependencies in this checkout and syncs the wg-quick helper. The dev server isn't restarted."
+ : `Pulls the latest code, installs dependencies, syncs helper scripts and restarts the ${service_name} service.`;
return (
-
- wg-ui isn't installed yet — provide the git repository
- URL so the installer can clone it, then run the update
- again to pull newer versions.
-
- ) : setup_status?.is_dev ? (
-
- Development mode — uses this local checkout. Reinstalls
- dependencies and syncs the wg-quick helper script. The
- dev server is not restarted.
-
- ) : (
-
- Pulls the latest code, installs dependencies, syncs
- helper scripts and restarts the{" "}
- {setup_status?.service_name || "wgui"} service. The
- server will briefly go offline.
-
- )
- }
- icon={ }
- button_title={
- setup_status?.app_installed ? "Update wg-ui" : "Install wg-ui"
+ }
+ button_title={setup_status?.app_installed ? "Update" : "Install"}
+ confirm_params={
+ setup_status?.is_dev
+ ? undefined
+ : {
+ title: setup_status?.app_installed ? "Update wg-ui?" : "Install wg-ui?",
+ message: `The ${service_name} service restarts and this dashboard goes briefly offline.`,
+ confirm_label: setup_status?.app_installed ? "Update and restart" : "Install",
+ tone: "danger",
+ }
}
on_run={async () => {
- const res = await fetchApi(
- `/api/admin/update-wg-ui`,
- {
- method: "POST",
- body: {
- repo_url: repoUrl,
- branch,
- },
- },
- );
+ const res = await fetchApi(`/api/admin/update-wg-ui`, {
+ method: "POST",
+ body: { repo_url: repoUrl, branch },
+ });
setup.refresh();
-
return res;
}}
>
{needs_repo_url || !setup_status?.is_dev ? (
-
+
{needs_repo_url ? (
- label="Repository URL"
+ name="repo_url"
+ title="Repository URL"
placeholder={SiteData["RepoURL"]}
defaultValue={repoUrl}
- onChange={(e) => {
- setRepoUrl(e.target.value);
- }}
+ showLabel
+ onChange={(e) => setRepoUrl(e.target.value)}
/>
) : null}
{!setup_status?.is_dev ? (
- label="Branch"
+ name="branch"
+ title="Branch"
placeholder="main"
defaultValue={branch}
- onChange={(e) => {
- setBranch(e.target.value);
- }}
- title="Repo Branch"
showLabel
+ onChange={(e) => setBranch(e.target.value)}
/>
) : null}
) : null}
-
+
);
}
diff --git a/src/pages/admin/setup/(sections)/firewall-section.tsx b/src/pages/admin/setup/(sections)/firewall-section.tsx
index af75d09..3ce0761 100644
--- a/src/pages/admin/setup/(sections)/firewall-section.tsx
+++ b/src/pages/admin/setup/(sections)/firewall-section.tsx
@@ -1,119 +1,94 @@
-import AdminCard from "@/src/components/general/admin-card";
+import { RefreshCw, ShieldCheck } from "lucide-react";
import Button from "@/src/components/twui/layout/Button";
-import H3 from "@/src/components/twui/layout/H3";
-import P from "@/src/components/twui/layout/P";
-import Row from "@/src/components/twui/layout/Row";
-import Span from "@/src/components/twui/layout/Span";
+import LoadingBlock from "@/src/components/twui/elements/LoadingBlock";
import Stack from "@/src/components/twui/layout/Stack";
-import Tag from "@/src/components/twui/elements/Tag";
-import { RefreshCw, Shield, TriangleAlert } from "lucide-react";
+import Panel from "@/src/components/general/panel";
+import StatusDot from "@/src/components/general/status-dot";
+import InlineAlert from "@/src/components/general/inline-alert";
+import EmptyState from "@/src/components/general/empty-state";
import useFirewallStatus from "../(hooks)/use-firewall-status";
import FirewallRuleRow from "../(partials)/firewall-rule-row";
const MANAGER_LABEL: Record = {
- firewalld: `firewalld (active)`,
- ufw: `ufw (active)`,
- none: `none detected`,
+ firewalld: "firewalld active",
+ ufw: "ufw active",
+ none: "No manager detected",
};
export default function FirewallSection() {
const { firewall_status, refresh, loading, error } = useFirewallStatus();
- if (!firewall_status) {
- return (
-
-
-
-
-
-
-
- Firewall
-
-
- {error ||
- `Checking firewall status on this host ...`}
-
-
-
-
- );
- }
-
- const { manager, rules, hosts_found } = firewall_status;
+ const manager = firewall_status?.manager;
return (
-
-
-
-
-
-
-
-
- Firewall
-
-
- {MANAGER_LABEL[manager] || manager}
-
-
-
- Host setups automatically allow their wireguard subnet
- and listen port here. Rules that are no longer needed
- (bootstrap or stale) are listed with removal commands.
-
-
- }
- onClick={refresh}
- >
- Refresh
-
-
-
- {manager == "none" ? (
-
-
-
- No active high-level firewall manager detected
- (firewalld/ufw). If you use iptables/nftables directly,
- allow each host subnet and its wireguard listen port
- manually.
-
-
- ) : null}
-
- {rules.length ? (
-
- {rules.map((rule, index) => (
-
+ {manager ? (
+
- ))}
-
+ ) : null}
+
+
+ Refresh
+
+ >
+ }
+ >
+ {!firewall_status ? (
+ error ? (
+
+
+
+ ) : (
+
+
+
+
+ )
) : (
-
-
- {hosts_found
- ? `No wg-ui related firewall rules found.`
- : `No hosts created yet — creating a host automatically allows its subnet (e.g. 10.5.0.0/24) and its wireguard listen port (e.g. 51821/udp) here.`}
-
-
+ <>
+ {manager == "none" ? (
+
+
+
+ ) : null}
+ {firewall_status.rules.length ? (
+
+ {firewall_status.rules.map((rule, index) => (
+
+ ))}
+
+ ) : (
+ }
+ title={firewall_status.hosts_found ? "No wg-ui firewall rules" : "No hosts yet"}
+ description={
+ firewall_status.hosts_found
+ ? undefined
+ : "Creating a host allows its subnet (e.g. 10.5.0.0/24) and listen port (e.g. 51821/udp) here."
+ }
+ />
+ )}
+ >
)}
-
+
);
-}
\ No newline at end of file
+}
diff --git a/src/pages/admin/setup/(sections)/setup-actions-section.tsx b/src/pages/admin/setup/(sections)/setup-actions-section.tsx
index b0ea773..96f66ac 100644
--- a/src/pages/admin/setup/(sections)/setup-actions-section.tsx
+++ b/src/pages/admin/setup/(sections)/setup-actions-section.tsx
@@ -1,12 +1,11 @@
+import { RotateCcw, Wrench } from "lucide-react";
import fetchApi from "@/src/components/twui/utils/fetch/fetchApi";
-import Row from "@/src/components/twui/layout/Row";
-import Span from "@/src/components/twui/layout/Span";
-import type useSystemSetupStatus from "../(hooks)/use-system-setup-status";
-import SetupActionCard from "../(partials)/setup-action-card";
-import SetupUpdateWgUiAction from "../(partials)/setup-update-wg-ui-action";
+import Panel from "@/src/components/general/panel";
import type { ApiReqParams } from "@/src/types";
import type { APIResponseObject } from "@moduletrace/bunext/types";
-import { RotateCcw, TriangleAlert, Wrench } from "lucide-react";
+import type useSystemSetupStatus from "../(hooks)/use-system-setup-status";
+import SetupActionRow from "../(partials)/setup-action-row";
+import SetupUpdateWgUiAction from "../(partials)/setup-update-wg-ui-action";
type Props = {
setup: ReturnType;
@@ -25,41 +24,21 @@ export default function SetupActionsSection({ setup }: Props) {
.filter((tool) => WG_TOOL_COMMANDS.includes(tool.command))
.every((tool) => tool.installed);
- const show_restart =
- !setup_status.is_dev && setup_status.init_system !== "unknown";
+ const show_restart = !setup_status.is_dev && setup_status.init_system !== "unknown";
return (
- <>
- {!setup_status.is_root ? (
-
-
-
- The server is not running as root — installs and
- tunnel management may fail. Run wg-ui as root for full
- functionality.
-
-
- ) : null}
-
-
+ }
- button_title={
- have_wg_tools
- ? "Update WireGuard tools"
- : "Install WireGuard tools"
- }
+ description="Installs or updates wg, wg-quick and iproute2 with your distro's package manager. Safe to re-run."
+ icon={ }
+ button_title={have_wg_tools ? "Update tools" : "Install tools"}
on_run={async () => {
const res = await fetchApi(
`/api/admin/setup-wireguard-tools`,
- {
- method: "POST",
- },
+ { method: "POST" },
);
-
setup.refresh();
-
return res;
}}
/>
@@ -67,25 +46,27 @@ export default function SetupActionsSection({ setup }: Props) {
{show_restart ? (
- }
- button_title="Restart service"
+ description={`Restarts the ${setup_status.service_name} system service. Use after manual changes.`}
+ icon={ }
+ button_title="Restart"
+ confirm_params={{
+ title: "Restart wg-ui?",
+ message: `The ${setup_status.service_name} service restarts and this dashboard goes briefly offline.`,
+ confirm_label: "Restart service",
+ tone: "danger",
+ }}
on_run={async () => {
- const res = await fetchApi<
- ApiReqParams,
- APIResponseObject
- >(`/api/admin/restart-wg-ui-service`, {
- method: "POST",
- });
-
+ const res = await fetchApi(
+ `/api/admin/restart-wg-ui-service`,
+ { method: "POST" },
+ );
setup.refresh();
-
return res;
}}
/>
) : null}
- >
+
);
-}
\ No newline at end of file
+}
diff --git a/src/pages/admin/setup/(sections)/setup-page-frame.tsx b/src/pages/admin/setup/(sections)/setup-page-frame.tsx
new file mode 100644
index 0000000..b1bcf82
--- /dev/null
+++ b/src/pages/admin/setup/(sections)/setup-page-frame.tsx
@@ -0,0 +1,36 @@
+import type { PropsWithChildren } from "react";
+import PageHeader from "@/src/components/general/page-header";
+import PageBody from "@/src/components/general/page-body";
+import SubNav from "@/src/components/general/sub-nav";
+import InlineAlert from "@/src/components/general/inline-alert";
+import { SetupNav } from "../(data)/setup-nav-dict";
+
+type Props = PropsWithChildren & {
+ title: string;
+ description: string;
+ /** `false` shows the not-root warning */
+ is_root?: boolean;
+};
+
+/** Header, sub-navigation and body shared by the three setup subpages. */
+export default function SetupPageFrame({ title, description, is_root, children }: Props) {
+ return (
+ <>
+ }
+ />
+
+ {is_root === false ? (
+
+ ) : null}
+ {children}
+
+ >
+ );
+}
diff --git a/src/pages/admin/setup/(sections)/system-status-section.tsx b/src/pages/admin/setup/(sections)/system-status-section.tsx
index d8a79f2..47389fd 100644
--- a/src/pages/admin/setup/(sections)/system-status-section.tsx
+++ b/src/pages/admin/setup/(sections)/system-status-section.tsx
@@ -1,18 +1,13 @@
-import AdminCard from "@/src/components/general/admin-card";
+import { RefreshCw, ServerCrash } from "lucide-react";
import Button from "@/src/components/twui/layout/Button";
-import Divider from "@/src/components/twui/layout/Divider";
-import H3 from "@/src/components/twui/layout/H3";
-import P from "@/src/components/twui/layout/P";
-import Row from "@/src/components/twui/layout/Row";
-import Span from "@/src/components/twui/layout/Span";
import Stack from "@/src/components/twui/layout/Stack";
-import EmptyContent from "@/src/components/twui/elements/EmptyContent";
+import Panel from "@/src/components/general/panel";
+import KeyValueList from "@/src/components/general/key-value-list";
+import EmptyState from "@/src/components/general/empty-state";
import type useSystemSetupStatus from "../(hooks)/use-system-setup-status";
-import SetupCheckStatusItem from "../(partials)/setup-check-status-item";
-import SetupStatCell, {
- type SetupStatCellTone,
-} from "../(partials)/setup-stat-cell";
-import { RefreshCw } from "lucide-react";
+import SetupCheckGroup from "../(partials)/setup-check-group";
+import buildSetupOverview from "../(functions)/build-setup-overview";
+import buildSetupRuntimeChecks from "../(functions)/build-setup-runtime-checks";
type Props = {
setup: ReturnType;
@@ -22,173 +17,50 @@ export default function SystemStatusSection({ setup }: Props) {
const setup_status = setup.setup_status;
if (!setup_status) {
- return ;
+ return (
+
+ } title="No system status available" />
+
+ );
}
- const service_label =
- setup_status.service_active === null
- ? "Not managed"
- : setup_status.service_active
- ? "Active"
- : "Inactive";
+ const overview = buildSetupOverview({ setup_status });
+ const half = Math.ceil(overview.length / 2);
- const service_tone: SetupStatCellTone =
- setup_status.service_active === null
- ? "muted"
- : setup_status.service_active
- ? "success"
- : "error";
-
- const overview_items: {
- label: string;
- value: string;
- tone?: SetupStatCellTone;
- }[] = [
- {
- label: "Environment",
- value: setup_status.environment,
- tone: setup_status.is_dev ? "warning" : "success",
- },
- {
- label: "Running as root",
- value: setup_status.is_root ? "Yes" : "No",
- tone: setup_status.is_root ? "success" : "error",
- },
- {
- label: "Distro",
- value: setup_status.distro_pretty_name || setup_status.distro,
- },
- {
- label: "Init system",
- value: setup_status.init_system,
- },
- {
- label: "Install directory",
- value: setup_status.install_dir,
- },
- {
- label: "App version",
- value: setup_status.app_version || "—",
- },
- {
- label: "Repository",
- value: setup_status.repo_url || "—",
- },
- {
- label: "Service",
- value: setup_status.service_name,
- },
- {
- label: "Service status",
- value: service_label,
- tone: service_tone,
- },
- ];
-
- const runtime_items: {
- key: string;
- title: string;
- subtitle: string;
- ok: boolean;
- ok_label: string;
- }[] = [
- {
- key: "kernel-module",
- title: "WireGuard kernel module",
- subtitle: "/sys/module/wireguard",
- ok: setup_status.wg_module_loaded,
- ok_label: "Loaded",
- },
- {
- key: "wg-quick-helper",
- title: "wg-quick manage helper",
- subtitle: `/var/lib/wgui/scripts/wg-quick-manage.sh`,
- ok: setup_status.wg_quick_helper_installed,
- ok_label: "Installed",
- },
- ...setup_status.lib_dirs.map((dir) => ({
- key: dir.path,
- title: `Runtime directory · ${dir.label}`,
- subtitle: dir.path,
- ok: dir.exists,
- ok_label: "Exists",
- })),
- ];
+ const tool_checks = setup_status.tools.map((tool) => ({
+ key: tool.command,
+ title: tool.name,
+ subtitle: tool.version ? `${tool.command} · ${tool.version}` : tool.command,
+ ok: tool.installed,
+ }));
return (
-
-
-
-
- System status
-
-
- The web server's environment and required tools
-
-
+ }
+ size="small"
loading={setup.loading}
onClick={setup.refresh}
>
+
Refresh
-
-
-
-
-
- {overview_items.map((item) => (
-
- ))}
-
-
-
-
-
- WireGuard tools
-
-
- {setup_status.tools.map((tool) => (
-
- ))}
-
-
-
- Runtime
-
-
- {runtime_items.map((item) => (
-
- ))}
-
-
+ }
+ >
+
+
+
+
+
+
+
+
+
);
}
diff --git a/src/pages/admin/setup/firewall/index.server.ts b/src/pages/admin/setup/firewall/index.server.ts
new file mode 100644
index 0000000..30d721e
--- /dev/null
+++ b/src/pages/admin/setup/firewall/index.server.ts
@@ -0,0 +1,16 @@
+import type { PagePropsType } from "@/src/types";
+import type { BunextPageServerFn } from "@moduletrace/bunext/types";
+import guardSetupPage from "../(functions)/guard-setup-page";
+
+/** Firewall status is fetched client-side by `useFirewallStatus`. */
+const server: BunextPageServerFn = async ({ req }) => {
+ const redirect = await guardSetupPage({ req });
+
+ if (redirect) {
+ return { redirect };
+ }
+
+ return { props: {} };
+};
+
+export default server;
diff --git a/src/pages/admin/setup/firewall/index.tsx b/src/pages/admin/setup/firewall/index.tsx
new file mode 100644
index 0000000..8c144c9
--- /dev/null
+++ b/src/pages/admin/setup/firewall/index.tsx
@@ -0,0 +1,20 @@
+import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
+import { SiteData } from "@/src/data/site-data";
+import SetupPageFrame from "../(sections)/setup-page-frame";
+import FirewallSection from "../(sections)/firewall-section";
+
+export default function SetupFirewallPage() {
+ return (
+
+
+
+ );
+}
+
+export const meta: BunextPageModuleMeta = {
+ title: `Firewall | ${SiteData["SiteName"]}`,
+ description: `Firewall rules managed by wg-ui`,
+};
diff --git a/src/pages/admin/setup/index.server.ts b/src/pages/admin/setup/index.server.ts
index 5a66895..51af143 100644
--- a/src/pages/admin/setup/index.server.ts
+++ b/src/pages/admin/setup/index.server.ts
@@ -1,37 +1,14 @@
-import checkUserAccess from "@/src/functions/backend/auth/check-user-access";
-import userAuth from "@/src/functions/backend/auth/user-auth";
-import grabSystemSetupStatus from "@/src/functions/backend/setup/grab-system-setup-status";
import type { PagePropsType } from "@/src/types";
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
+import guardSetupPage from "./(functions)/guard-setup-page";
+/** `/admin/setup` has no content of its own; land on System status. */
const server: BunextPageServerFn = async ({ req }) => {
- const { user, user_types } = await userAuth({ req });
-
- if (!user?.logged_in_status || !user.id) {
- return {
- redirect: {
- destination: "/auth/login",
- permanent: false,
- },
- };
- }
-
- const is_super_admin = checkUserAccess({ user_types });
-
- if (!is_super_admin.success) {
- return {
- redirect: {
- destination: "/admin",
- permanent: false,
- },
- };
- }
+ const redirect = await guardSetupPage({ req });
return {
- props: {
- setup_status: grabSystemSetupStatus(),
- },
+ redirect: redirect || { destination: "/admin/setup/status", permanent: false },
};
};
-export default server;
\ No newline at end of file
+export default server;
diff --git a/src/pages/admin/setup/index.tsx b/src/pages/admin/setup/index.tsx
index a48760c..dc23050 100644
--- a/src/pages/admin/setup/index.tsx
+++ b/src/pages/admin/setup/index.tsx
@@ -1,39 +1,4 @@
-import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
-import AdminHero from "@/src/components/general/admin-hero";
-import { SiteData } from "@/src/data/site-data";
-import Divider from "@/src/components/twui/layout/Divider";
-import Stack from "@/src/components/twui/layout/Stack";
-import useSystemSetupStatus from "./(hooks)/use-system-setup-status";
-import SystemStatusSection from "./(sections)/system-status-section";
-import SetupActionsSection from "./(sections)/setup-actions-section";
-import FirewallSection from "./(sections)/firewall-section";
-import { useContext } from "react";
-import { AppContext } from "@/src/pages/__root";
-
-export default function AdminWireguardSetupPage() {
- const { pageProps } = useContext(AppContext);
-
- const setup = useSystemSetupStatus({
- setup_status: pageProps?.setup_status,
- });
-
- return (
- <>
-
-
-
-
-
-
-
- >
- );
+/** Always redirected by `index.server.ts`. */
+export default function SetupIndexPage() {
+ return null;
}
-
-export const meta: BunextPageModuleMeta = {
- title: `Admin Wireguard Setup | ${SiteData["SiteName"]}`,
- description: `Setup wireguard and wg-ui. Check if dependencies are installed. Update wg-ui and related packages. Etc.`,
-};
\ No newline at end of file
diff --git a/src/pages/admin/setup/maintenance/index.server.ts b/src/pages/admin/setup/maintenance/index.server.ts
new file mode 100644
index 0000000..9e24498
--- /dev/null
+++ b/src/pages/admin/setup/maintenance/index.server.ts
@@ -0,0 +1,20 @@
+import grabSystemSetupStatus from "@/src/functions/backend/setup/grab-system-setup-status";
+import type { PagePropsType } from "@/src/types";
+import type { BunextPageServerFn } from "@moduletrace/bunext/types";
+import guardSetupPage from "../(functions)/guard-setup-page";
+
+const server: BunextPageServerFn = async ({ req }) => {
+ const redirect = await guardSetupPage({ req });
+
+ if (redirect) {
+ return { redirect };
+ }
+
+ return {
+ props: {
+ setup_status: grabSystemSetupStatus(),
+ },
+ };
+};
+
+export default server;
diff --git a/src/pages/admin/setup/maintenance/index.tsx b/src/pages/admin/setup/maintenance/index.tsx
new file mode 100644
index 0000000..1359fe3
--- /dev/null
+++ b/src/pages/admin/setup/maintenance/index.tsx
@@ -0,0 +1,27 @@
+import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
+import { useContext } from "react";
+import { SiteData } from "@/src/data/site-data";
+import { AppContext } from "@/src/pages/__root";
+import useSystemSetupStatus from "../(hooks)/use-system-setup-status";
+import SetupPageFrame from "../(sections)/setup-page-frame";
+import SetupActionsSection from "../(sections)/setup-actions-section";
+
+export default function SetupMaintenancePage() {
+ const { pageProps } = useContext(AppContext);
+ const setup = useSystemSetupStatus({ setup_status: pageProps?.setup_status });
+
+ return (
+
+
+
+ );
+}
+
+export const meta: BunextPageModuleMeta = {
+ title: `Maintenance | ${SiteData["SiteName"]}`,
+ description: `Install, update and restart wg-ui`,
+};
diff --git a/src/pages/admin/setup/status/index.server.ts b/src/pages/admin/setup/status/index.server.ts
new file mode 100644
index 0000000..9e24498
--- /dev/null
+++ b/src/pages/admin/setup/status/index.server.ts
@@ -0,0 +1,20 @@
+import grabSystemSetupStatus from "@/src/functions/backend/setup/grab-system-setup-status";
+import type { PagePropsType } from "@/src/types";
+import type { BunextPageServerFn } from "@moduletrace/bunext/types";
+import guardSetupPage from "../(functions)/guard-setup-page";
+
+const server: BunextPageServerFn = async ({ req }) => {
+ const redirect = await guardSetupPage({ req });
+
+ if (redirect) {
+ return { redirect };
+ }
+
+ return {
+ props: {
+ setup_status: grabSystemSetupStatus(),
+ },
+ };
+};
+
+export default server;
diff --git a/src/pages/admin/setup/status/index.tsx b/src/pages/admin/setup/status/index.tsx
new file mode 100644
index 0000000..a2cd721
--- /dev/null
+++ b/src/pages/admin/setup/status/index.tsx
@@ -0,0 +1,29 @@
+import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
+import { useContext } from "react";
+import { SiteData } from "@/src/data/site-data";
+import { AppContext } from "@/src/pages/__root";
+import useSystemSetupStatus from "../(hooks)/use-system-setup-status";
+import SetupPageFrame from "../(sections)/setup-page-frame";
+import SystemStatusSection from "../(sections)/system-status-section";
+
+export default function SetupStatusPage() {
+ const { pageProps } = useContext(AppContext);
+ const setup = useSystemSetupStatus({
+ setup_status: pageProps?.setup_status,
+ });
+
+ return (
+
+
+
+ );
+}
+
+export const meta: BunextPageModuleMeta = {
+ title: `System status | ${SiteData["SiteName"]}`,
+ description: `Server environment and dependency checks`,
+};
diff --git a/src/pages/auth/index.tsx b/src/pages/auth/index.tsx
index 76f32bd..529cb03 100644
--- a/src/pages/auth/index.tsx
+++ b/src/pages/auth/index.tsx
@@ -1,31 +1,11 @@
import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
-import Section from "@/src/components/twui/layout/Section";
-import Container from "@/src/components/twui/layout/Container";
-import Stack from "@/src/components/twui/layout/Stack";
-import H1 from "@/src/components/twui/layout/H1";
-import Span from "@/src/components/twui/layout/Span";
-import BlurredImageBG from "@/src/components/general/blured-image-bg";
import { SiteData } from "@/src/data/site-data";
+/** Always redirected by `index.server.ts`. */
export default function AuthPage() {
- return (
- <>
-
-
-
-
- Admin Section
-
- Redirecting you to login ...
-
-
-
-
- >
- );
+ return null;
}
export const meta: BunextPageModuleMeta = {
- title: `Admin Login | ${SiteData["SiteName"]}`,
- description: `Admin section access. Redirecting to login.`,
+ title: `Sign in | ${SiteData["SiteName"]}`,
};
diff --git a/src/pages/auth/login/(functions)/submit-login-form.ts b/src/pages/auth/login/(functions)/submit-login-form.ts
index fd8b760..fdc6028 100644
--- a/src/pages/auth/login/(functions)/submit-login-form.ts
+++ b/src/pages/auth/login/(functions)/submit-login-form.ts
@@ -28,10 +28,8 @@ export default async function submitLoginForm({
} else {
setStatus({
error: true,
- msg: res.msg || "Login Failed",
+ msg: res.msg || "Sign in failed. Check your credentials.",
});
- console.log("res", res);
- console.log("form", form);
setLoading(false);
}
diff --git a/src/pages/auth/login/(partials)/login-form/index.tsx b/src/pages/auth/login/(partials)/login-form/index.tsx
index 5d681c3..e1247d3 100644
--- a/src/pages/auth/login/(partials)/login-form/index.tsx
+++ b/src/pages/auth/login/(partials)/login-form/index.tsx
@@ -1,6 +1,5 @@
import Form from "@/src/components/twui/form/Form";
-import LoadingOverlay from "@/src/components/twui/elements/LoadingOverlay";
-import Tag from "@/src/components/twui/elements/Tag";
+import InlineAlert from "@/src/components/general/inline-alert";
import useFormInit from "@/src/hooks/use-form-init";
import { type LoginFormObject } from "@/src/types";
import LoginFormEmailUsername from "./login-form-email-username";
@@ -10,22 +9,19 @@ import LoginFormAction from "./login-form-action";
export default function LoginForm() {
const init = useFormInit();
- const { loading, status } = init;
+ const { status } = init;
return (
- <>
-
- >
+
);
-}
\ No newline at end of file
+}
diff --git a/src/pages/auth/login/(partials)/login-form/login-form-action.tsx b/src/pages/auth/login/(partials)/login-form/login-form-action.tsx
index 1f9315e..9243dfe 100644
--- a/src/pages/auth/login/(partials)/login-form/login-form-action.tsx
+++ b/src/pages/auth/login/(partials)/login-form/login-form-action.tsx
@@ -1,16 +1,13 @@
-import Input from "@/src/components/twui/form/Input";
import Button from "@/src/components/twui/layout/Button";
import useFormInit from "@/src/hooks/use-form-init";
import { type LoginFormObject } from "@/src/types";
-export default function LoginFormAction({}: ReturnType<
- typeof useFormInit
->) {
+export default function LoginFormAction({
+ loading,
+}: ReturnType>) {
return (
- <>
-
- Login
-
- >
+
+ Sign in
+
);
}
diff --git a/src/pages/auth/login/(partials)/login-form/login-form-email-username.tsx b/src/pages/auth/login/(partials)/login-form/login-form-email-username.tsx
index f145662..40ad184 100644
--- a/src/pages/auth/login/(partials)/login-form/login-form-email-username.tsx
+++ b/src/pages/auth/login/(partials)/login-form/login-form-email-username.tsx
@@ -7,7 +7,10 @@ export default function LoginFormEmailUsername({
}: ReturnType>) {
return (
{
setForm((prev) => ({
...prev,
diff --git a/src/pages/auth/login/(partials)/login-form/login-form-password.tsx b/src/pages/auth/login/(partials)/login-form/login-form-password.tsx
index fa3e6d1..15cf21d 100644
--- a/src/pages/auth/login/(partials)/login-form/login-form-password.tsx
+++ b/src/pages/auth/login/(partials)/login-form/login-form-password.tsx
@@ -7,8 +7,9 @@ export default function LoginFormPassword({
}: ReturnType>) {
return (
{
setForm((prev) => ({
@@ -20,4 +21,4 @@ export default function LoginFormPassword({
required
/>
);
-}
\ No newline at end of file
+}
diff --git a/src/pages/auth/login/(sections)/hero.tsx b/src/pages/auth/login/(sections)/hero.tsx
deleted file mode 100644
index a1af0c4..0000000
--- a/src/pages/auth/login/(sections)/hero.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import Section from "@/src/components/twui/layout/Section";
-import Container from "@/src/components/twui/layout/Container";
-import Stack from "@/src/components/twui/layout/Stack";
-import H1 from "@/src/components/twui/layout/H1";
-import Span from "@/src/components/twui/layout/Span";
-import LoginShell from "@/src/components/general/login-shell";
-
-export default function Hero() {
- // return ;
- return (
-
-
-
- Welcome Back Admin
-
- Login to your account
-
-
-
-
- );
-}
diff --git a/src/pages/auth/login/(sections)/login-form-section.tsx b/src/pages/auth/login/(sections)/login-form-section.tsx
index 2d12377..d023bd4 100644
--- a/src/pages/auth/login/(sections)/login-form-section.tsx
+++ b/src/pages/auth/login/(sections)/login-form-section.tsx
@@ -1,10 +1,14 @@
-import Stack from "@/src/components/twui/layout/Stack";
+import AuthHeading from "@/src/components/general/auth-heading";
import LoginForm from "../(partials)/login-form";
export default function LoginFormSection() {
return (
-
+ <>
+
-
+ >
);
-}
\ No newline at end of file
+}
diff --git a/src/pages/auth/login/index.tsx b/src/pages/auth/login/index.tsx
index 38d3c70..8c614f0 100644
--- a/src/pages/auth/login/index.tsx
+++ b/src/pages/auth/login/index.tsx
@@ -1,11 +1,12 @@
-import Hero from "./(sections)/hero";
+import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
+import { SiteData } from "@/src/data/site-data";
import LoginFormSection from "./(sections)/login-form-section";
export default function LoginPage() {
- return (
- <>
- {/* */}
-
- >
- );
+ return ;
}
+
+export const meta: BunextPageModuleMeta = {
+ title: `Sign in | ${SiteData["SiteName"]}`,
+ description: "Sign in to manage your WireGuard hosts and clients.",
+};
diff --git a/src/pages/auth/logout/index.tsx b/src/pages/auth/logout/index.tsx
index 1d185d4..436e1a7 100644
--- a/src/pages/auth/logout/index.tsx
+++ b/src/pages/auth/logout/index.tsx
@@ -1,29 +1,28 @@
import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
import Loading from "@/src/components/twui/elements/Loading";
-import Row from "@/src/components/twui/layout/Row";
-import Span from "@/src/components/twui/layout/Span";
+import Stack from "@/src/components/twui/layout/Stack";
+import MutedText from "@/src/components/general/muted-text";
import { useEffect } from "react";
import { SiteData } from "@/src/data/site-data";
-import Stack from "@/src/components/twui/layout/Stack";
export default function LogoutPage() {
useEffect(() => {
- setTimeout(() => {
- window.location.pathname = "/";
- }, 2000);
+ // Cookies are cleared by `index.server.ts`
+ const timer = setTimeout(() => {
+ window.location.replace("/auth/login");
+ }, 600);
+
+ return () => clearTimeout(timer);
}, []);
return (
-
-
-
- Logging out ...
-
+
+
+ Signing out…
);
}
export const meta: BunextPageModuleMeta = {
- title: `Logging Out | ${SiteData["SiteName"]}`,
- description: `You are being logged out and redirected to the homepage.`,
+ title: `Signing out | ${SiteData["SiteName"]}`,
};
diff --git a/src/pages/auth/signup/index.tsx b/src/pages/auth/signup/index.tsx
index 9b5236e..600f486 100644
--- a/src/pages/auth/signup/index.tsx
+++ b/src/pages/auth/signup/index.tsx
@@ -1,19 +1,21 @@
import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
-import Stack from "@/src/components/twui/layout/Stack";
import UserForm from "@/src/components/general/user-form";
+import AuthHeading from "@/src/components/general/auth-heading";
import { SiteData } from "@/src/data/site-data";
-import H1 from "@/src/components/twui/layout/H1";
-export default function LogoutPage() {
+export default function SignupPage() {
return (
-
- Create Admin User
+ <>
+
-
+ >
);
}
export const meta: BunextPageModuleMeta = {
- title: `Create Super Admin Account | ${SiteData["SiteName"]}`,
- description: `Create Super Admin Account`,
+ title: `Create admin account | ${SiteData["SiteName"]}`,
+ description: `Create the super admin account`,
};
diff --git a/src/pages/index.server.tsx b/src/pages/index.server.tsx
index 3c57ae8..4c9e1da 100644
--- a/src/pages/index.server.tsx
+++ b/src/pages/index.server.tsx
@@ -1,9 +1,13 @@
import type { BunextPageServerFn } from "@moduletrace/bunext/types";
+import userAuth from "../functions/backend/auth/user-auth";
+
+export const server: BunextPageServerFn = async ({ req }) => {
+ const { user } = await userAuth({ req });
-export const server: BunextPageServerFn = async (ctx) => {
return {
redirect: {
- destination: `/auth/login`,
+ destination: user?.logged_in_status ? "/admin" : "/auth/login",
+ permanent: false,
},
};
};
diff --git a/src/pages/index.tsx b/src/pages/index.tsx
index 3416678..f647e83 100755
--- a/src/pages/index.tsx
+++ b/src/pages/index.tsx
@@ -1,16 +1,12 @@
-import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
-import Hero from "./(sections)/hero";
-import { SiteData } from "../data/site-data";
-
-export default function Home() {
- return (
- <>
-
- >
- );
-}
-
-export const meta: BunextPageModuleMeta = {
- title: `Homepage | ${SiteData["SiteName"]}`,
- description: "Creating Smiles.",
-};
+import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
+import { SiteData } from "../data/site-data";
+
+/** Always redirected by `index.server.tsx`. */
+export default function Home() {
+ return null;
+}
+
+export const meta: BunextPageModuleMeta = {
+ title: SiteData["SiteName"],
+ description: SiteData["SiteDescription"],
+};
diff --git a/src/pages/privacy/(sections)/privacy-section.tsx b/src/pages/privacy/(sections)/privacy-section.tsx
deleted file mode 100644
index ce67941..0000000
--- a/src/pages/privacy/(sections)/privacy-section.tsx
+++ /dev/null
@@ -1,245 +0,0 @@
-import Section from "@/src/components/twui/layout/Section";
-import Container from "@/src/components/twui/layout/Container";
-import Stack from "@/src/components/twui/layout/Stack";
-import H2 from "@/src/components/twui/layout/H2";
-import { SiteData } from "@/src/data/site-data";
-
-export default function PrivacySection() {
- const siteName = SiteData["SiteName"];
- const siteUrl = SiteData["SiteURL"];
-
- return (
-
-
-
-
- At {siteName}, accessible from {siteUrl}, one of our
- main priorities is the privacy of our visitors. This
- Privacy Policy document contains types of information
- that is collected and recorded by {siteName} and how we
- use it.
-
-
-
- If you have additional questions or require more
- information about our Privacy Policy, do not hesitate to
- contact us.
-
-
-
- This Privacy Policy applies only to our online
- activities and is valid for visitors to our website with
- regards to the information that they shared and/or
- collect in {siteName}. This policy is not applicable to
- any information collected offline or via channels other
- than this website.
-
-
- Consent
-
- By using our website, you hereby consent to our Privacy
- Policy and agree to its terms.
-
-
- Information we collect
-
- The personal information that you are asked to provide,
- and the reasons why you are asked to provide it, will be
- made clear to you at the point we ask you to provide
- your personal information.
-
-
- If you contact us directly, we may receive additional
- information about you such as your name, email address,
- phone number, the contents of the message and/or
- attachments you may send us, and any other information
- you may choose to provide.
-
-
- When you register for an Account, we may ask for your
- contact information, including items such as name,
- company name, address, email address, and telephone
- number.
-
-
- How we use your information
-
- We use the information we collect in various ways,
- including to:
-
-
- Provide, operate, and maintain our website
- Improve, personalize, and expand our website
- Understand and analyze how you use our website
-
- Develop new products, services, features, and
- functionality
-
-
- Communicate with you, either directly or through one
- of our partners, including for customer service, to
- provide you with updates and other information
- relating to the website, and for marketing and
- promotional purposes
-
- Send you emails
- Find and prevent fraud
-
-
- Log Files
-
- {siteName} follows a standard procedure of using log
- files. These files log visitors when they visit
- websites. All hosting companies do this and a part of
- hosting services' analytics. The information collected
- by log files include internet protocol (IP) addresses,
- browser type, Internet Service Provider (ISP), date and
- time stamp, referring/exit pages, and possibly the
- number of clicks. These are not linked to any
- information that is personally identifiable. The purpose
- of the information is for analyzing trends,
- administering the site, tracking users' movement on the
- website, and gathering demographic information.
-
-
- Cookies and Web Beacons
-
- Like any other website, {siteName} uses "cookies". These
- cookies are used to store information including
- visitors' preferences, and the pages on the website that
- the visitor accessed or visited. The information is used
- to optimize the users' experience by customizing our web
- page content based on visitors' browser type and/or
- other information.
-
-
- Advertising Partners Privacy Policies
-
- You may consult this list to find the Privacy Policy for
- each of the advertising partners of {siteName}.
-
-
- Third-party ad servers or ad networks uses technologies
- like cookies, JavaScript, or Web Beacons that are used
- in their respective advertisements and links that appear
- on {siteName}, which are sent directly to users'
- browser. They automatically receive your IP address when
- this occurs. These technologies are used to measure the
- effectiveness of their advertising campaigns and/or to
- personalize the advertising content that you see on
- websites that you visit.
-
-
- Note that {siteName} has no access to or control over
- these cookies that are used by third-party advertisers.
-
-
- Third Party Privacy Policies
-
- {siteName}'s Privacy Policy does not apply to other
- advertisers or websites. Thus, we are advising you to
- consult the respective Privacy Policies of these
- third-party ad servers for more detailed information. It
- may include their practices and instructions about how
- to opt-out of certain options.
-
-
- You can choose to disable cookies through your
- individual browser options. To know more detailed
- information about cookie management with specific web
- browsers, it can be found at the browsers' respective
- websites.
-
-
-
- CCPA Privacy Rights (Do Not Sell My Personal
- Information)
-
-
- Under the CCPA, among other rights, California consumers
- have the right to:
-
-
- Request that a business that collects a consumer's
- personal data disclose the categories and specific
- pieces of personal data that a business has collected
- about consumers.
-
-
- Request that a business delete any personal data about
- the consumer that a business has collected.
-
-
- Request that a business that sells a consumer's personal
- data, not sell the consumer's personal data.
-
-
- If you make a request, we have one month to respond to
- you. If you would like to exercise any of these rights,
- please contact us.
-
-
- GDPR Data Protection Rights
-
- We would like to make sure you are fully aware of all of
- your data protection rights. Every user is entitled to
- the following:
-
-
- The right to access – You have the right to request
- copies of your personal data. We may charge you a small
- fee for this service.
-
-
- The right to rectification – You have the right to
- request that we correct any information you believe is
- inaccurate. You also have the right to request that we
- complete the information you believe is incomplete.
-
-
- The right to erasure – You have the right to request
- that we erase your personal data, under certain
- conditions.
-
-
- The right to restrict processing – You have the right to
- request that we restrict the processing of your personal
- data, under certain conditions.
-
-
- The right to object to processing – You have the right
- to object to our processing of your personal data, under
- certain conditions.
-
-
- The right to data portability – You have the right to
- request that we transfer the data that we have collected
- to another organization, or directly to you, under
- certain conditions.
-
-
- If you make a request, we have one month to respond to
- you. If you would like to exercise any of these rights,
- please contact us.
-
-
- Children's Information
-
- Another part of our priority is adding protection for
- children while using the internet. We encourage parents
- and guardians to observe, participate in, and/or monitor
- and guide their online activity.
-
-
- {siteName} does not knowingly collect any Personal
- Identifiable Information from children under the age of
- 13. If you think that your child provided this kind of
- information on our website, we strongly encourage you to
- contact us immediately and we will do our best efforts
- to promptly remove such information from our records.
-
-
-
-
- );
-}
diff --git a/src/pages/privacy/index.tsx b/src/pages/privacy/index.tsx
deleted file mode 100644
index 633c8db..0000000
--- a/src/pages/privacy/index.tsx
+++ /dev/null
@@ -1,19 +0,0 @@
-import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
-import PrivacySection from "./(sections)/privacy-section";
-import InnerPageHeroSection from "@/src/components/general/inner-page-hero-section";
-import { SiteData } from "@/src/data/site-data";
-
-export default function PrivacyPage() {
- return (
- <>
-
-
-
- >
- );
-}
-
-export const meta: BunextPageModuleMeta = {
- title: `Privacy Policy | ${SiteData["SiteName"]}`,
- description: `Read our privacy policy to understand how we collect, use, and protect your personal information.`,
-};
diff --git a/src/pages/terms/(sections)/terms-section.tsx b/src/pages/terms/(sections)/terms-section.tsx
deleted file mode 100644
index f84e63d..0000000
--- a/src/pages/terms/(sections)/terms-section.tsx
+++ /dev/null
@@ -1,338 +0,0 @@
-import Section from "@/src/components/twui/layout/Section";
-import Container from "@/src/components/twui/layout/Container";
-import Stack from "@/src/components/twui/layout/Stack";
-import H3 from "@/src/components/twui/layout/H3";
-import { SiteData } from "@/src/data/site-data";
-
-export default function TermsSection() {
- const siteName = SiteData["SiteName"];
- const siteUrl = SiteData["SiteURL"];
-
- return (
-
-
-
-
- These terms and conditions outline the rules and
- regulations for the use of {siteName}'s Website, located
- at {siteUrl}.
-
-
-
- By accessing this website we assume you accept these
- terms and conditions. Do not continue to use {siteName}{" "}
- if you do not agree to take all of the terms and
- conditions stated on this page.
-
-
-
- The following terminology applies to these Terms and
- Conditions, Privacy Statement and Disclaimer Notice and
- all Agreements: "Client", "You" and "Your" refers to
- you, the person log on this website and compliant to the
- Company's terms and conditions. "The Company",
- "Ourselves", "We", "Our" and "Us", refers to our
- Company. "Party", "Parties", or "Us", refers to both the
- Client and ourselves. All terms refer to the offer,
- acceptance and consideration of payment necessary to
- undertake the process of our assistance to the Client in
- the most appropriate manner for the express purpose of
- meeting the Client's needs in respect of provision of
- the Company's stated services, in accordance with and
- subject to, prevailing law of Nigeria. Any use of the
- above terminology or other words in the singular,
- plural, capitalization and/or he/she or they, are taken
- as interchangeable and therefore as referring to same.
-
-
- Cookies
-
- We employ the use of cookies. By accessing {siteName},
- you agreed to use cookies in agreement with the
- {siteName}'s Privacy Policy.
-
-
- Most interactive websites use cookies to let us retrieve
- the user's details for each visit. Cookies are used by
- our website to enable the functionality of certain areas
- to make it easier for people visiting our website. Some
- of our affiliate/advertising partners may also use
- cookies.
-
-
- License
-
- Unless otherwise stated, {siteName} and/or its licensors
- own the intellectual property rights for all material on{" "}
- {siteName}. All intellectual property rights are
- reserved. You may access this from {siteName} for your
- own personal use subjected to restrictions set in these
- terms and conditions.
-
- You must not:
-
- Republish material from {siteName}
-
- Sell, rent or sub-license material from {siteName}
-
-
- Reproduce, duplicate or copy material from{" "}
- {siteName}
-
- Redistribute content from {siteName}
-
-
- This Agreement shall begin on the date hereof.
-
-
- Parts of this website offer an opportunity for users to
- post and exchange opinions and information in certain
- areas of the website. {siteName} does not filter, edit,
- publish or review Comments prior to their presence on
- the website. Comments do not reflect the views and
- opinions of {siteName},its agents and/or affiliates.
- Comments reflect the views and opinions of the person
- who post their views and opinions. To the extent
- permitted by applicable laws, {siteName} shall not be
- liable for the Comments or for any liability, damages or
- expenses caused and/or suffered as a result of any use
- of and/or posting of and/or appearance of the Comments
- on this website.
-
-
-
- {siteName} reserves the right to monitor all Comments
- and to remove any Comments which can be considered
- inappropriate, offensive or causes breach of these Terms
- and Conditions.
-
-
- You warrant and represent that:
-
-
- You are entitled to post the Comments on our website
- and have all necessary licenses and consents to do
- so;
-
-
- The Comments do not invade any intellectual property
- right, including without limitation copyright,
- patent or trademark of any third party;
-
-
- The Comments do not contain any defamatory,
- libelous, offensive, indecent or otherwise unlawful
- material which is an invasion of privacy
-
-
- The Comments will not be used to solicit or promote
- business or custom or present commercial activities
- or unlawful activity.
-
-
-
-
- You hereby grant {siteName} a non-exclusive license to
- use, reproduce, edit and authorize others to use,
- reproduce and edit any of your Comments in any and all
- forms, formats or media.
-
-
- Hyperlinking to our Content
-
- The following organizations may link to our Website
- without prior written approval:
-
-
- Government agencies;
- Search engines;
- News organizations;
-
- Online directory distributors may link to our
- Website in the same manner as they hyperlink to the
- Websites of other listed businesses; and
-
-
- System wide Accredited Businesses except soliciting
- non-profit organizations, charity shopping malls,
- and charity fundraising groups which may not
- hyperlink to our Web site.
-
-
-
-
- These organizations may link to our home page, to
- publications or to other Website information so long as
- the link: (a) is not in any way deceptive; (b) does not
- falsely imply sponsorship, endorsement or approval of
- the linking party and its products and/or services; and
- (c) fits within the context of the linking party's site.
-
-
-
- We may consider and approve other link requests from the
- following types of organizations:
-
-
-
- commonly-known consumer and/or business information
- sources;
-
- dot.com community sites;
-
- associations or other groups representing charities;
-
- online directory distributors;
- internet portals;
- accounting, law and consulting firms; and
-
- educational institutions and trade associations.
-
-
-
-
- We will approve link requests from these organizations
- if we decide that: (a) the link would not make us look
- unfavorably to ourselves or to our accredited
- businesses; (b) the organization does not have any
- negative records with us; (c) the benefit to us from the
- visibility of the hyperlink compensates the absence of{" "}
- {siteName}; and (d) the link is in the context of
- general resource information.
-
-
-
- These organizations may link to our home page so long as
- the link: (a) is not in any way deceptive; (b) does not
- falsely imply sponsorship, endorsement or approval of
- the linking party and its products or services; and (c)
- fits within the context of the linking party's site.
-
-
-
- If you are one of the organizations listed in paragraph
- 2 above and are interested in linking to our website,
- you must inform us by sending an e-mail to {siteName}.
- Please include your name, your organization name,
- contact information as well as the URL of your site, a
- list of any URLs from which you intend to link to our
- Website, and a list of the URLs on our site to which you
- would like to link. Wait 2-3 weeks for a response.
-
-
-
- Approved organizations may hyperlink to our Website as
- follows:
-
-
- By use of our corporate name; or
-
- By use of the uniform resource locator being linked
- to; or
-
-
- By use of any other description of our Website being
- linked to that makes sense within the context and
- format of content on the linking party's site.
-
-
-
-
- No use of {siteName}'s logo or other artwork will be
- allowed for linking absent a trademark license
- agreement.
-
-
- iFrames
-
- Without prior approval and written permission, you may
- not create frames around our Webpages that alter in any
- way the visual presentation or appearance of our
- Website.
-
-
- Content Liability
-
- We shall not be hold responsible for any content that
- appears on your Website. You agree to protect and defend
- us against all claims that is rising on your Website. No
- link(s) should appear on any Website that may be
- interpreted as libelous, obscene or criminal, or which
- infringes, otherwise violates, or advocates the
- infringement or other violation of, any third party
- rights.
-
-
- Your Privacy
- Please read our Privacy Policy.
-
- Reservation of Rights
-
- We reserve the right to request that you remove all
- links or any particular link to our Website. You approve
- to immediately remove all links to our Website upon
- request. We also reserve the right to amen these terms
- and conditions and it's linking policy at any time. By
- continuously linking to our Website, you agree to be
- bound to and follow these linking terms and conditions.
-
-
- Removal of links from our website
-
- If you find any link on our Website that is offensive
- for any reason, you are free to contact and inform us
- any moment. We will consider requests to remove links
- but we are not obligated to or so or to respond to you
- directly.
-
-
- We do not ensure that the information on this website is
- correct, we do not warrant its completeness or accuracy;
- nor do we promise to ensure that the website remains
- available or that the material on the website is kept up
- to date.
-
-
- Disclaimer
-
- To the maximum extent permitted by applicable law, we
- exclude all representations, warranties and conditions
- relating to our website and the use of this website.
- Nothing in this disclaimer will:
-
-
-
- limit or exclude our or your liability for death or
- personal injury;
-
-
- limit or exclude our or your liability for fraud or
- fraudulent misrepresentation;
-
-
- limit any of our or your liabilities in any way that
- is not permitted under applicable law; or
-
-
- exclude any of our or your liabilities that may not
- be excluded under applicable law.
-
-
-
- The limitations and prohibitions of liability set in
- this Section and elsewhere in this disclaimer: (a) are
- subject to the preceding paragraph; and (b) govern all
- liabilities arising under the disclaimer, including
- liabilities arising in contract, in tort and for breach
- of statutory duty.
-
-
- As long as the website and the information and services
- on the website are provided free of charge, we will not
- be liable for any loss or damage of any nature.
-
-
-
-
- );
-}
diff --git a/src/pages/terms/index.tsx b/src/pages/terms/index.tsx
deleted file mode 100644
index a7a4908..0000000
--- a/src/pages/terms/index.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import type { BunextPageModuleMeta } from "@moduletrace/bunext/types";
-import TermsSection from "./(sections)/terms-section";
-import InnerPageHeroSection from "@/src/components/general/inner-page-hero-section";
-import { SiteData } from "@/src/data/site-data";
-
-export default function TermsPage() {
- return (
- <>
-
-
-
- >
- );
-}
-
-export const meta: BunextPageModuleMeta = {
- title: `Terms and Conditions | ${SiteData["SiteName"]}`,
- description: `Read our terms and conditions for using the ${SiteData["SiteName"]} website.`,
-};
diff --git a/src/styles/main.css b/src/styles/main.css
index 0ac5788..51d5238 100755
--- a/src/styles/main.css
+++ b/src/styles/main.css
@@ -2,74 +2,129 @@
@import "../components/twui/base.css";
+/* -------------------------------------------------------------------------- */
+/* Design tokens */
+/* -------------------------------------------------------------------------- */
+/*
+ * One accent (WireGuard red). twui reads `primary`, `secondary` and `accent` in 8 forms
+ * each, so all three alias the same accent.
+ *
+ * Undefined twui Button combos — avoid them:
+ * variant="normal" + color="text" | "warning"
+ * variant="outlined" + color="text" | "success"
+ */
@theme inline {
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
+ --font-mono:
+ ui-monospace, "JetBrains Mono", SFMono-Regular, Menlo, Consolas,
+ monospace;
+
+ /* Neutrals (zinc) */
+ --color-background-light: #ffffff;
+ --color-background-dark: #09090b;
+ --color-foreground-light: #18181b;
+ --color-foreground-dark: #fafafa;
+ --color-gray: #f4f4f5;
+ --color-gray-hover: #e4e4e7;
+ --color-gray-dark: #27272a;
+ --color-gray-dark-hover: #3f3f46;
+ --color-dark: #09090b;
+ --color-light: #fafafa;
+
+ /* Accent: WireGuard red (#89161b, the logo colour). Lighter in dark
+ mode so text and buttons keep contrast on near-black. */
--color-primary: #89161b;
- --color-primary-hover: #89161b;
+ --color-primary-hover: #6f1216;
--color-primary-outline: #89161b;
--color-primary-text: #89161b;
- --color-primary-dark: #89161b;
- --color-primary-dark-hover: #89161b;
- --color-primary-dark-outline: #89161b;
- --color-primary-dark-text: #89161b;
+ --color-primary-dark: #b3242a;
+ --color-primary-dark-hover: #c9343a;
+ --color-primary-dark-outline: #d9575c;
+ --color-primary-dark-text: #ef7a7e;
- --color-secondary: #32736c;
- --color-secondary-hover: #32736c;
- --color-secondary-outline: #32736c;
- --color-secondary-text: #32736c;
- --color-secondary-dark: #1d524c;
- --color-secondary-dark-hover: #1d524c;
- --color-secondary-dark-outline: #32736c;
- --color-secondary-dark-text: #32736c;
+ --color-secondary: #89161b;
+ --color-secondary-hover: #6f1216;
+ --color-secondary-outline: #89161b;
+ --color-secondary-text: #89161b;
+ --color-secondary-dark: #b3242a;
+ --color-secondary-dark-hover: #c9343a;
+ --color-secondary-dark-outline: #d9575c;
+ --color-secondary-dark-text: #ef7a7e;
--color-accent: #89161b;
- --color-accent-hover: #89161b;
+ --color-accent-hover: #6f1216;
--color-accent-outline: #89161b;
--color-accent-text: #89161b;
- --color-accent-dark: #89161b;
- --color-accent-dark-hover: #89161b;
- --color-accent-dark-outline: #89161b;
- --color-accent-dark-text: #89161b;
+ --color-accent-dark: #b3242a;
+ --color-accent-dark-hover: #c9343a;
+ --color-accent-dark-outline: #d9575c;
+ --color-accent-dark-text: #ef7a7e;
- --color-dark: #000000;
- --color-dark-hover: #000000;
- --color-dark-outline: #000000;
- --color-dark-text: #000000;
- --color-dark-dark: #000000;
- --color-dark-dark-hover: #000000;
- --color-dark-dark-outline: #000000;
- --color-dark-dark-text: #000000;
+ /* Status (incl. hover tokens twui references) */
+ --color-success: #16a34a;
+ --color-success-hover: #15803d;
+ --color-success-dark: #22c55e;
+ --color-error: #e5484d;
+ --color-error-hover: #cd2b31;
+ --color-error-dark: #ff6369;
+ --color-warning: #d97706;
+ --color-warning-hover: #b45309;
+ --color-warning-dark: #fbbf24;
- --color-light: #f2f1f0;
- --color-light-hover: #f2f1f0;
- --color-light-outline: #f2f1f0;
- --color-light-text: #f2f1f0;
- --color-light-dark: #f2f1f0;
- --color-light-dark-hover: #f2f1f0;
- --color-light-dark-outline: #f2f1f0;
- --color-light-dark-text: #f2f1f0;
+ --color-link: #89161b;
+ --color-link-dark: #ef7a7e;
- /* --color-gray: #817d78;
- --color-gray-hover: #817d78; */
+ /* Semantic, theme-flipping — app code uses these without `dark:` pairs */
+ --color-surface: var(--wg-surface);
+ --color-surface-2: var(--wg-surface-2);
+ --color-line: var(--wg-line);
+ --color-muted: var(--wg-muted);
+ --color-subtle: var(--wg-subtle);
+ --color-fg: var(--wg-fg);
- --radius-default: 3px;
+ --radius-default: 6px;
+ --radius-default-sm: 4px;
+ --radius-default-xs: 2px;
+ --radius-default-lg: 8px;
+ --radius-default-xl: 12px;
}
+:root {
+ --wg-surface: #ffffff;
+ --wg-surface-2: #fafafa;
+ --wg-line: #e4e4e7;
+ --wg-muted: #52525b;
+ --wg-subtle: #a1a1aa;
+ --wg-fg: #18181b;
+ color-scheme: light;
+}
+
+.dark {
+ --wg-surface: #0f0f11;
+ --wg-surface-2: #18181b;
+ --wg-line: #27272a;
+ --wg-muted: #a1a1aa;
+ --wg-subtle: #71717a;
+ --wg-fg: #fafafa;
+ color-scheme: dark;
+}
+
+/* -------------------------------------------------------------------------- */
+/* Base */
+/* -------------------------------------------------------------------------- */
+
body {
- font-family: "Inter", Arial, Helvetica, sans-serif;
+ font-family: var(--font-sans);
font-optical-sizing: auto;
font-feature-settings:
"cv02" 1,
"cv03" 1,
"cv11" 1;
+ @apply text-base bg-surface-2 text-fg antialiased;
}
.tabular {
font-variant-numeric: tabular-nums;
- font-feature-settings:
- "tnum" 1,
- "cv02" 1,
- "cv03" 1,
- "cv11" 1;
}
h1,
@@ -79,122 +134,174 @@ h4,
h5,
h6,
.heading {
- font-family: "Inter", Arial, Helvetica, sans-serif;
- font-optical-sizing: auto;
- @apply m-0! font-bold;
+ font-family: var(--font-sans);
+ @apply m-0! font-semibold tracking-tight text-fg;
}
h1 {
- @apply text-5xl xl:text-7xl!;
+ @apply text-2xl;
}
h2 {
- @apply text-4xl xl:text-5xl!;
+ @apply text-lg;
}
-h3 {
- @apply text-2xl! xl:text-3xl!;
-}
-
-h4 {
- @apply text-lg! xl:text-xl!;
-}
-
-.twui-button-general {
- @apply font-semibold;
-}
-
-a {
- @apply outline-none border-none text-primary;
-}
-
-.twui-section {
- @apply py-44;
-}
-
-.htag {
- @apply uppercase font-bold text-secondary/70 tracking-widest;
-}
-
-header nav a > * {
- @apply transition-transform;
-}
-
-header nav a:hover > * {
- @apply -translate-y-0.5;
-}
-
-#hero-image {
- @apply object-center lg:object-left -z-1 max-lg:scale-[120%];
-}
-
-.hero-section {
- @apply pt-[200px] text-white bg-dark relative;
- /* @apply max-w-sm:pb-0; */
-}
-
-.hero-section .htag {
- @apply text-accent;
-}
-
-#admin-main {
- @apply max-w-[1200px];
-}
-
-#admin-main section {
- @apply p-6 flex flex-col items-start;
-}
-
-#admin-main h1 {
- @apply text-4xl!;
-}
-
-#admin-main h2 {
- @apply text-2xl!;
-}
-
-#admin-main h3 {
- @apply text-xl!;
-}
-
-#admin-main h4 {
- @apply text-lg!;
-}
-
-.header-user-dropdown * {
- @apply text-dark;
-}
-
-.header-user-dropdown a {
+h3,
+h4,
+h5,
+h6 {
@apply text-base;
}
-.twui-anchor {
- @apply text-secondary;
-}
-
-.twui-anchor.active {
- @apply font-bold opacity-100! text-secondary-dark;
-}
-
-.bio-subtitle {
- @apply max-w-2xl text-ellipsis line-clamp-3;
-}
-
-.bio-subtitle p,
-.bio-subtitle span {
- @apply text-sm;
-}
-
-.bio-content p {
- @apply my-4;
-}
-
-#admin-main section {
- @apply gap-10;
+a {
+ @apply outline-none border-none text-primary dark:text-primary-dark-text;
}
p code,
span code {
- @apply p-1 mx-1 bg-slate-100;
+ @apply font-mono text-sm px-1 py-0.5 rounded-default-sm bg-gray dark:bg-gray-dark;
+}
+
+:focus-visible {
+ @apply outline-2 outline-offset-2 outline-primary dark:outline-primary-dark;
+}
+
+/* -------------------------------------------------------------------------- */
+/* twui overrides */
+/* -------------------------------------------------------------------------- */
+
+.twui-anchor {
+ @apply border-b-0 leading-normal;
+}
+
+.twui-button-general {
+ @apply font-medium;
+}
+
+/* twui buttons default to 14px; match the app's 15–16px type scale */
+.twui-button-base,
+.twui-button-small {
+ @apply text-[15px];
+}
+
+.twui-button-smaller {
+ @apply text-[13px];
+}
+
+.twui-p {
+ @apply py-0 text-muted;
+}
+
+.twui-card,
+.twui-paper,
+.twui-border {
+ @apply border-line! bg-surface;
+}
+
+.twui-card {
+ @apply transition-colors;
+}
+
+.twui-card:hover {
+ @apply border-subtle!;
+}
+
+.twui-tag {
+ @apply font-medium;
+}
+
+.twui-modal-content {
+ @apply rounded-default-lg border-line! bg-surface! shadow-2xl;
+}
+
+.twui-modal-root > div:first-child {
+ @apply bg-dark/60 backdrop-blur-[2px];
+}
+
+.twui-toast {
+ @apply shadow-lg rounded-default-lg pl-4 pr-9 py-3 text-base max-w-[calc(100vw-2rem)];
+}
+
+.twui-empty-content-border {
+ @apply border-line! border-dashed;
+}
+
+.twui-code-block-wrapper {
+ @apply outline-line! max-w-full!;
+}
+
+.twui-code-block-pre {
+ @apply font-mono text-sm leading-relaxed text-zinc-100;
+}
+
+/* Inputs: label sits above the field instead of floating on the border */
+.twui-input-label {
+ @apply -top-6! left-0! px-0! bg-transparent! text-muted! text-[13px] font-medium;
+}
+
+.twui-input-wrapper:has(> .twui-input-label),
+div:has(> .twui-select):has(> .twui-input-label) {
+ @apply mt-6;
+}
+
+.twui-input-wrapper,
+div:has(> .twui-select) {
+ @apply bg-surface! text-[15px];
+}
+
+.twui-input-wrapper:not(.twui-input-invalid) {
+ outline-color: var(--wg-line) !important;
+}
+
+.twui-input-wrapper:not(.twui-input-invalid):focus-within,
+div:has(> .twui-select):focus-within {
+ outline-color: var(--color-primary) !important;
+ border-color: var(--color-primary) !important;
+}
+
+div:has(> .twui-select) {
+ border-color: var(--wg-line) !important;
+}
+
+.twui-select {
+ @apply bg-transparent text-fg;
+}
+
+.twui-clear-input-field-button {
+ @apply bg-transparent!;
+}
+
+/* Tabs: underline style */
+.twui-tabs-border {
+ @apply border-0! border-b! border-line! rounded-none! bg-transparent;
+}
+
+.twui-tab-buttons {
+ @apply px-3 py-2.5 rounded-none -mb-px border-b-2 border-transparent text-base font-medium text-muted ml-0;
+}
+
+.twui-tab-buttons:hover {
+ @apply text-fg;
+}
+
+.twui-tab-button-active {
+ @apply bg-transparent! text-fg! border-primary! dark:border-primary-dark!;
+}
+
+/* TinyMCE: the content iframe styles itself via buildRteContentStyle; this
+ only keeps the frame from flashing white before that CSS loads. */
+.tox .tox-edit-area__iframe {
+ background-color: var(--wg-surface) !important;
+}
+
+.twui-dropdown-content {
+ @apply z-50;
+}
+
+/* -------------------------------------------------------------------------- */
+/* App layout */
+/* -------------------------------------------------------------------------- */
+
+#admin-main {
+ @apply w-full max-w-[1280px] mx-auto;
}
diff --git a/src/types/index.ts b/src/types/index.ts
index 30aab5e..644796b 100755
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -9,8 +9,8 @@ import type {
BUN_SQLITE_WGUI_VARIABLES,
BunSQLiteTables,
} from "@/db/types/db";
-import type { SBFSusidiaries } from "../dict/subsidiaries-dict";
import type { UserTypes } from "../dict/user-types-dict";
+import type { ThemeMode } from "../dict/theme-dict";
import type { MediaParadigms, MediaTypes } from "../dict/media-dict";
import type {
ClientRuleProtocols,
@@ -74,39 +74,17 @@ export type PagePropsType = {
next_available_client_ip?: string | null;
is_local_ip?: boolean | null;
setup_status?: ReturnType | null;
+ theme?: ThemeMode | null;
};
export type AppContextObject = {
user: User | null;
- appState: AppStateObject;
- setAppState: React.Dispatch>;
- refresh?: number;
- setRefresh?: React.Dispatch>;
- pageData?: any;
pageProps?: PagePropsType;
query?: PageQueryObject | null;
};
export type AppDataObject = {};
-export type AppStateObject = {
- data?: AppDataObject;
- likes?: string[] | null;
- currency?: CurrencyObjectType;
- fonts?: any;
- setOpenLoginModal?: React.Dispatch>;
- setOpenSignUpModal?: React.Dispatch>;
-};
-
-export type CurrencyObjectType = {
- symbol: string;
- code: string;
- multiplier?: number;
- icon?: string;
- country?: string;
- countryCode?: string;
-} | null;
-
export type DSQL_RESPONSE = {
success: boolean;
payload?: Object[] | string | any[] | { error: string } | null;
@@ -185,7 +163,6 @@ export type FileBase64Object = {
fileType: string;
};
-export type SubsidiaryType = (typeof SBFSusidiaries)[number];
export type UserType = (typeof UserTypes)[number]["value"];
diff --git a/src/utils/derive-host-display-name.ts b/src/utils/derive-host-display-name.ts
new file mode 100644
index 0000000..fc42f6b
--- /dev/null
+++ b/src/utils/derive-host-display-name.ts
@@ -0,0 +1,9 @@
+type Params = {
+ host?: { id?: number | string; name?: string } | null;
+};
+
+export default function deriveHostDisplayName({ host }: Params) {
+ if (host?.name) return host.name;
+ const id = Number(host?.id || 0);
+ return id == 0 ? "Main host" : `Host #${id}`;
+}