Files
spliit/src/components/group-form.tsx
Sebastien Castiel 210c12b7ef Use tRPC in other group pages (#249)
* Use tRPC in group edition + group layout

* Use tRPC in group modals

* Use tRPC in group stats

* Use tRPC in group activity
2024-10-19 21:29:53 -04:00

335 lines
11 KiB
TypeScript

import { SubmitButton } from '@/components/submit-button'
import { Button } from '@/components/ui/button'
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from '@/components/ui/card'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form'
import {
HoverCard,
HoverCardContent,
HoverCardTrigger,
} from '@/components/ui/hover-card'
import { Input } from '@/components/ui/input'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select'
import { getGroup } from '@/lib/api'
import { GroupFormValues, groupFormSchema } from '@/lib/schemas'
import { zodResolver } from '@hookform/resolvers/zod'
import { Save, Trash2 } from 'lucide-react'
import { useTranslations } from 'next-intl'
import Link from 'next/link'
import { useEffect, useState } from 'react'
import { useFieldArray, useForm } from 'react-hook-form'
import { Textarea } from './ui/textarea'
export type Props = {
group?: NonNullable<Awaited<ReturnType<typeof getGroup>>>
onSubmit: (
groupFormValues: GroupFormValues,
participantId?: string,
) => Promise<void>
protectedParticipantIds?: string[]
}
export function GroupForm({
group,
onSubmit,
protectedParticipantIds = [],
}: Props) {
const t = useTranslations('GroupForm')
const form = useForm<GroupFormValues>({
resolver: zodResolver(groupFormSchema),
defaultValues: group
? {
name: group.name,
information: group.information ?? '',
currency: group.currency,
participants: group.participants,
}
: {
name: '',
information: '',
currency: '',
participants: [
{ name: t('Participants.John') },
{ name: t('Participants.Jane') },
{ name: t('Participants.Jack') },
],
},
})
const { fields, append, remove } = useFieldArray({
control: form.control,
name: 'participants',
keyName: 'key',
})
const [activeUser, setActiveUser] = useState<string | null>(null)
useEffect(() => {
if (activeUser === null) {
const currentActiveUser =
fields.find(
(f) => f.id === localStorage.getItem(`${group?.id}-activeUser`),
)?.name || t('Settings.ActiveUserField.none')
setActiveUser(currentActiveUser)
}
}, [t, activeUser, fields, group?.id])
const updateActiveUser = () => {
if (!activeUser) return
if (group?.id) {
const participant = group.participants.find((p) => p.name === activeUser)
if (participant?.id) {
localStorage.setItem(`${group.id}-activeUser`, participant.id)
} else {
localStorage.setItem(`${group.id}-activeUser`, activeUser)
}
} else {
localStorage.setItem('newGroup-activeUser', activeUser)
}
}
return (
<Form {...form}>
<form
onSubmit={form.handleSubmit(async (values) => {
await onSubmit(
values,
group?.participants.find((p) => p.name === activeUser)?.id ??
undefined,
)
})}
>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('title')}</CardTitle>
</CardHeader>
<CardContent className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('NameField.label')}</FormLabel>
<FormControl>
<Input
className="text-base"
placeholder={t('NameField.placeholder')}
{...field}
/>
</FormControl>
<FormDescription>
{t('NameField.description')}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="currency"
render={({ field }) => (
<FormItem>
<FormLabel>{t('CurrencyField.label')}</FormLabel>
<FormControl>
<Input
className="text-base"
placeholder={t('CurrencyField.placeholder')}
max={5}
{...field}
/>
</FormControl>
<FormDescription>
{t('CurrencyField.description')}
</FormDescription>
<FormMessage />
</FormItem>
)}
/>
<div className="col-span-2">
<FormField
control={form.control}
name="information"
render={({ field }) => (
<FormItem>
<FormLabel>{t('InformationField.label')}</FormLabel>
<FormControl>
<Textarea
rows={2}
className="text-base"
{...field}
placeholder={t('InformationField.placeholder')}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</div>
</CardContent>
</Card>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('Participants.title')}</CardTitle>
<CardDescription>{t('Participants.description')}</CardDescription>
</CardHeader>
<CardContent>
<ul className="flex flex-col gap-2">
{fields.map((item, index) => (
<li key={item.key}>
<FormField
control={form.control}
name={`participants.${index}.name`}
render={({ field }) => (
<FormItem>
<FormLabel className="sr-only">
Participant #{index + 1}
</FormLabel>
<FormControl>
<div className="flex gap-2">
<Input
className="text-base"
{...field}
placeholder={t('Participants.new')}
/>
{item.id &&
protectedParticipantIds.includes(item.id) ? (
<HoverCard>
<HoverCardTrigger>
<Button
variant="ghost"
className="text-destructive-"
type="button"
size="icon"
disabled
>
<Trash2 className="w-4 h-4 text-destructive opacity-50" />
</Button>
</HoverCardTrigger>
<HoverCardContent
align="end"
className="text-sm"
>
{t('Participants.protectedParticipant')}
</HoverCardContent>
</HoverCard>
) : (
<Button
variant="ghost"
className="text-destructive"
onClick={() => remove(index)}
type="button"
size="icon"
>
<Trash2 className="w-4 h-4" />
</Button>
)}
</div>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</li>
))}
</ul>
</CardContent>
<CardFooter>
<Button
variant="secondary"
onClick={() => {
append({ name: '' })
}}
type="button"
>
{t('Participants.add')}
</Button>
</CardFooter>
</Card>
<Card className="mb-4">
<CardHeader>
<CardTitle>{t('Settings.title')}</CardTitle>
<CardDescription>{t('Settings.description')}</CardDescription>
</CardHeader>
<CardContent>
<div className="grid sm:grid-cols-2 gap-4">
{activeUser !== null && (
<FormItem>
<FormLabel>{t('Settings.ActiveUserField.label')}</FormLabel>
<FormControl>
<Select
onValueChange={(value) => {
setActiveUser(value)
}}
defaultValue={activeUser}
>
<SelectTrigger>
<SelectValue
placeholder={t(
'Settings.ActiveUserField.placeholder',
)}
/>
</SelectTrigger>
<SelectContent>
{[
{ name: t('Settings.ActiveUserField.none') },
...form.watch('participants'),
]
.filter((item) => item.name.length > 0)
.map(({ name }) => (
<SelectItem key={name} value={name}>
{name}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormDescription>
{t('Settings.ActiveUserField.description')}
</FormDescription>
</FormItem>
)}
</div>
</CardContent>
</Card>
<div className="flex mt-4 gap-2">
<SubmitButton
loadingContent={t(group ? 'Settings.saving' : 'Settings.creating')}
onClick={updateActiveUser}
>
<Save className="w-4 h-4 mr-2" />{' '}
{t(group ? 'Settings.save' : 'Settings.create')}
</SubmitButton>
{!group && (
<Button variant="ghost" asChild>
<Link href="/groups">{t('Settings.cancel')}</Link>
</Button>
)}
</div>
</form>
</Form>
)
}