fix(ux-audit): landing next/image + confirm AlertDialog + search-modal i18n
Some checks failed
CI / Deploy production (on server) (push) Has been cancelled
CI / Lint, Unit Tests & Build (push) Has been cancelled

Landing page:
- <img> → next/image avec width/height (CLS fix)
- padding nav responsive px-4 sm:px-8

note-card:
- confirm() natif → AlertDialog (non-bloquant, stylable)
- showLeaveDialog state + dialog component

search-modal (20 chaînes FR → i18n):
- useLanguage ajouté
- 20 strings FR hardcoded → t('searchModal.*')
- Clés ajoutées dans en.json + fr.json
This commit is contained in:
Antigravity
2026-07-04 21:49:24 +00:00
parent e72ca26f97
commit 03a3fb7411
5 changed files with 94 additions and 29 deletions

View File

@@ -17,6 +17,7 @@ import {
} from 'lucide-react'
import { useRouter } from 'next/navigation'
import { useNotebooks } from '@/context/notebooks-context'
import { useLanguage } from '@/lib/i18n'
import type { Note } from '@/lib/types'
interface SearchMatch {
@@ -58,6 +59,7 @@ function escapeRegExp(s: string) {
export function SearchModal({ isOpen, onClose }: SearchModalProps) {
const router = useRouter()
const { notebooks } = useNotebooks()
const { t } = useLanguage()
const [query, setQuery] = useState('')
const [useRegex, setUseRegex] = useState(false)
@@ -413,7 +415,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
className="fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm flex items-center justify-center z-[200] p-4 sm:p-6 select-none"
role="dialog"
aria-modal="true"
aria-label="Recherche"
aria-label={t('searchModal.ariaLabel')}
onClick={(e) => { if (e.target === e.currentTarget) onClose() }}
>
<motion.div
@@ -432,13 +434,13 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
type="text"
value={query}
onChange={e => setQuery(e.target.value)}
placeholder="Rechercher dans toutes vos notes…"
placeholder={t('searchModal.placeholder')}
className="w-full text-sm pl-10 pr-28 py-2.5 rounded-xl border border-border/70 dark:border-zinc-800 bg-white/85 dark:bg-[#1C1C1C] text-ink dark:text-dark-ink placeholder-concrete/50 outline-none focus:border-blueprint transition-colors"
/>
<div className="absolute right-3 top-1/2 -translate-y-1/2 flex items-center gap-1">
<button
onClick={() => setCaseSensitive(c => !c)}
title="Respecter la casse"
title={t('searchModal.caseSensitive')}
className={`px-1.5 py-1 text-[9.5px] font-bold rounded-md transition-colors select-none ${
caseSensitive ? 'text-blueprint bg-blueprint/8' : 'text-concrete hover:bg-black/5 dark:hover:bg-white/5'
}`}
@@ -447,7 +449,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
</button>
<button
onClick={() => setUseRegex(r => !r)}
title="Mode regex"
title={t('searchModal.regexMode')}
className={`px-1.5 py-1 text-[9.5px] font-bold rounded-md transition-colors select-none ${
useRegex ? 'text-blueprint bg-blueprint/8' : 'text-concrete hover:bg-black/5 dark:hover:bg-white/5'
}`}
@@ -463,7 +465,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
{/* Saved queries */}
{savedQueries.length > 0 && (
<div className="flex items-center gap-2 text-[10px] text-concrete">
<span className="uppercase text-[9px] font-bold">Favoris :</span>
<span className="uppercase text-[9px] font-bold">{t('searchModal.favorites')}</span>
<div className="flex flex-wrap gap-1.5">
{savedQueries.map(sq => (
<button
@@ -507,12 +509,12 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
</div>
<span className="text-[11px] font-medium text-concrete">
{isLoading
? 'Recherche en cours…'
? t('searchModal.searching')
: filteredMatches.length > 0
? `${filteredMatches.length} occurrence${filteredMatches.length > 1 ? 's' : ''} dans ${docMatchesCount} note${docMatchesCount > 1 ? 's' : ''}`
: query.trim()
? 'Aucun résultat'
: 'Tapez pour rechercher'}
? t('searchModal.noResults')
: t('searchModal.typeToSearch')}
</span>
</div>
<div className="flex items-center gap-4">
@@ -531,7 +533,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
onChange={e => setSearchInTrash(e.target.checked)}
className="rounded border-border/60 text-blueprint focus:ring-blueprint w-3 h-3"
/>
<span>Corbeille incluse</span>
<span>{t('searchModal.trashIncluded')}</span>
</label>
</div>
</div>
@@ -547,13 +549,13 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
<div className="flex items-center gap-1.5 mb-1.5">
<Sparkles size={12} className="text-brand-accent" />
<span className="text-[9px] font-bold uppercase tracking-widest text-brand-accent">
{overviewLoading ? 'Analyse...' : 'Réponse IA'}
{overviewLoading ? t('searchModal.aiAnalysis') : t('searchModal.aiResponse')}
</span>
</div>
{overviewLoading ? (
<div className="flex items-center gap-2">
<Loader2 size={12} className="animate-spin text-brand-accent" />
<span className="text-[11px] text-muted-foreground">Synthèse des résultats...</span>
<span className="text-[11px] text-muted-foreground">t('searchModal.resultsSummary')</span>
</div>
) : (
<p className="text-[12px] leading-relaxed text-foreground/80">{overview}</p>
@@ -614,7 +616,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
<div className="h-full flex flex-col items-center justify-center text-center p-6 text-concrete pt-24 space-y-2">
<Search size={20} className="opacity-30 animate-pulse" />
<p className="text-[11px] font-medium italic opacity-70">
{query.trim() ? 'Aucune note ne correspond.' : 'Tapez pour obtenir des résultats instantanés.'}
{query.trim() ? t('searchModal.noMatch') : t('searchModal.typeForResults')}
</p>
</div>
)}
@@ -644,7 +646,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
{activeMatch.noteTitle}
</h4>
<p className="text-[8px] uppercase tracking-wider text-concrete font-bold mt-0.5">
APERÇU CONTEXTUEL
{t('searchModal.documentPreview')}
</p>
</div>
@@ -662,7 +664,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
className="px-5 py-2.5 bg-ink text-white dark:bg-white dark:text-black hover:opacity-90 text-xs font-semibold rounded-xl flex items-center gap-2 transition-all shadow-sm"
>
<CornerDownRight size={13} />
<span>Ouvrir dans l'éditeur</span>
<span>{t('searchModal.openInEditor')}</span>
</button>
<span className="text-[10px] text-concrete font-bold font-mono bg-paper dark:bg-white/5 border border-border/30 px-2 py-1 rounded">
ID: {activeMatch.noteId.slice(0, 8)}
@@ -673,7 +675,7 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
<div className="flex-1 flex flex-col items-center justify-center text-center p-6 text-concrete space-y-3">
<HelpCircle size={24} className="opacity-25" />
<div className="space-y-1">
<p className="text-[11.5px] font-bold">Aperçu du document</p>
<p className="text-[11.5px] font-bold">{t('searchModal.documentPreview')}</p>
<p className="text-[10px] italic opacity-60">
Sélectionnez un résultat pour explorer son contenu.
</p>
@@ -688,20 +690,20 @@ export function SearchModal({ isOpen, onClose }: SearchModalProps) {
<div className="flex items-center gap-5 text-[9.5px] font-bold text-concrete/70">
<span className="flex items-center gap-1.5">
<kbd className="bg-slate-200 dark:bg-zinc-800 px-1 py-0.5 rounded text-ink dark:text-white text-[9px]"></kbd>
naviguer
{t('searchModal.hintNavigate')}
</span>
<span className="flex items-center gap-1.5">
<kbd className="bg-slate-200 dark:bg-zinc-800 px-1 py-0.5 rounded text-ink dark:text-white text-[9px]">Entrée</kbd>
ouvrir
{t('searchModal.hintOpen')}
</span>
<span className="flex items-center gap-1.5">
<kbd className="bg-slate-200 dark:bg-zinc-800 px-1 py-0.5 rounded text-ink dark:text-white text-[9px]">Échap</kbd>
fermer
{t('searchModal.hintClose')}
</span>
</div>
<div className="flex items-center gap-1.5 text-[9px] font-bold uppercase tracking-wider text-concrete/50">
<Command size={10} />
<span>Memento Search</span>
<span>{t('searchModal.title')}</span>
</div>
</div>
</motion.div>