Add StepImportProfile component
This commit is contained in:
parent
97e86308ce
commit
9e0f3d3b73
@ -0,0 +1,72 @@
|
|||||||
|
@import "@Themes/constants.scss";
|
||||||
|
|
||||||
|
.root {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: 530px;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 220px;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
text-align: left;
|
||||||
|
|
||||||
|
@media (max-width: $screen-s) {
|
||||||
|
font-family: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-top: 16px;
|
||||||
|
color: var(--color-neutral-700);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.import-section {
|
||||||
|
margin-top: 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
margin-top: 16px;
|
||||||
|
padding: 12px;
|
||||||
|
background-color: var(--color-error-50);
|
||||||
|
border: 1px solid var(--color-error-200);
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-preview {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding: 16px;
|
||||||
|
background-color: var(--color-neutral-50);
|
||||||
|
border: 1px solid var(--color-neutral-200);
|
||||||
|
border-radius: 8px;
|
||||||
|
|
||||||
|
.preview-title {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
color: var(--color-neutral-800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions {
|
||||||
|
margin-top: 32px;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: flex-end;
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-button {
|
||||||
|
min-width: 140px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: $screen-s) {
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.back-button,
|
||||||
|
.submit-button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
src/front/Components/Layouts/Login/StepImportProfile/index.tsx
Normal file
132
src/front/Components/Layouts/Login/StepImportProfile/index.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import React, { useState, useCallback } from "react";
|
||||||
|
import classes from "./classes.module.scss";
|
||||||
|
import Typography, { ETypo, ETypoColor } from "@Front/Components/DesignSystem/Typography";
|
||||||
|
import Button, { EButtonVariant } from "@Front/Components/DesignSystem/Button";
|
||||||
|
import DragAndDrop from "@Front/Components/DesignSystem/DragAndDrop";
|
||||||
|
import { ValidationError } from "class-validator";
|
||||||
|
|
||||||
|
type IProps = {
|
||||||
|
onSubmit: (profileData: any) => void;
|
||||||
|
onBack: () => void;
|
||||||
|
validationErrors: ValidationError[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function StepImportProfile(props: IProps) {
|
||||||
|
const { onSubmit, onBack, validationErrors } = props;
|
||||||
|
const [importedFile, setImportedFile] = useState<File | null>(null);
|
||||||
|
const [profileData, setProfileData] = useState<any>(null);
|
||||||
|
const [error, setError] = useState<string>("");
|
||||||
|
|
||||||
|
const validateProfileStructure = (data: any): boolean => {
|
||||||
|
// Basic validation for profile structure
|
||||||
|
return data &&
|
||||||
|
typeof data === "object" &&
|
||||||
|
data.version &&
|
||||||
|
data.userData &&
|
||||||
|
typeof data.userData === "object";
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFileUpload = useCallback(async (files: File[]) => {
|
||||||
|
const file = files[0];
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
// Validate file type
|
||||||
|
if (file.type !== "application/json") {
|
||||||
|
setError("Veuillez sélectionner un fichier JSON valide.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate file size (max 1MB)
|
||||||
|
if (file.size > 1024 * 1024) {
|
||||||
|
setError("Le fichier est trop volumineux. Taille maximum : 1MB.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setImportedFile(file);
|
||||||
|
setError("");
|
||||||
|
|
||||||
|
// Read and parse JSON
|
||||||
|
try {
|
||||||
|
const text = await file.text();
|
||||||
|
const data = JSON.parse(text);
|
||||||
|
|
||||||
|
// Validate profile structure
|
||||||
|
if (!validateProfileStructure(data)) {
|
||||||
|
setError("Le fichier ne contient pas un profil valide.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setProfileData(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError("Erreur lors de la lecture du fichier JSON.");
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSubmit = useCallback(() => {
|
||||||
|
if (profileData) {
|
||||||
|
onSubmit(profileData);
|
||||||
|
}
|
||||||
|
}, [profileData, onSubmit]);
|
||||||
|
|
||||||
|
// Get validation error for import
|
||||||
|
const importError = validationErrors.find((error) => error.property === "import");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={classes["root"]}>
|
||||||
|
<Typography typo={ETypo.DISPLAY_LARGE}>
|
||||||
|
<div className={classes["title"]}>Importer un profil</div>
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR} className={classes["description"]}>
|
||||||
|
Sélectionnez un fichier JSON contenant vos données de profil pour restaurer votre session.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div className={classes["import-section"]}>
|
||||||
|
<DragAndDrop
|
||||||
|
title="Importer un profil"
|
||||||
|
description="Glissez-déposez votre fichier JSON ou cliquez pour parcourir"
|
||||||
|
onChange={handleFileUpload}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(error || importError) && (
|
||||||
|
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_ERROR_500} className={classes["error"]}>
|
||||||
|
{error || (importError?.constraints && Object.values(importError.constraints)[0])}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{profileData && (
|
||||||
|
<div className={classes["profile-preview"]}>
|
||||||
|
<Typography typo={ETypo.TEXT_LG_SEMIBOLD} className={classes["preview-title"]}>
|
||||||
|
Profil détecté :
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_MD_REGULAR}>
|
||||||
|
{profileData.userData?.email || "Email non disponible"}
|
||||||
|
</Typography>
|
||||||
|
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_NEUTRAL_600}>
|
||||||
|
Version : {profileData.version}
|
||||||
|
</Typography>
|
||||||
|
{profileData.exportedAt && (
|
||||||
|
<Typography typo={ETypo.TEXT_SM_REGULAR} color={ETypoColor.COLOR_NEUTRAL_600}>
|
||||||
|
Exporté le : {new Date(profileData.exportedAt).toLocaleDateString('fr-FR')}
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className={classes["actions"]}>
|
||||||
|
<Button variant={EButtonVariant.SECONDARY} onClick={onBack} className={classes["back-button"]}>
|
||||||
|
Retour
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={EButtonVariant.PRIMARY}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
disabled={!profileData}
|
||||||
|
className={classes["submit-button"]}
|
||||||
|
>
|
||||||
|
Importer le profil
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user