Add first run UI and fix errors

This commit is contained in:
James Brunton 2025-11-14 17:27:49 +00:00
parent fb704cc901
commit 17e038e206
6 changed files with 207 additions and 4 deletions

View File

@ -5281,5 +5281,139 @@
"offline": "Backend Offline", "offline": "Backend Offline",
"starting": "Backend starting up...", "starting": "Backend starting up...",
"wait": "Please wait for the backend to finish launching and try again." "wait": "Please wait for the backend to finish launching and try again."
},
"setup": {
"welcome": "Welcome to Stirling PDF",
"description": "Get started by choosing how you want to use Stirling PDF",
"step1": {
"label": "Choose Mode",
"description": "Offline or Server"
},
"step2": {
"label": "Select Server",
"description": "SaaS or Self-hosted"
},
"step3": {
"label": "Login",
"description": "Enter credentials"
},
"mode": {
"offline": {
"title": "Use Offline",
"description": "Run locally without an internet connection"
},
"server": {
"title": "Connect to Server",
"description": "Connect to a remote Stirling PDF server"
}
},
"server": {
"type": {
"saas": "Stirling PDF SaaS (stirling.com/app)",
"selfhosted": "Self-hosted server"
},
"url": {
"label": "Server URL",
"description": "Enter the full URL of your self-hosted Stirling PDF server"
},
"error": {
"emptyUrl": "Please enter a server URL",
"unreachable": "Could not connect to server",
"testFailed": "Connection test failed"
},
"testing": "Testing connection..."
},
"login": {
"connectingTo": "Connecting to:",
"username": {
"label": "Username",
"placeholder": "Enter your username"
},
"password": {
"label": "Password",
"placeholder": "Enter your password"
},
"error": {
"emptyUsername": "Please enter your username",
"emptyPassword": "Please enter your password"
},
"submit": "Login"
}
},
"settings": {
"connection": {
"title": "Connection Mode",
"mode": {
"offline": "Offline",
"server": "Server"
},
"server": "Server",
"user": "Logged in as",
"switchToServer": "Connect to Server",
"switchToOffline": "Switch to Offline",
"logout": "Logout",
"selectServer": "Select Server",
"login": "Login"
},
"general": {
"title": "General",
"description": "Configure general application preferences.",
"user": "User",
"logout": "Log out",
"enableFeatures": {
"dismiss": "Dismiss",
"title": "For System Administrators",
"intro": "Enable user authentication, team management, and workspace features for your organisation.",
"action": "Configure",
"and": "and",
"benefit": "Enables user roles, team collaboration, admin controls, and enterprise features.",
"learnMore": "Learn more in documentation"
},
"defaultToolPickerMode": "Default tool picker mode",
"defaultToolPickerModeDescription": "Choose whether the tool picker opens in fullscreen or sidebar by default",
"mode": {
"sidebar": "Sidebar",
"fullscreen": "Fullscreen"
},
"autoUnzipTooltip": "Automatically extract ZIP files returned from API operations. Disable to keep ZIP files intact. This does not affect automation workflows.",
"autoUnzip": "Auto-unzip API responses",
"autoUnzipDescription": "Automatically extract files from ZIP responses",
"autoUnzipFileLimitTooltip": "Only unzip if the ZIP contains this many files or fewer. Set higher to extract larger ZIPs.",
"autoUnzipFileLimit": "Auto-unzip file limit",
"autoUnzipFileLimitDescription": "Maximum number of files to extract from ZIP"
},
"hotkeys": {
"errorConflict": "Shortcut already used by {{tool}}.",
"searchPlaceholder": "Search tools...",
"none": "Not assigned",
"customBadge": "Custom",
"defaultLabel": "Default: {{shortcut}}",
"capturing": "Press keys… (Esc to cancel)",
"change": "Change shortcut",
"reset": "Reset",
"shortcut": "Shortcut",
"noShortcut": "No shortcut set"
}
},
"auth": {
"sessionExpired": "Session Expired",
"pleaseLoginAgain": "Please login again.",
"accessDenied": "Access Denied",
"insufficientPermissions": "You do not have permission to perform this action."
},
"common": {
"loading": "Loading...",
"back": "Back",
"continue": "Continue",
"preview": "Preview",
"previous": "Previous",
"next": "Next",
"copied": "Copied!",
"copy": "Copy",
"expand": "Expand",
"collapse": "Collapse",
"retry": "Retry",
"refresh": "Refresh",
"cancel": "Cancel"
} }
} }

View File

@ -1,14 +1,44 @@
import { ReactNode } from "react"; import { ReactNode } from "react";
import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders"; import { AppProviders as ProprietaryAppProviders } from "@proprietary/components/AppProviders";
import { DesktopConfigSync } from '@app/components/DesktopConfigSync'; import { DesktopConfigSync } from '@app/components/DesktopConfigSync';
import { SetupWizard } from '@app/components/SetupWizard';
import { useAppInitialization } from '@app/hooks/useAppInitialization';
import { DESKTOP_DEFAULT_APP_CONFIG } from '@app/config/defaultAppConfig'; import { DESKTOP_DEFAULT_APP_CONFIG } from '@app/config/defaultAppConfig';
/** /**
* Desktop application providers * Desktop application providers
* Wraps proprietary providers and adds desktop-specific configuration * Wraps proprietary providers and adds desktop-specific configuration
* - Enables retry logic for app config (needed for Tauri mode when backend is starting) * - Enables retry logic for app config (needed for Tauri mode when backend is starting)
* - Shows setup wizard on first launch
*/ */
export function AppProviders({ children }: { children: ReactNode }) { export function AppProviders({ children }: { children: ReactNode }) {
const { isFirstLaunch, setupComplete } = useAppInitialization();
// Show setup wizard on first launch
if (isFirstLaunch && !setupComplete) {
return (
<ProprietaryAppProviders
appConfigRetryOptions={{
maxRetries: 5,
initialDelay: 1000,
}}
appConfigProviderProps={{
initialConfig: DESKTOP_DEFAULT_APP_CONFIG,
bootstrapMode: 'non-blocking',
autoFetch: false,
}}
>
<SetupWizard
onComplete={() => {
// Reload the page to reinitialize with new connection config
window.location.reload();
}}
/>
</ProprietaryAppProviders>
);
}
// Normal app flow
return ( return (
<ProprietaryAppProviders <ProprietaryAppProviders
appConfigRetryOptions={{ appConfigRetryOptions={{

View File

@ -1,7 +1,8 @@
import React from 'react'; import React from 'react';
import { Stack, Button, Text } from '@mantine/core'; import { Stack, Button, Text } from '@mantine/core';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { IconCloud, IconDeviceDesktop } from '@tabler/icons-react'; import CloudIcon from '@mui/icons-material/Cloud';
import ComputerIcon from '@mui/icons-material/Computer';
interface ModeSelectionProps { interface ModeSelectionProps {
onSelect: (mode: 'offline' | 'server') => void; onSelect: (mode: 'offline' | 'server') => void;
@ -18,7 +19,7 @@ export const ModeSelection: React.FC<ModeSelectionProps> = ({ onSelect, loading
variant="light" variant="light"
onClick={() => onSelect('offline')} onClick={() => onSelect('offline')}
disabled={loading} disabled={loading}
leftSection={<IconDeviceDesktop size={24} />} leftSection={<ComputerIcon />}
> >
<div style={{ textAlign: 'left', flex: 1 }}> <div style={{ textAlign: 'left', flex: 1 }}>
<Text fw={600}>{t('setup.mode.offline.title', 'Use Offline')}</Text> <Text fw={600}>{t('setup.mode.offline.title', 'Use Offline')}</Text>
@ -33,7 +34,7 @@ export const ModeSelection: React.FC<ModeSelectionProps> = ({ onSelect, loading
variant="light" variant="light"
onClick={() => onSelect('server')} onClick={() => onSelect('server')}
disabled={loading} disabled={loading}
leftSection={<IconCloud size={24} />} leftSection={<CloudIcon />}
> >
<div style={{ textAlign: 'left', flex: 1 }}> <div style={{ textAlign: 'left', flex: 1 }}>
<Text fw={600}>{t('setup.mode.server.title', 'Connect to Server')}</Text> <Text fw={600}>{t('setup.mode.server.title', 'Connect to Server')}</Text>

View File

@ -117,7 +117,7 @@ export const SetupWizard: React.FC<SetupWizardProps> = ({ onComplete }) => {
{t('setup.description', 'Get started by choosing how you want to use Stirling PDF')} {t('setup.description', 'Get started by choosing how you want to use Stirling PDF')}
</Text> </Text>
<Stepper active={activeStep} breakpoint="sm"> <Stepper active={activeStep}>
<Stepper.Step <Stepper.Step
label={t('setup.step1.label', 'Choose Mode')} label={t('setup.step1.label', 'Choose Mode')}
description={t('setup.step1.description', 'Offline or Server')} description={t('setup.step1.description', 'Offline or Server')}

View File

@ -0,0 +1,30 @@
import { createConfigNavSections as createProprietaryConfigNavSections } from '@proprietary/components/shared/config/configNavSections';
import { ConfigNavSection } from '@core/components/shared/config/configNavSections';
import { ConnectionSettings } from '@app/components/ConnectionSettings';
/**
* Desktop extension of createConfigNavSections that adds connection settings
*/
export const createConfigNavSections = (
isAdmin: boolean = false,
runningEE: boolean = false,
loginEnabled: boolean = false
): ConfigNavSection[] => {
// Get the proprietary sections (includes core Preferences + admin sections)
const sections = createProprietaryConfigNavSections(isAdmin, runningEE, loginEnabled);
// Add Connection section at the beginning (after Preferences)
sections.splice(1, 0, {
title: 'Connection',
items: [
{
key: 'connectionMode',
label: 'Connection Mode',
icon: 'cloud-rounded',
component: <ConnectionSettings />,
},
],
});
return sections;
};

View File

@ -0,0 +1,8 @@
import { VALID_NAV_KEYS as CORE_NAV_KEYS } from '@core/components/shared/config/types';
export const VALID_NAV_KEYS = [
...CORE_NAV_KEYS,
'connectionMode',
]
export type NavKey = typeof VALID_NAV_KEYS[number];