feat(frontend): add reassurance strip and supported-formats section
Part of the Ombrora Convert redesign (Task 3/5).
This commit is contained in:
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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'],
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -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 />
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user