Update bun packages and format
This commit is contained in:
		@@ -16,7 +16,8 @@ export const { auth, signIn, signOut, store, isAuthenticated } = convexAuth({
 | 
			
		||||
 | 
			
		||||
export const PASSWORD_MIN = 8;
 | 
			
		||||
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) => {
 | 
			
		||||
  const userId = await getAuthUserId(ctx);
 | 
			
		||||
 
 | 
			
		||||
@@ -9,26 +9,26 @@ import {
 | 
			
		||||
import type { Doc, Id } from './_generated/dataModel';
 | 
			
		||||
import { paginationOptsValidator } from 'convex/server';
 | 
			
		||||
 | 
			
		||||
type RWCtx = MutationCtx | QueryCtx
 | 
			
		||||
type RWCtx = MutationCtx | QueryCtx;
 | 
			
		||||
 | 
			
		||||
type StatusRow = {
 | 
			
		||||
  user: {
 | 
			
		||||
    id: Id<'users'>;
 | 
			
		||||
    name: string | null;
 | 
			
		||||
    imageUrl: string | null;
 | 
			
		||||
  },
 | 
			
		||||
  };
 | 
			
		||||
  status: {
 | 
			
		||||
    id: Id<'statuses'>;
 | 
			
		||||
    message: string;
 | 
			
		||||
    updatedAt: number;
 | 
			
		||||
    updatedBy: StatusRow['user'] | null;
 | 
			
		||||
  } | null,
 | 
			
		||||
  } | null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
type Paginated<T> = {
 | 
			
		||||
  page: T[],
 | 
			
		||||
  isDone: boolean,
 | 
			
		||||
  continueCursor: string | null,
 | 
			
		||||
  page: T[];
 | 
			
		||||
  isDone: boolean;
 | 
			
		||||
  continueCursor: string | null;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// CHANGED: typed helpers
 | 
			
		||||
@@ -240,9 +240,10 @@ export const listHistory = query({
 | 
			
		||||
    userId: v.optional(v.id('users')),
 | 
			
		||||
    paginationOpts: paginationOptsValidator,
 | 
			
		||||
  },
 | 
			
		||||
  handler: async (ctx, { userId, paginationOpts }): Promise<
 | 
			
		||||
    Paginated<StatusRow>
 | 
			
		||||
  > => {
 | 
			
		||||
  handler: async (
 | 
			
		||||
    ctx,
 | 
			
		||||
    { userId, paginationOpts },
 | 
			
		||||
  ): Promise<Paginated<StatusRow>> => {
 | 
			
		||||
    // Query statuses newest-first, optionally filtered by user
 | 
			
		||||
    const result = userId
 | 
			
		||||
      ? await ctx.db
 | 
			
		||||
@@ -250,17 +251,12 @@ export const listHistory = query({
 | 
			
		||||
          .withIndex('by_user_updatedAt', (q) => q.eq('userId', userId))
 | 
			
		||||
          .order('desc')
 | 
			
		||||
          .paginate(paginationOpts)
 | 
			
		||||
      : await ctx.db
 | 
			
		||||
          .query('statuses')
 | 
			
		||||
          .order('desc')
 | 
			
		||||
          .paginate(paginationOpts);
 | 
			
		||||
      : await ctx.db.query('statuses').order('desc').paginate(paginationOpts);
 | 
			
		||||
 | 
			
		||||
    // Cache user display objects to avoid refetching repeatedly
 | 
			
		||||
    const displayCache = new Map<string, StatusRow['user']>();
 | 
			
		||||
 | 
			
		||||
    const getDisplay = async (
 | 
			
		||||
      uid: Id<'users'>,
 | 
			
		||||
    ): Promise<StatusRow['user']> => {
 | 
			
		||||
    const getDisplay = async (uid: Id<'users'>): Promise<StatusRow['user']> => {
 | 
			
		||||
      const key = uid as unknown as string;
 | 
			
		||||
      const cached = displayCache.get(key);
 | 
			
		||||
      if (cached) return cached;
 | 
			
		||||
@@ -304,5 +300,3 @@ export const listHistory = query({
 | 
			
		||||
    };
 | 
			
		||||
  },
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -30,11 +30,9 @@ const signInFormSchema = z.object({
 | 
			
		||||
  email: z.email({
 | 
			
		||||
    message: 'Please enter a valid email address.',
 | 
			
		||||
  }),
 | 
			
		||||
  password: z.string()
 | 
			
		||||
    .regex(PASSWORD_REGEX, {
 | 
			
		||||
        message: 'Incorrect password. Does not meet requirements.',
 | 
			
		||||
      },
 | 
			
		||||
    ),
 | 
			
		||||
  password: z.string().regex(PASSWORD_REGEX, {
 | 
			
		||||
    message: 'Incorrect password. Does not meet requirements.',
 | 
			
		||||
  }),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const signUpFormSchema = z
 | 
			
		||||
@@ -51,8 +49,7 @@ const signUpFormSchema = z
 | 
			
		||||
        message: `Password must be at least ${PASSWORD_MIN} characters.`,
 | 
			
		||||
      })
 | 
			
		||||
      .max(PASSWORD_MAX, {
 | 
			
		||||
        message:
 | 
			
		||||
          `Password must be no more than ${PASSWORD_MAX} characters.`,
 | 
			
		||||
        message: `Password must be no more than ${PASSWORD_MAX} characters.`,
 | 
			
		||||
      })
 | 
			
		||||
      .regex(/^\S+$/, {
 | 
			
		||||
        message: 'Password must not contain whitespace.',
 | 
			
		||||
 
 | 
			
		||||
@@ -25,16 +25,17 @@ import { PASSWORD_MIN, PASSWORD_MAX, PASSWORD_REGEX } from '@/lib/types';
 | 
			
		||||
 | 
			
		||||
const formSchema = z
 | 
			
		||||
  .object({
 | 
			
		||||
    currentPassword: z
 | 
			
		||||
      .string()
 | 
			
		||||
      .regex(PASSWORD_REGEX, {
 | 
			
		||||
          message: 'Incorrect current password. Does not meet requirements.',
 | 
			
		||||
        },
 | 
			
		||||
      ),
 | 
			
		||||
    currentPassword: z.string().regex(PASSWORD_REGEX, {
 | 
			
		||||
      message: 'Incorrect current password. Does not meet requirements.',
 | 
			
		||||
    }),
 | 
			
		||||
    newPassword: z
 | 
			
		||||
      .string()
 | 
			
		||||
      .min(PASSWORD_MIN, { message: 'New password must be at least 8 characters.' })
 | 
			
		||||
      .max(PASSWORD_MAX, { message: 'New password must be less than 100 characters.' })
 | 
			
		||||
      .min(PASSWORD_MIN, {
 | 
			
		||||
        message: 'New password must be at least 8 characters.',
 | 
			
		||||
      })
 | 
			
		||||
      .max(PASSWORD_MAX, {
 | 
			
		||||
        message: 'New password must be less than 100 characters.',
 | 
			
		||||
      })
 | 
			
		||||
      .regex(/^\S+$/, {
 | 
			
		||||
        message: 'Password must not contain whitespace.',
 | 
			
		||||
      })
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ import {
 | 
			
		||||
} from '@/components/ui';
 | 
			
		||||
 | 
			
		||||
type StatusHistoryProps = {
 | 
			
		||||
  user?: typeof api.statuses.getCurrentForAll._returnType[0]['user'],
 | 
			
		||||
  user?: (typeof api.statuses.getCurrentForAll._returnType)[0]['user'];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const PAGE_SIZE = 25;
 | 
			
		||||
@@ -58,8 +58,7 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
 | 
			
		||||
    const nextIndex = pageIndex + 1;
 | 
			
		||||
    setCursors((prev) => {
 | 
			
		||||
      const copy = [...prev];
 | 
			
		||||
      if (copy[nextIndex] === undefined)
 | 
			
		||||
        copy[nextIndex] = data.continueCursor;
 | 
			
		||||
      if (copy[nextIndex] === undefined) copy[nextIndex] = data.continueCursor;
 | 
			
		||||
      return copy;
 | 
			
		||||
    });
 | 
			
		||||
  }, [data, pageIndex]);
 | 
			
		||||
@@ -91,14 +90,14 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
 | 
			
		||||
                className='w-8 h-8 md:w-12 md:h-12'
 | 
			
		||||
              />
 | 
			
		||||
            ) : (
 | 
			
		||||
                <Image
 | 
			
		||||
                  src='/favicon.png'
 | 
			
		||||
                  alt='Tech Tracker Logo'
 | 
			
		||||
                  width={32}
 | 
			
		||||
                  height={32}
 | 
			
		||||
              <Image
 | 
			
		||||
                src='/favicon.png'
 | 
			
		||||
                alt='Tech Tracker Logo'
 | 
			
		||||
                width={32}
 | 
			
		||||
                height={32}
 | 
			
		||||
                className='w-8 h-8 md:w-12 md:h-12'
 | 
			
		||||
                />
 | 
			
		||||
              )}
 | 
			
		||||
              />
 | 
			
		||||
            )}
 | 
			
		||||
            <h1 className='text-lg md:text-2xl lg:text-4xl font-bold pl-2 md:pl-4'>
 | 
			
		||||
              {user ? `${user.name ?? 'Technician'}'s History` : 'All History'}
 | 
			
		||||
            </h1>
 | 
			
		||||
@@ -135,10 +134,7 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
 | 
			
		||||
                      {r.user.name ?? 'Technician'}
 | 
			
		||||
                    </TableCell>
 | 
			
		||||
                    <TableCell className='max-w-xs'>
 | 
			
		||||
                      <div
 | 
			
		||||
                        className='truncate'
 | 
			
		||||
                        title={r.status?.message ?? ''}
 | 
			
		||||
                      >
 | 
			
		||||
                      <div className='truncate' title={r.status?.message ?? ''}>
 | 
			
		||||
                        {r.status?.message ?? 'No status'}
 | 
			
		||||
                      </div>
 | 
			
		||||
                    </TableCell>
 | 
			
		||||
@@ -147,9 +143,9 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
 | 
			
		||||
                    </TableCell>
 | 
			
		||||
                    <TableCell className='text-right text-sm'>
 | 
			
		||||
                      {r.status
 | 
			
		||||
                        ? `${formatTime(r.status.updatedAt)} · ${
 | 
			
		||||
                            formatDate(r.status.updatedAt)
 | 
			
		||||
                          }`
 | 
			
		||||
                        ? `${formatTime(r.status.updatedAt)} · ${formatDate(
 | 
			
		||||
                            r.status.updatedAt,
 | 
			
		||||
                          )}`
 | 
			
		||||
                        : '--:-- · --/--'}
 | 
			
		||||
                    </TableCell>
 | 
			
		||||
                  </TableRow>
 | 
			
		||||
@@ -161,7 +157,6 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <DrawerFooter>
 | 
			
		||||
 | 
			
		||||
        <Pagination>
 | 
			
		||||
          <PaginationContent>
 | 
			
		||||
            <PaginationPrevious
 | 
			
		||||
@@ -175,9 +170,7 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
 | 
			
		||||
            />
 | 
			
		||||
            <div className='flex items-center gap-2 text-sm text-muted-foreground'>
 | 
			
		||||
              <span>Page</span>
 | 
			
		||||
              <span className='font-bold text-foreground'>
 | 
			
		||||
                {pageIndex + 1}
 | 
			
		||||
              </span>
 | 
			
		||||
              <span className='font-bold text-foreground'>{pageIndex + 1}</span>
 | 
			
		||||
            </div>
 | 
			
		||||
            <PaginationNext
 | 
			
		||||
              href='#'
 | 
			
		||||
@@ -192,10 +185,7 @@ export const StatusHistory = ({ user }: StatusHistoryProps) => {
 | 
			
		||||
        </Pagination>
 | 
			
		||||
 | 
			
		||||
        <DrawerClose asChild>
 | 
			
		||||
          <Button
 | 
			
		||||
            variant='outline'
 | 
			
		||||
            className='mt-4'
 | 
			
		||||
          >
 | 
			
		||||
          <Button variant='outline' className='mt-4'>
 | 
			
		||||
            Close
 | 
			
		||||
          </Button>
 | 
			
		||||
        </DrawerClose>
 | 
			
		||||
 
 | 
			
		||||
@@ -151,7 +151,8 @@ export const StatusList = ({
 | 
			
		||||
            >
 | 
			
		||||
              <CardContent className='p-2'>
 | 
			
		||||
                <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'
 | 
			
		||||
                  >
 | 
			
		||||
                    <BasedAvatar
 | 
			
		||||
@@ -159,12 +160,12 @@ export const StatusList = ({
 | 
			
		||||
                      fullName={u.name ?? 'Technician'}
 | 
			
		||||
                      className={tvMode ? 'w-16 h-16' : 'w-12 h-12'}
 | 
			
		||||
                    />
 | 
			
		||||
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div className='flex-1'>
 | 
			
		||||
                    <div className='flex items-start justify-between mb-2'>
 | 
			
		||||
                      <div>
 | 
			
		||||
                        <h3 className={`font-semibold cursor-pointer
 | 
			
		||||
                        <h3
 | 
			
		||||
                          className={`font-semibold cursor-pointer
 | 
			
		||||
                            hover:text-primary/80 truncate
 | 
			
		||||
                            ${tvMode ? 'text-3xl' : 'text-2xl'}
 | 
			
		||||
                          `}
 | 
			
		||||
@@ -172,7 +173,8 @@ export const StatusList = ({
 | 
			
		||||
                          {u.name ?? 'Technician'}
 | 
			
		||||
                        </h3>
 | 
			
		||||
 | 
			
		||||
                        <div className={`pl-2 pr-15 pt-2
 | 
			
		||||
                        <div
 | 
			
		||||
                          className={`pl-2 pr-15 pt-2
 | 
			
		||||
                            ${tvMode ? 'text-2xl' : 'text-xl'}
 | 
			
		||||
                          `}
 | 
			
		||||
                        >
 | 
			
		||||
@@ -187,7 +189,9 @@ export const StatusList = ({
 | 
			
		||||
                            text-muted-foreground flex-shrink-0'
 | 
			
		||||
                          >
 | 
			
		||||
                            <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'}>
 | 
			
		||||
                                {s ? formatTime(s.updatedAt) : '--:--'}
 | 
			
		||||
                              </span>
 | 
			
		||||
@@ -208,7 +212,9 @@ export const StatusList = ({
 | 
			
		||||
                                  fullName={s.updatedBy.name ?? 'User'}
 | 
			
		||||
                                  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'>
 | 
			
		||||
                                    <p>Updated by</p>
 | 
			
		||||
                                    {s.updatedBy.name ?? 'User'}
 | 
			
		||||
@@ -267,10 +273,7 @@ export const StatusList = ({
 | 
			
		||||
                >
 | 
			
		||||
                  {selectedUserIds.length > 0
 | 
			
		||||
                    ? `Update ${selectedUserIds.length}
 | 
			
		||||
                      ${selectedUserIds.length > 1
 | 
			
		||||
                        ? 'users'
 | 
			
		||||
                        : 'user'
 | 
			
		||||
                      }`
 | 
			
		||||
                      ${selectedUserIds.length > 1 ? 'users' : 'user'}`
 | 
			
		||||
                    : 'Update Status'}
 | 
			
		||||
                </SubmitButton>
 | 
			
		||||
              </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -143,9 +143,7 @@ export const StatusTable = ({
 | 
			
		||||
            <th className={thCn}>Updated At</th>
 | 
			
		||||
          </tr>
 | 
			
		||||
        </thead>
 | 
			
		||||
        <tbody>
 | 
			
		||||
 | 
			
		||||
        </tbody>
 | 
			
		||||
        <tbody></tbody>
 | 
			
		||||
      </table>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
 
 | 
			
		||||
@@ -51,9 +51,9 @@ export {
 | 
			
		||||
  PaginationPrevious,
 | 
			
		||||
  PaginationNext,
 | 
			
		||||
  PaginationEllipsis,
 | 
			
		||||
} from './pagination'
 | 
			
		||||
} from './pagination';
 | 
			
		||||
export { Progress } from './progress';
 | 
			
		||||
export { ScrollArea, ScrollBar } from './scroll-area'
 | 
			
		||||
export { ScrollArea, ScrollBar } from './scroll-area';
 | 
			
		||||
export { Separator } from './separator';
 | 
			
		||||
export { StatusMessage } from './status-message';
 | 
			
		||||
export { SubmitButton } from './submit-button';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,68 +1,68 @@
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import {
 | 
			
		||||
  ChevronLeftIcon,
 | 
			
		||||
  ChevronRightIcon,
 | 
			
		||||
  MoreHorizontalIcon,
 | 
			
		||||
} from "lucide-react"
 | 
			
		||||
} from 'lucide-react';
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import { Button, buttonVariants } from "@/components/ui/button"
 | 
			
		||||
import { cn } from '@/lib/utils';
 | 
			
		||||
import { Button, buttonVariants } from '@/components/ui/button';
 | 
			
		||||
 | 
			
		||||
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
 | 
			
		||||
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <nav
 | 
			
		||||
      role="navigation"
 | 
			
		||||
      aria-label="pagination"
 | 
			
		||||
      data-slot="pagination"
 | 
			
		||||
      className={cn("mx-auto flex w-full justify-center", className)}
 | 
			
		||||
      role='navigation'
 | 
			
		||||
      aria-label='pagination'
 | 
			
		||||
      data-slot='pagination'
 | 
			
		||||
      className={cn('mx-auto flex w-full justify-center', className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function PaginationContent({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<"ul">) {
 | 
			
		||||
}: React.ComponentProps<'ul'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <ul
 | 
			
		||||
      data-slot="pagination-content"
 | 
			
		||||
      className={cn("flex flex-row items-center gap-1", className)}
 | 
			
		||||
      data-slot='pagination-content'
 | 
			
		||||
      className={cn('flex flex-row items-center gap-1', className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
 | 
			
		||||
  return <li data-slot="pagination-item" {...props} />
 | 
			
		||||
function PaginationItem({ ...props }: React.ComponentProps<'li'>) {
 | 
			
		||||
  return <li data-slot='pagination-item' {...props} />;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type PaginationLinkProps = {
 | 
			
		||||
  isActive?: boolean
 | 
			
		||||
} & Pick<React.ComponentProps<typeof Button>, "size"> &
 | 
			
		||||
  React.ComponentProps<"a">
 | 
			
		||||
  isActive?: boolean;
 | 
			
		||||
} & Pick<React.ComponentProps<typeof Button>, 'size'> &
 | 
			
		||||
  React.ComponentProps<'a'>;
 | 
			
		||||
 | 
			
		||||
function PaginationLink({
 | 
			
		||||
  className,
 | 
			
		||||
  isActive,
 | 
			
		||||
  size = "icon",
 | 
			
		||||
  size = 'icon',
 | 
			
		||||
  ...props
 | 
			
		||||
}: PaginationLinkProps) {
 | 
			
		||||
  return (
 | 
			
		||||
    <a
 | 
			
		||||
      aria-current={isActive ? "page" : undefined}
 | 
			
		||||
      data-slot="pagination-link"
 | 
			
		||||
      aria-current={isActive ? 'page' : undefined}
 | 
			
		||||
      data-slot='pagination-link'
 | 
			
		||||
      data-active={isActive}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        buttonVariants({
 | 
			
		||||
          variant: isActive ? "outline" : "ghost",
 | 
			
		||||
          variant: isActive ? 'outline' : 'ghost',
 | 
			
		||||
          size,
 | 
			
		||||
        }),
 | 
			
		||||
        className
 | 
			
		||||
        className,
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function PaginationPrevious({
 | 
			
		||||
@@ -71,15 +71,15 @@ function PaginationPrevious({
 | 
			
		||||
}: React.ComponentProps<typeof PaginationLink>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <PaginationLink
 | 
			
		||||
      aria-label="Go to previous page"
 | 
			
		||||
      size="default"
 | 
			
		||||
      className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
 | 
			
		||||
      aria-label='Go to previous page'
 | 
			
		||||
      size='default'
 | 
			
		||||
      className={cn('gap-1 px-2.5 sm:pl-2.5', className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <ChevronLeftIcon />
 | 
			
		||||
      <span className="hidden sm:block">Previous</span>
 | 
			
		||||
      <span className='hidden sm:block'>Previous</span>
 | 
			
		||||
    </PaginationLink>
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function PaginationNext({
 | 
			
		||||
@@ -88,32 +88,32 @@ function PaginationNext({
 | 
			
		||||
}: React.ComponentProps<typeof PaginationLink>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <PaginationLink
 | 
			
		||||
      aria-label="Go to next page"
 | 
			
		||||
      size="default"
 | 
			
		||||
      className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
 | 
			
		||||
      aria-label='Go to next page'
 | 
			
		||||
      size='default'
 | 
			
		||||
      className={cn('gap-1 px-2.5 sm:pr-2.5', className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <span className="hidden sm:block">Next</span>
 | 
			
		||||
      <span className='hidden sm:block'>Next</span>
 | 
			
		||||
      <ChevronRightIcon />
 | 
			
		||||
    </PaginationLink>
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function PaginationEllipsis({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<"span">) {
 | 
			
		||||
}: React.ComponentProps<'span'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <span
 | 
			
		||||
      aria-hidden
 | 
			
		||||
      data-slot="pagination-ellipsis"
 | 
			
		||||
      className={cn("flex size-9 items-center justify-center", className)}
 | 
			
		||||
      data-slot='pagination-ellipsis'
 | 
			
		||||
      className={cn('flex size-9 items-center justify-center', className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <MoreHorizontalIcon className="size-4" />
 | 
			
		||||
      <span className="sr-only">More pages</span>
 | 
			
		||||
      <MoreHorizontalIcon className='size-4' />
 | 
			
		||||
      <span className='sr-only'>More pages</span>
 | 
			
		||||
    </span>
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
@@ -124,4 +124,4 @@ export {
 | 
			
		||||
  PaginationPrevious,
 | 
			
		||||
  PaginationNext,
 | 
			
		||||
  PaginationEllipsis,
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
"use client"
 | 
			
		||||
'use client';
 | 
			
		||||
 | 
			
		||||
import * as React from "react"
 | 
			
		||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area"
 | 
			
		||||
import * as React from 'react';
 | 
			
		||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area';
 | 
			
		||||
 | 
			
		||||
import { cn } from "@/lib/utils"
 | 
			
		||||
import { cn } from '@/lib/utils';
 | 
			
		||||
 | 
			
		||||
function ScrollArea({
 | 
			
		||||
  className,
 | 
			
		||||
@@ -12,47 +12,47 @@ function ScrollArea({
 | 
			
		||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <ScrollAreaPrimitive.Root
 | 
			
		||||
      data-slot="scroll-area"
 | 
			
		||||
      className={cn("relative", className)}
 | 
			
		||||
      data-slot='scroll-area'
 | 
			
		||||
      className={cn('relative', className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <ScrollAreaPrimitive.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"
 | 
			
		||||
        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'
 | 
			
		||||
      >
 | 
			
		||||
        {children}
 | 
			
		||||
      </ScrollAreaPrimitive.Viewport>
 | 
			
		||||
      <ScrollBar />
 | 
			
		||||
      <ScrollAreaPrimitive.Corner />
 | 
			
		||||
    </ScrollAreaPrimitive.Root>
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function ScrollBar({
 | 
			
		||||
  className,
 | 
			
		||||
  orientation = "vertical",
 | 
			
		||||
  orientation = 'vertical',
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <ScrollAreaPrimitive.ScrollAreaScrollbar
 | 
			
		||||
      data-slot="scroll-area-scrollbar"
 | 
			
		||||
      data-slot='scroll-area-scrollbar'
 | 
			
		||||
      orientation={orientation}
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "flex touch-none p-px transition-colors select-none",
 | 
			
		||||
        orientation === "vertical" &&
 | 
			
		||||
          "h-full w-2.5 border-l border-l-transparent",
 | 
			
		||||
        orientation === "horizontal" &&
 | 
			
		||||
          "h-2.5 flex-col border-t border-t-transparent",
 | 
			
		||||
        className
 | 
			
		||||
        'flex touch-none p-px transition-colors select-none',
 | 
			
		||||
        orientation === 'vertical' &&
 | 
			
		||||
          'h-full w-2.5 border-l border-l-transparent',
 | 
			
		||||
        orientation === 'horizontal' &&
 | 
			
		||||
          'h-2.5 flex-col border-t border-t-transparent',
 | 
			
		||||
        className,
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    >
 | 
			
		||||
      <ScrollAreaPrimitive.ScrollAreaThumb
 | 
			
		||||
        data-slot="scroll-area-thumb"
 | 
			
		||||
        className="bg-border relative flex-1 rounded-full"
 | 
			
		||||
        data-slot='scroll-area-thumb'
 | 
			
		||||
        className='bg-border relative flex-1 rounded-full'
 | 
			
		||||
      />
 | 
			
		||||
    </ScrollAreaPrimitive.ScrollAreaScrollbar>
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export { ScrollArea, ScrollBar }
 | 
			
		||||
export { ScrollArea, ScrollBar };
 | 
			
		||||
 
 | 
			
		||||
@@ -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 (
 | 
			
		||||
    <div
 | 
			
		||||
      data-slot="table-container"
 | 
			
		||||
      className="relative w-full overflow-x-auto"
 | 
			
		||||
      data-slot='table-container'
 | 
			
		||||
      className='relative w-full overflow-x-auto'
 | 
			
		||||
    >
 | 
			
		||||
      <table
 | 
			
		||||
        data-slot="table"
 | 
			
		||||
        className={cn("w-full caption-bottom text-sm", className)}
 | 
			
		||||
        data-slot='table'
 | 
			
		||||
        className={cn('w-full caption-bottom text-sm', className)}
 | 
			
		||||
        {...props}
 | 
			
		||||
      />
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function TableHeader({ className, ...props }: React.ComponentProps<"thead">) {
 | 
			
		||||
function TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <thead
 | 
			
		||||
      data-slot="table-header"
 | 
			
		||||
      className={cn("[&_tr]:border-b", className)}
 | 
			
		||||
      data-slot='table-header'
 | 
			
		||||
      className={cn('[&_tr]:border-b', className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function TableBody({ className, ...props }: React.ComponentProps<"tbody">) {
 | 
			
		||||
function TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <tbody
 | 
			
		||||
      data-slot="table-body"
 | 
			
		||||
      className={cn("[&_tr:last-child]:border-0", className)}
 | 
			
		||||
      data-slot='table-body'
 | 
			
		||||
      className={cn('[&_tr:last-child]:border-0', className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function TableFooter({ className, ...props }: React.ComponentProps<"tfoot">) {
 | 
			
		||||
function TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <tfoot
 | 
			
		||||
      data-slot="table-footer"
 | 
			
		||||
      data-slot='table-footer'
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "bg-muted/50 border-t font-medium [&>tr]:last:border-b-0",
 | 
			
		||||
        className
 | 
			
		||||
        'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',
 | 
			
		||||
        className,
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function TableRow({ className, ...props }: React.ComponentProps<"tr">) {
 | 
			
		||||
function TableRow({ className, ...props }: React.ComponentProps<'tr'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <tr
 | 
			
		||||
      data-slot="table-row"
 | 
			
		||||
      data-slot='table-row'
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors",
 | 
			
		||||
        className
 | 
			
		||||
        'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',
 | 
			
		||||
        className,
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function TableHead({ className, ...props }: React.ComponentProps<"th">) {
 | 
			
		||||
function TableHead({ className, ...props }: React.ComponentProps<'th'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <th
 | 
			
		||||
      data-slot="table-head"
 | 
			
		||||
      data-slot='table-head'
 | 
			
		||||
      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]",
 | 
			
		||||
        className
 | 
			
		||||
        '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,
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function TableCell({ className, ...props }: React.ComponentProps<"td">) {
 | 
			
		||||
function TableCell({ className, ...props }: React.ComponentProps<'td'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <td
 | 
			
		||||
      data-slot="table-cell"
 | 
			
		||||
      data-slot='table-cell'
 | 
			
		||||
      className={cn(
 | 
			
		||||
        "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
 | 
			
		||||
        className
 | 
			
		||||
        'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
 | 
			
		||||
        className,
 | 
			
		||||
      )}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function TableCaption({
 | 
			
		||||
  className,
 | 
			
		||||
  ...props
 | 
			
		||||
}: React.ComponentProps<"caption">) {
 | 
			
		||||
}: React.ComponentProps<'caption'>) {
 | 
			
		||||
  return (
 | 
			
		||||
    <caption
 | 
			
		||||
      data-slot="table-caption"
 | 
			
		||||
      className={cn("text-muted-foreground mt-4 text-sm", className)}
 | 
			
		||||
      data-slot='table-caption'
 | 
			
		||||
      className={cn('text-muted-foreground mt-4 text-sm', className)}
 | 
			
		||||
      {...props}
 | 
			
		||||
    />
 | 
			
		||||
  )
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export {
 | 
			
		||||
@@ -113,4 +113,4 @@ export {
 | 
			
		||||
  TableRow,
 | 
			
		||||
  TableCell,
 | 
			
		||||
  TableCaption,
 | 
			
		||||
}
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
export const PASSWORD_MIN = 8;
 | 
			
		||||
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;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@ export const ccn = ({
 | 
			
		||||
  return twMerge(className, context ? on : off);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const toDate = (ts: Timestamp): Date | null => {
 | 
			
		||||
  if (ts instanceof Date) return isNaN(ts.getTime()) ? null : ts;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user