Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

162 add a modal when submitting an osbl creation to get metadata about the contribution #164

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 101 additions & 0 deletions app/frontend/components/pages/contribution/new/ContributionDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { ReactElement, ChangeEvent } from 'react'
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '@/components/ui/dialog'
import { Button } from '@/components/ui/button'
import { Alert, AlertDescription } from '@/components/ui/alert'
// @ts-expect-error
import GoodIdea from '@/assets/icons/good-idea.svg?react'
import MyFileInput from '@/components/shared/MyFileInput'
import { Label } from '@/components/ui/label'
import { Textarea } from '@/components/ui/textarea'
import { Save } from 'lucide-react'
import { ContributionData } from '@/pages/Contribution/types'

interface ContributionDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
contribution: ContributionData
setContributionField: (
field: keyof ContributionData,
value: ContributionData[keyof ContributionData]
) => void
onConfirm: () => void
error: string | undefined
clearError: () => void
processing: boolean
}

export default function ContributionDialog ({
open,
onOpenChange,
contribution,
setContributionField,
onConfirm,
error,
clearError,
processing
}: ContributionDialogProps): ReactElement {
const handleCommentChange = (e: ChangeEvent<HTMLTextAreaElement>): void => {
setContributionField('body', e.target.value)
}

const handleFileChange = (file: File | File[] | undefined): void => {
if (file === undefined) {
setContributionField('files', [])
} else if (Array.isArray(file)) {
setContributionField('files', file)
} else {
setContributionField('files', [file])
}
clearError()
}

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent>
<DialogHeader>
<DialogTitle className='text-xl'>Enregistrer votre contribution</DialogTitle>
<DialogDescription className='sr-only'>
Enregistrez votre contribution pour la soumettre à la modération.
</DialogDescription>
</DialogHeader>

<Alert className='mt-4'>
<GoodIdea className='min-w-8 min-h-8' />
<AlertDescription>
Pour que votre contribution soit validée, le modérateur doit pouvoir vérifier les informations fournies. Facilitez son travail en indiquant clairement vos sources !
</AlertDescription>
</Alert>

<div className='mt-8 gap-8 flex flex-col w-full'>
<div className='flex flex-col gap-4'>
<Label htmlFor='comment'>Commentaire :</Label>
<Textarea
id='comment'
placeholder='Fournissez vos sources, les liens webs qui vous ont servi de référence, ...'
value={contribution.body}
onChange={handleCommentChange}
className='bg-white focus-visible:ring-0 focus-visible:border-primary placeholder:text-ellipsis placeholder:text-xs md:placeholder:text-sm focus-visible:ring-offset-0 w-auto flex-grow h-40'
/>
</div>
<div className='flex flex-col gap-4'>
<MyFileInput
id='files'
labelText='Fichiers complémentaires :'
multiple
onChange={handleFileChange}
file={contribution.files}
error={error}
/>
</div>
</div>

<DialogFooter className='mt-8'>
<Button onClick={onConfirm} disabled={processing}>
<Save className='mr-2' />
Enregistrer
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
}
70 changes: 34 additions & 36 deletions app/frontend/components/pages/contribution/new/OsblDatasheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,25 @@ const OsblTypeList = [
{ value: 'fondation', label: 'Fondation' }
]

const TaxReductionList = [
{
value: 'intérêt_général',
label: '66 %',
tooltip: {
title: 'Les associations d\'intérêt général, ou reconnues d\'utilité publique (ARUP).',
description: 'Elles ouvrent le droit à une réduction d\'impôt de 66 %.'
}
},
{
value: 'aide_aux_personnes_en_difficulté',
label: '75 %',
tooltip: {
title: 'Les organismes d\'aide aux personnes en difficulté.',
description: 'Elles ouvrent le droit à une réduction d\'impôt de 75 %, jusqu\'à 1 000 € de dons. Le régime standard de réduction à 66 % s\'applique ensuite.'
}
}
]

export default function OsblDatasheet ({ data, setData, errors, clearErrors }: Omit<FormProps, 'setError'>): ReactElement {
const causes = usePage().props.causes as Record<string, number>
const labels = usePage().props.labels as Array<{ value: string, label: string }>
Expand Down Expand Up @@ -91,41 +110,21 @@ export default function OsblDatasheet ({ data, setData, errors, clearErrors }: O
clearErrors('tax_reduction')
}}
>
<div className='flex items-center justify-between'>
<Label
htmlFor='interet-general'
className='flex items-center justify-between w-16 cursor-pointer'
onClick={() => {
setData('tax_reduction', 'intérêt_général')
clearErrors('tax_reduction')
}}
>
66 %
<HelpTooltip>
<h2 className='font-semibold mb-4'>Les associations d'intérêt général, ou reconnues d'utilité publique (ARUP).</h2>
<p>Elles ouvrent le droit à une réduction d'impôt de 66 %.</p>
</HelpTooltip>
</Label>
<RadioGroupItem value='intérêt_général' id='interet-general' />
</div>
<div className='flex items-center justify-between'>
<Label
htmlFor='aide-difficulte'
className='flex items-center justify-between w-16 cursor-pointer'
onClick={() => {
setData('tax_reduction', 'aide_aux_personnes_en_difficulté')
clearErrors('tax_reduction')
}}
>
75 %
<HelpTooltip>
<h2 className='font-semibold mb-4'>Les organismes d'aide aux personnes en difficulté.</h2>
<p>Elles ouvrent le droit à une réduction d'impôt de 75 %, jusqu'à 1 000 € de dons.</p>
<p>Le régime standard de réduction à 66 % s'applique ensuite.</p>
</HelpTooltip>
</Label>
<RadioGroupItem value='aide_aux_personnes_en_difficulté' id='aide-difficulte' />
</div>
{TaxReductionList.map((item) => (
<div className='flex items-center justify-between' key={item.value}>
<Label
htmlFor={item.value}
className='flex items-center justify-between w-16 cursor-pointer'
>
{item.label}
<HelpTooltip>
<h2 className='font-semibold mb-4'>{item.tooltip.title}</h2>
<p>{item.tooltip.description}</p>
</HelpTooltip>
</Label>
<RadioGroupItem value={item.value} id={item.value} />
</div>
))}
</RadioGroup>
{Boolean(errors.tax_reduction) && <InputError>{errors.tax_reduction}</InputError>}
</div>
Expand Down Expand Up @@ -207,7 +206,6 @@ export default function OsblDatasheet ({ data, setData, errors, clearErrors }: O
options={labels}
onValueChange={(value) => {
setData('osbls_labels_attributes', value.map(label => ({ label_id: label })))
clearErrors('osbls_labels_attributes')
}}
placeholder=''
variant='default'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export default function OsblDocuments ({ data, setData, errors, clearErrors, set
<Button
onClick={(e) => handleDocumentRemove(e, index)}
variant='outline'
className='bg-white text-red-500 border-none'
className='bg-white text-red-600 border-none'
>
<TrashIcon className='w-4 h-4' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactElement, useRef, useState } from 'react'
import { Document, FormProps } from '@/pages/Contribution/types'
import UnsavedChangesAlert from '@/components/shared/UnsavedChangesAlert'
import { Button } from '@/components/ui/button'
import MyInput from '@/components/shared/MyInput'
import {
Expand Down Expand Up @@ -30,6 +31,8 @@ import { Label } from '@/components/ui/label'
import deepCleanData from '@/lib/deepCleanData'
import { z } from 'zod'
import { usePage } from '@inertiajs/react'
import getAllowedFormats from '@/lib/getAllowedFormats'

interface Props extends Omit<FormProps, 'data' | 'setData'> {
document: Partial<Document>
index: number
Expand All @@ -41,12 +44,8 @@ const ALLOWED_DOCUMENT_TYPES = ['application/pdf']

const documentValidation = z.object({
file: z.instanceof(File)
.refine((file) => {
return file.size <= MAX_DOCUMENT_SIZE
}, 'La taille du fichier doit être inférieure à 5 MB.')
.refine((file) => {
return ALLOWED_DOCUMENT_TYPES.includes(file.type)
}, 'Le type de fichier est invalide. Format accepté : PDF.')
.refine((file) => file.size <= MAX_DOCUMENT_SIZE, 'La taille du fichier doit être inférieure à 5 MB.')
.refine((file) => ALLOWED_DOCUMENT_TYPES.includes(file.type), `Le type de fichier est invalide. Format accepté : ${getAllowedFormats(ALLOWED_DOCUMENT_TYPES)}.`)
})

export default function OsblDocumentSheet ({
Expand Down Expand Up @@ -103,6 +102,8 @@ export default function OsblDocumentSheet ({
</SheetDescription>
</SheetHeader>

<UnsavedChangesAlert<Partial<Document>> originalData={document} currentData={sheetDocument} />

<div className='flex flex-col gap-8 mt-8'>
<div className='flex flex-col gap-4'>
<Label>Type de document *</Label>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export default function OsblFinances ({ data, setData, errors, clearErrors, setE
<Button
onClick={(e) => handleFinanceRemove(e, index)}
variant='outline'
className='bg-white text-red-500 border-none'
className='bg-white text-red-600 border-none'
>
<TrashIcon className='w-4 h-4' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ export default function FundManagementSection ({
)}

{items.map((item, index) => (
<Fragment key={`${baseErrorPath}-${index}`}>
<Fragment key={`${baseErrorPath}-${index}-${item.type}`}>
<div
className='flex flex-wrap items-center gap-x-4 sm:flex-nowrap sm:items-center sm:space-x-4'
>
Expand Down Expand Up @@ -131,7 +131,7 @@ export default function FundManagementSection ({
<Button
onClick={(e) => handleRemove(e, index)}
variant='ghost'
className='text-red-500 hover:text-red-700 p-0 h-auto mt-4'
className='text-red-600 hover:text-red-700 p-0 h-auto mt-4'
>
<TrashIcon className='w-4 h-4' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { ReactElement, useRef, useState } from 'react'
import { AnnualFinance, FormProps } from '@/pages/Contribution/types'
import UnsavedChangesAlert from '@/components/shared/UnsavedChangesAlert'
import MyNumberInput from '@/components/shared/MyNumberInput'
import HelpTooltip from '@/components/shared/HelpTooltip'
import FundManagementSection from '@/components/pages/contribution/new/OsblFinances/FundManagementSection'
Expand Down Expand Up @@ -131,6 +132,8 @@ export default function OsblFinanceSheet ({
</SheetDescription>
</SheetHeader>

<UnsavedChangesAlert<Partial<AnnualFinance>> originalData={finance} currentData={sheetFinance} />

<div className='flex flex-col gap-8 mt-8'>
<MyNumberInput
autoFocus
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export default function OsblHeader ({ data, setData, errors, clearErrors }: Omit
maxLength={300}
onChange={(e) => {
setData('description', e.target.value)
clearErrors('description')
}}
className='bg-white focus-visible:ring-0 focus-visible:border-primary placeholder:text-ellipsis placeholder:text-xs md:placeholder:text-sm focus-visible:ring-offset-0 w-auto flex-grow h-40'
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default function OsblLocations ({ data, setData }: Pick<FormProps, 'data'
<Button
onClick={(e) => handleLocationRemove(e, index)}
variant='outline'
className='bg-white text-red-500 border-none'
className='bg-white text-red-600 border-none'
>
<TrashIcon className='w-4 h-4' />
</Button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactElement, useRef, useState } from 'react'
import { Location } from '@/pages/Contribution/types'
import UnsavedChangesAlert from '@/components/shared/UnsavedChangesAlert'
import { Button } from '@/components/ui/button'
import MyInput from '@/components/shared/MyInput'
import {
Expand Down Expand Up @@ -28,6 +29,7 @@ import MyAsyncSelect from '@/components/shared/MyAsyncSelect'
import deepCleanData from '@/lib/deepCleanData'
import { Label } from '@/components/ui/label'
import { usePage } from '@inertiajs/react'

interface Props {
location: Partial<Location>
index: number
Expand Down Expand Up @@ -100,6 +102,8 @@ export default function OsblLocationSheet ({
</SheetDescription>
</SheetHeader>

<UnsavedChangesAlert<Partial<Location>> originalData={location} currentData={sheetLocation} />

<div className='flex flex-col gap-8 mt-8'>
<div className='flex flex-col gap-4'>
<Label>Type de lieu *</Label>
Expand Down
19 changes: 6 additions & 13 deletions app/frontend/components/pages/user/show/UserAvatarEdit.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState, useEffect, ReactElement } from 'react'
import { useForm, router } from '@inertiajs/react'
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover'
import { validate } from '@/lib/validate'
import {
AlertDialog,
AlertDialogAction,
Expand All @@ -19,19 +20,16 @@ import { cn } from '@/lib/utils'
import { Pencil, Trash2 } from 'lucide-react'
import { z } from 'zod'
import MyFileInput from '@/components/shared/MyFileInput'
import getAllowedFormats from '@/lib/getAllowedFormats'

const ALLOWED_CONTENT_TYPES = ['image/png', 'image/jpg', 'image/webp', 'image/jpeg']
const MAX_PROFILE_PICTURE_SIZE = 1 * 1024 * 1024 // 1MB

const profilePicSchema = z.object({
profile_picture: z
.instanceof(File)
.refine((file) => {
return file.size <= MAX_PROFILE_PICTURE_SIZE
}, 'La taille du fichier doit être inférieure à 1 MB.')
.refine((file) => {
return ALLOWED_CONTENT_TYPES.includes(file.type)
}, 'Le type de fichier est invalide. Formats acceptés : PNG, JPG, JPEG, WEBP.')
.refine((file) => file.size <= MAX_PROFILE_PICTURE_SIZE, 'La taille du fichier doit être inférieure à 1 MB.')
.refine((file) => ALLOWED_CONTENT_TYPES.includes(file.type), `Le type de fichier est invalide. Format accepté : ${getAllowedFormats(ALLOWED_CONTENT_TYPES)}.`)
})

interface UserAvatarProps {
Expand Down Expand Up @@ -61,12 +59,7 @@ export default function UserAvatarEdit ({
function submit (e: React.FormEvent): void {
e.preventDefault()

const result = profilePicSchema.safeParse(data)
if (!result.success) {
const issues = result.error.issues
issues.forEach(issue => {
setError('profile_picture', issue.message)
})
if (!validate(profilePicSchema, data, setError)) {
return
}

Expand Down Expand Up @@ -148,7 +141,7 @@ export default function UserAvatarEdit ({
required
accept='image/png, image/webp, image/jpg'
onChange={(file) => {
setData('profile_picture', file ?? '')
setData('profile_picture', file as File)
clearErrors('profile_picture')
}}
mpressen marked this conversation as resolved.
Show resolved Hide resolved
error={errors.profile_picture}
Expand Down
Loading