diff --git a/package.json b/package.json index 38991b2..70456fb 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "lucide-react": "^0.503.0", "next": "^15.3.0", "next-themes": "^0.4.1", + "next-safe-action": "^7.10.2", "react": "^19.1.0", "react-dom": "^19.1.0", "react-hook-form": "^7.54.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fde9778..45db0ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -59,6 +59,9 @@ importers: next: specifier: ^15.3.0 version: 15.3.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + next-safe-action: + specifier: ^7.10.2 + version: 7.10.5(next@15.3.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@3.24.1) next-themes: specifier: ^0.4.1 version: 0.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -3599,6 +3602,27 @@ packages: neo-async@2.6.2: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + next-safe-action@7.10.5: + resolution: {integrity: sha512-UQdu490byYN7034FYZtnPVFP3DjyvYBpmXSQ/dpdJFznSsGCDCmTRk+Hs51emt5jljEKtMcL1XCfBbFuGklmrA==} + engines: {node: '>=18.17'} + peerDependencies: + '@sinclair/typebox': '>= 0.33.3' + next: '>= 14.0.0' + react: '>= 18.2.0' + react-dom: '>= 18.2.0' + valibot: '>= 0.36.0' + yup: '>= 1.0.0' + zod: '>= 3.0.0' + peerDependenciesMeta: + '@sinclair/typebox': + optional: true + valibot: + optional: true + yup: + optional: true + zod: + optional: true + next-themes@0.4.1: resolution: {integrity: sha512-xD3cn42n0f1DFCAOlxJlrGPog+WdhWHObgJ+LTM7J5Bff/uBuq4vn/okSSao7puz7yBLcrELLOQ7F1/9hwycyQ==} peerDependencies: @@ -8300,6 +8324,14 @@ snapshots: neo-async@2.6.2: {} + next-safe-action@7.10.5(next@15.3.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(zod@3.24.1): + dependencies: + next: 15.3.1(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + zod: 3.24.1 + next-themes@0.4.1(react-dom@19.1.0(react@19.1.0))(react@19.1.0): dependencies: react: 19.1.0 diff --git a/src/libs/safe-action.ts b/src/libs/safe-action.ts new file mode 100644 index 0000000..2ff118c --- /dev/null +++ b/src/libs/safe-action.ts @@ -0,0 +1,30 @@ +import * as Sentry from "@sentry/nextjs" +import { createSafeActionClient } from "next-safe-action" +import { z } from "zod" + +export const actionClient = createSafeActionClient({ + defineMetadataSchema() { + return z.object({ + actionName: z.string() + }) + }, + handleServerError(error, utils) { + const { clientInput, metadata } = utils + + Sentry.captureException(error, (scope) => { + scope.clear() + scope.setContext("serverError", { message: error.message }) + scope.setContext("metadata", { actionName: metadata.actionName }) + scope.setContext("clientInput", { clientInput }) + return scope + }) + + // We don't want to leak any sensitive data + if (error.constructor.name === "DatabaseError") { + return "Database Error: Your data did not save. Support will be notified." + } + + console.error("Action error:", error.message) + return error.message + } +}) diff --git a/user-stories.md b/user-stories.md index 731288b..4ec8fe5 100644 --- a/user-stories.md +++ b/user-stories.md @@ -15,8 +15,8 @@ 13. [x] Users can have Employee, Manager, or Admin permissions 14. [ ] All users can create and view tickets 15. [ ] All users can create, edit and view customers -16. [ ] Employees can only edit their assigned tickets -17. [ ] Managers and Admins can view, edit, and delete all tickets +16. [x] Employees can only edit their assigned tickets +17. [x] Managers and Admins can view, edit, and complete all tickets 18. [ ] Desktop mode is most important but the app should be usable on tablet devices as well. 19. [x] Light / Dark mode option requested by employees 20. [x] Expects quick support if anything goes wrong with the app