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.
This commit is contained in:
175
hooks/useOnboarding.tsx
Normal file
175
hooks/useOnboarding.tsx
Normal file
@@ -0,0 +1,175 @@
|
||||
'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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user