Skip to content

Commit

Permalink
feat: new create forms
Browse files Browse the repository at this point in the history
  • Loading branch information
olros committed Apr 26, 2024
1 parent d5cfd3e commit 6c362bc
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 165 deletions.
11 changes: 2 additions & 9 deletions web/app/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const Navbar = ({ user, teams }: NavbarProps) => {
<DropdownMenuContent className='w-56'>
<DropdownMenuLabel>My teams</DropdownMenuLabel>
<DropdownMenuSeparator />
{teams.map((team) => (
{teams.map((team, index) => (
<DropdownMenuGroup key={team.id}>
<DropdownMenuItem asChild>
<Link to={`/dashboard/${team.slug}`} unstable_viewTransition>
Expand All @@ -66,16 +66,9 @@ export const Navbar = ({ user, teams }: NavbarProps) => {
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>
<DropdownMenuSeparator />
{index !== teams.length - 1 && <DropdownMenuSeparator />}
</DropdownMenuGroup>
))}
<DropdownMenuGroup>
<DropdownMenuItem asChild>
<Link to='/dashboard/new-team' unstable_viewTransition>
New Team
</Link>
</DropdownMenuItem>
</DropdownMenuGroup>
</DropdownMenuContent>
</DropdownMenu>
</div>
Expand Down
86 changes: 86 additions & 0 deletions web/app/components/ui/sheet.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import * as React from 'react';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import { Cross2Icon } from '@radix-ui/react-icons';
import { cva, type VariantProps } from 'class-variance-authority';

import { cn } from '~/lib/utils';

const Sheet = SheetPrimitive.Root;

const SheetTrigger = SheetPrimitive.Trigger;

const SheetClose = SheetPrimitive.Close;

const SheetPortal = SheetPrimitive.Portal;

const SheetOverlay = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Overlay>, React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>>(
({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 fixed inset-0 z-50 bg-black/80',
className,
)}
{...props}
ref={ref}
/>
),
);
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;

const sheetVariants = cva(
'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500',
{
variants: {
side: {
top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
bottom: 'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
right: 'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
},
},
defaultVariants: {
side: 'right',
},
},
);

interface SheetContentProps extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>, VariantProps<typeof sheetVariants> {}

const SheetContent = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Content>, SheetContentProps>(
({ side = 'right', className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
{children}
<SheetPrimitive.Close className='ring-offset-background focus:ring-ring data-[state=open]:bg-secondary absolute top-4 right-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:ring-2 focus:ring-offset-2 focus:outline-none disabled:pointer-events-none'>
<Cross2Icon className='h-4 w-4' />
<span className='sr-only'>Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
),
);
SheetContent.displayName = SheetPrimitive.Content.displayName;

const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
);
SheetHeader.displayName = 'SheetHeader';

const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)} {...props} />
);
SheetFooter.displayName = 'SheetFooter';

const SheetTitle = React.forwardRef<React.ElementRef<typeof SheetPrimitive.Title>, React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>>(
({ className, ...props }, ref) => <SheetPrimitive.Title ref={ref} className={cn('text-foreground text-lg font-semibold', className)} {...props} />,
);
SheetTitle.displayName = SheetPrimitive.Title.displayName;

const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => <SheetPrimitive.Description ref={ref} className={cn('text-muted-foreground text-sm', className)} {...props} />);
SheetDescription.displayName = SheetPrimitive.Description.displayName;

export { Sheet, SheetPortal, SheetOverlay, SheetTrigger, SheetClose, SheetContent, SheetHeader, SheetFooter, SheetTitle, SheetDescription };
4 changes: 4 additions & 0 deletions web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
--radius: 0.75rem;
}

* {
border-color: hsl(var(--border));
}

html,
body {
background: hsl(var(--background));
Expand Down
66 changes: 59 additions & 7 deletions web/app/routes/_authed.dashboard._index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { Link, NavLink, useLoaderData } from '@remix-run/react';
import type { LoaderFunctionArgs } from '@vercel/remix';
import { Prisma } from '@prisma/client';
import { Form, NavLink, useActionData, useLoaderData, useNavigation } from '@remix-run/react';
import type { ActionFunctionArgs, LoaderFunctionArgs } from '@vercel/remix';
import { getUserOrRedirect } from '~/auth.server';
import { Typography } from '~/components/typography';
import { Button } from '~/components/ui/button';
import { Card } from '~/components/ui/card';
import { Input } from '~/components/ui/input';
import { Label } from '~/components/ui/label';
import { Sheet, SheetClose, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '~/components/ui/sheet';
import { prismaClient } from '~/prismaClient';
import { redirect, slugify } from '~/utils.server';

export { ErrorBoundary } from '~/components/ErrorBoundary';

Expand All @@ -22,19 +27,66 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
return { teams };
};

export const action = async ({ request, response }: ActionFunctionArgs) => {
const user = await getUserOrRedirect(request);
const formData = await request.formData();
const name = formData.get('name') as string;
try {
const team = await prismaClient.team.create({
data: {
name,
slug: slugify(name),
teamUsers: {
create: {
userId: user.id,
},
},
},
});
return redirect(response, `/dashboard/${team.slug}`);
} catch (e) {
console.error('[New Team]', e);
if (e instanceof Prisma.PrismaClientKnownRequestError) {
response!.status = 400;
return { errors: { name: 'This team name is already taken' } };
}
}
response!.status = 400;
return { errors: { name: 'Something went wrong' } };
};

export default function Dashboard() {
const { teams } = useLoaderData<typeof loader>();
const actionData = useActionData<typeof action>();
const { state } = useNavigation();
return (
<>
<Card className='flex flex-col items-center justify-between gap-2 [view-transition-name:header-old] sm:flex-row'>
<Typography variant='h1' className='[view-transition-name:header-old-tex]'>
Your teams
</Typography>
<Button asChild>
<Link className='[view-transition-name:create-team]' to='new-team' unstable_viewTransition>
New team
</Link>
</Button>
<Sheet>
<SheetTrigger asChild>
<Button>New team</Button>
</SheetTrigger>
<SheetContent side='right'>
<SheetHeader className='mb-2'>
<SheetTitle>Create team</SheetTitle>
</SheetHeader>
<Form method='post'>
<Label htmlFor='name'>Team name</Label>
<Input id='name' required disabled={state === 'submitting'} error={Boolean(actionData?.errors.name)} name='name' />
<div className='mt-4 flex gap-2'>
<Button disabled={state === 'submitting'} type='submit'>
Save
</Button>
<SheetClose asChild>
<Button variant='link'>Cancel</Button>
</SheetClose>
</div>
</Form>
</SheetContent>
</Sheet>
</Card>
<div className='grid grid-cols-1 gap-2 sm:grid-cols-2 md:grid-cols-3'>
{teams.map((team) => (
Expand Down
65 changes: 0 additions & 65 deletions web/app/routes/_authed.dashboard.new-team.tsx

This file was deleted.

77 changes: 0 additions & 77 deletions web/app/routes/_authed.dashboard_.$teamSlug.new-project.tsx

This file was deleted.

Loading

0 comments on commit 6c362bc

Please sign in to comment.