chore: supprimer le code mort (soumission, connexion, stats, CGU)

This commit is contained in:
2026-07-04 11:46:46 +04:00
parent 4c662e89b5
commit c7e3faf10a
21 changed files with 0 additions and 2968 deletions
-5
View File
@@ -4,7 +4,6 @@ NEXT_PUBLIC_API_URL=$API_URL
NEXT_PUBLIC_API_URL_ROOT=http://localhost:1337
SITE_URL=http://localhost:3001
NEXT_PUBLIC_READ_TOKEN=
NEXT_PUBLIC_ADMIN_JWT_SECRET=
# IDENTITÉ DU SITE (branding)
NEXT_PUBLIC_SITE_NAME=PAWÒL-NU. Paroles et traductions.
@@ -77,7 +76,6 @@ NEXT_PUBLIC_LIBERAPAY_DONATE=
NEXT_PUBLIC_PALE_USERNAME=
NEXT_PUBLIC_GADE_USERNAME=
NEXT_PUBLIC_YOUTUBE_USERNAME=
NEXT_PUBLIC_TELEGRAM_GROUP=
NEXT_PUBLIC_XMPP=
NEXT_PUBLIC_GIT=
NEXT_PUBLIC_CODEBERG=
@@ -86,9 +84,6 @@ NEXT_PUBLIC_BLUESKY_URL=
# DOMAIN IMAGE
NEXT_PUBLIC_DOMAINS_IMAGE="localhost:1337 strapi.mondomaine.com"
# JWT SECRET
NEXT_PUBLIC_JWT_SECRET=
# STRIPE
NEXT_PUBLIC_STRIPE_PUBLIC_KEY=
-17
View File
@@ -1,17 +0,0 @@
'use client'
import Proptypes from 'prop-types'
import {SessionProvider} from 'next-auth/react'
export default function AuthProvider({children, session}) {
return (
<SessionProvider session={session}>
{children}
</SessionProvider>
)
}
AuthProvider.propTypes = {
children: Proptypes.node,
session: Proptypes.object
}
-93
View File
@@ -1,93 +0,0 @@
import {useRef, useEffect} from 'react'
import {styled, useTheme} from '@mui/material/styles'
import PropTypes from 'prop-types'
import {Button, Dialog, DialogActions, DialogContent, DialogTitle, Typography} from '@mui/material'
import useMediaQuery from '@mui/material/useMediaQuery'
import Cgu from '.'
const PREFIX = 'cgu-dialog'
const classes = {
dialog: `${PREFIX}-dialog`
}
const Root = styled('div')(() => ({
[`& .${classes.dialog}`]: {
zIndex: 1
}
}))
const CGU_DOWNLOAD_LINK = process.env.NEXT_PUBLIC_CGU_DOWNLOAD_LINK
export default function CGUDialog({open, setOpen}) {
const theme = useTheme()
const fullScreen = useMediaQuery(theme.breakpoints.down('md'))
const handleClose = () => {
setOpen(false)
}
const descriptionElementRef = useRef(null)
useEffect(() => {
if (open) {
const {current: descriptionElement} = descriptionElementRef
if (descriptionElement !== null) {
descriptionElement.focus()
}
}
}, [open])
return (
<Root>
<Dialog
fullScreen={fullScreen}
open={open}
scroll='paper'
aria-labelledby='scroll-dialog-title'
aria-describedby='scroll-dialog-description'
className={classes.dialog}
onClose={handleClose}
>
<DialogTitle className={classes.dialog} id='scroll-dialog-title'>CGU et politique de confidentialité</DialogTitle>
<DialogContent dividers>
<Typography
ref={descriptionElementRef}
component='div'
variant='inherit'
id='scroll-dialog-description'
tabIndex={-1}
>
{CGU_DOWNLOAD_LINK ? (
<div style={{textAlign: 'center', padding: '2rem'}}>
<Typography paragraph>
Consultez nos CGU et notre politique de confidentialité :
</Typography>
<Button
variant='contained'
component='a'
href={CGU_DOWNLOAD_LINK}
target='_blank'
rel='noopener noreferrer'
>
Consulter les CGU
</Button>
</div>
) : (
<Cgu />
)}
</Typography>
</DialogContent>
<DialogActions>
<Button color='primary' onClick={handleClose}>
Fermer
</Button>
</DialogActions>
</Dialog>
</Root>
)
}
CGUDialog.propTypes = {
open: PropTypes.bool.isRequired,
setOpen: PropTypes.func.isRequired
}
-592
View File
@@ -1,592 +0,0 @@
import {List, ListItem, ListItemText, Link, Typography, ListItemIcon} from '@mui/material'
import {styled} from '@mui/material/styles'
import ArrowRightAltIcon from '@mui/icons-material/ArrowRightAlt'
const PREFIX = 'index'
const classes = {
root: `${PREFIX}-root`
}
const Root = styled('div')({
[`&.${classes.root}`]: {
textAlign: 'justify'
}
})
export default function Cgu() {
return (
<Root className={classes.root}>
<Typography gutterBottom variant='h5' component='h2'>
Définitions
</Typography>
<Typography paragraph='true' variant='body1'>
<strong>Prestations et Services </strong>: <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> met à disposition :
</Typography>
<Typography paragraph='true' variant='body1'>
<strong>Contenu </strong>: Ensemble des éléments constituants linformation présente sur le site, notamment textes images vidéos.
</Typography>
<Typography paragraph='true' variant='body1'>
<strong>Informations utilisateurs </strong>: Ci après dénommé « Information (s) »
qui correspondent à lensemble des données personnelles susceptibles d’être détenues par
<Link href='https://pawol.nu'> <strong>pawol.nu</strong></Link> pour la gestion de votre compte, et à des fins danalyses et de statistiques.
</Typography>
<Typography paragraph='true' variant='body1'>
<strong>Utilisateur </strong>: Internaute, utilisant le site susnommé.
</Typography>
<Typography paragraph='true' variant='body1'>
<strong>Utilisateur enregistré</strong>: Internaute ayant compte utilisateur, utilisant le site susnommé.
</Typography>
<Typography variant='body1'>
<strong>Informations personnelles </strong>: « Les informations qui permettent, sous quelque forme que ce soit, directement ou non,
lidentification des personnes physiques auxquelles elles sappliquent » (article 4 de la loi n° 78-17 du 6 janvier 1978).
</Typography>
<Typography paragraph='true' variant='body1'>
Les termes « données à caractère personnel », « personne concernée », « sous traitant » et « données sensibles » ont le sens défini par le Règlement
Général sur la Protection des Données (RGPD : n° 2016-679)
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
1. Présentation du site internet
</Typography>
<Typography paragraph='true' variant='body1'>
En vertu de larticle 6 de la loi n° 2004-575 du 21 juin 2004 pour la confiance dans léconomie numérique, il est précisé aux utilisateurs du site internet
<Link href='https://pawol.nu'> <strong>pawol.nu</strong></Link> lidentité des différents intervenants dans le cadre de sa réalisation et de son suivi :
</Typography>
<Typography paragraph='true' variant='body1'>
<strong>Propriétaire</strong> : ORGANISATION KA INTERNATIONALE (Association loi 1901)
</Typography>
<Typography paragraph='true' variant='body1'>
<strong>Responsable publication</strong> : ORGANISATION KA INTERNATIONALE <Link href='mailto:kontak@o-k-i.net'><strong>kontak@o-k-i.net</strong></Link>
</Typography>
<Typography paragraph='true' variant='body1'>
Le responsable publication est une personne morale
</Typography>
<Typography variant='body1'>
<strong>Réalisation</strong> : Cédric Pronzola
</Typography>
<List>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
<strong>Courriel</strong> : <Link href='mailto:contact@cedric-pronzola.dev'><strong>contact@cedric-pronzola.dev</strong></Link>
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
<strong>WEB</strong> : <Link target='_blank' rel='noreferrer' href='https://cedric-pronzola.dev'><strong>cedric-pronzola.dev</strong></Link>
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
<strong>Git</strong> : <Link target='_blank' rel='noreferrer' href='https://labola.o-k-i.net/ORGANISATION-KA-INTERNATIONALE'><strong>LABOLA - OKI</strong></Link>
</ListItemText>
</ListItem>
</List>
<Typography variant='body1'>
<strong>Hébergeur</strong> : OVH 2 rue Kellermann - 59100 Roubaix - France
</Typography>
<Typography gutterBottom variant='body1'>
<strong>Délégué à la protection des données</strong> : ORGANISATION KA INTERNATIONALE <Link href='mailto:kontak@o-k-i.net'><strong>kontak@o-k-i.net</strong></Link>
</Typography>
<Typography paragraph='true' variant='body1'>
Ces mentions légales RGPD sont issues du <Link target='_blank' rel='noreferrer' href='https://fr.orson.io/1371/generateur-mentions-legales'>générateur gratuit de mentions légales pour un site internet (orson.io)</Link>.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
2. Conditions générales dutilisation du site et des services proposés.
</Typography>
<Typography paragraph='true' variant='body1'>
Lutilisation du site <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> implique lacceptation pleine et entière des conditions générales dutilisation ci-après décrites.
Ces conditions dutilisation sont susceptibles dêtre modifiées ou complétées à tout moment, les utilisateurs du site <Link href='https://pawol.nu'><strong>pawol.nu </strong></Link>
sont donc invités à les consulter de manière régulière.
</Typography>
<Typography paragraph='true' variant='body1'>
Ce site internet est normalement accessible à tout moment aux utilisateurs. Une interruption pour raison de maintenance technique peut être toutefois
décidée par <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link>, qui sefforcera alors de communiquer préalablement aux utilisateurs les dates et heures de lintervention.
Le site web <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> est mis à jour régulièrement par <strong>ORGANISATION KA INTERNATIONALE</strong>. De la même façon, les mentions légales peuvent être
modifiées à tout moment : elles simposent néanmoins à lutilisateur qui est invité à sy référer le plus souvent possible afin den prendre connaissance.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
3. Description des services fournis
</Typography>
<Typography paragraph='true' variant='body1'>
<Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> sefforce de fournir sur le site, des informations
aussi précises que possible. Toutefois, il ne pourra être tenu responsable des oublis, des inexactitudes et des carences dans la mise à jour, quelles soient de son fait ou du fait
des tiers partenaires qui lui fournissent ces informations.
</Typography>
<Typography paragraph='true' variant='body1'>
Toutes les informations indiquées sur le site <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> sont données à titre indicatif,
et sont susceptibles dévoluer. Par ailleurs, les renseignements figurant sur le site <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> ne sont pas exhaustifs.
Ils sont donnés sous réserve de modifications ayant été apportées depuis leur mise en ligne.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
4. Limitations contractuelles sur les données techniques
</Typography>
<Typography paragraph='true' variant='body1'>
Le site utilise la technologie JavaScript.
Le site Internet ne pourra être tenu responsable de dommages matériels liés à lutilisation du site. De plus, lutilisateur du site sengage à accéder au site en utilisant un matériel récent,
ne contenant pas de virus et avec un navigateur de dernière génération mis-à-jour.
Le site <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> est hébergé chez un prestataire sur le territoire de lUnion Européenne conformément aux dispositions du
Règlement Général sur la Protection des Données (RGPD : n° 2016-679).
</Typography>
<Typography paragraph='true' variant='body1'>
Lobjectif est dapporter une prestation qui assure le meilleur taux daccessibilité.
Lhébergeur assure la continuité de son service 24 Heures sur 24, tous les jours de lannée. Il se réserve néanmoins la possibilité dinterrompre le service dhébergement
pour les durées les plus courtes possibles notamment à des fins de maintenance, damélioration de ses infrastructures, de défaillance de ses infrastructures ou si les
Prestations et Services génèrent un trafic réputé anormal.
</Typography>
<Typography paragraph='true' variant='body1'>
<Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> et lhébergeur ne pourront être tenus responsables en cas de dysfonctionnement du réseau Internet,
des lignes téléphoniques ou du matériel informatique et de téléphonie lié notamment à lencombrement du réseau empêchant laccès au serveur.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
5. Limitations de responsabilité
</Typography>
<Typography paragraph='true' variant='body1'>
<Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> agit en tant quéditeur du site. <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> est responsable
de la qualité et de la véracité du Contenu quil publie.
</Typography>
<Typography paragraph='true' variant='body1'>
<Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> ne pourra être tenu responsable des dommages directs et indirects causés au matériel de lutilisateur,
lors de laccès au site internet <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link>, et résultant soit de lutilisation dun matériel
ne répondant pas aux spécifications indiquées au point 4, soit de lapparition dun bug ou dune incompatibilité.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
6. Gestion des données personnelles
</Typography>
<Typography gutterBottom variant='h6' component='h3'>
6.1 Responsables de la collecte des données personnelles
</Typography>
<Typography paragraph='true' variant='body1'>
Pour les données personnelles collectées dans le cadre de la création du compte personnel de lutilisateur et de sa navigation sur le site
le responsable du traitement des données personnelles est : ORGANISATION KA INTERNATIONALE. <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link>.
</Typography>
<Typography paragraph='true' variant='body1'>
En tant que responsable du traitement des données quil collecte, <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> sengage à respecter le cadre des dispositions légales en vigueur.
Chaque fois que <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> traite des Données Personnelles, <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> prend toutes les mesures raisonnables
pour sassurer de lexactitude et de la pertinence des Données Personnelles au regard des finalités pour lesquelles <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> les traite.
</Typography>
<Typography gutterBottom variant='h6' component='h3'>
6.2 Finalité des données collectées
</Typography>
<Typography variant='body1'>
<Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> est susceptible de traiter tout ou partie des données :
</Typography>
<List>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
pour permettre la navigation sur le site et la gestion et la traçabilité des prestations et services commandés par lutilisateur : données de connexion et dutilisation du site
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
pour prévenir et lutter contre la fraude informatique (spamming, hacking) : matériel informatique utilisé pour la navigation, ladresse IP, le mot de passe (hashé)
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
pour améliorer la navigation sur le site : données de connexion et dutilisation
</ListItemText>
</ListItem>
</List>
<Typography gutterBottom variant='h6' component='h3'>
6.3 Droit daccès, de rectification et dopposition
</Typography>
<Typography variant='body1'>
Conformément à la réglementation européenne en vigueur, les Utilisateurs de <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> disposent des droits suivants :
</Typography>
<List>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
droit daccès (article 15 RGPD) et de rectification (article 16 RGPD), de mise à jour, de complétude des données des Utilisateurs droit de verrouillage ou deffacement
des données des Utilisateurs à caractère personnel (article 17 du RGPD), lorsquelles sont inexactes, incomplètes, équivoques, périmées, ou dont la collecte, lutilisation,
la communication ou la conservation est interdite
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
droit de retirer à tout moment un consentement (article 13-2c RGPD)
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
droit à la limitation du traitement des données des Utilisateurs (article 18 RGPD)
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
droit à la portabilité des données que les Utilisateurs auront fournies, lorsque ces données font lobjet de traitements automatisés fondés sur leur consentement ou sur un contrat (article 20 RGPD)
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
droit de définir le sort des données des Utilisateurs après leur mort et de choisir à qui <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> devra communiquer (ou non)
ses données à un tiers quils aura préalablement désigné
</ListItemText>
</ListItem>
</List>
<Typography paragraph='true' variant='body1'>
Dès que <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> a connaissance du décès dun Utilisateur et à défaut dinstructions de sa part, <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> sengage à détruire
ses données, sauf si leur conservation savère nécessaire à des fins probatoires ou pour répondre à une obligation légale.
</Typography>
<Typography paragraph='true' variant='body1'>
Si lUtilisateur souhaite savoir comment <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> utilise ses Données Personnelles, demander à les rectifier ou soppose à leur traitement,
lUtilisateur peut contacter <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> par courriel à ladresse suivante, <Link href='mailto:kontak@o-k-i.net'><strong>kontak@o-k-i.net</strong></Link> ou via XMPP à <Link href='xmpp:oki@xmpp.cz'><strong>oki@xmpp.cz</strong></Link>.
</Typography>
<Typography paragraph='true' variant='body1'>
Dans ce cas, lUtilisateur doit indiquer les Données Personnelles quil souhaiterait que <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> corrige,
mette à jour ou supprime, en sidentifiant précisément avec une copie dune pièce didentité (carte didentité ou passeport).
</Typography>
<Typography paragraph='true' variant='body1'>
Les demandes de suppression de Données Personnelles seront soumises aux obligations qui sont imposées à <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> par la loi,
notamment en matière de conservation ou darchivage des documents. Enfin, les Utilisateurs de <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> peuvent déposer une réclamation
auprès des autorités de contrôle, et notamment de la CNIL (https://www.cnil.fr/fr/plaintes).
</Typography>
<Typography gutterBottom variant='h6' component='h3'>
6.4 Non-communication des données personnelles
</Typography>
<Typography paragraph='true' variant='body1'>
<Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> sinterdit de traiter, héberger ou transférer les Informations collectées sur ses utilisateurs vers un pays situé en dehors de
lUnion européenne ou reconnu comme « non adéquat » par la Commission européenne sans en informer préalablement lutilisateur. Pour autant, <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> reste
libre du choix de ses sous-traitants techniques et commerciaux à la condition quil présentent les garanties suffisantes au regard des exigences du Règlement Général sur la Protection des
Données (RGPD : n° 2016-679).
</Typography>
<Typography paragraph='true' variant='body1'>
<Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> sengage à prendre toutes les précautions nécessaires afin de préserver la sécurité des Informations et notamment quelles ne soient pas communiquées
à des personnes non autorisées. Cependant, si un incident impactant lintégrité ou la confidentialité des Informations de lutilisateur est portée
à la connaissance de <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link>, celle-ci devra dans les meilleurs délais informer lutilisateur
et lui communiquer les mesures de corrections prises. Par ailleurs <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> ne collecte aucune « données sensibles ».
</Typography>
<Typography gutterBottom variant='h6' component='h3'>
6.5 Inscription et connexion
</Typography>
<Typography paragraph='true' variant='body1'>
Les utilisateurs peuvent sinscrire sur <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> en renseignant leur adresse e-mail, un nom dutilisateur ainsi quun mot de passe.
À la suite de linscription, un lien de vérification est envoyé à ladresse utilisée. Ce lien permet dactiver le compte. Sans cette activation, lutilisateur naura pas la possibilité de se connecter au site.
Si un utilisateur na pas reçu le lien dactivation, il a la possibilité de demander son renvoi.
Lutilisateur ayant oublié son mot de passe peut faire la demande de renouvellement de celui-ci.
Avant toute inscription, il est important de noté que le nom dutilisateur est public et donc accessible à tous. En effet, le site fait appel à une API qui permet la consultation de plusieurs éléments, dont lidentifiant de lutilisateur ainsi que le nom quil aura choisi au moment de linscription.
Cependant, ladresse e-mail ne figure pas dans les informations accessibles. Seul <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> en à connaissance.
Les utilisateurs peuvent également se connecter en utilisant un compte Twitter. Dans ce cas, cest lidentifiant de cette plateforme qui sera affiché en cas de contribution.
Il est également possible de se connecter grâce à un compte Google.
En acceptant dutiliser la fonctionnalité de connexion via Google ou Twitter, vous autorisez <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> à récupérer les informations liées à votre profil, que vous avez transmises à ces plateformes lors de votre inscription.
Un compte GitHub permet désormais de se connecter au site. Nous utilisons uniquement le nom dutilisateur, ainsi que ladresse e-mail.
</Typography>
<Typography gutterBottom variant='h6' component='h3'>
6.6 Proposition de paroles
</Typography>
<Typography paragraph='true' variant='body1'>
Les utilisateurs connectés ont la possibilité de proposer des paroles depuis un espace dédié.
Il se trouve à ladresse <Link href='https://pawol.nu/pwopose'><strong>pawol.nu/pwopose</strong></Link>.
Les textes soumis ne sont pas directement publiés sur le site. Ils doivent être approuvé par léquipe de vérification.
Après approbation, il apparaît sur le site avec le nom de lutilisateur layant proposé.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
7. Notification dincident
</Typography>
<Typography paragraph='true' variant='body1'>
Quels que soient les efforts fournis, aucune méthode de transmission sur Internet et aucune méthode de stockage électronique nest complètement sûre.
Nous ne pouvons en conséquence pas garantir une sécurité absolue.
Si nous prenions connaissance dune brèche de la sécurité, nous avertirions les utilisateurs concernés afin quils puissent prendre les mesures appropriées.
Nos procédures de notification dincident tiennent compte de nos obligations légales, quelles se situent au niveau national ou européen. Nous nous engageons à informer pleinement
nos utilisateurs de toutes les questions relevant de la sécurité de leur compte et à leur fournir toutes les informations nécessaires pour les aider à respecter leurs propres
obligations réglementaires en matière de reporting.
</Typography>
<Typography paragraph='true' variant='body1'>
Aucune information personnelle de lutilisateur du site <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> nest publiée à linsu de lutilisateur, échangée, transférée,
cédée ou vendue sur un support quelconque à des tiers. Seule lhypothèse du rachat de <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> et de ses droits permettrait
la transmission des dites informations à léventuecodel acquéreur qui serait à son tour tenu de la même obligation de conservation et de modification des données vis à vis de
lutilisateur du site <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link>.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
Sécurité
</Typography>
<Typography paragraph='true' variant='body1'>
Pour assurer la sécurité et la confidentialité des Données Personnelles et des Données Personnelles de Santé, <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> utilise des réseaux protégés
par des dispositifs standards tels que par pare-feu, la pseudonymisation, lencryption et mot de passe.
</Typography>
<Typography paragraph='true' variant='body1'>
Lors du traitement des Données Personnelles, <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> prend toutes les mesures raisonnables visant à les protéger contre toute perte,
utilisation détournée, accès non autorisé, divulgation, altération ou destruction.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
8. Liens hypertextes « cookies » et balises (tags) internet
</Typography>
<Typography paragraph='true' variant='body1'>
Le site <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> contient un certain nombre de liens hypertextes vers dautres sites,
mis en place avec lautorisation de <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link>. Cependant, <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> na pas la possibilité de vérifier
le contenu des sites ainsi visités, et nassumera en conséquence aucune responsabilité de ce fait.
Sauf si vous décidez de désactiver les cookies, vous acceptez que le site puisse les utiliser.
Vous pouvez à tout moment désactiver ces cookies et ce gratuitement à partir des possibilités de désactivation qui vous sont offertes et rappelées ci-après,
sachant que cela peut réduire ou empêcher laccessibilité à tout ou partie des Services proposés par le site.
</Typography>
<Typography gutterBottom variant='h6' component='h3'>
8.1 « COOKIES »
</Typography>
<Typography paragraph='true' variant='body1'>
Un « cookie » est un petit fichier dinformation envoyé sur le navigateur de lUtilisateur et enregistré au sein du terminal de lUtilisateur (ex : ordinateur, smartphone),
(ci-après « Cookies »). Ce fichier comprend des informations telles que le nom de domaine de lUtilisateur, le fournisseur daccès Internet de lUtilisateur,
le système dexploitation de lUtilisateur, ainsi que la date et lheure daccès. Les Cookies ne risquent en aucun cas dendommager le terminal de lUtilisateur.
</Typography>
<Typography paragraph='true' variant='body1'>
<Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> est susceptible de traiter les informations de lUtilisateur concernant sa visite du Site, telles que les pages consultées,
les recherches effectuées. Ces informations permettent à <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> daméliorer le contenu du Site, de la navigation de lUtilisateur.
</Typography>
<Typography paragraph='true' variant='body1'>
Les Cookies facilitant la navigation et/ou la fourniture des services proposés par le Site, lUtilisateur peut configurer son navigateur pour quil lui permette de décider sil souhaite
ou non les accepter de manière à ce que des Cookies soient enregistrés dans le terminal ou, au contraire, quils soient rejetés, soit systématiquement, soit selon leur émetteur.
LUtilisateur peut également configurer son logiciel de navigation de manière à ce que lacceptation ou le refus des Cookies lui soient proposés ponctuellement, avant quun Cookie soit
susceptible dêtre enregistré dans son terminal. <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> informe lUtilisateur que, dans ce cas, il se peut que les fonctionnalités de son logiciel
de navigation ne soient pas toutes disponibles.
</Typography>
<Typography paragraph='true' variant='body1'>
Si lUtilisateur refuse lenregistrement de Cookies dans son terminal ou son navigateur, ou si lUtilisateur supprime ceux qui y sont enregistrés,
lUtilisateur est informé que sa navigation et son expérience sur le Site peuvent être limitées. Cela pourrait également être le cas lorsque <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> ne peut pas reconnaître,
à des fins de compatibilité technique, le type de navigateur utilisé par le terminal, les paramètres de langue et daffichage ou le pays depuis lequel le terminal semble connecté à Internet.
</Typography>
<Typography paragraph='true' variant='body1'>
Le cas échéant, <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> décline toute responsabilité pour les conséquences liées au fonctionnement dégradé du Site
et des services éventuellement proposés par <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link>, résultant du refus de Cookies par lUtilisateur de limpossibilité
pour <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> denregistrer ou de consulter les Cookies nécessaires à leur fonctionnement du fait du choix de lUtilisateur.
Pour la gestion des Cookies et des choix de lUtilisateur, la configuration de chaque navigateur est différente. Elle est décrite dans le menu daide du navigateur,
qui permettra de savoir de quelle manière lUtilisateur peut modifier ses souhaits en matière de Cookies.
</Typography>
<Typography paragraph='true' variant='body1'>
Enfin, en cliquant sur licône dédiée au réseau social Twitter, figurant sur le Site de <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> ou dans son application mobile
et si lUtilisateur a accepté le dépôt de cookies en poursuivant sa navigation sur le Site Internet ou lapplication mobile de <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link>, Twitter,
peut également déposer des cookies sur vos terminaux (ordinateur, tablette, téléphone portable).
</Typography>
<Typography paragraph='true' variant='body1'>
Ces types de cookies ne sont déposés sur vos terminaux quà condition que vous y consentiez, en continuant votre navigation sur le Site Internet ou
lapplication mobile de <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link>. À tout moment, lUtilisateur peut néanmoins revenir sur son consentement à ce
que <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> dépose ce type de cookies.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
9. Les dons
</Typography>
<Typography paragraph='true' variant='body1'>
Le site dispose dune page permettant de faire des dons à « ORGANISATION KA INTERNATIONALE » de manière sécurisée.
Pour se faire, il faut se rendre sur la page « <Link href='/soutyen'><strong>soutyen</strong></Link> ». Lutilisateur a 2 onglets disponibles :
</Typography>
<List>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
LIBERAPAY / PAYPAL
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
CARTE BANCAIRE
</ListItemText>
</ListItem>
</List>
<Typography gutterBottom variant='h6' component='h3'>
9.1 LIBERAPAY / PAYPAL
</Typography>
<Typography paragraph='true' variant='body1'>
La plateforme <Link target='_blank' rel='noreferrer' href='https://liberapay.com/OKi/donate'><strong>Liberapay</strong></Link> est mise à disposition. Elle permet non seulement de personnaliser le montant, mais aussi la récurrence des dons. Cest à dire quavec cet outil, lutilisateur peut effectuer des dons hebdomadaires, mensuels ou annuels.
</Typography>
<Typography paragraph='true' variant='body1'>
Lutilisateur peut faire un don en passant par la plateforme <Link target='_blank' rel='noreferrer' href='https://www.paypal.com/donate/?hosted_button_id=5Q3KPR79CAZVW'><strong>PayPal</strong></Link>. En cliquant sur le bouton prévu à cet effet, il est redirigé vers la page de don associée à ORGANISATION KA INTERNATIONALE. Il peut ensuite choisir un montant et valider le don en utilisant son compte <Link target='_blank' rel='noreferrer' href='https://www.paypal.com/donate/?hosted_button_id=5Q3KPR79CAZVW'><strong>PayPal</strong></Link> ou une carte bancaire sil nest pas enregistré.
</Typography>
<Typography gutterBottom variant='h6' component='h3'>
9.2 CARTE BANCAIRE
</Typography>
<Typography paragraph='true' variant='body1'>
En cliquant sur le bouton « CARTE BANCAIRE », lutilisateur se voit proposer plusieurs choix. Parmi ces derniers, deux options sont possibles :
<List>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
<strong>Ponctuel</strong> (1, 5, 10, 20, 50, Montant personnalisé)
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
<strong>Mensuel</strong> (1€ / mois, 5 / mois, 10 / mois, 20 / mois, 50 / mois)
</ListItemText>
</ListItem>
</List>
</Typography>
<Typography paragraph='true' variant='body1'>
Les dons peuvent donc être fait une seule fois ou de manière récurrente.
Quelque soit le choix, lutilisateur est redirigé vers le sous-domaine don.o-k-i.net suivi de lID du montant choisit. Cette URL est gérée par la plateforme de paiement sécurisée <Link href='https://stripe.com/' target='_blank' rel='noopener noreferrer'> <strong>Stripe</strong></Link>.
Il est important de noter qu<strong>aucune information de paiement nest stockée sur notre site internet</strong>.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
10. Propriété intellectuelle et licence
</Typography>
<Typography paragraph='true' gutterBottom variant='body1'>
Le site internet <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> et les éléments qui y sont accessibles
(textes, images, graphismes, logos, vidéos, icônes, sons, etc.) sont, sauf mention contraire,
mis à disposition sous la licence <Link href='https://www.gnu.org/licenses/agpl-3.0.html' target='_blank' rel='noopener noreferrer'> <strong>GNU Affero General Public License Version 3 (AGPL-3.0)</strong></Link>. Cette licence garantit aux utilisateurs les libertés suivantes :
<List>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
Liberté dexécuter le programme pour tous les usages,
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
Liberté détudier le fonctionnement du programme et de ladapter à vos besoins,
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
Liberté de redistribuer des copies du programme,
</ListItemText>
</ListItem>
<ListItem>
<ListItemIcon>
<ArrowRightAltIcon />
</ListItemIcon>
<ListItemText>
Liberté daméliorer le programme et de publier vos modifications.
</ListItemText>
</ListItem>
</List>
Conformément aux exigences de la licence AGPL-3.0, si vous redistribuez ou modifiez des éléments du site,
vous devez également fournir le code source complet, incluant vos modifications, sous la même licence.
Vous pouvez consulter les termes détaillés de la licence AGPL-3.0 sur le site officiel de la <Link href='https://www.gnu.org/licenses/agpl-3.0.html' target='_blank' rel='noopener noreferrer'> <strong>Free Software Foundation</strong></Link>.
</Typography>
<Typography gutterBottom variant='h5' component='h2'>
11. Droit applicable et attribution de juridiction
</Typography>
<Typography paragraph='true' variant='body1'>
Tout litige en relation avec lutilisation du site <Link href='https://pawol.nu'><strong>pawol.nu</strong></Link> est soumis au droit français.
En dehors des cas la loi ne le permet pas, il est fait attribution exclusive de juridiction aux tribunaux compétents.
</Typography>
<Typography gutterBottom variant='caption' >
Dernières modifications le 26/06/2026
</Typography>
</Root>
)
}
-156
View File
@@ -1,156 +0,0 @@
import {useCallback, useEffect, useState, forwardRef} from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'
import {
TextField,
Container,
Button,
Snackbar,
LinearProgress,
Typography
} from '@mui/material'
import MuiAlert from '@mui/material/Alert'
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:1337/api'
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert ref={ref} elevation={6} variant='filled' {...props} />
})
function EkriKomante({session, paroleId, meteEsKomanteOuve}) {
const {jwt, user} = session
const [komante, meteKomante] = useState({kontni: ''})
const [ere, meteEre] = useState('')
const [sikse, meteSikse] = useState('')
const [chaje, meteChaje] = useState(false)
const [esOuve, meteEsOuve] = useState(false)
const handleClick = async () => {
meteChaje(true)
const {kontni} = komante
if (kontni === '') {
meteEre({error: {message: 'Champ obligatoire'}})
meteChaje(false)
return
}
const headers = {
'content-type': 'application/json',
Authorization: `Bearer ${jwt}`
}
try {
await axios.post(`${API_URL}/commentaires`, {
data: {
contenu: kontni,
parole: paroleId,
user: {
id: user.id,
username: user.username,
email: user.email
},
datePublication: new Date()
}
}, {
headers
})
meteSikse('Commentaire envoyé avec succès. Il apparaîtra sur le site après validation.')
meteChaje(false)
} catch (error) {
meteEre(error?.response?.data)
meteChaje(false)
}
}
const handleUpdate = useCallback(update => {
meteKomante({...komante, ...update})
}, [komante])
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return
}
meteEsOuve(false)
meteSikse('')
meteEre('')
meteEsKomanteOuve(false)
}
const handleReset = () => {
meteKomante({kontni: ''})
}
useEffect(() => {
if (sikse) {
meteEsOuve(true)
handleReset()
}
}, [sikse])
useEffect(() => {
if (ere) {
meteEsOuve(true)
}
}, [ere])
return (
<Container maxWidth='sm'>
<form noValidate autoComplete='off'>
<TextField
fullWidth
multiline
required
helperText='*Obligatoire. 500 caractères maximum'
style={{marginTop: '1em'}}
id='komante'
label='Commentaire'
value={komante.kontni}
rows={8}
variant='outlined'
onChange={event => handleUpdate({kontni: event.target.value})}
/>
</form>
<div>
<Button
fullWidth
disabled={chaje || komante.komante === ''}
style={{marginBlock: 20}}
variant='contained'
color='primary'
onClick={handleClick}
>
<Typography style={{fontWeight: 'bold'}}>
Valider
</Typography>
</Button>
{chaje && <LinearProgress size={24} style={{width: '100%', marginBlock: '1em'}} />}
</div>
{sikse && (
<Snackbar open={esOuve} autoHideDuration={10_000} onClose={handleClose}>
<Alert severity='success' onClose={handleClose}>
<strong>{sikse}</strong>
</Alert>
</Snackbar>
)}
{ere && (
<Snackbar open={esOuve} autoHideDuration={10_000} onClose={handleClose}>
<Alert severity='error' onClose={handleClose}>
<strong>Une erreur sest produite</strong> : <i>{ere?.error?.message}</i>
</Alert>
</Snackbar>
)}
</Container>
)
}
EkriKomante.propTypes = {
session: PropTypes.object.isRequired,
paroleId: PropTypes.number.isRequired,
meteEsKomanteOuve: PropTypes.func.isRequired
}
export default EkriKomante
-67
View File
@@ -1,67 +0,0 @@
import PropTypes from 'prop-types'
import {format} from 'date-fns'
import {fr} from 'date-fns/locale'
import {styled} from '@mui/material/styles'
import {
Typography,
Divider,
List,
ListItemText
} from '@mui/material'
import {formatJsonString} from '../../lib/utils/format'
const PREFIX = 'komante-list'
const classes = {
root: `${PREFIX}-root`,
inline: `${PREFIX}-inline`
}
const StyledList = styled(List)((
{
theme
}
) => ({
[`&.${classes.root}`]: {
width: '100%',
maxWidth: '36ch',
backgroundColor: theme.palette.background.paper
},
[`& .${classes.inline}`]: {
display: 'inline'
}
}))
export default function KomanteList({commentaires}) {
return (
<StyledList className={classes.root}>
{commentaires.map(({id, user, contenu, datePublication}) => (
<div key={id}>
<ListItemText
primary={
<Typography gutterBottom style={{textDecoration: 'underline'}} variant='body1'>
<small>{user.username}</small>
</Typography>
}
/>
<ListItemText
primary={formatJsonString(contenu)}
secondary={
<Typography gutterBottom style={{marginBlock: 5, fontStyle: 'italic'}} variant='caption' display='block'>
{format(new Date(datePublication), 'Pp', {locale: fr})}
</Typography>
}
/>
<Divider style={{marginBottom: '1em'}} />
</div>
))}
</StyledList>
)
}
KomanteList.propTypes = {
commentaires: PropTypes.array.isRequired
}
-136
View File
@@ -1,136 +0,0 @@
import {useState, useEffect, useRef} from 'react'
import {styled} from '@mui/material/styles'
import PropTypes from 'prop-types'
import {useSession} from 'next-auth/react'
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Badge
} from '@mui/material'
import {useRouter} from 'next/navigation'
import Koneksyon from '../sesyon/koneksyon'
import KomanteList from './komante-list'
import EkriKomante from './ekri-komante'
const PREFIX = 'vwe-komante'
const classes = {
tooltip: `${PREFIX}-tooltip`,
margin: `${PREFIX}-margin`,
extendedIcon: `${PREFIX}-extendedIcon`
}
const Root = styled('div')((
{
theme
}
) => ({
[`& .${classes.margin}`]: {
margin: theme.spacing(1)
},
[`& .${classes.extendedIcon}`]: {
marginRight: theme.spacing(1)
}
}))
export default function VweKomante({commentaires, parole, paroleId}) {
const [esOuve, meteEsOuve] = useState(false)
const [esKoneksyonOuve, meteEsKoneksyonOuve] = useState(false)
const [esKomenteOuve, meteEsKomanteOuve] = useState(false)
const {data: session} = useSession()
const router = useRouter()
const handleClick = () => {
meteEsOuve(true)
}
const handleClose = () => {
meteEsOuve(false)
}
const descriptionElementRef = useRef(null)
useEffect(() => {
if (esOuve) {
const {current: descriptionElement} = descriptionElementRef
if (descriptionElement !== null) {
descriptionElement.focus()
}
}
}, [esOuve])
return (
<Root>
<Badge badgeContent={commentaires.length} color='primary'>
<Button sx={{marginBlock: 3}} variant='outlined' onClick={handleClick}>
Voir les commentaires
</Button>
</Badge>
<Dialog
open={esOuve}
scroll='paper'
aria-labelledby='scroll-dialog-title'
aria-describedby='scroll-dialog-description'
onClose={handleClose}
>
<DialogTitle align='center' id='scroll-dialog-title'>Commentaires</DialogTitle>
<DialogContent dividers>
<Typography
ref={descriptionElementRef}
variant='inherit'
id='scroll-dialog-description'
tabIndex={-1}
component='div'
>
{commentaires.length > 0 ? (
<KomanteList commentaires={commentaires} />
) : (
<Typography component='div' align='center'>
Aucun commentaire
</Typography>
)}
</Typography>
</DialogContent>
<DialogActions align='center' >
{session && session.user && !esKomenteOuve && (
<Button fullWidth color='primary' onClick={() => meteEsKomanteOuve(true)}>
Ajouter un commentaire
</Button>
)}
{!session && !esKoneksyonOuve && (
<Button fullWidth color='primary' onClick={() => meteEsKoneksyonOuve(true)}>
Se connecter pour commenter
</Button>
)}
{!session && esKoneksyonOuve && (
<Koneksyon chimen={router.asPath} />
)}
</DialogActions>
{!session && esKoneksyonOuve && (
<Button style={{marginBlock: 10}} color='secondary' onClick={() => meteEsKoneksyonOuve(false)}>
<strong>Annuler</strong>
</Button>
)}
{session && session.user && esKomenteOuve && (
<>
<EkriKomante session={session} parole={parole} paroleId={paroleId} meteEsKomanteOuve={meteEsKomanteOuve} />
<Button fullWidth style={{marginBottom: 10}} color='primary' onClick={() => meteEsKomanteOuve(false)}>
Fermer
</Button>
</>
)}
</Dialog>
</Root>
)
}
VweKomante.propTypes = {
commentaires: PropTypes.array,
parole: PropTypes.object.isRequired,
paroleId: PropTypes.number.isRequired
}
-202
View File
@@ -1,202 +0,0 @@
import {useState, forwardRef} from 'react'
import PropTypes from 'prop-types'
import axios from 'axios'
import {useRouter} from 'next/navigation'
import {FormControl, Snackbar, IconButton, Button, Input, InputAdornment, InputLabel, Box, Container, Typography, LinearProgress} from '@mui/material'
import MuiAlert from '@mui/material/Alert'
import Visibility from '@mui/icons-material/Visibility'
import VisibilityOff from '@mui/icons-material/VisibilityOff'
import Link from '@mui/material/Link'
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:1337'
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert ref={ref} elevation={6} variant='filled' {...props} />
})
export default function NewPassword({code}) {
const router = useRouter()
const [showPassword, setShowPassword] = useState('')
const [showPasswordConfirmation, setShowPasswordConfirmation] = useState('')
const [password, setPassword] = useState('')
const [passwordConfirmation, setPasswordConfirmation] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const [open, setOpen] = useState(true)
const [passwordSuccess, setPasswordSuccess] = useState(false)
const resetForm = () => {
setPassword('')
setPasswordConfirmation('')
}
const changePassword = async () => {
setLoading(true)
try {
await axios.post(`${API_URL}/auth/reset-password`, {
code,
password,
passwordConfirmation
})
setLoading(false)
setPasswordSuccess(true)
setTimeout(() => {
router.push('/pwopose')
}, 10_000)
} catch {
setOpen(true)
setError('Une erreur sest produite. Veuillez réessayer ultérieurement')
}
}
const handleMouseDownPassword = event => {
event.preventDefault()
}
const handleMouseDownPasswordConfirmation = event => {
event.preventDefault()
}
const handleKeyUpPassword = event => {
if (event.keyCode === 13) {
handleClick()
}
}
const handleKeyUpPasswordConfirmation = event => {
if (event.keyCode === 13) {
handleClick()
}
}
const handleClick = async () => {
if (password !== passwordConfirmation) {
setOpen(true)
setError('Les 2 mots de passe de correspondent pas')
return
}
if (password.length < 6) {
setOpen(true)
setError('Le mot de passe est trop court, 6 caratères minimum')
return
}
await changePassword()
setLoading(false)
resetForm()
}
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return
}
setOpen(false)
setError('')
}
return (
<Container sx={{marginTop: 2}} maxWidth='sm'>
<Box sx={{width: '100%'}}>
<Typography sx={{textAlign: 'center'}} variant='h5' component='div'>Nouveau mot de passe</Typography>
<FormControl fullWidth style={{marginTop: '1em'}}>
<InputLabel htmlFor='password'>Mot de passe</InputLabel>
<Input
value={password}
name='register-password'
type={showPassword ? 'text' : 'password'}
id='register-password'
endAdornment={
<InputAdornment position='end'>
<IconButton
aria-label='password visibility'
size='large'
onClick={() => setShowPassword(!showPassword)}
onMouseDown={handleMouseDownPassword}
>
{showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
onChange={event => setPassword(event.target.value)}
onKeyUp={handleKeyUpPassword}
/>
</FormControl>
<FormControl fullWidth style={{marginTop: '1em'}}>
<InputLabel htmlFor='password'>Vérification du mot de passe</InputLabel>
<Input
value={passwordConfirmation}
name='verification-password'
type={showPasswordConfirmation ? 'text' : 'password'}
id='verification-password'
error={password !== passwordConfirmation}
endAdornment={
<InputAdornment position='end'>
<IconButton
aria-label='password visibility'
size='large'
onClick={() => setShowPasswordConfirmation(!showPassword)}
onMouseDown={handleMouseDownPasswordConfirmation}
>
{showPasswordConfirmation ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
onChange={event => setPasswordConfirmation(event.target.value)}
onKeyUp={handleKeyUpPasswordConfirmation}
/>
</FormControl>
<Button
fullWidth
style={{marginTop: 20}}
variant='contained'
color='primary'
disabled={loading || password !== passwordConfirmation || password === ''}
onClick={handleClick}
>
<Typography style={{fontWeight: 'bold'}}>
Changer de mot de passe
</Typography>
</Button>
<Button
fullWidth
style={{marginTop: 20}}
variant='outlined'
color='primary'
onClick={() => router.push('/pwopose')}
>
<Typography style={{fontWeight: 'bold'}}>
Retour à la page de connexion
</Typography>
</Button>
</Box>
{loading && <LinearProgress size={24} style={{width: '100%', marginTop: '1em'}} />}
{error && (
<Snackbar
open={open}
autoHideDuration={6000}
onClose={handleClose}
>
<Alert severity='error' onClose={handleClose}><strong>{error}</strong></Alert>
</Snackbar>
)}
{passwordSuccess && (
<Snackbar
open={passwordSuccess}
onClose={handleClose}
>
<Alert severity='info' onClose={handleClose}>Votre changement de mot de passe a été pris en compte. Vous serez redirigé vers la page de connexion. <Link underline='hover' color='inherit' href='/pwopose'><strong>Cliquez ici si vous nêtes pas redirigé vers la page de connexion</strong></Link></Alert>
</Snackbar>
)}
</Container>
)
}
NewPassword.propTypes = {
code: PropTypes.string.isRequired
}
-88
View File
@@ -1,88 +0,0 @@
import {useState} from 'react'
import PropTypes from 'prop-types'
import Button from '@mui/material/Button'
import TextField from '@mui/material/TextField'
import Dialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent'
import DialogContentText from '@mui/material/DialogContentText'
import DialogTitle from '@mui/material/DialogTitle'
import {validateEmail} from '../../lib/utils/emails'
import {jwennUserEpiEmail, passwordRequest} from '../../lib/oki-api'
export default function ResetDialog({lyen, title, activation, content, open, setOpen, setLoading, setError, setSuccess}) {
const [email, setEmail] = useState('')
const forgotPasswordRequest = async () => {
setLoading(true)
try {
await passwordRequest(lyen, email)
if (activation) {
const user = await jwennUserEpiEmail(email)
localStorage.setItem('user-id', user?.id)
}
setLoading(false)
setSuccess(true)
} catch {
setError('Une erreur sest produite. Veuillez réessayer ultérieurement')
setLoading(false)
}
}
const resetFrom = () => {
setEmail('')
}
const handleClick = async () => {
forgotPasswordRequest()
setOpen(false)
resetFrom()
}
const handleClose = () => {
setOpen(false)
}
return (
<div>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{title}</DialogTitle>
<DialogContent>
<DialogContentText>
{content}
</DialogContentText>
<TextField
autoFocus
fullWidth
value={email}
margin='dense'
id='email'
label='Adresse e-mail'
type='email'
variant='standard'
onChange={event => setEmail(event.target.value)}
/>
</DialogContent>
<DialogActions>
<Button variant='contained' color='secondary' onClick={handleClose}>Annuler</Button>
<Button disabled={!validateEmail(email)} variant='contained' color='primary' onClick={handleClick}>Valider</Button>
</DialogActions>
</Dialog>
</div>
)
}
ResetDialog.propTypes = {
lyen: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
activation: PropTypes.bool,
content: PropTypes.string.isRequired,
open: PropTypes.bool.isRequired,
setOpen: PropTypes.func.isRequired,
setLoading: PropTypes.func.isRequired,
setError: PropTypes.func.isRequired,
setSuccess: PropTypes.func.isRequired,
}
-75
View File
@@ -1,75 +0,0 @@
import {useState, forwardRef} from 'react'
import Button from '@mui/material/Button'
import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
import Snackbar from '@mui/material/Snackbar'
import {LinearProgress} from '@mui/material'
import MuiAlert from '@mui/material/Alert'
import ResetDialog from './reset-dialog'
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert ref={ref} elevation={6} variant='filled' {...props} />
})
export default function ResetPassword() {
const [open, setOpen] = useState(false)
const [error, setError] = useState(false)
const [loading, setLoading] = useState(false)
const [success, setSuccess] = useState(false)
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return
}
setError('')
setSuccess(false)
}
return (
<>
<Box style={{marginTop: 20}}>
<Button
fullWidth
disabled={loading}
variant='outlined'
color='warning'
onClick={() => setOpen(true)}
>
<Typography style={{fontWeight: 'bold'}}>
Mot de passe oublié
</Typography>
</Button>
</Box>
{loading && <LinearProgress size={24} style={{width: '100%', marginTop: '1em'}} />}
{error && (
<Snackbar
open={Boolean(error)}
autoHideDuration={6000}
onClose={handleClose}
>
<Alert severity='error' onClose={handleClose}><strong>{error}</strong></Alert>
</Snackbar>
)}
{success && (
<Snackbar
open={success}
autoHideDuration={15_000}
onClose={handleClose}
>
<Alert severity='success' onClose={handleClose}><strong>Email envoyé avec succès. Vous pouvez changer votre mot de passe via le lien qui vous a été envoyé.</strong></Alert>
</Snackbar>
)}
<ResetDialog
lyen='forgot-password'
title='Mot de passe oublié'
content='Indiquez ladresse e-mail utilisée lors de votre inscription. Vous receverez des instructions par e-mail.'
open={open}
setOpen={setOpen}
setLoading={setLoading}
setError={setError}
setSuccess={setSuccess}
/>
</>
)
}
-113
View File
@@ -1,113 +0,0 @@
'use client'
import {useState, useEffect, forwardRef} from 'react'
import PropTypes from 'prop-types'
import {useSession} from 'next-auth/react'
import MuiAlert from '@mui/material/Alert'
import Snackbar from '@mui/material/Snackbar'
import Box from '@mui/material/Box'
import Koneksyon from '../../components/sesyon/koneksyon'
import Dekoneksyon from '../../components/sesyon/dekoneksyon'
import EkriTeks from '../../components/soumet/ekri-teks'
import Footer from '../../components/footer'
import {jwennUserEpiToken, jwennUserEpiUsername} from '../../lib/oki-api'
import NewPassword from '../../components/password/new-password'
import ChwaTeks from '../../components/soumet/chwa-teks'
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert ref={ref} elevation={6} variant='filled' {...props} />
})
export default function Pwopose({code}) {
const {data: session} = useSession()
const [localUsername, setLocalUsername] = useState(null)
const [username, setUsername] = useState(null)
const [open, setOpen] = useState(true)
const [selectedTeks, setSelectedTeks] = useState(null)
const [canAutoTranslate, setCanAutoTranslate] = useState(false)
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return
}
setOpen(false)
}
useEffect(() => {
if (session?.jwt) {
const getUser = async token => {
const user = await jwennUserEpiToken(token)
setCanAutoTranslate(user.canAutoTranslate)
}
getUser(session.jwt)
}
})
useEffect(() => {
if (localStorage.getItem('username')) {
const username = localStorage.getItem('username')
setLocalUsername(username)
}
}, [])
useEffect(() => {
if (localUsername) {
const getUser = async username => {
const user = await jwennUserEpiUsername(username)
setUsername(user?.username)
}
getUser(localUsername)
}
}, [localUsername])
useEffect(() => {
if (username && localStorage.getItem('username')) {
localStorage.removeItem('username')
}
}, [username])
return (
<Box sx={{display: 'flex', flexDirection: 'column', minHeight: '100vh'}}>
<Box sx={{flexGrow: 1, marginTop: 1, marginBottom: 10}}>
{!session && !code && (
<Koneksyon
chimen='/pwopose'
/>
)}
{!session && code && (
<NewPassword code={code} />
)}
{session && session.user && (
<>
<Dekoneksyon position='absolute' top={95} left={5} chimen='/pwopose' />
<ChwaTeks selectedTeks={selectedTeks} setSelectedTeks={setSelectedTeks} />
<EkriTeks canAutoTranslate={canAutoTranslate} selectedTeks={selectedTeks} setSelectedTeks={setSelectedTeks} />
</>
)}
{session && !session.user && (
<Dekoneksyon position='absolute' top={95} left={5} chimen='/pwopose' />
)}
{username && (
<Snackbar
open={open}
autoHideDuration={10_000}
onClose={handleClose}
>
<Alert severity='success' onClose={handleClose}><strong>Bonjour {username}, votre compte a été activé avec succès. Vous pouvez vous connecter.</strong></Alert>
</Snackbar>
)}
</Box>
<Footer />
</Box>
)
}
Pwopose.propTypes = {
code: PropTypes.string
}
-62
View File
@@ -1,62 +0,0 @@
import {useState} from 'react'
import {styled} from '@mui/material/styles'
import PropTypes from 'prop-types'
import {signOut} from 'next-auth/react'
import {Tooltip, Fab, Zoom} from '@mui/material'
import ExitToAppIcon from '@mui/icons-material/ExitToApp'
import Alet from '../alet'
const PREFIX = 'Dekoneksyon'
const classes = {
tooltip: `${PREFIX}-tooltip`,
dekoneksyon: `${PREFIX}-dekoneksyon`
}
const Root = styled('div')(() => ({
[`& .${classes.dekoneksyon}`]: {
position: props => props.position,
top: props => props.top,
left: props => props.left,
zIndex: 0
}
}))
const DekoneksonTooltip = Tooltip
function Dekoneksyon({tooltipPlacement}) {
const [esOuve, meteEsOuve] = useState(false)
return (
(
<Root>
<DekoneksonTooltip
title='Se déconnecter'
placement={tooltipPlacement}
TransitionComponent={Zoom}
classes={{
tooltip: classes.tooltip
}}
>
<Fab
className={classes.dekoneksyon}
color='error'
aria-label='logout'
size='small'
onClick={() => meteEsOuve(true)}
>
<ExitToAppIcon />
</Fab>
</DekoneksonTooltip>
<Alet esOuve={esOuve} meteEsOuve={meteEsOuve} handleKonfime={() => signOut()} />
</Root>
)
)
}
Dekoneksyon.propTypes = {
tooltipPlacement: PropTypes.string
}
export default Dekoneksyon
-459
View File
@@ -1,459 +0,0 @@
import {useEffect, useState, forwardRef} from 'react'
import {signIn} from 'next-auth/react'
import {useRouter} from 'next/navigation'
import PropTypes from 'prop-types'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import Container from '@mui/material/Container'
import FormControl from '@mui/material/FormControl'
import IconButton from '@mui/material/IconButton'
import Input from '@mui/material/Input'
import InputAdornment from '@mui/material/InputAdornment'
import InputLabel from '@mui/material/InputLabel'
import LinearProgress from '@mui/material/LinearProgress'
import Snackbar from '@mui/material/Snackbar'
import Tab from '@mui/material/Tab'
import Tabs from '@mui/material/Tabs'
import Typography from '@mui/material/Typography'
import Grid from '@mui/material/Grid'
import Visibility from '@mui/icons-material/Visibility'
import VisibilityOff from '@mui/icons-material/VisibilityOff'
import MuiAlert from '@mui/material/Alert'
import LoginIcon from '@mui/icons-material/Login'
import AppRegistrationRoundedIcon from '@mui/icons-material/AppRegistrationRounded'
import axios from 'axios'
import {validateEmail} from '../../lib/utils/emails'
import ResetPassword from '../password/reset-password'
import ResetDialog from '../password/reset-dialog'
import LoginProvider from './login-provider'
const siteUrl = process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3001'
const apiUrl = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:1337'
const PROVIDERS = [
{
id: 'google',
title: 'Google',
width: 16,
height: 16
},
{
id: 'twitter',
title: 'Twitter',
width: 16,
height: 13
}
]
function TabPanel(props) {
const {children, value, index, ...other} = props
return (
<div
role='tabpanel'
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && (
<Box sx={{p: 3}}>
{children}
</Box>
)}
</div>
)
}
TabPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.number.isRequired,
value: PropTypes.number.isRequired,
}
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
}
}
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert ref={ref} elevation={6} variant='filled' {...props} />
})
function Koneksyon({chimen}) {
const [error, setError] = useState('')
const [loginCredentials, setLoginCredentials] = useState({username: '', password: ''})
const [registerCredentials, setRegisterCredentials] = useState({username: '', email: '', password: ''})
const [passwordVerification, setPasswordVerification] = useState('')
const [showPassword, setShowPassword] = useState(false)
const [showRegisterPassword, setShowRegisterPassword] = useState(false)
const [showVerificationPassword, setShowVerificationPassword] = useState(false)
const [loading, setLoading] = useState(false)
const [open, setOpen] = useState(true)
const [success, setSuccess] = useState(false)
const [openDialog, setOpenDialog] = useState(false)
const [value, setValue] = useState(0)
const router = useRouter()
const handleUpdate = update => {
setLoginCredentials({...loginCredentials, ...update})
}
const handleRegisterUpdate = update => {
setRegisterCredentials({...registerCredentials, ...update})
}
const handleChange = (event, newValue) => {
setValue(newValue)
}
const register = async () => {
try {
const response = await axios.post(`${apiUrl}/auth/local/register`, {
...registerCredentials
}, {
headers: {
'content-type': 'application/json'
}
})
localStorage.setItem('username', `${response?.data?.user?.username}`)
setSuccess(true)
resetRegisterForm()
} catch (error_) {
if (error_.message.endsWith(400)) {
setError('E-mail ou utilisateur déjà enregistré')
} else {
setError('Une erreur sest produite')
}
}
}
const resetRegisterForm = () => {
setRegisterCredentials({username: '', email: '', password: ''})
setPasswordVerification('')
}
const handleClickLogin = async () => {
if (!validateEmail(loginCredentials.username) || loginCredentials.password === '') {
return
}
setLoading(true)
const response = await signIn('credentials', {
callbackUrl: `${siteUrl}${chimen}`,
redirect: false,
...loginCredentials
})
if (response.error) {
setError(response.error)
setLoading(false)
} else if (response.ok) {
setLoading(false)
router.push(chimen)
}
}
const handleClickRegister = async () => {
setLoading(true)
if (!validateEmail(registerCredentials.email) || registerCredentials.password === '') {
return
}
if (registerCredentials.password !== passwordVerification) {
setError('Les 2 mots de passe de correspondent pas')
setLoading(false)
return
}
if (registerCredentials.username.length < 3) {
setError('Le nom dutilisateur est trop court, 3 caratères minimum')
setLoading(false)
return
}
if (registerCredentials.password.length < 6) {
setError('Le mot de passe est trop court, 6 caratères minimum')
setLoading(false)
return
}
await register()
setLoading(false)
}
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return
}
setOpen(false)
setSuccess(false)
setError('')
}
useEffect(() => {
if (error) {
setOpen(true)
}
return () => {
setLoading(false)
}
}, [error])
const handleClickShowPassword = () => {
setShowPassword(!showPassword)
}
const handleClickShowRegisterPassword = () => {
setShowRegisterPassword(!showRegisterPassword)
}
const handleClickShowVerificationPassword = () => {
setShowVerificationPassword(!showVerificationPassword)
}
const handleMouseDownPassword = event => {
event.preventDefault()
}
const handleMouseDownRegisterPassword = event => {
event.preventDefault()
}
const handleMouseDownVerificationPassword = event => {
event.preventDefault()
}
const handleKeyUpLogin = event => {
if (event.keyCode === 13) {
handleClickLogin()
}
}
const handleKeyUpRegister = event => {
if (event.keyCode === 13) {
handleClickRegister()
}
}
return (
<Container sx={{marginTop: 6}} maxWidth='sm'>
<Box sx={{width: '100%'}}>
<Tabs centered value={value} aria-label='basic tabs example' onChange={handleChange}>
<Tab icon={<LoginIcon />} label='Se connecter' {...a11yProps(0)} />
<Tab icon={<AppRegistrationRoundedIcon />} label='Sinscrire' {...a11yProps(1)} />
</Tabs>
<TabPanel value={value} index={0}>
<Box sx={{textAlign: 'center', marginBottom: 3}}>
<Typography gutterBottom textalign='center'>Connectez-vous avec</Typography>
<Grid sx={{justifyContent: 'center'}} container columnSpacing={{xs: 1, sm: 2, md: 3}}>
{PROVIDERS.map(({id, title, width, height}) => (
<Grid sx={{marginTop: 1}} key={id}>
<LoginProvider id={id} title={title} width={width} height={height} callbackUrl={`${siteUrl}${chimen}`} />
</Grid>
))}
</Grid>
</Box>
<Box sx={{textAlign: 'center', marginBottom: 3}}>
<Typography>ou utilisez votre e-mail pour vous identifier</Typography>
</Box>
<FormControl fullWidth autoComplete='off'>
<InputLabel htmlFor='username'>E-mail</InputLabel>
<Input
autoComplete='new-password'
value={loginCredentials.username}
name='username'
type='email'
id='email'
onChange={event => handleUpdate({username: event.target.value})}
onKeyUp={handleKeyUpLogin}
/>
</FormControl>
<FormControl fullWidth style={{marginTop: '1em'}}>
<InputLabel htmlFor='password'>Mot de passe</InputLabel>
<Input
autoComplete='new-password'
value={loginCredentials.password}
name='password'
type={showPassword ? 'text' : 'password'}
id='password'
endAdornment={
<InputAdornment position='end'>
<IconButton
aria-label='password visibility'
size='large'
onClick={handleClickShowPassword}
onMouseDown={handleMouseDownPassword}
>
{showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
onChange={event => handleUpdate({password: event.target.value})}
onKeyUp={handleKeyUpLogin}
/>
</FormControl>
<Button
fullWidth
disabled={loading || !validateEmail(loginCredentials.username) || loginCredentials.password === ''}
style={{marginTop: 20}}
variant='contained'
color='primary'
onClick={handleClickLogin}
>
<Typography style={{fontWeight: 'bold'}}>
Connexion
</Typography>
</Button>
<ResetPassword />
</TabPanel>
<TabPanel value={value} index={1}>
<FormControl fullWidth>
<InputLabel htmlFor='register-email'>E-mail</InputLabel>
<Input
autoComplete='email'
value={registerCredentials.email}
name='register-email'
type='email'
id='register-email'
onChange={event => handleRegisterUpdate({email: event.target.value})}
onKeyUp={handleKeyUpRegister}
/>
</FormControl>
<FormControl fullWidth style={{marginTop: '1em'}}>
<InputLabel htmlFor='register-username'>Nom dutilisateur</InputLabel>
<Input
autoComplete='username'
value={registerCredentials.username}
name='register-username'
type='text'
id='register-username'
onChange={event => handleRegisterUpdate({username: event.target.value})}
onKeyUp={handleKeyUpRegister}
/>
</FormControl>
<FormControl fullWidth style={{marginTop: '1em'}}>
<InputLabel htmlFor='password'>Mot de passe</InputLabel>
<Input
autoComplete='new-password' // Disable auto complete
value={registerCredentials.password}
name='register-password'
type={showRegisterPassword ? 'text' : 'password'}
id='register-password'
endAdornment={
<InputAdornment position='end'>
<IconButton
aria-label='password visibility'
size='large'
onClick={handleClickShowRegisterPassword}
onMouseDown={handleMouseDownRegisterPassword}
>
{showRegisterPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
onChange={event => handleRegisterUpdate({password: event.target.value})}
onKeyUp={handleKeyUpRegister}
/>
</FormControl>
<FormControl fullWidth style={{marginTop: '1em'}}>
<InputLabel htmlFor='password'>Vérification du mot de passe</InputLabel>
<Input
value={passwordVerification}
name='verification-password'
type={showVerificationPassword ? 'text' : 'password'}
id='verification-password'
error={registerCredentials.password !== passwordVerification}
endAdornment={
<InputAdornment position='end'>
<IconButton
aria-label='password visibility'
size='large'
onClick={handleClickShowVerificationPassword}
onMouseDown={handleMouseDownVerificationPassword}
>
{showVerificationPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</InputAdornment>
}
onChange={event => setPasswordVerification(event.target.value)}
onKeyUp={handleKeyUpRegister}
/>
</FormControl>
<Button
fullWidth
disabled={loading || !validateEmail(registerCredentials.email) || registerCredentials.username === '' || registerCredentials.password === ''}
style={{marginTop: 20}}
variant='contained'
color='primary'
onClick={handleClickRegister}
>
<Typography style={{fontWeight: 'bold'}}>
Inscription
</Typography>
</Button>
<Button
fullWidth
style={{marginTop: 20}}
variant='outlined'
color='warning'
onClick={() => setOpenDialog(true)}
>
<Typography style={{fontWeight: 'bold'}}>
Renvoyer le-mail de confirmation
</Typography>
</Button>
</TabPanel>
</Box>
<ResetDialog
activation
lyen='send-email-confirmation'
title='Envoi de le-mail de confirmation'
content='Si vous navez pas reçu le-mail de confirmation, renseignez le champ et validez le formulaire.'
open={openDialog}
setOpen={setOpenDialog}
setLoading={setLoading}
setError={setError}
setSuccess={setSuccess}
/>
{loading && <LinearProgress size={24} style={{width: '100%'}} />}
{error && (
<Snackbar
open={open}
autoHideDuration={6000}
onClose={handleClose}
>
<Alert severity='error' onClose={handleClose}><strong>{error}</strong></Alert>
</Snackbar>
)}
{success && (
<Snackbar
open={success}
autoHideDuration={15_000}
onClose={handleClose}
>
<Alert severity='success' onClose={handleClose}><strong>Veuillez confirmer votre adresse e-mail via le lien qui vous a été envoyé.</strong></Alert>
</Snackbar>
)}
</Container>
)
}
Koneksyon.propTypes = {
chimen: PropTypes.string.isRequired
}
export default Koneksyon
-38
View File
@@ -1,38 +0,0 @@
import PropTypes from 'prop-types'
import Link from 'next/link'
import Image from 'next/image'
import {signIn} from 'next-auth/react'
import Button from '@mui/material/Button'
export default function LoginProvider({id, title, width, height, callbackUrl}) {
const hanleClick = event => {
event.preventDefault()
signIn(id, {
callbackUrl
})
}
return (
<Link passHref href='/api/auth/signin'>
<Button variant='outlined' color='info' startIcon={
<Image
width={width}
height={height}
alt={title}
src={`/images/${id === 'twitter' ? 'x' : id}.svg`}
/>
} onClick={hanleClick}
>
{title}
</Button>
</Link>
)
}
LoginProvider.propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
callbackUrl: PropTypes.string.isRequired
}
-162
View File
@@ -1,162 +0,0 @@
import {useState} from 'react'
import {styled} from '@mui/material/styles'
import PropTypes from 'prop-types'
import {MenuItem, Menu, Button} from '@mui/material'
import {uniq} from 'lodash'
import OtomatikSwitch from './otomatik-switch'
const PREFIX = 'AjouteTradiksyon'
const classes = {
paper: `${PREFIX}-paper`,
root: `${PREFIX}-root`
}
const Root = styled('div')((
{
theme
}
) => ({
[`& .${classes.paper}`]: {
border: '1px solid #d3d4d5'
},
[`& .${classes.root}`]: {
'&:focus': {
backgroundColor: theme.palette.primary.main,
'& .MuiListItemIcon-root, & .MuiListItemText-primary': {
color: theme.palette.common.white
}
}
}
}))
function StyledMenu(props) {
return (
<Menu
elevation={0}
anchorOrigin={{
vertical: 'top',
horizontal: 'center'
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'center'
}}
{...props}
/>
)
}
const StyledMenuItem = MenuItem
function AjouteTradiksyon({showSwitch, disableSwitch, tradiksyonOtomatik, setTradiksyonOtomatik, chwaLang, setChwaLang}) {
const [anchorElement, setAnchorElement] = useState(null)
const handleClick = event => {
setAnchorElement(event.currentTarget)
}
const handleClose = event => {
setAnchorElement(null)
const chwaInik = uniq([...chwaLang, event.currentTarget.id])
setChwaLang(chwaInik.filter(c => c !== ''))
}
return (
<>
{showSwitch && (
<OtomatikSwitch tradiksyonOtomatik={tradiksyonOtomatik} setTradiksyonOtomatik={setTradiksyonOtomatik} disabled={disableSwitch} />
)}
<Root style={{textAlign: 'center', marginTop: 20}}>
<Button
aria-controls='lang-menu'
variant='outlined'
aria-haspopup='true'
onClick={handleClick}
>
Ajouter une traduction <br /> 🇺🇸 🇫🇷 🇪🇸 🇩🇪 🇮🇹 🇧🇷 🇯🇵 🇰🇷
</Button>
<StyledMenu
keepMounted
id='lang-menu'
anchorEl={anchorElement}
open={Boolean(anchorElement)}
classes={{
paper: classes.paper
}}
onClose={handleClose}
>
<StyledMenuItem
id='en'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇺🇸 Anglais (US)</StyledMenuItem>
<StyledMenuItem
id='fr'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇫🇷 Français</StyledMenuItem>
<StyledMenuItem
id='es'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇪🇸 Espagnol</StyledMenuItem>
<StyledMenuItem
id='de'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇩🇪 Allemand</StyledMenuItem>
<StyledMenuItem
id='it'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇮🇹 Italien</StyledMenuItem>
<StyledMenuItem
id='pt'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇧🇷 Portugais</StyledMenuItem>
<StyledMenuItem
id='ja'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇯🇵 Japonais</StyledMenuItem>
<StyledMenuItem
id='ko'
classes={{
root: classes.root
}}
onClick={handleClose}
>🇰🇷 Coréen</StyledMenuItem>
</StyledMenu>
</Root>
</>
)
}
AjouteTradiksyon.propTypes = {
showSwitch: PropTypes.bool.isRequired,
disableSwitch: PropTypes.bool.isRequired,
tradiksyonOtomatik: PropTypes.bool.isRequired,
setTradiksyonOtomatik: PropTypes.func.isRequired,
chwaLang: PropTypes.array.isRequired,
setChwaLang: PropTypes.func.isRequired
}
export default AjouteTradiksyon
-109
View File
@@ -1,109 +0,0 @@
import {useState} from 'react'
import {useSession} from 'next-auth/react'
import PropTypes from 'prop-types'
import Button from '@mui/material/Button'
import Avatar from '@mui/material/Avatar'
import List from '@mui/material/List'
import ListItem from '@mui/material/ListItem'
import ListItemAvatar from '@mui/material/ListItemAvatar'
import ListItemText from '@mui/material/ListItemText'
import DialogTitle from '@mui/material/DialogTitle'
import Dialog from '@mui/material/Dialog'
import {green} from '@mui/material/colors'
import Container from '@mui/material/Container'
import CircularProgress from '@mui/material/CircularProgress'
import Typography from '@mui/material/Typography'
import AudiotrackIcon from '@mui/icons-material/Audiotrack'
import {jwennUserEpiToken} from '../../lib/oki-api'
function Chwa(props) {
const {onClose, teksLis, selectedTeks, loading, open} = props
const handleClose = () => {
onClose(selectedTeks)
}
const handleListItemClick = value => {
onClose(value)
}
return (
<Dialog open={open} onClose={handleClose}>
<DialogTitle>Choisir la parole à modifier</DialogTitle>
{loading ? (
<Container sx={{textAlign: 'center', marginBottom: 1}}>
<CircularProgress size={20} sx={{color: '#29d'}} />
</Container>
) : (
<List sx={{pt: 0}}>
{teksLis.length > 0 ? teksLis.map(({id, titre, artistes}) => (
<ListItem key={id} button onClick={() => handleListItemClick(id)}>
<ListItemAvatar>
<Avatar sx={{bgcolor: green[100], color: green[600]}}>
<AudiotrackIcon />
</Avatar>
</ListItemAvatar>
<ListItemText primary={`${new Intl.ListFormat('fr').format(artistes.map(({alias}) => alias))} - ${titre}`} />
</ListItem>
)) : (
<Typography sx={{textAlign: 'center'}} variant='button' component='div'>Aucun texte</Typography>
)}
</List>
)}
</Dialog>
)
}
Chwa.propTypes = {
onClose: PropTypes.func.isRequired,
teksLis: PropTypes.array.isRequired,
loading: PropTypes.bool.isRequired,
open: PropTypes.bool.isRequired,
selectedTeks: PropTypes.object,
}
export default function ChwaTeks({selectedTeks, setSelectedTeks}) {
const {data: session} = useSession()
const [loading, setLoading] = useState(false)
const [open, setOpen] = useState(false)
const [teksLis, setTeksLis] = useState([])
const handleClickOpen = async () => {
setOpen(true)
setLoading(true)
const user = await jwennUserEpiToken(session?.jwt)
const {paroles} = user
const parolesList = paroles && paroles.length > 0 ? paroles : []
setTeksLis(parolesList)
setLoading(false)
}
const handleClose = value => {
const teks = teksLis.find(({id}) => id === value)
setSelectedTeks(teks)
setOpen(false)
}
return (
<Container sx={{textAlign: 'center', marginBottom: 1}} maxWidth='sm'>
<Button variant='outlined' onClick={handleClickOpen}>
Modifier mes paroles
</Button>
<Chwa
selectedTeks={selectedTeks}
teksLis={teksLis}
open={open}
loading={loading}
onClose={handleClose}
/>
</Container>
)
}
ChwaTeks.propTypes = {
selectedTeks: PropTypes.object,
setSelectedTeks: PropTypes.func.isRequired,
}
-469
View File
@@ -1,469 +0,0 @@
import {useCallback, useEffect, forwardRef, useState} from 'react'
import {styled} from '@mui/material/styles'
import PropTypes from 'prop-types'
import axios from 'axios'
import {useSession} from 'next-auth/react'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'
import Container from '@mui/material/Container'
import FormControl from '@mui/material/FormControl'
import FormHelperText from '@mui/material/FormHelperText'
import Grid from '@mui/material/Grid'
import IconButton from '@mui/material/IconButton'
import InputAdornment from '@mui/material/InputAdornment'
import InputLabel from '@mui/material/InputLabel'
import LinearProgress from '@mui/material/LinearProgress'
import OutlinedInput from '@mui/material/OutlinedInput'
import Snackbar from '@mui/material/Snackbar'
import TextField from '@mui/material/TextField'
import Tooltip from '@mui/material/Tooltip'
import Typography from '@mui/material/Typography'
import MuiAlert from '@mui/material/Alert'
import VisibilityOffIcon from '@mui/icons-material/VisibilityOff'
import {jwennAnTeks} from '../../lib/oki-api'
import AjouteTradiksyon from './ajoute-tradiksyon'
const PREFIX = 'EkriTeks'
const classes = {
tooltip: `${PREFIX}-tooltip`
}
const StyledContainer = styled(Container)(() => ({
[`& .${classes.tooltip}`]: {
fontSize: 18
}
}))
const API_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:1337'
const textLabels = {
fr: {
title: 'Traduction',
help: 'Traduction des paroles',
emoji: '&#127467;&#127479;'
},
en: {
title: 'Translation',
help: 'Translation of the lyrics',
emoji: '&#127468;&#127463;'
},
es: {
title: 'Traducción',
help: 'Traducción de la letra',
emoji: '&#127466;&#127480;'
},
de: {
title: 'Übersetzung',
help: 'Übersetzung von Liedtexten',
emoji: '&#127465;&#127466;'
},
it: {
title: 'Traduzione',
help: 'Traduzione del testo',
emoji: '&#127470;&#127481;'
},
pt: {
title: 'Tradução',
help: 'Tradução da letra',
emoji: '&#127463;&#127479;'
},
ja: {
title: '翻訳',
help: '歌詞の翻訳',
emoji: '&#127471;&#127477;'
},
ko: {
title: '번역',
help: '가사 번역',
emoji: '&#127472;&#127479;'
}
}
const Alert = forwardRef(function Alert(props, ref) {
return <MuiAlert ref={ref} elevation={6} variant='filled' {...props} />
})
const RemoveTooltip = Tooltip
function EkriTeks({canAutoTranslate, selectedTeks, setSelectedTeks}) {
const {data: session} = useSession()
const {jwt, user} = session
const [teksEkri, setTeksEkri] = useState({awtis: '', tit: '', transkripsyon: ''})
const [tradiksyon, setTradiksyon] = useState({fr: '', en: '', es: '', de: '', it: '', pt: '', ja: '', ko: ''})
const [kiChawLang, setKiChwalang] = useState(['fr'])
const [error, setError] = useState('')
const [success, setSuccess] = useState('')
const [loading, setLoading] = useState(false)
const [currentTeksId, setCurrentTeksId] = useState(null)
const [open, setOpen] = useState(false)
const [tradiksyonOtomatik, setTradiksyonOtomatik] = useState(false)
const handleUpdate = useCallback(update => {
setTeksEkri({...teksEkri, ...update})
}, [teksEkri])
const handleUpdateTradiksyon = useCallback(update => {
setTradiksyon({...tradiksyon, ...update})
}, [tradiksyon])
const handleRemoveTradiksyon = useCallback(idTradiksyon => {
setKiChwalang(kiChawLang.filter(t => t !== idTradiksyon))
}, [kiChawLang, setKiChwalang])
const handleClose = (event, reason) => {
if (reason === 'clickaway') {
return
}
setOpen(false)
setSuccess('')
setError('')
}
const handleClick = async () => {
setLoading(true)
const {awtis, tit, transkripsyon} = teksEkri
const {fr, en, es, de, it, pt, ja, ko} = tradiksyon
if (teksEkri.awtis === '' || teksEkri.tit === '' || teksEkri.transkripsyon === '') {
setError({message: 'Certains champs sont obligatoires'})
setLoading(false)
return
}
const headers = {
'content-type': 'application/json',
Authorization: `Bearer ${jwt}`
}
if (currentTeksId) {
try {
const teks = await jwennAnTeks(currentTeksId, jwt)
const teksResponse = await axios.put(`${API_URL}/paroles/${currentTeksId}`, {
data: {
id: teks.id,
titre: tit,
transcription: transkripsyon,
traductions: {
francais: fr === '' ? null : fr,
anglais: en === '' ? null : en,
espagnol: es === '' ? null : es,
allemand: de === '' ? null : de,
italien: it === '' ? null : it,
portugais: pt === '' ? null : pt,
japonais: ja === '' ? null : ja,
coreen: ko === '' ? null : ko
},
user: {
id: user.id,
username: user.username,
email: user.email
},
artistes: teks.artistes.map(({id}) => id),
traductionAuto: tradiksyonOtomatik
}
}, {
headers
})
const {data} = teksResponse
setSuccess(`Le morceau "${data.tit}" a été modifié avec succès. Il apparaîtra sur le site après validation.`)
setLoading(false)
} catch (error) {
setError(error?.response?.data)
setLoading(false)
}
} else {
try {
const artiste = await axios.post(`${API_URL}/artistes`, {
data: {
alias: awtis,
user: {
id: user.id,
username: user.username,
email: user.email
}
}
}, {
headers
})
const parole = await axios.post(`${API_URL}/paroles`, {
data: {
titre: tit,
transcription: transkripsyon,
traductions: {
francais: fr === '' ? null : fr,
anglais: en === '' ? null : en,
espagnol: es === '' ? null : es,
allemand: de === '' ? null : de,
italien: it === '' ? null : it,
portugais: pt === '' ? null : pt,
japonais: ja === '' ? null : ja,
coreen: ko === '' ? null : ko
},
user: {
id: user.id,
username: user.username,
email: user.email
},
artistes: [artiste.id],
traductionAuto: tradiksyonOtomatik
}
}, {
headers
})
const {data} = parole
setSuccess(`Le texte "${data.titre}" a été soumis avec succès. Il apparaîtra sur le site après validation.`)
setLoading(false)
} catch (error) {
setError(error?.response?.data)
setLoading(false)
}
}
}
const handleReset = () => {
setTeksEkri({awtis: '', tit: '', transkripsyon: ''})
setTradiksyon({fr: '', en: '', es: '', de: '', it: '', pt: '', ja: '', ko: ''})
setKiChwalang(['fr'])
setCurrentTeksId(null)
setTradiksyonOtomatik(false)
}
useEffect(() => {
if (success) {
setOpen(true)
handleReset()
}
}, [success])
useEffect(() => {
if (error) {
setOpen(true)
}
}, [error])
useEffect(() => {
if (selectedTeks) {
setTradiksyonOtomatik(false)
setCurrentTeksId(selectedTeks.id)
setTeksEkri({
awtis: new Intl.ListFormat('fr').format(selectedTeks.artistes.map(({alias}) => alias)),
tit: selectedTeks.titre,
transkripsyon: selectedTeks.transcription
})
setTradiksyon({
fr: selectedTeks?.traductions?.francais || '',
en: selectedTeks?.traductions?.anglais || '',
es: selectedTeks?.traductions?.espagnol || '',
de: selectedTeks?.traductions?.allemand || '',
it: selectedTeks?.traductions?.italien || '',
pt: selectedTeks?.traductions?.portugais || '',
ja: selectedTeks?.traductions?.japonais || '',
ko: selectedTeks?.traductions?.coreen || ''
})
const jennLangId = () => {
const ids = []
if (selectedTeks?.traductions?.francais) {
ids.push('fr')
}
if (selectedTeks?.traductions?.anglais) {
ids.push('en')
}
if (selectedTeks?.traductions?.espagnol) {
ids.push('es')
}
if (selectedTeks?.traductions?.allemand) {
ids.push('de')
}
if (selectedTeks?.traductions?.italien) {
ids.push('it')
}
if (selectedTeks?.traductions?.portugais) {
ids.push('pt')
}
if (selectedTeks?.traductions?.japonais) {
ids.push('ja')
}
if (selectedTeks?.traductions?.coreen) {
ids.push('ko')
}
return ids
}
const langIds = jennLangId()
setKiChwalang(langIds)
}
return () => {
setSelectedTeks(null)
}
}, [teksEkri, selectedTeks, setSelectedTeks, kiChawLang])
return (
<StyledContainer maxWidth='sm'>
<Box sx={{textAlign: 'center', marginBottom: 2}}>
<Typography display='inline' variant='h6' component='h1'>
Proposer une parole
</Typography>
</Box>
<form noValidate autoComplete='off'>
<Grid container style={{textAlign: 'center'}} spacing={1}>
<Grid xs='true'>
<TextField
required
id='awtis'
name='awtis'
label='Artiste'
value={teksEkri.awtis}
disabled={Boolean(currentTeksId)}
variant='outlined'
helperText='Nom de lartiste (obligatoire)'
onChange={event => handleUpdate({awtis: event.target.value})}
/>
</Grid>
<Grid xs='true'>
<TextField
required
id='tit'
name='tit'
label='Titre'
value={selectedTeks?.tit || teksEkri.tit}
variant='outlined'
helperText='Titre de la musique (obligatoire)'
onChange={event => handleUpdate({tit: event.target.value})}
/>
</Grid>
</Grid>
<TextField
fullWidth
multiline
required
helperText='Transcription des paroles (obligatoire)'
style={{marginTop: '1em'}}
id='transkripsyon'
label='Transcription'
value={teksEkri.transkripsyon}
rows={8}
variant='outlined'
onChange={event => handleUpdate({transkripsyon: event.target.value})}
/>
{kiChawLang.length > 0 && kiChawLang.map(k => (
<FormControl key={k} fullWidth style={{marginBlock: 10}}>
<InputLabel htmlFor={`tradiksyon-${k}`}>
<span dangerouslySetInnerHTML={{__html: textLabels[k].emoji}} /> {textLabels[k].title}
</InputLabel>
<OutlinedInput
multiline
style={{marginTop: '1em'}}
id={`tradiksyon-${k}`}
name={`tradiksyon-${k}`}
label={`${textLabels[k].title}`}
value={tradiksyon[k]}
rows={8}
endAdornment={
<InputAdornment position='end'>
<RemoveTooltip
title='Cacher la fenêtre'
placement='left'
classes={{
tooltip: classes.tooltip
}}
>
<IconButton
style={{bottom: 75}}
aria-label='remove-tradiksyon'
edge='end'
size='large'
onClick={() => handleRemoveTradiksyon(k)}
>
<VisibilityOffIcon />
</IconButton>
</RemoveTooltip>
</InputAdornment>
}
onChange={event => handleUpdateTradiksyon({[k]: event.target.value})}
/>
<FormHelperText id={`tradiksyon-${k}`}>{textLabels[k].help}</FormHelperText>
</FormControl>
))}
</form>
<AjouteTradiksyon
showSwitch={Boolean(canAutoTranslate) && Boolean(!currentTeksId)}
disableSwitch={tradiksyon.fr === '' || tradiksyon.en !== '' || tradiksyon.es !== '' || tradiksyon.de !== '' || tradiksyon.it !== '' || tradiksyon.pt !== '' || tradiksyon.ja !== '' || tradiksyon.ko !== ''}
tradiksyonOtomatik={tradiksyonOtomatik}
setTradiksyonOtomatik={setTradiksyonOtomatik}
chwaLang={kiChawLang}
setChwaLang={setKiChwalang}
/>
<div>
<Button
fullWidth
disabled={loading || teksEkri.awtis === '' || teksEkri.tit === '' || teksEkri.transkripsyon === ''}
style={{marginTop: 20}}
variant='contained'
color='primary'
onClick={handleClick}
>
<Typography style={{fontWeight: 'bold'}}>
Valider
</Typography>
</Button>
<Button
fullWidth
disabled={loading || (teksEkri.awtis === '' && teksEkri.tit === '' && teksEkri.transkripsyon === '')}
style={{marginTop: 20}}
variant='contained'
color='error'
onClick={handleReset}
>
<Typography style={{fontWeight: 'bold'}}>
Tout effacer
</Typography>
</Button>
{loading && <LinearProgress size={24} style={{width: '100%', marginBlock: '1em'}} />}
</div>
{success && (
<Snackbar open={open} autoHideDuration={10_000} onClose={handleClose}>
<Alert severity='success' onClose={handleClose}>
<strong>{success}</strong>
</Alert>
</Snackbar>
)}
{error && (
<Snackbar open={open} autoHideDuration={10_000} onClose={handleClose}>
<Alert severity='error' onClose={handleClose}>
<strong>Une erreur sest produite</strong> : <i>{error?.error?.message}</i>
</Alert>
</Snackbar>
)}
</StyledContainer>
)
}
EkriTeks.propTypes = {
canAutoTranslate: PropTypes.bool,
selectedTeks: PropTypes.object,
setSelectedTeks: PropTypes.func.isRequired,
}
export default EkriTeks
-26
View File
@@ -1,26 +0,0 @@
import PropTypes from 'prop-types'
import FormGroup from '@mui/material/FormGroup'
import FormControlLabel from '@mui/material/FormControlLabel'
import Switch from '@mui/material/Switch'
export default function OtomatikSwitch({tradiksyonOtomatik, setTradiksyonOtomatik, disabled}) {
const handleChange = event => {
setTradiksyonOtomatik(event.target.checked)
}
return (
<FormGroup sx={{marginTop: 1}}>
<FormControlLabel
control={<Switch checked={tradiksyonOtomatik && !disabled} onChange={handleChange} />}
disabled={disabled}
label='Traduction du français vers les autres langues (remplir uniquement la traduction française)'
/>
</FormGroup>
)
}
OtomatikSwitch.propTypes = {
tradiksyonOtomatik: PropTypes.bool.isRequired,
setTradiksyonOtomatik: PropTypes.func.isRequired,
disabled: PropTypes.bool.isRequired
}
-42
View File
@@ -1,42 +0,0 @@
import PropTypes from 'prop-types'
import {styled} from '@mui/material/styles'
import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
import LinearProgress, {linearProgressClasses} from '@mui/material/LinearProgress'
const BorderLinearProgress = styled(LinearProgress)(({theme}) => ({
height: 10,
borderRadius: 5,
[`&.${linearProgressClasses.colorPrimary}`]: {
backgroundColor: 200,
...theme.applyStyles('dark', {
backgroundColor: 800
})
},
[`& .${linearProgressClasses.bar}`]: {
borderRadius: 5,
backgroundColor: '#1a90ff',
...theme.applyStyles('dark', {
backgroundColor: '#308fe8'
})
},
}))
export default function BarStats({percent}) {
return (
<Box sx={{display: 'flex', alignItems: 'center'}}>
<Box sx={{width: '100%'}}>
<BorderLinearProgress variant='determinate' value={percent} />
</Box>
<Box sx={{minWidth: 35}}>
<Typography textAlign='right' sx={{fontWeight: 'bold'}} variant='body2' color='text.secondary'>{`${Math.round(
percent,
)}%`}</Typography>
</Box>
</Box>
)
}
BarStats.propTypes = {
percent: PropTypes.number.isRequired,
}
-28
View File
@@ -1,28 +0,0 @@
import PropTypes from 'prop-types'
import Card from '@mui/material/Card'
import CardContent from '@mui/material/CardContent'
import Typography from '@mui/material/Typography'
import Grid from '@mui/material/Grid'
import TraductionsStats from './traductions-stats'
export default function KatStats({emoji, value, total}) {
return (
<Grid size={{xs: 12, md: 2}}>
<Card variant='outlined'>
<CardContent>
<Typography align='center' variant='h6'>
<span dangerouslySetInnerHTML={{__html: emoji}} />
</Typography>
<TraductionsStats value={value} total={total} />
</CardContent>
</Card>
</Grid>
)
}
KatStats.propTypes = {
emoji: PropTypes.string.isRequired,
value: PropTypes.number.isRequired,
total: PropTypes.number.isRequired
}
-29
View File
@@ -1,29 +0,0 @@
import PropTypes from 'prop-types'
import Typography from '@mui/material/Typography'
import Box from '@mui/material/Box'
import Grid from '@mui/material/Grid'
import BarStats from './bar-stats'
export default function TraductionsStats({value, total}) {
const translated = total - value
const percent = (translated / total) * 100
return (
<Box sx={{width: '100%'}}>
<Grid container rowSpacing={3} columnSpacing={6} justifyContent='space-around'>
<Grid size={{xs: 12, md: 6}}>
<Typography style={{fontWeight: 'bold'}} align='center' variant='h6'>
{translated}
</Typography>
</Grid>
</Grid>
<BarStats value={value} percent={percent} />
</Box>
)
}
TraductionsStats.propTypes = {
value: PropTypes.number.isRequired,
total: PropTypes.number.isRequired
}