Skip to content

Conversation

@tiankii
Copy link

@tiankii tiankii commented Dec 8, 2025

Summary by CodeRabbit

  • New Features
    • Invitation management UI: list, search, status/date filters, pagination, detail view, send-new flow, editable content, resends/revoke/change-status and toggles.
    • Template management UI: list, create modal, edit view, icons, languages, and pagination.
    • New UI components: status badge, invitation/client/history cards, change-status modal, invitation card, buttons, form controls, pagination, and exported notification content type.
  • Chores
    • Added mock data, date-format utility, sidebar links, and CSS to toggle sidebar visibility.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Dec 8, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a invitations and templates feature to the admin panel: list/new/detail pages, template list/edit pages, mock data and types, multiple UI components (cards, modal, badge), shared primitives (button, form controls, pagination), a date formatter, sidebar routes, and a sidebar-hide CSS rule.

Changes

Cohort / File(s) Summary
Invitations pages
apps/admin-panel/app/invitations/page.tsx, apps/admin-panel/app/invitations/new/page.tsx, apps/admin-panel/app/invitations/[id]/page.tsx
Added list, creation, and detail pages with filtering, pagination, template-driven compose flow, editable detail view, status/history events, and actions (send/resend/revoke/change status).
Templates pages
apps/admin-panel/app/templates/page.tsx, apps/admin-panel/app/templates/[id]/edit/page.tsx
Added templates list with create modal, pagination and per-row actions; edit page with form state, sidebar-hide behavior, and mock save/navigation.
Types & mock data
apps/admin-panel/app/invitations/types.ts, apps/admin-panel/app/mock-data.ts
New exported types for invitations/templates/events/editable content and exported mock arrays (visaInvitationsMock, visaTemplatesMock, notificationContentMock) plus InvitationContent.
Notification builder types
apps/admin-panel/components/notification/builder.tsx
Exported LocalizedNotificationContent type (visibility change only).
Utilities
apps/admin-panel/app/utils.ts
Added formatDateDisplay(dateString: string): string.
Invitations UI components
apps/admin-panel/components/invitations/status-badge.tsx, apps/admin-panel/components/invitations/invitation-card.tsx, apps/admin-panel/components/invitations/client-info-card.tsx, apps/admin-panel/components/invitations/status-history-card.tsx, apps/admin-panel/components/invitations/change-status-modal.tsx
New presentational and interactive components: status badge, editable invitation card, client info card, status history card, and change-status modal with template selection and toggle controls.
Shared UI primitives
apps/admin-panel/components/shared/button/*, apps/admin-panel/components/shared/form-controls/*, apps/admin-panel/components/shared/pagination/*
New Button component (variants) with types and index; TextInput/Select/TextArea/Checkbox controls with types and index; Pagination component with DEFAULT_PAGE_SIZE, types, and index barrel.
Sidebar & styling
apps/admin-panel/components/side-bar.tsx, apps/admin-panel/app/globals.css
Added "Invitations" and "Templates" sidebar routes and a CSS rule to hide the main sidebar when body.hide-sidebar is set.
Barrel exports / indexes
apps/admin-panel/components/shared/button/index.ts, apps/admin-panel/components/shared/form-controls/index.ts, apps/admin-panel/components/shared/pagination/index.ts
Added re-export index files for shared modules.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • apps/admin-panel/app/invitations/[id]/page.tsx — state transitions, event creation, modal interactions.
    • apps/admin-panel/components/invitations/change-status-modal.tsx — prop contracts, internal state, and callback wiring.
    • Pagination (components/shared/pagination/*) — resetKey handling and page-bound adjustments.
    • Shared primitives (button, form-controls) — prop spreading, class merging, and variant/type definitions.
    • apps/admin-panel/components/notification/builder.tsx — newly exported type and any downstream imports.

Poem

🐇 I nibble a template, stitch a tiny note,
Invites hop out on a code-lined boat,
Badges blink like stars and modals softly chime,
History hops home, each event keeps time—
🥕 Cheers from a rabbit for this sprint's bright rhyme!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
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.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'feat: blink card invitations' accurately reflects the main change: adding UI support for Blink card Visa invitations across multiple new pages and components.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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

month: "short",
year: "numeric",
})
.replace(/ /g, " ")

Check warning

Code scanning / CodeQL

Replacement of a substring with itself Medium

This replaces ' ' with itself.
@grimen grimen marked this pull request as ready for review December 9, 2025 08:53
Copilot AI review requested due to automatic review settings December 9, 2025 08:53
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces a new visa card invitations feature to the admin panel, allowing administrators to send and manage invitation notifications. The implementation includes invitation list management with filtering capabilities, template-based invitation creation, and reusable UI components for forms and pagination.

Key Changes

  • Added invitation and template management pages with filtering, pagination, and CRUD operations
  • Created reusable UI components (Button, form controls, Pagination) for consistent styling and behavior
  • Implemented mock data structure for invitations and templates to support development before API integration

Reviewed changes

Copilot reviewed 17 out of 19 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
apps/admin-panel/components/side-bar.tsx Added "Invitations" and "Templates" navigation menu items with corresponding icons
apps/admin-panel/components/shared/pagination/types.ts Defined TypeScript types for pagination component props and page change payload
apps/admin-panel/components/shared/pagination/pagination.tsx Implemented reusable pagination component with page navigation and state management
apps/admin-panel/components/shared/pagination/index.ts Barrel export for pagination module
apps/admin-panel/components/shared/form-controls/types.ts Defined TypeScript types for text input, select, textarea, and checkbox components
apps/admin-panel/components/shared/form-controls/index.ts Barrel export for form controls module
apps/admin-panel/components/shared/form-controls/form-controls.tsx Implemented reusable form control components with consistent styling
apps/admin-panel/components/shared/button/types.ts Defined button component props with variant support
apps/admin-panel/components/shared/button/index.ts Barrel export for button module
apps/admin-panel/components/shared/button/button.tsx Implemented reusable button component with primary and outline variants
apps/admin-panel/components/invitations/status-badge.tsx Created status badge component for displaying invitation states
apps/admin-panel/app/utils.ts Added date formatting utility for consistent date display
apps/admin-panel/app/templates/page.tsx Implemented templates listing page with table view and pagination
apps/admin-panel/app/mock-data.ts Created mock data for invitations and templates during development
apps/admin-panel/app/invitations/types.ts Defined invitation and template types and enums
apps/admin-panel/app/invitations/page.tsx Implemented invitations listing page with filtering, search, and pagination
apps/admin-panel/app/invitations/new/page.tsx Created invitation creation page with template selection and customization

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 11 to 13
export default function TemplatesPage() {
const router = useRouter()
const [pageItems, setPageItems] = useState<TemplateRow[]>(visaTemplatesMock.slice(0, 0))
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

Initial state uses slice(0, 0) which returns an empty array. This should be slice(0, DEFAULT_PAGE_SIZE) or rely on the initial page change to populate items, but the current implementation may cause the first page to appear empty until pagination triggers.

Suggested change
export default function TemplatesPage() {
const router = useRouter()
const [pageItems, setPageItems] = useState<TemplateRow[]>(visaTemplatesMock.slice(0, 0))
const DEFAULT_PAGE_SIZE = 10;
export default function TemplatesPage() {
const router = useRouter()
const [pageItems, setPageItems] = useState<TemplateRow[]>(visaTemplatesMock.slice(0, DEFAULT_PAGE_SIZE))

Copilot uses AI. Check for mistakes.
Comment on lines 20 to 25
export const TemplateIcon = {
Star: "star",
Check: "check",
}

export type TemplateIcon = (typeof TemplateIcon)[keyof typeof TemplateIcon]
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

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

[nitpick] The constant and type share the same name TemplateIcon, which can cause confusion. Consider renaming the constant to TemplateIcons (plural) or TemplateIconValues to differentiate it from the type.

Suggested change
export const TemplateIcon = {
Star: "star",
Check: "check",
}
export type TemplateIcon = (typeof TemplateIcon)[keyof typeof TemplateIcon]
export const TemplateIcons = {
Star: "star",
Check: "check",
}
export type TemplateIcon = (typeof TemplateIcons)[keyof typeof TemplateIcons]

Copilot uses AI. Check for mistakes.
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

♻️ Duplicate comments (1)
apps/admin-panel/app/utils.ts (1)

31-31: Remove the redundant replace operation.

This line replaces a space with itself, which is redundant and serves no purpose. The operation can be safely removed.

Apply this diff to remove the redundant operation:

 export const formatDateDisplay = (dateString: string): string => {
   return new Date(`${dateString}T00:00:00Z`)
     .toLocaleDateString("en-GB", {
       day: "2-digit",
       month: "short",
       year: "numeric",
     })
-    .replace(/ /g, " ")
 }
🧹 Nitpick comments (4)
apps/admin-panel/app/utils.ts (1)

24-32: Consider adding input validation and error handling.

The function assumes a valid date string format (YYYY-MM-DD) and will return "Invalid Date" if the input is malformed. Consider adding validation or error handling if the input source is not strictly controlled.

Example with validation:

export const formatDateDisplay = (dateString: string): string => {
  const date = new Date(`${dateString}T00:00:00Z`)
  if (isNaN(date.getTime())) {
    throw new Error(`Invalid date string: ${dateString}`)
  }
  return date.toLocaleDateString("en-GB", {
    day: "2-digit",
    month: "short",
    year: "numeric",
  })
}
apps/admin-panel/components/shared/pagination/pagination.tsx (1)

52-75: Optional: reuse shared Button for Previous/Next to avoid style duplication.

You’re re-declaring button classes here that are very close to the shared Button’s base styles. Consider swapping these <button>s for the shared <Button> component (perhaps with a lighter variant) to keep styling consistent and reduce duplication.

apps/admin-panel/app/templates/page.tsx (1)

15-20: Align handlePageChange parameter type with the shared pagination payload.

Right now the callback is typed as { offset: number; limit: number }, but Pagination is documented to call with a richer payload. To keep types in sync and avoid surprises if PageChangePayload changes, consider:

-import { Pagination } from "../../components/shared/pagination"
+import { Pagination } from "../../components/shared/pagination"
+import type { PageChangePayload } from "../../components/shared/pagination/types"

-  const handlePageChange = useCallback(
-    ({ offset, limit }: { offset: number; limit: number }) => {
+  const handlePageChange = useCallback(
+    ({ offset, limit }: PageChangePayload) => {
       setPageItems(visaTemplatesMock.slice(offset, offset + limit))
     },
-    [],
+    [],
   )
apps/admin-panel/app/invitations/new/page.tsx (1)

15-167: New invitation flow is coherent; toggles are ready for backend integration.

State management for user, template selection, and canSend gating looks good. Once you wire this to a real API, remember to pass sendPush, addHistory, and addBulletin through to the request so those toggles have an effect.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a85ba1e and 5e75d90.

⛔ Files ignored due to path filters (2)
  • apps/admin-panel/components/icons/fileText.svg is excluded by !**/*.svg
  • apps/admin-panel/components/icons/send.svg is excluded by !**/*.svg
📒 Files selected for processing (17)
  • apps/admin-panel/app/invitations/new/page.tsx (1 hunks)
  • apps/admin-panel/app/invitations/page.tsx (1 hunks)
  • apps/admin-panel/app/invitations/types.ts (1 hunks)
  • apps/admin-panel/app/mock-data.ts (1 hunks)
  • apps/admin-panel/app/templates/page.tsx (1 hunks)
  • apps/admin-panel/app/utils.ts (1 hunks)
  • apps/admin-panel/components/invitations/status-badge.tsx (1 hunks)
  • apps/admin-panel/components/shared/button/button.tsx (1 hunks)
  • apps/admin-panel/components/shared/button/index.ts (1 hunks)
  • apps/admin-panel/components/shared/button/types.ts (1 hunks)
  • apps/admin-panel/components/shared/form-controls/form-controls.tsx (1 hunks)
  • apps/admin-panel/components/shared/form-controls/index.ts (1 hunks)
  • apps/admin-panel/components/shared/form-controls/types.ts (1 hunks)
  • apps/admin-panel/components/shared/pagination/index.ts (1 hunks)
  • apps/admin-panel/components/shared/pagination/pagination.tsx (1 hunks)
  • apps/admin-panel/components/shared/pagination/types.ts (1 hunks)
  • apps/admin-panel/components/side-bar.tsx (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (7)
apps/admin-panel/components/shared/button/button.tsx (1)
apps/admin-panel/components/shared/button/types.ts (2)
  • ButtonVariant (3-3)
  • ButtonProps (5-7)
apps/admin-panel/app/invitations/new/page.tsx (2)
apps/admin-panel/app/mock-data.ts (1)
  • visaTemplatesMock (70-111)
apps/admin-panel/components/shared/form-controls/form-controls.tsx (4)
  • TextInput (11-14)
  • SelectInput (16-23)
  • TextArea (25-28)
  • Checkbox (30-33)
apps/admin-panel/components/shared/pagination/pagination.tsx (1)
apps/admin-panel/components/shared/pagination/types.ts (1)
  • PaginationProps (10-15)
apps/admin-panel/app/templates/page.tsx (3)
apps/admin-panel/app/invitations/types.ts (1)
  • TemplateRow (27-34)
apps/admin-panel/app/mock-data.ts (1)
  • visaTemplatesMock (70-111)
apps/admin-panel/components/shared/pagination/pagination.tsx (1)
  • Pagination (9-77)
apps/admin-panel/components/invitations/status-badge.tsx (1)
apps/admin-panel/app/invitations/types.ts (1)
  • InvitationStatus (1-1)
apps/admin-panel/app/mock-data.ts (1)
apps/admin-panel/app/invitations/types.ts (2)
  • InvitationRow (3-8)
  • TemplateRow (27-34)
apps/admin-panel/components/shared/form-controls/form-controls.tsx (1)
apps/admin-panel/components/shared/form-controls/types.ts (4)
  • TextInputProps (7-7)
  • SelectInputProps (8-8)
  • TextAreaProps (9-9)
  • CheckboxProps (10-10)
🪛 GitHub Actions: Spelling
apps/admin-panel/app/mock-data.ts

[error] 100-100: typos: 'ist' should be 'is', 'it', 'its', 'sit', or 'list'. Command './typos . --config typos.toml' exited with code 2.


[error] 101-101: typos: 'Sie' should be 'Size', 'Sigh', or 'Side'. Command './typos . --config typos.toml' exited with code 2.


[warning] 1-1: typos: potential typos detected on lines 100-101. Review German strings for proper capitalization/terms. Command './typos . --config typos.toml' exited with code 2.

🪛 GitHub Check: CodeQL
apps/admin-panel/app/utils.ts

[warning] 31-31: Replacement of a substring with itself
This replaces ' ' with itself.

🪛 GitHub Check: Spell Check with Typos
apps/admin-panel/app/mock-data.ts

[warning] 101-101:
"Sie" should be "Size" or "Sigh" or "Side".


[warning] 100-100:
"ist" should be "is" or "it" or "its" or "sit" or "list".

🔇 Additional comments (17)
apps/admin-panel/components/shared/pagination/types.ts (1)

1-15: LGTM!

The pagination types are well-structured and comprehensive. The PageChangePayload includes all necessary pagination state fields, and PaginationProps provides a clean, flexible API with required callback and optional configuration.

apps/admin-panel/components/shared/form-controls/form-controls.tsx (2)

30-33: LGTM! Correct prop spreading order.

The type="checkbox" attribute is correctly placed after {...rest} to ensure the input type cannot be overridden accidentally via props. This is the proper defensive pattern for a dedicated Checkbox component.


1-33: Well-structured form control components.

The implementation follows a consistent pattern across all form controls with appropriate base styling and proper prop spreading. The components provide a clean, reusable API for form inputs throughout the admin panel.

apps/admin-panel/components/shared/form-controls/types.ts (1)

1-10: LGTM!

Clean type definitions that leverage React's built-in HTML attribute types. The semantic naming improves code readability while maintaining full type safety.

apps/admin-panel/components/shared/form-controls/index.ts (1)

1-1: LGTM!

Standard barrel export pattern for aggregating the form-controls public API.

apps/admin-panel/components/shared/button/index.ts (1)

1-1: LGTM!

Standard barrel export pattern consistent with other shared components.

apps/admin-panel/components/shared/pagination/index.ts (1)

1-1: LGTM!

Standard barrel export pattern for the pagination module.

apps/admin-panel/components/side-bar.tsx (2)

14-15: LGTM!

Icon imports follow the existing pattern and are appropriately named for the new navigation items.


38-47: LGTM!

The new Invitations and Templates routes are properly integrated following the existing structure. The route objects match the established pattern with appropriate icons and paths.

apps/admin-panel/components/shared/button/types.ts (1)

3-7: Button type definitions look good.

ButtonVariant and ButtonProps are minimal, type-safe, and match the Button implementation.

apps/admin-panel/components/shared/button/button.tsx (1)

5-22: Shared Button implementation is solid.

Good use of variant-based classes, safe default type="button", and className merging; no issues spotted.

apps/admin-panel/components/invitations/status-badge.tsx (1)

5-37: InvitationStatusBadge is concise and type-safe.

The status-to-meta mapping and optional label override are straightforward and correct.

apps/admin-panel/components/shared/pagination/pagination.tsx (1)

15-42: Pagination state and payload logic look correct.

The clamping via safePage, reset behavior via resetKey, and computed offset/limit payload are all consistent and robust, including the 0-items case.

apps/admin-panel/app/templates/page.tsx (1)

26-29: Please verify that /templates/new is implemented.

The “Create New Template” button navigates to /templates/new. If that route/page isn’t implemented in this PR or already present in the app, this will currently 404.

apps/admin-panel/app/invitations/page.tsx (1)

26-36: [Rewritten review comment]
[Classification tag]

apps/admin-panel/app/invitations/types.ts (2)

1-19: Invitation status and filter modelling looks solid

The InvitationStatus, InvitationRow, and InvitationStatusOptions/StatusFilter combo is clear and idiomatic: literal union for status, a row shape that matches likely table usage, and a filter type derived from the options object so it stays in sync with the UI.


20-34: Add as const to preserve TemplateIcon as a literal union type

The TemplateIcon object lacks as const, causing TypeScript to widen its properties to string instead of the literal union "star" | "check". This removes type safety and auto-complete for TemplateRow["icon"] and consumers.

Fix by adding as const:

export const TemplateIcon = {
  Star: "star",
  Check: "check",
+} as const

This ensures the type correctly resolves to "star" | "check" rather than string.

Comment on lines +31 to +32
const [pageItems, setPageItems] = useState<InvitationRow[]>([])

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

Initial empty-state flicker and copy wording.

  • Because pageItems starts as [], the table briefly shows “No users found.” until the first onPageChange runs. Seeding pageItems with the first page (similar to the Templates page suggestion) would avoid this.
  • The empty-state message says “No users found.” while the page is titled “Invitations”; consider “No invitations found.” for clearer wording.

Also applies to: 60-62, 160-164

Comment on lines 145 to 153
<Button
variant="outline-blue"
onClick={() => handleRowClick(invitation.id)}
>
View
</Button>
{invitation.status === InvitationStatusOptions.Pending && (
<Button variant="outline-red">Revoke</Button>
)}
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

Revoke button is inert; consider wiring it or making it clearly disabled.

The Revoke button is rendered only for pending invitations but has no onClick handler. From a user’s perspective it looks actionable yet does nothing. Either hook it up to the intended revoke flow (even a stub handler/toast for now) or hide/disable it until backend support is ready.

Comment on lines 96 to 102
id: "blink-private-de",
name: "Blink Private Invite (DE)",
language: "German",
icon: "star",
title: "Blink Private ist da!",
body: "Melden Sie sich an, um Ihre Visa-Karte und mehr zu erhalten",
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Typos check is failing on valid German strings.

The typos pipeline is flagging "ist" and "Sie" here, even though the German copy is correct. Consider adding these words (or this file/path) to the typos.toml allowlist/ignore rules so the pipeline passes without degrading the translations, especially since this file is explicitly temporary mock data.

🧰 Tools
🪛 GitHub Actions: Spelling

[error] 100-100: typos: 'ist' should be 'is', 'it', 'its', 'sit', or 'list'. Command './typos . --config typos.toml' exited with code 2.


[error] 101-101: typos: 'Sie' should be 'Size', 'Sigh', or 'Side'. Command './typos . --config typos.toml' exited with code 2.

🪛 GitHub Check: Spell Check with Typos

[warning] 101-101:
"Sie" should be "Size" or "Sigh" or "Side".


[warning] 100-100:
"ist" should be "is" or "it" or "its" or "sit" or "list".

🤖 Prompt for AI Agents
In apps/admin-panel/app/mock-data.ts around lines 96 to 102, the typos pipeline
is incorrectly flagging valid German words ("ist", "Sie"); add these words or
this file/path to the typos.toml allowlist or ignore rules so the pipeline won't
fail on this temporary mock data—update typos.toml to either add "ist" and "Sie"
to the allowed-words list or add apps/admin-panel/app/mock-data.ts (or its
directory) to the ignore patterns, commit the change, and rerun the pipeline.

Comment on lines 13 to 14
const [pageItems, setPageItems] = useState<TemplateRow[]>(visaTemplatesMock.slice(0, 0))

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

Avoid initial “No templates found.” flicker before pagination loads.

Because pageItems starts as visaTemplatesMock.slice(0, 0), the table initially renders the “No templates found.” row until the first onPageChange fires. You can seed pageItems with the first page (e.g., using DEFAULT_PAGE_SIZE) to avoid this brief misleading empty state.

Also applies to: 78-84

🤖 Prompt for AI Agents
In apps/admin-panel/app/templates/page.tsx around lines 13-14 (and also apply
same change to lines 78-84), the pageItems state is initialized to
visaTemplatesMock.slice(0, 0) which causes a brief “No templates found.”
flicker; initialize pageItems with the first page of data instead (e.g.,
visaTemplatesMock.slice(0, DEFAULT_PAGE_SIZE) or compute start/end based on
current page and DEFAULT_PAGE_SIZE) so the table renders the first page
immediately and update setPageItems logic consistently where pagination handlers
run.

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: 10

♻️ Duplicate comments (2)
apps/admin-panel/app/mock-data.ts (1)

96-102: Pipeline failing on valid German strings.

The typos pipeline flags "ist" and "Sie" as errors, but these are correct German words in the template content. Add these words to the typos.toml allowlist or exclude this mock data file from spell checking.

apps/admin-panel/app/templates/page.tsx (1)

13-13: Initial empty state causes "No templates found." flicker.

visaTemplatesMock.slice(0, 0) produces an empty array, causing the empty state message to briefly appear before pagination populates the first page.

🧹 Nitpick comments (3)
apps/admin-panel/components/invitations/invitation-card.tsx (1)

43-45: Placeholder menu button has no functionality.

The "⋮" button currently has no onClick handler. If this is intentional placeholder for future features, consider adding a TODO comment or disabling the button to indicate it's not yet functional.

-          <button className="rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50">
+          <button
+            disabled
+            className="rounded-md border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 disabled:opacity-50"
+          >
             ⋮
           </button>
apps/admin-panel/app/invitations/[id]/page.tsx (2)

90-92: Extract magic number to a named constant.

86400000 is milliseconds in a day. Extract to a constant for clarity.

+const ONE_DAY_MS = 86400000
+
 // Then in the useEffect:
-          new Date(foundInvitation.lastActivity).getTime() + 86400000,
+          new Date(foundInvitation.lastActivity).getTime() + ONE_DAY_MS,

147-150: handleRevokeInvitation is defined but never used.

This function is not referenced anywhere in the component. Either remove it or wire it up to a UI element.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e75d90 and ea63427.

📒 Files selected for processing (11)
  • apps/admin-panel/app/globals.css (1 hunks)
  • apps/admin-panel/app/invitations/[id]/page.tsx (1 hunks)
  • apps/admin-panel/app/invitations/types.ts (1 hunks)
  • apps/admin-panel/app/mock-data.ts (1 hunks)
  • apps/admin-panel/app/templates/[id]/edit/page.tsx (1 hunks)
  • apps/admin-panel/app/templates/page.tsx (1 hunks)
  • apps/admin-panel/components/invitations/change-status-modal.tsx (1 hunks)
  • apps/admin-panel/components/invitations/client-info-card.tsx (1 hunks)
  • apps/admin-panel/components/invitations/invitation-card.tsx (1 hunks)
  • apps/admin-panel/components/invitations/status-badge.tsx (1 hunks)
  • apps/admin-panel/components/invitations/status-history-card.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/admin-panel/components/invitations/status-badge.tsx
  • apps/admin-panel/app/invitations/types.ts
🧰 Additional context used
🧬 Code graph analysis (8)
apps/admin-panel/app/invitations/[id]/page.tsx (2)
apps/admin-panel/app/invitations/types.ts (5)
  • EditableContent (46-49)
  • InvitationRow (3-8)
  • TemplateRow (29-36)
  • Event (38-44)
  • InvitationStatus (1-1)
apps/admin-panel/app/mock-data.ts (2)
  • visaInvitationsMock (7-68)
  • visaTemplatesMock (70-111)
apps/admin-panel/components/invitations/change-status-modal.tsx (1)
apps/admin-panel/app/invitations/types.ts (2)
  • InvitationStatus (1-1)
  • TemplateRow (29-36)
apps/admin-panel/components/invitations/status-history-card.tsx (1)
apps/admin-panel/app/invitations/types.ts (1)
  • Event (38-44)
apps/admin-panel/app/templates/[id]/edit/page.tsx (2)
apps/admin-panel/app/invitations/types.ts (1)
  • TemplateRow (29-36)
apps/admin-panel/app/mock-data.ts (1)
  • visaTemplatesMock (70-111)
apps/admin-panel/components/invitations/client-info-card.tsx (2)
apps/admin-panel/app/invitations/types.ts (1)
  • InvitationRow (3-8)
apps/admin-panel/components/invitations/status-badge.tsx (1)
  • InvitationStatusBadge (32-42)
apps/admin-panel/components/invitations/invitation-card.tsx (1)
apps/admin-panel/app/invitations/types.ts (1)
  • EditableContent (46-49)
apps/admin-panel/app/templates/page.tsx (3)
apps/admin-panel/app/invitations/types.ts (3)
  • TemplateRow (29-36)
  • TemplateIcon (21-25)
  • TemplateIcon (27-27)
apps/admin-panel/app/mock-data.ts (1)
  • visaTemplatesMock (70-111)
apps/admin-panel/components/shared/pagination/pagination.tsx (1)
  • Pagination (9-77)
apps/admin-panel/app/mock-data.ts (1)
apps/admin-panel/app/invitations/types.ts (2)
  • InvitationRow (3-8)
  • TemplateRow (29-36)
🪛 GitHub Actions: Spelling
apps/admin-panel/app/mock-data.ts

[warning] 100-100: "ist" should be "is" or "it" or "its" or "sit" or "list"


[error] 100-100: ist should be is, it, its, sit, list


[warning] 101-101: "Sie" should be "Size" or "Sigh" or "Side"


[error] 101-101: Sie should be Size, Sigh, or Side

🪛 GitHub Check: Spell Check with Typos
apps/admin-panel/app/mock-data.ts

[warning] 101-101:
"Sie" should be "Size" or "Sigh" or "Side".


[warning] 100-100:
"ist" should be "is" or "it" or "its" or "sit" or "list".

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build and test admin-panel
  • GitHub Check: execute via buck2
  • GitHub Check: Migrate Mongodb
  • GitHub Check: Check SDLs
  • GitHub Check: execute via bats
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (6)
apps/admin-panel/components/invitations/invitation-card.tsx (1)

15-55: LGTM!

The component correctly handles nullable editableContent with optional chaining for both the preview display and the editing form inputs.

apps/admin-panel/app/globals.css (1)

55-59: LGTM!

The CSS rule provides a clean mechanism for full-screen modal views to hide the sidebar, with a clear explanatory comment.

apps/admin-panel/components/invitations/change-status-modal.tsx (1)

40-50: LGTM!

Good modal structure with accessible backdrop click-to-close and clear header with close button.

apps/admin-panel/app/invitations/[id]/page.tsx (3)

45-59: LGTM!

State initialization is well-structured with proper typing, and the dynamic route parameter handling correctly accounts for Next.js potentially returning an array.


25-43: Verify duplication with existing date formatting utilities.

The review suggests these formatDate and formatDateTime functions may duplicate a formatDateDisplay utility in apps/admin-panel/app/utils.ts. Confirm whether this utility exists, compare implementations, and consolidate if the functions are redundant.


6-16: Duplicate EditableContent type definition.

EditableContent is imported from ../types on line 6, but then redefined locally on lines 13-16. Remove the local definition to avoid redundancy and potential drift between definitions.

 import type { Event, InvitationRow, TemplateRow, InvitationStatus } from "../types"
+import type { EditableContent } from "../types"
 
 import { InvitationCard } from "../../../components/invitations/invitation-card"
 import { ClientInfoCard } from "../../../components/invitations/client-info-card"
 import { StatusHistoryCard } from "../../../components/invitations/status-history-card"
 import { ChangeStatusModal } from "../../../components/invitations/change-status-modal"
 
-type EditableContent = {
-  title: string
-  body: string
-} | null
-
 const InvitationStatuses = {

Likely an incorrect or invalid review comment.

Comment on lines 18 to 23
const InvitationStatuses = {
pending: { label: "Pending", color: "bg-yellow-100 text-yellow-800" },
accepted: { label: "Accepted", color: "bg-green-100 text-green-800" },
revoked: { label: "Revoked", color: "bg-red-100 text-red-800" },
active: { label: "Active", color: "bg-red-100 text-red-800" },
} as const
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

"Active" status uses the same color as "Revoked".

Line 22 assigns bg-red-100 text-red-800 to "active", which is identical to "revoked". Active status typically represents a positive state and should use a different color (e.g., blue).

 const InvitationStatuses = {
   pending: { label: "Pending", color: "bg-yellow-100 text-yellow-800" },
   accepted: { label: "Accepted", color: "bg-green-100 text-green-800" },
   revoked: { label: "Revoked", color: "bg-red-100 text-red-800" },
-  active: { label: "Active", color: "bg-red-100 text-red-800" },
+  active: { label: "Active", color: "bg-blue-100 text-blue-800" },
 } as const
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const InvitationStatuses = {
pending: { label: "Pending", color: "bg-yellow-100 text-yellow-800" },
accepted: { label: "Accepted", color: "bg-green-100 text-green-800" },
revoked: { label: "Revoked", color: "bg-red-100 text-red-800" },
active: { label: "Active", color: "bg-red-100 text-red-800" },
} as const
const InvitationStatuses = {
pending: { label: "Pending", color: "bg-yellow-100 text-yellow-800" },
accepted: { label: "Accepted", color: "bg-green-100 text-green-800" },
revoked: { label: "Revoked", color: "bg-red-100 text-red-800" },
active: { label: "Active", color: "bg-blue-100 text-blue-800" },
} as const
🤖 Prompt for AI Agents
In apps/admin-panel/app/invitations/[id]/page.tsx around lines 18 to 23, the
"active" status is using the same red color as "revoked"; update the "active"
entry to a positive blue variant (for example "bg-blue-100 text-blue-800") so
its styling differs and correctly conveys a positive/active state, preserving
the existing object shape and const assertion.

Comment on lines 204 to 212
<div className="grid gap-6 lg:grid-cols-3">
<ClientInfoCard invitation={invitation} />

<StatusHistoryCard
events={events}
getActor={getActor}
formatDate={formatDate}
/>
</div>
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

Grid column count doesn't match children count.

lg:grid-cols-3 creates a 3-column layout, but only 2 children are rendered (ClientInfoCard and StatusHistoryCard). This leaves an empty column. Consider using lg:grid-cols-2 or verify if a third card is intended.

-        <div className="grid gap-6 lg:grid-cols-3">
+        <div className="grid gap-6 lg:grid-cols-2">
           <ClientInfoCard invitation={invitation} />
 
           <StatusHistoryCard
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="grid gap-6 lg:grid-cols-3">
<ClientInfoCard invitation={invitation} />
<StatusHistoryCard
events={events}
getActor={getActor}
formatDate={formatDate}
/>
</div>
<div className="grid gap-6 lg:grid-cols-2">
<ClientInfoCard invitation={invitation} />
<StatusHistoryCard
events={events}
getActor={getActor}
formatDate={formatDate}
/>
</div>
🤖 Prompt for AI Agents
In apps/admin-panel/app/invitations/[id]/page.tsx around lines 204–212 the grid
is declared with lg:grid-cols-3 but only two child components are rendered,
leaving an empty column; change the grid class to lg:grid-cols-2 (or add the
intended third card) so the column count matches the number of children — update
the className on the wrapping div to use lg:grid-cols-2 unless you actually
intend to render a third component, in which case add that component in the same
div.

Comment on lines 32 to 36
setSendPush(found.sendPush ?? true)
setAddToHistory(found.addToHistory ?? true)
setAction((found as any).action ?? "Open Deep Link")
setDeepLinkScreen((found as any).deepLinkScreen ?? "Wallet")
setDeepLinkAction((found as any).deepLinkAction ?? "None")
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid as any casts; extend TemplateRow type instead.

The code accesses properties (action, deepLinkScreen, deepLinkAction) that don't exist on TemplateRow. Instead of using as any, consider extending the type definition to include these optional properties.

In apps/admin-panel/app/invitations/types.ts, extend TemplateRow:

export type TemplateRow = {
  id: string
  name: string
  language: string
  icon: TemplateIcon
  title: string
  body: string
  sendPush?: boolean
  addToHistory?: boolean
  action?: string
  deepLinkScreen?: string
  deepLinkAction?: string
}

Then update the code:

-      setAction((found as any).action ?? "Open Deep Link")
-      setDeepLinkScreen((found as any).deepLinkScreen ?? "Wallet")
-      setDeepLinkAction((found as any).deepLinkAction ?? "None")
+      setAction(found.action ?? "Open Deep Link")
+      setDeepLinkScreen(found.deepLinkScreen ?? "Wallet")
+      setDeepLinkAction(found.deepLinkAction ?? "None")
🤖 Prompt for AI Agents
In apps/admin-panel/app/templates/[id]/edit/page.tsx around lines 32-36, the
code uses `as any` to access action, deepLinkScreen, and deepLinkAction which
are missing from TemplateRow; extend the TemplateRow type in
apps/admin-panel/app/invitations/types.ts to add optional fields action?:
string, deepLinkScreen?: string, deepLinkAction?: string (keep sendPush? and
addToHistory? if not present), then update imports so the component uses the
typed TemplateRow and remove the `as any` casts, keeping the existing nullish
defaults (e.g. ?? "Open Deep Link") when setting state.

Comment on lines 155 to 163
<label className="flex items-center gap-2">
<input
type="checkbox"
checked={false}
onChange={() => {}}
className="h-4 w-4 rounded"
/>
<span className="text-sm text-gray-700">Add to Bulletin</span>
</label>
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

"Add to Bulletin" checkbox is non-functional.

Similar to the Icon select, this checkbox has checked={false} and an empty onChange, making it non-interactive. Either implement the state management or remove the control until it's needed.

🤖 Prompt for AI Agents
In apps/admin-panel/app/templates/[id]/edit/page.tsx around lines 155-163, the
"Add to Bulletin" checkbox is hard-coded with checked={false} and an empty
onChange, so it is non-interactive; fix by wiring it to component state (e.g.,
create a useState boolean, use that state for checked, and update it in
onChange) or remove the input until it's needed; if you keep it, also ensure the
bulletin flag is included in the form submission/update handler so the backend
receives the value.

Comment on lines 184 to 193
<select
className="mt-1 block w-full rounded-md border border-gray-200 px-3 py-2"
value={template.icon}
onChange={() => {}}
>
<option value="star">★ Star</option>
<option value="check">✓ Check</option>
<option value="bell">🔔 Bell</option>
</select>
</div>
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

Icon select is non-functional.

The Icon select displays template.icon but the onChange handler is empty, so users cannot change the icon. Either wire up state management or mark the field as read-only.

+  const [icon, setIcon] = useState(template?.icon || "star")
+
+  // In useEffect where template is loaded:
+  setIcon(found.icon)
+
+  // In the select:
                 <select
                   className="mt-1 block w-full rounded-md border border-gray-200 px-3 py-2"
-                  value={template.icon}
-                  onChange={() => {}}
+                  value={icon}
+                  onChange={(e) => setIcon(e.target.value)}
                 >

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/admin-panel/app/templates/[id]/edit/page.tsx around lines 184 to 193 the
<select> is controlled by template.icon but the onChange is a no-op, making the
icon non-functional; update the handler to either set the new icon into
component state or props (e.g., read the event.target.value and call the
existing setTemplate/updateTemplate function to set template.icon) or explicitly
mark the field readOnly/disabled if it must not be editable; ensure you handle
the event type correctly and preserve other template fields when updating.

Comment on lines 71 to 80
<td className="px-6 py-4 text-gray-700">
{template.icon === TemplateIcon.Star ? (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-yellow-500">
</span>
) : (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-green-600">
</span>
)}
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

Missing handling for "bell" icon type.

The icon rendering only handles "star" explicitly; all other icons default to the checkmark. Add explicit handling for the "bell" icon defined in TemplateIcon.

                 <td className="px-6 py-4 text-gray-700">
                   {template.icon === TemplateIcon.Star ? (
                     <span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-yellow-500">
                       ★
                     </span>
+                  ) : template.icon === TemplateIcon.Bell ? (
+                    <span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-blue-500">
+                      🔔
+                    </span>
                   ) : (
                     <span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-green-600">
                       ✓
                     </span>
                   )}
                 </td>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<td className="px-6 py-4 text-gray-700">
{template.icon === TemplateIcon.Star ? (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-yellow-500">
</span>
) : (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-green-600">
</span>
)}
<td className="px-6 py-4 text-gray-700">
{template.icon === TemplateIcon.Star ? (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-yellow-500">
</span>
) : template.icon === TemplateIcon.Bell ? (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-blue-500">
🔔
</span>
) : (
<span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-gray-300 text-green-600">
</span>
)}
</td>
🤖 Prompt for AI Agents
In apps/admin-panel/app/templates/page.tsx around lines 71 to 80, the JSX only
checks for TemplateIcon.Star and otherwise renders a checkmark, so
TemplateIcon.Bell is not handled; add an explicit conditional branch for
TemplateIcon.Bell (rendering the bell glyph/icon with its own styling and
classes) between the Star and default branches so the bell icon displays
correctly, and keep the existing checkmark as the fallback for any other/unknown
icon values.

Comment on lines 136 to 139
<input
placeholder="e.g., Weekly Digest"
className="mt-1 block w-full rounded-md border border-gray-200 px-3 py-2"
/>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Create modal form inputs are uncontrolled.

The Template Name, Title, and Body inputs have no value or onChange handlers, so the entered data cannot be captured when "Save Template" is clicked. Add state management for these fields.

+  const [createName, setCreateName] = useState("")
+  const [createTitle, setCreateTitle] = useState("")
+  const [createBody, setCreateBody] = useState("")

   // Then in the JSX:
                   <input
                     placeholder="e.g., Weekly Digest"
+                    value={createName}
+                    onChange={(e) => setCreateName(e.target.value)}
                     className="mt-1 block w-full rounded-md border border-gray-200 px-3 py-2"
                   />

Also applies to: 230-244

🤖 Prompt for AI Agents
In apps/admin-panel/app/templates/page.tsx around lines 136-139 and 230-244, the
modal form inputs for Template Name, Title, and Body are uncontrolled and their
values are not captured on submit; add React state (useState) for each field,
set each input/textarea's value to the corresponding state variable and wire an
onChange handler to update state, and then read those state variables in the
"Save Template" handler (or pass them to the existing save function). Initialize
state to empty strings (or sensible defaults), ensure types if using TypeScript,
and if the modal is a child component either lift state up or expose
callbacks/props so the parent can receive the final values on save.

onAddHistoryChange,
onSave,
}: ChangeStatusModalProps) {
const [addToBulletin, setAddToBulletin] = useState(false)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

addToBulletin state is not communicated to parent on save.

The addToBulletin checkbox updates local state but this value is never passed back to the parent component via onSave or a callback. Either add an onAddToBulletinChange prop similar to the other toggle callbacks, or include addToBulletin in an extended onSave signature.

Option 1 - Add callback prop:

 interface ChangeStatusModalProps {
   // ...existing props...
   onAddHistoryChange: (checked: boolean) => void
+  onAddToBulletinChange: (checked: boolean) => void
   onSave: () => void
 }

Option 2 - Include in save:

-  onSave: () => void
+  onSave: (options: { addToBulletin: boolean }) => void

-  onClick={onSave}
+  onClick={() => onSave({ addToBulletin })}

Also applies to: 122-130

🤖 Prompt for AI Agents
In apps/admin-panel/components/invitations/change-status-modal.tsx around lines
33 and 122-130, the local state addToBulletin is toggled but never communicated
to the parent when saving; update the component API and save flow to surface
this value. Either add a new prop onAddToBulletinChange: (value: boolean) =>
void and call it whenever addToBulletin changes, or extend the existing onSave
signature to accept an options object (e.g., onSave({ status, addToBulletin }))
and pass addToBulletin in the save handler; update the parent call sites to
handle the new callback/argument and bump prop types accordingly.

<td className="py-2 text-gray-900">{invitation.id}</td>
</tr>
<tr>
<td className="py-2 font-medium tex-gray-700">LN Address</td>
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

Typo in className: tex-gray-700 should be text-gray-700.

The missing "t" will prevent the text color styling from being applied to the "LN Address" label.

-            <td className="py-2 font-medium tex-gray-700">LN Address</td>
+            <td className="py-2 font-medium text-gray-700">LN Address</td>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<td className="py-2 font-medium tex-gray-700">LN Address</td>
<td className="py-2 font-medium text-gray-700">LN Address</td>
🤖 Prompt for AI Agents
In apps/admin-panel/components/invitations/client-info-card.tsx around line 19,
the className on the "LN Address" table cell has a typo "tex-gray-700"
preventing the text color style from applying; change it to "text-gray-700" so
Tailwind applies the intended color (i.e., update the className string to
include the missing "t").

Comment on lines 28 to 32
events
.sort(
(a, b) =>
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
)
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

Avoid mutating the events prop array.

Array.sort() mutates the array in place. Since events is a prop, mutating it can cause unexpected side effects in the parent component.

             {events.length > 0 ? (
-              events
-                .sort(
+              [...events]
+                .sort(
                   (a, b) =>
                     new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
                 )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
events
.sort(
(a, b) =>
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
)
[...events]
.sort(
(a, b) =>
new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime(),
)
🤖 Prompt for AI Agents
In apps/admin-panel/components/invitations/status-history-card.tsx around lines
28 to 32, the code calls events.sort(...) which mutates the events prop; make a
non-mutating copy before sorting (e.g., use [...events] or events.slice()) and
sort that copy instead, or compute the sorted array in a useMemo to avoid
re-sorting on every render.

Copy link

@littledino2112 littledino2112 left a comment

Choose a reason for hiding this comment

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

  • The PR just has UI code, there's no integration with the backend yet (i.e. doesn't have any call to notifications service to send out invitations)
  • There's added function for 'status' tracking for invitation but unclear to me how this will be implemented. For example, you have a 'revoked' status. Afaik, it's not possible to revoke a push notification that was already sent.
    Can you elaborate on what this status tracking is? What kind of backend api you'll need for this?

@tiankii tiankii changed the title feat: visa card invitations feat: card invitations Dec 10, 2025
@tiankii tiankii changed the title feat: card invitations feat: blink card invitations Dec 10, 2025
@tiankii
Copy link
Author

tiankii commented Dec 10, 2025

  • The PR just has UI code, there's no integration with the backend yet (i.e. doesn't have any call to notifications service to send out invitations)
  • There's added function for 'status' tracking for invitation but unclear to me how this will be implemented. For example, you have a 'revoked' status. Afaik, it's not possible to revoke a push notification that was already sent.
    Can you elaborate on what this status tracking is? What kind of backend api you'll need for this?

PR for Backend:
notification message templates
#356

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: 2

♻️ Duplicate comments (1)
apps/admin-panel/app/mock-data.ts (1)

128-137: Pipeline failure: Add German words to typos allowlist.

The spell checker is flagging valid German words "ist" and "Sie" as typos. Add these words to typos.toml to fix the pipeline failure:

[default.extend-words]
ist = "ist"
Sie = "Sie"
🧹 Nitpick comments (3)
apps/admin-panel/app/mock-data.ts (1)

5-28: Remove unused import and consider extending NotificationContent.

NotificationContent is imported but never used. Additionally, InvitationContent duplicates nearly all fields from NotificationContent, only adding id. Consider:

 import { InvitationRow, TemplateRow } from "./invitations/types"
 import {
   NotificationContent,
   LocalizedNotificationContent,
 } from "../components/notification/builder"
-import { NotificationIcon, DeepLinkScreen, DeepLinkAction } from "../generated"
-import { NotificationAction } from "../components/notification/types"

-export type InvitationContent = {
-  id: string
-  localizedNotificationContents: LocalizedNotificationContent[]
-  action?: NotificationAction
-  openDeepLink?: {
-    screen?: DeepLinkScreen | undefined
-    action?: DeepLinkAction | undefined
-  }
-  openExternalUrl?: {
-    url: string
-  }
-  icon?: NotificationIcon | undefined
-  shouldSendPush: boolean
-  shouldAddToHistory: boolean
-  shouldAddToBulletin: boolean
-}
+export type InvitationContent = NotificationContent & { id: string }

Given this is temporary mock data, this is optional.

apps/admin-panel/app/invitations/new/page.tsx (2)

83-122: Fix function naming and event type signature.

  1. InvitationSender uses PascalCase (reserved for components/classes). Use camelCase: handleSendInvitation or sendInvitation.

  2. The function expects React.FormEvent but is called from Button.onClick which passes React.MouseEvent. While both have preventDefault(), the types don't match.

-  const InvitationSender = async (e: React.FormEvent) => {
+  const handleSendInvitation = async (e: React.MouseEvent<HTMLButtonElement>) => {
     e.preventDefault()
     // ...
   }

And update the button:

-          <Button onClick={InvitationSender} disabled={!canSend}>
+          <Button onClick={handleSendInvitation} disabled={!canSend}>

235-243: Consider post-success UX: reset form or redirect.

After a successful send, the success message displays but the form retains its values. Users sending multiple invitations would need to manually clear the form. Consider:

  1. Resetting form state after success, or
  2. Redirecting to the invitations list, or
  3. Adding a "Send Another" button
if (res.success) {
  setSuccess(true)
  // Option 1: Reset form
  setUserQuery("")
  setTemplateId("")
  // Option 2: Redirect after delay
  setTimeout(() => router.push("/invitations"), 1500)
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea63427 and c2fde17.

📒 Files selected for processing (4)
  • apps/admin-panel/app/invitations/new/page.tsx (1 hunks)
  • apps/admin-panel/app/invitations/types.ts (1 hunks)
  • apps/admin-panel/app/mock-data.ts (1 hunks)
  • apps/admin-panel/components/notification/builder.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/admin-panel/app/invitations/types.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/admin-panel/app/mock-data.ts (2)
apps/admin-panel/components/notification/builder.tsx (1)
  • LocalizedNotificationContent (339-343)
apps/admin-panel/app/invitations/types.ts (2)
  • InvitationRow (3-8)
  • TemplateRow (29-39)
🪛 GitHub Actions: Spelling
apps/admin-panel/app/mock-data.ts

[warning] 132-132: ist should be is, it, its, sit, or list (typo rule)


[warning] 133-133: Sie should be Size, Sigh, or Side (typo rule)


[warning] 208-208: ist should be is, it, its, sit, or list (typo rule)


[warning] 209-209: Sie should be Size, Sigh, or Side (typo rule)


[error] 132-132: ist should be is, it, its, sit, or list


[error] 133-133: Sie should be Size, Sigh, or Side


[error] 208-208: ist should be is, it, its, sit, or list


[error] 209-209: Sie should be Size, Sigh, or Side

🪛 GitHub Check: Spell Check with Typos
apps/admin-panel/app/mock-data.ts

[warning] 209-209:
"Sie" should be "Size" or "Sigh" or "Side".


[warning] 208-208:
"ist" should be "is" or "it" or "its" or "sit" or "list".


[warning] 133-133:
"Sie" should be "Size" or "Sigh" or "Side".


[warning] 132-132:
"ist" should be "is" or "it" or "its" or "sit" or "list".

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build and test admin-panel
  • GitHub Check: execute via buck2
  • GitHub Check: execute via bats
  • GitHub Check: Check SDLs
  • GitHub Check: Migrate Mongodb
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
apps/admin-panel/components/notification/builder.tsx (1)

339-343: LGTM!

Exporting LocalizedNotificationContent is appropriate as it enables reuse in other modules like the mock data file.

apps/admin-panel/app/mock-data.ts (2)

30-91: LGTM!

Mock invitation data provides good coverage of different statuses for development testing.


161-164: String literals are type-safe in this context and do not require refactoring.

The enums are defined as const objects with string literal values (DeepLinkScreen = { Chat: 'CHAT', ... } and NotificationIcon = { Bell: 'BELL', ... }). The TypeScript type system treats this as a union of string literal types, which means screen: "CHAT" and icon: "BELL" are already validated at compile time. Using string literals directly provides the same type safety as referencing the enum members (e.g., DeepLinkScreen.Chat), and both approaches are valid. If the underlying enum values change, TypeScript will surface errors either way.

apps/admin-panel/app/invitations/new/page.tsx (1)

40-58: Only the first localization is preserved when modifying templates.

The invitationTemplate construction only keeps localizedNotificationContents[0], discarding any additional localizations from the selected template. If templates support multiple languages, this would result in data loss.

If single-language invitations are intentional, consider adding a comment. Otherwise, consider preserving all localizations while allowing edits to the displayed one.

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: 0

🧹 Nitpick comments (3)
apps/admin-panel/app/invitations/new/page.tsx (3)

93-93: Consider using camelCase for function names.

The function name InvitationSender uses PascalCase, which is typically reserved for React component names. For consistency with JavaScript/TypeScript conventions, consider renaming to camelCase (e.g., handleInvitationSubmit or sendInvitation).

-  const InvitationSender = async (e: React.FormEvent) => {
+  const handleInvitationSubmit = async (e: React.FormEvent) => {

Don't forget to update the reference at line 277:

-          <Button onClick={InvitationSender} disabled={!canSend}>
+          <Button onClick={handleInvitationSubmit} disabled={!canSend}>

93-94: Wrap form fields in a <form> element for better semantics.

The handler is typed as React.FormEvent but is called from a Button's onClick (which produces a MouseEvent). While preventDefault() works on both event types, wrapping the form fields in a <form> element and using onSubmit provides better semantic HTML, native form validation support, and proper keyboard accessibility (e.g., Enter key submission).

Example refactor:

-      <div className="mx-auto max-w-2xl space-y-6">
+      <form onSubmit={handleInvitationSubmit} className="mx-auto max-w-2xl space-y-6">
         <div className="rounded-lg border border-gray-200 bg-white p-6">
           {/* form fields */}
         </div>
         
         <div className="flex justify-start">
-          <Button onClick={InvitationSender} disabled={!canSend}>
+          <Button type="submit" disabled={!canSend}>
             <span className="mr-2">➤</span>
             <span>Send Invite</span>
           </Button>
         </div>
         {/* feedback messages */}
-      </div>
+      </form>

Also applies to: 159-281


163-169: Add aria-label to the close button for accessibility.

The close button lacks a text label, making it unclear to screen reader users. Adding an aria-label improves accessibility.

         <button
           type="button"
           onClick={() => router.back()}
           className="inline-flex h-9 w-9 items-center justify-center rounded-full border border-gray-300 text-gray-500 hover:bg-gray-50"
+          aria-label="Close"
         >
           ✕
         </button>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2fde17 and 56650fb.

📒 Files selected for processing (2)
  • apps/admin-panel/app/invitations/new/page.tsx (1 hunks)
  • apps/admin-panel/app/invitations/types.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/admin-panel/app/invitations/types.ts
🧰 Additional context used
🧬 Code graph analysis (1)
apps/admin-panel/app/invitations/new/page.tsx (4)
apps/admin-panel/app/invitations/types.ts (2)
  • FormState (54-62)
  • SubmitState (64-68)
apps/admin-panel/app/mock-data.ts (1)
  • notificationContentMock (151-237)
apps/admin-panel/components/notification/notification-actions.tsx (1)
  • userIdByUsername (127-157)
apps/admin-panel/components/shared/form-controls/form-controls.tsx (4)
  • TextInput (11-14)
  • SelectInput (16-23)
  • TextArea (25-28)
  • Checkbox (30-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: build and test admin-panel
  • GitHub Check: execute via bats
  • GitHub Check: Migrate Mongodb
  • GitHub Check: Check SDLs
  • GitHub Check: execute via buck2
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (2)
apps/admin-panel/app/invitations/new/page.tsx (2)

41-91: Well-structured computed values and synchronization logic.

The memoized values (selectedTemplate, invitationTemplate, canSend) are efficiently implemented with correct dependencies. The useEffect properly synchronizes the selected template's content and flags into the form state, clearing fields when no template is selected. This approach keeps the UI responsive and prevents unnecessary recalculations.


100-153: Comprehensive error handling and validation.

The submission flow properly handles multiple failure scenarios:

  • User not found or invalid username
  • Missing template selection
  • API call failures
  • Unexpected errors

Each path correctly updates the submit state and provides actionable error messages to the user.

dolcalmi and others added 3 commits December 11, 2025 16:54
* refactor(core): make network fee mandatory

* fix: intraledger withdrawal fee calc test

* fix: cmd substitution error handling
…3e68dc0b9c9577214154d4a7db5d70aa5dcfc4a0de193af9184f4
…linkbitcoin#350)

* fix(core): use transaction fee when sender is exempt from bank fees

* test: add test to cover bank fee exemption

* fix: use ZERO_SATS const
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants