diff --git a/package-lock.json b/package-lock.json index a9d6eaab..93e7ede0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19006,7 +19006,6 @@ "swr": "^2.2.4", "tailwind-merge": "^2.2.0", "tailwindcss-animate": "^1.0.7", - "uuid": "^9.0.1", "winston": "^3.11.0", "zod": "^3.22.4" }, diff --git a/packages/app/src/app/teams/layout.tsx b/packages/app/src/app/teams/layout.tsx index deadc813..6de98566 100644 --- a/packages/app/src/app/teams/layout.tsx +++ b/packages/app/src/app/teams/layout.tsx @@ -15,16 +15,7 @@ type PageProps = { export default function Layout({ children }: PageProps) { return ( <> - - - - Teams - Manage teams and team members. - - - - {children} - + {children} > ) } diff --git a/packages/app/src/app/teams/new/page.tsx b/packages/app/src/app/teams/new/page.tsx new file mode 100644 index 00000000..fa1488ee --- /dev/null +++ b/packages/app/src/app/teams/new/page.tsx @@ -0,0 +1,22 @@ +import { SubNav, SubNavTitle, SubNavSubtitle } from '@/components/sub-nav' +import { Section } from '@/components/section' +import { NewTeamForm } from '@/components/teams/new-form' +import { Suspense } from 'react' + +export default function Page() { + return ( + <> + + + New Team + Create a new team. + + + + + + + + > + ) +} diff --git a/packages/app/src/components/teams/new/new-form.action.tsx b/packages/app/src/components/teams/new-form.action.tsx similarity index 100% rename from packages/app/src/components/teams/new/new-form.action.tsx rename to packages/app/src/components/teams/new-form.action.tsx diff --git a/packages/app/src/components/teams/new-form.schema.tsx b/packages/app/src/components/teams/new-form.schema.tsx new file mode 100644 index 00000000..5320e84b --- /dev/null +++ b/packages/app/src/components/teams/new-form.schema.tsx @@ -0,0 +1,12 @@ +import { z } from 'zod' + +export const rhfActionSchema = z.object({ + name: z.string().min(3).max(128), + description: z.string().min(10).max(256).optional(), + contactEmail: z.string().email().optional() +}) + +export type NewTeamFormValues = z.infer +export const defaultValues: Partial = { + name: '' +} diff --git a/packages/app/src/components/teams/new-form.tsx b/packages/app/src/components/teams/new-form.tsx new file mode 100644 index 00000000..a9e2ad43 --- /dev/null +++ b/packages/app/src/components/teams/new-form.tsx @@ -0,0 +1,124 @@ +'use client' + +import { + Form, + FormControl, + FormItem, + FormLabel, + FormDescription, + FormMessage, + FormField +} from '@/components/ui/form' +import { Textarea } from '@/components/ui/textarea' +import { useEffect } from 'react' +import { Input } from '@/components/ui/input' +import { Button } from '@/components/ui/button' +import { zodResolver } from '@hookform/resolvers/zod' +import { rhfActionSchema } from './new-form.schema' +import { rhfAction } from './new-form.action' +import { useForm } from 'react-hook-form' +import { z } from 'zod' +import { useAction } from '@/trpc/client' +import { useRouter } from 'next/navigation' +import type { PropsWithChildren } from 'react' +import type { NewTeamFormValues } from './new-form.schema' +import { defaultValues } from './new-form.schema' + +export type NewTeamFormProps = {} + +export function NewTeamForm({ ...props }: PropsWithChildren) { + const form = useForm({ + resolver: zodResolver(rhfActionSchema), + defaultValues, + mode: 'onChange' + }) + const router = useRouter() + + const mutation = useAction(rhfAction) + const handleSubmit = async (data: z.infer) => + await mutation.mutateAsync({ ...data }) + + useEffect(() => { + if (mutation.status === 'success') { + router.push(`/teams/${mutation.data?.id}`) + } + }) + + return ( + <> + + + ( + + + Name + + + + + Give it a great name. + + + )} + /> + + ( + + + Contact email + + + + + + Add a shared inbox for you team (optional). + + + + )} + /> + + ( + + + + Description + + + + + A desciption of your team + + + + )} + /> + + + Create team + + + + > + ) +} diff --git a/packages/app/src/components/teams/new/new-form.schema.tsx b/packages/app/src/components/teams/new/new-form.schema.tsx deleted file mode 100644 index 884a2e04..00000000 --- a/packages/app/src/components/teams/new/new-form.schema.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { z } from 'zod' - -export const rhfActionSchema = z.object({ - name: z.string().min(3, {}).default(''), - lensesIds: z.array(z.string().uuid()).min(1).default([]), - environmentsIds: z.array(z.bigint()).min(1).default([]), - description: z - .string() - .min(10, { - message: 'Description must be at least 30 characters.' - }) - .max(2024, { - message: 'Description must be less than 2024 characters.' - }) - .default(''), - profilesId: z.string().uuid().default(''), - tags: z.array(z.string()).default([]) -}) diff --git a/packages/app/src/components/teams/new/new-form.tsx b/packages/app/src/components/teams/new/new-form.tsx deleted file mode 100644 index 5e4f744b..00000000 --- a/packages/app/src/components/teams/new/new-form.tsx +++ /dev/null @@ -1,188 +0,0 @@ -'use client' - -import { - Form, - FormControl, - FormItem, - FormLabel, - FormDescription, - FormMessage, - FormField -} from '@/components/ui/form' -import { - Select, - SelectContent, - SelectGroup, - SelectItem, - SelectTrigger, - SelectValue -} from '@/components/ui/select' -import { api } from '@/trpc/client' -import { use } from 'react' -import { FancyMultiSelect } from '@/components/fancy-multi-select' -import { Textarea } from '@/components/ui/textarea' -import { useEffect } from 'react' -import { Input } from '@/components/ui/input' -import { Button } from '@/components/ui/button' -import { zodResolver } from '@hookform/resolvers/zod' -import { rhfActionSchema } from './new-form.schema' -import { rhfAction } from './new-form.action' -import { useForm } from 'react-hook-form' -import { z } from 'zod' -import { useAction } from '@/trpc/client' -import { useRouter } from 'next/navigation' - -export type NewProfilesFormProps = { - className?: string -} - -export function NewProfilesForm({ ...props }: NewProfilesFormProps) { - const profiles = use(api.listProfiles.query({})) - const environments = use(api.listEnvironments.query({})) - const lenses = use(api.listLenses.query({})) - - const form = useForm>({ - resolver: zodResolver(rhfActionSchema), - defaultValues: { - name: '', - description: '' - }, - mode: 'onChange' - }) - const router = useRouter() - - const mutation = useAction(rhfAction) - const handleSubmit = async (data: z.infer) => - await mutation.mutateAsync({ ...data }) - - useEffect(() => { - if (mutation.status === 'success') { - router.push(`/dashboard/workloads/${mutation.data?.id}`) - } - }) - - return ( - <> - - - ( - - - Name - - - - - Give it a great name. - - - )} - /> - ( - - Environments - - ({ - value: env.id, - label: env.name - }))} - /> - - Select matching environments. - - - )} - /> - ( - - Profile - - - - - - - {profiles?.rows.map((profile, idx) => ( - - {profile.name} - - ))} - - - - Select matching profile. - - - )} - /> - ( - - Lenses - - ({ - value: lens.id, - label: lens.name - }))} - /> - - Select the matching lenses. - - - )} - /> - ( - - - - - - A desciption of your workload. - - - - )} - /> - - - Add Workload - - - - > - ) -} diff --git a/packages/app/src/db/models/team.ts b/packages/app/src/db/models/team.ts new file mode 100644 index 00000000..9319a3d7 --- /dev/null +++ b/packages/app/src/db/models/team.ts @@ -0,0 +1,79 @@ +import { + Table, + Model, + CreatedAt, + UpdatedAt, + DeletedAt, + Column, + PrimaryKey, + DataType, + NotEmpty, + Min, + Max, + BelongsToMany, + AllowNull, + Default +} from 'sequelize-typescript' +import { ProfileQuestionAnswer } from '@/db/models/profile-question-answers' +import { ProfileQuestionChoice } from '@/db/models/profile-question-choice' + +export interface ProfileAttributes { + id: string + name: string + description?: string + createdAt: Date + updatedAt: Date + deletedAt: Date +} + +export type ProfileCreationAttributes = Omit< + ProfileAttributes, + 'id' | 'createdAt' | 'updatedAt' | 'deletedAt' +> + +@Table({ + tableName: 'profiles', + modelName: 'Profile' +}) +export class Profile extends Model< + ProfileAttributes, + ProfileCreationAttributes +> { + @PrimaryKey + @Default(DataType.UUIDV4) + @AllowNull(false) + @Column(DataType.UUIDV4) + id!: string + + @NotEmpty + @Min(3) + @Max(256) + @Column + name!: string + + @NotEmpty + @Min(12) + @Max(2048) + @Column + description?: string + + @BelongsToMany( + () => ProfileQuestionChoice, + () => ProfileQuestionAnswer, + 'profileId', + 'choiceId' + ) + answers?: ProfileQuestionChoice[] + + @CreatedAt + @Column + createdAt?: Date + + @UpdatedAt + @Column + updatedAt?: Date + + @DeletedAt + @Column + deletedAt?: Date +}