diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..72e9aa42 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,7 @@ +Dockerfile +.dockerignore +node_modules +npm-debug.log +README.md +.next +.git \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index fd84c1ed..ff064ce3 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -25,6 +25,7 @@ const config = { 'plugin:storybook/recommended', 'prettier', ], + ignorePatterns: ['node_modules', 'lib', '*.stories.*', '*.test.*'], rules: { 'no-process-env': 'error', 'no-console': 'error', diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..73981ae5 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,55 @@ +name: Build + +on: + pull_request: + branches: + - '*' + +env: + SKIP_ENV_VALIDATION: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - uses: pnpm/action-setup@v2 + with: + version: 8 + + - name: Get pnpm store directory + shell: bash + run: | + echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV + + - uses: actions/cache@v3 + name: Setup pnpm cache + with: + path: ${{ env.STORE_PATH }} + key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} + restore-keys: | + ${{ runner.os }}-pnpm-store- + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Generate prisma client + run: pnpm prisma generate + + - uses: actions/cache@v3 + name: Setup next cache + with: + path: | + ${{ env.STORE_PATH }} + ${{ github.workspace }}/.next/cache + # Generate a new cache whenever packages or source files change. + key: ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ hashFiles('**/*.js', '**/*.jsx', '**/*.ts', '**/*.tsx') }} + # If source files changed but packages didn't, rebuild from a prior cache. + restore-keys: | + ${{ runner.os }}-nextjs-${{ hashFiles('**/pnpm-lock.yaml') }}- + + - name: Run build + run: pnpm next build diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 00000000..54ac48fe --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,35 @@ +name: Build docker container +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +env: + SKIP_ENV_VALIDATION: true + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set env + run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV + + - name: Build the production image + run: docker build -t fresco . + + - name: Login to GitHub Container Registry + uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push the production image to the container registry + if: github.ref == 'refs/heads/master' + run: | + docker tag app ghcr.io/${{ github.repository }}:${{ env.RELEASE_VERSION }} + docker push ghcr.io/${{ github.repository }}:${{ env.RELEASE_VERSION }} diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..5e88edde --- /dev/null +++ b/Dockerfile @@ -0,0 +1,62 @@ +FROM node:18-alpine AS base +RUN corepack enable +ENV SKIP_ENV_VALIDATION=true + +# Install dependencies only when needed +FROM base AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +RUN apk add --no-cache libc6-compat +WORKDIR /app +# Prisma stuff +COPY prisma ./prisma +# Install dependencies +COPY package.json pnpm-lock.yaml* ./ +RUN pnpm install +RUN pnpm prisma generate + + +# Rebuild the source code only when needed +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . + + +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN pnpm run build + +# If using npm comment out above and use below instead +# RUN npm run build + +# Production image, copy all the files and run next +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV production +# Uncomment the following line in case you want to disable telemetry during runtime. +ENV NEXT_TELEMETRY_DISABLED 1 + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public + +# Set the correct permission for prerender cache +RUN mkdir .next +RUN chown nextjs:nodejs .next + +# Automatically leverage output traces to reduce image size +# https://nextjs.org/docs/advanced-features/output-file-tracing +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT 3000 +# set hostname to localhost +ENV HOSTNAME "0.0.0.0" + +CMD ["node", "server.js"] \ No newline at end of file diff --git a/app/(dashboard)/dashboard/_actions/importProtocol.ts b/app/(dashboard)/dashboard/_actions/importProtocol.ts index 399aede9..53b27912 100644 --- a/app/(dashboard)/dashboard/_actions/importProtocol.ts +++ b/app/(dashboard)/dashboard/_actions/importProtocol.ts @@ -57,7 +57,7 @@ export const uploadProtocolAssets = async (protocol: NCProtocol, zip: Zip) => { const file = new Blob([blob], { type: `application/${fileExtension}`, - }) as FileEsque; + }) as File; data.append('files', file, `${asset.id}.${fileExtension}`); }), diff --git a/app/(dashboard)/dashboard/_components/InterviewsTable/Columns.tsx b/app/(dashboard)/dashboard/_components/InterviewsTable/Columns.tsx index 5f99ecab..04676a14 100644 --- a/app/(dashboard)/dashboard/_components/InterviewsTable/Columns.tsx +++ b/app/(dashboard)/dashboard/_components/InterviewsTable/Columns.tsx @@ -5,7 +5,6 @@ import type { Interview } from '@prisma/client'; import { ActionsDropdown } from '~/components/DataTable/ActionsDropdown'; import { Checkbox } from '~/components/ui/checkbox'; import { DataTableColumnHeader } from '~/components/DataTable/ColumnHeader'; - import { Tooltip, TooltipContent, @@ -13,6 +12,7 @@ import { TooltipTrigger, } from '~/components/ui/tooltip'; import { Settings } from 'lucide-react'; +import { DropdownMenuItem } from '~/components/ui/dropdown-menu'; type InterviewWithoutNetwork = Omit; @@ -139,9 +139,14 @@ export const InterviewColumns = ( menuItems={[ { label: 'Delete', - id: row.original.id, - idendtifier: row.original.id, - deleteItem: handleDelete, + row, + component: ( + void handleDelete(row.original.id)} + > + Edit + + ), }, ]} /> diff --git a/app/(dashboard)/dashboard/_components/InterviewsTable/InterviewsTable.tsx b/app/(dashboard)/dashboard/_components/InterviewsTable/InterviewsTable.tsx index ee5473df..d6d18121 100644 --- a/app/(dashboard)/dashboard/_components/InterviewsTable/InterviewsTable.tsx +++ b/app/(dashboard)/dashboard/_components/InterviewsTable/InterviewsTable.tsx @@ -47,14 +47,13 @@ export const InterviewsTable = () => { columns={InterviewColumns(handleDelete)} data={convertedData} filterColumnAccessorKey="id" - handleDeleteSelected={(data: InterviewWithoutNetwork[]) => { - deleteInterviews(data) - .then((result) => { - if (result.error) throw new Error(result.error); - }) - .catch((error) => { - console.error(error); - }); + handleDeleteSelected={async (data: InterviewWithoutNetwork[]) => { + try { + await deleteInterviews(data); + } catch (error) { + // eslint-disable-next-line no-console + console.error(error); + } }} /> ); diff --git a/app/(dashboard)/dashboard/_components/InterviewsTable/Loader.ts b/app/(dashboard)/dashboard/_components/InterviewsTable/Loader.ts deleted file mode 100644 index c17b034f..00000000 --- a/app/(dashboard)/dashboard/_components/InterviewsTable/Loader.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { prisma } from '~/utils/db'; -import { safeLoader } from '~/utils/safeLoader'; -import { z } from 'zod'; - -const InterviewValidation = z.array( - z.object({ - id: z.string(), - startTime: z.date(), - finishTime: z.date().nullable(), - exportTime: z.date().nullable(), - lastUpdated: z.date(), - protocolId: z.string(), - currentStep: z.number(), - network: z.string(), - }), -); - -export const safeLoadInterviews = safeLoader({ - outputValidation: InterviewValidation, - loader: () => - prisma.interview.findMany({ - include: { - protocol: true, - }, - }), -}); diff --git a/app/(dashboard)/dashboard/_components/NavigationBar.tsx b/app/(dashboard)/dashboard/_components/NavigationBar.tsx index 46719785..c10aebfb 100644 --- a/app/(dashboard)/dashboard/_components/NavigationBar.tsx +++ b/app/(dashboard)/dashboard/_components/NavigationBar.tsx @@ -6,7 +6,8 @@ import Link from 'next/link'; import { usePathname } from 'next/navigation'; import { cn } from '~/utils/shadcn'; import UserMenu from './UserMenu'; -import { UrlObject } from 'url'; +import type { UrlObject } from 'url'; +import type { Route } from 'next'; export const NavButton = ({ children, @@ -14,7 +15,7 @@ export const NavButton = ({ isActive = false, }: { children: React.ReactNode; - href: UrlObject; + href: UrlObject | Route; isActive?: boolean; }) => { return ( @@ -51,7 +52,7 @@ export function NavigationBar() { Home Protocols diff --git a/app/(dashboard)/dashboard/_components/ParticipantsTable/ParticipantsTable.tsx b/app/(dashboard)/dashboard/_components/ParticipantsTable/ParticipantsTable.tsx index fb395c14..99620ce8 100644 --- a/app/(dashboard)/dashboard/_components/ParticipantsTable/ParticipantsTable.tsx +++ b/app/(dashboard)/dashboard/_components/ParticipantsTable/ParticipantsTable.tsx @@ -28,6 +28,7 @@ export const ParticipantsTable = ({ initialData, refetchOnMount: false, onError(error) { + // eslint-disable-next-line no-console console.error(error); }, }); diff --git a/app/(dashboard)/dashboard/_components/ProtocolUploader.tsx b/app/(dashboard)/dashboard/_components/ProtocolUploader.tsx index 21181794..1aef7252 100644 --- a/app/(dashboard)/dashboard/_components/ProtocolUploader.tsx +++ b/app/(dashboard)/dashboard/_components/ProtocolUploader.tsx @@ -2,7 +2,7 @@ import { useDropzone } from 'react-dropzone'; import { ChevronDown, ChevronUp } from 'lucide-react'; import type { FileWithPath } from 'react-dropzone'; -import { generateReactHelpers } from '~/utils/uploadthing/useUploadThing'; +import { generateReactHelpers } from '@uploadthing/react/hooks'; import { useState, useCallback } from 'react'; import { importProtocol } from '../_actions/importProtocol'; @@ -102,9 +102,8 @@ export default function ProtocolUploader({ }); }; - const { startUpload } = useUploadThing({ - endpoint: 'protocolUploader', - onClientUploadComplete: handleUploadComplete, + const { startUpload } = useUploadThing('protocolUploader', { + onClientUploadComplete: (res) => void handleUploadComplete(res), onUploadError: (error) => { setOpen(true); setDialogContent({ @@ -123,12 +122,6 @@ export default function ProtocolUploader({ error: '', }); }, - onUploadProgress: (file, progress) => { - console.log( - '🚀 ~ file: ProtocolUploader.tsx:102 ~ ProtocolUploader ~ file>:progress', - `${file}>${progress}`, - ); - }, }); const onDrop = useCallback( @@ -141,6 +134,7 @@ export default function ProtocolUploader({ }); startUpload([file]).catch((e: Error) => { + // eslint-disable-next-line no-console console.log(e); setOpen(true); setDialogContent({ @@ -223,7 +217,7 @@ export default function ProtocolUploader({ {!dialogContent.progress && !dialogContent.error && (
void form.handleSubmit(onSubmit)} className="w-full space-y-6" >
diff --git a/app/(dashboard)/dashboard/_components/ProtocolsTable/Columns.tsx b/app/(dashboard)/dashboard/_components/ProtocolsTable/Columns.tsx index 07c549ba..11dc09ac 100644 --- a/app/(dashboard)/dashboard/_components/ProtocolsTable/Columns.tsx +++ b/app/(dashboard)/dashboard/_components/ProtocolsTable/Columns.tsx @@ -92,7 +92,7 @@ export const ProtocolColumns: ColumnDef[] = [ ), cell: () => { - return ; + return ; }, }, ]; diff --git a/app/(dashboard)/dashboard/_components/ProtocolsTable/Loader.ts b/app/(dashboard)/dashboard/_components/ProtocolsTable/Loader.ts deleted file mode 100644 index 124b921c..00000000 --- a/app/(dashboard)/dashboard/_components/ProtocolsTable/Loader.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { prisma } from '~/utils/db'; -import { safeLoader } from '~/utils/safeLoader'; -import { z } from 'zod'; - -const ProtocolValidation = z.array( - z.object({ - id: z.string(), - hash: z.string(), - name: z.string(), - schemaVersion: z.number(), - description: z.string(), - assetPath: z.string(), - importedAt: z.date(), - lastModified: z.date(), - stages: z.string(), - }), -); - -export const safeLoadProtocols = safeLoader({ - outputValidation: ProtocolValidation, - loader: () => prisma.protocol.findMany(), -}); diff --git a/app/(dashboard)/dashboard/_components/ProtocolsTable/ProtocolsTable.tsx b/app/(dashboard)/dashboard/_components/ProtocolsTable/ProtocolsTable.tsx index 20ffde5c..0a436ba6 100644 --- a/app/(dashboard)/dashboard/_components/ProtocolsTable/ProtocolsTable.tsx +++ b/app/(dashboard)/dashboard/_components/ProtocolsTable/ProtocolsTable.tsx @@ -1,10 +1,14 @@ import { DataTable } from '~/components/DataTable/DataTable'; -import { ProtocolColumns } from '~/app/(main)/_components/ProtocolsTable/Columns'; -import { safeLoadProtocols } from '~/app/(main)/_components/ProtocolsTable/Loader'; +import { ProtocolColumns } from './Columns'; +import { trpc } from '~/app/_trpc/server'; -const protocols = await safeLoadProtocols(); +export const ProtocolsTable = async () => { + const protocols = await trpc.protocol.get.all.query(undefined, { + context: { + revalidate: 0, + }, + }); -export const ProtocolsTable = () => { return ( + void onDrop(acceptedFiles, fileRejections), }); const componentId = id || name; diff --git a/app/(dashboard)/dashboard/participants/_components/ExportCSVParticipants.tsx b/app/(dashboard)/dashboard/participants/_components/ExportCSVParticipants.tsx index 68dc15b2..66a5785b 100644 --- a/app/(dashboard)/dashboard/participants/_components/ExportCSVParticipants.tsx +++ b/app/(dashboard)/dashboard/participants/_components/ExportCSVParticipants.tsx @@ -39,6 +39,7 @@ function ExportCSVParticipants({ // Clean up the URL object URL.revokeObjectURL(url); } catch (error) { + // eslint-disable-next-line no-console console.error(error); } diff --git a/app/(interview)/interview/[[...interview]]/page.tsx b/app/(interview)/interview/[[...interview]]/page.tsx index ffa68202..5fe58f7a 100644 --- a/app/(interview)/interview/[[...interview]]/page.tsx +++ b/app/(interview)/interview/[[...interview]]/page.tsx @@ -3,7 +3,7 @@ import { NetworkProvider } from '~/providers/NetworkProvider'; import Stage from '~/app/(interview)/interview/_components/Stage'; import { prisma } from '~/utils/db'; import InterviewNavigation from '~/app/(interview)/interview/_components/InterviewNavigation'; -import type { NcNetwork, Stage as StageType } from '@codaco/shared-consts'; +import type { NcNetwork, Protocol } from '@codaco/shared-consts'; import Link from 'next/link'; import { trpc } from '~/app/_trpc/server'; @@ -19,7 +19,14 @@ export default async function Page({ if (!interviewId) { return; } - const interview = await trpc.interview.get.byId.query({ id: interviewId }); + const interview = await trpc.interview.get.byId.query( + { id: interviewId }, + { + context: { + revalidate: 0, + }, + }, + ); if (!interview) { return
No interview found
; @@ -34,27 +41,17 @@ export default async function Page({ } redirect(`/interview/${interviewId}/1`); - - return null; } // Fetch the protocol stage configuration for the current page from the database - const { - network, - protocol: { stages }, - } = interview; - - if (!stages) { - return
No stages found
; - } - - const stagesJson = JSON.parse(stages) as StageType[]; + const network = interview.network as NcNetwork; + const protocol = interview.protocol as unknown as Protocol; + const stages = protocol.stages; - const currentStageConfig = - stagesJson[parseInt(currentInterviewPage!, 10) - 1]; + const currentStageConfig = stages[parseInt(currentInterviewPage!, 10) - 1]; if (!currentStageConfig) { - // redirect(`/interview/${interviewId}/1`); + return
No stage found
; } const updateNetwork = async (network: NcNetwork) => { @@ -78,7 +75,10 @@ export default async function Page({ }; return ( - + void updateNetwork(data)} + >

Interview

Exit interview diff --git a/app/(interview)/interview/_components/Stage.tsx b/app/(interview)/interview/_components/Stage.tsx index d4ffbb97..dabd52f7 100644 --- a/app/(interview)/interview/_components/Stage.tsx +++ b/app/(interview)/interview/_components/Stage.tsx @@ -1,7 +1,11 @@ 'use client'; import { useInterview } from '~/providers/NetworkProvider'; -import { Stage } from '@codaco/shared-consts'; +import { + Stage, + entityAttributesProperty, + entityPrimaryKeyProperty, +} from '@codaco/shared-consts'; import Button from '@codaco/ui/lib/components/Button'; import { v4 as uuid } from 'uuid'; @@ -30,7 +34,17 @@ const Stage = ({ stageConfig }: { stageConfig: Stage }) => {
-
diff --git a/app/(interview)/interview/error.tsx b/app/(interview)/interview/error.tsx index 3575bbdc..902d6348 100644 --- a/app/(interview)/interview/error.tsx +++ b/app/(interview)/interview/error.tsx @@ -1,7 +1,7 @@ -"use client"; // Error components must be Client components +'use client'; // Error components must be Client components -import { useEffect } from "react"; -import { Button } from "~/components/ui/Button"; +import { useEffect } from 'react'; +import { Button } from '~/components/ui/Button'; export default function Error({ error, @@ -12,6 +12,7 @@ export default function Error({ }) { useEffect(() => { // Log the error to an error reporting service + // eslint-disable-next-line no-console console.error(error); }, [error]); diff --git a/app/(onboard)/_components/OnboardSteps/ManageParticipants.tsx b/app/(onboard)/_components/OnboardSteps/ManageParticipants.tsx index 5103d1ff..4155c971 100644 --- a/app/(onboard)/_components/OnboardSteps/ManageParticipants.tsx +++ b/app/(onboard)/_components/OnboardSteps/ManageParticipants.tsx @@ -4,9 +4,10 @@ import ProtocolUploader from '~/app/(dashboard)/dashboard/_components/ProtocolUp import { Button } from '~/components/ui/Button'; import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import AnonymousRecruitmentSwitch from '~/app/(dashboard)/dashboard/_components/AnonymousRecruitmentSwitch'; +import type { Route } from 'next'; function ManageParticipants() { - const pathname = usePathname(); + const pathname = usePathname() as Route; const searchParams = useSearchParams(); const currentStep = searchParams.get('step') as string; const [participantsUploaded, setParticipantsUploaded] = useState(false); diff --git a/app/(onboard)/_components/OnboardSteps/StepsSidebar.tsx b/app/(onboard)/_components/OnboardSteps/StepsSidebar.tsx index ea361699..42eee7f1 100644 --- a/app/(onboard)/_components/OnboardSteps/StepsSidebar.tsx +++ b/app/(onboard)/_components/OnboardSteps/StepsSidebar.tsx @@ -46,7 +46,7 @@ function OnboardSteps({ currentStep }: { currentStep: keyof typeof steps }) { number={index} description={stepItem.description} active={index === currentStep} - complete={index < currentStep} + complete={index < Number(currentStep)} /> ))}
diff --git a/app/(onboard)/_components/SignUpForm.tsx b/app/(onboard)/_components/SignUpForm.tsx index 0d101dd7..955c8831 100644 --- a/app/(onboard)/_components/SignUpForm.tsx +++ b/app/(onboard)/_components/SignUpForm.tsx @@ -28,7 +28,7 @@ export const SignUpForm = ({ }); const { mutateAsync: signUp, isLoading } = trpc.session.signUp.useMutation({ - onSuccess: async (result) => { + onSuccess: (result) => { if (result.error) { const error = result.error; setSignupError(error); diff --git a/app/(onboard)/signin/page.tsx b/app/(onboard)/signin/page.tsx index 89725317..c14da9d2 100644 --- a/app/(onboard)/signin/page.tsx +++ b/app/(onboard)/signin/page.tsx @@ -1,7 +1,7 @@ import { userFormClasses } from '../_shared'; import SignInForm from '../_components/SignInForm'; import { cn } from '~/utils/shadcn'; -import { redirect } from 'next/navigation'; +import type { Route } from 'next'; export const metadata = { title: 'Fresco - Sign In', @@ -12,7 +12,7 @@ export default function Page({ searchParams, }: { searchParams: { - callbackUrl?: string; + callbackUrl?: Route; }; }) { const { callbackUrl } = searchParams; diff --git a/app/_trpc/server.ts b/app/_trpc/server.ts index ca24c755..d18cc5e0 100644 --- a/app/_trpc/server.ts +++ b/app/_trpc/server.ts @@ -4,6 +4,7 @@ import { loggerLink } from '@trpc/client'; import { experimental_nextHttpLink } from '@trpc/next/app-dir/links/nextHttp'; import { experimental_createTRPCNextAppDirServer } from '@trpc/next/app-dir/server'; import { cookies } from 'next/headers'; +import SuperJSON from 'superjson'; import { env } from '~/env.mjs'; import { type AppRouter } from '~/server/router'; import { getUrl } from '~/utils/getURL'; @@ -14,6 +15,7 @@ import { getUrl } from '~/utils/getURL'; export const trpc = experimental_createTRPCNextAppDirServer({ config() { return { + transformer: SuperJSON, links: [ loggerLink({ enabled: (opts) => diff --git a/app/api/parseProtocol/route.ts b/app/api/parseProtocol/route.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/app/layout.tsx b/app/layout.tsx index d824c540..558aee2a 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -3,7 +3,6 @@ import '~/styles/globals.scss'; import Providers from '../providers/Providers'; import RedirectWrapper from '~/components/RedirectWrapper'; import { trpc } from './_trpc/server'; -import type { Session } from 'lucia'; export const metadata = { title: 'Network Canvas Fresco', @@ -11,11 +10,12 @@ export const metadata = { }; async function RootLayout({ children }: { children: React.ReactNode }) { - const session = (await trpc.session.get.query(undefined, { + const session = await trpc.session.get.query(undefined, { context: { revalidate: 0, }, - })) as Session | null; + }); + const { expired, configured } = await trpc.appSettings.get.allappSettings.query(undefined, { context: { diff --git a/components/DataTable/ActionsDropdown.tsx b/components/DataTable/ActionsDropdown.tsx index b8b7285a..6f0fb0af 100644 --- a/components/DataTable/ActionsDropdown.tsx +++ b/components/DataTable/ActionsDropdown.tsx @@ -5,7 +5,6 @@ import { Button } from '~/components/ui/Button'; import { DropdownMenu, DropdownMenuContent, - DropdownMenuItem, DropdownMenuLabel, DropdownMenuTrigger, } from '~/components/ui/dropdown-menu'; @@ -19,7 +18,7 @@ interface Actions { } interface Props { - menuItems: TMenuItem[]; + menuItems?: TMenuItem[]; } export const ActionsDropdown = ({ @@ -35,9 +34,10 @@ export const ActionsDropdown = ({ Actions - {menuItems.map((item, index) => ( - {item.component} - ))} + {menuItems && + menuItems.map((item, index) => ( + {item.component} + ))} ); diff --git a/components/DataTable/DataTable.tsx b/components/DataTable/DataTable.tsx index b54d7e0f..ad3459b0 100644 --- a/components/DataTable/DataTable.tsx +++ b/components/DataTable/DataTable.tsx @@ -10,7 +10,6 @@ import { type SortingState, } from '@tanstack/react-table'; import { useState } from 'react'; - import { Button } from '~/components/ui/Button'; import { Input } from '~/components/ui/Input'; import { @@ -21,7 +20,6 @@ import { TableHeader, TableRow, } from '~/components/ui/table'; - import { makeDefaultColumns } from '~/components/DataTable/DefaultColumns'; import { Loader } from 'lucide-react'; @@ -29,7 +27,7 @@ interface DataTableProps { columns?: ColumnDef[]; data: TData[]; filterColumnAccessorKey?: string; - handleDeleteSelected: (data: TData[]) => Promise; + handleDeleteSelected?: (data: TData[]) => Promise; } export function DataTable({ @@ -55,8 +53,9 @@ export function DataTable({ .rows.map((r) => r.original); try { - await handleDeleteSelected(selectedData); + await handleDeleteSelected?.(selectedData); } catch (error) { + // eslint-disable-next-line no-console console.error(error); } diff --git a/components/ui/alert-dialog.tsx b/components/ui/alert-dialog.tsx index 9a7bdb32..3eee4ff9 100644 --- a/components/ui/alert-dialog.tsx +++ b/components/ui/alert-dialog.tsx @@ -21,6 +21,7 @@ AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName; const AlertDialogOverlay = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef + // eslint-disable-next-line @typescript-eslint/no-unused-vars >(({ className, children, ...props }, ref) => ( , React.ComponentPropsWithoutRef + // eslint-disable-next-line @typescript-eslint/no-unused-vars >(({ className, ...props }, ref) => ( )); diff --git a/env.mjs b/env.mjs index 53e19b51..33adef3e 100644 --- a/env.mjs +++ b/env.mjs @@ -9,10 +9,6 @@ export const env = createEnv({ */ server: { DATABASE_URL: z.string().url(), - NODE_ENV: z - .enum(["development", "test", "production"]) - .default("development"), - VERCEL_URL: z.string().url().optional(), }, /** @@ -20,10 +16,10 @@ export const env = createEnv({ * isn't built with invalid env vars. To expose them to the client, prefix them with * `NEXT_PUBLIC_`. */ - client: { - NEXT_PUBLIC_URL: z.string().url(), - }, + client: {}, shared: { + NEXT_PUBLIC_URL: z.string().url().optional(), + VERCEL_URL: z.string().optional(), NODE_ENV: z .enum(["development", "test", "production"]) .default("development"), diff --git a/next.config.mjs b/next.config.mjs index 51d1f040..a4e2d338 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -6,6 +6,7 @@ import('./env.mjs'); /** @type {import("next").NextConfig} */ const config = { + output: 'standalone', reactStrictMode: true, experimental: { serverActions: true, diff --git a/package.json b/package.json index 0faf16f2..1e759d0e 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "fresco", "version": "0.1.0", "private": true, - "packageManager": "^pnpm@8.6.3", + "packageManager": "pnpm@8.6.3", "engines": { "node": ">=18.16.0", "pnpm": ">=8.6.3" @@ -10,8 +10,8 @@ "scripts": { "build": "next build", "dev": "next dev", - "postinstall": "prisma generate && prisma db push", - "lint": "next lint", + "lint": "SKIP_ENV_VALIDATION=true next lint", + "ts-lint": "SKIP_ENV_VALIDATION=true tsc --noEmit --incremental --watch", "start": "next start", "storybook": "storybook dev -p 6006", "build-storybook": "storybook build", @@ -54,7 +54,6 @@ "@trpc/server": "^10.38.3", "@types/validator": "^13.11.1", "@uploadthing/react": "^5.6.1", - "@uploadthing/shared": "^5.2.2", "ajv": "^8.12.0", "axios": "^1.5.0", "bcrypt": "^5.1.1", @@ -75,6 +74,7 @@ "react-dropzone": "^14.2.3", "react-hook-form": "^7.46.1", "sass": "^1.67.0", + "sharp": "^0.32.6", "superjson": "1.13.1", "tailwind-merge": "^1.14.0", "tailwindcss-animate": "^1.0.7", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 455ad752..4003400f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,9 +101,6 @@ dependencies: '@uploadthing/react': specifier: ^5.6.1 version: 5.6.1(next@13.5.2)(react@18.2.0)(uploadthing@5.6.1)(zod@3.22.2) - '@uploadthing/shared': - specifier: ^5.2.2 - version: 5.2.2(@uploadthing/mime-types@0.2.1)(zod@3.22.2) ajv: specifier: ^8.12.0 version: 8.12.0 @@ -164,6 +161,9 @@ dependencies: sass: specifier: ^1.67.0 version: 1.67.0 + sharp: + specifier: ^0.32.6 + version: 0.32.6 superjson: specifier: 1.13.1 version: 1.13.1 @@ -6336,6 +6336,10 @@ packages: dequal: 2.0.3 dev: true + /b4a@1.6.4: + resolution: {integrity: sha512-fpWrvyVHEKyeEvbKZTVOeZF3VSKKWtJxFIxX/jaVPf+cLbGUSitjb49pHLqPV2BUNNZ0LcoeEGfE/YCpyDYHIw==} + dev: false + /babel-core@7.0.0-bridge.0(@babel/core@7.22.20): resolution: {integrity: sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==} peerDependencies: @@ -6492,7 +6496,6 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true /bcrypt@5.1.1: resolution: {integrity: sha512-AGBHOG5hPYZ5Xl9KXzU5iKq9516yEmvCKDg3ecP5kX2aB6UqTeXZxk2ELnDgDm6BQSMlLt9rDB4LoSMx0rYwww==} @@ -6531,7 +6534,6 @@ packages: buffer: 5.7.1 inherits: 2.0.4 readable-stream: 3.6.2 - dev: true /blobs@2.3.0-beta.2: resolution: {integrity: sha512-kzLT5TOg/hsETxeyHae1sNpRWXNHnB1VN467FASoZfLRm4LdoXyp6HTTjes0cPE1sOVoOHEJFT9qyFGOpQFvPw==} @@ -6697,7 +6699,6 @@ packages: dependencies: base64-js: 1.5.1 ieee754: 1.2.1 - dev: true /buffer@6.0.3: resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} @@ -6849,7 +6850,6 @@ packages: /chownr@1.1.4: resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - dev: true /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} @@ -6989,11 +6989,26 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /color-string@1.9.1: + resolution: {integrity: sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==} + dependencies: + color-name: 1.1.4 + simple-swizzle: 0.2.2 + dev: false + /color-support@1.1.3: resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} hasBin: true dev: false + /color@4.2.3: + resolution: {integrity: sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==} + engines: {node: '>=12.5.0'} + dependencies: + color-convert: 2.0.1 + color-string: 1.9.1 + dev: false + /colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} dev: true @@ -7354,6 +7369,13 @@ packages: dependencies: ms: 2.1.2 + /decompress-response@6.0.0: + resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} + engines: {node: '>=10'} + dependencies: + mimic-response: 3.1.0 + dev: false + /dedent@0.7.0: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true @@ -7390,6 +7412,11 @@ packages: which-typed-array: 1.1.11 dev: true + /deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + dev: false + /deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -7763,7 +7790,6 @@ packages: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 - dev: true /endent@2.1.0: resolution: {integrity: sha512-r8VyPX7XL8U01Xgnb1CjZ3XV+z90cXIJ9JPE/R9SEC9vpw2P6CfsRPJmp20DppC5N7ZAMCmjYkJIa744Iyg96w==} @@ -8393,6 +8419,11 @@ packages: engines: {node: '>= 0.8.0'} dev: true + /expand-template@2.0.3: + resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} + engines: {node: '>=6'} + dev: false + /expect@29.7.0: resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -8465,6 +8496,10 @@ packages: resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==} dev: false + /fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + dev: false + /fast-glob@3.3.1: resolution: {integrity: sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==} engines: {node: '>=8.6.0'} @@ -8762,7 +8797,6 @@ packages: /fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - dev: true /fs-extra@10.1.0: resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} @@ -8904,6 +8938,10 @@ packages: - supports-color dev: true + /github-from-package@0.0.0: + resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + dev: false + /github-slugger@1.5.0: resolution: {integrity: sha512-wIh+gKBI9Nshz2o46B0B3f5k/W+WI9ZAv6y5Dn5WJ5SK1t0TnDimB4WE5rmTD05ZAIn8HALCZVmCsvj0w0v0lw==} dev: true @@ -9326,7 +9364,6 @@ packages: /ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true /ignore@5.2.4: resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} @@ -9385,6 +9422,10 @@ packages: /inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + /ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: false + /inline-style-parser@0.1.1: resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} dev: false @@ -9448,6 +9489,10 @@ packages: resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} dev: true + /is-arrayish@0.3.2: + resolution: {integrity: sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==} + dev: false + /is-async-function@2.0.0: resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} engines: {node: '>= 0.4'} @@ -10835,6 +10880,11 @@ packages: engines: {node: '>=12'} dev: false + /mimic-response@3.1.0: + resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} + engines: {node: '>=10'} + dev: false + /min-document@2.19.0: resolution: {integrity: sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==} dependencies: @@ -10880,7 +10930,6 @@ packages: /minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - dev: true /minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} @@ -10906,7 +10955,6 @@ packages: /mkdirp-classic@0.5.3: resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - dev: true /mkdirp@0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} @@ -10952,6 +11000,10 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + /napi-build-utils@1.0.2: + resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==} + dev: false + /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -11011,6 +11063,13 @@ packages: tslib: 2.6.2 dev: true + /node-abi@3.49.0: + resolution: {integrity: sha512-ji8IK8VT2zAQv9BeOqwnpuvJnCivxPCe2HNiPe8P1z1SDhqEFpm7GqctqTWkujb8mLfZ1PWDrjMeiq6l9TN7fA==} + engines: {node: '>=10'} + dependencies: + semver: 7.5.4 + dev: false + /node-abort-controller@3.1.1: resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==} dev: true @@ -11019,6 +11078,10 @@ packages: resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==} dev: false + /node-addon-api@6.1.0: + resolution: {integrity: sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==} + dev: false + /node-dir@0.1.17: resolution: {integrity: sha512-tmPX422rYgofd4epzrNoOXiE8XFZYOcCq1vD7MAXCDO+O+zndlA2ztdKKMa+EeuBG5tHETpr4ml4RGgpqDCCAg==} engines: {node: '>= 0.10.5'} @@ -11726,6 +11789,25 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /prebuild-install@7.1.1: + resolution: {integrity: sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw==} + engines: {node: '>=10'} + hasBin: true + dependencies: + detect-libc: 2.0.2 + expand-template: 2.0.3 + github-from-package: 0.0.0 + minimist: 1.2.8 + mkdirp-classic: 0.5.3 + napi-build-utils: 1.0.2 + node-abi: 3.49.0 + pump: 3.0.0 + rc: 1.2.8 + simple-get: 4.0.1 + tar-fs: 2.1.1 + tunnel-agent: 0.6.0 + dev: false + /prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -11914,7 +11996,6 @@ packages: dependencies: end-of-stream: 1.4.4 once: 1.4.0 - dev: true /pumpify@1.5.1: resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} @@ -11978,6 +12059,10 @@ packages: /queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + /queue-tick@1.0.1: + resolution: {integrity: sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==} + dev: false + /queue@6.0.2: resolution: {integrity: sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==} dependencies: @@ -12016,6 +12101,16 @@ packages: unpipe: 1.0.0 dev: true + /rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + dev: false + /react-colorful@5.6.1(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} peerDependencies: @@ -12817,6 +12912,21 @@ packages: kind-of: 6.0.3 dev: true + /sharp@0.32.6: + resolution: {integrity: sha512-KyLTWwgcR9Oe4d9HwCwNM2l7+J0dUQwn/yf7S0EnTtb0eVS4RxO0eUSvxPtzT4F3SY+C4K6fqdv/DO27sJ/v/w==} + engines: {node: '>=14.15.0'} + requiresBuild: true + dependencies: + color: 4.2.3 + detect-libc: 2.0.2 + node-addon-api: 6.1.0 + prebuild-install: 7.1.1 + semver: 7.5.4 + simple-get: 4.0.1 + tar-fs: 3.0.4 + tunnel-agent: 0.6.0 + dev: false + /shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -12843,6 +12953,24 @@ packages: engines: {node: '>=14'} dev: true + /simple-concat@1.0.1: + resolution: {integrity: sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==} + dev: false + + /simple-get@4.0.1: + resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} + dependencies: + decompress-response: 6.0.0 + once: 1.4.0 + simple-concat: 1.0.1 + dev: false + + /simple-swizzle@0.2.2: + resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + dependencies: + is-arrayish: 0.3.2 + dev: false + /simple-update-notifier@2.0.0: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} @@ -13022,6 +13150,13 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + /streamx@2.15.1: + resolution: {integrity: sha512-fQMzy2O/Q47rgwErk/eGeLu/roaFWV0jVsogDmrszM9uIw8L5OA+t+V93MgYlufNptfjmYR1tOMWhei/Eh7TQA==} + dependencies: + fast-fifo: 1.3.2 + queue-tick: 1.0.1 + dev: false + /string-length@4.0.2: resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} engines: {node: '>=10'} @@ -13135,6 +13270,11 @@ packages: min-indent: 1.0.1 dev: true + /strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + dev: false + /strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -13306,7 +13446,14 @@ packages: mkdirp-classic: 0.5.3 pump: 3.0.0 tar-stream: 2.2.0 - dev: true + + /tar-fs@3.0.4: + resolution: {integrity: sha512-5AFQU8b9qLfZCX9zp2duONhPmZv0hGYiBPJsyUdqMjzq/mqVpy/rEUSeHk1+YitmxugaptgBh5oDGU3VsAJq4w==} + dependencies: + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 3.1.6 + dev: false /tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -13317,7 +13464,14 @@ packages: fs-constants: 1.0.0 inherits: 2.0.4 readable-stream: 3.6.2 - dev: true + + /tar-stream@3.1.6: + resolution: {integrity: sha512-B/UyjYwPpMBv+PaFSWAmtYjwdrlEaZQEhMIBFNC5oEG8lpiW8XjcSdmEaClj28ArfKScKHs2nshz3k2le6crsg==} + dependencies: + b4a: 1.6.4 + fast-fifo: 1.3.2 + streamx: 2.15.1 + dev: false /tar@6.2.0: resolution: {integrity: sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ==} @@ -13594,6 +13748,12 @@ packages: resolution: {integrity: sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==} dev: true + /tunnel-agent@0.6.0: + resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} + dependencies: + safe-buffer: 5.2.1 + dev: false + /type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 807f97c7..77d7d462 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -2,7 +2,8 @@ // learn more about it in the docs: https://pris.ly/d/prisma-schema generator client { - provider = "prisma-client-js" + provider = "prisma-client-js" + binaryTargets = ["native", "linux-musl-arm64-openssl-3.0.x"] } datasource db { @@ -23,7 +24,7 @@ model Protocol { codebook Json assets Asset[] interviews Interview[] - active Boolean @default(false) + active Boolean @default(false) } model Asset { @@ -91,9 +92,9 @@ model Key { } model AppSettings { - configured Boolean @default(false) - initializedAt DateTime @default(now()) - configuredAt DateTime? + configured Boolean @default(false) + initializedAt DateTime @default(now()) + configuredAt DateTime? allowAnonymousRecruitment Boolean @default(false) @@id([configured, initializedAt]) diff --git a/providers/NetworkProvider.tsx b/providers/NetworkProvider.tsx index 4d10c01d..063fe823 100644 --- a/providers/NetworkProvider.tsx +++ b/providers/NetworkProvider.tsx @@ -7,7 +7,7 @@ import { useEffect, type PropsWithChildren, } from 'react'; -import type { NcNetwork } from '@codaco/shared-consts'; +import type { NcEdge, NcNetwork, NcNode } from '@codaco/shared-consts'; const initialState: NcNetwork = { nodes: [], @@ -15,7 +15,7 @@ const initialState: NcNetwork = { ego: undefined, }; -type NetworkAction = { +type NetworkActionBase = { type: | 'ADD_NODE' | 'ADD_EDGE' @@ -23,9 +23,47 @@ type NetworkAction = { | 'UPDATE_EDGE' | 'DELETE_NODE' | 'DELETE_EDGE'; - payload: Record; + payload: unknown; }; +type ActionAddNode = NetworkActionBase & { + type: 'ADD_NODE'; + payload: NcNode; +}; + +type ActionAddEdge = NetworkActionBase & { + type: 'ADD_EDGE'; + payload: NcEdge; +}; + +type ActionUpdateNode = NetworkActionBase & { + type: 'UPDATE_NODE'; + payload: NcNode; +}; + +type ActionUpdateEdge = NetworkActionBase & { + type: 'UPDATE_EDGE'; + payload: NcEdge; +}; + +type ActionDeleteNode = NetworkActionBase & { + type: 'DELETE_NODE'; + payload: string; +}; + +type ActionDeleteEdge = NetworkActionBase & { + type: 'DELETE_EDGE'; + payload: string; +}; + +type NetworkAction = + | ActionAddNode + | ActionAddEdge + | ActionUpdateNode + | ActionUpdateEdge + | ActionDeleteNode + | ActionDeleteEdge; + function reducer(state: NcNetwork, action: NetworkAction): NcNetwork { switch (action.type) { case 'ADD_NODE': @@ -42,25 +80,25 @@ function reducer(state: NcNetwork, action: NetworkAction): NcNetwork { return { ...state, nodes: state.nodes.map((node) => - node.id === action.payload.id ? action.payload : node, + node._uid === action.payload._uid ? action.payload : node, ), }; case 'UPDATE_EDGE': return { ...state, edges: state.edges.map((edge) => - edge.id === action.payload.id ? action.payload : edge, + edge._uid === action.payload._uid ? action.payload : edge, ), }; case 'DELETE_NODE': return { ...state, - nodes: state.nodes.filter((node) => node.id !== action.payload), + nodes: state.nodes.filter((node) => node._uid !== action.payload), }; case 'DELETE_EDGE': return { ...state, - edges: state.edges.filter((edge) => edge.id !== action.payload), + edges: state.edges.filter((edge) => edge._uid !== action.payload), }; default: return state; @@ -76,25 +114,16 @@ const NetworkContext = createContext({ }); type NetworkProviderProps = { - network: string | null; + network: NcNetwork; updateNetwork: (network: NcNetwork) => void; }; -const getInitialState = (network: string | null): NcNetwork => { - if (network) { - return JSON.parse(network) as NcNetwork; - } - return initialState; -}; - function NetworkProvider({ network, updateNetwork, children, }: PropsWithChildren) { - const [state, dispatch] = useReducer(reducer, initialState, () => - getInitialState(network), - ); + const [state, dispatch] = useReducer(reducer, network); // When state changes, sync it with the server using react query useEffect(() => { @@ -111,27 +140,27 @@ function NetworkProvider({ const useInterview = () => { const { state, dispatch } = useContext(NetworkContext); - const addNode = (node) => { + const addNode = (node: NcNode) => { dispatch({ type: 'ADD_NODE', payload: node }); }; - const addEdge = (edge) => { + const addEdge = (edge: NcEdge) => { dispatch({ type: 'ADD_EDGE', payload: edge }); }; - const updateNode = (node) => { + const updateNode = (node: NcNode) => { dispatch({ type: 'UPDATE_NODE', payload: node }); }; - const updateEdge = (edge) => { + const updateEdge = (edge: NcEdge) => { dispatch({ type: 'UPDATE_EDGE', payload: edge }); }; - const deleteNode = (nodeId) => { + const deleteNode = (nodeId: string) => { dispatch({ type: 'DELETE_NODE', payload: nodeId }); }; - const deleteEdge = (edgeId) => { + const deleteEdge = (edgeId: string) => { dispatch({ type: 'DELETE_EDGE', payload: edgeId }); }; diff --git a/providers/Providers.tsx b/providers/Providers.tsx index 0d4748e8..091f3fe2 100644 --- a/providers/Providers.tsx +++ b/providers/Providers.tsx @@ -8,6 +8,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { SessionProvider } from '~/providers/SessionPrivider'; import type { Session } from 'lucia'; import { env } from '~/env.mjs'; +import SuperJSON from 'superjson'; export default function Providers({ children, @@ -19,6 +20,7 @@ export default function Providers({ const [queryClient] = useState(() => new QueryClient()); const [trpcClient] = useState(() => trpc.createClient({ + transformer: SuperJSON, links: [ loggerLink({ enabled: (opts) => diff --git a/server/routers/protocol.ts b/server/routers/protocol.ts index 2bacfe14..95caf2d0 100644 --- a/server/routers/protocol.ts +++ b/server/routers/protocol.ts @@ -9,6 +9,13 @@ const updateActiveProtocolSchema = z.object({ }); export const protocolRouter = router({ + get: router({ + all: protectedProcedure.query(async () => { + const protocols = await prisma.protocol.findMany(); + + return protocols; + }), + }), getActive: publicProcedure.query(async () => { const activeProtocol = await prisma.protocol.findFirst({ where: { diff --git a/server/trpc.ts b/server/trpc.ts index a5194cf0..9a2508f7 100644 --- a/server/trpc.ts +++ b/server/trpc.ts @@ -1,8 +1,11 @@ import { TRPCError, initTRPC } from '@trpc/server'; import type { createTRPCContext } from './context'; +import superjson from 'superjson'; import { env } from '~/env.mjs'; -const t = initTRPC.context().create(); +const t = initTRPC.context().create({ + transformer: superjson, +}); const enforceUserIsAuthed = t.middleware(({ ctx, next }) => { if (!ctx.session || !ctx.session?.user) { diff --git a/stories/Header.tsx b/stories/Header.tsx index 6e63c3d8..c7ab80cd 100644 --- a/stories/Header.tsx +++ b/stories/Header.tsx @@ -1,6 +1,5 @@ import React from 'react'; - -import Button from '../ui/components/Button'; +import { Button } from '~/components/ui//Button'; import './header.css'; type User = { @@ -52,17 +51,14 @@ export const Header = ({ Welcome, {user.name}! - ) : ( <> - + )} diff --git a/tailwind.config.ts b/tailwind.config.ts index c2a9b420..26342560 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -57,12 +57,12 @@ export default { }, keyframes: { 'accordion-down': { - from: { height: 0 }, + from: { height: '0' }, to: { height: 'var(--radix-accordion-content-height)' }, }, 'accordion-up': { from: { height: 'var(--radix-accordion-content-height)' }, - to: { height: 0 }, + to: { height: '0' }, }, 'indeterminate-progress-bar': { '0%': { transform: ' translateX(0) scaleX(0)' }, diff --git a/tsconfig.json b/tsconfig.json index a3d45ab8..c6e714aa 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,9 +1,13 @@ { "compilerOptions": { "target": "es2017", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, - "checkJs": true, + "checkJs": false, "skipLibCheck": true, "strict": true, "forceConsistentCasingInFileNames": true, @@ -18,7 +22,9 @@ "noUncheckedIndexedAccess": true, "baseUrl": ".", "paths": { - "~/*": ["./*"] + "~/*": [ + "./*" + ] }, "plugins": [ { @@ -36,5 +42,7 @@ ".next/types/**/*.ts", "eslint-local-rules/index.js" ], - "exclude": ["node_modules"] -} + "exclude": [ + "node_modules" + ] +} \ No newline at end of file diff --git a/utils/calculateRedirectedRoutes.ts b/utils/calculateRedirectedRoutes.ts index 401b1d15..e97f4706 100644 --- a/utils/calculateRedirectedRoutes.ts +++ b/utils/calculateRedirectedRoutes.ts @@ -78,7 +78,7 @@ export const calculateRedirect = ({ return; } - return '/signin?callbackUrl=' + encodeURI(path); + return ('/signin?callbackUrl=' + encodeURI(path)) as Route; } // APP IS CONFIGURED AND SESSION EXISTS diff --git a/utils/generateMockData/interview/interview.ts b/utils/generateMockData/interview/interview.ts index 291c4cb5..26d71128 100644 --- a/utils/generateMockData/interview/interview.ts +++ b/utils/generateMockData/interview/interview.ts @@ -4,7 +4,7 @@ import network from './network.json' assert { type: 'json' }; type InterviewUncheckedCreateWithoutProtocolAndUserInput = Omit< Prisma.InterviewUncheckedCreateWithoutProtocolInput, - 'userId' + 'participantId' >; const mockInterview = diff --git a/utils/generateMockData/protocol/protocol.ts b/utils/generateMockData/protocol/protocol.ts index b70e0bc9..8185b6bc 100644 --- a/utils/generateMockData/protocol/protocol.ts +++ b/utils/generateMockData/protocol/protocol.ts @@ -2,16 +2,15 @@ import { faker } from '@faker-js/faker'; import { type Prisma } from '@prisma/client'; import stages from './stages.json' assert { type: 'json' }; -const mockProtocol = (): Prisma.ProtocolUncheckedCreateWithoutOwnerInput => { +const mockProtocol = (): Prisma.ProtocolUncheckedCreateInput => { return { name: faker.word.words(4), hash: faker.word.words(4), + codebook: {}, schemaVersion: faker.number.int({ min: 6, max: 8 }), description: faker.lorem.sentence(), - assetPath: 'assets/path', lastModified: faker.date.past(), - stages: JSON.stringify(stages), - codebook: '{}', + stages: stages, }; }; diff --git a/utils/generateMockData/team.ts b/utils/generateMockData/team.ts deleted file mode 100644 index d4108616..00000000 --- a/utils/generateMockData/team.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { faker } from '@faker-js/faker'; -import { Prisma } from '@prisma/client'; - -const mockTeam = (): Prisma.TeamUncheckedCreateWithoutStudiesInput => { - return { - name: faker.company.name(), - description: faker.company.catchPhrase(), - image: faker.image.url(), - createdAt: faker.date.past(), - updatedAt: faker.date.recent(), - }; -}; - -export default mockTeam; diff --git a/utils/uploadthing/useFetch.ts b/utils/uploadthing/useFetch.ts index c0c909c4..4b13d4be 100644 --- a/utils/uploadthing/useFetch.ts +++ b/utils/uploadthing/useFetch.ts @@ -11,7 +11,7 @@ type Cache = { [url: string]: T }; // discriminated union type type Action = | { type: 'loading' } - | { type: 'fetched'; payload: T } + | { type: 'fetched'; payload: T | undefined } | { type: 'error'; payload: Error }; function useFetch(url?: string, options?: RequestInit): State { diff --git a/utils/uploadthing/useUploadThing.ts b/utils/uploadthing/useUploadThing.ts deleted file mode 100644 index d995f940..00000000 --- a/utils/uploadthing/useUploadThing.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { useState } from 'react'; - -import { DANGEROUS__uploadFiles } from 'uploadthing/client'; -import type { FileRouter } from 'uploadthing/server'; - -import { useEvent } from './useEvent'; -import useFetch from './useFetch'; -import type { OurFileRouter } from '../../app/api/uploadthing/core'; - -type EndpointMetadata = { - slug: string; - config: OurFileRouter; -}[]; -const useEndpointMetadata = (endpoint: string) => { - const { data } = useFetch('/api/uploadthing'); - - // TODO: Log on errors in dev - - return data?.find((x) => x.slug === endpoint); -}; - -export const useUploadThing = ({ - endpoint, - onClientUploadComplete, - onUploadError, - onUploadBegin, - onUploadProgress, -}: { - endpoint: T; - onClientUploadComplete?: ( - res?: Awaited>, - ) => Promise; - onUploadError?: (e: Error) => void; - onUploadBegin?: () => void; - onUploadProgress?: (file: string, progress: number) => void; -}) => { - const [isUploading, setUploading] = useState(false); - - const permittedFileInfo = useEndpointMetadata(endpoint); - - const startUpload = useEvent(async (files: File[]) => { - setUploading(true); - try { - onUploadBegin?.(); - const res = await DANGEROUS__uploadFiles({ - endpoint, - onUploadProgress: ({ file, progress }) => { - onUploadProgress?.(file, progress); - }, - files, - }); - setUploading(false); - await onClientUploadComplete?.(res); - return res; - } catch (e) { - setUploading(false); - onUploadError?.(e as Error); - return; - } - }); - return { - startUpload, - isUploading, - permittedFileInfo, - } as const; -}; - -export const generateReactHelpers = () => { - type TRouterKey = keyof TRouter extends string ? keyof TRouter : string; - - return { - useUploadThing: useUploadThing, - uploadFiles: DANGEROUS__uploadFiles, - } as const; -}; - -export type FullFile = { - file: File; - contents: string; -}; diff --git a/utils/validateProtocol.ts b/utils/validateProtocol.ts deleted file mode 100644 index 7f20b50c..00000000 --- a/utils/validateProtocol.ts +++ /dev/null @@ -1,83 +0,0 @@ -import friendlyErrorMessage from './friendlyErrorMessage'; -import { validateSchema, validateLogic } from '../lib/protocol-utils'; -import { errToString } from '../lib/protocol-utils/validate/helpers'; -import { APP_SUPPORTED_SCHEMA_VERSIONS } from '~/fresco.config'; - -const openError = friendlyErrorMessage( - "There was an error reading that protocol file. It doesn't seem to be a valid JSON object. Check the format of your protocol, and try again.", -); -const validationError = friendlyErrorMessage( - 'Your protocol file failed validation. See below for the specific problems we found. This is often caused by attempting to open a protocol file authored in an incompatible version of Architect.', -); - -// Basic validation on protocol format; -// any error will halt loading and display a message to the user. -const validateProtocol = (protocol) => { - // console.log('utils>>>', utils); - // console.log('utils>>>', utils.validateSchema); - // console.log('utils>>>', utils.validateSchema.default); - // const validateSchema = utils.validateSchema.default; - // const validateLogic = utils.validateLogic.default; - // concentricCircles: 3, skewedTowardCenter: true } - - protocol.stages[17].background = { - ...protocol.stages[17].background, - concentricCircles: 3, - skewedTowardCenter: true, - }; - - protocol.stages[29].background = { - ...protocol.stages[29].background, - skewedTowardCenter: true, - }; - const schemaErrors = validateSchema.default(protocol); - // const schemaErrors = []; - // console.log( - // '🚀 ~ file: parseProtocol.ts:24 ~ validateProtocol ~ schemaErrors:', - // schemaErrors, - // ); - // console.log('/stages/17/background>16', protocol.stages[16].background); - // console.log('/stages/17/background>29', protocol.stages[29].background); - // const logicErrors = validateLogic.default(protocol); - const logicErrors = []; - - if (schemaErrors.length > 0 || logicErrors.length > 0) { - throw new Error( - [...schemaErrors, ...logicErrors].map(errToString).join(''), - ); - } -}; - -const checkSchemaVersion = (protocol: any) => { - if (!APP_SUPPORTED_SCHEMA_VERSIONS.includes(protocol.schemaVersion)) { - throw new Error( - 'The schema version of this protocol is not compatible with this version of Network Canvas Interviewer. Upgrade the protocol using Architect, and try importing it again.', - ); - } -}; - -// const parseProtocol1 = (protocolUID, name) => -// readFile(protocolPath(protocolUID, 'protocol.json')) -// .then((json) => JSON.parse(json)) -// .then((protocol) => checkSchemaVersion(protocol)) -// .then((protocol) => validateProtocol(protocol)) -// .catch(validationError) -// .then((protocol) => { -// const withFilename = { -// ...protocol, -// name, -// uid: protocolUID, -// }; -// return withFilename; -// }) -// .catch(openError); - -export default function parseProtocol(protocol: any) { - try { - checkSchemaVersion(protocol); - validateProtocol(protocol); - console.log('protocal parsed'); - } catch (e) { - validationError(e); - } -}