Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

@steven-tey
Copy link
Collaborator

@steven-tey steven-tey commented Aug 22, 2025

Summary by CodeRabbit

  • New Features
    • Unified "current folder" context across links, controls, dropdowns, and import flows for consistent folder selection.
  • Bug Fixes
    • Link counts, filters, prefetching, and loading placeholders now work for "Unsorted" folders.
    • Folder icon visibility and folder navigation links behave more accurately in lists.
  • Chores
    • Workspace navigation updated to use slug-based paths when setting a default folder.
  • Tests
    • Internal updates to support the new folder context behavior.

@vercel
Copy link
Contributor

vercel bot commented Aug 22, 2025

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

Project Deployment Preview Updated (UTC)
dub Ready Ready Preview Aug 22, 2025 2:02am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

Walkthrough

Replaces URL query-based folderId access with a new hook useCurrentFolderId across many UI and SWR hooks, adds the hook implementation, removes the SWR enabled gating in use-folder-link-count, updates folder dropdown/selection and some modal flows, and changes SetDefaultFolderModal to use workspace slug for its API path.

Changes

Cohort / File(s) Summary
New hook: current folder context
apps/web/lib/swr/use-current-folder-id.ts
Adds useCurrentFolderId hook: reads folderId from search params, falls back to workspace default, normalizes "unsorted" to null, returns { folderId }.
Adopt hook in links UI
apps/web/ui/links/link-card.tsx, apps/web/ui/links/link-controls.tsx, apps/web/ui/links/link-title-column.tsx, apps/web/ui/links/links-container.tsx, apps/web/ui/links/use-link-filters.tsx
Replaces URL-based folderId derivation with useCurrentFolderId; updates showFolderIcon logic and slug-based folder link path in link-title-column; ensures hooks receive folderId ?? "" where needed.
Dashboard page client
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx
Switches folderId source to useCurrentFolderId; permission check and control flow otherwise unchanged.
Folder dropdown behavior
apps/web/ui/folders/folder-dropdown.tsx
Uses useCurrentFolderId; prefetches links for any folderId (including "unsorted"), broadens placeholder condition, and adjusts selection handling for "unsorted" vs defaultFolderId.
Folder metadata hooks
apps/web/lib/swr/use-is-mega-folder.ts
Uses useCurrentFolderId instead of useSearchParams; rest of logic unchanged.
Folder link count SWR
apps/web/lib/swr/use-folder-link-count.ts
Removes enabled gating on the SWR fetch (no longer conditionally disabled when folderId is missing or "unsorted").
Import modals adopt hook
apps/web/ui/modals/import-bitly-modal.tsx, apps/web/ui/modals/import-csv-modal/index.tsx, apps/web/ui/modals/import-rebrandly-modal.tsx, apps/web/ui/modals/import-short-modal.tsx
Replace URL-derived folderId with useCurrentFolderId; folderId still conditionally included in API payloads/state and redirects when present.
Set default folder modal
apps/web/ui/modals/set-default-folder-modal.tsx
Uses slug from useWorkspace; changes API path to /api/workspaces/${slug}; notes useWorkspace now returns { id, slug }.
Dashboard / general imports
apps/web/app/.../page-client.tsx, various UI components
Removes direct useSearchParams usage in favor of useCurrentFolderId imports where applicable.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Component as Component (e.g., Link UI)
  participant Hook as useCurrentFolderId
  participant NextNav as useSearchParams
  participant Workspace as useWorkspace

  User->>Component: Render
  Component->>Hook: request { folderId }
  Hook->>NextNav: read ?folderId
  alt folderId present in URL
    Hook-->>Component: { folderId }
  else no folderId in URL
    Hook->>Workspace: get defaultFolderId
    alt defaultFolderId present
      Hook-->>Component: { folderId: defaultFolderId }
    else none or "unsorted"
      Hook-->>Component: { folderId: null }
    end
  end
  Component-->>User: Render UI using current folder context
Loading
sequenceDiagram
  autonumber
  participant UI as FolderDropdown
  participant Hook as useCurrentFolderId
  participant Data as useLinks / Prefetch
  participant Router as Router/URL

  UI->>Hook: Read currentFolderId
  Note over UI,Hook: folderId = selectedFolderId || currentFolderId
  UI->>Data: Prefetch links for folderId (including "unsorted")
  User->>UI: Select "Unsorted" or a Folder
  alt Select "Unsorted" and no defaultFolderId
    UI->>Router: delete ?folderId
  else Select folder or Unsorted with defaultFolderId
    UI->>Router: set ?folderId=<chosen or unsorted>
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • devkiran

Poem

A twitch of whiskers, context found,
I hop from query strings unbound.
A folder’s fate by hook I know,
Through slug and state the currents flow.
Links all lined in tidy rows—
Thump! says the rabbit: onward goes. 🥕

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix-folders

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.
    • 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.
  • 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 the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

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

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

@steven-tey
Copy link
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

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

Caution

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

⚠️ Outside diff range comments (7)
apps/web/ui/modals/import-rebrandly-modal.tsx (1)

172-181: Add React keys to mapped domain rows

Each item in domains.map(...) lacks a key, which can cause unstable renders and warnings.

-              {domains.map(({ id, domain, links }) => (
-                <div className="flex items-center justify-between space-x-2 rounded-md border border-neutral-200 bg-white px-4 py-2">
+              {domains.map(({ id, domain, links }) => (
+                <div
+                  key={id}
+                  className="flex items-center justify-between space-x-2 rounded-md border border-neutral-200 bg-white px-4 py-2"
+                >
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx (1)

225-235: Avoid non-null assertion on possibly null folderId

With the new hook, folderId can be null (unsorted). Passing folderId! risks runtime issues in RequestFolderEditAccessButton.

-              ) : (
-                <div className="w-fit">
-                  <RequestFolderEditAccessButton
-                    folderId={folderId!}
-                    workspaceId={workspace.id!}
-                    variant="primary"
-                  />
-                </div>
-              )}
+              ) : folderId ? (
+                <div className="w-fit">
+                  <RequestFolderEditAccessButton
+                    folderId={folderId}
+                    workspaceId={workspace.id!}
+                    variant="primary"
+                  />
+                </div>
+              ) : null}
apps/web/ui/modals/import-short-modal.tsx (1)

160-191: Add React keys to mapped domain rows

Same issue as the Rebrandly modal: missing key on mapped elements.

-                {domains.map(({ id, domain, links }) => (
-                  <div className="flex items-center justify-between space-x-2 rounded-md border border-neutral-200 bg-white px-4 py-2">
+                {domains.map(({ id, domain, links }) => (
+                  <div
+                    key={id}
+                    className="flex items-center justify-between space-x-2 rounded-md border border-neutral-200 bg-white px-4 py-2"
+                  >
apps/web/ui/modals/import-bitly-modal.tsx (1)

78-83: OAuth state isn’t URL-encoded — this can break or weaken the OAuth flow.

state is a JSON string placed directly in the query string. Characters like quotes and braces must be encoded; otherwise the provider may reject the URL or misparse state. Encode the JSON payload.

Apply this diff:

-  const bitlyOAuthURL = `https://bitly.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_BITLY_CLIENT_ID}&redirect_uri=${APP_DOMAIN_WITH_NGROK}/api/callback/bitly&state=${JSON.stringify(
+  const bitlyOAuthURL = `https://bitly.com/oauth/authorize?client_id=${process.env.NEXT_PUBLIC_BITLY_CLIENT_ID}&redirect_uri=${APP_DOMAIN_WITH_NGROK}/api/callback/bitly&state=${encodeURIComponent(JSON.stringify(
     {
       workspaceId,
       ...(folderId ? { folderId } : {}),
-    },
-  )}`;
+    },
+  ))}`;

Optional: build the URL with URL/URLSearchParams to avoid future encoding bugs.

const u = new URL("https://bitly.com/oauth/authorize");
u.searchParams.set("client_id", process.env.NEXT_PUBLIC_BITLY_CLIENT_ID!);
u.searchParams.set("redirect_uri", `${APP_DOMAIN_WITH_NGROK}/api/callback/bitly`);
u.searchParams.set("state", JSON.stringify({ workspaceId, ...(folderId ? { folderId } : {}) }));
const bitlyOAuthURL = u.toString();
apps/web/ui/links/use-link-filters.tsx (1)

36-104: Fix stale memo: include tagsAsync in dependencies.

The filters memo reads tagsAsync (for shouldFilter) but it isn't in the dependency array, so toggling async mode won’t update the UI until another dep changes.

Apply this diff:

-  }, [domains, tags, users]);
+  }, [domains, tags, users, tagsAsync]);
apps/web/ui/folders/folder-dropdown.tsx (2)

86-89: Avoid fetching “unsorted” via useFolder.

folderId can be "unsorted" (from selectedFolderId). Calling useFolder with that will 404 or waste network. Disable the query for the sentinel.

-  const { folder: selectedFolderData } = useFolder({
-    folderId,
-    enabled: !!folderId,
-  });
+  const { folder: selectedFolderData } = useFolder({
+    folderId,
+    enabled: !!(folderId && folderId !== "unsorted"),
+  });

117-130: Move prefetch side-effect out of useMemo.

Side-effects inside useMemo is an antipattern and may be skipped. Prefetch in an effect; also prefetch the base path for “unsorted”.

Apply this diff to remove the side-effect:

-    if (folderId) {
-      router.prefetch(`/${slug}/links?folderId=${folderId}`);
-    }

Then add this effect nearby (e.g., after line 114):

// Prefetch the next view for snappy nav
useEffect(() => {
  if (!folderId) return;
  if (folderId === "unsorted") {
    router.prefetch(`/${slug}/links`);
  } else {
    router.prefetch(`/${slug}/links?folderId=${folderId}`);
  }
}, [folderId, slug, router]);
🧹 Nitpick comments (13)
apps/web/ui/modals/set-default-folder-modal.tsx (1)

87-90: Disable action if workspaceId isn’t ready

Button can be clicked when workspaceId is not loaded (early-return inside handler silently no-ops). Disable it until ready for clearer UX.

-          loading={isPending}
+          loading={isPending}
+          disabled={!workspaceId}
apps/web/ui/modals/import-rebrandly-modal.tsx (1)

152-154: Optional: build the redirect URL via query helpers to preserve existing params

Using string templates drops any other existing query params. Consider useRouterStuff().queryParams({ set: { folderId } }) and then router.push or call it directly to maintain state.

apps/web/lib/swr/use-current-folder-id.ts (1)

4-14: Add explicit return type for clarity and downstream typing

Helps call sites reason about nullability and avoids implicit any regressions if this grows.

-export default function useCurrentFolderId() {
+export default function useCurrentFolderId(): { folderId: string | null } {
apps/web/ui/modals/import-short-modal.tsx (1)

141-142: Optional: preserve existing query params when redirecting after import

Consider using useRouterStuff().queryParams to set folderId rather than manually constructing ?folderId=..., which drops other stateful params.

apps/web/ui/modals/import-csv-modal/index.tsx (1)

220-222: URL-encode folderId when composing the redirect.

If folderId ever contains non-URL-safe characters, the query string can break. Safer to encode the value.

Apply this diff:

-                    router.push(
-                      `/${slug}/links${folderId ? `?folderId=${folderId}` : ""}`,
-                    );
+                    router.push(
+                      `/${slug}/links${
+                        folderId ? `?folderId=${encodeURIComponent(folderId)}` : ""
+                      }`,
+                    );
apps/web/ui/links/links-container.tsx (2)

91-99: Empty-state “filtered” logic may ignore default-folder scoping.

isFiltered only checks URL params. With the new hook, users can be scoped to a default folder even without a folderId param, which would still show the “No links yet” state (with CTA) instead of “No links found” (no CTA). If the intent is to treat default-folder scoping as a filter, thread that signal into LinksList.

Apply these diffs:

@@
   return (
     <PageWidthWrapper className="grid gap-y-2">
       <LinksList
         CreateLinkButton={CreateLinkButton}
         links={links}
         count={count}
         loading={isValidating}
         compact={viewMode === "rows"}
+        folderScoped={!!folderId}
       />
     </PageWidthWrapper>
   );
@@
-function LinksList({
+function LinksList({
   CreateLinkButton,
   links,
   count,
   loading,
   compact,
+  folderScoped,
 }: {
   CreateLinkButton: () => JSX.Element;
   links?: ResponseLink[];
   count?: number;
   loading?: boolean;
   compact: boolean;
+  folderScoped: boolean;
 }) {
@@
-  const isFiltered = [
-    "folderId",
-    "tagIds",
-    "domain",
-    "userId",
-    "search",
-    "showArchived",
-  ].some((param) => searchParams.has(param));
+  const isFiltered =
+    folderScoped ||
+    ["tagIds", "domain", "userId", "search", "showArchived"].some((param) =>
+      searchParams.has(param),
+    );

Also applies to: 54-61, 73-85


42-43: Replace empty‐string sentinel with undefined for folderId in useLinks calls

To ensure downstream query/serialization treats “unspecified” correctly, update both calls in apps/web/ui/links/links-container.tsx:

• At lines 42–43:

-    folderId: folderId ?? "",
+    folderId: folderId ?? undefined,

• At lines 48–49 (useLinksCount):

-    folderId: folderId ?? "",
+    folderId: folderId ?? undefined,

This preserves the existing typing (opts is a partial Zod‐inferred schema) while omitting folderId entirely when falsy, avoiding accidental ?folderId= emissions.

apps/web/ui/modals/import-bitly-modal.tsx (1)

13-13: Sanity-check env configuration at build time.

If NEXT_PUBLIC_BITLY_CLIENT_ID is missing, the generated OAuth URL will be invalid (client_id=undefined). Consider a build-time guard to surface misconfig early.

I can provide a tiny compile-time assertion or runtime console.warn gated to dev if useful.

apps/web/ui/links/link-controls.tsx (1)

103-105: Prefer nullish coalescing for folder selection.

Use ?? rather than || to avoid treating empty strings as falsy. While folderId shouldn’t be "", using the nullish operator communicates intent and avoids accidental fallthrough.

Apply this diff:

-  const { folderId: currentFolderId } = useCurrentFolderId();
-  const folderId = link.folderId || currentFolderId;
+  const { folderId: currentFolderId } = useCurrentFolderId();
+  const folderId = link.folderId ?? currentFolderId;
apps/web/lib/swr/use-is-mega-folder.ts (1)

11-12: Minor boolean simplification.

The ternary can be simplified for readability.

Apply this diff:

-    isMegaFolder: folderId && folder?.type === "mega" ? true : false,
+    isMegaFolder: !!folderId && folder?.type === "mega",
apps/web/ui/links/use-link-filters.tsx (2)

86-101: Remove the ts-expect-error by tightening return types from useUserFilterOptions.

The // @ts-expect-error masks type drift between the two branches of useUserFilterOptions. Normalize the return type so this map is type-safe.

Apply these changes:

-  // @ts-expect-error
-  users?.map(({ id, name, email, image, count }) => ({
+  users?.map(({ id, name, email, image, count }) => ({
     value: id,
     label: name || email,
     icon: (
       <Avatar
         user={{
           id,
           name,
           image,
         }}
         className="h-4 w-4"
       />
     ),
     right: nFormatter(count, { full: true }),
   })) ?? null,

And update the helper’s signature and shape to be consistent (lower in the file):

-function useUserFilterOptions({ folderId }: { folderId: string }) {
+type UserFilterOption = {
+  id: string;
+  name?: string;
+  email?: string;
+  image?: string;
+  count: number;
+};
+
+function useUserFilterOptions({ folderId }: { folderId: string }): UserFilterOption[] | null {

120-136: Guard against duplicate tagIds when selecting.

Repeatedly selecting the same tag can introduce duplicates in tagIds. Deduplicate when composing the param.

-      queryParams({
-        set: {
-          tagIds: selectedTagIds.concat(value).join(","),
-        },
-        del: "page",
-      });
+      queryParams({
+        set: {
+          tagIds: Array.from(new Set(selectedTagIds.concat(value))).join(","),
+        },
+        del: "page",
+      });
apps/web/ui/links/link-card.tsx (1)

80-83: Add editUrl to the prefetch effect deps.

router.prefetch(editUrl) depends on editUrl. Add it to avoid linter noise and ensure correctness if props change.

-useEffect(() => {
-  if (isInView) router.prefetch(editUrl);
-}, [isInView]);
+useEffect(() => {
+  if (isInView) router.prefetch(editUrl);
+}, [isInView, editUrl]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 5a9f0c5 and 317bc6b.

📒 Files selected for processing (15)
  • apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx (2 hunks)
  • apps/web/lib/swr/use-current-folder-id.ts (1 hunks)
  • apps/web/lib/swr/use-folder-link-count.ts (0 hunks)
  • apps/web/lib/swr/use-is-mega-folder.ts (1 hunks)
  • apps/web/ui/folders/folder-dropdown.tsx (4 hunks)
  • apps/web/ui/links/link-card.tsx (3 hunks)
  • apps/web/ui/links/link-controls.tsx (3 hunks)
  • apps/web/ui/links/link-title-column.tsx (4 hunks)
  • apps/web/ui/links/links-container.tsx (2 hunks)
  • apps/web/ui/links/use-link-filters.tsx (2 hunks)
  • apps/web/ui/modals/import-bitly-modal.tsx (2 hunks)
  • apps/web/ui/modals/import-csv-modal/index.tsx (2 hunks)
  • apps/web/ui/modals/import-rebrandly-modal.tsx (2 hunks)
  • apps/web/ui/modals/import-short-modal.tsx (2 hunks)
  • apps/web/ui/modals/set-default-folder-modal.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/web/lib/swr/use-folder-link-count.ts
🧰 Additional context used
🧬 Code graph analysis (14)
apps/web/lib/swr/use-is-mega-folder.ts (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/ui/links/link-controls.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/ui/modals/import-short-modal.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/ui/modals/import-bitly-modal.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/ui/links/use-link-filters.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/ui/modals/import-rebrandly-modal.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/lib/swr/use-current-folder-id.ts (1)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-45)
apps/web/ui/links/link-card.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/ui/modals/set-default-folder-modal.tsx (1)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-45)
apps/web/ui/modals/import-csv-modal/index.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/ui/folders/folder-dropdown.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/ui/links/links-container.tsx (1)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
apps/web/ui/links/link-title-column.tsx (2)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-45)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (4-14)
⏰ 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). (1)
  • GitHub Check: build
🔇 Additional comments (16)
apps/web/ui/modals/set-default-folder-modal.tsx (2)

26-26: Good move to use slug alongside workspaceId

Destructuring slug from useWorkspace() aligns this modal with the slug-based SWR key used by useWorkspace.


33-35: Folder SWR key usage is consistent

All folder-related SWR mutations and fetches in the codebase use the workspaceId query parameter (e.g. mutate('/api/folders?workspaceId=…')), and there are no instances of slug-based folder keys (such as ?workspaceSlug= or ?slug=). The set-default-folder-modal.tsx call to mutate('/api/folders?workspaceId=${workspaceId}') matches the existing convention, so you can disregard the fragmentation concern raised earlier.

Likely an incorrect or invalid review comment.

apps/web/ui/modals/import-rebrandly-modal.tsx (1)

1-1: Nice adoption of useCurrentFolderId

Centralizing folderId sourcing through the new hook removes URL-coupling and keeps behavior consistent with defaults.

apps/web/app/app.dub.co/(dashboard)/[slug]/links/page-client.tsx (2)

3-3: Good change: useCurrentFolderId import

Using the hook removes direct coupling to URL parsing across the page.


89-97: useCheckFolderPermission already handles null/“unsorted” folderId

The implementation in apps/web/lib/swr/use-folder-permissions.ts returns true if folderId is falsy (e.g. null) or equals "unsorted", so unsorted folders are granted access by design. No changes needed.

apps/web/lib/swr/use-current-folder-id.ts (1)

4-14: Solid, minimal hook to source folderId

Reading from search params with fallback to defaultFolderId and normalizing “unsorted” to null is clean and predictable.

apps/web/ui/modals/import-short-modal.tsx (1)

1-1: Good use of useCurrentFolderId

Consistent with the rest of the PR, removing direct URL parsing here improves cohesion.

apps/web/ui/modals/import-csv-modal/index.tsx (1)

4-4: Good migration to the centralized folderId source.

Adopting useCurrentFolderId keeps folder scoping consistent across the app and avoids URL/searchParam drift. Appending folderId to FormData only when present is also correct.

Also applies to: 103-103, 208-208

apps/web/ui/links/links-container.tsx (1)

3-3: Hook adoption looks solid.

Replacing ad-hoc searchParam/default logic with useCurrentFolderId reduces edge cases and aligns with the rest of the PR.

Also applies to: 37-37

apps/web/ui/modals/import-bitly-modal.tsx (1)

1-1: Folder scoping wired correctly.

Using the new hook for folderId and threading it through state and POST payload keeps Bitly imports aligned with the current folder context.

Also applies to: 36-36, 138-139

apps/web/ui/links/link-controls.tsx (1)

146-149: I’ve requested the full implementation of useCheckFolderPermission for inspection. Once we see how it treats folderId === null, we can confirm whether an explicit workspace-level fallback is needed.

apps/web/ui/links/use-link-filters.tsx (1)

199-205: Confirm folderId sentinel semantics for “unsorted”.

You pass folderId as string, with null normalized to "". Ensure useLinksCount interprets "" as “unsorted/null folder” consistently across endpoints. If not, consider sending undefined instead and handling defaulting inside the hook.

apps/web/ui/links/link-card.tsx (1)

55-66: Consistent “show folder” condition with new hook — LGTM.

Switching to useCurrentFolderId() and keeping the “not default nor selected” check preserves existing UX. Nice.

apps/web/ui/links/link-title-column.tsx (1)

108-116: Navigation consistency

Good call switching to a slug-based folder link here. Once the identity check above is fixed, this stays consistent with the “Open folder” banner in LinkCard.

apps/web/ui/folders/folder-dropdown.tsx (2)

96-101: Confirm redirect intent after folder creation.

Current logic only redirects to the new folder if you were already in a folder view. If the user creates a folder from “All/Unsorted”, it won’t navigate into the new folder. Is that intended?

If not, consider always navigating to the new folder when auto-redirect is enabled:

-        router.push(
-          `/${slug}/links${folderId && folderId !== "unsorted" ? `?folderId=${folder.id}` : ""}`,
-        );
+        router.push(`/${slug}/links?folderId=${folder.id}`);

195-205: URL handling for “unsorted” with default folder

Good handling: delete folderId only when selecting “unsorted” and there is no defaultFolderId; otherwise set folderId=unsorted. This keeps URL state explicit when needed.

Comment on lines 89 to 97
const showFolderIcon = useMemo(() => {
return Boolean(
!loading &&
link.folderId &&
![defaultFolderId, selectedFolderId].includes(link.folderId),
currentFolderId &&
currentFolderId.includes(link.folderId),
);
}, [loading, link.folderId, defaultFolderId, selectedFolderId]);
}, [loading, link.folderId, currentFolderId]);

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Bug: using String.includes to compare folder IDs leads to false positives.

currentFolderId.includes(link.folderId) performs substring matching, not identity. IDs like abc and ab will match incorrectly, toggling the icon unpredictably.

Match the logic in LinkCard and check identity against both the current and default folder:

-  const { folderId: currentFolderId } = useCurrentFolderId();
+  const { folderId: currentFolderId } = useCurrentFolderId();
+  const { slug, defaultFolderId } = useWorkspace();
@@
-  const showFolderIcon = useMemo(() => {
-    return Boolean(
-      !loading &&
-        link.folderId &&
-        currentFolderId &&
-        currentFolderId.includes(link.folderId),
-    );
-  }, [loading, link.folderId, currentFolderId]);
+  const showFolderIcon = useMemo(() => {
+    return Boolean(
+      !loading &&
+        link.folderId &&
+        ![defaultFolderId ?? null, currentFolderId ?? null].includes(link.folderId),
+    );
+  }, [loading, link.folderId, defaultFolderId, currentFolderId]);
📝 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 showFolderIcon = useMemo(() => {
return Boolean(
!loading &&
link.folderId &&
![defaultFolderId, selectedFolderId].includes(link.folderId),
currentFolderId &&
currentFolderId.includes(link.folderId),
);
}, [loading, link.folderId, defaultFolderId, selectedFolderId]);
}, [loading, link.folderId, currentFolderId]);
const { folderId: currentFolderId } = useCurrentFolderId();
const { slug, defaultFolderId } = useWorkspace();
- const showFolderIcon = useMemo(() => {
- return Boolean(
- !loading &&
- link.folderId &&
- currentFolderId &&
- currentFolderId.includes(link.folderId),
- );
const showFolderIcon = useMemo(() => {
return Boolean(
!loading &&
link.folderId &&
![defaultFolderId ?? null, currentFolderId ?? null].includes(link.folderId),
);
}, [loading, link.folderId, defaultFolderId, currentFolderId]);
🤖 Prompt for AI Agents
In apps/web/ui/links/link-title-column.tsx around lines 89 to 97, the
showFolderIcon logic uses currentFolderId.includes(link.folderId) which does
substring matching and causes false positives; change it to check identity like
currentFolderId === link.folderId || defaultFolderId === link.folderId to mirror
LinkCard logic, ensure you guard against undefined (e.g., Boolean(!loading &&
link.folderId && (currentFolderId === link.folderId || defaultFolderId ===
link.folderId))), and update the useMemo dependency array to include
defaultFolderId as well as the existing values.

Comment on lines 28 to 36
const { executeAsync, isPending } = useAction(setDefaultFolderAction, {
onSuccess: async () => {
setShowDefaultFolderModal(false);
await Promise.all([
mutate("/api/workspaces"),
mutate(`/api/workspaces/${workspaceId}`),
mutate(`/api/workspaces/${slug}`),
mutate(`/api/folders?workspaceId=${workspaceId}`),
]);
},
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid mutating a potentially undefined SWR key; use the bound mutate from useWorkspace()

If slug isn’t available yet, mutate(\/api/workspaces/${slug}`)will attempt to mutate an invalid key. Prefer the boundmutatereturned byuseWorkspace()and guard whenslug` is falsy.

Apply:

-  const { id: workspaceId, slug } = useWorkspace();
+  const { id: workspaceId, slug, mutate: mutateWorkspace } = useWorkspace();
...
       await Promise.all([
         mutate("/api/workspaces"),
-        mutate(`/api/workspaces/${slug}`),
+        slug ? mutateWorkspace() : Promise.resolve(),
         mutate(`/api/folders?workspaceId=${workspaceId}`),
       ]);
📝 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 { executeAsync, isPending } = useAction(setDefaultFolderAction, {
onSuccess: async () => {
setShowDefaultFolderModal(false);
await Promise.all([
mutate("/api/workspaces"),
mutate(`/api/workspaces/${workspaceId}`),
mutate(`/api/workspaces/${slug}`),
mutate(`/api/folders?workspaceId=${workspaceId}`),
]);
},
// Before: guards against undefined `slug` and uses the workspace’s own mutate
const { id: workspaceId, slug, mutate: mutateWorkspace } = useWorkspace();
const { executeAsync, isPending } = useAction(setDefaultFolderAction, {
onSuccess: async () => {
setShowDefaultFolderModal(false);
await Promise.all([
mutate("/api/workspaces"),
// Only call the bound workspace-mutate if slug is defined
slug ? mutateWorkspace() : Promise.resolve(),
mutate(`/api/folders?workspaceId=${workspaceId}`),
]);
},
// …other callbacks/config…
});
🤖 Prompt for AI Agents
In apps/web/ui/modals/set-default-folder-modal.tsx around lines 28 to 36, the
success handler calls mutate(`/api/workspaces/${slug}`) which can run with an
undefined slug; replace that global mutate call with the bound mutate from
useWorkspace() (the hook that provides workspace-scoped mutate) and only invoke
it when slug is truthy — keep the other global mutate calls for
"/api/workspaces" and `/api/folders?workspaceId=${workspaceId}` as-is, but guard
the workspace-specific mutate with a simple if (slug) or conditional so you
never call mutate with an invalid key.

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

♻️ Duplicate comments (1)
apps/web/ui/links/link-title-column.tsx (1)

78-94: Show folder icon only when link’s folder differs from both the current and the default folder (match LinkCard).

Current check only excludes the current folder. Links in the default folder will still show a folder icon when viewed outside that folder, which diverges from LinkCard and leads to inconsistent UI. Include the defaultFolderId in the exclusion and update deps.

Apply:

-  const { slug } = useWorkspace();
+  const { slug, defaultFolderId } = useWorkspace();
@@
-  const showFolderIcon = useMemo(() => {
-    return Boolean(
-      !loading && link.folderId && currentFolderId !== link.folderId,
-    );
-  }, [loading, link.folderId, currentFolderId]);
+  const showFolderIcon = useMemo(() => {
+    return Boolean(
+      !loading &&
+        link.folderId &&
+        ![defaultFolderId ?? null, currentFolderId ?? null].includes(
+          link.folderId,
+        ),
+    );
+  }, [loading, link.folderId, defaultFolderId, currentFolderId]);

Please confirm LinkCard uses the same identity-based exclusion for both current and default folders so the behavior stays consistent across views.

🧹 Nitpick comments (1)
apps/web/ui/links/link-title-column.tsx (1)

105-113: Verify slug availability for the folder link href.

This builds /${slug}/links?folderId=.... If this component can render in contexts without a workspace slug (e.g., admin mode), the href becomes malformed. If that can’t happen, we’re good; otherwise guard or compute a safe fallback.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 317bc6b and 59c827c.

📒 Files selected for processing (3)
  • apps/web/lib/swr/use-current-folder-id.ts (1 hunks)
  • apps/web/ui/links/link-card.tsx (3 hunks)
  • apps/web/ui/links/link-title-column.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web/lib/swr/use-current-folder-id.ts
  • apps/web/ui/links/link-card.tsx
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/ui/links/link-title-column.tsx (2)
apps/web/lib/swr/use-workspace.ts (1)
  • useWorkspace (6-45)
apps/web/lib/swr/use-current-folder-id.ts (1)
  • useCurrentFolderId (6-16)
🔇 Additional comments (2)
apps/web/ui/links/link-title-column.tsx (2)

3-3: LGTM: Switched to useCurrentFolderId.

Good move away from ad-hoc URL parsing; centralizing folder resolution reduces edge cases (e.g., “unsorted” → null mapping).


45-45: LGTM: Added useParams import for UserAvatar.

Required for the admin-mode conditional; import aligns with usage below.

@steven-tey steven-tey merged commit 0003601 into main Aug 22, 2025
7 of 8 checks passed
@steven-tey steven-tey deleted the fix-folders branch August 22, 2025 02:09
@coderabbitai coderabbitai bot mentioned this pull request Oct 23, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants