feat(frontend): add reassurance strip and supported-formats section

Part of the Ombrora Convert redesign (Task 3/5).
This commit is contained in:
2026-07-31 14:57:46 +02:00
parent bc7a3b0691
commit 2aa8e51b80
5 changed files with 170 additions and 34 deletions
+19
View File
@@ -0,0 +1,19 @@
import { useTranslation } from 'react-i18next';
import { FORMAT_FAMILIES } from '../data/formats.js';
export function FormatsGrid() {
const { t } = useTranslation();
return (
<section className="formats-grid-section">
<h2>{t('formats.title')}</h2>
<div className="formats-grid">
{FORMAT_FAMILIES.map(({ key, formats }) => (
<div key={key} className="formats-family">
<h3>{t(`formats.${key}`)}</h3>
<p>{formats.join(', ').toUpperCase()}</p>
</div>
))}
</div>
</section>
);
}
@@ -0,0 +1,26 @@
import { useTranslation } from 'react-i18next';
import { Clock, LockKey, CloudArrowUp, HardDrives } from '@phosphor-icons/react';
const ITEMS = [
{ key: 'autoDelete', Icon: Clock },
{ key: 'noAccount', Icon: LockKey },
{ key: 'online', Icon: CloudArrowUp },
{ key: 'maxSize', Icon: HardDrives },
];
export function ReassuranceStrip() {
const { t } = useTranslation();
return (
<section className="reassurance">
<h2>{t('reassurance.title')}</h2>
<ul className="reassurance-list">
{ITEMS.map(({ key, Icon }) => (
<li key={key}>
<Icon size={24} weight="regular" />
<span>{t(`reassurance.${key}`)}</span>
</li>
))}
</ul>
</section>
);
}
+18
View File
@@ -0,0 +1,18 @@
export const FORMAT_FAMILIES = [
{
key: 'images',
formats: ['jpg', 'jpeg', 'png', 'webp', 'gif', 'tiff', 'avif', 'heic', 'heif', 'ico'],
},
{
key: 'documents',
formats: ['docx', 'txt', 'html', 'md', 'pdf', 'csv', 'xlsx'],
},
{
key: 'fonts',
formats: ['ttf', 'otf', 'woff', 'dfont'],
},
{
key: 'ebooks',
formats: ['epub', 'fb2', 'lrf', 'mobi', 'pdb', 'rb', 'snb', 'tcr', 'azw3', 'pdf'],
},
];
+42 -34
View File
@@ -4,7 +4,10 @@ import { fetchFormats, uploadFiles } from '../api.js';
import { FileCard } from '../FileCard.jsx'; import { FileCard } from '../FileCard.jsx';
import { Dropzone } from '../components/Dropzone.jsx'; import { Dropzone } from '../components/Dropzone.jsx';
import { FileConfigCard } from '../components/FileConfigCard.jsx'; import { FileConfigCard } from '../components/FileConfigCard.jsx';
import { ReassuranceStrip } from '../components/ReassuranceStrip.jsx';
import { FormatsGrid } from '../components/FormatsGrid.jsx';
import '../styles/home.css'; import '../styles/home.css';
import '../styles/sections.css';
const DEFAULT_QUALITY = { const DEFAULT_QUALITY = {
jpg: 80, jpg: 80,
@@ -79,41 +82,46 @@ export function HomePage() {
} }
return ( return (
<section className="hero"> <>
<h1>{t('hero.title')}</h1> <section className="hero">
<p>{t('hero.subtitle')}</p> <h1>{t('hero.title')}</h1>
<Dropzone label={t('hero.dropzoneLabel')} onFilesSelected={handleFilesSelected} /> <p>{t('hero.subtitle')}</p>
<Dropzone label={t('hero.dropzoneLabel')} onFilesSelected={handleFilesSelected} />
{pendingFiles.length > 0 && ( {pendingFiles.length > 0 && (
<div className="pending-files"> <div className="pending-files">
<ul> <ul>
{pendingFiles.map((item, index) => ( {pendingFiles.map((item, index) => (
<FileConfigCard <FileConfigCard
key={`${item.file.name}-${index}`} key={`${item.file.name}-${index}`}
item={item} item={item}
index={index} index={index}
t={t} t={t}
onTargetFormatChange={updateTargetFormat} onTargetFormatChange={updateTargetFormat}
onQualityChange={updateQuality} onQualityChange={updateQuality}
onIconSizeChange={updateIconSize} onIconSizeChange={updateIconSize}
/> />
))} ))}
</ul> </ul>
<button className="convert-button" onClick={handleConvert}> <button className="convert-button" onClick={handleConvert}>
{t('hero.convert')} {t('hero.convert')}
</button> </button>
</div> </div>
)}
<ul className="job-list">
{submittedJobs.map((job, index) =>
job.id ? (
<FileCard key={job.id} fileName={job.file} jobId={job.id} />
) : (
<FileCard key={`${job.file}-${index}`} fileName={job.file} initialError={job.error} />
)
)} )}
</ul>
</section> <ul className="job-list">
{submittedJobs.map((job, index) =>
job.id ? (
<FileCard key={job.id} fileName={job.file} jobId={job.id} />
) : (
<FileCard key={`${job.file}-${index}`} fileName={job.file} initialError={job.error} />
)
)}
</ul>
</section>
<ReassuranceStrip />
<FormatsGrid />
</>
); );
} }
+65
View File
@@ -0,0 +1,65 @@
.reassurance,
.formats-grid-section {
max-width: 960px;
margin: 0 auto;
padding: 2.5rem 1.5rem;
text-align: center;
}
.reassurance-list {
list-style: none;
padding: 0;
margin: 1.5rem 0 0;
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
}
.reassurance-list li {
display: flex;
align-items: center;
gap: 0.75rem;
padding: 1rem;
border: 1px solid var(--color-border);
border-radius: 8px;
text-align: left;
}
.formats-grid {
display: grid;
grid-template-columns: 1fr;
gap: 1rem;
margin-top: 1.5rem;
}
.formats-family {
padding: 1rem;
border: 1px solid var(--color-border);
border-radius: 8px;
text-align: left;
}
.formats-family h3 {
margin: 0 0 0.5rem;
}
.formats-family p {
margin: 0;
opacity: 0.8;
font-size: 0.875rem;
}
@media (min-width: 768px) {
.reassurance-list {
grid-template-columns: repeat(2, 1fr);
}
.formats-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (min-width: 1024px) {
.formats-grid {
grid-template-columns: repeat(4, 1fr);
}
}