Files
ERitors-Scribe-Desktop/hooks/useOnboarding.tsx
natreex d4765e6576 Add foundational components and logic for migration, UI design, and input handling
- Introduced foundational UI components (`Badge`, `LockCard`, `SectionHeader`, `AvatarIcon`, etc.) for flexible layouts and consistent design.
- Added migration support with the `MigrationModal` component and backend integration for exporting/importing data between Electron and Tauri.
- Extended form components with `TextAreaInput`, `OrderInput`, `ToggleField`, and `ToolbarSelect` for improved input handling.
- Updated `ScribeShell` with migration popup logic to prompt users for data migration.
- Integrated `AlertStack` for better alert handling and notification management.
- Enhanced Rust/Tauri services with migration command implementations.
- Added translations and styles for new components.
2026-04-05 12:52:54 -04:00

176 lines
6.3 KiB
TypeScript

'use client';
import {Dispatch, SetStateAction, useContext, useEffect, useState} from 'react';
import {useTranslations} from '@/lib/i18n';
import {AlertContext, AlertContextProps} from '@/context/AlertContext';
import {LangContext, LangContextProps} from '@/context/LangContext';
import {SessionProps} from '@/lib/types/session';
import {guideTourDone, setNewGuideTour} from '@/lib/utils/user';
import {apiPost} from '@/lib/api/client';
import {GuideStep} from '@/components/GuideTour';
import OfflineContext, {OfflineContextType} from '@/context/OfflineContext';
interface UseOnboardingReturn {
isTermsAccepted: boolean;
homeStepsGuide: boolean;
setHomeStepsGuide: Dispatch<SetStateAction<boolean>>;
handleTermsAcceptance: () => Promise<void>;
handleHomeTour: () => Promise<void>;
homeSteps: GuideStep[];
}
interface UseOnboardingParams {
session: SessionProps;
setSession: Dispatch<SetStateAction<SessionProps>>;
}
export default function useOnboarding({session, setSession}: UseOnboardingParams): UseOnboardingReturn {
const t = useTranslations();
const {lang: locale}: LangContextProps = useContext<LangContextProps>(LangContext);
const {errorMessage}: AlertContextProps = useContext<AlertContextProps>(AlertContext);
const {isCurrentlyOffline}: OfflineContextType = useContext<OfflineContextType>(OfflineContext);
const [isTermsAccepted, setIsTermsAccepted] = useState<boolean>(false);
const [homeStepsGuide, setHomeStepsGuide] = useState<boolean>(false);
useEffect((): void => {
if (isCurrentlyOffline()) {
setIsTermsAccepted(true);
const localGuideDone: boolean = localStorage.getItem('guide-tour-home-basic') === 'true';
setHomeStepsGuide(!localGuideDone);
return;
}
if (session.isConnected) {
setIsTermsAccepted(session.user?.termsAccepted ?? false);
const guides: string[] = ['home-basic', 'new-first-book'];
for (const guide of guides) {
const done: boolean = !guideTourDone(session.user?.guideTour ?? [], guide);
localStorage.setItem(`guide-tour-${guide}`, done ? 'true' : 'false');
}
setHomeStepsGuide(guideTourDone(session.user?.guideTour ?? [], 'home-basic'));
}
}, [session]);
const homeSteps: GuideStep[] = [
{
id: 0,
x: 50,
y: 50,
title: t('homePage.guide.welcome', {name: session.user?.name || ''}),
content: (
<>
<p>{t('homePage.guide.step0.description1')}</p>
<br/>
<p>{t('homePage.guide.step0.description2')}</p>
</>
),
},
{
id: 1, position: 'right',
targetSelector: `[data-guide="left-panel-container"]`,
title: t('homePage.guide.step1.title'),
content: (
<>
<p className={'flex items-center space-x-2'}>
<strong>{t('homePage.guide.step1.addBook')}</strong>
</p>
<br/>
<p><strong>{t('homePage.guide.step1.generateStory')}</strong></p>
</>
),
},
{
id: 2,
title: t('homePage.guide.step2.title'), position: 'bottom',
targetSelector: `[data-guide="search-bar"]`,
content: (
<p>{t('homePage.guide.step2.description')}</p>
),
},
{
id: 3,
title: t('homePage.guide.step3.title'),
targetSelector: `[data-guide="user-dropdown"]`,
position: 'auto',
content: (
<p>{t('homePage.guide.step3.description')}</p>
),
},
{
id: 4,
title: t('homePage.guide.step4.title'),
content: (
<>
<p>{t('homePage.guide.step4.description1')}</p>
<br/>
<p>{t('homePage.guide.step4.description2')}</p>
</>
),
},
];
async function handleTermsAcceptance(): Promise<void> {
try {
const response: boolean = await apiPost<boolean>(`user/terms/accept`, {
version: '2025-07-1'
}, session.accessToken, locale);
if (response && session.user) {
setIsTermsAccepted(true);
setHomeStepsGuide(true);
const newSession: SessionProps = {
...session,
user: {
...session.user,
termsAccepted: true
}
};
setSession(newSession);
} else {
errorMessage(t('homePage.errors.termsAcceptError'));
}
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t('homePage.errors.termsAcceptError'));
}
}
}
async function handleHomeTour(): Promise<void> {
if (isCurrentlyOffline()) {
localStorage.setItem('guide-tour-home-basic', 'true');
setHomeStepsGuide(false);
return;
}
try {
const response: boolean = await apiPost<boolean>('logs/tour', {
plateforme: 'desktop',
tour: 'home-basic'
},
session.accessToken,
locale
);
if (response) {
localStorage.setItem('guide-tour-home-basic', 'true');
setSession(setNewGuideTour(session, 'home-basic'));
setHomeStepsGuide(false);
}
} catch (e: unknown) {
if (e instanceof Error) {
errorMessage(e.message);
} else {
errorMessage(t('homePage.errors.termsError'));
}
}
}
return {
isTermsAccepted,
homeStepsGuide,
setHomeStepsGuide,
handleTermsAcceptance,
handleHomeTour,
homeSteps,
};
}