Compare commits
14
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6804e3626f
|
||
|
|
461b063cff
|
||
|
|
6601b00545
|
||
|
|
f88b0184ea
|
||
|
|
43964ec91d
|
||
|
|
ce19fa3b5d
|
||
|
|
33514bf633
|
||
|
|
35c39a97f3
|
||
|
|
72b86fdc5c
|
||
|
|
53287f9c80
|
||
|
|
8979970a90
|
||
|
|
75c5979478
|
||
|
|
5778d865e2
|
||
|
|
0ac18faf5c |
+1
-1
@@ -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.
|
||||
|
||||
@@ -24,6 +24,24 @@ cp .env.sample .env
|
||||
yarn && yarn dev
|
||||
```
|
||||
|
||||
## Commentaires fédérés (Mastodon / bokante.o-k-i.net)
|
||||
|
||||
Les commentaires sous une parole peuvent provenir d'un compte pawol.nu ou du Fediverse
|
||||
(réponses reçues sur le statut miroir publié sur bokante.o-k-i.net par le backend). Un
|
||||
bouton permet de répondre directement depuis son propre compte Mastodon, quelle que soit
|
||||
son instance, via `authorize_interaction` — aucun compte pawol.nu requis.
|
||||
|
||||
**Variables d'environnement :**
|
||||
|
||||
| Variable | Description |
|
||||
|---|---|
|
||||
| `NEXT_PUBLIC_BOKANTE_URL` | Instance Mastodon de l'organisation (défaut : `https://bokante.o-k-i.net`) |
|
||||
| `NEXT_PUBLIC_BOKANTE_ACCOUNT` | Compte bot dont les statuts servent de miroir (défaut : `pawol_nu`) |
|
||||
|
||||
Voir le RFC dédié pour le détail de la conception
|
||||
(`RFC-commentaires-activitypub-2026-07-04.md`, à la racine du dossier `PAWOL.NU`) et le
|
||||
`README.md` de `api.pawol.nu` pour la configuration côté backend.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (C) 2020 - 2026 Cédric Famibelle-Pronzola & ORGANISATION KA INTERNATIONALE (OKI)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
import {jwennToutAwtis} from '../../../lib/oki-api'
|
||||
|
||||
export async function GET() {
|
||||
const data = await jwennToutAwtis()
|
||||
return Response.json(data)
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -34,4 +34,18 @@ describe('Komante', () => {
|
||||
expect(screen.getByRole('link', {name: 'Quelqu\'un'})).toHaveAttribute('href', 'https://mastodon.social/@quelqun')
|
||||
expect(screen.getByRole('link', {name: /voir sur mastodon/i})).toHaveAttribute('href', 'https://bokante.o-k-i.net/@quelqun/1')
|
||||
})
|
||||
|
||||
it('met en valeur les mentions @ dans le contenu', () => {
|
||||
render(<Komante commentaires={[{
|
||||
id: 3,
|
||||
contenu: '@pawol_nu oh oh ye ye, avec @cybermawonaj@bokante.o-k-i.net aussi',
|
||||
origine: 'activitypub',
|
||||
auteurNom: 'Quelqu\'un'
|
||||
}]} />)
|
||||
|
||||
const mansyon = screen.getByText('@pawol_nu')
|
||||
expect(mansyon).toBeInTheDocument()
|
||||
expect(screen.getByText('@cybermawonaj@bokante.o-k-i.net')).toBeInTheDocument()
|
||||
expect(screen.getByText(/oh oh ye ye/)).toBeInTheDocument()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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}>
|
||||
|
||||
+75
-25
@@ -2,10 +2,12 @@
|
||||
|
||||
import PropTypes from 'prop-types'
|
||||
import Box from '@mui/material/Box'
|
||||
import Stack from '@mui/material/Stack'
|
||||
import Avatar from '@mui/material/Avatar'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Chip from '@mui/material/Chip'
|
||||
import Link from 'next/link'
|
||||
import {Mastodon} from '@icons-pack/react-simple-icons'
|
||||
|
||||
function formatAuteur(commentaire) {
|
||||
if (commentaire.origine === 'activitypub') {
|
||||
@@ -15,6 +17,29 @@ function formatAuteur(commentaire) {
|
||||
return commentaire.user?.username || 'Anonim'
|
||||
}
|
||||
|
||||
function formatDat(commentaire) {
|
||||
if (!commentaire.datePublication) {
|
||||
return null
|
||||
}
|
||||
|
||||
return new Intl.DateTimeFormat(undefined, {dateStyle: 'short', timeStyle: 'short'})
|
||||
.format(new Date(commentaire.datePublication))
|
||||
}
|
||||
|
||||
const MENTION_REGEX = /(?:@[\w.-]+){1,2}/g
|
||||
|
||||
function metanValèMansyon(contenu) {
|
||||
const mansyon = contenu.match(MENTION_REGEX) || []
|
||||
return contenu.split(MENTION_REGEX).flatMap((moso, index) => (
|
||||
mansyon[index] ? [
|
||||
moso,
|
||||
<Box key={index} component='span' sx={{color: 'primary.main', fontWeight: 700}}>
|
||||
{mansyon[index]}
|
||||
</Box>
|
||||
] : [moso]
|
||||
))
|
||||
}
|
||||
|
||||
export default function Komante({commentaires}) {
|
||||
if (!commentaires || commentaires.length === 0) {
|
||||
return null
|
||||
@@ -23,36 +48,61 @@ export default function Komante({commentaires}) {
|
||||
return (
|
||||
<Box sx={{maxWidth: 700, margin: '2em auto 0'}}>
|
||||
<Typography gutterBottom variant='h6'>
|
||||
Komantè
|
||||
Komantè ({commentaires.length})
|
||||
</Typography>
|
||||
{commentaires.map(commentaire => {
|
||||
const auteur = formatAuteur(commentaire)
|
||||
const estFedere = commentaire.origine === 'activitypub'
|
||||
<Stack spacing={1.5}>
|
||||
{commentaires.map(commentaire => {
|
||||
const auteur = formatAuteur(commentaire)
|
||||
const estFedere = commentaire.origine === 'activitypub'
|
||||
const dat = formatDat(commentaire)
|
||||
|
||||
return (
|
||||
<Box key={commentaire.id} sx={{display: 'flex', gap: 1.5, mb: 2}}>
|
||||
<Avatar src={commentaire.auteurAvatarUrl} alt={auteur} />
|
||||
<Box sx={{flex: 1}}>
|
||||
<Typography variant='subtitle2' component='div'>
|
||||
{estFedere && commentaire.auteurProfilUrl ? (
|
||||
<Link href={commentaire.auteurProfilUrl} target='_blank' rel='noopener noreferrer'>
|
||||
{auteur}
|
||||
return (
|
||||
<Box
|
||||
key={commentaire.id}
|
||||
sx={{
|
||||
display: 'flex',
|
||||
gap: 1.5,
|
||||
p: 2,
|
||||
borderRadius: 2,
|
||||
bgcolor: 'action.hover'
|
||||
}}
|
||||
>
|
||||
<Avatar src={commentaire.auteurAvatarUrl} alt={auteur} />
|
||||
<Box sx={{flex: 1, minWidth: 0}}>
|
||||
<Stack direction='row' alignItems='center' flexWrap='wrap' rowGap={0.5}>
|
||||
<Stack direction='row' alignItems='center' spacing={1}>
|
||||
<Typography variant='subtitle2' component='div'>
|
||||
{estFedere && commentaire.auteurProfilUrl ? (
|
||||
<Link href={commentaire.auteurProfilUrl} target='_blank' rel='noopener noreferrer'>
|
||||
{auteur}
|
||||
</Link>
|
||||
) : auteur}
|
||||
</Typography>
|
||||
{estFedere && (
|
||||
<Chip
|
||||
label='Mastodon'
|
||||
size='small'
|
||||
icon={<Mastodon size={14} color='#6364FF' title='' />}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
{dat && (
|
||||
<Typography variant='caption' color='text.secondary' sx={{ml: 'auto', pl: 2}}>
|
||||
{dat}
|
||||
</Typography>
|
||||
)}
|
||||
</Stack>
|
||||
<Typography variant='body2' sx={{mt: 0.5}}>{metanValèMansyon(commentaire.contenu)}</Typography>
|
||||
{estFedere && commentaire.remoteUrl && (
|
||||
<Link href={commentaire.remoteUrl} target='_blank' rel='noopener noreferrer'>
|
||||
<Typography variant='caption' color='text.secondary'>Voir sur Mastodon</Typography>
|
||||
</Link>
|
||||
) : auteur}
|
||||
{estFedere && (
|
||||
<Chip label='Mastodon' size='small' sx={{ml: 1}} />
|
||||
)}
|
||||
</Typography>
|
||||
<Typography variant='body2'>{commentaire.contenu}</Typography>
|
||||
{estFedere && commentaire.remoteUrl && (
|
||||
<Link href={commentaire.remoteUrl} target='_blank' rel='noopener noreferrer'>
|
||||
<Typography variant='caption' color='text.secondary'>Voir sur Mastodon</Typography>
|
||||
</Link>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
)
|
||||
})}
|
||||
)
|
||||
})}
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
+106
-46
@@ -3,8 +3,10 @@
|
||||
import {useEffect, useState} from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import Box from '@mui/material/Box'
|
||||
import Tabs from '@mui/material/Tabs'
|
||||
import Tab from '@mui/material/Tab'
|
||||
import Select from '@mui/material/Select'
|
||||
import MenuItem from '@mui/material/MenuItem'
|
||||
import ListSubheader from '@mui/material/ListSubheader'
|
||||
import FormControl from '@mui/material/FormControl'
|
||||
import Typography from '@mui/material/Typography'
|
||||
import Tooltip from '@mui/material/Tooltip'
|
||||
import {useMediaQuery} from '@mui/material'
|
||||
@@ -13,8 +15,8 @@ import slugify from 'slugify'
|
||||
|
||||
import {styled} from '@mui/material/styles'
|
||||
import ExplicitIcon from '@mui/icons-material/Explicit'
|
||||
import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'
|
||||
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'
|
||||
import TranslateIcon from '@mui/icons-material/Translate'
|
||||
import StarIcon from '@mui/icons-material/Star'
|
||||
|
||||
import Image from 'next/image'
|
||||
|
||||
@@ -91,15 +93,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 => {
|
||||
@@ -109,7 +130,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
|
||||
@@ -136,6 +165,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}}>
|
||||
@@ -230,39 +273,56 @@ export default function Teks({parole}) {
|
||||
)}
|
||||
</Box>
|
||||
<Box sx={{maxWidth: 700, margin: '0 auto'}} className={classes.gridText}>
|
||||
<Tabs
|
||||
scrollButtons
|
||||
allowScrollButtonsMobile
|
||||
value={tab}
|
||||
variant='scrollable'
|
||||
centered={langArray.length === 0}
|
||||
slots={{
|
||||
startScrollButtonIcon: ArrowBackIosNewIcon,
|
||||
endScrollButtonIcon: ArrowForwardIosIcon
|
||||
}}
|
||||
sx={{
|
||||
borderBottom: 1,
|
||||
borderColor: 'divider',
|
||||
mb: 2,
|
||||
'& .MuiTabs-scrollButtons': {
|
||||
width: 36,
|
||||
height: 36,
|
||||
borderRadius: '50%',
|
||||
color: 'primary.main',
|
||||
backgroundColor: 'action.hover',
|
||||
mx: 0.5,
|
||||
'&.Mui-disabled': {
|
||||
opacity: 0
|
||||
}
|
||||
}
|
||||
}}
|
||||
onChange={(event, value) => setTab(value)}
|
||||
>
|
||||
<Tab label='Transcription' />
|
||||
{langArray.map(({title}) => (
|
||||
<Tab key={title} label={title} />
|
||||
))}
|
||||
</Tabs>
|
||||
<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
|
||||
return (
|
||||
<Box sx={{display: 'flex', alignItems: 'center', gap: 1}}>
|
||||
<TranslateIcon fontSize='small' color='primary' />
|
||||
{label}
|
||||
</Box>
|
||||
)
|
||||
}}
|
||||
onChange={event => setTab(event.target.value)}
|
||||
>
|
||||
<MenuItem value={0}>Transcription</MenuItem>
|
||||
{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 && (
|
||||
<>
|
||||
{parole.langueSource && parole.langueSource !== 'ka' && (
|
||||
|
||||
+1
-1
@@ -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},
|
||||
|
||||
Reference in New Issue
Block a user