diff --git a/desktop-client/src-tauri/src/lib.rs b/desktop-client/src-tauri/src/lib.rs index 469b629..2751410 100644 --- a/desktop-client/src-tauri/src/lib.rs +++ b/desktop-client/src-tauri/src/lib.rs @@ -213,6 +213,24 @@ fn load_state(app: AppHandle, state: State<'_, AppState>) -> Result) -> Result<(), String> { + let app_dir = ensure_app_dir(&app)?; + let session_path = app_dir.join("session.json"); + let profile_path = app_dir.join(format!("{}.conf", PROFILE_NAME)); + + if session_path.exists() { + fs::remove_file(&session_path).map_err(|err| format!("Unable to remove session state: {}", err))?; + } + if profile_path.exists() { + fs::remove_file(&profile_path).map_err(|err| format!("Unable to remove profile: {}", err))?; + } + + let mut session = state.session.lock().map_err(|_| "Unable to clear client state".to_string())?; + *session = None; + Ok(()) +} + #[tauri::command] async fn sync_profile(app: AppHandle, state: State<'_, AppState>) -> Result { let existing = { @@ -417,7 +435,7 @@ pub fn run() { } _ => {} }) - .invoke_handler(tauri::generate_handler![load_state, enroll_device, sync_profile, connect_tunnel, disconnect_tunnel]) + .invoke_handler(tauri::generate_handler![load_state, clear_session, enroll_device, sync_profile, connect_tunnel, disconnect_tunnel]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/desktop-client/src/App.tsx b/desktop-client/src/App.tsx index cdc58b2..61b7f3f 100644 --- a/desktop-client/src/App.tsx +++ b/desktop-client/src/App.tsx @@ -103,6 +103,17 @@ export function App() { } } + async function resetEnrollment() { + try { + await invoke("clear_session"); + setConnected(false); + setState(null); + setError(null); + } catch (err) { + setError(formatInvokeError(err, "Unable to clear local profile")); + } + } + return (
@@ -111,14 +122,19 @@ export function App() { NexaVPN

NexaVPN

-

Desktop Access Client

-

Provisioned WireGuard access with profile sync and one-click reconnect.

+

VPN Client

+

{state ? profileLabel : "Sign in to provision this device"}

{state ? ( + ) : null} + {state ? ( + ) : null}
-
-
-

Managed Tunnel

-

Private access with fewer moving parts.

-

- NexaVPN signs you in, enrolls the device, stores the WireGuard profile locally, and lets you resync the - assigned access profile after policy changes. -

-
-
-
- Current profile - {profileLabel} -
-
- Last sync - {state?.lastSyncTime ?? "Not synced yet"} -
-
-
-
{!state ? (
-

Onboarding

-

Sign in and provision this device

+

Sign in

+

Provision device

@@ -182,11 +177,11 @@ export function App() {
-

Connection

+

Status

{connected ? "Connected" : "Disconnected"}

- {connected ? "Tunnel active" : "Ready to connect"} + {connected ? "Tunnel active" : "Ready"}
@@ -194,8 +189,8 @@ export function App() {
-

Session

-

Provisioned device state

+

Connection

+

Current profile

@@ -208,8 +203,8 @@ export function App() { {state.gatewayEndpoint}
- Profile revision - {state.profileRevision} + Profile + {profileLabel}
Last sync @@ -221,8 +216,8 @@ export function App() {
-

Profile

-

Assigned access profile

+

Local

+

Stored config

@@ -231,8 +226,8 @@ export function App() { {state.profilePath}
- Tunnel strategy - {state.tunnelStrategy} + Revision + {state.profileRevision}
@@ -244,19 +239,21 @@ export function App() {
diff --git a/desktop-client/src/styles.css b/desktop-client/src/styles.css index c8ebc81..81e5cdd 100644 --- a/desktop-client/src/styles.css +++ b/desktop-client/src/styles.css @@ -35,7 +35,7 @@ input { width: min(1120px, 100%); margin: 0 auto; display: grid; - gap: 22px; + gap: 18px; } .top-strip { @@ -72,7 +72,6 @@ input { } .brand-copy h1, -.hero-copy h2, .status-panel h3 { margin: 0; } @@ -120,7 +119,6 @@ input { cursor: default; } -.hero-surface, .surface, .status-panel, .login-panel { @@ -130,50 +128,6 @@ input { backdrop-filter: blur(18px); } -.hero-surface { - border-radius: 30px; - padding: 28px; - display: grid; - grid-template-columns: 1.2fr 0.8fr; - gap: 20px; - align-items: center; -} - -.hero-copy { - display: grid; - gap: 14px; -} - -.hero-copy h2 { - font-size: clamp(2rem, 4vw, 3.6rem); - line-height: 1.04; - max-width: 10ch; -} - -.hero-copy p { - max-width: 56ch; - font-size: 1.03rem; - line-height: 1.6; -} - -.hero-meta { - display: grid; - gap: 14px; -} - -.meta-tile { - padding: 18px; - border-radius: 22px; - background: linear-gradient(180deg, rgba(117, 227, 186, 0.08), rgba(117, 227, 186, 0.02)); - border: 1px solid rgba(117, 227, 186, 0.16); -} - -.meta-tile strong { - display: block; - margin-top: 8px; - font-size: 1.1rem; -} - .body-grid { display: grid; grid-template-columns: 1.3fr 0.7fr; @@ -188,6 +142,12 @@ input { gap: 20px; } +.status-panel > p { + margin: 0; + color: #9eb1d1; + line-height: 1.5; +} + .status-top { display: flex; align-items: center; @@ -310,7 +270,6 @@ input { } @media (max-width: 960px) { - .hero-surface, .body-grid { grid-template-columns: 1fr; }