Update bun packages and format

This commit is contained in:
2025-09-08 11:25:57 -05:00
parent 1d82c18179
commit c0c752eaad
13 changed files with 165 additions and 181 deletions

View File

@@ -16,7 +16,8 @@ export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
export const PASSWORD_MIN = 8; export const PASSWORD_MIN = 8;
export const PASSWORD_MAX = 100; export const PASSWORD_MAX = 100;
export const PASSWORD_REGEX = /^(?=.{8,100}$)(?!.*\s)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\p{P}\p{S}]).*$/u; export const PASSWORD_REGEX =
/^(?=.{8,100}$)(?!.*\s)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\p{P}\p{S}]).*$/u;
export const getUser = query(async (ctx) => { export const getUser = query(async (ctx) => {
const userId = await getAuthUserId(ctx); const userId = await getAuthUserId(ctx);

View File

@@ -9,26 +9,26 @@ import {
import type { Doc, Id } from './_generated/dataModel'; import type { Doc, Id } from './_generated/dataModel';
import { paginationOptsValidator } from 'convex/server'; import { paginationOptsValidator } from 'convex/server';
type RWCtx = MutationCtx | QueryCtx type RWCtx = MutationCtx | QueryCtx;
type StatusRow = { type StatusRow = {
user: { user: {
id: Id<'users'>; id: Id<'users'>;
name: string | null; name: string | null;
imageUrl: string | null; imageUrl: string | null;
}, };
status: { status: {
id: Id<'statuses'>; id: Id<'statuses'>;
message: string; message: string;
updatedAt: number; updatedAt: number;
updatedBy: StatusRow['user'] | null; updatedBy: StatusRow['user'] | null;
} | null, } | null;
}; };
type Paginated<T> = { type Paginated<T> = {
page: T[], page: T[];
isDone: boolean, isDone: boolean;
continueCursor: string | null, continueCursor: string | null;
}; };
// CHANGED: typed helpers // CHANGED: typed helpers
@@ -240,9 +240,10 @@ export const listHistory = query({
userId: v.optional(v.id('users')), userId: v.optional(v.id('users')),
paginationOpts: paginationOptsValidator, paginationOpts: paginationOptsValidator,
}, },
handler: async (ctx, { userId, paginationOpts }): Promise< handler: async (
Paginated<StatusRow> ctx,
> => { { userId, paginationOpts },
): Promise<Paginated<StatusRow>> => {
// Query statuses newest-first, optionally filtered by user // Query statuses newest-first, optionally filtered by user
const result = userId const result = userId
? await ctx.db ? await ctx.db
@@ -250,17 +251,12 @@ export const listHistory = query({
.withIndex('by_user_updatedAt', (q) => q.eq('userId', userId)) .withIndex('by_user_updatedAt', (q) => q.eq('userId', userId))
.order('desc') .order('desc')
.paginate(paginationOpts) .paginate(paginationOpts)
: await ctx.db : await ctx.db.query('statuses').order('desc').paginate(paginationOpts);
.query('statuses')
.order('desc')
.paginate(paginationOpts);
// Cache user display objects to avoid refetching repeatedly // Cache user display objects to avoid refetching repeatedly
const displayCache = new Map<string, StatusRow['user']>(); const displayCache = new Map<string, StatusRow['user']>();
const getDisplay = async ( const getDisplay = async (uid: Id<'users'>): Promise<StatusRow['user']> => {
uid: Id<'users'>,
): Promise<StatusRow['user']> => {
const key = uid as unknown as string; const key = uid as unknown as string;
const cached = displayCache.get(key); const cached = displayCache.get(key);
if (cached) return cached; if (cached) return cached;
@@ -304,5 +300,3 @@ export const listHistory = query({
}; };
}, },
}); });

View File

@@ -30,11 +30,9 @@ const signInFormSchema = z.object({
email: z.email({ email: z.email({
message: 'Please enter a valid email address.', message: 'Please enter a valid email address.',
}), }),
password: z.string() password: z.string().regex(PASSWORD_REGEX, {
.regex(PASSWORD_REGEX, {
message: 'Incorrect password. Does not meet requirements.', message: 'Incorrect password. Does not meet requirements.',
}, }),
),
}); });
const signUpFormSchema = z const signUpFormSchema = z
@@ -51,8 +49,7 @@ const signUpFormSchema = z
message: `Password must be at least ${PASSWORD_MIN} characters.`, message: `Password must be at least ${PASSWORD_MIN} characters.`,
}) })
.max(PASSWORD_MAX, { .max(PASSWORD_MAX, {
message: message: `Password must be no more than ${PASSWORD_MAX} characters.`,
`Password must be no more than ${PASSWORD_MAX} characters.`,
}) })
.regex(/^\S+$/, { .regex(/^\S+$/, {
message: 'Password must not contain whitespace.', message: 'Password must not contain whitespace.',

View File

@@ -25,16 +25,17 @@ import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@/lib/types';
const formSchema = z const formSchema = z
.object({ .object({
currentPassword: z currentPassword: z.string().regex(PASSWORD_REGEX, {
.string()
.regex(PASSWORD_REGEX, {
message: 'Incorrect current password. Does not meet requirements.', message: 'Incorrect current password. Does not meet requirements.',
}, }),
),
newPassword: z newPassword: z
.string() .string()
.min(PASSWORD_MIN, { message: 'New password must be at least 8 characters.' }) .min(PASSWORD_MIN, {
.max(PASSWORD_MAX, { message: 'New password must be less than 100 characters.' }) message: 'New password must be at least 8 characters.',
})
.max(PASSWORD_MAX, {
message: 'New password must be less than 100 characters.',
})
.regex(/^\S+$/, { .regex(/^\S+$/, {
message: 'Password must not contain whitespace.', message: 'Password must not contain whitespace.',
}) })

View File

@@ -27,7 +27,7 @@ import {
} from '@/components/ui'; } from '@/components/ui';
type StatusHistoryProps = { type StatusHistoryProps = {
user?: typeof api.statuses.getCurrentForAll._returnType[0]['user'], user?: (typeof api.statuses.getCurrentForAll._returnType)[0]['user'];
}; };
const PAGE_SIZE = 25; const PAGE_SIZE = 25;
@@ -58,8 +58,7 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
const nextIndex = pageIndex + 1; const nextIndex = pageIndex + 1;
setCursors((prev) => { setCursors((prev) => {
const copy = [...prev]; const copy = [...prev];
if (copy[nextIndex] === undefined) if (copy[nextIndex] === undefined) copy[nextIndex] = data.continueCursor;
copy[nextIndex] = data.continueCursor;
return copy; return copy;
}); });
}, [data, pageIndex]); }, [data, pageIndex]);
@@ -135,10 +134,7 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
{r.user.name ?? 'Technician'} {r.user.name ?? 'Technician'}
</TableCell> </TableCell>
<TableCell className='max-w-xs'> <TableCell className='max-w-xs'>
<div <div className='truncate' title={r.status?.message ?? ''}>
className='truncate'
title={r.status?.message ?? ''}
>
{r.status?.message ?? 'No status'} {r.status?.message ?? 'No status'}
</div> </div>
</TableCell> </TableCell>
@@ -147,9 +143,9 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
</TableCell> </TableCell>
<TableCell className='text-right text-sm'> <TableCell className='text-right text-sm'>
{r.status {r.status
? `${formatTime(r.status.updatedAt)} · ${ ? `${formatTime(r.status.updatedAt)} · ${formatDate(
formatDate(r.status.updatedAt) r.status.updatedAt,
}` )}`
: '--:-- · --/--'} : '--:-- · --/--'}
</TableCell> </TableCell>
</TableRow> </TableRow>
@@ -161,7 +157,6 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
</div> </div>
<DrawerFooter> <DrawerFooter>
<Pagination> <Pagination>
<PaginationContent> <PaginationContent>
<PaginationPrevious <PaginationPrevious
@@ -175,9 +170,7 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
/> />
<div className='flex items-center gap-2 text-sm text-muted-foreground'> <div className='flex items-center gap-2 text-sm text-muted-foreground'>
<span>Page</span> <span>Page</span>
<span className='font-bold text-foreground'> <span className='font-bold text-foreground'>{pageIndex + 1}</span>
{pageIndex + 1}
</span>
</div> </div>
<PaginationNext <PaginationNext
href='#' href='#'
@@ -192,10 +185,7 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
</Pagination> </Pagination>
<DrawerClose asChild> <DrawerClose asChild>
<Button <Button variant='outline' className='mt-4'>
variant='outline'
className='mt-4'
>
Close Close
</Button> </Button>
</DrawerClose> </DrawerClose>

View File

@@ -151,7 +151,8 @@ export const StatusList = ({
> >
<CardContent className='p-2'> <CardContent className='p-2'>
<div className='flex items-start gap-3'> <div className='flex items-start gap-3'>
<div className='flex-shrink-0 cursor-pointer <div
className='flex-shrink-0 cursor-pointer
hover:opacity-80 transition-opacity' hover:opacity-80 transition-opacity'
> >
<BasedAvatar <BasedAvatar
@@ -159,12 +160,12 @@ export const StatusList = ({
fullName={u.name ?? 'Technician'} fullName={u.name ?? 'Technician'}
className={tvMode ? 'w-16 h-16' : 'w-12 h-12'} className={tvMode ? 'w-16 h-16' : 'w-12 h-12'}
/> />
</div> </div>
<div className='flex-1'> <div className='flex-1'>
<div className='flex items-start justify-between mb-2'> <div className='flex items-start justify-between mb-2'>
<div> <div>
<h3 className={`font-semibold cursor-pointer <h3
className={`font-semibold cursor-pointer
hover:text-primary/80 truncate hover:text-primary/80 truncate
${tvMode ? 'text-3xl' : 'text-2xl'} ${tvMode ? 'text-3xl' : 'text-2xl'}
`} `}
@@ -172,7 +173,8 @@ export const StatusList = ({
{u.name ?? 'Technician'} {u.name ?? 'Technician'}
</h3> </h3>
<div className={`pl-2 pr-15 pt-2 <div
className={`pl-2 pr-15 pt-2
${tvMode ? 'text-2xl' : 'text-xl'} ${tvMode ? 'text-2xl' : 'text-xl'}
`} `}
> >
@@ -187,7 +189,9 @@ export const StatusList = ({
text-muted-foreground flex-shrink-0' text-muted-foreground flex-shrink-0'
> >
<div className='flex items-center gap-2'> <div className='flex items-center gap-2'>
<Clock className={tvMode ? 'w-6 h-6' : 'w-5 h-5'} /> <Clock
className={tvMode ? 'w-6 h-6' : 'w-5 h-5'}
/>
<span className={tvMode ? 'text-2xl' : 'text-xl'}> <span className={tvMode ? 'text-2xl' : 'text-xl'}>
{s ? formatTime(s.updatedAt) : '--:--'} {s ? formatTime(s.updatedAt) : '--:--'}
</span> </span>
@@ -208,7 +212,9 @@ export const StatusList = ({
fullName={s.updatedBy.name ?? 'User'} fullName={s.updatedBy.name ?? 'User'}
className={tvMode ? 'w-6 h-6' : 'w-5 h-5'} className={tvMode ? 'w-6 h-6' : 'w-5 h-5'}
/> />
<span className={tvMode ? 'text-base' : 'text-sm'}> <span
className={tvMode ? 'text-base' : 'text-sm'}
>
<div className='flex flex-col'> <div className='flex flex-col'>
<p>Updated by</p> <p>Updated by</p>
{s.updatedBy.name ?? 'User'} {s.updatedBy.name ?? 'User'}
@@ -267,10 +273,7 @@ export const StatusList = ({
> >
{selectedUserIds.length > 0 {selectedUserIds.length > 0
? `Update ${selectedUserIds.length} ? `Update ${selectedUserIds.length}
${selectedUserIds.length > 1 ${selectedUserIds.length > 1 ? 'users' : 'user'}`
? 'users'
: 'user'
}`
: 'Update Status'} : 'Update Status'}
</SubmitButton> </SubmitButton>
</div> </div>

View File

@@ -143,9 +143,7 @@ export const StatusTable = ({
<th className={thCn}>Updated At</th> <th className={thCn}>Updated At</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody></tbody>
</tbody>
</table> </table>
</div> </div>
); );

View File

@@ -51,9 +51,9 @@ export {
PaginationPrevious, PaginationPrevious,
PaginationNext, PaginationNext,
PaginationEllipsis, PaginationEllipsis,
} from './pagination' } from './pagination';
export { Progress } from './progress'; export { Progress } from './progress';
export { ScrollArea, ScrollBar } from './scroll-area' export { ScrollArea, ScrollBar } from './scroll-area';
export { Separator } from './separator'; export { Separator } from './separator';
export { StatusMessage } from './status-message'; export { StatusMessage } from './status-message';
export { SubmitButton } from './submit-button'; export { SubmitButton } from './submit-button';

View File

@@ -1,68 +1,68 @@
import * as React from "react" import * as React from 'react';
import { import {
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
MoreHorizontalIcon, MoreHorizontalIcon,
} from "lucide-react" } from 'lucide-react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
import { Button, buttonVariants } from "@/components/ui/button" import { Button, buttonVariants } from '@/components/ui/button';
function Pagination({ className, ...props }: React.ComponentProps<"nav">) { function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
return ( return (
<nav <nav
role="navigation" role='navigation'
aria-label="pagination" aria-label='pagination'
data-slot="pagination" data-slot='pagination'
className={cn("mx-auto flex w-full justify-center", className)} className={cn('mx-auto flex w-full justify-center', className)}
{...props} {...props}
/> />
) );
} }
function PaginationContent({ function PaginationContent({
className, className,
...props ...props
}: React.ComponentProps<"ul">) { }: React.ComponentProps<'ul'>) {
return ( return (
<ul <ul
data-slot="pagination-content" data-slot='pagination-content'
className={cn("flex flex-row items-center gap-1", className)} className={cn('flex flex-row items-center gap-1', className)}
{...props} {...props}
/> />
) );
} }
function PaginationItem({ ...props }: React.ComponentProps<"li">) { function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
return <li data-slot="pagination-item" {...props} /> return <li data-slot='pagination-item' {...props} />;
} }
type PaginationLinkProps = { type PaginationLinkProps = {
isActive?: boolean isActive?: boolean;
} & Pick<React.ComponentProps<typeof Button>, "size"> & } & Pick<React.ComponentProps<typeof Button>, 'size'> &
React.ComponentProps<"a"> React.ComponentProps<'a'>;
function PaginationLink({ function PaginationLink({
className, className,
isActive, isActive,
size = "icon", size = 'icon',
...props ...props
}: PaginationLinkProps) { }: PaginationLinkProps) {
return ( return (
<a <a
aria-current={isActive ? "page" : undefined} aria-current={isActive ? 'page' : undefined}
data-slot="pagination-link" data-slot='pagination-link'
data-active={isActive} data-active={isActive}
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: isActive ? "outline" : "ghost", variant: isActive ? 'outline' : 'ghost',
size, size,
}), }),
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function PaginationPrevious({ function PaginationPrevious({
@@ -71,15 +71,15 @@ function PaginationPrevious({
}: React.ComponentProps<typeof PaginationLink>) { }: React.ComponentProps<typeof PaginationLink>) {
return ( return (
<PaginationLink <PaginationLink
aria-label="Go to previous page" aria-label='Go to previous page'
size="default" size='default'
className={cn("gap-1 px-2.5 sm:pl-2.5", className)} className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
{...props} {...props}
> >
<ChevronLeftIcon /> <ChevronLeftIcon />
<span className="hidden sm:block">Previous</span> <span className='hidden sm:block'>Previous</span>
</PaginationLink> </PaginationLink>
) );
} }
function PaginationNext({ function PaginationNext({
@@ -88,32 +88,32 @@ function PaginationNext({
}: React.ComponentProps<typeof PaginationLink>) { }: React.ComponentProps<typeof PaginationLink>) {
return ( return (
<PaginationLink <PaginationLink
aria-label="Go to next page" aria-label='Go to next page'
size="default" size='default'
className={cn("gap-1 px-2.5 sm:pr-2.5", className)} className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
{...props} {...props}
> >
<span className="hidden sm:block">Next</span> <span className='hidden sm:block'>Next</span>
<ChevronRightIcon /> <ChevronRightIcon />
</PaginationLink> </PaginationLink>
) );
} }
function PaginationEllipsis({ function PaginationEllipsis({
className, className,
...props ...props
}: React.ComponentProps<"span">) { }: React.ComponentProps<'span'>) {
return ( return (
<span <span
aria-hidden aria-hidden
data-slot="pagination-ellipsis" data-slot='pagination-ellipsis'
className={cn("flex size-9 items-center justify-center", className)} className={cn('flex size-9 items-center justify-center', className)}
{...props} {...props}
> >
<MoreHorizontalIcon className="size-4" /> <MoreHorizontalIcon className='size-4' />
<span className="sr-only">More pages</span> <span className='sr-only'>More pages</span>
</span> </span>
) );
} }
export { export {
@@ -124,4 +124,4 @@ export {
PaginationPrevious, PaginationPrevious,
PaginationNext, PaginationNext,
PaginationEllipsis, PaginationEllipsis,
} };

View File

@@ -1,9 +1,9 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
function ScrollArea({ function ScrollArea({
className, className,
@@ -12,47 +12,47 @@ function ScrollArea({
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) { }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return ( return (
<ScrollAreaPrimitive.Root <ScrollAreaPrimitive.Root
data-slot="scroll-area" data-slot='scroll-area'
className={cn("relative", className)} className={cn('relative', className)}
{...props} {...props}
> >
<ScrollAreaPrimitive.Viewport <ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport" data-slot='scroll-area-viewport'
className="focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1" className='focus-visible:ring-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:outline-1'
> >
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
) );
} }
function ScrollBar({ function ScrollBar({
className, className,
orientation = "vertical", orientation = 'vertical',
...props ...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) { }: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return ( return (
<ScrollAreaPrimitive.ScrollAreaScrollbar <ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar" data-slot='scroll-area-scrollbar'
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"flex touch-none p-px transition-colors select-none", 'flex touch-none p-px transition-colors select-none',
orientation === "vertical" && orientation === 'vertical' &&
"h-full w-2.5 border-l border-l-transparent", 'h-full w-2.5 border-l border-l-transparent',
orientation === "horizontal" && orientation === 'horizontal' &&
"h-2.5 flex-col border-t border-t-transparent", 'h-2.5 flex-col border-t border-t-transparent',
className className,
)} )}
{...props} {...props}
> >
<ScrollAreaPrimitive.ScrollAreaThumb <ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb" data-slot='scroll-area-thumb'
className="bg-border relative flex-1 rounded-full" className='bg-border relative flex-1 rounded-full'
/> />
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
) );
} }
export { ScrollArea, ScrollBar } export { ScrollArea, ScrollBar };

View File

@@ -1,107 +1,107 @@
"use client" 'use client';
import * as React from "react" import * as React from 'react';
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils';
function Table({ className, ...props }: React.ComponentProps<"table">) { function Table({ className, ...props }: React.ComponentProps<'table'>) {
return ( return (
<div <div
data-slot="table-container" data-slot='table-container'
className="relative w-full overflow-x-auto" className='relative w-full overflow-x-auto'
> >
<table <table
data-slot="table" data-slot='table'
className={cn("w-full caption-bottom text-sm", className)} className={cn('w-full caption-bottom text-sm', className)}
{...props} {...props}
/> />
</div> </div>
) );
} }
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) { function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
return ( return (
<thead <thead
data-slot="table-header" data-slot='table-header'
className={cn("[&_tr]:border-b", className)} className={cn('[&_tr]:border-b', className)}
{...props} {...props}
/> />
) );
} }
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) { function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
return ( return (
<tbody <tbody
data-slot="table-body" data-slot='table-body'
className={cn("[&_tr:last-child]:border-0", className)} className={cn('[&_tr:last-child]:border-0', className)}
{...props} {...props}
/> />
) );
} }
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) { function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
return ( return (
<tfoot <tfoot
data-slot="table-footer" data-slot='table-footer'
className={cn( className={cn(
"bg-muted/50 border-t font-medium [&>tr]:last:border-b-0", 'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function TableRow({ className, ...props }: React.ComponentProps<"tr">) { function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
return ( return (
<tr <tr
data-slot="table-row" data-slot='table-row'
className={cn( className={cn(
"hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors", 'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function TableHead({ className, ...props }: React.ComponentProps<"th">) { function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
return ( return (
<th <th
data-slot="table-head" data-slot='table-head'
className={cn( className={cn(
"text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", 'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function TableCell({ className, ...props }: React.ComponentProps<"td">) { function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
return ( return (
<td <td
data-slot="table-cell" data-slot='table-cell'
className={cn( className={cn(
"p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]", 'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
className className,
)} )}
{...props} {...props}
/> />
) );
} }
function TableCaption({ function TableCaption({
className, className,
...props ...props
}: React.ComponentProps<"caption">) { }: React.ComponentProps<'caption'>) {
return ( return (
<caption <caption
data-slot="table-caption" data-slot='table-caption'
className={cn("text-muted-foreground mt-4 text-sm", className)} className={cn('text-muted-foreground mt-4 text-sm', className)}
{...props} {...props}
/> />
) );
} }
export { export {
@@ -113,4 +113,4 @@ export {
TableRow, TableRow,
TableCell, TableCell,
TableCaption, TableCaption,
} };

View File

@@ -1,5 +1,6 @@
export const PASSWORD_MIN = 8; export const PASSWORD_MIN = 8;
export const PASSWORD_MAX = 100; export const PASSWORD_MAX = 100;
export const PASSWORD_REGEX = /^(?=.{8,100}$)(?!.*\s)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\p{P}\p{S}]).*$/u; export const PASSWORD_REGEX =
/^(?=.{8,100}$)(?!.*\s)(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[\p{P}\p{S}]).*$/u;
export type Timestamp = number | string | Date; export type Timestamp = number | string | Date;

View File

@@ -20,7 +20,6 @@ export const ccn = ({
return twMerge(className, context ? on : off); return twMerge(className, context ? on : off);
}; };
const toDate = (ts: Timestamp): Date | null => { const toDate = (ts: Timestamp): Date | null => {
if (ts instanceof Date) return isNaN(ts.getTime()) ? null : ts; if (ts instanceof Date) return isNaN(ts.getTime()) ? null : ts;