mirror of
https://github.com/arsvendg/Stirling-PDF.git
synced 2026-07-16 19:33:11 +02:00
Remove "Download for Desktop" and "Security Check" slides from desktop app onboarding (#5012)
# Description of Changes - Title^ + I changed the text for set as default to make it more visible --- ## Checklist ### General - [ ] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [ ] I have performed a self-review of my own code - [ ] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/DeveloperGuide.md#6-testing) for more details. --------- Co-authored-by: Anthony Stirling <[email protected]> Co-authored-by: Connor Yoh <[email protected]>
This commit is contained in:
co-authored by
Anthony Stirling
Connor Yoh
parent
f8386843d4
commit
d74856f675
@@ -71,8 +71,10 @@ export default function ServerLicenseModal({
|
|||||||
slideKey={slide.key}
|
slideKey={slide.key}
|
||||||
/>
|
/>
|
||||||
<div className={styles.heroLogo}>
|
<div className={styles.heroLogo}>
|
||||||
<div className={styles.heroLogoCircle}>
|
<div className={styles.heroIconsContainer}>
|
||||||
<img src={`${BASE_PATH}/branding/StirlingPDFLogoNoTextLightHC.svg`} alt="Stirling logo" />
|
<div className={styles.iconWrapper}>
|
||||||
|
<img src={`${BASE_PATH}/modern-logo/logo512.png`} alt="Stirling icon" className={styles.downloadIcon} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ export const SLIDE_DEFINITIONS: Record<SlideId, SlideDefinition> = {
|
|||||||
'server-license': {
|
'server-license': {
|
||||||
id: 'server-license',
|
id: 'server-license',
|
||||||
createSlide: ({ licenseNotice }) => ServerLicenseSlide({ licenseNotice }),
|
createSlide: ({ licenseNotice }) => ServerLicenseSlide({ licenseNotice }),
|
||||||
hero: { type: 'logo' },
|
hero: { type: 'dual-icon' },
|
||||||
buttons: [
|
buttons: [
|
||||||
{
|
{
|
||||||
key: 'license-close',
|
key: 'license-close',
|
||||||
|
|||||||
@@ -130,6 +130,11 @@ export const InfoBanner: React.FC<InfoBannerProps> = ({
|
|||||||
onClick={onButtonClick}
|
onClick={onButtonClick}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
leftSection={<LocalIcon icon={buttonIcon} width="0.9rem" height="0.9rem" />}
|
leftSection={<LocalIcon icon={buttonIcon} width="0.9rem" height="0.9rem" />}
|
||||||
|
styles={{
|
||||||
|
label: {
|
||||||
|
color: textColor ?? toneStyle.text,
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{buttonText}
|
{buttonText}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
+390
@@ -0,0 +1,390 @@
|
|||||||
|
/**
|
||||||
|
* Desktop override of useInitialOnboardingState.
|
||||||
|
*
|
||||||
|
* Key difference: Handles the simplified onboarding flow where desktop-install
|
||||||
|
* and security-check slides are removed. When welcome is the last slide,
|
||||||
|
* clicking Next will launch the tools tour instead of doing nothing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { useCallback, useEffect, useMemo, useState, useRef } from 'react';
|
||||||
|
import { usePreferences } from '@app/contexts/PreferencesContext';
|
||||||
|
import { useOnboarding } from '@app/contexts/OnboardingContext';
|
||||||
|
import { useOs } from '@app/hooks/useOs';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
SLIDE_DEFINITIONS,
|
||||||
|
type ButtonAction,
|
||||||
|
type FlowState,
|
||||||
|
type SlideId,
|
||||||
|
} from '@app/components/onboarding/onboardingFlowConfig';
|
||||||
|
import type { LicenseNotice } from '@app/types/types';
|
||||||
|
import { resolveFlow } from '@app/components/onboarding/InitialOnboardingModal/flowResolver';
|
||||||
|
import { useServerExperience } from '@app/hooks/useServerExperience';
|
||||||
|
import { DEFAULT_STATE, type InitialOnboardingModalProps, type OnboardingState } from '@app/components/onboarding/InitialOnboardingModal/types';
|
||||||
|
import { DOWNLOAD_URLS } from '@app/constants/downloads';
|
||||||
|
|
||||||
|
interface UseInitialOnboardingStateResult {
|
||||||
|
state: OnboardingState;
|
||||||
|
totalSteps: number;
|
||||||
|
slideDefinition: (typeof SLIDE_DEFINITIONS)[SlideId];
|
||||||
|
currentSlide: ReturnType<(typeof SLIDE_DEFINITIONS)[SlideId]['createSlide']>;
|
||||||
|
licenseNotice: LicenseNotice;
|
||||||
|
flowState: FlowState;
|
||||||
|
closeAndMarkSeen: () => void;
|
||||||
|
handleButtonAction: (action: ButtonAction) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useInitialOnboardingState({
|
||||||
|
opened,
|
||||||
|
onClose,
|
||||||
|
onRequestServerLicense,
|
||||||
|
onLicenseNoticeUpdate,
|
||||||
|
}: InitialOnboardingModalProps): UseInitialOnboardingStateResult | null {
|
||||||
|
const { preferences, updatePreference } = usePreferences();
|
||||||
|
const { startTour } = useOnboarding();
|
||||||
|
const {
|
||||||
|
loginEnabled: loginEnabledFromServer,
|
||||||
|
configIsAdmin,
|
||||||
|
totalUsers: serverTotalUsers,
|
||||||
|
userCountResolved: serverUserCountResolved,
|
||||||
|
freeTierLimit,
|
||||||
|
hasPaidLicense,
|
||||||
|
scenarioKey,
|
||||||
|
setSelfReportedAdmin,
|
||||||
|
isNewServer,
|
||||||
|
} = useServerExperience();
|
||||||
|
const osType = useOs();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const selectedDownloadUrlRef = useRef<string>('');
|
||||||
|
|
||||||
|
const [state, setState] = useState<OnboardingState>(DEFAULT_STATE);
|
||||||
|
|
||||||
|
const resetState = useCallback(() => {
|
||||||
|
setState(DEFAULT_STATE);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!opened) {
|
||||||
|
resetState();
|
||||||
|
}
|
||||||
|
}, [opened, resetState]);
|
||||||
|
|
||||||
|
const handleRoleSelect = useCallback(
|
||||||
|
(role: 'admin' | 'user' | null) => {
|
||||||
|
const isAdminSelection = role === 'admin';
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
selectedRole: role,
|
||||||
|
selfReportedAdmin: isAdminSelection,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
if (isAdminSelection) {
|
||||||
|
window.localStorage.setItem('stirling-self-reported-admin', 'true');
|
||||||
|
} else {
|
||||||
|
window.localStorage.removeItem('stirling-self-reported-admin');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelfReportedAdmin(isAdminSelection);
|
||||||
|
},
|
||||||
|
[setSelfReportedAdmin],
|
||||||
|
);
|
||||||
|
|
||||||
|
const closeAndMarkSeen = useCallback(() => {
|
||||||
|
if (!preferences.hasSeenIntroOnboarding) {
|
||||||
|
updatePreference('hasSeenIntroOnboarding', true);
|
||||||
|
}
|
||||||
|
onClose();
|
||||||
|
}, [onClose, preferences.hasSeenIntroOnboarding, updatePreference]);
|
||||||
|
|
||||||
|
const isAdmin = configIsAdmin;
|
||||||
|
const enableLogin = loginEnabledFromServer;
|
||||||
|
|
||||||
|
const effectiveEnableLogin = enableLogin;
|
||||||
|
const effectiveIsAdmin = isAdmin;
|
||||||
|
const shouldAssumeAdminForNewServer = Boolean(isNewServer) && !effectiveEnableLogin;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldAssumeAdminForNewServer && !state.selfReportedAdmin) {
|
||||||
|
handleRoleSelect('admin');
|
||||||
|
}
|
||||||
|
}, [handleRoleSelect, shouldAssumeAdminForNewServer, state.selfReportedAdmin]);
|
||||||
|
|
||||||
|
const shouldUseServerCount =
|
||||||
|
(effectiveEnableLogin && effectiveIsAdmin) || !effectiveEnableLogin;
|
||||||
|
const licenseUserCountFromServer =
|
||||||
|
shouldUseServerCount && serverUserCountResolved ? serverTotalUsers : null;
|
||||||
|
|
||||||
|
const effectiveLicenseUserCount = licenseUserCountFromServer ?? null;
|
||||||
|
|
||||||
|
const os = useMemo(() => {
|
||||||
|
switch (osType) {
|
||||||
|
case 'windows':
|
||||||
|
return { label: 'Windows', url: DOWNLOAD_URLS.WINDOWS };
|
||||||
|
case 'mac-apple':
|
||||||
|
return { label: 'Mac (Apple Silicon)', url: DOWNLOAD_URLS.MAC_APPLE_SILICON };
|
||||||
|
case 'mac-intel':
|
||||||
|
return { label: 'Mac (Intel)', url: DOWNLOAD_URLS.MAC_INTEL };
|
||||||
|
case 'linux-x64':
|
||||||
|
case 'linux-arm64':
|
||||||
|
return { label: 'Linux', url: DOWNLOAD_URLS.LINUX_DOCS };
|
||||||
|
default:
|
||||||
|
return { label: '', url: '' };
|
||||||
|
}
|
||||||
|
}, [osType]);
|
||||||
|
|
||||||
|
const osOptions = useMemo(() => {
|
||||||
|
const options = [
|
||||||
|
{ label: 'Windows', url: DOWNLOAD_URLS.WINDOWS, value: 'windows' },
|
||||||
|
{ label: 'Mac (Apple Silicon)', url: DOWNLOAD_URLS.MAC_APPLE_SILICON, value: 'mac-apple' },
|
||||||
|
{ label: 'Mac (Intel)', url: DOWNLOAD_URLS.MAC_INTEL, value: 'mac-intel' },
|
||||||
|
{ label: 'Linux', url: DOWNLOAD_URLS.LINUX_DOCS, value: 'linux' },
|
||||||
|
];
|
||||||
|
return options.filter(opt => opt.url);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const resolvedFlow = useMemo(
|
||||||
|
() => resolveFlow(effectiveEnableLogin, effectiveIsAdmin, state.selfReportedAdmin),
|
||||||
|
[effectiveEnableLogin, effectiveIsAdmin, state.selfReportedAdmin],
|
||||||
|
);
|
||||||
|
// Desktop: No security-check slide, so no need to filter it out
|
||||||
|
const flowSlideIds = resolvedFlow.ids;
|
||||||
|
const flowType = resolvedFlow.type;
|
||||||
|
const totalSteps = flowSlideIds.length;
|
||||||
|
const maxIndex = Math.max(totalSteps - 1, 0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (state.step >= flowSlideIds.length) {
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
step: Math.max(flowSlideIds.length - 1, 0),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}, [flowSlideIds.length, state.step]);
|
||||||
|
|
||||||
|
const currentSlideId = flowSlideIds[state.step] ?? flowSlideIds[flowSlideIds.length - 1];
|
||||||
|
const slideDefinition = SLIDE_DEFINITIONS[currentSlideId];
|
||||||
|
|
||||||
|
if (!slideDefinition) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const scenarioProvidesInfo =
|
||||||
|
scenarioKey && scenarioKey !== 'unknown' && scenarioKey !== 'licensed';
|
||||||
|
const scenarioIndicatesAdmin = scenarioProvidesInfo
|
||||||
|
? scenarioKey!.includes('admin')
|
||||||
|
: state.selfReportedAdmin || effectiveIsAdmin;
|
||||||
|
const scenarioIndicatesOverLimit = scenarioProvidesInfo
|
||||||
|
? scenarioKey!.includes('over-limit')
|
||||||
|
: effectiveLicenseUserCount != null && effectiveLicenseUserCount > freeTierLimit;
|
||||||
|
const scenarioRequiresLicense =
|
||||||
|
scenarioKey === 'licensed' ? false : scenarioKey === 'unknown' ? !hasPaidLicense : true;
|
||||||
|
|
||||||
|
const shouldShowServerLicenseInfo = scenarioIndicatesAdmin && scenarioRequiresLicense;
|
||||||
|
|
||||||
|
const licenseNotice = useMemo<LicenseNotice>(
|
||||||
|
() => ({
|
||||||
|
totalUsers: effectiveLicenseUserCount,
|
||||||
|
freeTierLimit,
|
||||||
|
isOverLimit: scenarioIndicatesOverLimit,
|
||||||
|
requiresLicense: shouldShowServerLicenseInfo,
|
||||||
|
}),
|
||||||
|
[
|
||||||
|
effectiveLicenseUserCount,
|
||||||
|
freeTierLimit,
|
||||||
|
scenarioIndicatesOverLimit,
|
||||||
|
shouldShowServerLicenseInfo,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const requestServerLicenseIfNeeded = useCallback(
|
||||||
|
(options?: { deferUntilTourComplete?: boolean; selfReportedAdmin?: boolean }) => {
|
||||||
|
if (!shouldShowServerLicenseInfo) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onRequestServerLicense?.(options);
|
||||||
|
},
|
||||||
|
[onRequestServerLicense, shouldShowServerLicenseInfo],
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
onLicenseNoticeUpdate?.(licenseNotice);
|
||||||
|
}, [licenseNotice, onLicenseNoticeUpdate]);
|
||||||
|
|
||||||
|
// Initialize ref with default URL
|
||||||
|
useEffect(() => {
|
||||||
|
if (!selectedDownloadUrlRef.current && os.url) {
|
||||||
|
selectedDownloadUrlRef.current = os.url;
|
||||||
|
}
|
||||||
|
}, [os.url]);
|
||||||
|
|
||||||
|
const handleDownloadUrlChange = useCallback((url: string) => {
|
||||||
|
selectedDownloadUrlRef.current = url;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const currentSlide = slideDefinition.createSlide({
|
||||||
|
osLabel: os.label,
|
||||||
|
osUrl: os.url,
|
||||||
|
osOptions,
|
||||||
|
onDownloadUrlChange: handleDownloadUrlChange,
|
||||||
|
selectedRole: state.selectedRole,
|
||||||
|
onRoleSelect: handleRoleSelect,
|
||||||
|
licenseNotice,
|
||||||
|
loginEnabled: effectiveEnableLogin,
|
||||||
|
});
|
||||||
|
|
||||||
|
const goNext = useCallback(() => {
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
step: Math.min(prev.step + 1, maxIndex),
|
||||||
|
}));
|
||||||
|
}, [maxIndex]);
|
||||||
|
|
||||||
|
const goPrev = useCallback(() => {
|
||||||
|
setState((prev) => ({
|
||||||
|
...prev,
|
||||||
|
step: Math.max(prev.step - 1, 0),
|
||||||
|
}));
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const launchTour = useCallback(
|
||||||
|
(mode: 'admin' | 'tools', options?: { closeOnboardingSlides?: boolean }) => {
|
||||||
|
if (options?.closeOnboardingSlides) {
|
||||||
|
closeAndMarkSeen();
|
||||||
|
}
|
||||||
|
|
||||||
|
startTour(mode, {
|
||||||
|
source: 'initial-onboarding-modal',
|
||||||
|
metadata: {
|
||||||
|
hasCompletedOnboarding: preferences.hasCompletedOnboarding,
|
||||||
|
toolPanelModePromptSeen: preferences.toolPanelModePromptSeen,
|
||||||
|
selfReportedAdmin: state.selfReportedAdmin,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
[closeAndMarkSeen, preferences.hasCompletedOnboarding, preferences.toolPanelModePromptSeen, startTour, state.selfReportedAdmin],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleButtonAction = useCallback(
|
||||||
|
(action: ButtonAction) => {
|
||||||
|
const isOnLastSlide = state.step >= maxIndex;
|
||||||
|
|
||||||
|
// Desktop: For login-user and no-login flows, when on the last slide (welcome),
|
||||||
|
// clicking Next should launch the tools tour
|
||||||
|
const shouldAutoLaunchToolsTour =
|
||||||
|
(flowType === 'login-user' || flowType === 'no-login') && isOnLastSlide;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'next':
|
||||||
|
if (shouldAutoLaunchToolsTour) {
|
||||||
|
launchTour('tools', { closeOnboardingSlides: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
goNext();
|
||||||
|
return;
|
||||||
|
case 'prev':
|
||||||
|
goPrev();
|
||||||
|
return;
|
||||||
|
case 'close':
|
||||||
|
closeAndMarkSeen();
|
||||||
|
return;
|
||||||
|
case 'download-selected': {
|
||||||
|
const downloadUrl = selectedDownloadUrlRef.current || os.url || currentSlide.downloadUrl;
|
||||||
|
if (downloadUrl) {
|
||||||
|
window.open(downloadUrl, '_blank', 'noopener');
|
||||||
|
}
|
||||||
|
if (shouldAutoLaunchToolsTour) {
|
||||||
|
launchTour('tools', { closeOnboardingSlides: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
goNext();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'complete-close':
|
||||||
|
updatePreference('hasCompletedOnboarding', true);
|
||||||
|
closeAndMarkSeen();
|
||||||
|
return;
|
||||||
|
case 'security-next':
|
||||||
|
// Desktop: security-check slide is removed, but keep this for completeness
|
||||||
|
if (!state.selectedRole) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (state.selectedRole === 'admin') {
|
||||||
|
goNext();
|
||||||
|
} else {
|
||||||
|
launchTour('tools', { closeOnboardingSlides: true });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
case 'launch-admin':
|
||||||
|
requestServerLicenseIfNeeded({
|
||||||
|
deferUntilTourComplete: true,
|
||||||
|
selfReportedAdmin: state.selfReportedAdmin || effectiveIsAdmin,
|
||||||
|
});
|
||||||
|
launchTour('admin', { closeOnboardingSlides: true });
|
||||||
|
return;
|
||||||
|
case 'launch-tools':
|
||||||
|
launchTour('tools', { closeOnboardingSlides: true });
|
||||||
|
return;
|
||||||
|
case 'launch-auto': {
|
||||||
|
const launchMode = state.selfReportedAdmin || effectiveIsAdmin ? 'admin' : 'tools';
|
||||||
|
if (launchMode === 'admin') {
|
||||||
|
requestServerLicenseIfNeeded({
|
||||||
|
deferUntilTourComplete: true,
|
||||||
|
selfReportedAdmin: state.selfReportedAdmin || effectiveIsAdmin,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
launchTour(launchMode, { closeOnboardingSlides: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
case 'skip-to-license':
|
||||||
|
updatePreference('hasCompletedOnboarding', true);
|
||||||
|
requestServerLicenseIfNeeded({
|
||||||
|
deferUntilTourComplete: false,
|
||||||
|
selfReportedAdmin: state.selfReportedAdmin || effectiveIsAdmin,
|
||||||
|
});
|
||||||
|
closeAndMarkSeen();
|
||||||
|
return;
|
||||||
|
case 'see-plans':
|
||||||
|
closeAndMarkSeen();
|
||||||
|
navigate('/settings/adminPlan');
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[
|
||||||
|
closeAndMarkSeen,
|
||||||
|
currentSlide,
|
||||||
|
currentSlideId,
|
||||||
|
effectiveIsAdmin,
|
||||||
|
flowType,
|
||||||
|
goNext,
|
||||||
|
goPrev,
|
||||||
|
launchTour,
|
||||||
|
maxIndex,
|
||||||
|
navigate,
|
||||||
|
requestServerLicenseIfNeeded,
|
||||||
|
onRequestServerLicense,
|
||||||
|
os.url,
|
||||||
|
state.selectedRole,
|
||||||
|
state.selfReportedAdmin,
|
||||||
|
state.step,
|
||||||
|
updatePreference,
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
const flowState: FlowState = { selectedRole: state.selectedRole };
|
||||||
|
|
||||||
|
return {
|
||||||
|
state,
|
||||||
|
totalSteps,
|
||||||
|
slideDefinition,
|
||||||
|
currentSlide,
|
||||||
|
licenseNotice,
|
||||||
|
flowState,
|
||||||
|
closeAndMarkSeen,
|
||||||
|
handleButtonAction,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
/**
|
||||||
|
* Desktop override of onboarding flow config.
|
||||||
|
*
|
||||||
|
* This version removes the desktop-install and security-check slides
|
||||||
|
* since they're not relevant when running as a desktop app.
|
||||||
|
*
|
||||||
|
* The SlideId type still includes all values for type compatibility,
|
||||||
|
* but the actual FLOW_SEQUENCES don't use these slides.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import WelcomeSlide from '@app/components/onboarding/slides/WelcomeSlide';
|
||||||
|
import PlanOverviewSlide from '@app/components/onboarding/slides/PlanOverviewSlide';
|
||||||
|
import ServerLicenseSlide from '@app/components/onboarding/slides/ServerLicenseSlide';
|
||||||
|
import { SlideConfig, LicenseNotice } from '@app/types/types';
|
||||||
|
|
||||||
|
// Keep the full type for compatibility, but these slides won't be used
|
||||||
|
export type SlideId =
|
||||||
|
| 'welcome'
|
||||||
|
| 'desktop-install'
|
||||||
|
| 'security-check'
|
||||||
|
| 'admin-overview'
|
||||||
|
| 'server-license';
|
||||||
|
|
||||||
|
export type HeroType = 'rocket' | 'dual-icon' | 'shield' | 'diamond' | 'logo';
|
||||||
|
|
||||||
|
export type ButtonAction =
|
||||||
|
| 'next'
|
||||||
|
| 'prev'
|
||||||
|
| 'close'
|
||||||
|
| 'complete-close'
|
||||||
|
| 'download-selected'
|
||||||
|
| 'security-next'
|
||||||
|
| 'launch-admin'
|
||||||
|
| 'launch-tools'
|
||||||
|
| 'launch-auto'
|
||||||
|
| 'see-plans'
|
||||||
|
| 'skip-to-license';
|
||||||
|
|
||||||
|
export interface FlowState {
|
||||||
|
selectedRole: 'admin' | 'user' | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface OSOption {
|
||||||
|
label: string;
|
||||||
|
url: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SlideFactoryParams {
|
||||||
|
osLabel: string;
|
||||||
|
osUrl: string;
|
||||||
|
osOptions?: OSOption[];
|
||||||
|
onDownloadUrlChange?: (url: string) => void;
|
||||||
|
selectedRole: 'admin' | 'user' | null;
|
||||||
|
onRoleSelect: (role: 'admin' | 'user' | null) => void;
|
||||||
|
licenseNotice?: LicenseNotice;
|
||||||
|
loginEnabled?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HeroDefinition {
|
||||||
|
type: HeroType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ButtonDefinition {
|
||||||
|
key: string;
|
||||||
|
type: 'button' | 'icon';
|
||||||
|
label?: string;
|
||||||
|
icon?: 'chevron-left';
|
||||||
|
variant?: 'primary' | 'secondary' | 'default';
|
||||||
|
group: 'left' | 'right';
|
||||||
|
action: ButtonAction;
|
||||||
|
disabledWhen?: (state: FlowState) => boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SlideDefinition {
|
||||||
|
id: SlideId;
|
||||||
|
createSlide: (params: SlideFactoryParams) => SlideConfig;
|
||||||
|
hero: HeroDefinition;
|
||||||
|
buttons: ButtonDefinition[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SLIDE_DEFINITIONS: Record<SlideId, SlideDefinition> = {
|
||||||
|
'welcome': {
|
||||||
|
id: 'welcome',
|
||||||
|
createSlide: () => WelcomeSlide(),
|
||||||
|
hero: { type: 'rocket' },
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
key: 'welcome-next',
|
||||||
|
type: 'button',
|
||||||
|
label: 'onboarding.buttons.next',
|
||||||
|
variant: 'primary',
|
||||||
|
group: 'right',
|
||||||
|
action: 'next',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// Stub definitions for desktop-install and security-check - not used on desktop
|
||||||
|
// but kept for type compatibility with core code
|
||||||
|
'desktop-install': {
|
||||||
|
id: 'desktop-install',
|
||||||
|
createSlide: () => WelcomeSlide(), // Placeholder - never used
|
||||||
|
hero: { type: 'dual-icon' },
|
||||||
|
buttons: [],
|
||||||
|
},
|
||||||
|
'security-check': {
|
||||||
|
id: 'security-check',
|
||||||
|
createSlide: () => WelcomeSlide(), // Placeholder - never used
|
||||||
|
hero: { type: 'shield' },
|
||||||
|
buttons: [],
|
||||||
|
},
|
||||||
|
'admin-overview': {
|
||||||
|
id: 'admin-overview',
|
||||||
|
createSlide: ({ licenseNotice, loginEnabled }) =>
|
||||||
|
PlanOverviewSlide({ isAdmin: true, licenseNotice, loginEnabled }),
|
||||||
|
hero: { type: 'diamond' },
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
key: 'admin-back',
|
||||||
|
type: 'icon',
|
||||||
|
icon: 'chevron-left',
|
||||||
|
group: 'left',
|
||||||
|
action: 'prev',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'admin-show',
|
||||||
|
type: 'button',
|
||||||
|
label: 'onboarding.buttons.showMeAround',
|
||||||
|
variant: 'primary',
|
||||||
|
group: 'right',
|
||||||
|
action: 'launch-admin',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'admin-skip',
|
||||||
|
type: 'button',
|
||||||
|
label: 'onboarding.buttons.skipTheTour',
|
||||||
|
variant: 'secondary',
|
||||||
|
group: 'left',
|
||||||
|
action: 'skip-to-license',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'server-license': {
|
||||||
|
id: 'server-license',
|
||||||
|
createSlide: ({ licenseNotice }) => ServerLicenseSlide({ licenseNotice }),
|
||||||
|
hero: { type: 'dual-icon' },
|
||||||
|
buttons: [
|
||||||
|
{
|
||||||
|
key: 'license-close',
|
||||||
|
type: 'button',
|
||||||
|
label: 'onboarding.buttons.skipForNow',
|
||||||
|
variant: 'secondary',
|
||||||
|
group: 'left',
|
||||||
|
action: 'close',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'license-see-plans',
|
||||||
|
type: 'button',
|
||||||
|
label: 'onboarding.serverLicense.seePlans',
|
||||||
|
variant: 'primary',
|
||||||
|
group: 'right',
|
||||||
|
action: 'see-plans',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Desktop flow sequences - simplified without desktop-install and security-check slides
|
||||||
|
* since users are already on desktop and security check is not needed.
|
||||||
|
*/
|
||||||
|
export const FLOW_SEQUENCES = {
|
||||||
|
loginAdmin: ['welcome', 'admin-overview'] as SlideId[],
|
||||||
|
loginUser: ['welcome'] as SlideId[],
|
||||||
|
noLoginBase: ['welcome'] as SlideId[],
|
||||||
|
noLoginAdmin: ['admin-overview'] as SlideId[],
|
||||||
|
};
|
||||||
|
|
||||||
Reference in New Issue
Block a user