chore(monitor/frontend): demolish template shell + landing for clean dashboard canvas

Cleared:
- core/app/shell.tsx + shell.module.scss (sidebar+header layout)
- core/app/toast.module.scss (replaced by inline ethos toaster styling)
- core/lib/shell.ui.store.ts (sidebar collapsed state, no longer needed)
- pages/landing/ (template marketing copy)
- pages/dashboard/ template placeholder (replaced with minimal scaffold)
- components/index.tsx (empty stub)

Kept:
- All auth infrastructure (apiClient, auth.store, ProtectedRoute)
- Auth pages (login/register/settings/admin) — they keep the template's
  visual style for now; not on the operator's hot path
- All my Phase 5 work (ethos tokens, snapshot/ws/stores)

Routes now: / -> dashboard scaffold (no Shell), /login + /register
standalone, /settings + /admin auth-gated standalone (no Shell wrapper).
This commit is contained in:
CarterPerez-dev 2026-05-03 02:04:26 -04:00
parent 7a97aac091
commit 50b0e96735
12 changed files with 40 additions and 1158 deletions

View File

@ -1,7 +1,5 @@
// ===========================
// ©AngelaMos | 2025
// ©AngelaMos | 2026
// App.tsx
// ===========================
import { QueryClientProvider } from '@tanstack/react-query'
import { ReactQueryDevtools } from '@tanstack/react-query-devtools'
@ -10,26 +8,26 @@ import { Toaster } from 'sonner'
import { queryClient } from '@/core/api'
import { router } from '@/core/app/routers'
import '@/core/app/toast.module.scss'
export default function App(): React.ReactElement {
return (
<QueryClientProvider client={queryClient}>
<div className="app">
<RouterProvider router={router} />
<Toaster
position="top-right"
duration={2000}
theme="dark"
toastOptions={{
style: {
background: 'hsl(0, 0%, 12.2%)',
border: '1px solid hsl(0, 0%, 18%)',
color: 'hsl(0, 0%, 98%)',
},
}}
/>
</div>
<RouterProvider router={router} />
<Toaster
position="top-right"
duration={2500}
theme="dark"
toastOptions={{
style: {
background: 'var(--bg-panel)',
border: '1px solid var(--fg-4)',
color: 'var(--fg-1)',
borderRadius: 0,
fontFamily: 'var(--font-sans)',
fontSize: 'var(--type-body)',
},
}}
/>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
)

View File

@ -1,4 +0,0 @@
/**
* ©AngelaMos | 2025
* index.tsx
*/

View File

@ -1,18 +1,15 @@
// ===================
// © AngelaMos | 2025
// ©AngelaMos | 2026
// routers.tsx
// ===================
import { createBrowserRouter, type RouteObject } from 'react-router-dom'
import { UserRole } from '@/api/types'
import { ROUTES } from '@/config'
import { ProtectedRoute } from './protected-route'
import { Shell } from './shell'
const routes: RouteObject[] = [
{
path: ROUTES.HOME,
lazy: () => import('@/pages/landing'),
lazy: () => import('@/pages/dashboard'),
},
{
path: ROUTES.LOGIN,
@ -26,17 +23,8 @@ const routes: RouteObject[] = [
element: <ProtectedRoute />,
children: [
{
element: <Shell />,
children: [
{
path: ROUTES.DASHBOARD,
lazy: () => import('@/pages/dashboard'),
},
{
path: ROUTES.SETTINGS,
lazy: () => import('@/pages/settings'),
},
],
path: ROUTES.SETTINGS,
lazy: () => import('@/pages/settings'),
},
],
},
@ -44,23 +32,14 @@ const routes: RouteObject[] = [
element: <ProtectedRoute allowedRoles={[UserRole.ADMIN]} />,
children: [
{
element: <Shell />,
children: [
{
path: ROUTES.ADMIN.USERS,
lazy: () => import('@/pages/admin'),
},
],
path: ROUTES.ADMIN.USERS,
lazy: () => import('@/pages/admin'),
},
],
},
{
path: ROUTES.UNAUTHORIZED,
lazy: () => import('@/pages/landing'),
},
{
path: '*',
lazy: () => import('@/pages/landing'),
lazy: () => import('@/pages/dashboard'),
},
]

View File

@ -1,331 +0,0 @@
// ===================
// © AngelaMos | 2025
// shell.module.scss
// ===================
@use '@/styles' as *;
$sidebar-width: 240px;
$sidebar-collapsed-width: 64px;
$header-height: 56px;
.shell {
display: flex;
min-height: 100vh;
min-height: 100dvh;
}
.sidebar {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: $sidebar-width;
background: $bg-surface-100;
border-right: 1px solid $border-default;
display: flex;
flex-direction: column;
z-index: $z-fixed;
@include transition-fast;
&.collapsed {
width: $sidebar-collapsed-width;
}
@include breakpoint-down('sm') {
transform: translateX(-100%);
&.open {
transform: translateX(0);
}
&.collapsed {
width: $sidebar-width;
}
}
}
.sidebarHeader {
height: $header-height;
padding: 0 $space-3;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 1px solid $border-default;
.sidebar.collapsed & {
justify-content: center;
padding: 0;
}
}
.logo {
font-size: $font-size-base;
font-weight: $font-weight-semibold;
color: $text-default;
@include transition-fast;
.sidebar.collapsed & {
display: none;
}
}
.nav {
flex: 1;
padding: $space-3;
display: flex;
flex-direction: column;
gap: $space-1;
}
.navItem {
display: flex;
align-items: center;
gap: $space-3;
padding: $space-2 $space-3;
border-radius: $radius-md;
font-size: $font-size-sm;
color: $text-light;
@include transition-fast;
@include hover {
background: $bg-surface-200;
color: $text-default;
}
&.active {
background: $bg-selection;
color: $text-default;
}
.sidebar.collapsed & {
justify-content: center;
}
}
.navIcon {
width: 17px;
height: 17px;
flex-shrink: 0;
}
.navLabel {
@include transition-fast;
.sidebar.collapsed & {
display: none;
}
}
.adminItem {
margin-top: auto;
border-top: 1px solid $border-default;
padding-top: $space-3;
}
.collapseBtn {
width: 45px;
height: 45px;
border-radius: $radius-md;
color: $text-light;
@include flex-center;
@include transition-fast;
svg {
width: 23.5px;
height: 23.5px;
}
@include hover {
background: $bg-surface-200;
color: $text-default;
}
@include breakpoint-down('sm') {
display: none;
}
}
.sidebarFooter {
padding: $space-3;
border-top: 1px solid $border-default;
}
.logoutBtn {
width: 100%;
display: flex;
align-items: center;
gap: $space-3;
padding: $space-3;
border-radius: $radius-md;
font-size: $font-size-sm;
color: $text-default;
@include transition-fast;
@include hover {
background: $bg-surface-200;
}
.sidebar.collapsed & {
justify-content: center;
.logoutText {
display: none;
}
}
}
.logoutIcon {
width: 18px;
height: 18px;
flex-shrink: 0;
}
.logoutText {
font-weight: $font-weight-medium;
@include transition-fast;
}
.overlay {
position: fixed;
inset: 0;
background: rgb(0, 0, 0, 50%);
z-index: calc($z-fixed - 1);
display: none;
border: none;
padding: 0;
cursor: pointer;
@include breakpoint-down('sm') {
display: block;
}
}
.main {
flex: 1;
display: flex;
flex-direction: column;
margin-left: $sidebar-width;
min-width: 0;
@include transition-fast;
&.collapsed {
margin-left: $sidebar-collapsed-width;
}
@include breakpoint-down('sm') {
margin-left: 0;
&.collapsed {
margin-left: 0;
}
}
}
.header {
position: sticky;
top: 0;
height: $header-height;
background: $bg-surface-100;
border-bottom: 1px solid $border-default;
z-index: $z-sticky;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 $space-4;
}
.headerLeft {
display: flex;
align-items: center;
gap: $space-3;
}
.menuBtn {
display: none;
width: 36px;
height: 36px;
border-radius: $radius-md;
color: $text-light;
align-items: center;
justify-content: center;
@include transition-fast;
svg {
width: 20px;
height: 20px;
}
@include hover {
background: $bg-surface-200;
color: $text-default;
}
@media (width <= 479px) {
display: flex;
}
}
.pageTitle {
font-size: $font-size-base;
font-weight: $font-weight-medium;
color: $text-default;
margin-left: 7px;
}
.headerRight {
display: flex;
align-items: center;
gap: $space-3;
}
.avatar {
width: 32px;
height: 32px;
border-radius: $radius-full;
background: $bg-surface-300;
color: $text-light;
font-size: $font-size-sm;
font-weight: $font-weight-medium;
@include flex-center;
cursor: pointer;
@include transition-fast;
@include hover {
filter: brightness(1.2);
}
}
.content {
flex: 1;
overflow-y: auto;
}
.loading {
@include flex-center;
height: 100%;
color: $text-muted;
}
.error {
@include flex-column-center;
height: 100%;
gap: $space-4;
padding: $space-6;
color: $error-default;
h2 {
font-size: $font-size-xl;
font-weight: $font-weight-semibold;
}
pre {
font-family: $font-mono;
font-size: $font-size-sm;
padding: $space-4;
background: $bg-surface-200;
border-radius: $radius-lg;
overflow-x: auto;
max-width: 100%;
}
}

View File

@ -1,160 +0,0 @@
/**
* ©AngelaMos | 2025
* shell.tsx
*/
import { Suspense } from 'react'
import { ErrorBoundary } from 'react-error-boundary'
import { GiCardAceClubs, GiCardJoker, GiExitDoor } from 'react-icons/gi'
import { LuChevronLeft, LuChevronRight, LuMenu, LuShield } from 'react-icons/lu'
import { Link, NavLink, Outlet, useLocation } from 'react-router-dom'
import { useLogout } from '@/api/hooks'
import { ROUTES } from '@/config'
import { useIsAdmin, useUIStore, useUser } from '@/core/lib'
import styles from './shell.module.scss'
const NAV_ITEMS = [
{ path: ROUTES.DASHBOARD, label: 'Dashboard', icon: GiCardJoker },
{ path: ROUTES.SETTINGS, label: 'Settings', icon: GiCardAceClubs },
]
const ADMIN_NAV_ITEM = {
path: ROUTES.ADMIN.USERS,
label: 'Admin',
icon: LuShield,
}
function ShellErrorFallback({ error }: { error: Error }): React.ReactElement {
return (
<div className={styles.error}>
<h2>Something went wrong</h2>
<pre>{error.message}</pre>
</div>
)
}
function ShellLoading(): React.ReactElement {
return <div className={styles.loading}>Loading...</div>
}
function getPageTitle(pathname: string, isAdmin: boolean): string {
if (isAdmin && pathname === ADMIN_NAV_ITEM.path) {
return ADMIN_NAV_ITEM.label
}
const item = NAV_ITEMS.find((i) => i.path === pathname)
return item?.label ?? 'Dashboard'
}
export function Shell(): React.ReactElement {
const location = useLocation()
const { sidebarOpen, sidebarCollapsed, toggleSidebar, toggleSidebarCollapsed } =
useUIStore()
const { mutate: logout } = useLogout()
const isAdmin = useIsAdmin()
const user = useUser()
const pageTitle = getPageTitle(location.pathname, isAdmin)
const avatarLetter =
user?.full_name?.[0]?.toUpperCase() ?? user?.email?.[0]?.toUpperCase() ?? 'U'
return (
<div className={styles.shell}>
<aside
className={`${styles.sidebar} ${sidebarOpen ? styles.open : ''} ${sidebarCollapsed ? styles.collapsed : ''}`}
>
<div className={styles.sidebarHeader}>
<span className={styles.logo}>NavBar Template</span>
<button
type="button"
className={styles.collapseBtn}
onClick={toggleSidebarCollapsed}
aria-label={sidebarCollapsed ? 'Expand sidebar' : 'Collapse sidebar'}
>
{sidebarCollapsed ? <LuChevronRight /> : <LuChevronLeft />}
</button>
</div>
<nav className={styles.nav}>
{NAV_ITEMS.map((item) => (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
`${styles.navItem} ${isActive ? styles.active : ''}`
}
onClick={() => sidebarOpen && toggleSidebar()}
>
<item.icon className={styles.navIcon} />
<span className={styles.navLabel}>{item.label}</span>
</NavLink>
))}
{isAdmin && (
<NavLink
to={ADMIN_NAV_ITEM.path}
className={({ isActive }) =>
`${styles.navItem} ${styles.adminItem} ${isActive ? styles.active : ''}`
}
onClick={() => sidebarOpen && toggleSidebar()}
>
<ADMIN_NAV_ITEM.icon className={styles.navIcon} />
<span className={styles.navLabel}>{ADMIN_NAV_ITEM.label}</span>
</NavLink>
)}
</nav>
<div className={styles.sidebarFooter}>
<button
type="button"
className={styles.logoutBtn}
onClick={() => logout()}
>
<GiExitDoor className={styles.logoutIcon} />
<span className={styles.logoutText}>Logout</span>
</button>
</div>
</aside>
{sidebarOpen && (
<button
type="button"
className={styles.overlay}
onClick={toggleSidebar}
onKeyDown={(e) => e.key === 'Escape' && toggleSidebar()}
aria-label="Close sidebar"
/>
)}
<div
className={`${styles.main} ${sidebarCollapsed ? styles.collapsed : ''}`}
>
<header className={styles.header}>
<div className={styles.headerLeft}>
<button
type="button"
className={styles.menuBtn}
onClick={toggleSidebar}
aria-label="Toggle menu"
>
<LuMenu />
</button>
<h1 className={styles.pageTitle}>{pageTitle}</h1>
</div>
<div className={styles.headerRight}>
<Link to={ROUTES.SETTINGS} className={styles.avatar}>
{avatarLetter}
</Link>
</div>
</header>
<main className={styles.content}>
<ErrorBoundary FallbackComponent={ShellErrorFallback}>
<Suspense fallback={<ShellLoading />}>
<Outlet />
</Suspense>
</ErrorBoundary>
</main>
</div>
</div>
)
}

View File

@ -1,67 +0,0 @@
// ===================
// © AngelaMos | 2025
// toast.module.scss
// ===================
@use '@/styles' as *;
:global {
[data-sonner-toaster] {
--normal-bg: #{$bg-surface-100};
--normal-border: #{$border-default};
--normal-text: #{$text-default};
--success-bg: #{$bg-surface-100};
--success-border: #{$border-default};
--success-text: #{$text-default};
--error-bg: #{$bg-surface-100};
--error-border: #{$error-default};
--error-text: #{$text-default};
--warning-bg: #{$bg-surface-100};
--warning-border: #{$border-default};
--warning-text: #{$text-default};
--info-bg: #{$bg-surface-100};
--info-border: #{$border-default};
--info-text: #{$text-default};
font-family: $font-sans;
}
[data-sonner-toast] {
border-radius: $radius-md;
padding: $space-3 $space-4;
font-size: $font-size-sm;
border: 1px solid $border-default;
background: $bg-surface-100;
color: $text-default;
[data-title] {
font-weight: $font-weight-medium;
}
[data-description] {
color: $text-light;
font-size: $font-size-xs;
}
[data-close-button] {
background: none;
border: none;
padding: 0;
cursor: pointer;
color: $text-muted;
@include transition-fast;
@include hover {
color: $text-default;
}
}
}
[data-sonner-toast][data-type='error'] {
border-color: $error-default;
}
}

View File

@ -1,8 +1,5 @@
// ===================
// © AngelaMos | 2025
// ©AngelaMos | 2026
// index.ts
// ===================
export * from './auth.form.store'
export * from './auth.store'
export * from './shell.ui.store'

View File

@ -1,63 +0,0 @@
/**
* ©AngelaMos | 2025
* ui.store.ts
*/
import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
type Theme = 'light' | 'dark' | 'system'
interface UIState {
theme: Theme
sidebarOpen: boolean
sidebarCollapsed: boolean
setTheme: (theme: Theme) => void
toggleSidebar: () => void
setSidebarOpen: (open: boolean) => void
toggleSidebarCollapsed: () => void
}
export const useUIStore = create<UIState>()(
devtools(
persist(
(set) => ({
theme: 'dark',
sidebarOpen: false,
sidebarCollapsed: false,
setTheme: (theme) => set({ theme }, false, 'ui/setTheme'),
toggleSidebar: () =>
set(
(state) => ({ sidebarOpen: !state.sidebarOpen }),
false,
'ui/toggleSidebar'
),
setSidebarOpen: (open) =>
set({ sidebarOpen: open }, false, 'ui/setSidebarOpen'),
toggleSidebarCollapsed: () =>
set(
(state) => ({ sidebarCollapsed: !state.sidebarCollapsed }),
false,
'ui/toggleSidebarCollapsed'
),
}),
{
name: 'ui-storage',
partialize: (state) => ({
theme: state.theme,
sidebarCollapsed: state.sidebarCollapsed,
}),
}
),
{ name: 'UIStore' }
)
)
export const useTheme = (): Theme => useUIStore((s) => s.theme)
export const useSidebarOpen = (): boolean => useUIStore((s) => s.sidebarOpen)
export const useSidebarCollapsed = (): boolean =>
useUIStore((s) => s.sidebarCollapsed)

View File

@ -1,152 +1,19 @@
// ===================
// © AngelaMos | 2025
// ©AngelaMos | 2026
// dashboard.module.scss
// ===================
@use '@/styles' as *;
.page {
min-height: calc(100vh - 56px);
padding: $space-6;
background-color: $bg-default;
}
.container {
max-width: 800px;
margin: 0 auto;
}
.header {
margin-bottom: $space-6;
}
.title {
font-size: $font-size-2xl;
font-weight: $font-weight-semibold;
color: $text-default;
margin-bottom: $space-2;
}
.subtitle {
font-size: $font-size-sm;
color: $text-lighter;
}
.userCard {
.scaffold {
height: 100dvh;
width: 100%;
background: var(--bg);
display: flex;
align-items: center;
gap: $space-4;
padding: $space-5;
background: $bg-surface-100;
border: 1px solid $border-default;
border-radius: $radius-lg;
margin-bottom: $space-8;
justify-content: center;
}
.avatar {
width: 56px;
height: 56px;
border-radius: $radius-full;
background: $bg-surface-300;
color: $text-default;
font-size: $font-size-xl;
font-weight: $font-weight-semibold;
@include flex-center;
flex-shrink: 0;
}
.userInfo {
@include flex-column;
gap: $space-1;
min-width: 0;
}
.userName {
font-size: $font-size-base;
font-weight: $font-weight-medium;
color: $text-default;
@include truncate;
}
.userEmail {
font-size: $font-size-sm;
color: $text-light;
@include truncate;
}
.userRole {
display: inline-flex;
align-self: flex-start;
padding: $space-0-5 $space-2;
background: $bg-surface-200;
border-radius: $radius-full;
font-size: $font-size-xs;
color: $text-lighter;
.label {
font-family: var(--font-sans);
font-size: var(--type-label);
letter-spacing: var(--letter-spacing-label);
text-transform: uppercase;
letter-spacing: $tracking-wide;
}
.section {
margin-bottom: $space-8;
}
.sectionTitle {
font-size: $font-size-sm;
font-weight: $font-weight-medium;
color: $text-light;
text-transform: uppercase;
letter-spacing: $tracking-wide;
margin-bottom: $space-4;
}
.grid {
display: grid;
gap: $space-4;
@include breakpoint-up('md') {
grid-template-columns: repeat(3, 1fr);
}
}
.card {
padding: $space-4;
background: $bg-surface-100;
border: 1px solid $border-default;
border-radius: $radius-lg;
}
.hookName {
display: inline-block;
font-size: $font-size-sm;
font-weight: $font-weight-medium;
color: $text-default;
background: $bg-surface-200;
padding: $space-1 $space-2;
border-radius: $radius-sm;
margin-bottom: $space-2;
}
.description {
font-size: $font-size-sm;
color: $text-light;
margin-bottom: $space-2;
line-height: $line-height-relaxed;
}
.file {
font-size: $font-size-xs;
color: $text-muted;
font-family: $font-mono;
}
.list {
@include flex-column;
gap: $space-2;
padding-left: $space-5;
list-style: disc;
li {
font-size: $font-size-sm;
color: $text-light;
}
color: var(--fg-3);
}

View File

@ -1,88 +1,12 @@
/**
* ©AngelaMos | 2025
* index.tsx
*/
// ©AngelaMos | 2026
// index.tsx
import { useUser } from '@/core/lib'
import styles from './dashboard.module.scss'
const AVAILABLE_STORES = [
{
name: 'useUser()',
file: 'core/lib/auth.store.ts',
description: 'Get current authenticated user',
},
{
name: 'useIsAuthenticated()',
file: 'core/lib/auth.store.ts',
description: 'Check if user is logged in',
},
{
name: 'useIsAdmin()',
file: 'core/lib/auth.store.ts',
description: 'Check if user has admin role',
},
]
const SUGGESTED_FEATURES = [
'User stats and metrics',
'Recent activity feed',
'Quick actions',
'Charts and analytics',
'Notifications overview',
'Task/project summary',
]
export function Component(): React.ReactElement {
const user = useUser()
return (
<div className={styles.page}>
<div className={styles.container}>
<div className={styles.header}>
<h1 className={styles.title}>
Welcome{user?.full_name ? `, ${user.full_name}` : ''}
</h1>
<p className={styles.subtitle}>
Template page build your dashboard here
</p>
</div>
<div className={styles.userCard}>
<div className={styles.avatar}>
{user?.full_name?.[0]?.toUpperCase() ??
user?.email?.[0]?.toUpperCase() ??
'U'}
</div>
<div className={styles.userInfo}>
<span className={styles.userName}>{user?.full_name ?? 'User'}</span>
<span className={styles.userEmail}>{user?.email}</span>
<span className={styles.userRole}>{user?.role}</span>
</div>
</div>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Available Stores</h2>
<div className={styles.grid}>
{AVAILABLE_STORES.map((store) => (
<div key={store.name} className={styles.card}>
<code className={styles.hookName}>{store.name}</code>
<p className={styles.description}>{store.description}</p>
<span className={styles.file}>{store.file}</span>
</div>
))}
</div>
</section>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Suggested Features</h2>
<ul className={styles.list}>
{SUGGESTED_FEATURES.map((feature) => (
<li key={feature}>{feature}</li>
))}
</ul>
</section>
</div>
<div className={styles.scaffold}>
<span className={styles.label}>MONITOR THE SITUATION · SCAFFOLD</span>
</div>
)
}

View File

@ -1,97 +0,0 @@
/**
* ©AngelaMos | 2026
* index.tsx
*/
import { FiGithub } from 'react-icons/fi'
import { Link } from 'react-router-dom'
import { ROUTES } from '@/config'
import styles from './landing.module.scss'
export function Component(): React.ReactElement {
return (
<div className={styles.page}>
<header className={styles.header}>
<h1 className={styles.title}>Full Stack Template</h1>
<p className={styles.subtitle}>by Carter Perez</p>
<a
href="https://github.com/CarterPerez-dev/fullstack-template"
target="_blank"
rel="noopener noreferrer"
className={styles.github}
aria-label="View source on GitHub"
>
<FiGithub />
</a>
</header>
<div className={styles.content}>
<p className={styles.description}>
Boilerplate for medium-large scale applications. Built with modern
patterns, strict typing, and security best practices.
</p>
<div className={styles.sections}>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Frontend</h2>
<ul className={styles.features}>
<li>React 19 + TypeScript with strict mode</li>
<li>TanStack Query for server state caching</li>
<li>Zustand stores with persistence</li>
<li>Axios interceptors with auto token refresh</li>
<li>Zod runtime validation on API responses</li>
<li>SCSS modules with design tokens</li>
</ul>
</section>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Backend</h2>
<ul className={styles.features}>
<li>DDD + DI Architecture</li>
<li>FastAPI with async/await throughout</li>
<li>SQLAlchemy 2.0+ async with connection pooling</li>
<li>JWT auth with token rotation and replay detection</li>
<li>Argon2id hashing with timing safe verification</li>
<li>Pydantic v2 strict validation</li>
</ul>
</section>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>Infrastructure</h2>
<ul className={styles.features}>
<li>Docker Compose with dev/prod configs</li>
<li>Nginx reverse proxy with rate limiting</li>
<li>PostgreSQL 18 + Redis 7</li>
<li>Health checks and graceful shutdown</li>
</ul>
</section>
<section className={styles.section}>
<h2 className={styles.sectionTitle}>DevOps</h2>
<ul className={styles.features}>
<li>GitHub Actions CI (Ruff, Pylint, Mypy, Biome)</li>
<li>Strict type checking on both ends</li>
<li>Alembic async migrations</li>
</ul>
</section>
</div>
<div className={styles.actions}>
<Link to={ROUTES.REGISTER} className={styles.button}>
Try Auth Flow
</Link>
<a
href="/api/docs"
target="_blank"
rel="noopener noreferrer"
className={styles.buttonOutline}
>
API Docs
</a>
</div>
</div>
</div>
)
}
Component.displayName = 'Landing'

View File

@ -1,161 +0,0 @@
// ===================
// © AngelaMos | 2025
// landing.module.scss
// ===================
@use '@/styles' as *;
.page {
min-height: 100vh;
min-height: 100dvh;
@include flex-column-center;
background-color: $bg-default;
background-image: radial-gradient(
circle,
$bg-landing 1px,
transparent 1px
);
background-size: 20px 20px;
padding: $space-8;
}
.header {
text-align: center;
margin-bottom: $space-5;
}
.title {
font-size: $font-size-4xl;
font-weight: $font-weight-semibold;
color: $text-default;
letter-spacing: $tracking-tight;
margin-bottom: $space-2;
}
.subtitle {
font-size: $font-size-lg;
color: $text-light;
margin-bottom: $space-3;
}
.github {
display: inline-flex;
color: $text-muted;
font-size: $font-size-2xl;
@include transition-fast;
@include hover {
&:hover {
color: $text-default;
}
}
}
.content {
max-width: 800px;
text-align: center;
}
.description {
font-size: $font-size-base;
color: $text-light;
line-height: $line-height-relaxed;
margin-bottom: $space-8;
}
.sections {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: $space-6;
margin-bottom: $space-10;
text-align: left;
@include breakpoint-down(md) {
grid-template-columns: 1fr;
}
}
.section {
padding: $space-5;
background-color: $bg-surface-75;
border: 1px solid $border-muted;
border-radius: $radius-lg;
}
.sectionTitle {
font-size: $font-size-sm;
font-weight: $font-weight-semibold;
color: $text-default;
margin-bottom: $space-3;
letter-spacing: $tracking-wide;
text-transform: uppercase;
}
.features {
list-style: none;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
gap: $space-2;
li {
font-size: $font-size-sm;
color: $text-muted;
&::before {
content: '';
margin-right: $space-2;
color: $text-lighter;
}
}
}
.actions {
display: flex;
gap: $space-3;
justify-content: center;
flex-wrap: wrap;
}
.button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: $space-3 $space-5;
font-size: $font-size-sm;
font-weight: $font-weight-medium;
color: $bg-default;
background-color: $text-default;
border-radius: $radius-md;
text-decoration: none;
@include transition-fast;
@include hover {
&:hover {
filter: brightness(0.9);
}
}
}
.buttonOutline {
display: inline-flex;
align-items: center;
justify-content: center;
padding: $space-3 $space-5;
font-size: $font-size-sm;
font-weight: $font-weight-medium;
color: $text-default;
background-color: transparent;
border: 1px solid $border-default;
border-radius: $radius-md;
text-decoration: none;
@include transition-fast;
@include hover {
&:hover {
border-color: $border-strong;
background-color: $bg-surface-75;
}
}
}