Skip to content

Display referral and group moves changelog#3427

Merged
steven-tey merged 4 commits intomainfrom
group-move-changelog-new
Feb 6, 2026
Merged

Display referral and group moves changelog#3427
steven-tey merged 4 commits intomainfrom
group-move-changelog-new

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Feb 6, 2026

Summary by CodeRabbit

  • New Features

    • Full activity log UI: feed, items, renderers, chips, descriptions, sections, partner group history sheet with "View history" button.
    • New hook to fetch activity logs and three new UI icons.
  • Bug Fixes / Improvements

    • Activity logs: nullable user support, richer schemas/types, and new referral-status → activity-action mapping.
    • Workflow events and partner-related triggers now include workspace context.
    • Expanded cache invalidation after referral status changes.

@vercel
Copy link
Contributor

vercel bot commented Feb 6, 2026

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

Project Deployment Actions Updated (UTC)
dub Ready Ready Preview Feb 6, 2026 3:07am

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

📝 Walkthrough

Walkthrough

Adds workspaceId to workflow identity payloads across multiple conversion/webhook/workflow flows; introduces activity-log schemas/types, API, SWR hook and a full frontend activity-log UI; makes ActivityLog.user optional in Prisma; updates partner/group move and partner-link generation logic; adds new icon assets.

Changes

Cohort / File(s) Summary
Workflow identity updates
apps/web/app/(ee)/api/stripe/integration/webhook/checkout-session-completed.ts, apps/web/app/(ee)/api/stripe/integration/webhook/invoice-paid.ts, apps/web/app/(ee)/api/stripe/integration/webhook/utils/create-new-customer.ts, apps/web/lib/api/conversions/track-lead.ts, apps/web/lib/api/conversions/track-sale.ts, apps/web/lib/integrations/shopify/create-sale.ts, apps/web/lib/actions/partners/create-manual-commission.ts, apps/web/lib/partners/create-partner-commission.ts, apps/web/app/(ee)/api/workflows/partner-approved/route.ts
Injected workspaceId into identity objects passed to executeWorkflows for partner/lead/sale/commission flows.
Activity-log types & schemas
apps/web/lib/types.ts, apps/web/lib/zod/schemas/activity-log.ts, apps/web/lib/api/activity-log/build-change-set.ts
Added activity-log Zod schemas and public types (GetActivityLogsQuery, ActivityLogAction, FieldDiff, ChangeSet, ActivityLog); made changeSet and user nullable; centralized ChangeSet type in lib/types.
API & SWR hook
apps/web/lib/swr/use-activity-logs.ts, apps/web/lib/api/activity-logs/route.ts, apps/web/api/activity-logs/route.ts
New useActivityLogs hook; activity-logs GET now accepts optional action, derives programId, and filters by programId/resourceType/resourceId/action.
Activity-log UI system
apps/web/ui/activity-logs/* (activity-entry-chips.tsx, activity-feed.tsx, activity-log-description.tsx, activity-log-registry.tsx, action-renderers/*, *activity-item.tsx, *activity-section.tsx, partner-group-history-sheet.tsx, etc.)
New frontend activity-log subsystem: chips, renderer components, registry, feed/items/sections, history sheet + hook and skeletons; registry maps actions to renderers and icons.
Referral mapping & update
apps/web/lib/referrals/constants.ts, apps/web/lib/actions/referrals/update-referral-status.ts
Added REFERRAL_STATUS_TO_ACTIVITY_ACTION mapping and replaced prior inline mapping usage in referral status updates.
Partner/group workflow adjustments
apps/web/lib/api/groups/move-partners-to-group.ts, apps/web/lib/api/workflows/execute-move-group-workflow.ts, apps/web/ui/partners/partner-info-cards.tsx
Made userId nullable in move flow, added resolution to workspace owner when missing, removed owner-fetch guard in executor, and integrated partner group history UI into partner cards.
Referral UI & cache invalidation
apps/web/ui/referrals/partner-referral-sheet.tsx, apps/web/ui/modals/confirm-referral-status-change-modal.tsx
Added referral activity section to referral sheet; on confirm-referral-status success, mutatePrefix now invalidates both referrals and activity-logs endpoints.
DB schema change
packages/prisma/schema/activity.prisma
Made userId and its user relation optional on ActivityLog to support system-origin logs.
Icons
packages/ui/src/icons/file-pen.tsx, packages/ui/src/icons/file-send.tsx, packages/ui/src/icons/user-clock.tsx, packages/ui/src/icons/index.tsx
Added FilePen, FileSend, and UserClock SVG icon components and re-exported them.
Partner link generation & cron
apps/web/app/(ee)/api/cron/groups/remap-default-links/route.ts, apps/web/lib/api/partners/generate-partner-link.ts
Made userId optional in inputs and call sites; route coerces userId ?? undefined when generating links; generatePartnerLink accepts optional userId.
Misc / small fixes
apps/web/lib/api/conversions/*, apps/web/ui/partners/groups/.../earnings-calculator-block-modal.tsx, apps/web/app/.../use-commission-filters.tsx
Minor typing adjustments, import reorder/cleanup, and small syntax/format tweaks.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client as Client (UI)
  participant Hook as useActivityLogs (frontend)
  participant API as Server /api/activity-logs
  participant DB as Database (ActivityLog)

  Client->>Hook: mount & request logs (query)
  Hook->>API: GET /api/activity-logs?programId=...&resourceType=...&resourceId=...&action=...
  API->>DB: query ActivityLog (filters: programId, resourceType, resourceId, action)
  DB-->>API: return logs
  API-->>Hook: JSON logs
  Hook-->>Client: activityLogs (render)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

🐰 I hopped through branches, tidy and spry,
Poked workspaceIds where metrics fly,
Logs now bloom with chips and light,
Sheets unfold to show the flight,
Hop—history hums, and code feels spry 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Display referral and group moves changelog' is clear and specific, describing the main feature addition—a changelog UI for referral and group move activity logs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch group-move-changelog-new

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.

Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In `@apps/web/lib/api/groups/move-partners-to-group.ts`:
- Around line 147-158: Wrap the call to getWorkspaceUsers in a try/catch so any
thrown error does not abort the rest of the function: attempt to fetch users to
populate workspaceUserId (when userId is falsy) inside a try block, on success
keep the existing logic (assign users[0].id if present), and on failure catch
the error, log it, and leave workspaceUserId as null so downstream tasks
(Promise.allSettled calling recordLink, notifyPartnerGroupChange,
trackActivityLog, etc.) still run; ensure you reference the existing variables
and functions (workspaceUserId, userId, getWorkspaceUsers, Promise.allSettled,
recordLink, notifyPartnerGroupChange, trackActivityLog) when making this change.

In `@packages/ui/src/icons/user-clock.tsx`:
- Around line 1-32: The UserClock SVG hardcodes color values (`#525252`) on path
elements causing inconsistency with the icon set; update the component (function
UserClock) to use "currentColor" for all stroke and fill attributes (replace any
stroke="#525252" and fill="#525252" with stroke="currentColor" and
fill="currentColor") so parent CSS can control the icon color, ensuring all path
elements inside the returned <svg> use currentColor.
🧹 Nitpick comments (12)
apps/web/lib/integrations/shopify/create-sale.ts (1)

164-178: Nit: use property shorthand.

workspaceId: workspaceId can be simplified to workspaceId.

Proposed diff
          identity: {
-            workspaceId: workspaceId,
+            workspaceId,
             programId: link.programId,
apps/web/app/api/activity-logs/route.ts (1)

28-31: No pagination support — hardcoded take: 100.

There's no cursor/offset-based pagination. If a resource accumulates more than 100 activity logs, older entries won't be retrievable. This may be acceptable for the current use case but could become limiting.

apps/web/ui/activity-logs/partner-group-activity-section.tsx (1)

24-32: Error state says "Please try again" but offers no retry action.

SWR will auto-revalidate on window focus, but consider adding a retry button using mutate from useActivityLogs to make the error state actionable — or simplify the text to just "Failed to load history."

apps/web/ui/activity-logs/action-renderers/partner-group-changed-renderer.tsx (1)

7-10: The old group in GroupChangeSet is declared but never used.

The interface defines both old and new, but only new is read (line 22). The UI shows "Moved to [new group]" without indicating which group the partner was moved from. If this is intentional (e.g., the old group is shown elsewhere or isn't needed), consider removing old from the interface to avoid confusion. Otherwise, consider displaying the origin group for richer context:

Moved from [old group] to [new group]

Also applies to: 19-28

apps/web/lib/swr/use-activity-logs.ts (1)

14-20: URLSearchParams may stringify undefined values.

If query is provided but resourceType or resourceId is undefined, new URLSearchParams(...) will produce keys like resourceType=undefined. The SWR key guard on Lines 22–26 prevents the fetch in this scenario, so it's harmless at runtime, but it's slightly misleading if you ever log or inspect searchParams during debugging.

Optional: guard URLSearchParams construction
  const searchParams = query
-    ? new URLSearchParams({
-        resourceType: query.resourceType,
-        resourceId: query.resourceId,
-        ...(query.action && { action: query.action }),
-      }).toString()
+    && query.resourceType
+    && query.resourceId
+    ? new URLSearchParams({
+        resourceType: query.resourceType,
+        resourceId: query.resourceId,
+        ...(query.action && { action: query.action }),
+      }).toString()
     : "";
apps/web/ui/activity-logs/referral-activity-item.tsx (2)

41-48: Consider adding a dateTime attribute to the <time> element.

For semantic HTML and accessibility, the <time> element should include a machine-readable dateTime attribute (e.g., the ISO string from log.createdAt).

Proposed fix
-          <time className="hidden shrink-0 text-xs text-neutral-500 sm:block">
+          <time
+            dateTime={new Date(log.createdAt).toISOString()}
+            className="hidden shrink-0 text-xs text-neutral-500 sm:block"
+          >

13-53: Significant structural overlap with PartnerGroupActivityItem.

This component shares the same pattern as PartnerGroupActivityItem (icon + renderer + timestamp in a timeline layout). The differences are minor: responsive date visibility, ActivityLogDescription, and spacing. Consider extracting a shared ActivityTimelineItem base component that accepts children/slots for the content area and an optional description, to reduce duplication if more activity item types are introduced.

apps/web/ui/activity-logs/partner-group-activity-item.tsx (1)

39-46: Same dateTime attribute suggestion as referral-activity-item.tsx.

The <time> element here also lacks a dateTime attribute for semantic correctness.

Proposed fix
-        <time className="text-xs text-neutral-500">
+        <time
+          dateTime={new Date(log.createdAt).toISOString()}
+          className="text-xs text-neutral-500"
+        >
apps/web/lib/api/activity-log/track-activity-log.ts (1)

27-31: Filter logic now has action-specific bypasses — consider a more extensible approach.

The filter was extended to allow referral.created to bypass the non-empty changeSet requirement. This works, but if more actions without changeSets are added later, this condition will need to be updated each time.

A minor suggestion would be to use a set of actions that don't require changeSets:

Optional: use a set for exempted actions
+const ACTIONS_WITHOUT_CHANGESET: Set<ActivityLogAction> = new Set([
+  "referral.created",
+]);
+
 export const trackActivityLog = async (
   input: TrackActivityLogInput | TrackActivityLogInput[],
 ) => {
   let inputs = Array.isArray(input) ? input : [input];

   inputs = inputs.filter(
     (i) =>
-      i.action === "referral.created" ||
+      ACTIONS_WITHOUT_CHANGESET.has(i.action) ||
       (i.changeSet && Object.keys(i.changeSet).length > 0),
   );
apps/web/ui/partners/partner-info-cards.tsx (1)

85-89: Unnecessary activity-log fetch for non-enrolled (network) partners.

usePartnerGroupHistorySheet is invoked unconditionally, so it fires an API request for network partners too — even though the "View history" button and sheet are only used when isEnrolled. Consider gating the hook's partner argument:

- } = usePartnerGroupHistorySheet({ partner: partner || null });
+ } = usePartnerGroupHistorySheet({
+   partner: isEnrolled ? partner || null : null,
+ });

This avoids a wasted network round-trip for the network-partner view.

apps/web/ui/activity-logs/partner-group-history-sheet.tsx (1)

14-36: PartnerGroupHistorySheetContent receives setIsOpen but never uses it.

The type Omit<PartnerGroupHistorySheetProps, "isOpen"> includes setIsOpen, but the component only uses partner. Since the close button relies on Sheet.Close, the extra prop is harmless but imprecise.

🔧 Optional: tighten the prop type
-function PartnerGroupHistorySheetContent({
-  partner,
-}: Omit<PartnerGroupHistorySheetProps, "isOpen">) {
+function PartnerGroupHistorySheetContent({
+  partner,
+}: Pick<PartnerGroupHistorySheetProps, "partner">) {
apps/web/ui/activity-logs/activity-entry-chips.tsx (1)

63-74: Minor: Inconsistent fallback text between alt and display.

The alt attribute falls back to "User" (line 68) while the visible text falls back to "Unknown user" (line 71). Consider aligning them for consistency.

Proposed fix
-        alt={`${user.name || user.email || "User"}`}
+        alt={user.name || user.email || "Unknown user"}

- Activity log userId optional; workspaceId in workflow identity
- Activity logs API, registry, renderers (action-renderers)
- Partner group history sheet and activity section
- Referral activity section and status change renderers
- New icons: FilePen, FileSend, UserClock
- Activity log types in lib/types, zod schema updates
- Aligns branch with #3423 (group-move-changelog)

Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Contributor

@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

🤖 Fix all issues with AI agents
In `@apps/web/lib/api/groups/move-partners-to-group.ts`:
- Around line 162-171: The cron job body currently always sets userId to
workspaceUserId which can be null and will fail the route's z.string()
validation; update the qstash.publishJSON call in move-partners-to-group.ts so
the body only includes userId when workspaceUserId is non-null (e.g., build the
body object then conditionally add userId if workspaceUserId != null) or else
provide a guaranteed non-null fallback (like 'system'); alternatively, if you
prefer schema-side changes, update the cron route (route.ts) to accept userId as
optional (z.string().optional()) and ensure generatePartnerLink handles a
missing/undefined userId.

In `@apps/web/ui/activity-logs/activity-log-description.tsx`:
- Line 31: The paragraph element renders even when the nullable prop description
is null, leaving a vertical gap; update the ActivityLogDescription component to
only render the <p> element when description is non-null/non-empty (e.g., check
description != null and description !== '' or use a truthy check) so that the
element with className "text-content-subtle mt-2 text-sm" is not output when
description is absent; locate the JSX where description is used (the <p
className="text-content-subtle mt-2 text-sm">{description}</p>) and wrap or
replace it with a conditional render that returns nothing when description is
null/empty.
🧹 Nitpick comments (3)
apps/web/ui/activity-logs/action-renderers/referral-created-renderer.tsx (1)

8-12: Consider extracting the Label component to a shared module.

The Label helper is duplicated identically across three renderer files (referral-created-renderer.tsx, partner-group-changed-renderer.tsx, and referral-status-changed-renderer.tsx). Extracting it to activity-entry-chips.tsx or a dedicated shared utilities file would eliminate this duplication.

apps/web/lib/swr/use-activity-logs.ts (1)

15-22: searchParams is built eagerly with workspaceId! before the SWR key gates on it.

The workspaceId! non-null assertion on line 17 will produce "workspaceId=undefined" in the URLSearchParams string when workspaceId is null/undefined. This is harmless because the SWR key (lines 25–28) short-circuits to falsy when workspaceId is missing, so the string is never used as a fetch URL. Still, computing the params lazily (or guarding the construction) would be slightly cleaner.

♻️ Optional: guard searchParams construction
- const searchParams = query
-   ? new URLSearchParams({
-       workspaceId: workspaceId!,
-       resourceType: query.resourceType,
-       resourceId: query.resourceId,
-       ...(query.action && { action: query.action }),
-     }).toString()
-   : "";
+ const searchParams =
+   query && workspaceId
+     ? new URLSearchParams({
+         workspaceId,
+         resourceType: query.resourceType,
+         resourceId: query.resourceId,
+         ...(query.action && { action: query.action }),
+       }).toString()
+     : "";

Based on learnings, workspace authentication patterns in the Dub codebase prevent issues when workspaceId is missing, so the current code is safe in practice.

apps/web/ui/activity-logs/partner-group-history-sheet.tsx (1)

16-18: setIsOpen is passed to PartnerGroupHistorySheetContent but never used.

The Omit<…, "isOpen"> type includes setIsOpen, and the ...rest spread in PartnerGroupHistorySheet forwards it to the content component. However, the content component only uses partner — closing is handled by Sheet.Close. Consider narrowing the content component's props to just what it needs.

Proposed fix
-function PartnerGroupHistorySheetContent({
-  partner,
-}: Omit<PartnerGroupHistorySheetProps, "isOpen">) {
+function PartnerGroupHistorySheetContent({
+  partner,
+}: Pick<PartnerGroupHistorySheetProps, "partner">) {

And in PartnerGroupHistorySheet:

 export function PartnerGroupHistorySheet({
   isOpen,
-  ...rest
+  setIsOpen,
+  partner,
 }: PartnerGroupHistorySheetProps) {
   return (
-    <Sheet open={isOpen} onOpenChange={rest.setIsOpen}>
-      <PartnerGroupHistorySheetContent {...rest} />
+    <Sheet open={isOpen} onOpenChange={setIsOpen}>
+      <PartnerGroupHistorySheetContent partner={partner} />
     </Sheet>
   );
 }

@devkiran devkiran self-requested a review February 6, 2026 02:55
@steven-tey
Copy link
Collaborator Author

@coderabbitai full review, any breaking changes?

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 6, 2026

@steven-tey: I'll trigger a full review. Here are the breaking changes I've identified:

🔴 Breaking Changes

1. Database Schema Migration Required

File: packages/prisma/schema/activity.prisma

  • ActivityLog.userId changed from String to String? (nullable)
  • ActivityLog.user relation changed from User to User? (nullable)
  • Impact: Requires database migration before deployment

2. WorkflowIdentity Interface - Required Field Added

File: apps/web/lib/types.ts

  • Added required field workspaceId: string to WorkflowIdentity interface
  • Impact: All workflow executions now expect workspaceId in identity object. The PR updates all call sites, but any external code calling executeWorkflows will need updates.

3. API Parameter Type Changes

File: apps/web/lib/api/groups/move-partners-to-group.ts

  • MovePartnersToGroupParams.userId changed from string to string | null
  • Impact: Callers can now pass null for userId (will auto-resolve to workspace owner)

4. Zod Schema Changes

File: apps/web/lib/zod/schemas/activity-log.ts

  • activityLogSchema: both changeSet and user are now nullable
  • getActivityLogsQuerySchema: added optional action field
  • Impact: Code expecting these fields to always be present may need null checks

5. Type Export Relocations

Files: apps/web/lib/zod/schemas/activity-log.tsapps/web/lib/types.ts

  • Removed exports: ActivityLogResourceType and ActivityLogAction type aliases
  • Now exported from @/lib/types instead
  • Impact: External imports of these types need path updates (though the PR updates internal usage)

6. API Behavior Change

File: apps/web/app/api/activity-logs/route.ts

  • Route now filters by programId instead of workspaceId
  • Impact: Changes data scope for activity log queries

✅ Mitigation

Most breaking changes are internal and already handled in the PR. The main external concerns are:

  • Database migration must run before deployment
  • Any external consumers of WorkflowIdentity or executeWorkflows need updates
✅ Actions performed

Full review triggered.

Copy link
Contributor

@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: 1

Caution

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

⚠️ Outside diff range comments (1)
packages/prisma/schema/activity.prisma (1)

1-16: ⚠️ Potential issue | 🟠 Major

Add indexes on programId and createdAt for query performance.

The activity-logs API filters by programId and orders results by createdAt in descending order. Without indexes on these columns, queries will result in full table scans as the table grows. A composite index on programId and createdAt would significantly improve query performance.

Suggested index additions
   @@index([resourceType, resourceId])
+  @@index([programId, createdAt])
 }
🤖 Fix all issues with AI agents
In `@apps/web/ui/activity-logs/partner-group-activity-item.tsx`:
- Around line 44-51: formatDate is being called without the day option so
timestamps omit the day; update the formatDate calls that pass log.createdAt in
PartnerGroupActivityItem (partner-group-activity-item.tsx) and
ReferralActivityItem (referral-activity-item.tsx) to include day: "numeric" in
the options object so dates render like "Feb 6, 2026, 2:30 PM"; search for
formatDate(log.createdAt, {...}) in those components and add day: "numeric" to
the options.
🧹 Nitpick comments (10)
apps/web/lib/integrations/shopify/create-sale.ts (1)

164-178: Nit: use shorthand property.

workspaceId: workspaceId can be simplified to workspaceId, consistent with how programId and partnerId are used elsewhere in this codebase (e.g., line 394 in create-partner-commission.ts doesn't repeat programId: programId).

♻️ Suggested diff
          identity: {
-            workspaceId: workspaceId,
+            workspaceId,
             programId: link.programId,
             partnerId: link.partnerId,
           },
apps/web/app/api/activity-logs/route.ts (1)

20-23: Passing optional action directly into the where clause works but is implicit.

When action is undefined, Prisma silently ignores it. This is correct behavior, but for clarity you could use a conditional spread:

♻️ Optional: explicit conditional spread
     where: {
       programId,
       resourceType,
       resourceId,
-      action,
+      ...(action && { action }),
     },
apps/web/ui/activity-logs/action-renderers/partner-group-changed-renderer.tsx (1)

7-10: old field in GroupChangeSet is defined but never read.

The old group is part of the type but the renderer only uses new. If this is intentional (for future use or other renderers), that's fine — just noting it for awareness.

apps/web/app/(ee)/api/cron/groups/remap-default-links/route.ts (1)

251-258: error.message may throw if error is not an Error instance.

If a non-Error value is thrown (e.g., a string or object), accessing .message will return undefined. Consider using a safer pattern.

🛡️ Safer error message extraction
-      message: `Error creating default links for the partners: ${error.message}.`,
+      message: `Error creating default links for the partners: ${error instanceof Error ? error.message : String(error)}.`,
apps/web/ui/partners/partner-info-cards.tsx (1)

85-89: usePartnerGroupHistorySheet fires for network partners too.

The hook is called unconditionally, so the underlying useActivityLogs SWR request will fire even for type: "network" partners (who never see the "View history" button). This is harmless since SWR is lightweight, but you could pass partner: isEnrolled ? (partner || null) : null to skip the fetch entirely for network partners.

♻️ Optional: skip fetch for non-enrolled partners
   const {
     partnerGroupHistorySheet,
     setIsOpen: setGroupHistoryOpen,
     hasActivityLogs,
-  } = usePartnerGroupHistorySheet({ partner: partner || null });
+  } = usePartnerGroupHistorySheet({
+    partner: isEnrolled ? (partner || null) : null,
+  });
apps/web/ui/activity-logs/referral-activity-item.tsx (1)

46-53: Consider adding a dateTime attribute to the <time> element.

The <time> element renders a human-readable date but lacks the machine-readable dateTime attribute. This is a minor semantic HTML / accessibility improvement.

♻️ Add dateTime attribute
-          <time className="hidden shrink-0 text-xs text-neutral-500 sm:block">
+          <time
+            dateTime={log.createdAt}
+            className="hidden shrink-0 text-xs text-neutral-500 sm:block"
+          >
apps/web/lib/swr/use-activity-logs.ts (1)

15-22: workspaceId! non-null assertion runs before the SWR guard.

searchParams is built every render with workspaceId!, even when workspaceId is still undefined (workspace loading). The SWR key guard on line 26 prevents an actual fetch, so this isn't a runtime bug, but it produces a transient "workspaceId=undefined" string. Moving the construction inside the SWR key callback (or guarding it) would be cleaner.

Proposed fix
- const searchParams = query
-   ? new URLSearchParams({
-       workspaceId: workspaceId!,
-       resourceType: query.resourceType,
-       resourceId: query.resourceId,
-       ...(query.action && { action: query.action }),
-     }).toString()
-   : "";
-
  const { data, error, isLoading, mutate } = useSWR<ActivityLog[]>(
    enabled &&
      workspaceId &&
      query?.resourceType &&
-     query?.resourceId &&
-     `/api/activity-logs?${searchParams}`,
+     query?.resourceId &&
+     `/api/activity-logs?${new URLSearchParams({
+       workspaceId,
+       resourceType: query.resourceType,
+       resourceId: query.resourceId,
+       ...(query.action && { action: query.action }),
+     }).toString()}`,
    fetcher,
apps/web/ui/activity-logs/activity-log-registry.tsx (1)

16-27: referral.updated and reward.* actions have no icon or renderer entries.

These actions exist in activityLogActionSchema but aren't mapped here. If they can appear in the activity feed, they'll render the fallback CircleInfo icon and getActivityLogRenderer will return null. Just confirming this is intentional for now (perhaps future work).

apps/web/ui/activity-logs/partner-group-history-sheet.tsx (1)

16-39: setIsOpen is passed but unused in PartnerGroupHistorySheetContent.

The Omit<PartnerGroupHistorySheetProps, "isOpen"> type includes setIsOpen, which gets spread into this component but is never consumed — the close button works through Sheet.Close context instead. Not a bug, just a minor type surface nit.

Tighter type for the content component
 function PartnerGroupHistorySheetContent({
   partner,
-}: Omit<PartnerGroupHistorySheetProps, "isOpen">) {
+}: Pick<PartnerGroupHistorySheetProps, "partner">) {
apps/web/ui/activity-logs/activity-entry-chips.tsx (1)

71-82: Consider adding referrerPolicy to the avatar <img> to avoid leaking origin.

The avatar loads from a user-controlled URL (user.image) or the OG fallback. If user.image points to an external domain, the browser may send the full Referer header.

Minor hardening
       <img
         src={user.image || `${OG_AVATAR_URL}${user.id}`}
         alt={`${user.name || user.email || "User"}`}
         className="size-4 shrink-0 rounded-full"
+        referrerPolicy="no-referrer"
       />

@steven-tey steven-tey merged commit e88e89c into main Feb 6, 2026
10 of 11 checks passed
@steven-tey steven-tey deleted the group-move-changelog-new branch February 6, 2026 03:13
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