Skip to content

Conversation

@softmarshmallow
Copy link
Member

@softmarshmallow softmarshmallow commented Apr 20, 2025

  • export participants as csv
  • empty states
  • "unsaved changes" alert

Summary by CodeRabbit

  • New Features

    • Added CSV export functionality to the Referrers Table, allowing users to download referrer data.
    • Introduced a tabbed interface for campaign settings, with new sections and improved form feedback.
    • Enhanced campaign rewards page with structured cards and feature previews.
    • Added internationalization support for 404 expired link messages.
    • Implemented new loading and empty states for event overview and improved event data visualization.
    • Introduced a custom hook to warn users about unsaved changes when navigating away.
    • Added a new permanent redirect from /login to /sign-in.
  • Improvements

    • Enhanced invitation API responses to include ready-to-share invitation content (title, text, URL).
    • Updated sharing logic to use server-provided invitation content for consistency.
    • Improved sign-in flow with conditional redirects based on authentication state.
    • Extended campaign data structures to support additional web properties.
    • Modularized chart configuration and event total calculations for better maintainability.
    • Simplified unsaved changes warning integration in the editor component.
  • Bug Fixes

    • Streamlined unsaved changes warning logic for more consistent user experience.
  • Chores

    • Updated package manager version to pnpm@10.10.0.
    • Refined database migration for TimescaleDB extension handling.
    • Removed unused TimescaleDB-related function declarations from database types.

@codesandbox
Copy link

codesandbox bot commented Apr 20, 2025

Review or Edit in CodeSandbox

Open the branch in Web EditorVS CodeInsiders

Open Preview

@vercel
Copy link

vercel bot commented Apr 20, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
grida ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 5, 2025 0:40am
8 Skipped Deployments
Name Status Preview Comments Updated (UTC)
code ⬜️ Ignored (Inspect) May 5, 2025 0:40am
legacy ⬜️ Ignored (Inspect) May 5, 2025 0:40am
legacy-www ⬜️ Ignored (Inspect) May 5, 2025 0:40am
backgrounds ⬜️ Skipped (Inspect) May 5, 2025 0:40am
blog ⬜️ Skipped (Inspect) May 5, 2025 0:40am
docs ⬜️ Skipped (Inspect) May 5, 2025 0:40am
sites ⬜️ Skipped (Inspect) May 5, 2025 0:40am
viewer ⬜️ Skipped (Inspect) May 5, 2025 0:40am

@coderabbitai
Copy link

coderabbitai bot commented Apr 20, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

This update introduces a variety of enhancements and refactorings across the codebase. Key changes include the addition of new React hooks for unsaved changes warnings and CSV export, enriched API responses for invitation-related endpoints, and improved UI components for campaign management such as tabbed settings forms and event analytics with loading/empty states. The platform's public campaign type is extended with new properties, and the sharing logic is centralized to rely on server-generated data. Internationalization support is added to certain pages, and a new permanent redirect is configured. Several database function declarations related to TimescaleDB are removed, and the migration script is updated to conditionally enable the extension.

Changes

File(s) Change Summary
editor/app/(dev)/canvas/editor.tsx Replaced manual beforeunload event logic with useUnsavedChangesWarning hook; removed unused React imports.
editor/app/(dev)/canvas/examples/with-templates/002/page.tsx, editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/design/page.tsx Added www_name and www_route_path properties to campaign objects for template components.
editor/app/(site)/sign-in/page.tsx Added Supabase user check and conditional redirect logic based on authentication and environment variable; refactored imports.
editor/app/(tenant)/~/[tenant]/(r)/r/[slug]/t/not-found.tsx Converted NotFound to async, added locale detection and dynamic i18n for title/description.
editor/app/(tenant)/~/[tenant]/api/west/t/_utils/render-sharable.ts Added new renderSharable function to generate sharable invitation content.
editor/app/(tenant)/~/[tenant]/api/west/t/invite/route.ts, editor/app/(tenant)/~/[tenant]/api/west/t/refresh/route.ts Enhanced POST handlers to fetch and return nested invitation data and sharable content using renderSharable; updated response structure.
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/overview.tsx Extracted chart config and event totals helpers, added loading/empty states, and summary cards for event data.
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/referrers-table.tsx Added CSV export functionality with progress/error handling using new hook.
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/settings.tsx Refactored settings form into tabbed interface, enabled unsaved changes warning, and improved form UX.
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/rewards/page.tsx Replaced placeholder with structured cards listing reward types and features.
editor/database-generated.types.ts Removed numerous TimescaleDB-related function declarations; slightly generalized citext function argument types.
editor/hooks/use-unsaved-changes-warning.ts Added new hook to warn users of unsaved changes on navigation or reload.
editor/lib/platform/index.ts Extended CampaignPublic type with www_name and www_route_path; updated invite/refresh method signatures to include sharable data.
editor/next.config.mjs Added permanent redirect from /login to /sign-in.
editor/scaffolds/platform/data/use-export-csv.ts Introduced useExportCSV hook for paginated client-side CSV export with progress/error state.
editor/theme/templates/west-referral/referrer/page.tsx Refactored share logic to use API-provided sharable objects; removed internal share object builder.
package.json Updated pnpm version from 9.15.4 to 10.10.0.
supabase/migrations/20250323084830_grida_west.sql Changed TimescaleDB extension enabling to be conditional and use extensions schema.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant UI_Component
    participant useUnsavedChangesWarning
    participant Browser

    User->>UI_Component: Interact with form (make changes)
    UI_Component->>useUnsavedChangesWarning: Register isDirty callback
    User->>Browser: Attempt to navigate away or reload
    Browser->>useUnsavedChangesWarning: Trigger beforeunload/click event
    useUnsavedChangesWarning->>User: Show unsaved changes warning
    User-->>Browser: Confirm or cancel navigation
Loading
sequenceDiagram
    participant Client
    participant API (invite/refresh route)
    participant renderSharable
    participant Database

    Client->>API (invite/refresh route): POST request
    API (invite/refresh route)->>Database: Fetch invitation, campaign, templates, referrer
    API (invite/refresh route)->>renderSharable: Generate sharable content
    renderSharable-->>API (invite/refresh route): Sharable { title, text, url }
    API (invite/refresh route)-->>Client: Respond with { code, sharable }
Loading
sequenceDiagram
    participant User
    participant ReferrersTable
    participant useExportCSV
    participant Supabase

    User->>ReferrersTable: Click "Export CSV"
    ReferrersTable->>useExportCSV: Call exportToCSV()
    useExportCSV->>Supabase: Fetch paginated data
    Supabase-->>useExportCSV: Return data pages
    useExportCSV->>ReferrersTable: Update progress
    useExportCSV->>User: Trigger CSV download
Loading

Poem

🐰
A hop, a skip, a code refactor,
New hooks for warnings, charts with laughter.
Export to CSV, tabs on display,
Sharable links now lead the way.
With i18n and redirects set,
This bunny’s proud—best update yet!
🥕✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 457f125 and e226308.

📒 Files selected for processing (2)
  • editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/settings.tsx (5 hunks)
  • editor/scaffolds/platform/data/use-export-csv.ts (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@vercel vercel bot temporarily deployed to Preview – blog April 21, 2025 05:08 Inactive
@vercel vercel bot temporarily deployed to Preview – legacy-www April 21, 2025 05:08 Inactive
@vercel vercel bot temporarily deployed to Preview – viewer April 21, 2025 05:08 Inactive
@vercel vercel bot temporarily deployed to Preview – sites April 21, 2025 05:08 Inactive
@vercel vercel bot temporarily deployed to Preview – backgrounds April 21, 2025 05:08 Inactive
@vercel vercel bot temporarily deployed to Preview – docs April 21, 2025 05:08 Inactive
@vercel vercel bot temporarily deployed to Preview – blog May 5, 2025 12:15 Inactive
@vercel vercel bot temporarily deployed to Preview – sites May 5, 2025 12:15 Inactive
@vercel vercel bot temporarily deployed to Preview – docs May 5, 2025 12:15 Inactive
@vercel vercel bot temporarily deployed to Preview – backgrounds May 5, 2025 12:15 Inactive
@vercel vercel bot temporarily deployed to Preview – viewer May 5, 2025 12:15 Inactive
@softmarshmallow softmarshmallow changed the title Model/west [Grida WEST] UX Improvements May 5, 2025
@softmarshmallow softmarshmallow marked this pull request as ready for review May 5, 2025 12:19
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: 9

🔭 Outside diff range comments (2)
editor/lib/platform/index.ts (1)

685-717: 🛠️ Refactor suggestion

Implementation not aligned with new return-type contract
invite/refresh promise types were narrowed to a success payload, but the methods still blindly return res.json(). If the server answers with { error } the caller will mis-interpret the response and crash on missing fields. Add an if (!res.ok) guard or widen the type to include the error branch.

return fetch(url).then(async (res) => {
  const body = await res.json();
  if (!res.ok) {
    throw new Error(body?.error ?? "invite request failed");
  }
  return body as {
    data: { code: string; sharable: { title: string; text: string; url: string } };
  };
});
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/settings.tsx (1)

124-131: ⚠️ Potential issue

Wrong variable referenced – updated is ignored after mutation

After the update() call, you correctly destructure the response into updated.
However, the success branch checks and returns based on the parameter data, which is always truthy, instead of the newly-fetched updated result. This can mask failed updates (e.g. constraint errors that return null data but no error).

-      if (data) {
-        setCampaign(updated as unknown as Platform.WEST.Referral.Campaign);
-        return true;
-      }
+      if (updated) {
+        setCampaign(updated as Platform.WEST.Referral.Campaign);
+        return true;
+      }

Without this fix the UI will optimistically claim success while the DB row was not changed.

🧹 Nitpick comments (14)
editor/app/(tenant)/~/[tenant]/api/west/t/_utils/render-sharable.ts (1)

13-15: Consider adding internationalization support for the title.

The title is hardcoded in Korean, which contrasts with the internationalization approach implemented in other files like the NotFound component.

For consistency, consider implementing a similar locale-based approach:

+ // Import getLocale or pass locale as a parameter
+ const dictionary = {
+   en: {
+     title: "Get a gift when you test drive Polestar 🎁",
+     textTemplate: "{referrer_name} invites you to the Polestar test drive event!"
+   },
+   ko: {
+     title: "Polestar 시승하고 경품 받아가세요 🎁",
+     textTemplate: "{referrer_name} 님 께서 Polestar 시승 이벤트에 초대합니다!"
+   }
+ };

export function renderSharable({
-  template,
  context: { baseUrl, referrer_name, invitation_code, locale = "ko" },
}: {
-  template: unknown;
  context: {
    baseUrl: string | URL;
    referrer_name: string;
    invitation_code: string;
+   locale?: "en" | "ko";
  };
}) {
+  const t = dictionary[locale];
  return {
-    // FIXME: content template
-    title: "Polestar 시승하고 경품 받아가세요 🎁",
-    text: `${referrer_name} 님 께서 Polestar 시승 이벤트에 초대합니다!`,
+    title: t.title,
+    text: t.textTemplate.replace("{referrer_name}", referrer_name),
    url: `${baseUrl.toString()}/t/${invitation_code}`,
  };
}
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/rewards/page.tsx (1)

52-57: Consider using dedicated components for the feature lists.

The feature lists are implemented as direct bullet points in the UI. For better maintainability and potential reuse, these could be extracted into dedicated components.

// Example implementation for a FeatureList component
function FeatureList({ items }: { items: string[] }) {
  return (
    <ul className="space-y-2 text-sm text-muted-foreground">
      {items.map((item, index) => (
        <li key={index}>{item}</li>
      ))}
    </ul>
  );
}

// Usage:
<FeatureList 
  items={[
    "Discount codes and coupons",
    "Gift cards and vouchers",
    "Points and credits",
    "Custom rewards"
  ]} 
/>

Also applies to: 69-74

editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/referrers-table.tsx (2)

243-254: Consider adding success feedback for export completion.

While the export button shows progress during export, there's no success notification when the export completes successfully.

Consider adding a toast notification for successful export completion, similar to the clipboard copy action on line 68:

const { exportToCSV, isExporting, progress, error } =
  useExportCSV<ReferrerWithCustomer>({
    fetchData: async (page, pageSize) => {
      // ... existing code
    },
    transformToCSV: (referrer) => [
      // ... existing code
    ],
    headers: [
      // ... existing code
    ],
    pageSize: 100,
+   onSuccess: () => {
+     toast.success("Export completed successfully");
+   }
  });

// Then modify the useExportCSV hook to support the onSuccess callback

255-255: Enhance error display for better visibility.

The error display is minimal with just text color styling.

Consider using a more visible error component for better user experience:

- {error && <div className="text-red-500 text-sm mb-4">{error}</div>}
+ {error && (
+   <div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded mb-4 flex items-center">
+     <svg className="w-5 h-5 mr-2" fill="currentColor" viewBox="0 0 20 20">
+       <path fillRule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2h-1V9z" clipRule="evenodd" />
+     </svg>
+     <span>{error}</span>
+   </div>
+ )}
editor/hooks/use-unsaved-changes-warning.ts (1)

31-34: Consider handling route changes more efficiently

When preventing navigation, you're currently pushing the current URL back to the router which might create an unnecessary history entry.

if (link && isDirty() && !window.confirm(message)) {
  e.preventDefault();
-  router.push(window.location.href);
+  // Optional: You could use e.stopPropagation() instead of re-pushing the URL
+  // to avoid creating an unnecessary history entry
}
editor/app/(tenant)/~/[tenant]/api/west/t/invite/route.ts (1)

42-49: Avoid unsafe type casting

The current type casting approach using as unknown as ... is error-prone. Consider using proper type validation or a type guard.

- const invitation = _data as unknown as Platform.WEST.Referral.Invitation & {
+ // Use a type guard function or zod for validation
+ const invitation = validateInvitationData(_data) as Platform.WEST.Referral.Invitation & {
  referrer: Omit<Platform.WEST.Referral.ReferrerPublicRead, "type">;
  campaign: Platform.WEST.Referral.CampaignPublic;
  templates: {
    invitation_email_template: unknown;
    invitation_share_template: unknown;
  };
};
editor/scaffolds/platform/data/use-export-csv.ts (2)

131-132: Improve progress reporting accuracy

The current progress reporting is based on page numbers rather than actual processed items, which could be misleading.

for (let page = 2; page <= totalPages; page++) {
  const { data } = await config.fetchData(page, pageSize);
  allData = [...allData, ...data];
- setProgress((page - 1) * pageSize);
+ setProgress(allData.length);
}

145-145: Improve CSV filename with a safer date format

The current filename uses ISO string which includes characters that might be problematic in some contexts.

- link.setAttribute("download", `export-${new Date().toISOString()}.csv`);
+ const formattedDate = new Date().toISOString().replace(/:/g, '-').replace(/\..+/, '');
+ link.setAttribute("download", `export-${formattedDate}.csv`);
editor/app/(site)/sign-in/page.tsx (2)

31-33: Avoid re-awaiting searchParams
searchParams is awaited twice. Resolve it once and reuse to avoid redundant work and accidental divergence if the object is ever mutated.

-const { next, redirect_uri } = await searchParams;
-const q = new URLSearchParams(await searchParams).toString();
+const params = await searchParams;
+const { next, redirect_uri } = params;
+const q = new URLSearchParams(params).toString();

34-40: Expose Supabase auth errors
client.auth.getUser() can return { error, data }. Swallowing error makes mis-configuration harder to diagnose.

const { data: { user }, error: authErr } = await client.auth.getUser();
if (authErr) {
  console.error("Supabase getUser failed", authErr);
}
if (user) {
  return redirect("/");
}
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/overview.tsx (2)

170-195: Avoid repeated heavy computations
generateChartConfig(data) is executed multiple times per render (totals, card colour, chart). Cache the result with useMemo to save unnecessary array scans.

const chartConfig = useMemo(() => generateChartConfig(data.data), [data.data]);

135-144: Handle fetch errors from SWR
If /events/analyze returns a non-200 status, isLoading becomes false but data is undefined, silently showing the empty state. Provide an onError or validate res.ok before res.json() to surface issues to the user.

editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/settings.tsx (2)

71-74: Inconsistent title length constraints (256 vs 40 characters)

The Zod schema allows up to 256 characters, while the helper text says “1-40 characters”.
Please align these two sources of truth to avoid user confusion and accidental validation failures downstream.

-  title: z.string().min(1).max(256, {
+  // Pick one: 40 or 256 – example below assumes 40
+  title: z.string().min(1).max(40, {
     message: "Campaign title must be between 1 and 40 characters",
   }),

Also applies to: 280-283


590-610: Editing JSON feels broken – textarea never shows invalid draft

When the user types malformed JSON you ignore the change, so the textarea immediately re-renders with the last valid value, effectively discarding everything the user just typed.

Consider keeping a local jsonDraft state that always reflects the textarea contents, and only propagate to field.onChange after a successful JSON.parse (e.g. on blur or via debounce). Example sketch:

+const [jsonDraft, setJsonDraft] = useState(
+  JSON.stringify(field.value ?? {}, null, 2)
+);
+
 <Textarea
-  className="font-mono h-32 resize-none"
-  placeholder="{}"
-  value={JSON.stringify(field.value, null, 2)}
-  onChange={(e) => {
-    try {
-      field.onChange(JSON.parse(e.target.value));
-    } catch {
-      // Allow invalid JSON during editing
-    }
-  }}
+  className="font-mono h-32 resize-none"
+  placeholder="{}"
+  value={jsonDraft}
+  onChange={(e) => setJsonDraft(e.target.value)}
+  onBlur={() => {
+    try {
+      field.onChange(JSON.parse(jsonDraft));
+    } catch {/* keep draft, show error via FormMessage if desired */}
+  }}
 />

This preserves user input and enables proper validation messaging.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0326e2b and 457f125.

📒 Files selected for processing (20)
  • editor/app/(dev)/canvas/editor.tsx (2 hunks)
  • editor/app/(dev)/canvas/examples/with-templates/002/page.tsx (2 hunks)
  • editor/app/(site)/sign-in/page.tsx (3 hunks)
  • editor/app/(tenant)/~/[tenant]/(r)/r/[slug]/t/not-found.tsx (1 hunks)
  • editor/app/(tenant)/~/[tenant]/api/west/t/_utils/render-sharable.ts (1 hunks)
  • editor/app/(tenant)/~/[tenant]/api/west/t/invite/route.ts (2 hunks)
  • editor/app/(tenant)/~/[tenant]/api/west/t/refresh/route.ts (2 hunks)
  • editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/overview.tsx (5 hunks)
  • editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/referrers-table.tsx (3 hunks)
  • editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/settings.tsx (5 hunks)
  • editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/design/page.tsx (1 hunks)
  • editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/rewards/page.tsx (1 hunks)
  • editor/database-generated.types.ts (1 hunks)
  • editor/hooks/use-unsaved-changes-warning.ts (1 hunks)
  • editor/lib/platform/index.ts (3 hunks)
  • editor/next.config.mjs (1 hunks)
  • editor/scaffolds/platform/data/use-export-csv.ts (1 hunks)
  • editor/theme/templates/west-referral/referrer/page.tsx (2 hunks)
  • package.json (1 hunks)
  • supabase/migrations/20250323084830_grida_west.sql (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (5)
editor/app/(dev)/canvas/editor.tsx (1)
editor/hooks/use-unsaved-changes-warning.ts (1)
  • useUnsavedChangesWarning (9-44)
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/rewards/page.tsx (4)
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/observability/page.tsx (1)
  • Page (3-14)
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/quests/page.tsx (1)
  • Page (3-14)
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/participants/page.tsx (1)
  • Page (5-28)
editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/settings/page.tsx (1)
  • Page (3-9)
editor/app/(tenant)/~/[tenant]/api/west/t/refresh/route.ts (2)
editor/lib/platform/index.ts (3)
  • Invitation (612-621)
  • ReferrerPublicRead (630-645)
  • CampaignPublic (578-592)
editor/app/(tenant)/~/[tenant]/api/west/t/_utils/render-sharable.ts (1)
  • renderSharable (1-18)
editor/app/(tenant)/~/[tenant]/api/west/t/invite/route.ts (2)
editor/lib/platform/index.ts (3)
  • Invitation (612-621)
  • ReferrerPublicRead (630-645)
  • CampaignPublic (578-592)
editor/app/(tenant)/~/[tenant]/api/west/t/_utils/render-sharable.ts (1)
  • renderSharable (1-18)
editor/app/(site)/sign-in/page.tsx (1)
editor/lib/supabase/server.ts (1)
  • createClient (41-43)
🔇 Additional comments (18)
supabase/migrations/20250323084830_grida_west.sql (1)

23-23: Improved TimescaleDB extension installation

The change adds conditional installation and explicitly specifies the schema for the TimescaleDB extension. This is a safer approach that prevents errors if the extension already exists and ensures it's installed in the extensions schema rather than the default schema.

editor/database-generated.types.ts (2)

4290-4290: Improved type declaration for the citext function

The function signature has been updated to more accurately reflect the possible argument types that citext can accept: unknown, string, or boolean. This makes the type definition more flexible and accurate for function callers.


4293-4466:

Details

✅ Verification successful

Verify removal of TimescaleDB function types

I notice that TimescaleDB-related function declarations have been removed from the type definitions. This aligns with the migration changes to install the extension in the extensions schema rather than the public schema.

Please verify that these removed function types won't affect any existing code that might be importing or referencing them.


🏁 Script executed:

#!/bin/bash
# Search for imports or references to TimescaleDB functions in the codebase
echo "Searching for potential references to removed TimescaleDB functions..."
grep -r "time_bucket\|hypertable\|compression_policy\|continuous_aggregate\|chunks" --include="*.ts" --include="*.tsx" .

Length of output: 273


Let’s re-run the search using ripgrep for more reliable results:


🏁 Script executed:

#!/bin/bash
echo "Re-checking for references to TimescaleDB functions with ripgrep..."
rg "time_bucket|hypertable|compression_policy|continuous_aggregate|chunks" -t ts -t tsx .

Length of output: 264


Let’s try one more time using explicit glob patterns for both .ts and .tsx files:


🏁 Script executed:

#!/bin/bash
echo "Re-running search with explicit globs for .ts and .tsx..."
rg "time_bucket|hypertable|compression_policy|continuous_aggregate|chunks" --glob '*.ts' --glob '*.tsx' .

Length of output: 232


No references to TimescaleDB functions detected – removal is safe

I’ve searched all TypeScript files (.ts, .tsx) for calls to time_bucket, hypertable, compression_policy, continuous_aggregate, and chunks and found none. Removing these function types will not impact existing code.

editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/design/page.tsx (1)

164-165: LGTM: Campaign object updated with new properties.

The addition of www_name and www_route_path properties to the campaign object aligns with the type extensions in the platform definition. These properties will support URL construction for invitation sharing.

editor/app/(dev)/canvas/examples/with-templates/002/page.tsx (2)

3-3: LGTM: Consistent import style.

Moving the React import to the top of the file follows good code organization practices.


286-287: LGTM: Consistent campaign object structure.

The addition of www_name and www_route_path properties with dummy values ensures the example component matches the updated campaign data structure used in production, maintaining consistency across the codebase.

editor/next.config.mjs (1)

46-51: LGTM: Appropriate redirect configuration.

Adding a permanent redirect from /login to /sign-in is a clean approach to standardizing authentication URLs. The permanent: true flag ensures browsers and search engines understand this is a long-term change (301 redirect).

editor/app/(dev)/canvas/editor.tsx (2)

3-5: LGTM: Clean import management.

Removing unused imports and adding the new hook import improves code cleanliness.


17-17: Improved unsaved changes warning implementation.

Replacing the manual event listener with the reusable useUnsavedChangesWarning hook is a good refactoring that centralizes this functionality. The hook handles both browser unload events and Next.js App Router navigation more robustly.

Note that this implementation will always show warnings since the isDirty function simply returns true. Ensure this is the intended behavior for this editor component.

editor/app/(tenant)/~/[tenant]/(r)/r/[slug]/t/not-found.tsx (2)

1-20: LGTM! Good internationalization implementation.

The approach of using async/await with getLocale for dynamic language detection and implementing a dictionary for translations is a clean implementation that enhances user experience.


24-25: Well-implemented text localization.

The component properly renders localized title and description based on the detected locale.

editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/rewards/page.tsx (1)

17-78: Well-structured UI implementation with clear information hierarchy.

The card-based layout with distinct sections for settings, types, and features provides a clear organization that aligns well with the design patterns used across other campaign pages.

editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/referrers-table.tsx (1)

178-217: LGTM! Well-implemented CSV export functionality.

The CSV export implementation with the useExportCSV hook is well-structured with proper pagination, data transformation, progress tracking, and error handling.

editor/hooks/use-unsaved-changes-warning.ts (1)

1-44: Well-implemented unsaved changes warning hook with good UX considerations

This hook provides a complete solution for warning users about unsaved changes in both browser navigation (beforeunload) and app router navigation scenarios. The implementation is clean with proper event handling and cleanup.

editor/app/(tenant)/~/[tenant]/api/west/t/invite/route.ts (1)

4-5: Good refactoring to centralize sharable content generation

Moving the sharable content generation logic to the server side with the renderSharable utility creates a more maintainable architecture.

editor/scaffolds/platform/data/use-export-csv.ts (1)

1-174: Excellent reusable CSV export hook with comprehensive documentation

This hook provides a well-structured solution for client-side CSV export with pagination, progress tracking, and proper error handling. The documentation is thorough with clear examples.

editor/theme/templates/west-referral/referrer/page.tsx (2)

51-51: Good refactoring to use server-provided sharable content

The simplified approach of directly using the server-provided sharable object improves code maintainability and ensures consistency between server and client.


69-69: Good refactoring to use server-provided sharable content

The simplified approach of directly using the server-provided sharable object improves code maintainability and ensures consistency between server and client.

"test": "pnpm -r --filter 'packages/*' run test"
},
"packageManager": "pnpm@9.15.4",
"packageManager": "pnpm@10.10.0",
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Verify upgrade to pnpm v10.10.0

Upgrading the packageManager field to pnpm@10.10.0 may introduce breaking changes in lockfile formats and workspace commands. Please ensure:

  • CI/CD pipelines and local development environments are updated to pnpm v10.
  • The pnpm-lock.yaml is regenerated via pnpm install and committed.
  • Any scripts or configuration files referencing pnpm are updated.

🏁 Script executed:

#!/bin/bash
# 1. Search for pnpm version references across the repo
rg -n "pnpm@" --glob "*.*"

# 2. Regenerate lockfile and verify changes
pnpm install
if git diff --quiet -- pnpm-lock.yaml; then
  echo "Warning: pnpm-lock.yaml unchanged. Please run 'pnpm install' to regenerate the lockfile."
else
  echo "Success: pnpm-lock.yaml has been updated."
fi

Length of output: 8306


Verify pnpm v10.10.0 upgrade across all workspaces

  • Root package.json has been updated to "packageManager": "pnpm@10.10.0".
  • desktop/package.json still declares "packageManager": "pnpm@9.15.4" – align this to 10.10.0.
  • The editor/package.json (and any other nested package.json) contains a top-level pnpm.overrides field, which only takes effect at the workspace root. Please move those override entries into the root package.json.
  • Running pnpm install with pnpm v10.10.0 left pnpm-lock.yaml unchanged (no dep changes detected). If any lockfile updates do occur, commit the updated pnpm-lock.yaml.
  • Ensure CI/CD pipelines and all local environments are running pnpm v10.10.0.
🤖 Prompt for AI Agents (early access)
In package.json at line 8, the packageManager field is updated to "pnpm@10.10.0". To fix the issues, update all nested package.json files, such as desktop/package.json, to also use "pnpm@10.10.0" for consistency. Move any pnpm.overrides fields from nested package.json files like editor/package.json to the root package.json. Run pnpm install with pnpm v10.10.0 to regenerate the pnpm-lock.yaml if needed and commit any changes. Finally, ensure all CI/CD pipelines and local environments use pnpm v10.10.0.

Comment on lines +1 to +18
export function renderSharable({
template,
context: { baseUrl, referrer_name, invitation_code },
}: {
template: unknown;
context: {
baseUrl: string | URL;
referrer_name: string;
invitation_code: string;
};
}) {
return {
// FIXME: content template
title: "Polestar 시승하고 경품 받아가세요 🎁",
text: `${referrer_name} 님 께서 Polestar 시승 이벤트에 초대합니다!`,
url: `${baseUrl.toString()}/t/${invitation_code}`,
};
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Address the FIXME comment and clarify template usage.

The function is well-structured, but there are two issues to address:

  1. The FIXME comment indicates unfinished work
  2. The template parameter is typed as unknown but not used in the implementation

Consider either implementing the templating functionality or removing the unused parameter:

export function renderSharable({
-  template,
  context: { baseUrl, referrer_name, invitation_code },
}: {
-  template: unknown;
  context: {
    baseUrl: string | URL;
    referrer_name: string;
    invitation_code: string;
  };
}) {
  return {
-    // FIXME: content template
    title: "Polestar 시승하고 경품 받아가세요 🎁",
    text: `${referrer_name} 님 께서 Polestar 시승 이벤트에 초대합니다!`,
    url: `${baseUrl.toString()}/t/${invitation_code}`,
  };
}

Alternatively, if templating is planned for future implementation, keep the parameter but document its intended use with a more specific TODO comment.

📝 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
export function renderSharable({
template,
context: { baseUrl, referrer_name, invitation_code },
}: {
template: unknown;
context: {
baseUrl: string | URL;
referrer_name: string;
invitation_code: string;
};
}) {
return {
// FIXME: content template
title: "Polestar 시승하고 경품 받아가세요 🎁",
text: `${referrer_name} 님 께서 Polestar 시승 이벤트에 초대합니다!`,
url: `${baseUrl.toString()}/t/${invitation_code}`,
};
}
export function renderSharable({
context: { baseUrl, referrer_name, invitation_code },
}: {
context: {
baseUrl: string | URL;
referrer_name: string;
invitation_code: string;
};
}) {
return {
title: "Polestar 시승하고 경품 받아가세요 🎁",
text: `${referrer_name} 님 께서 Polestar 시승 이벤트에 초대합니다!`,
url: `${baseUrl.toString()}/t/${invitation_code}`,
};
}
🤖 Prompt for AI Agents (early access)
In editor/app/(tenant)/~/[tenant]/api/west/t/_utils/render-sharable.ts lines 1 to 18, the FIXME comment indicates unfinished work related to the content template, and the template parameter is typed as unknown but unused. To fix this, either implement the templating logic using the template parameter to generate the title and text dynamically, or if templating is not yet ready, remove the template parameter entirely to avoid confusion. If you plan to implement templating later, keep the parameter but replace the FIXME comment with a clear TODO explaining the intended use of the template parameter.

Comment on lines +176 to +177
const client = useMemo(() => createBrowserWestReferralClient(), []);

Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Consider reusing the existing Supabase client.

A new client is created for export operations when one already exists from line 148. Reusing the existing client would avoid redundant instantiation.

export function ReferrersTable() {
  const campaign = useCampaign();
  const importCustomersDialog = useDialogState("import-customers", {
    refreshkey: true,
  });
  const { tokens } = useReferrers(campaign.id);
- const client = useMemo(() => createBrowserWestReferralClient(), []);
+ // Reuse the existing client from useReferrers
+ const client = useMemo(() => createBrowserWestReferralClient(), []);

  const { exportToCSV, isExporting, progress, error } =
    useExportCSV<ReferrerWithCustomer>({

🏁 Script executed:

#!/bin/bash
# Check if useReferrers function exposes its client for reuse
grep -A 30 "function useReferrers" . | grep -B 10 -A 20 "return"

Length of output: 88


🏁 Script executed:

#!/bin/bash
# Locate the definition of useReferrers and inspect its return signature
rg "function useReferrers" -C20 -n .
# Check for any instantiation or exposure of createBrowserWestReferralClient within useReferrers
rg "createBrowserWestReferralClient" -n .

Length of output: 7820


Refactor shared Supabase client instantiation

To avoid creating multiple Supabase clients in the same component, lift client creation into a single, reusable instance and consume that in both your hook and the export logic.

Suggested changes:

  • In editor/lib/supabase/client.ts, export a singleton client instead of a factory:

-export const createBrowserWestReferralClient = () =>

  • createBrowserClient(...)
    +export const supabaseClient = createBrowserClient(...)

- Update your `useReferrers` hook to use (and return) the shared client:

```ts
import { supabaseClient } from "@/lib/supabase/client";

export function useReferrers(campaignId: string) {
  const [participants, setParticipants] = useState<ReferrerWithCustomer[] | null>(null);
-   const client = useMemo(() => createBrowserWestReferralClient(), []);
+   const client = useMemo(() => supabaseClient, []);

  useEffect(() => {
    client
      .from("referrer")
      … // fetch logic
  }, [campaignId]);

  return { participants, client };
}
  • In your ReferrersTable component, consume the hook’s client instead of calling the factory again:

    export function ReferrersTable() {
      const campaign = useCampaign();
  • const { tokens } = useReferrers(campaign.id);

  • const { participants, client } = useReferrers(campaign.id);
  • const client = useMemo(() => createBrowserWestReferralClient(), []);
    const { exportToCSV, isExporting, progress, error } =
    useExportCSV({ client, data: participants });

These changes ensure there’s only one Supabase client instance per page, reducing redundant instantiation.

<details>
<summary>🤖 Prompt for AI Agents (early access)</summary>

In editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/referrers-table.tsx around lines 176-177, avoid creating a new Supabase client with createBrowserWestReferralClient(). Instead, refactor to use a single shared Supabase client instance exported from editor/lib/supabase/client.ts. Update the useReferrers hook to import and use this shared client and return it, then in ReferrersTable consume the client from the hook rather than creating a new one. This eliminates redundant client instantiation and ensures a single client per page.


</details>

<!-- This is an auto-generated comment by CodeRabbit -->

Comment on lines +67 to +72
baseUrl: new URL(
www_route_path ?? "",
IS_HOSTED
? `https://${www_name}.grida.site/`
: `http://${www_name}.localhost:3000/`
),
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add null safety checks for URL construction

The current implementation might fail if www_name is null, and the empty fallback for www_route_path could lead to invalid URLs.

baseUrl: new URL(
-  www_route_path ?? "",
+  www_route_path ?? "/",
  IS_HOSTED
-    ? `https://${www_name}.grida.site/`
-    : `http://${www_name}.localhost:3000/`
+    ? `https://${www_name || 'default'}.grida.site/`
+    : `http://${www_name || 'default'}.localhost:3000/`
),
📝 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
baseUrl: new URL(
www_route_path ?? "",
IS_HOSTED
? `https://${www_name}.grida.site/`
: `http://${www_name}.localhost:3000/`
),
baseUrl: new URL(
www_route_path ?? "/",
IS_HOSTED
? `https://${www_name || 'default'}.grida.site/`
: `http://${www_name || 'default'}.localhost:3000/`
),
🤖 Prompt for AI Agents (early access)
In editor/app/(tenant)/~/[tenant]/api/west/t/invite/route.ts around lines 67 to 72, the URL construction lacks null safety checks for www_name and www_route_path, which can cause runtime errors or invalid URLs. Add explicit checks to ensure www_name is not null or undefined before using it in the URL template, and provide a valid default or handle the case when www_route_path is empty or null to avoid constructing invalid URLs.

Comment on lines +58 to +74
const { www_name, www_route_path } = invitation.campaign;
const { invitation_share_template } = invitation.templates;

return NextResponse.json({
data: next,
data: {
code: invitation.code,
sharable: renderSharable({
template: invitation_share_template,
context: {
referrer_name: invitation.referrer.referrer_name ?? "",
invitation_code: invitation.code,
baseUrl: new URL(
www_route_path ?? "",
IS_HOSTED
? `https://${www_name}.grida.site/`
: `http://${www_name}.localhost:3000/`
),
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Guard against missing www_name
If campaign.www_name is null, the generated URL becomes https://null.grida.site/, yielding broken share links. Assert or default the field before URL construction.

assert(invitation.campaign.www_name, "campaign.www_name is required");

To confirm whether campaign.www_name can ever be null or undefined, let’s locate its type definition and usage:


🏁 Script executed:

#!/bin/bash
set -e

# Find the Campaign type definition
rg "interface Campaign" -A5
rg "type Campaign" -A5

# See how www_name is declared and whether it’s optional
rg "www_name" -A3

Length of output: 17533


Require non-null www_name before URL construction
The Campaign type defines www_name: string | null (see editor/lib/platform/index.ts), so it can be null. Without a guard, you’ll emit URLs like https://null.grida.site/. Add an explicit check or default before using it:

• File: editor/app/(tenant)/~/[tenant]/api/west/t/refresh/route.ts
• File: editor/app/(tenant)/~/[tenant]/api/west/t/invite/route.ts

Suggested change:

// At top of your handler(s)
const wwwName = invitation.campaign.www_name;
if (!wwwName) {
  throw new Error("campaign.www_name is required for share links");
}

const baseUrl = new URL(
  www_route_path ?? "",
  IS_HOSTED
    ? `https://${wwwName}.grida.site/`
    : `http://${wwwName}.localhost:3000/`
);

// …
return NextResponse.json({
  data: {
    // …
    sharable: renderSharable({
      template: invitation_share_template,
      context: {
        // …
        baseUrl,
      },
    }),
  },
});

This ensures you never construct a URL with null or undefined.

🤖 Prompt for AI Agents (early access)
In editor/app/(tenant)/~/[tenant]/api/west/t/refresh/route.ts around lines 58 to 74, the code constructs a URL using invitation.campaign.www_name which can be null, causing invalid URLs like https://null.grida.site/. To fix this, add an explicit check before URL construction to ensure www_name is not null or undefined. If it is missing, throw an error indicating "campaign.www_name is required for share links". Then use the validated www_name variable for building the baseUrl to prevent broken share links.

Comment on lines +24 to +43
const { data: _data, error: refresh_err } = await service_role.west_referral
.rpc("refresh", {
p_campaign_id: campaign_id,
p_invitation_id: invitation_id,
});
})
.select(
`
*,
referrer:referrer_public_secure(
*
),
campaign:campaign_public(*),
templates:campaign(
invitation_email_template,
invitation_share_template
)
`
)
.single();

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Check error & null data before casting
_data is cast to invitation before confirming success, risking runtime crashes if the RPC fails and returns null.

-const { data: _data, error: refresh_err } = await service_role.west_referral
-  ... .single();
-
-const invitation = _data as ...;
-
-if (refresh_err) {
+const { data: _data, error: refresh_err } = await service_role.west_referral
+  ... .single();
+
+if (refresh_err || !_data) {
   console.error("error while refreshing", refresh_err);
   return NextResponse.json({ error: refresh_err }, { status: 400 });
 }
+
+const invitation = _data as Platform.WEST.Referral.Invitation & { ... };
📝 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 { data: _data, error: refresh_err } = await service_role.west_referral
.rpc("refresh", {
p_campaign_id: campaign_id,
p_invitation_id: invitation_id,
});
})
.select(
`
*,
referrer:referrer_public_secure(
*
),
campaign:campaign_public(*),
templates:campaign(
invitation_email_template,
invitation_share_template
)
`
)
.single();
const { data: _data, error: refresh_err } = await service_role.west_referral
.rpc("refresh", {
p_campaign_id: campaign_id,
p_invitation_id: invitation_id,
})
.select(
`
*,
referrer:referrer_public_secure(
*
),
campaign:campaign_public(*),
templates:campaign(
invitation_email_template,
invitation_share_template
)
`
)
.single();
if (refresh_err || !_data) {
console.error("error while refreshing", refresh_err);
return NextResponse.json({ error: refresh_err }, { status: 400 });
}
const invitation = _data as Platform.WEST.Referral.Invitation & {
templates: {
invitation_email_template: string;
invitation_share_template: string;
};
};
🤖 Prompt for AI Agents (early access)
In editor/app/(tenant)/~/[tenant]/api/west/t/refresh/route.ts between lines 24 and 43, the code casts _data to invitation without verifying if the RPC call succeeded or if _data is non-null. To fix this, add a check for refresh_err and ensure _data is not null before casting or using it. Handle the error or null case appropriately to prevent runtime crashes.

Comment on lines +289 to +299
{Object.keys(generateChartConfig(data)).map((name, index) => (
<Line
key={index}
type="linear"
dataKey={name}
stroke={`var(--color-${name})`}
strokeWidth={2}
activeDot={{ r: 6, strokeWidth: 2 }}
dot={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

Line colours will not render as intended
stroke={"var(--color-" + name + ")"} assumes a CSS variable for every event name, which rarely exists. Use the colour computed in generateChartConfig instead.

-<Line ... stroke={`var(--color-${name})`} ... />
+const cfg = chartConfig[name];
+<Line ... stroke={cfg.color} ... />

Create const chartConfig = useMemo(() => generateChartConfig(data), [data]); once to reuse throughout the component.

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

🤖 Prompt for AI Agents (early access)
In editor/app/(workbench)/[org]/[proj]/(console)/(campaign)/campaigns/[campaign]/_components/overview.tsx around lines 289 to 299, the stroke color for each Line component is incorrectly set using CSS variables based on event names, which likely do not exist. To fix this, first create a memoized constant chartConfig using useMemo to call generateChartConfig(data) once and reuse it. Then, update the stroke prop to use the color value from chartConfig for each corresponding name instead of the CSS variable string.

@vercel vercel bot temporarily deployed to Preview – blog May 5, 2025 12:36 Inactive
@vercel vercel bot temporarily deployed to Preview – viewer May 5, 2025 12:36 Inactive
@vercel vercel bot temporarily deployed to Preview – docs May 5, 2025 12:36 Inactive
@vercel vercel bot temporarily deployed to Preview – backgrounds May 5, 2025 12:36 Inactive
@vercel vercel bot temporarily deployed to Preview – sites May 5, 2025 12:36 Inactive
@softmarshmallow softmarshmallow merged commit c01ca78 into main May 5, 2025
10 of 12 checks passed
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