8 Commits
Author SHA1 Message Date
cedric 5936de59ff fix: mettre en valeur l'option Transcription dans le select
Déploiement FRONT BETA / check (push) Successful in 2m7s
Vérification PR / check (pull_request) Successful in 2m11s
Déploiement FRONT BETA / deploy (push) Successful in 20s
Vérification PR / deploy-beta (pull_request) Successful in 19s
2026-07-06 02:01:44 +04:00
cedric 6804e3626f feat: remplacer les puces par un select groupé par continent
Déploiement FRONT BETA / check (push) Successful in 2m14s
Vérification PR / check (pull_request) Successful in 2m14s
Déploiement FRONT BETA / deploy (push) Successful in 23s
Vérification PR / deploy-beta (pull_request) Successful in 20s
2026-07-06 00:18:34 +04:00
cedric 461b063cff feat: regrouper les langues par continent, Afrique en tête 2026-07-06 00:10:34 +04:00
cedric 6601b00545 fix: ne plus exposer le jeton API au navigateur (recherche d'artiste)
Déploiement FRONT PROD / check (push) Successful in 2m13s
Déploiement FRONT PROD / deploy (push) Successful in 20s
Déploiement FRONT BETA / check (push) Successful in 2m6s
Déploiement FRONT BETA / deploy (push) Successful in 20s
2026-07-05 23:16:19 +04:00
cedric f88b0184ea feat: sélectionner la traduction par défaut selon la langue du navigateur
Déploiement FRONT BETA / check (push) Successful in 2m12s
Déploiement FRONT PROD / check (push) Successful in 2m14s
Déploiement FRONT BETA / deploy (push) Successful in 21s
Déploiement FRONT PROD / deploy (push) Successful in 20s
2026-07-05 22:33:09 +04:00
cedric 43964ec91d feat: afficher les langues asiatiques avant les langues européennes 2026-07-05 22:28:57 +04:00
cedric ce19fa3b5d fix: remplacer le libellé de date par une icône universelle
Déploiement FRONT BETA / check (push) Successful in 2m10s
Déploiement FRONT PROD / check (push) Successful in 2m5s
Déploiement FRONT BETA / deploy (push) Successful in 20s
Déploiement FRONT PROD / deploy (push) Successful in 19s
2026-07-05 20:54:14 +04:00
cedric 33514bf633 feat: généraliser le formatage de date adaptatif à la locale 2026-07-05 20:47:52 +04:00
8 changed files with 156 additions and 36 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ API_URL=http://localhost:1337/api
NEXT_PUBLIC_API_URL=$API_URL
NEXT_PUBLIC_API_URL_ROOT=http://localhost:1337
SITE_URL=http://localhost:3001
NEXT_PUBLIC_READ_TOKEN=
API_READ_TOKEN=
# IDENTITÉ DU SITE (branding)
NEXT_PUBLIC_SITE_NAME=PAWÒL-NU. Paroles et traductions.
+23
View File
@@ -0,0 +1,23 @@
import {describe, it, expect, vi, afterEach} from 'vitest'
import {jwennToutAwtis} from '../../../../lib/oki-api'
import {GET} from '../route'
vi.mock('../../../../lib/oki-api', () => ({
jwennToutAwtis: vi.fn()
}))
describe('GET /api/awtis', () => {
afterEach(() => {
vi.restoreAllMocks()
})
it('renvoie les données de jwennToutAwtis en JSON, sans exposer de jeton', async () => {
jwennToutAwtis.mockResolvedValue({data: [{id: 1, alias: 'Foo'}], meta: {}})
const response = await GET()
const body = await response.json()
expect(body).toEqual({data: [{id: 1, alias: 'Foo'}], meta: {}})
expect(JSON.stringify(body)).not.toMatch(/bearer|token/i)
})
})
+6
View File
@@ -0,0 +1,6 @@
import {jwennToutAwtis} from '../../../lib/oki-api'
export async function GET() {
const data = await jwennToutAwtis()
return Response.json(data)
}
+1 -2
View File
@@ -7,7 +7,6 @@ import Autocomplete from '@mui/material/Autocomplete'
import Avatar from '@mui/material/Avatar'
import Container from '@mui/material/Container'
import {jwennToutAwtis} from '../../lib/oki-api'
import {formatKuveti} from '../../lib/kuveti'
const IMAGE_URL = process.env.NEXT_PUBLIC_API_URL_ROOT || 'http://localhost:1337'
@@ -27,7 +26,7 @@ export default function ChecheAwtis() {
(async () => {
try {
const {data} = await jwennToutAwtis()
const {data} = await fetch('/api/awtis').then(response => response.json())
const filteredData = data.map(artiste => {
const firstLetter = artiste.alias[0].toUpperCase()
+1 -4
View File
@@ -1,9 +1,6 @@
import React, {forwardRef, useState} from 'react'
import PropTypes from 'prop-types'
import {format} from 'date-fns'
import {fr} from 'date-fns/locale'
import Button from '@mui/material/Button'
import Dialog from '@mui/material/Dialog'
import DialogTitle from '@mui/material/DialogTitle'
@@ -50,7 +47,7 @@ export default function DiferansDialog({difference}) {
<List sx={{width: '100%', maxWidth: 360, bgcolor: 'background.paper'}}>
{difference.map(({id, admin_user, date, jsonDiff}) => {
const {firstname} = admin_user
const diferansDate = format(new Date(date), 'PPPppp', {locale: fr})
const diferansDate = new Intl.DateTimeFormat(undefined, {dateStyle: 'full', timeStyle: 'medium'}).format(new Date(date))
return (
<React.Fragment key={id}>
+11 -5
View File
@@ -3,8 +3,6 @@
import PropTypes from 'prop-types'
import {useRouter} from 'next/navigation'
import Image from 'next/image'
import {format} from 'date-fns'
import {fr} from 'date-fns/locale'
import Card from '@mui/material/Card'
import CardActionArea from '@mui/material/CardActionArea'
@@ -13,6 +11,7 @@ import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
import Grid from '@mui/material/Grid'
import ExplicitIcon from '@mui/icons-material/Explicit'
import EventIcon from '@mui/icons-material/Event'
import {styled} from '@mui/material/styles'
import {getAlias} from '../../lib/utils/get-alias'
@@ -37,7 +36,7 @@ export default function TeksKat({parole}) {
const router = useRouter()
const {titre, artistes, annee, couverture, createdAt, slug} = parole
const datPiblikasyon = format(new Date(createdAt), 'P', {locale: fr})
const datPiblikasyon = new Intl.DateTimeFormat(undefined, {dateStyle: 'short'}).format(new Date(createdAt))
const aliases = getAlias(artistes, parole.prioriteArtistes)
const handleClick = slug => {
@@ -103,8 +102,15 @@ export default function TeksKat({parole}) {
</>
)}
</Typography>
<Typography align='center' style={{marginTop: '0.5em'}} variant='body1' color='textSecondary' component='p'>
Publiée le : {datPiblikasyon}
<Typography
align='center'
style={{marginTop: '0.5em'}}
variant='body1'
color='textSecondary'
component='p'
sx={{display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 0.5}}
>
<EventIcon fontSize='inherit' /> {datPiblikasyon}
</Typography>
</Box>
</CardContent>
+112 -23
View File
@@ -3,7 +3,11 @@
import {useEffect, useState} from 'react'
import PropTypes from 'prop-types'
import Box from '@mui/material/Box'
import Chip from '@mui/material/Chip'
import Select from '@mui/material/Select'
import MenuItem from '@mui/material/MenuItem'
import ListSubheader from '@mui/material/ListSubheader'
import Divider from '@mui/material/Divider'
import FormControl from '@mui/material/FormControl'
import Typography from '@mui/material/Typography'
import Tooltip from '@mui/material/Tooltip'
import {useMediaQuery} from '@mui/material'
@@ -12,6 +16,9 @@ import slugify from 'slugify'
import {styled} from '@mui/material/styles'
import ExplicitIcon from '@mui/icons-material/Explicit'
import TranslateIcon from '@mui/icons-material/Translate'
import StarIcon from '@mui/icons-material/Star'
import ArticleIcon from '@mui/icons-material/Article'
import Image from 'next/image'
@@ -88,15 +95,34 @@ const LANG_NAMES = {
pt: 'Português', ja: '日本語', ko: '한국어',
}
const CONTINENTS = ['Afrique', 'Asie', 'Europe']
const TRAD_FIELDS = [
{field: 'anglais', title: 'English'},
{field: 'francais', title: 'Français'},
{field: 'espagnol', title: 'Español'},
{field: 'allemand', title: 'Deutsch'},
{field: 'italien', title: 'Italiano'},
{field: 'portugais', title: 'Português'},
{field: 'japonais', title: '日本語'},
{field: 'coreen', title: '한국어'},
// Afrique — langues vedettes en premier
{field: 'yoruba', title: 'Yorùbá', continent: 'Afrique', vedette: true},
{field: 'lingala', title: 'Lingála', continent: 'Afrique', vedette: true},
{field: 'wolof', title: 'Wolof', continent: 'Afrique', vedette: true},
{field: 'swahili', title: 'Kiswahili', continent: 'Afrique', vedette: true},
{field: 'hausa', title: 'Hausa', continent: 'Afrique', vedette: true},
{field: 'arabe', title: 'العربية', continent: 'Afrique'},
{field: 'oromo', title: 'Oromoo', continent: 'Afrique'},
{field: 'igbo', title: 'Igbo', continent: 'Afrique'},
{field: 'zoulou', title: 'isiZulu', continent: 'Afrique'},
{field: 'malgache', title: 'Malagasy', continent: 'Afrique'},
{field: 'xhosa', title: 'isiXhosa', continent: 'Afrique'},
{field: 'tswana', title: 'Setswana', continent: 'Afrique'},
{field: 'tsonga', title: 'Xitsonga', continent: 'Afrique'},
{field: 'sesotho', title: 'Sesotho', continent: 'Afrique'},
// Asie
{field: 'japonais', title: '日本語', continent: 'Asie'},
{field: 'coreen', title: '한국어', continent: 'Asie'},
// Europe
{field: 'anglais', title: 'English', continent: 'Europe'},
{field: 'francais', title: 'Français', continent: 'Europe'},
{field: 'espagnol', title: 'Español', continent: 'Europe'},
{field: 'allemand', title: 'Deutsch', continent: 'Europe'},
{field: 'italien', title: 'Italiano', continent: 'Europe'},
{field: 'portugais', title: 'Português', continent: 'Europe'},
]
const langToArray = parole => {
@@ -106,7 +132,15 @@ const langToArray = parole => {
return TRAD_FIELDS
.filter(({field}) => parole.traductions[field])
.map(({field, title}) => ({title, lang: parole.traductions[field]}))
.map(({field, title, continent, vedette}) => ({field, title, continent, vedette, lang: parole.traductions[field]}))
}
const BROWSER_LANG_TO_FIELD = {
yo: 'yoruba', ln: 'lingala', wo: 'wolof', sw: 'swahili', ha: 'hausa',
ar: 'arabe', om: 'oromo', ig: 'igbo', zu: 'zoulou', mg: 'malgache',
xh: 'xhosa', tn: 'tswana', ts: 'tsonga', st: 'sesotho',
ja: 'japonais', ko: 'coreen', en: 'anglais', fr: 'francais',
es: 'espagnol', de: 'allemand', it: 'italien', pt: 'portugais',
}
const ExplicitTooltip = Tooltip
@@ -118,7 +152,6 @@ export default function Teks({parole}) {
const coverFmt = formatKuveti(parole.couverture)
const [tab, setTab] = useState(0)
const [, setCurrentTrack] = useCurrentTrack()
const options = ['Transcription', ...langArray.map(({title}) => title)]
useEffect(() => {
const isBrowser = () => typeof window !== 'undefined'
@@ -134,6 +167,20 @@ export default function Teks({parole}) {
return () => setCurrentTrack(null)
}, [parole, setCurrentTrack])
useEffect(() => {
const browserLang = navigator.language?.split('-')[0]?.toLowerCase()
if (!browserLang || browserLang === parole.langueSource) {
return
}
const champ = BROWSER_LANG_TO_FIELD[browserLang]
const index = langArray.findIndex(lang => lang.field === champ)
if (index !== -1) {
setTab(index + 1)
}
// eslint-disable-next-line react-hooks/exhaustive-deps -- ne doit s'exécuter qu'au montage, pas à chaque changement d'onglet
}, [])
return (
<Root className={classes.container}>
<Box sx={{textAlign: 'center', marginTop: 12}}>
@@ -228,18 +275,60 @@ export default function Teks({parole}) {
)}
</Box>
<Box sx={{maxWidth: 700, margin: '0 auto'}} className={classes.gridText}>
<Box sx={{display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: 1, borderBottom: 1, borderColor: 'divider', mb: 2, pb: 2}}>
{options.map((label, index) => (
<Chip
key={label}
clickable
color='primary'
label={label}
sx={{fontWeight: tab === index ? 600 : 400}}
variant={tab === index ? 'filled' : 'outlined'}
onClick={() => setTab(index)}
/>
))}
<Box sx={{display: 'flex', justifyContent: 'center', borderBottom: 1, borderColor: 'divider', mb: 2, pb: 2}}>
<FormControl size='small' sx={{minWidth: 240, width: isMobile ? '100%' : 'auto'}}>
<Select
value={tab}
MenuProps={{PaperProps: {sx: {maxHeight: 420}}}}
renderValue={value => {
const label = value === 0 ? 'Transcription' : langArray[value - 1]?.title
const Icon = value === 0 ? ArticleIcon : TranslateIcon
return (
<Box sx={{display: 'flex', alignItems: 'center', gap: 1, fontWeight: value === 0 ? 700 : 400}}>
<Icon fontSize='small' color='primary' />
{label}
</Box>
)
}}
onChange={event => setTab(event.target.value)}
>
<MenuItem value={0} sx={{display: 'flex', gap: 0.75, fontWeight: 700}}>
<ArticleIcon fontSize='inherit' color='primary' />
Transcription
</MenuItem>
<Divider />
{CONTINENTS.flatMap(continent => {
const items = langArray
.map((item, index) => ({...item, index}))
.filter(item => item.continent === continent)
if (items.length === 0) {
return []
}
return [
<ListSubheader
key={`entet-${continent}`}
sx={{
fontWeight: 700,
textTransform: 'uppercase',
letterSpacing: 1,
color: 'primary.main',
lineHeight: '2.5em'
}}
>
{continent}
</ListSubheader>,
...items.map(({title, index, vedette}) => (
<MenuItem key={title} value={index + 1} sx={{display: 'flex', gap: 0.75}}>
{vedette && <StarIcon fontSize='inherit' color='primary' />}
{title}
</MenuItem>
))
]
})}
</Select>
</FormControl>
</Box>
{tab === 0 && (
<>
+1 -1
View File
@@ -2,7 +2,7 @@ import qs from 'qs'
const OKI_API = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:1337'
const AWTIS_POU_CHAK_PAJ = process.env.NEXT_PUBLIC_AWTIS_POU_CHAK_PAJ || 6
const readToken = process.env.NEXT_PUBLIC_READ_TOKEN || 'read-token'
const readToken = process.env.API_READ_TOKEN || 'read-token'
const headers = {
next: {revalidate: 60},