- 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.
176 lines
6.3 KiB
TypeScript
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,
|
|
};
|
|
}
|