diff --git a/apps/nextjs/src/_components/settings/members-tab.tsx b/apps/nextjs/src/_components/settings/members-tab.tsx index 19804cde..51c536f7 100644 --- a/apps/nextjs/src/_components/settings/members-tab.tsx +++ b/apps/nextjs/src/_components/settings/members-tab.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { useParams } from "next/navigation"; +import * as z from "zod"; import { Role } from "@acme/db"; import { Avatar, AvatarFallback, AvatarImage } from "@acme/ui/avatar"; @@ -21,9 +22,29 @@ import { CommandItem, CommandList, } from "@acme/ui/command"; +import { + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@acme/ui/dialog"; +import { + Form, + FormField, + FormItem, + FormLabel, + FormMessage, + useForm, +} from "@acme/ui/form"; import { Icons } from "@acme/ui/icons"; +import { MultiSelect } from "@acme/ui/multiselect"; import { Popover, PopoverContent, PopoverTrigger } from "@acme/ui/popover"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@acme/ui/tabs"; import { toast } from "@acme/ui/toast"; +import { CreateMembersSchema } from "@acme/validators"; import { getAvatarFallback } from "~/_utils/common"; import { PERMISSION_LIST } from "~/_utils/constants"; @@ -32,10 +53,16 @@ import { api } from "~/trpc/react"; export function MembersTab() { const utils = api.useUtils(); - const [role, setRole] = useState<(typeof PERMISSION_LIST)[number]>(); const params = useParams<{ id: string }>(); const { data: permissions } = api.workspacesMembers.all.useQuery(params.id); + const form = useForm({ + schema: CreateMembersSchema, + defaultValues: { + emails: [], + }, + }); + const updatePermission = api.workspacesMembers.update.useMutation({ async onSuccess() { toast.success("Your workspace permissions updated successfully!"); @@ -51,13 +78,103 @@ export function MembersTab() { }, }); + const onSubmit = async (data: z.infer) => {}; + return ( - - Team Members - - Invite your team members to collaborate. - + +
+ Team Members + + Invite your team members to collaborate. + +
+ + + + + + + Invite members + + + + Public link + Personal email + + + +
+ + ( + + Select Frameworks + + + + )} + /> + + + + + +
+ +
+
+
{permissions?.map(({ id, user, permission }) => ( diff --git a/packages/ui/package.json b/packages/ui/package.json index 26212a6a..d2588fb9 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -7,6 +7,7 @@ ".": "./src/index.ts", "./alert-dialog": "./src/alert-dialog.tsx", "./avatar": "./src/avatar.tsx", + "./badge": "./src/badge.tsx", "./button": "./src/button.tsx", "./card": "./src/card.tsx", "./command": "./src/command.tsx", @@ -16,6 +17,7 @@ "./icons": "./src/icons.tsx", "./input": "./src/input.tsx", "./label": "./src/label.tsx", + "./multiselect": "./src/multiselect.tsx", "./popover": "./src/popover.tsx", "./radio-group": "./src/radio-group.tsx", "./scroll-area": "./src/scroll-area.tsx", diff --git a/packages/ui/src/badge.tsx b/packages/ui/src/badge.tsx new file mode 100644 index 00000000..f8a0927d --- /dev/null +++ b/packages/ui/src/badge.tsx @@ -0,0 +1,37 @@ +import type { VariantProps } from "class-variance-authority"; +import * as React from "react"; +import { cva } from "class-variance-authority"; + +import { cn } from "."; + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ); +} + +export { Badge, badgeVariants }; diff --git a/packages/ui/src/multiselect.tsx b/packages/ui/src/multiselect.tsx new file mode 100644 index 00000000..d357cbf9 --- /dev/null +++ b/packages/ui/src/multiselect.tsx @@ -0,0 +1,154 @@ +"use client"; + +import * as React from "react"; + +import { cn } from "."; +import { Badge } from "./badge"; +import { Button } from "./button"; +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandSeparator, +} from "./command"; +import { Icons } from "./icons"; +import { Input } from "./input"; +import { Popover, PopoverContent, PopoverTrigger } from "./popover"; + +export type OptionType = { + label: string; + value: string; +}; + +interface MultiSelectProps { + options: OptionType[]; + selected: string[]; + onChange: React.Dispatch>; + className?: string; +} + +function MultiSelect({ + options, + selected, + onChange, + className, + ...props +}: MultiSelectProps) { + const [open, setOpen] = React.useState(false); + + const handleUnselect = (item: string) => { + onChange(selected.filter((i) => i !== item)); + }; + + const [newOption, setNewOption] = React.useState(""); + + const handleNewOptionEntry = (e: React.ChangeEvent) => { + setNewOption(e.target.value); + }; + + const handleNewOptionSubmit = () => { + if (newOption) { + options.push({ label: newOption, value: newOption }); + onChange( + selected.includes(newOption) + ? selected.filter((item) => item !== newOption) + : [...selected, newOption], + ); + setNewOption(""); + setOpen(true); + } + }; + + return ( + + + + + ))} +
+ + + + + + + No item found. + + {options.map((option) => ( + { + onChange( + selected.includes(option.value) + ? selected.filter((item) => item !== option.value) + : [...selected, option.value], + ); + setOpen(true); + }} + > + + {option.label} + + ))} + + + +
+ + +
+
+
+
+ + ); +} + +export { MultiSelect }; diff --git a/packages/validators/src/index.ts b/packages/validators/src/index.ts index a06065ff..c3a9d282 100644 --- a/packages/validators/src/index.ts +++ b/packages/validators/src/index.ts @@ -44,3 +44,7 @@ export const CreateProjectSchema = z.object({ export const AuthLoginSchema = z.object({ email: z.string().min(1), }); + +export const CreateMembersSchema = z.object({ + emails: z.array(z.record(z.string().trim())), +});