Skip to content

Commit

Permalink
wip: team members form
Browse files Browse the repository at this point in the history
  • Loading branch information
katallaxie committed Jan 22, 2024
1 parent 89e4682 commit 188f382
Show file tree
Hide file tree
Showing 14 changed files with 322 additions and 44 deletions.
10 changes: 8 additions & 2 deletions packages/app/src/app/teams/[team]/settings/members/page.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Separator } from '@/components/ui/separator'
import { SettingsGeneralForm } from '@/components/teams/settings-general-form'
import { PropsWithChildren, Suspense } from 'react'
import { LoadingSpinner } from '@/components/loading-spinner'
import { SettingsMembersForm } from '@/components/teams/settings-members-form'
import { AddMemberForm } from '@/components/teams/add-member-form'

export interface NextPageProps<Team = string> {
params: { team: Team }
Expand All @@ -18,8 +19,13 @@ export default function Page({ params }: PropsWithChildren<NextPageProps>) {
</p>
</div>
<Separator />

<Suspense fallback={<LoadingSpinner />}>
<SettingsMembersForm teamId={params.team} />
</Suspense>

<Suspense fallback={<LoadingSpinner />}>
<SettingsGeneralForm teamId={params.team} />
<AddMemberForm />
</Suspense>
</>
)
Expand Down
36 changes: 4 additions & 32 deletions packages/app/src/components/team-switcher.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,36 +161,6 @@ export default function TeamSwitcher({
))}
</CommandGroup>
))}
{/* <CommandGroup heading="Personal Account">
<CommandItem
onSelect={() => router.push('/dashboard')}
className="text-sm"
>
<Avatar className="mr-2 h-5 w-5">
<AvatarImage
src={me?.user.image ?? ''}
alt={me?.user.name ?? ''}
className="grayscale"
/>
<AvatarFallback>SC</AvatarFallback>
</Avatar>
{me?.user.name}
</CommandItem>
</CommandGroup>
<CommandGroup heading="Teams">
{teams?.map(team => (
<CommandItem
key={team.value}
className="text-sm"
onSelect={() => {
rhfActionSetScope(team.value)
router.push(`/teams/${team.value}`)
}}
>
{team.label}
</CommandItem>
))}
</CommandGroup> */}
</CommandList>
<CommandSeparator />
<CommandList>
Expand All @@ -205,8 +175,10 @@ export default function TeamSwitcher({
Create Team
</CommandItem>
</DialogTrigger>
<CommandItem onSelect={() => router.push('/account/teams')}>
Manage Teams
<CommandItem
onSelect={() => router.push(`/teams/${scope}/settings`)}
>
Manage Team
</CommandItem>
</CommandGroup>
</CommandList>
Expand Down
13 changes: 13 additions & 0 deletions packages/app/src/components/teams/add-member-form.action.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
'use server'

import { createAction, protectedProcedure } from '@/server/trpc'
import { rhfActionAddMemberSchema } from './add-member-form.schema'
import { User } from '@/db/models/users'
import { addSolutionComment } from '@/db/services/solutions'
import { revalidatePath } from 'next/cache'

export const rhfActionAddMember = createAction(
protectedProcedure.input(rhfActionAddMemberSchema).mutation(async opts => {
// revalidatePath('/dashboard/solutions/[id]', 'page')
})
)
18 changes: 18 additions & 0 deletions packages/app/src/components/teams/add-member-form.schema.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { z } from 'zod'

export const rhfActionAddMemberSchema = z.object({
members: z
.array(
z.object({
email: z.string().email(),
type: z.union([z.literal('member'), z.literal('owner')])
})
)
.min(1)
.max(100)
})

export type AddMembersFormValues = z.infer<typeof rhfActionAddMemberSchema>
export const defaultValues: Partial<AddMembersFormValues> = {
members: [{ email: '', type: 'member' }]
}
114 changes: 114 additions & 0 deletions packages/app/src/components/teams/add-member-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
'use client'

import {
Form,
FormItem,
FormLabel,
FormMessage,
FormControl,
FormField
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { type PropsWithChildren } from 'react'
import { Button } from '@/components/ui/button'
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue
} from '@/components/ui/select'
import { zodResolver } from '@hookform/resolvers/zod'
import {
rhfActionAddMemberSchema,
defaultValues,
type AddMembersFormValues
} from './add-member-form.schema'
import { rhfActionAddMember } from './add-member-form.action'
import { useFieldArray, useForm } from 'react-hook-form'
import { useAction } from '@/trpc/client'

export type AddMemberFormProps = {
className?: string
}

export function AddMemberForm({
...props
}: PropsWithChildren<AddMemberFormProps>) {
const form = useForm<AddMembersFormValues>({
resolver: zodResolver(rhfActionAddMemberSchema),
defaultValues
})

const { fields, append, prepend, remove, swap, move, insert } = useFieldArray(
{ control: form.control, name: 'members' }
)

const mutation = useAction(rhfActionAddMember)
async function onSubmit(data: AddMembersFormValues) {
await mutation.mutateAsync({ ...data })
}

return (
<>
<Form {...form}>
<form
action={rhfActionAddMember}
onSubmit={form.handleSubmit(onSubmit)}
className="py-4"
>
{fields.map((field, index) => (
<div key={field.id} className="flex flex-row gap-x-2 py-2">
<FormField
control={form.control}
name={`members.${index}.email`}
render={({ field }) => (
<FormItem className="space-y-0 w-full">
<FormLabel className="sr-only">Email</FormLabel>
<FormControl>
<Input
placeholder="[email protected]"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name={`members.${index}.type`}
render={({ field }) => (
<FormItem className="min-w-[160px]">
<Select onValueChange={value => field.onChange(value)}>
<SelectTrigger>
<SelectValue placeholder="Not selected" />
</SelectTrigger>
<SelectContent>
<SelectItem value="member">Member</SelectItem>
<SelectItem value="owner">Owner</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
</div>
))}
<div className="flex justify-between py-4">
<Button
variant={'outline'}
className="items-stretch"
onClick={() => append({ email: '', type: 'member' }, {})}
>
Add Member
</Button>
<Button type="submit" disabled={form.formState.isSubmitting}>
Add
</Button>
</div>
</form>
</Form>
</>
)
}
12 changes: 9 additions & 3 deletions packages/app/src/components/teams/settings-general-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,21 @@ import {
FormMessage
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { use } from 'react'
import { PropsWithChildren, use } from 'react'
import {
SettingGeneralFormValues,
settingsGeneralFormSchema
} from './settings-general-form.schema'
import { api } from '@/trpc/client'

export function SettingsGeneralForm({ teamId }: { teamId: string }) {
const team = use(api.teams.getByName.query(teamId))
export type SettingsGeneralFormProps = {
teamId: string
}

export function SettingsGeneralForm({
teamId = ''
}: PropsWithChildren<SettingsGeneralFormProps>) {
const team = use(api.teams.getByName.query({ slug: teamId }))

const form = useForm<SettingGeneralFormValues>({
resolver: zodResolver(settingsGeneralFormSchema),
Expand Down
107 changes: 107 additions & 0 deletions packages/app/src/components/teams/settings-members-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
'use client'

import { zodResolver } from '@hookform/resolvers/zod'
import { useForm } from 'react-hook-form'
import { Button } from '@/components/ui/button'
import {
Form,
FormControl,
FormDescription,
FormField,
FormItem,
FormLabel,
FormMessage
} from '@/components/ui/form'
import { Input } from '@/components/ui/input'
import { PropsWithChildren, use } from 'react'
import {
SettingGeneralFormValues,
settingsGeneralFormSchema
} from './settings-general-form.schema'
import { api } from '@/trpc/client'

export type SettingsMembersFormProps = {
teamId: string
}

export function SettingsMembersForm({
teamId = ''
}: PropsWithChildren<SettingsMembersFormProps>) {
const team = use(api.teams.getUsersByName.query({ slug: teamId }))

const form = useForm<SettingGeneralFormValues>({
resolver: zodResolver(settingsGeneralFormSchema),
defaultValues: {
name: team?.name,
slug: team?.slug,
description: team?.description
},
mode: 'onChange'
})

return (
<>{team?.users.map((user, index) => <div key={index}>{user.name}</div>)}</>
)

// return (

// <Form {...form}>
// <form className="space-y-8">
// <FormField
// control={form.control}
// name="name"
// render={({ field }) => (
// <FormItem>
// <FormLabel className="sr-only">Name</FormLabel>
// <FormControl>
// <Input disabled={true} placeholder="Name ..." {...field} />
// </FormControl>
// <FormDescription>This is the name of the team.</FormDescription>
// <FormMessage />
// </FormItem>
// )}
// />
// <FormField
// control={form.control}
// name="slug"
// render={({ field }) => (
// <FormItem>
// <FormLabel className="sr-only">Slug</FormLabel>
// <FormControl>
// <Input disabled={true} placeholder="Slug ..." {...field} />
// </FormControl>
// <FormDescription>
// {`This is the short name used for URLs (e.g.
// 'solution-architects', 'order-service')`}
// </FormDescription>
// <FormMessage />
// </FormItem>
// )}
// />
// <FormField
// control={form.control}
// name="description"
// render={({ field }) => (
// <FormItem>
// <FormLabel className="sr-only">Description</FormLabel>
// <FormControl>
// <Input
// disabled={true}
// placeholder="Description ..."
// {...field}
// />
// </FormControl>
// <FormDescription>
// This a brief description of the application instance.
// </FormDescription>
// <FormMessage />
// </FormItem>
// )}
// />
// <Button disabled={true} type="submit">
// Update settings
// </Button>
// </form>
// </Form>
// )
}
13 changes: 13 additions & 0 deletions packages/app/src/db/models/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import {
import { Optional } from 'sequelize'
import { Workload } from '@/db/models/workload'
import { Ownership } from '@/db/models/ownership'
import { User } from '@/db/models/users'
import { UserTeam } from '@/db/models/users-teams'

export interface TeamAttributes {
id: string
Expand Down Expand Up @@ -75,6 +77,17 @@ export class Team extends Model<TeamAttributes, TeamCreationAttributes> {
})
declare workloads: Workload[]

@BelongsToMany(() => User, {
through: {
model: () => UserTeam,
unique: false
},
foreignKey: 'teamId',
otherKey: 'userId',
constraints: false
})
declare users: User[]

@CreatedAt
@Column(DataType.DATE)
declare createdAt: Date
Expand Down
6 changes: 3 additions & 3 deletions packages/app/src/db/models/users-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,13 @@ export class UserTeam extends Model<
@PrimaryKey
@AutoIncrement
@Column(DataType.BIGINT)
id!: bigint
declare id: bigint

@ForeignKey(() => User)
@Column(DataType.UUIDV4)
userId?: string
declare userId: string

@ForeignKey(() => Team)
@Column(DataType.UUIDV4)
teamId?: string
declare teamId: string
}
Loading

0 comments on commit 188f382

Please sign in to comment.