refactor: extract UI components and redesign desktop client interface with improved visual hierarchy
Extract App.tsx logic into reusable components: AppHeader, ResourcePanel, StatusCard, StatTile, and ActionButton. Replace inline markup with component composition and props-based data flow. Redesign visual system with enhanced gradients, refined color palette, and improved spacing. Update app-shell grid layout with 18px gaps and 1140px max width. Add radial gradient overlays and linear background
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
import { FormEvent, useEffect, useMemo, useState } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { AppHeader } from "./components/AppHeader";
|
||||
import { ResourcePanel } from "./components/ResourcePanel";
|
||||
import { StatusCard } from "./components/StatusCard";
|
||||
|
||||
type EnrollmentState = {
|
||||
assignedIp: string;
|
||||
@@ -191,40 +194,23 @@ export function App() {
|
||||
|
||||
return (
|
||||
<div className="client-shell">
|
||||
<div className="app-frame">
|
||||
<div className="top-strip">
|
||||
<div className="brand-lockup">
|
||||
<img src="/icon.png" alt="NexaVPN" />
|
||||
<div className="brand-copy">
|
||||
<p className="eyebrow">NexaVPN</p>
|
||||
<h1>VPN Client</h1>
|
||||
<p>{state ? "Private access client" : "Sign in to add this device"}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="top-actions">
|
||||
{state ? (
|
||||
<button className="shell-button-secondary" disabled={syncing} onClick={onSyncProfile} type="button">
|
||||
{syncing ? "Syncing..." : "Sync"}
|
||||
</button>
|
||||
) : null}
|
||||
{state ? (
|
||||
<button className="shell-button-secondary" onClick={resetEnrollment} type="button">
|
||||
Logout
|
||||
</button>
|
||||
) : null}
|
||||
<button className="shell-button" disabled={!state} onClick={toggleConnection} type="button">
|
||||
{!state ? "Provision first" : connected ? "Disconnect" : "Connect"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="app-shell">
|
||||
<AppHeader
|
||||
connected={connected}
|
||||
enrolled={Boolean(state)}
|
||||
onLogout={resetEnrollment}
|
||||
onSync={onSyncProfile}
|
||||
onToggleConnection={toggleConnection}
|
||||
syncing={syncing}
|
||||
/>
|
||||
|
||||
<div className="body-grid">
|
||||
{!state ? (
|
||||
<section className="login-panel">
|
||||
<div className="surface-header">
|
||||
<section className="login-card">
|
||||
<div className="panel-head">
|
||||
<div>
|
||||
<p className="eyebrow">Sign in</p>
|
||||
<h3>Provision device</h3>
|
||||
<p className="section-eyebrow">Sign in</p>
|
||||
<h2>Provision device</h2>
|
||||
</div>
|
||||
</div>
|
||||
<form onSubmit={onSubmit}>
|
||||
@@ -241,75 +227,31 @@ export function App() {
|
||||
<input type="password" value={password} onChange={(event) => setPassword(event.target.value)} />
|
||||
</label>
|
||||
{error ? <div className="error">{error}</div> : null}
|
||||
<div className="login-card-actions">
|
||||
<button className="shell-button" disabled={loading} type="submit">
|
||||
<div className="login-actions">
|
||||
<button className="action-button action-button-primary" disabled={loading} type="submit">
|
||||
{loading ? "Provisioning..." : "Sign in"}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</section>
|
||||
) : (
|
||||
<section className="status-panel">
|
||||
<div className="status-top">
|
||||
<div>
|
||||
<p className="eyebrow">Status</p>
|
||||
<h3>{connected ? "Connected" : "Disconnected"}</h3>
|
||||
<div className="status-state">
|
||||
<span className={`status-dot ${connected ? "online" : ""}`} />
|
||||
{connected ? "Tunnel active" : "Ready"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="surface">
|
||||
<div className="surface-header">
|
||||
<div>
|
||||
<p className="eyebrow">Connection</p>
|
||||
<h4>Overview</h4>
|
||||
</div>
|
||||
</div>
|
||||
<div className="status-grid">
|
||||
<div className="detail-card">
|
||||
<span>Assigned VPN IP</span>
|
||||
<strong>{state.assignedIp}</strong>
|
||||
</div>
|
||||
<div className="detail-card">
|
||||
<span>Gateway endpoint</span>
|
||||
<strong>{state.gatewayEndpoint}</strong>
|
||||
</div>
|
||||
<div className="detail-card">
|
||||
<span>Access</span>
|
||||
<strong>{profileLabel}</strong>
|
||||
</div>
|
||||
<div className="detail-card">
|
||||
<span>Last sync</span>
|
||||
<strong>{state.lastSyncTime}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="main-column">
|
||||
<StatusCard
|
||||
accessLabel={profileLabel}
|
||||
assignedIp={state.assignedIp}
|
||||
connected={connected}
|
||||
gatewayEndpoint={state.gatewayEndpoint}
|
||||
lastSyncTime={state.lastSyncTime}
|
||||
/>
|
||||
{error ? <div className="error">{error}</div> : null}
|
||||
</section>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<aside className="status-panel">
|
||||
<div className="surface-header">
|
||||
<div>
|
||||
<p className="eyebrow">Access</p>
|
||||
<h4>Resources</h4>
|
||||
</div>
|
||||
</div>
|
||||
<ul className="resource-stack">
|
||||
{(state?.resources ?? ["No resources assigned yet"]).map((resource) => (
|
||||
<li key={resource}>{resource}</li>
|
||||
))}
|
||||
</ul>
|
||||
{state ? (
|
||||
<button className="shell-button-secondary" onClick={resetEnrollment} type="button">
|
||||
Neu einbinden
|
||||
</button>
|
||||
) : null}
|
||||
</aside>
|
||||
<ResourcePanel
|
||||
onReset={resetEnrollment}
|
||||
profileLabel={profileLabel}
|
||||
resources={state?.resources ?? []}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user