chore init

This commit is contained in:
devcodetools committed 2026-09-16 21:34:09 +02:00
1 parent 1b7406b1d3
commit a0d312a884
60 files changed
+5046 -125

No files matched your search

+101
View File
@@ -0,0 +1,101 @@
"use client";
import { useCallback } from "react";
import {
createDefaultCVData,
type CVCareerData,
type CVTabId,
type Experience,
} from "@/lib/types/cv";
import { localStorageAdapter } from "@/lib/storage";
import { useLocalStorage } from "@/lib/hooks/useLocalStorage";
export interface UseCVDataReturn {
data: CVCareerData;
setText: (tab: CVTabId, value: string) => void;
addExperience: (titre: string, contenu: string) => void;
updateExperience: (id: string, titre: string, contenu: string) => void;
removeExperience: (id: string) => void;
resetCV: () => void;
importCV: (value: CVCareerData) => void;
}
const DEFAULT_TITLE = "Expérience";
export function useCVData(): UseCVDataReturn {
const [data, setData] = useLocalStorage<CVCareerData>(
localStorageAdapter,
createDefaultCVData(),
);
const setText = useCallback(
(tab: CVTabId, value: string) => {
setData((prev) => ({ ...prev, [tab]: value }));
},
[setData],
);
const addExperience = useCallback(
(titre: string, contenu: string) => {
setData((prev) => {
const now = Date.now();
const experience: Experience = {
id: crypto.randomUUID(),
titre: titre.trim() || DEFAULT_TITLE,
contenu,
createdAt: now,
updatedAt: now,
};
return { ...prev, experiences: [...prev.experiences, experience] };
});
},
[setData],
);
const updateExperience = useCallback(
(id: string, titre: string, contenu: string) => {
setData((prev) => ({
...prev,
experiences: prev.experiences.map((exp) =>
exp.id === id
? {
...exp,
titre: titre.trim() || DEFAULT_TITLE,
contenu,
updatedAt: Date.now(),
}
: exp,
),
}));
},
[setData],
);
const removeExperience = useCallback(
(id: string) => {
setData((prev) => ({
...prev,
experiences: prev.experiences.filter((exp) => exp.id !== id),
}));
},
[setData],
);
const resetCV = useCallback(() => {
setData(createDefaultCVData());
}, [setData]);
const importCV = useCallback((value: CVCareerData) => {
setData(value);
}, [setData]);
return {
data,
setText,
addExperience,
updateExperience,
removeExperience,
resetCV,
importCV,
};
}
+52
View File
@@ -0,0 +1,52 @@
"use client";
import { useEffect, useState } from "react";
export interface StorageLike<T> {
load(): T | null;
save(value: T): void;
}
const SAVE_DELAY_MS = 60;
export function useLocalStorage<T>(
storage: StorageLike<T>,
initialValue: T,
enable = true,
): [T, (value: T | ((prev: T) => T)) => void] {
const [value, setValue] = useState<T>(initialValue);
useEffect(() => {
if (!enable) return;
let disposed = false;
const timer = window.setTimeout(() => {
try {
const stored = storage.load();
if (disposed || stored === null || stored === undefined) return;
setValue(stored);
} catch (error) {
console.warn("Échec du chargement localStorage", error);
}
}, 0);
return () => {
disposed = true;
window.clearTimeout(timer);
};
}, [storage, enable]);
useEffect(() => {
if (!enable) return;
const timer = window.setTimeout(() => {
try {
storage.save(value);
} catch (error) {
console.warn("Échec de la sauvegarde localStorage", error);
}
}, SAVE_DELAY_MS);
return () => {
window.clearTimeout(timer);
};
}, [storage, value, enable]);
return [value, setValue];
}