mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: update development and deployment scripts to include build step; refactor navigation links in AppAuthenticatedShell component
This commit is contained in:
+4
-4
@@ -7,16 +7,16 @@
|
||||
"main": "src/index.ts",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "wrangler dev -c wrangler.toml",
|
||||
"dev:kv": "wrangler dev -c wrangler.kv.toml",
|
||||
"dev": "npm run build && wrangler dev -c wrangler.toml",
|
||||
"dev:kv": "npm run build && wrangler dev -c wrangler.kv.toml",
|
||||
"dev:demo": "vite --config webapp/vite.config.ts --mode demo --host 127.0.0.1 --port 5174",
|
||||
"build": "vite build --config webapp/vite.config.ts",
|
||||
"build:demo": "vite build --config webapp/vite.config.ts --mode demo && node scripts/pages-spa-redirects.cjs",
|
||||
"domains:sync": "node scripts/sync-global-domains.mjs",
|
||||
"i18n": "node scripts/i18n-validate.cjs",
|
||||
"i18n:validate": "node scripts/i18n-validate.cjs",
|
||||
"deploy": "wrangler deploy",
|
||||
"deploy:kv": "wrangler deploy -c wrangler.kv.toml",
|
||||
"deploy": "npm run build && wrangler deploy",
|
||||
"deploy:kv": "npm run build && wrangler deploy -c wrangler.kv.toml",
|
||||
"deploy:demo": "npm run build:demo && wrangler pages deploy dist --project-name nw-demo"
|
||||
},
|
||||
"keywords": [
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { ChevronDown, Clock3, Cloud, Folder as FolderIcon, KeyRound, Lock, LogOut, Send as SendIcon, Settings as SettingsIcon, ShieldUser } from 'lucide-preact';
|
||||
import type { ComponentChildren } from 'preact';
|
||||
import { useState } from 'preact/hooks';
|
||||
import { ArrowUpDown, Clock3, Cloud, Folder as FolderIcon, Globe2, KeyRound, Lock, LogOut, MonitorSmartphone, Send as SendIcon, Settings as SettingsIcon, ShieldUser, Users } from 'lucide-preact';
|
||||
import { Link } from 'wouter';
|
||||
import AppMainRoutes from '@/components/AppMainRoutes';
|
||||
import ThemeSwitch from '@/components/ThemeSwitch';
|
||||
@@ -34,53 +32,6 @@ function isAdminProfile(profile: Profile | null): boolean {
|
||||
export default function AppAuthenticatedShell(props: AppAuthenticatedShellProps) {
|
||||
const routeAnimationKey = props.isImportRoute ? props.importRoute : props.location;
|
||||
const isAdmin = isAdminProfile(props.profile);
|
||||
const vaultActive = props.location === '/vault' || props.location === '/vault/totp';
|
||||
const settingsActive = props.location === props.settingsAccountRoute || props.location === '/settings/domain-rules';
|
||||
const dataActive = props.location === '/backup' || props.isImportRoute;
|
||||
const managementActive = props.location === '/admin' || props.location === '/security/devices';
|
||||
const [expandedGroups, setExpandedGroups] = useState({
|
||||
vault: true,
|
||||
settings: false,
|
||||
data: false,
|
||||
management: false,
|
||||
});
|
||||
|
||||
function toggleGroup(group: keyof typeof expandedGroups): void {
|
||||
setExpandedGroups((current) => ({ ...current, [group]: !current[group] }));
|
||||
}
|
||||
|
||||
function groupOpen(group: keyof typeof expandedGroups, active: boolean): boolean {
|
||||
return expandedGroups[group] || active;
|
||||
}
|
||||
|
||||
function renderNavGroup(
|
||||
group: keyof typeof expandedGroups,
|
||||
title: string,
|
||||
icon: ComponentChildren,
|
||||
active: boolean,
|
||||
children: ComponentChildren
|
||||
) {
|
||||
const open = groupOpen(group, active);
|
||||
return (
|
||||
<div className={`side-nav-group ${open ? 'open' : ''}`}>
|
||||
<button
|
||||
type="button"
|
||||
className={`side-group-trigger ${active ? 'active' : ''}`}
|
||||
aria-expanded={open}
|
||||
onClick={() => toggleGroup(group)}
|
||||
>
|
||||
{icon}
|
||||
<span>{title}</span>
|
||||
<ChevronDown size={15} className="side-group-chevron" />
|
||||
</button>
|
||||
<div className={`side-subnav ${open ? 'open' : ''}`}>
|
||||
<div className="side-subnav-inner">
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app-page">
|
||||
@@ -125,70 +76,46 @@ export default function AppAuthenticatedShell(props: AppAuthenticatedShellProps)
|
||||
|
||||
<div className="app-main">
|
||||
<aside className="app-side">
|
||||
{renderNavGroup(
|
||||
'vault',
|
||||
t('nav_my_vault'),
|
||||
<KeyRound size={16} />,
|
||||
vaultActive,
|
||||
<>
|
||||
<Link href="/vault" className={`side-sub-link ${props.location === '/vault' ? 'active' : ''}`}>
|
||||
<span>{t('nav_vault_items')}</span>
|
||||
</Link>
|
||||
<Link href="/vault/totp" className={`side-sub-link ${props.location === '/vault/totp' ? 'active' : ''}`}>
|
||||
<span>{t('txt_verification_code')}</span>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<Link href="/vault" className={`side-link ${props.location === '/vault' ? 'active' : ''}`}>
|
||||
<KeyRound size={16} />
|
||||
<span>{t('nav_vault_items')}</span>
|
||||
</Link>
|
||||
<Link href="/vault/totp" className={`side-link ${props.location === '/vault/totp' ? 'active' : ''}`}>
|
||||
<Clock3 size={16} />
|
||||
<span>{t('txt_verification_code')}</span>
|
||||
</Link>
|
||||
<Link href="/sends" className={`side-link ${props.location === '/sends' ? 'active' : ''}`}>
|
||||
<SendIcon size={16} />
|
||||
<span>{t('nav_sends')}</span>
|
||||
</Link>
|
||||
{renderNavGroup(
|
||||
'settings',
|
||||
t('txt_settings'),
|
||||
<SettingsIcon size={16} />,
|
||||
settingsActive,
|
||||
<>
|
||||
<Link href={props.settingsAccountRoute} className={`side-sub-link ${props.location === props.settingsAccountRoute ? 'active' : ''}`}>
|
||||
<span>{t('nav_account_settings')}</span>
|
||||
</Link>
|
||||
<Link href="/settings/domain-rules" className={`side-sub-link ${props.location === '/settings/domain-rules' ? 'active' : ''}`}>
|
||||
<span>{t('nav_domain_rules')}</span>
|
||||
</Link>
|
||||
</>
|
||||
<Link href={props.settingsAccountRoute} className={`side-link ${props.location === props.settingsAccountRoute ? 'active' : ''}`}>
|
||||
<SettingsIcon size={16} />
|
||||
<span>{t('nav_account_settings')}</span>
|
||||
</Link>
|
||||
<Link href="/settings/domain-rules" className={`side-link ${props.location === '/settings/domain-rules' ? 'active' : ''}`}>
|
||||
<Globe2 size={16} />
|
||||
<span>{t('nav_domain_rules')}</span>
|
||||
</Link>
|
||||
{isAdmin && (
|
||||
<Link href="/backup" className={`side-link ${props.location === '/backup' ? 'active' : ''}`}>
|
||||
<Cloud size={16} />
|
||||
<span>{t('nav_backup_strategy')}</span>
|
||||
</Link>
|
||||
)}
|
||||
{renderNavGroup(
|
||||
'data',
|
||||
t('nav_group_data_backup'),
|
||||
<Cloud size={16} />,
|
||||
dataActive,
|
||||
<>
|
||||
{isAdmin && (
|
||||
<Link href="/backup" className={`side-sub-link ${props.location === '/backup' ? 'active' : ''}`}>
|
||||
<span>{t('nav_backup_strategy')}</span>
|
||||
</Link>
|
||||
)}
|
||||
<Link href={props.importRoute} className={`side-sub-link ${props.isImportRoute ? 'active' : ''}`}>
|
||||
<span>{t('nav_import_export')}</span>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
{renderNavGroup(
|
||||
'management',
|
||||
t('nav_group_management'),
|
||||
<ShieldUser size={16} />,
|
||||
managementActive,
|
||||
<>
|
||||
{isAdmin && (
|
||||
<Link href="/admin" className={`side-sub-link ${props.location === '/admin' ? 'active' : ''}`}>
|
||||
<span>{t('nav_admin_panel')}</span>
|
||||
</Link>
|
||||
)}
|
||||
<Link href="/security/devices" className={`side-sub-link ${props.location === '/security/devices' ? 'active' : ''}`}>
|
||||
<span>{t('nav_device_management')}</span>
|
||||
</Link>
|
||||
</>
|
||||
<Link href={props.importRoute} className={`side-link ${props.isImportRoute ? 'active' : ''}`}>
|
||||
<ArrowUpDown size={16} />
|
||||
<span>{t('nav_import_export')}</span>
|
||||
</Link>
|
||||
{isAdmin && (
|
||||
<Link href="/admin" className={`side-link ${props.location === '/admin' ? 'active' : ''}`}>
|
||||
<Users size={16} />
|
||||
<span>{t('nav_admin_panel')}</span>
|
||||
</Link>
|
||||
)}
|
||||
<Link href="/security/devices" className={`side-link ${props.location === '/security/devices' ? 'active' : ''}`}>
|
||||
<MonitorSmartphone size={16} />
|
||||
<span>{t('nav_device_management')}</span>
|
||||
</Link>
|
||||
</aside>
|
||||
<main className="content">
|
||||
<div key={routeAnimationKey} className={`route-stage ${props.location === '/settings/domain-rules' ? 'route-stage-fixed' : ''}`}>
|
||||
|
||||
@@ -167,73 +167,12 @@
|
||||
@apply flex items-center gap-2.5 rounded-xl border border-transparent px-3 py-2.5 text-sm font-semibold text-muted-strong no-underline transition;
|
||||
}
|
||||
|
||||
.side-nav-group {
|
||||
@apply grid gap-1;
|
||||
}
|
||||
|
||||
.side-group-trigger {
|
||||
@apply flex w-full cursor-pointer items-center gap-2.5 rounded-xl border border-transparent px-3 py-2.5 text-left text-sm font-semibold text-muted-strong transition;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.side-group-trigger:hover {
|
||||
background: #fff;
|
||||
border-color: rgba(128, 152, 192, 0.18);
|
||||
color: var(--text);
|
||||
box-shadow: 0 10px 18px rgba(15, 23, 42, 0.04);
|
||||
}
|
||||
|
||||
.side-group-trigger.active {
|
||||
color: var(--primary-strong);
|
||||
}
|
||||
|
||||
.side-group-trigger span {
|
||||
.side-link span {
|
||||
@apply min-w-0 flex-1 truncate;
|
||||
}
|
||||
|
||||
.side-group-chevron {
|
||||
.side-link svg {
|
||||
@apply shrink-0;
|
||||
transition: transform 190ms var(--ease-out-soft);
|
||||
}
|
||||
|
||||
.side-nav-group.open .side-group-chevron {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.side-subnav {
|
||||
display: grid;
|
||||
grid-template-rows: 0fr;
|
||||
opacity: 0;
|
||||
transform: translateY(-4px);
|
||||
transition:
|
||||
grid-template-rows 220ms var(--ease-smooth),
|
||||
opacity 170ms var(--ease-smooth),
|
||||
transform 220ms var(--ease-out-soft);
|
||||
}
|
||||
|
||||
.side-subnav.open {
|
||||
grid-template-rows: 1fr;
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.side-subnav-inner {
|
||||
@apply grid gap-1 overflow-hidden pl-[38px] pr-1;
|
||||
}
|
||||
|
||||
.side-sub-link {
|
||||
@apply block rounded-lg border border-transparent px-3 py-2 text-sm font-semibold text-muted no-underline transition;
|
||||
}
|
||||
|
||||
.side-sub-link:hover {
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.side-sub-link.active {
|
||||
background: rgba(37, 99, 235, 0.10);
|
||||
border-color: rgba(37, 99, 235, 0.18);
|
||||
color: var(--primary-strong);
|
||||
}
|
||||
|
||||
.side-link:hover {
|
||||
|
||||
@@ -2,27 +2,12 @@ name = "nodewarden"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-01-01"
|
||||
|
||||
[observability]
|
||||
enabled = false
|
||||
|
||||
[observability.logs]
|
||||
enabled = true
|
||||
head_sampling_rate = 1
|
||||
persist = true
|
||||
invocation_logs = true
|
||||
|
||||
[observability.traces]
|
||||
enabled = false
|
||||
|
||||
[assets]
|
||||
binding = "ASSETS"
|
||||
directory = "./dist"
|
||||
not_found_handling = "single-page-application"
|
||||
run_worker_first = false
|
||||
|
||||
[build]
|
||||
command = "npm run build"
|
||||
|
||||
[triggers]
|
||||
crons = [ "*/5 * * * *" ]
|
||||
|
||||
|
||||
@@ -2,27 +2,12 @@ name = "nodewarden"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-01-01"
|
||||
|
||||
[observability]
|
||||
enabled = false
|
||||
|
||||
[observability.logs]
|
||||
enabled = true
|
||||
head_sampling_rate = 1
|
||||
persist = true
|
||||
invocation_logs = true
|
||||
|
||||
[observability.traces]
|
||||
enabled = false
|
||||
|
||||
[assets]
|
||||
binding = "ASSETS"
|
||||
directory = "./dist"
|
||||
not_found_handling = "single-page-application"
|
||||
run_worker_first = false
|
||||
|
||||
[build]
|
||||
command = "npm run build"
|
||||
|
||||
[triggers]
|
||||
crons = [ "*/5 * * * *" ]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user