Skip to content

Credential Actions Implementation and WIP#20

Merged
FindMalek merged 17 commits intomainfrom
findmalek/fin-79-credential-actions-implementation-and-wip
Oct 6, 2025
Merged

Credential Actions Implementation and WIP#20
FindMalek merged 17 commits intomainfrom
findmalek/fin-79-credential-actions-implementation-and-wip

Conversation

@FindMalek
Copy link
Owner

@FindMalek FindMalek commented Oct 2, 2025

Summary by CodeRabbit

  • New Features

    • Duplicate credential action, Unarchive action, ARCHIVED status/indicator, Delete/Move dialogs, PRO QR Code dialog, and mobile-responsive alert dialogs.
  • Improvements

    • Revamped credential grid/cards and headers (platform logos, quick copy ID, keyboard/accessibility), richer type-safe breadcrumb selector, centralized date formatting, and consolidated action menus with integrated toasts.
  • Bug Fixes

    • Improved compatibility for decrypting legacy-encrypted data.
  • Documentation

    • Added loading button patterns guide.

@FindMalek FindMalek self-assigned this Oct 2, 2025
@vercel
Copy link
Contributor

vercel bot commented Oct 2, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
zero-locker Ready Ready Preview Comment Oct 6, 2025 8:52pm

@coderabbitai
Copy link

coderabbitai bot commented Oct 2, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds credential duplication backend and client hook; introduces ARCHIVED account status across DB/schema/entities/UI; refactors item actions into credential-scoped components (adds Unarchive); adds Move/Delete/QR dialogs and a responsive alert dialog; centralizes date formatting; enhances platform lookup utilities; expands encryption legacy decryption; adds breadcrumb Zod schemas and docs.

Changes

Cohort / File(s) Summary of changes
Docs & Rules
.cursor/rules/loading-button-patterns.mdc, .../hooks-folder.mdc, .../schemas-breadcrumb.mdc
Added loading-button patterns doc and breadcrumb schema doc; removed extra blank lines in hooks-folder doc.
Breadcrumb schemas & consumer
schemas/utils/breadcrumb.ts, components/layout/breadcrumb-resource-select.tsx
Added Zod breadcrumb item schemas/types (credential/card/secret) and refactored breadcrumb selector to use discriminated union + typed accessors and platform resolution.
Credential actions & menus
components/shared/item-actions-dropdown.tsx
Reworked actions into credential-scoped exports (CredentialActionsDropdown, CredentialActionsContextMenu); added Unarchive action; wired Move/Delete/QR dialogs; introduced credential-focused props/types; retained ItemActionsDropdown adjustments.
New dialogs
components/app/dashboard-credential-delete-dialog.tsx, components/app/dashboard-credential-move-dialog.tsx, components/app/dashboard-qr-code-dialog.tsx
Added Delete, Move, and QR code dialogs with zod/react-hook-form integration, permission gating, toasts, and exported components.
Responsive Alert Dialog
components/ui/responsive-alert-dialog.tsx
Added ResponsiveAlertDialog and subcomponents that render Drawer on mobile and AlertDialog on desktop with shared context and forwarded props.
Dashboard & UI refactors
components/app/dashboard-*.tsx
components/app/dashboard-add-credential-dialog.tsx, .../dashboard-credential-cards-view.tsx, .../dashboard-credential-detail-view.tsx, .../dashboard-credential-footer.tsx, .../dashboard-credential-grid-view.tsx, .../dashboard-credential-header.tsx, .../dashboard-credential-key-value-pairs.tsx, .../dashboard-credential-sidebar.tsx, .../dashboard-recent-activity.tsx
Replaced ad-hoc platform lookups with PlatformEntity helpers; swapped ItemActions → CredentialActions components; removed detail-view delete flow; migrated date formatting to DateFormatter; added per-card copy/navigation behaviors and minor dependency/toast refinements.
Date formatting centralization
lib/date-utils.ts, lib/utils/index.ts
Introduced DateFormatter class with multiple format helpers; replaced local formatDate usage and removed local helper.
Platform utilities
entities/utils/platform/entity.ts
Added PlatformEntity.findById (fallback object) and findByIdStrict (throws if not found).
ARCHIVED status propagation
prisma/schema/enums.prisma, schemas/credential/credential.ts, entities/credential/credential/entity.ts, config/converter.tsx, components/shared/status-badge.tsx
Added ARCHIVED enum member across Prisma/schema/entities/config and added status-badge styling/label for Archived.
Migration
prisma/schema/migrations/.../migration.sql
Migration SQL to add ARCHIVED to AccountStatus enum, adjust FK constraints and unique indexes, and re-add FKs with cascade rules.
Encryption compatibility
lib/encryption.ts
Expanded IV handling and added legacy decryption fallbacks (AES‑CBC/GCM variants), legacy key derivation and multi-path retry logic to support legacy encrypted data formats.
ORPC duplication feature
orpc/routers/credential.ts, orpc/hooks/use-credentials.ts, schemas/credential/dto.ts
Added duplicateCredential router (transactional duplication of credential + metadata + empty encrypted KVPs), new duplicateCredentialInputSchema/type, and useDuplicateCredential hook; adjusted decryption helpers to return empty string on empty/failed values.
Miscellaneous
components/shared/encrypted-key-value-display.tsx, lib/utils/index.ts, components/shared/status-badge.tsx
Adjusted callback dependency arrays to include toast; replaced local date helpers with DateFormatter usages; added ARCHIVED mapping where needed.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant UI as CredentialActions (Dropdown/Context)
  participant H as useDuplicateCredential Hook
  participant R as credential.duplicate Router
  participant DB as Database
  Note over UI: Menu includes Duplicate action
  U->>UI: Click "Duplicate"
  UI->>H: mutate({ credentialId })
  H->>R: POST duplicateCredential(input)
  R->>DB: Begin transaction
  R->>DB: Read original credential, metadata, KVPs
  R->>DB: Create duplicated credential + metadata + empty encrypted KVPs
  DB-->>R: New credential row
  R-->>H: CredentialOutput
  H-->>UI: onSuccess (invalidate + cache update)
  UI-->>U: Show success toast and refreshed list
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant UI as CredentialActions
  participant P as Permissions
  participant D as Dialogs (Move/Delete/QR)
  U->>UI: Open menu
  UI->>P: Check permission (e.g., canMove)
  alt Move selected & permitted
    UI->>D: Open Move dialog
    D->>DB: Update credential container
    DB-->>D: Success/Failure
    D-->>U: Toast + close/reset
  else Move selected & not permitted
    UI-->>U: Show permission error toast
  else Delete selected
    UI->>D: Open Delete dialog
    D->>DB: Delete mutation
    DB-->>D: Success/Failure
  else QR selected
    UI->>D: Open QR dialog
    D-->>U: Configure/Save/Download
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

In my burrow I copy a key with a hop,
ARCHIVED badges gleam and the breadcrumbs pop.
Dates now dress tidy, platforms I find,
Dialogs swing open, permissions aligned.
I twitch my nose and whisper: "Another hop, rewind!" 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title references the introduction of credential action components but also includes “WIP,” which is a vague placeholder that does not convey meaningful information about the changeset’s scope or completion state. While it does touch on a real aspect of the PR, the inclusion of a non‐descriptive term makes the overall title unclear. Please remove the “WIP” placeholder and provide a concise, specific title that focuses on the primary change, for example “Implement Credential Actions Dropdown and Context Menu.”
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2cddfd2 and 081c885.

📒 Files selected for processing (7)
  • components/app/dashboard-credential-delete-dialog.tsx (1 hunks)
  • components/app/dashboard-credential-grid-view.tsx (2 hunks)
  • components/app/dashboard-credential-move-dialog.tsx (1 hunks)
  • components/shared/item-actions-dropdown.tsx (7 hunks)
  • lib/date-utils.ts (2 hunks)
  • orpc/routers/credential.ts (8 hunks)
  • schemas/utils/breadcrumb.ts (1 hunks)

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

…ption (#21)

* chore: preserve 'iv' in duplicate

* fix: resolved 'eslint' errors

* fix: resolved 'entity' missing 'AccountStatus'

* chore: removed 'default' case

* fix: resolved 'encrypt'

* fix: resolved 'conflict' duplicate
Co-authored-by: Malek Gara-Hellal <malek.gara@JF-QPY7J407RM.local>
@FindMalek FindMalek marked this pull request as ready for review October 6, 2025 20:09
@FindMalek
Copy link
Owner Author

@coderabbitai please give this a review

@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

@FindMalek: I'll review the changes in this pull request right away!

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
components/app/dashboard-credential-cards-view.tsx (1)

93-103: Prevent navigation when pressing “Copy”

Removing the dedicated handler also dropped the event.stopPropagation(). The button click now bubbles to the card’s onClick, so copying immediately navigates to the detail view. Wrap the handler to stop propagation before calling copy so the button remains usable.

-                    <Button
+                    <Button
                       variant="ghost"
                       size="sm"
                       className="hover:text-primary  opacity-0 transition-all group-hover/identifier:opacity-100"
-                      onClick={async () => await copy(credential.identifier)}
+                      onClick={async (event) => {
+                        event.stopPropagation()
+                        await copy(credential.identifier)
+                      }}
                     >
🧹 Nitpick comments (14)
components/ui/responsive-alert-dialog.tsx (2)

65-69: Consider adding displayName for better debugging.

Adding a display name to the context improves the debugging experience in React DevTools.

Apply this diff:

 const ResponsiveAlertDialogContext = React.createContext<{
   isMobile: boolean
 }>({
   isMobile: false,
 })
+
+ResponsiveAlertDialogContext.displayName = "ResponsiveAlertDialogContext"

100-206: Well-structured subcomponents with consistent API.

All subcomponents correctly consume context and conditionally render appropriate variants. The mobile variants have sensible default styles that can be overridden via className.

Consider adding JSDoc comments to document usage and the requirement that subcomponents must be used within ResponsiveAlertDialog:

/**
 * A responsive alert dialog that renders as a Drawer on mobile and AlertDialog on desktop.
 * 
 * @example
 * <ResponsiveAlertDialog open={open} onOpenChange={setOpen}>
 *   <ResponsiveAlertDialogContent>
 *     <ResponsiveAlertDialogHeader>
 *       <ResponsiveAlertDialogTitle>Title</ResponsiveAlertDialogTitle>
 *       <ResponsiveAlertDialogDescription>Description</ResponsiveAlertDialogDescription>
 *     </ResponsiveAlertDialogHeader>
 *     <ResponsiveAlertDialogFooter>
 *       <ResponsiveAlertDialogCancel>Cancel</ResponsiveAlertDialogCancel>
 *     </ResponsiveAlertDialogFooter>
 *   </ResponsiveAlertDialogContent>
 * </ResponsiveAlertDialog>
 */
function ResponsiveAlertDialog({ open, onOpenChange, children }: ResponsiveAlertDialogProps) {
  // ... existing implementation
}
components/app/dashboard-credential-key-value-pairs.tsx (1)

27-31: Consider wrapping in React.memo for performance.

Since the component already uses multiple optimization hooks (useCallback, useMemo), wrapping it in React.memo could prevent unnecessary re-renders when parent props haven't changed.

-export function CredentialKeyValuePairs({
+export const CredentialKeyValuePairs = React.memo(function CredentialKeyValuePairs({
   credentialId,
   onFormChange,
   onDataChange,
-}: CredentialKeyValuePairsProps) {
+}: CredentialKeyValuePairsProps) {
+  // ... component body
+})
components/app/dashboard-qr-code-dialog.tsx (2)

46-46: Avoid hardcoding the domain in the default URL.

The default URL uses a hardcoded domain (https://zero-locker.app). Consider using an environment variable or configuration constant to make this flexible across environments (development, staging, production).

+import { siteConfig } from "@/config/site"
+
 const form = useForm<QrCodeFormData>({
   resolver: zodResolver(qrCodeFormSchema),
   defaultValues: {
-    url: `https://zero-locker.app/credential/${credentialId}`,
+    url: `${siteConfig.url}/credential/${credentialId}`,
     requirePassword: false,
     password: "",
   },
 })

54-68: Implement QR code generation functionality.

The placeholder handlers for download and copy are incomplete. Consider implementing the actual QR code generation logic using a library like qrcode.react or similar.

Do you want me to generate an implementation for QR code generation, download, and copy functionality?

components/app/dashboard-credential-delete-dialog.tsx (1)

104-104: Replace console.log with console.error in error handler.

In error handling paths, use console.error instead of console.log to properly categorize the log level and make errors more visible in production monitoring.

 } catch (error) {
-  console.log(error)
+  console.error("Failed to delete credential:", error)
   toast("Failed to delete credential. Please try again later.", "error")
 }
components/app/dashboard-credential-move-dialog.tsx (2)

182-201: Add explicit button types (accessibility/guidelines)

Buttons should declare type. Add type="button" to both.

-          <Button
+          <Button
+            type="button"
             variant="outline"
             onClick={() => handleOpenChange(false)}
             disabled={isMoving}
             className="order-2 sm:order-1"
           >
             Cancel
           </Button>
-          <Button
+          <Button
+            type="button"
             onClick={form.handleSubmit(handleMove)}
             disabled={
               isMoving ||
               selectedContainerId === credential.containerId ||
               !canMoveCredential
             }
             className="order-1 disabled:opacity-50 sm:order-2"
           >

[Based on coding guidelines]


98-101: Avoid console in production paths

Replace console.error with your logging utility or remove it; rely on toast and mutation error handling.

-      console.error("Failed to move credential:", error)
       toast("Failed to move credential. Please try again later.", "error")
lib/date-utils.ts (1)

117-137: Handle future dates in getRelativeTime

Future timestamps produce “-Xd ago”. Clamp or return “in Xd/Today”.

 export function getRelativeTime(date: Date | null): string {
   if (!date) return "Never"
 
   const now = new Date()
 
   // Use date-fns timezone-aware functions for today/yesterday detection
   if (isToday(date)) return "Today"
   if (isYesterday(date)) return "Yesterday"
 
   // Use date-fns precise difference calculations
-  const days = differenceInDays(now, date)
-  const weeks = differenceInWeeks(now, date)
-  const months = differenceInMonths(now, date)
-  const years = differenceInYears(now, date)
+  const days = differenceInDays(now, date)
+  if (days < 0) return "Today"
+  const weeks = differenceInWeeks(now, date)
+  const months = differenceInMonths(now, date)
+  const years = differenceInYears(now, date)
lib/utils/index.ts (2)

24-24: Use absolute import for top-level modules

Replace relative import with "@/lib/date-utils" per guidelines.

- import { DateFormatter, getRelativeTime } from "../date-utils"
+ import { DateFormatter, getRelativeTime } from "@/lib/date-utils"

[As per coding guidelines]


343-350: Minor: consistent phrasing

Strings look good; consider leaving “Created recently” without “at ” if you want less redundancy, but current output is acceptable.

lib/encryption.ts (2)

205-208: Remove console usage in crypto paths

Avoid console in library code. Prefer a logger or omit.

-          console.error("AES-GCM fallback also failed:", gcmError)
           throw cbcError

282-292: Hardcoded seed keys in shared lib

SEED_ENCRYPTION_CONFIG with zeroed keys is fine for seeds, but keep it out of general-purpose runtime libs to avoid accidental use and static scanner noise. Move to a seed-specific module (e.g., scripts/seed/encryption.ts) and import only in seed code.

components/shared/item-actions-dropdown.tsx (1)

218-233: Route error logs through an error logger

Error branches still call console.log(error). Please switch to an error logger (or at least console.error) so failures surface correctly without violating our “no console logging” rule for success paths.

Also applies to: 244-255, 259-269, 304-326, 358-373, 385-395, 399-409, 443-461

Comment on lines +129 to +137
{Array.from({ length: 64 }).map((_, i) => (
<div
key={i}
className={`size-3 rounded-sm ${
Math.random() > 0.5 ? "bg-current" : "bg-transparent"
}`}
style={{ color: qrCodeColor }}
/>
))}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Math.random() in render causes unnecessary re-renders.

Using Math.random() directly in the render loop generates a new random value on every render, causing the mock QR code pattern to change unexpectedly. Consider using useMemo to stabilize the pattern or replace the mock with the actual QR code implementation.

+import { useMemo } from "react"
+
 export function DashboardQrCodeDialog({
   open,
   onOpenChange,
   credentialId,
 }: QrCodeDialogProps) {
+  const mockPattern = useMemo(() => 
+    Array.from({ length: 64 }).map(() => Math.random() > 0.5),
+    []
+  )
+
   // ... rest of component
   
   <div className="grid grid-cols-8 gap-1">
-    {Array.from({ length: 64 }).map((_, i) => (
+    {mockPattern.map((filled, i) => (
       <div
         key={i}
         className={`size-3 rounded-sm ${
-          Math.random() > 0.5 ? "bg-current" : "bg-transparent"
+          filled ? "bg-current" : "bg-transparent"
         }`}
         style={{ color: qrCodeColor }}
       />
     ))}
   </div>
🤖 Prompt for AI Agents
In components/app/dashboard-qr-code-dialog.tsx around lines 129 to 137, the
render uses Math.random() inside the JSX map which produces a new random pattern
on every render; replace that with a stable, memoized pattern (e.g. use useMemo
to generate an array of booleans once or when dependencies change) and map over
that memoized array to set the className, or swap this mock with the actual QR
code implementation so the pattern does not change on re-renders.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
components/app/dashboard-credential-cards-view.tsx (2)

62-66: Make the clickable row accessible (keyboard) or use Link

Add role, tabIndex, and key handler (or wrap with next/link) so keyboard users can activate the row.

-            <div
+            <div
               className="dark:hover:bg-secondary/50 hover:border-secondary-foreground/20 border-secondary group flex cursor-pointer items-center gap-4 rounded-lg border-2 p-4 transition-colors duration-200 hover:shadow-sm"
-              onClick={() => handleCardClick(credential.id)}
+              onClick={() => handleCardClick(credential.id)}
+              role="link"
+              tabIndex={0}
+              onKeyDown={(e) => {
+                if (e.key === "Enter" || e.key === " ") {
+                  e.preventDefault()
+                  handleCardClick(credential.id)
+                }
+              }}
             >

91-96: Prevent navigation when clicking copy; also set button type

Stops bubbling to the row onClick and ensures button type is explicit.

-                    <Button
+                    <Button
                       variant="ghost"
                       size="sm"
                       className="hover:text-primary  opacity-0 transition-all group-hover/identifier:opacity-100"
-                      onClick={async () => await copy(credential.identifier)}
+                      type="button"
+                      onClick={async (e) => {
+                        e.stopPropagation()
+                        await copy(credential.identifier)
+                      }}
                     >
🧹 Nitpick comments (7)
components/app/dashboard-credential-delete-dialog.tsx (2)

1-35: Consider organizing imports per coding guidelines.

The import statements could be better organized to follow the established pattern: React imports, third-party libraries, internal absolute imports, then type-only imports. Currently, entity/utility imports are interspersed with third-party libraries.

Apply this diff to reorganize imports:

 "use client"
 
 import Image from "next/image"
 import { useRouter } from "next/navigation"
+import { zodResolver } from "@hookform/resolvers/zod"
+import { useForm } from "react-hook-form"
+import { z } from "zod"
+
 import { PlatformEntity } from "@/entities/utils/platform"
+import { useToast } from "@/hooks/use-toast"
+import { getLogoDevUrlWithToken, getPlaceholderImage } from "@/lib/utils"
 import { useDeleteCredential } from "@/orpc/hooks/use-credentials"
-import type { CredentialOutput } from "@/schemas/credential/dto"
-import type { PlatformSimpleRo } from "@/schemas/utils/platform"
-import { zodResolver } from "@hookform/resolvers/zod"
-import { useForm } from "react-hook-form"
-import { z } from "zod"
-
-import { getLogoDevUrlWithToken, getPlaceholderImage } from "@/lib/utils"
-import { useToast } from "@/hooks/use-toast"
 
 import { Icons } from "@/components/shared/icons"
 import { Button } from "@/components/ui/button"
 import {
   Form,
   FormControl,
   FormField,
   FormItem,
   FormLabel,
   FormMessage,
 } from "@/components/ui/form"
 import { Input } from "@/components/ui/input"
 import {
   ResponsiveAlertDialog,
   ResponsiveAlertDialogCancel,
   ResponsiveAlertDialogContent,
   ResponsiveAlertDialogDescription,
   ResponsiveAlertDialogFooter,
   ResponsiveAlertDialogHeader,
   ResponsiveAlertDialogTitle,
 } from "@/components/ui/responsive-alert-dialog"
+
+import type { CredentialOutput } from "@/schemas/credential/dto"
+import type { PlatformSimpleRo } from "@/schemas/utils/platform"

37-39: Consider adding schema-level validation for the identifier match.

The schema currently only validates that the confirmation text is non-empty. The actual match validation against credential.identifier occurs in the handler function (Line 82). While this works, incorporating the match validation into the schema using Zod's refine method would provide more robust form-level validation and better error messaging.

However, this would require passing credential.identifier into the schema definition or using a dynamic schema factory function:

const createDeleteCredentialSchema = (expectedIdentifier: string) =>
  z.object({
    confirmationText: z
      .string()
      .min(1, "Confirmation text is required")
      .refine((val) => val === expectedIdentifier, {
        message: "The identifier doesn't match. Please type it exactly as shown.",
      }),
  })

Then update the form setup to use the dynamic schema:

const form = useForm<DeleteCredentialFormData>({
  resolver: zodResolver(createDeleteCredentialSchema(credential.identifier)),
  defaultValues: {
    confirmationText: "",
  },
})

This would eliminate the manual validation check at Line 82-88.

components/app/dashboard-credential-grid-view.tsx (3)

72-86: Ensure copy button doesn’t submit forms and is explicit

Set type="button" to avoid accidental form submission in composite UIs.

-                  <Button
+                  <Button
                     variant="ghost"
                     size="sm"
                     className="size-8 p-0"
+                    type="button"
                     onClick={async (e) => {
                       e.stopPropagation()
                       await copy(credential.identifier)
                     }}
                   >

39-42: Optional: pre-index platforms for O(1) lookups

For large lists, computing a Map once avoids repeated array scans.

-        const platform = PlatformEntity.findById(
-          platforms,
-          credential.platformId
-        )
+        // Precompute outside map: const platformMap = new Map(platforms.map(p => [p.id, p]))
+        // Then here:
+        const platform =
+          PlatformEntity.getSimpleRo?.(platformMap.get(credential.platformId)!) ??
+          PlatformEntity.findById(platforms, credential.platformId)

Note: create platformMap before the credentials.map loop.


28-31: Optional: avoid global isCopied state across all cards

Current hook instance toggles all copy icons. Track the last copied id to localize UI feedback.

  • Keep a lastCopiedId state and show check icon only when lastCopiedId === credential.id.

Also applies to: 81-86

components/app/dashboard-credential-cards-view.tsx (2)

50-53: Optional: pre-index platforms for performance

Same as grid-view: index platforms once to avoid repeated array scans.

-        const platform = PlatformEntity.findById(
-          platforms,
-          credential.platformId
-        )
+        // Use a precomputed Map outside the loop when rendering many items.
+        const platform = PlatformEntity.findById(platforms, credential.platformId)

Implement platformMap at component start if dataset is large.


115-131: Optional: align date formatting with DateFormatter for consistency

Consider using DateFormatter to standardize date text across views.

-                      <span>{getRelativeTime(primaryDate)}</span>
+                      <span>{getRelativeTime(primaryDate)}</span>
+                      {/* or standardized: DateFormatter.formatShortDate(primaryDate) */}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 08ceee1 and 2cddfd2.

📒 Files selected for processing (5)
  • .cursor/rules/hooks-folder.mdc (0 hunks)
  • components/app/dashboard-credential-cards-view.tsx (5 hunks)
  • components/app/dashboard-credential-delete-dialog.tsx (1 hunks)
  • components/app/dashboard-credential-grid-view.tsx (2 hunks)
  • components/app/dashboard-credential-header.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • .cursor/rules/hooks-folder.mdc
🚧 Files skipped from review as they are similar to previous changes (1)
  • components/app/dashboard-credential-header.tsx
🧰 Additional context used
📓 Path-based instructions (14)
**/*.{html,jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{html,jsx,tsx}: Don't use the accessKey attribute on any HTML element
Don't set aria-hidden="true" on focusable elements
Don't add ARIA roles, states, and properties to elements that don't support them
Don't use distracting elements like or
Only use the scope prop on elements
Don't assign non-interactive ARIA roles to interactive HTML elements
Make sure label elements have text content and are associated with an input
Don't assign interactive ARIA roles to non-interactive HTML elements
Don't assign tabIndex to non-interactive HTML elements
Don't use positive integers for the tabIndex property
Don't include "image", "picture", or "photo" in img alt prop
Don't use an explicit role property that's the same as the implicit/default role
Make static elements with click handlers use a valid role attribute
Give all elements requiring alt text meaningful information for screen readers
Make sure anchors have content that's accessible to screen readers
Assign tabIndex to non-interactive HTML elements with aria-activedescendant
Include all required ARIA attributes for elements with ARIA roles
Make sure ARIA properties are valid for the element's supported roles
Always include a type attribute for button elements
Make elements with interactive roles and handlers focusable
Give heading elements content that's accessible to screen readers (not hidden with aria-hidden)
Always include a title attribute for iframe elements
Include caption tracks for audio and video elements
Make sure all anchors are valid and navigable
Ensure all ARIA properties (aria-*) are valid
Use valid, non-abstract ARIA roles for elements with ARIA roles
Use valid ARIA state and property values
Use valid values for the autocomplete attribute on input elements
Use correct ISO language/country codes for the lang attribute
Don't use variables that haven't been declared in the document
Make sure void (self-closing) elements don't have children
Don't use event handlers on non-interactiv...

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{jsx,tsx}: Accompany onClick with at least one of: onKeyUp, onKeyDown, or onKeyPress
Accompany onMouseOver/onMouseOut with onFocus/onBlur
Use semantic elements instead of role attributes in JSX
Don't use unnecessary fragments
Don't pass children as props
Don't use the return value of React.render
Make sure all dependencies are correctly specified in React hooks
Make sure all React hooks are called from the top level of component functions
Don't forget key props in iterators and collection literals
Don't define React components inside other components
Don't assign to React component props
Don't use dangerous JSX props
Don't use both children and dangerouslySetInnerHTML props on the same element
Use <>...</> instead of ...
Don't add extra closing tags for components without children
Don't use Array index in keys
Don't insert comments as text nodes
Don't assign JSX properties multiple times
Watch out for possible "wrong" semicolons inside JSX elements
Don't put multiple components in one file; each file must have one component

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{js,jsx,ts,tsx}: Don't use consecutive spaces in regular expression literals
Don't use the arguments object
Don't use the comma operator
Don't write functions that exceed a given Cognitive Complexity score
Don't use unnecessary boolean casts
Don't use unnecessary callbacks with flatMap
Use for...of statements instead of Array.forEach
Don't create classes that only have static members
Don't use this and super in static contexts
Don't use unnecessary catch clauses
Don't use unnecessary constructors
Don't use unnecessary continue statements
Don't export empty modules that don't change anything
Don't use unnecessary escape sequences in regular expression literals
Don't use unnecessary labels
Don't use unnecessary nested block statements
Don't rename imports, exports, and destructured assignments to the same name
Don't use unnecessary string or template literal concatenation
Don't use String.raw in template literals when there are no escape sequences
Don't use useless case statements in switch statements
Don't use ternary operators when simpler alternatives exist
Don't use useless this aliasing
Don't initialize variables to undefined
Don't use void operators
Use arrow functions instead of function expressions
Use Date.now() to get milliseconds since the Unix Epoch
Use .flatMap() instead of map().flat() when possible
Use literal property access instead of computed property access
Don't use parseInt() or Number.parseInt() when binary, octal, or hexadecimal literals work
Use concise optional chaining instead of chained logical expressions
Use regular expression literals instead of the RegExp constructor when possible
Don't use number literal object member names that aren't base 10 or use underscore separators
Remove redundant terms from logical expressions
Use while loops instead of for loops when you don't need initializer and update expressions
Don't reassign const variables
Don't use constant expressions in conditions
Don't use Math.min and Math.max to clamp value...

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

**/*.{ts,tsx}: Don't use primitive type aliases or misleading types
Don't use empty type parameters in type aliases and interfaces
Don't use any or unknown as type constraints
Don't return a value from a function that has a 'void' return type
Don't use the TypeScript directive @ts-ignore
Make sure switch-case statements are exhaustive
Don't use TypeScript enums
Don't add type annotations to variables, parameters, and class properties that are initialized with literal expressions
Don't use TypeScript namespaces
Don't use non-null assertions with the ! postfix operator
Don't use parameter properties in class constructors
Don't use user-defined types
Use as const instead of literal types and type annotations
Use either T[] or Array consistently
Use consistent accessibility modifiers on class properties and methods
Put default function parameters and optional function parameters last
Initialize each enum member value explicitly
Use export type for types
Use import type for types
Make sure all enum members are literal values
Use function types instead of object types with call signatures
Don't use void type outside of generic or return types
Don't use TypeScript const enum
Don't declare empty interfaces
Don't let variables evolve into any type through reassignments
Don't use the any type
Don't misuse the non-null assertion operator (!) in TypeScript files
Don't use implicit any type on variable declarations
Don't merge interfaces and classes unsafely
Don't use overload signatures that aren't next to each other
Use the namespace keyword instead of the module keyword to declare TypeScript namespaces

Use type-only imports when possible

**/*.{ts,tsx}: Use camelCase for function and method names
Use UPPER_SNAKE_CASE for constants
Use camelCase for variables
Use PascalCase for classes and types
Use absolute imports for top-level modules (e.g., @/components, @/lib, @/entities) instead of long relative paths
Follow import order: React imports, third-party, internal abso...

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
**/*.{html,jsx,tsx,css}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Don't use TailwindCSS class names 'h-NUMBER w-NUMBER'; instead use 'size-NUMBR'

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
components/app/**

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Place application-specific React components under components/app

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
components/app/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Use naming pattern {feature}-{purpose}-{type}.tsx for app components

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
components/app/dashboard-*.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

Dashboard app components in components/app must be prefixed with dashboard-

Place dashboard components under components/app/ with filenames starting dashboard-*.tsx

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

components/**/*.{ts,tsx}: Always define a TypeScript interface for component props
Follow established naming patterns consistently across files
Use strict, explicit TypeScript types for safety

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
components/**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

components/**/*.tsx: Add 'use client' directive for components that use client-side hooks/effects
Use cn() for conditional Tailwind classes
Use semantic Tailwind colors (e.g., text-foreground, bg-background) and consistent spacing (space-y-4, gap-4)
Use responsive Tailwind prefixes (md:, lg:, xl:) for adaptive layouts
Define component variants with cva and defaultVariants when variants are needed
Prefer useState for local state; use useReducer for complex state
Use TanStack Query hooks from /orpc/hooks for server state; prefer server-side data fetching when possible
Implement proper ARIA attributes and keyboard navigation for accessibility
Use React.memo for expensive components
Use useMemo and useCallback for expensive computations and stable callbacks
Lazy-load heavy components via dynamic import
Import only needed icons from lucide-react (no wildcard imports)
Wrap components that might fail in Error Boundaries
Provide loading and error states for async UI
Add JSDoc comments for complex components

components/**/*.tsx: Components must be named in kebab-case.tsx (e.g., dashboard-credential-form.tsx)
Use React Hook Form for form management in UI forms
Use React Hook Form error handling, display validation errors, and handle submission errors gracefully in forms

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
components/**/[a-z0-9-]*.tsx

📄 CodeRabbit inference engine (.cursor/rules/project-overview.mdc)

Name React components in kebab-case with .tsx under the components directory (e.g., dashboard-credential-form.tsx)

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
components/{app,layout,shared,ui}/**

📄 CodeRabbit inference engine (.cursor/rules/architecture.mdc)

Organize React components under components/app, components/layout, components/shared, and components/ui

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/zero-locker-patterns.mdc)

**/*.tsx: Use Component Composition Pattern: build complex UI by composing smaller, reusable components (e.g., Form, FormField, FormItem)
Handle form errors with React Hook Form and Zod resolver for client-side validation
Avoid direct database access in React components; use hooks/services instead

**/*.tsx: Use PascalCase for React component names
Use functional components only, typed with explicit props interfaces
Optimize components using React.memo for expensive components, useMemo for heavy computations, and useCallback for stable handlers

Files:

  • components/app/dashboard-credential-cards-view.tsx
  • components/app/dashboard-credential-delete-dialog.tsx
  • components/app/dashboard-credential-grid-view.tsx
components/**/*-dialog.tsx

📄 CodeRabbit inference engine (.cursor/rules/components-folder.mdc)

components/**/*-dialog.tsx: Dialog components should accept open, onOpenChange, and optional data props and render a Dialog/DialogContent structure
Manage focus in dialogs/modals and use focus-visible for keyboard users

Files:

  • components/app/dashboard-credential-delete-dialog.tsx
🧬 Code graph analysis (3)
components/app/dashboard-credential-cards-view.tsx (3)
hooks/use-copy-to-clipboard.ts (1)
  • useCopyToClipboard (16-66)
entities/utils/platform/entity.ts (1)
  • PlatformEntity (6-74)
components/shared/item-actions-dropdown.tsx (1)
  • CredentialActionsContextMenu (334-464)
components/app/dashboard-credential-delete-dialog.tsx (6)
schemas/credential/dto.ts (1)
  • CredentialOutput (63-63)
schemas/utils/platform.ts (1)
  • PlatformSimpleRo (34-34)
hooks/use-toast.ts (1)
  • useToast (87-186)
orpc/hooks/use-credentials.ts (1)
  • useDeleteCredential (371-410)
entities/utils/platform/entity.ts (1)
  • PlatformEntity (6-74)
lib/utils/index.ts (2)
  • getPlaceholderImage (204-213)
  • getLogoDevUrlWithToken (219-225)
components/app/dashboard-credential-grid-view.tsx (5)
hooks/use-copy-to-clipboard.ts (1)
  • useCopyToClipboard (16-66)
entities/utils/platform/entity.ts (1)
  • PlatformEntity (6-74)
components/shared/item-actions-dropdown.tsx (1)
  • CredentialActionsDropdown (194-332)
lib/date-utils.ts (1)
  • DateFormatter (11-110)
components/shared/status-badge.tsx (1)
  • StatusBadge (16-74)
🔇 Additional comments (4)
components/app/dashboard-credential-delete-dialog.tsx (4)

41-49: LGTM!

The type definitions are well-structured, follow TypeScript best practices, and properly define the component's interface. The use of z.infer for form data types ensures type safety with the Zod schema.


51-79: LGTM!

The component setup follows React and React Hook Form best practices:

  • Hooks are called at the top level
  • Form configuration with zodResolver is correct
  • Derived state is efficiently computed using watch
  • The guard clause prevents rendering invalid states
  • Platform lookup handles missing platforms gracefully with a fallback

108-113: LGTM!

The handleOpenChange function correctly resets the form state when the dialog is closed, preventing stale data from persisting between dialog sessions.


115-211: LGTM!

The UI implementation is excellent and follows all best practices:

  • Accessibility: Proper ARIA structure via ResponsiveAlertDialog components, semantic labels, keyboard navigation support, and proper disabled states
  • Responsive Design: Button order adapts to screen size using sm: prefixes (lines 193, 201)
  • Visual Feedback: Loading state with spinner icon (lines 203-205), destructive button variant for danger action
  • Form UX: Clear instructions showing the exact text to type, real-time validation feedback, and proper placeholder text
  • Credential Preview: Well-structured card layout showing platform logo, identifier, and optional description
  • Error Handling: Form-level error messages displayed via FormMessage component
  • Styling: Follows Tailwind conventions using semantic color tokens (text-foreground, text-destructive, bg-muted), proper spacing utilities, and approved size-* classes instead of h-/w-* combinations

The component provides a professional, user-friendly deletion confirmation experience.

@FindMalek FindMalek merged commit 02192a9 into main Oct 6, 2025
1 of 2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Oct 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant