Skip to content

feat(portal-framework-auth, portal-framework-ui, portal-plugin-dashboard): implement remember me functionality and various UI enhancements#471

Merged
pcfreak30 merged 1 commit intodevelopfrom
libs/portal-plugin-dashboard
Aug 30, 2025
Merged

feat(portal-framework-auth, portal-framework-ui, portal-plugin-dashboard): implement remember me functionality and various UI enhancements#471
pcfreak30 merged 1 commit intodevelopfrom
libs/portal-plugin-dashboard

Conversation

@pcfreak30
Copy link
Copy Markdown
Member

@pcfreak30 pcfreak30 commented Aug 30, 2025

  • Authentication Provider (libs/portal-framework-auth/src/dataProviders/auth.ts):
    • Added js-cookie as a dependency
    • Introduced wrapErrorWithName helper function to provide more context to authentication errors
    • Defined constants for error names (LOGIN_ERROR_NAME, REGISTRATION_ERROR_NAME) and redirect paths (LOGIN_PATH, OTP_PATH, DASHBOARD_PATH)
    • Refactored redirect paths to use constants instead of hardcoded strings
    • Applied error wrapping in login and registration responses for better error context
  • Login Form (libs/portal-framework-auth/src/ui/forms/login.tsx):
    • Added "remember me" checkbox field to the form configuration
    • Set default value for "remember me" checkbox based on existing cookie
    • Removed "remember me" cookie if the checkbox is unchecked during form submission
  • UI Components:
    • Main Navigation (libs/portal-framework-ui/src/components/MainNavigation.tsx):
      • Fixed the href generation logic for child navigation items, particularly handling index routes correctly
    • Schema Form (libs/portal-framework-ui/src/components/form/SchemaForm.tsx):
      • Improved form instance handling within dialogs by adding an isActiveDialog check
    • Desktop Sidebar (libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx):
      • Adjusted CSS classes for layout and transition effects
    • User Navigation (libs/portal-framework-ui/src/components/layout/UserNav.tsx):
      • Updated avatar image alt text to use the user's full name
      • Improved avatar fallback logic to handle cases where first or last name might be missing
  • Dashboard Plugin:
    • New Components:
      • Added AvatarUpload.tsx component for profile picture management, utilizing Uppy for drag-and-drop and file upload capabilities
      • Added uploadAvatar.tsx dialog configuration for the avatar upload feature
    • Bio Widget (libs/portal-plugin-dashboard/src/ui/widgets/account/bio.tsx):
      • Integrated the avatar upload dialog trigger
      • Added functionality to refetch user identity after a successful avatar update
  • Core UI (libs/portal-framework-ui/src/index.ts):
    • Exported the new useAccountUrl hook
  • Dependencies & Lockfile (package.json, pnpm-lock.yaml):
    • Updated pnpm version in package.json
    • Added @uppy/* dependencies for file upload functionality
    • Updated/refined @refinedev/* package resolutions in the lockfile
    • Moved tailwind-scrollbar from devDependencies to dependencies in the lockfile

Summary by CodeRabbit

  • New Features

    • Profile avatar upload dialog with drag-and-drop, preview, progress, and success notification.
    • New account URL helper exported for integrations.
  • Bug Fixes

    • Submenu index items now navigate to the parent route.
    • Login "Remember Me" now respects cookie state and clears when unchecked.
    • Authentication flows: more consistent redirects and normalized error handling across login/OTP/logout/register.
  • Style

    • Adjusted collapsed desktop sidebar width and improved avatar display/alt text fallbacks.
  • Chores

    • Added cookie and file-upload dependencies; updated package manager.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Aug 30, 2025

⚠️ No Changeset found

Latest commit: 7246267

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 30, 2025

Warning

Rate limit exceeded

@pcfreak30 has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 7 minutes and 23 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between d3738fe and 7246267.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • libs/portal-framework-auth/package.json (1 hunks)
  • libs/portal-framework-auth/src/dataProviders/auth.ts (15 hunks)
  • libs/portal-framework-auth/src/ui/forms/login.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/MainNavigation.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/form/SchemaForm.tsx (4 hunks)
  • libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx (2 hunks)
  • libs/portal-framework-ui/src/components/layout/UserNav.tsx (1 hunks)
  • libs/portal-framework-ui/src/index.ts (1 hunks)
  • libs/portal-plugin-dashboard/package.json (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/dialogs/uploadAvatar.tsx (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/widgets/account/bio.tsx (4 hunks)
  • package.json (1 hunks)

Walkthrough

Adds js-cookie, refactors auth provider API and error/redirect handling, updates login remember-me behavior, tweaks navigation/sidebar/user avatar UI, gates SchemaForm dialog instance setting, exports useAccountUrl, and introduces an Uppy-based avatar upload component plus dialog integrated into account bio with identity refetch.

Changes

Cohort / File(s) Summary of Changes
Auth framework: deps, API, errors, redirects
libs/portal-framework-auth/package.json, libs/portal-framework-auth/src/dataProviders/auth.ts, libs/portal-framework-auth/src/ui/forms/login.tsx
Add js-cookie dependency. Replace local OTP type with SDK type. Add wrapErrorWithName and named error constants (LOGIN/REGISTRATION/LOGOUT). Introduce redirect constants (LOGIN_PATH/OTP_PATH/DASHBOARD_PATH). Replace Auth1ProviderWithEmitter with AuthProviderWithEmitter (adds on), change createAuthProvider return type, and normalize login/OTP/register/logout/check flows and error wrapping. Login form reads/writes remember_me cookie and removes it when unchecked.
Navigation & layout
libs/portal-framework-ui/src/components/MainNavigation.tsx, libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx, libs/portal-framework-ui/src/components/layout/UserNav.tsx
MainNavigation: index child submenu href now resolves to parent item.path for index routes. DesktopSidebar: collapsed width changed (md:w-40 → md:w-32) and class reorderings. UserNav: use identity avatar (with fallback), dynamic alt text, and single-initial avatar fallback.
SchemaForm: dialog-aware behavior
libs/portal-framework-ui/src/components/form/SchemaForm.tsx
Add isActiveDialog guard; only set form instance when dialog active; include isActiveDialog in effect deps; compute final config from dialog when active; minor class order tweak.
Public exports
libs/portal-framework-ui/src/index.ts
Re-export useAccountUrl to public surface.
Dashboard plugin: avatar upload & dialog integration
libs/portal-plugin-dashboard/package.json, libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx, libs/portal-plugin-dashboard/src/ui/dialogs/uploadAvatar.tsx, libs/portal-plugin-dashboard/src/ui/widgets/account/bio.tsx
Add Uppy deps. New AvatarUpload component (preview, drag/drop, XHR upload to account avatar API, progress, notifications). Add uploadAvatarDialogConfig helper. Integrate dialog into account bio; on success refetch identity and close dialog.
Repository tooling
package.json
Update packageManager from pnpm@10.11.0pnpm@10.15.0+sha512....

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant U as User
  participant L as Login Form
  participant A as Auth Provider
  participant R as Router

  U->>L: Submit credentials (remember?)
  L->>A: login(credentials)
  alt OTP required
    A-->>R: redirect to OTP_PATH
    R-->>U: Navigate to OTP
  else Success
    A-->>R: redirect to DASHBOARD_PATH (or provided redirect)
    R-->>U: Navigate to Dashboard
  else Error
    A-->>L: throw Error with name LOGIN_ERROR_NAME
    L-->>U: Show error
  end
  note right of L: "remember_me" cookie is read/removed via js-cookie
Loading
sequenceDiagram
  autonumber
  participant U as User
  participant B as Account Bio
  participant D as Dialog Manager
  participant V as AvatarUpload (Uppy)
  participant S as Server (account/avatar API)

  U->>B: Click "Edit Avatar"
  B->>D: openDialog(uploadAvatarDialogConfig)
  D->>V: Mount AvatarUpload
  U->>V: Drag/drop or choose image
  V->>S: XHRUpload(file, withCredentials)
  S-->>V: Progress / Success / Error
  alt Success
    V-->>D: onSuccess()
    D-->>B: callback → refetch identity
    D-->>U: Close dialog, show success notification
  else Error
    V-->>U: Show error notification
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

A nibble of cookies, a hop to log in,
Errors get names so they don't cause chagrin.
I upload my face with a jubilant puff,
Progress bars hum and the server says "stuff."
Sidebars shrink, links point true — a rabbit's small win. 🐇✨

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch libs/portal-plugin-dashboard

🪧 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 or @coderabbit 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.

@pcfreak30
Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Aug 30, 2025

✅ Actions performed

Review triggered.

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

Copy link
Copy Markdown

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

Caution

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

⚠️ Outside diff range comments (2)
libs/portal-framework-ui/src/components/form/SchemaForm.tsx (1)

35-46: Fix hooks-order violation (early return before hooks) and guard effect by active

Returning before calling hooks breaks React’s rules and can cause runtime bugs. Move the early return below hooks and include active in the effect guard/deps.

 export function SchemaForm<T extends FieldValues = FieldValues>({
   active = true,
   closeDialog = () => void 0,
   config,
 }: SchemaFormProps<T>) {
-  if (!active) {
-    return null;
-  }
-  const { currentDialog, setFormMethods: setFormInstance } = useDialog();
-  const { open: openNotification } = useNotification();
+  const { currentDialog, setFormMethods: setFormInstance } = useDialog();
+  const { open: openNotification } = useNotification();
+  if (!active) {
+    return null;
+  }
   if (!config) throw new Error("SchemaForm requires a form config");
@@
-  useEffect(() => {
-    if (setFormInstance && isActiveDialog) {
+  useEffect(() => {
+    if (setFormInstance && isActiveDialog && active) {
       setFormInstance(formInstance);
     }
-  }, [formInstance, setFormInstance, isActiveDialog]);
+  }, [active, formInstance, setFormInstance, isActiveDialog]);

Also applies to: 82-87

libs/portal-framework-auth/src/dataProviders/auth.ts (1)

196-207: Expose avatar in identity to support new avatar UI

getIdentity() doesn’t return an avatar URL; UserNav now expects identity.avatar. Map it from the API response (supporting both avatar and avatar_url).

-      const { created_at, email, first_name, id, last_name, otp, verified } =
-        response.data;
+      const {
+        created_at,
+        email,
+        first_name,
+        id,
+        last_name,
+        otp,
+        verified,
+        avatar,
+        avatar_url,
+      } = response.data;
       return {
         created_at,
         email,
         firstName: first_name,
         id,
         lastName: last_name,
         otp,
         verified,
+        avatar: avatar ?? avatar_url,
       };
🧹 Nitpick comments (14)
libs/portal-plugin-dashboard/package.json (1)

26-28: Uppy deps OK; check style imports and version alignment.
All at major v4—good. Confirm any required Uppy CSS is imported in the new AvatarUpload flow and consider aligning minors to reduce duplicate subtrees.

libs/portal-framework-ui/src/components/layout/UserNav.tsx (1)

45-48: Improve avatar alt text and fallback initial for better a11y and UX

Use a meaningful alt when names are missing and fall back to email initial before "?". Also ensure this matches getIdentity returning an avatar URL.

-                  <AvatarImage alt={`${firstName} ${lastName}`} src={identity?.avatar || "/placeholder.svg"} />
+                  <AvatarImage
+                    alt={(firstName || lastName ? `${firstName} ${lastName}`.trim() : email || "User avatar")}
+                    src={identity?.avatar || "/placeholder.svg"}
+                  />
                   <AvatarFallback className="bg-transparent">
-                    {firstName.charAt(0).toUpperCase() || lastName.charAt(0).toUpperCase() || "?"}
+                    {(firstName || lastName
+                      ? (firstName || lastName).charAt(0).toUpperCase()
+                      : (email?.charAt(0)?.toUpperCase() || "?"))}
                   </AvatarFallback>
libs/portal-framework-ui/src/components/MainNavigation.tsx (1)

325-330: Guard parent path when mapping index child hrefs

If item.path is undefined, an index child will produce href: undefined. Default to empty string to keep <Link> valid.

-      const submenus: Submenu[] = item.children.map((child) => ({
+      const submenus: Submenu[] = item.children.map((child) => ({
         active: isChildRouteActive(child, item, pathname),
-        href: child.index ? item.path : (child.path || ""),
+        href: child.index ? (item.path ?? "") : (child.path ?? ""),
         icon: child.icon,
         label: child.label,
       }));
libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx (1)

41-49: Remove duplicate Tailwind margins

Both mb-3 and mb-4 are present; the last one wins. Drop the duplicate for clarity.

-            "text-foreground/60 mb-3 mb-4 space-y-1 transition-opacity duration-300",
+            "text-foreground/60 mb-4 space-y-1 transition-opacity duration-300",
libs/portal-framework-auth/src/dataProviders/auth.ts (2)

34-36: Typo: OTP interface name

Minor rename for consistency.

-export interface OPTGenerateResponse {
+export interface OTPGenerateResponse {
   otp: string;
 }

242-249: Standardize on path constants for redirects

Use DASHBOARD_PATH, OTP_PATH, and LOGIN_PATH consistently.

-            return createAuthResponse({
-              redirectTo: params.redirectTo ?? "/dashboard",
+            return createAuthResponse({
+              redirectTo: params.redirectTo ?? DASHBOARD_PATH,
               success: true,
               successNotification: {
                 description: "You have successfully logged in with 2FA.",
                 message: "Login Successful",
               },
             });
@@
-          return createAuthResponse({
-            redirectTo: `/otp?to=${encodeURIComponent(
-              params.redirectTo ?? DASHBOARD_PATH,
-            )}`,
+          return createAuthResponse({
+            redirectTo: `${OTP_PATH}?to=${encodeURIComponent(
+              params.redirectTo ?? DASHBOARD_PATH,
+            )}`,
             success: true,
             successNotification: {
               description: "Please enter your 2FA code to complete login.",
               message: "Two-Factor Authentication Required",
             },
           });
@@
       } catch (error) {
         return createAuthResponse({
           error: error as Error,
-          redirectTo: "/login",
+          redirectTo: LOGIN_PATH,
           success: false,
         });
       }

Also applies to: 266-269, 304-309

libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx (5)

99-107: Reset preview and progress on each cycle to avoid stale UI.

resetState() clears Uppy/input but leaves previewUrl and uploadProgress intact.

 const resetState = () => {
   // Clear Uppy and reset file input to prevent duplicate entries
   if (uppyRef.current) {
     uppyRef.current.clear();
   }
   if (fileInputRef.current) {
     fileInputRef.current.value = "";
   }
+  setPreviewUrl(null);
+  setUploadProgress(0);
 };

127-129: Guard DropTarget target to avoid null ref edge case.

If containerRef.current is momentarily null, DropTarget may throw. Fallback to document.body.

-  .use(DropTarget, {
-    target: containerRef.current,
-  });
+  .use(DropTarget, {
+    target: containerRef.current ?? document.body,
+  });

144-149: Optional: switch preview to URL.createObjectURL for perf.

Avoids Base64 inflation and is faster for larger images. Remember to revoke on reset/unmount.

-const reader = new FileReader();
-reader.onload = (e) => {
-  setPreviewUrl(e.target?.result as string);
-};
-reader.readAsDataURL(file.data as Blob);
+const url = URL.createObjectURL(file.data as Blob);
+setPreviewUrl(url);

And in resetState()/cleanup:

+if (previewUrl) {
+  try { URL.revokeObjectURL(previewUrl); } catch {}
+}

71-77: Robust avatar alt/fallback when userName is empty.

Ensure a sensible alt and fallback glyph in all cases.

-<AvatarImage alt={userName} src={previewUrl || currentAvatar} />
+<AvatarImage alt={userName || "User avatar"} src={previewUrl || currentAvatar} />
 <AvatarFallback className="bg-primary text-primary-foreground text-xl">
-  {userName.charAt(0).toUpperCase()}
+  {(userName?.trim().charAt(0)?.toUpperCase() || "?")}
 </AvatarFallback>

298-305: Add keyboard accessibility to drop zone.

Enable Enter/Space to trigger the file picker.

-<div
+<div
   className={cn(
     "rounded-lg border-2 border-dashed p-8 text-center transition-colors",
     "border-muted hover:border-muted/50",
   )}
-  ref={containerRef}>
+  ref={containerRef}
+  role="button"
+  tabIndex={0}
+  onKeyDown={(e) => {
+    if (e.key === "Enter" || e.key === " ") {
+      e.preventDefault();
+      handleFileButtonClick();
+    }
+  }}>
libs/portal-plugin-dashboard/src/ui/dialogs/uploadAvatar.tsx (1)

7-7: Loosen currentAvatar type to optional.

The caller already passes a possibly empty value; the config shouldn’t force a string.

-  currentAvatar: string,
+  currentAvatar?: string,
libs/portal-plugin-dashboard/src/ui/widgets/account/bio.tsx (2)

43-46: Uppercase the fallback initial consistently.

Minor UX polish for placeholder glyph.

-  {identity.firstName?.charAt(0) ||
-    identity.lastName?.charAt(0) ||
-    "?"}
+  {(identity.firstName?.charAt(0) ||
+    identity.lastName?.charAt(0) ||
+    "?")?.toUpperCase()}

49-62: Add accessible label to the avatar edit button.

Improves screen-reader support.

-<Button
+<Button
   className="absolute -bottom-2 -right-2 h-8 w-8 rounded-full p-0"
+  aria-label="Change profile picture"
+  title="Change profile picture"
   onClick={() =>
     openDialog(
       uploadAvatarDialogConfig(
         displayName,
         identity.avatar || "/placeholder.svg",
         handleAvatarUpdate,
       ),
     )
   }
   size="sm"
   variant="secondary">
📜 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 7ab473d and d3a5da5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • libs/portal-framework-auth/package.json (1 hunks)
  • libs/portal-framework-auth/src/dataProviders/auth.ts (7 hunks)
  • libs/portal-framework-auth/src/ui/forms/login.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/MainNavigation.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/form/SchemaForm.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx (2 hunks)
  • libs/portal-framework-ui/src/components/layout/UserNav.tsx (1 hunks)
  • libs/portal-framework-ui/src/index.ts (1 hunks)
  • libs/portal-plugin-dashboard/package.json (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/dialogs/uploadAvatar.tsx (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/widgets/account/bio.tsx (4 hunks)
  • package.json (1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
libs/portal-framework-ui/src/components/form/SchemaForm.tsx

[error] 82-82: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (9)
libs/portal-framework-auth/package.json (1)

24-24: LGTM: runtime dep is correct for client-only cookie use.
No action needed here.

libs/portal-framework-ui/src/index.ts (1)

16-16: Re-export looks good; verify it’s built and published.
Ensure the hook file exists and appears in the built dist/types to prevent broken imports.

package.json (1)

98-98: PNPM version bump—confirm CI uses the same version.
Prevents lockfile churn and inconsistent installs.

libs/portal-framework-auth/src/ui/forms/login.tsx (2)

19-21: Apply SSR-safe cookie read, reuse constant, and mark schema property optional
Ensure in login.tsx you guard Cookies.get behind a typeof document !== "undefined" check and reference a shared REMEMBER_ME_COOKIE constant, and update login.schema.ts to include remember: z.boolean().optional().

-  defaultValues: {
-    remember: Cookies.get("remember_me") === "true",
-  },
+  defaultValues: {
+    remember:
+      typeof document !== "undefined" &&
+      Cookies.get(REMEMBER_ME_COOKIE) === "true",
+  },
// login.schema.ts
// add:
remember: z.boolean().optional();

62-65: Verify cookie path/domain when removing “remember_me”
Cookies.remove() defaults to path="/" and the current host domain. Confirm this matches how your backend sets the “remember_me” cookie; if not, explicitly pass matching options:

if (!data.remember) {
  Cookies.remove("remember_me", { path: "/", domain: ".example.com" });
}
libs/portal-framework-ui/src/components/form/SchemaForm.tsx (1)

98-101: Confirm whether dialog-aware config should be used beyond FormFooter

You compute finalConfig but only pass it to <FormFooter>. If dialog metadata should influence other areas (header/actions/submit), consider threading finalConfig consistently or confirm the footer-only usage is intentional.

Also applies to: 152-154

libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx (1)

95-98: Prefer useAccountUrl over manual URL parsing
useAccountUrl(path: string) (exported in libs/portal-framework-ui/src/hooks/useAccountUrl.ts) returns a fully-qualified account URL—use it instead of extracting protocol and hostname from useApiUrl(). For example:

-import { useApiUrl } from "@lumeweb/portal-framework-ui";
+import { useAccountUrl } from "@lumeweb/portal-framework-ui";
@@
-const apiUrl = useApiUrl();
-const apiDomain = new URL(apiUrl).hostname;
-const apiProto = new URL(apiUrl).protocol;
+const accountUrl = useAccountUrl("");
@@
-  .use(XHRUpload, {
-    endpoint: `${apiProto}//account.${apiDomain}${ENDPOINT}`,
+  .use(XHRUpload, {
+    endpoint: new URL(ENDPOINT, accountUrl).toString(),
@@
-}, [apiProto, apiUrl]);
+}, [accountUrl]);

Verify that useAccountUrl returns the correct base URL (protocol + hostname) before merging.

libs/portal-plugin-dashboard/src/ui/widgets/account/bio.tsx (2)

78-96: LGTM on created-at rendering.

Date parsing and guarded rendering look good.


28-31: Incorrect suggestion: openDialog doesn’t return an ID and closeDialog only takes a source flag, not an ID. The built-in useDialog implementation simply pushes the config onto an internal stack (no return value) and its closeDialog callback signature is (source?: "programmatic" | "user")—it cannot close by ID.

Likely an incorrect or invalid review comment.

Copy link
Copy Markdown

@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 (2)
libs/portal-framework-auth/src/dataProviders/auth.ts (2)

100-103: Interface rename aligns with usages

Consistent AuthProviderWithEmitter resolves the prior mismatch.


343-348: LGTM: logout uses dedicated error name and redirect constant

Addresses the earlier suggestion to avoid reusing the registration error name.

🧹 Nitpick comments (9)
libs/portal-framework-auth/src/dataProviders/auth.ts (4)

34-36: Avoid duplicating SDK types for OTP response

Re-export the SDK’s type to prevent divergence and naming collisions across modules.

-export interface OTPGenerateResponse {
-  otp: string;
-}
+export type OTPGenerateResponse =
+  import("@lumeweb/portal-sdk").OTPGenerateResponse;

68-89: Make wrapper accept unknown and preserve original error robustly

Accept unknown, preserve cause, and drop the redundant prototype tweak. This also lets you use it directly in catch blocks.

-// Helper function to wrap error objects with custom names
-const wrapErrorWithName = (error: Error, name: string): Error => {
-  const e = new Error(error.message);
-  e.name = name;
-  e.stack = error.stack;
-  if ((error as any).cause) {
-    e.cause = (error as any).cause;
-  }
-  
-  // Copy non-standard enumerable properties
-  Object.keys(error).forEach(key => {
-    if (!(key in e)) {
-      (e as any)[key] = (error as any)[key];
-    }
-  });
-  
-  // Ensure proper prototype chain
-  Object.setPrototypeOf(e, Error.prototype);
-  
-  return e;
-};
+// Helper function to wrap unknown errors with a custom name
+const wrapErrorWithName = (error: unknown, name: string): Error => {
+  const original = error instanceof Error ? error : new Error(String(error));
+  const e = new Error(original.message);
+  e.name = name;
+  e.stack = original.stack;
+  if ((original as any).cause) (e as any).cause = (original as any).cause;
+  Object.keys(original).forEach((key) => {
+    if (!(key in e)) (e as any)[key] = (original as any)[key];
+  });
+  return e;
+};

236-238: Preserve intended post-login redirect when OTP validation fails

Keep the to query param so users aren’t bounced to OTP without the original target.

-              error: wrapErrorWithName(response.error, LOGIN_ERROR_NAME),
-              redirectTo: OTP_PATH,
+              error: wrapErrorWithName(response.error, LOGIN_ERROR_NAME),
+              redirectTo: `${OTP_PATH}${
+                params.redirectTo
+                  ? `?to=${encodeURIComponent(params.redirectTo)}`
+                  : ""
+              }`,

320-321: Name unexpected login errors consistently

Use the wrapper for thrown/unknown errors to keep telemetry uniform.

-          error: error as Error,
+          error: wrapErrorWithName(error, LOGIN_ERROR_NAME),
           redirectTo: LOGIN_PATH,
libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx (5)

164-177: Prevent state updates after unmount; clear the success timeout.

If the dialog closes within the 500ms delay, setState will run on an unmounted component. Track and clear the timer on unmount.

Apply:

@@
-        setTimeout(() => {
+        // store timeout so we can clear it on unmount
+        successTimerRef.current = window.setTimeout(() => {
           setUploading(false);
           resetState();
           onSuccess();
@@
-        }, 500);
+        }, 500);
@@
-    return () => {
-      if (uppyRef.current) {
-        uppyRef.current.destroy();
-      }
-    };
+    return () => {
+      if (successTimerRef.current !== null) {
+        clearTimeout(successTimerRef.current);
+        successTimerRef.current = null;
+      }
+      if (uppyRef.current) {
+        uppyRef.current.destroy();
+      }
+    };

And add the ref:

@@
-  const { open } = useNotification();
+  const { open } = useNotification();
+  const successTimerRef = useRef<number | null>(null);

Also applies to: 137-141


108-113: Revoke preview object URL on unmount to avoid blob leaks.

If the dialog closes mid-flow, the current previewUrl isn’t revoked. Add a small cleanup effect.

Apply:

@@
   const resetState = () => {
@@
   };
 
+  // Revoke preview URL if component unmounts with a preview active
+  useEffect(() => {
+    return () => {
+      if (previewUrl) {
+        try { URL.revokeObjectURL(previewUrl); } catch {}
+      }
+    };
+  }, [previewUrl]);

Also applies to: 137-141


118-123: Tighten allowed file types (avoid SVG).

image/* admits SVG; if the server later serves avatars with an incorrect content-type or uses them in non-img contexts, SVG can be risky. Prefer explicit safe types; keep server-side validation regardless.

Apply:

-      restrictions: {
-        allowedFileTypes: ["image/*"],
+      restrictions: {
+        allowedFileTypes: ["image/png", "image/jpeg", "image/webp", "image/gif"],
         maxFileSize: 5 * 1024 * 1024, // 5MB
         maxNumberOfFiles: 1,
       },

154-160: Guard against zero total in upload-progress.

Avoid NaN if bytesTotal is 0 (rare but possible at start).

Apply:

-      const percentage = (progress.bytesUploaded / progress.bytesTotal) * 100;
+      const percentage =
+        progress.bytesTotal > 0
+          ? (progress.bytesUploaded / progress.bytesTotal) * 100
+          : 0;
       setUploadProgress(percentage);

133-135: Prefer targeting the dropzone instead of document.body.

If containerRef isn’t ready at init, DropTarget binds to body and stays global. Initialize only when the container is available so drops outside don’t trigger the upload. Also make the dropzone clickable and more accessible.

Apply:

@@
-      .use(DropTarget, {
-        target: containerRef.current ?? document.body,
-      });
+      .use(DropTarget, {
+        target: containerRef.current!,
+      });
@@
-  }, [accountUrl]);
+  }, [accountUrl, containerRef]);

And make the zone clickable + accessible:

@@
-        <div
+        <div
           className={cn(
             "rounded-lg border-2 border-dashed p-8 text-center transition-colors",
             "border-muted hover:border-muted/50",
           )}
           ref={containerRef}
-          role="button"
+          role="button"
           tabIndex={0}
+          aria-label="Upload profile picture. Press Enter, Space, or click to choose a file, or drag and drop an image."
+          onClick={handleFileButtonClick}
           onKeyDown={(e) => {
             if (e.key === "Enter" || e.key === " ") {
               e.preventDefault();
               handleFileButtonClick();
             }
           }}>

Note: If your linter flags the containerRef dependency, consider a guard inside the effect:

if (!containerRef.current) return;

Also applies to: 260-273, 276-335

📜 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 d3a5da5 and bba6f72.

📒 Files selected for processing (5)
  • libs/portal-framework-auth/src/dataProviders/auth.ts (10 hunks)
  • libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx (2 hunks)
  • libs/portal-framework-ui/src/components/layout/UserNav.tsx (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/dialogs/uploadAvatar.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • libs/portal-framework-ui/src/components/layout/UserNav.tsx
  • libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
libs/portal-plugin-dashboard/src/ui/dialogs/uploadAvatar.tsx (1)
libs/portal-framework-ui/src/components/dialog/Dialog.types.ts (1)
  • CustomDialogConfig (68-71)
libs/portal-framework-auth/src/dataProviders/auth.ts (1)
libs/portal-sdk/src/account/generated/accountAPI.schemas.ts (2)
  • OTPGenerateResponse (99-101)
  • Error (74-78)
🔇 Additional comments (11)
libs/portal-framework-auth/src/dataProviders/auth.ts (8)

90-94: LGTM: clear, scoped error name constants

Good normalization across flows.


95-99: LGTM: centralized redirect path constants

This removes stringly-typed paths; nice.


142-142: LGTM: check() redirect uses constant

Keeps redirects consistent.


256-257: LGTM: OTP success redirect respects caller or defaults to dashboard

Consistent with the non-OTP path.


273-273: LGTM: normalized login error naming

Consistent error tagging helps UX/telemetry.


280-282: LGTM: OTP gate keeps destination via query param

Solid.


304-305: LGTM: post-login redirect constant

All good.


374-378: LGTM: registration error naming + redirect constant

Consistent handling and UX messaging.

libs/portal-plugin-dashboard/src/ui/dialogs/uploadAvatar.tsx (1)

5-21: LGTM: clean dialog config wrapper.

Straightforward config and composition with AvatarUpload. No issues.

libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx (2)

10-12: Types look correct now for Uppy.

The previous namespace-type issue appears resolved (importing Uppy and UppyFile directly). Good fix.

Also applies to: 94-95


1-1: Use useAccountUrl with ENDPOINT param for XHRUpload endpoint
Pass ENDPOINT directly to useAccountUrl and use its return value instead of manually constructing the URL:

-import { useApiUrl } from "@lumeweb/portal-framework-ui";
+import { useAccountUrl } from "@lumeweb/portal-framework-ui";
@@
-  const apiUrl = useApiUrl();
+  const accountUrl = useAccountUrl(ENDPOINT);
@@
-        endpoint: new URL(ENDPOINT, accountUrl).toString(),
+        endpoint: accountUrl,

Likely an incorrect or invalid review comment.

@pcfreak30 pcfreak30 force-pushed the libs/portal-plugin-dashboard branch from 1cf1abf to d3738fe Compare August 30, 2025 06:45
Copy link
Copy Markdown

@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

Caution

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

⚠️ Outside diff range comments (2)
libs/portal-framework-auth/src/dataProviders/auth.ts (1)

285-304: Missing authCheckSuccess emit on non-OTP login success.
Event is emitted for OTP success but not here; subscribers will miss the signal.

Apply this diff:

         if (response.data.token) {
           sdk.setAuthToken(response.data.token);
+          emitter.emit("authCheckSuccess", { token: response.data.token });
           const baseUrl = getApiBaseUrl();
libs/portal-framework-ui/src/components/form/SchemaForm.tsx (1)

40-46: Fix Rules of Hooks violation: early return before hooks

Biome is correct: hooks (useDialog, useNotification, useEffect, adapter hooks) are called after an early return, which breaks hook call order when active === false. Move the early return to wrap the final JSX instead of short-circuiting before hooks.

Apply this diff:

@@
 export function SchemaForm<T extends FieldValues = FieldValues>({
   active = true,
   closeDialog = () => void 0,
   config,
 }: SchemaFormProps<T>) {
-  if (!active) {
-    return null;
-  }
   const { currentDialog, setFormMethods: setFormInstance } = useDialog();
   const { open: openNotification } = useNotification();
   if (!config) throw new Error("SchemaForm requires a form config");
@@
-  return (
+  return active ? (
     <FormProvider<T>
       adapter={adapterName as keyof typeof adapters}
       autoSave={autoSaveProps}
       config={cConfig}
       formInstance={formInstance}>
@@
-    </FormProvider>
-  );
+    </FormProvider>
+  ) : null;
 }

Also applies to: 104-166

♻️ Duplicate comments (3)
libs/portal-framework-auth/src/dataProviders/auth.ts (3)

67-79: LGTM: Error wrapping now preserves Error semantics (name/stack/cause).


80-84: LGTM: Introduced named error constants (incl. LOGOUT).


90-93: LGTM: Interface naming fixed and consistent with return type.

🧹 Nitpick comments (6)
libs/portal-framework-auth/src/dataProviders/auth.ts (4)

85-89: Centralize route constants in a shared module.
Avoid drift across packages (auth/ui) by exporting these from a common constants module.


226-233: Sanitize the “to” redirect target to prevent open-redirects.
Ensure OTP page only honors relative paths or a strict allowlist before navigating.

Also applies to: 274-276


115-121: Guard localStorage access and scope it to localhost.
Add SSR-safe checks and mirror the localhost gate in maybeSetupAuth to avoid picking up stray tokens in non-localhost environments.

Apply this diff:

   const maybeSetupAuth = () => {
-    const token = localStorage.getItem("jwt");
-    if (token) {
-      sdk.setAuthToken(token);
-    }
+    if (typeof window === "undefined") return;
+    try {
+      const baseUrl = getApiBaseUrl();
+      const isLocal = !!baseUrl && new URL(baseUrl).hostname === "localhost";
+      if (!isLocal) return;
+      const token = window.localStorage?.getItem("jwt");
+      if (token) sdk.setAuthToken(token);
+    } catch {
+      /* noop */
+    }
   };

And wrap the other localStorage set/remove sites with typeof window checks similarly.

Also applies to: 240-249, 288-296


145-190: Align error taxonomy across flows.
Use named errors for forgotPassword/updatePassword for consistency (e.g., PASSWORD_RESET_ERROR_NAME, UPDATE_PASSWORD_ERROR_NAME) and wrap with wrapErrorWithName.

Also applies to: 381-399

libs/portal-framework-ui/src/components/form/SchemaForm.tsx (2)

82-86: Set/clear form instance in dialog lifecycle

Setting form methods only when a dialog is active is correct. Consider clearing the instance on unmount or when the dialog deactivates to avoid stale references being read by consumers.

If setFormMethods accepts undefined/null, add a cleanup:

useEffect(() => {
  if (!setFormInstance) return;
  if (isActiveDialog) setFormInstance(formInstance);
  return () => {
    // clear on unmount or when dialog closes
    try { setFormInstance(undefined as any); } catch {}
  };
}, [formInstance, setFormInstance, isActiveDialog]);

Please confirm the accepted type for clearing; if not allowed, the dialog context should handle clearing internally on close.


98-101: finalConfig merge is correct; memoization optional

Merging currentDialog with the updated formConfig is correct. If you observe unnecessary re-renders of FormFooter, you can memoize finalConfig. Low priority.

Example:

const finalConfig = useMemo(
  () => (isActiveDialog ? { ...currentDialog!, formConfig: cConfig } : cConfig),
  [isActiveDialog, currentDialog, cConfig],
);
📜 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 bba6f72 and d3738fe.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (13)
  • libs/portal-framework-auth/package.json (1 hunks)
  • libs/portal-framework-auth/src/dataProviders/auth.ts (10 hunks)
  • libs/portal-framework-auth/src/ui/forms/login.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/MainNavigation.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/form/SchemaForm.tsx (3 hunks)
  • libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx (2 hunks)
  • libs/portal-framework-ui/src/components/layout/UserNav.tsx (1 hunks)
  • libs/portal-framework-ui/src/index.ts (1 hunks)
  • libs/portal-plugin-dashboard/package.json (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/dialogs/uploadAvatar.tsx (1 hunks)
  • libs/portal-plugin-dashboard/src/ui/widgets/account/bio.tsx (4 hunks)
  • package.json (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (11)
  • libs/portal-framework-auth/package.json
  • libs/portal-framework-ui/src/index.ts
  • libs/portal-plugin-dashboard/src/ui/dialogs/uploadAvatar.tsx
  • libs/portal-framework-ui/src/components/layout/UserNav.tsx
  • libs/portal-plugin-dashboard/package.json
  • libs/portal-plugin-dashboard/src/ui/components/AvatarUpload.tsx
  • package.json
  • libs/portal-framework-auth/src/ui/forms/login.tsx
  • libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx
  • libs/portal-framework-ui/src/components/MainNavigation.tsx
  • libs/portal-plugin-dashboard/src/ui/widgets/account/bio.tsx
🧰 Additional context used
🧬 Code graph analysis (2)
libs/portal-framework-auth/src/dataProviders/auth.ts (1)
libs/portal-sdk/src/account/generated/accountAPI.schemas.ts (1)
  • OTPGenerateResponse (99-101)
libs/portal-framework-ui/src/components/form/SchemaForm.tsx (2)
libs/portal-framework-ui/src/components/form/types.ts (1)
  • FormConfig (28-108)
libs/portal-framework-ui/src/components/dialog/Dialog.types.ts (1)
  • FormDialogConfig (177-195)
🪛 Biome (2.1.2)
libs/portal-framework-ui/src/components/form/SchemaForm.tsx

[error] 82-82: This hook is being called conditionally, but all hooks must be called in the exact same order in every component render.

Hooks should not be called after an early return.

For React to preserve state between calls, hooks needs to be called unconditionally and always in the same order.
See https://reactjs.org/docs/hooks-rules.html#only-call-hooks-at-the-top-level

(lint/correctness/useHookAtTopLevel)

🔇 Additional comments (9)
libs/portal-framework-auth/src/dataProviders/auth.ts (7)

34-35: LGTM: SDK-sourced OTP type re-export is correct.


132-132: LGTM: check() redirects to login on auth failure.


250-250: LGTM: OTP success redirect uses fallback DASHBOARD_PATH.


267-267: LGTM: Login errors wrapped with LOGIN_ERROR_NAME.


313-315: LGTM: Catch wraps with LOGIN_ERROR_NAME and redirects to login.


337-342: LGTM: Logout wraps errors with LOGOUT_ERROR_NAME and redirects to login.


367-372: LGTM: Registration errors wrapped; success redirects to login.

libs/portal-framework-ui/src/components/form/SchemaForm.tsx (2)

80-80: Dialog activity check looks good

Using !!currentDialog?.formConfig is a clear, cheap signal to detect a form dialog context. No issues.


116-116: Class reordering is harmless

Reordered classes don’t change behavior; horizontal layout remains intact.

@pcfreak30 pcfreak30 force-pushed the libs/portal-plugin-dashboard branch from d3738fe to 2934068 Compare August 30, 2025 06:55
…ard): implement remember me functionality and various UI enhancements

* **Authentication Provider (`libs/portal-framework-auth/src/dataProviders/auth.ts`)**:
  * Added `js-cookie` as a dependency
  * Introduced `wrapErrorWithName` helper function to provide more context to authentication errors
  * Defined constants for error names (`LOGIN_ERROR_NAME`, `REGISTRATION_ERROR_NAME`) and redirect paths (`LOGIN_PATH`, `OTP_PATH`, `DASHBOARD_PATH`)
  * Refactored redirect paths to use constants instead of hardcoded strings
  * Applied error wrapping in login and registration responses for better error context
* **Login Form (`libs/portal-framework-auth/src/ui/forms/login.tsx`)**:
  * Added "remember me" checkbox field to the form configuration
  * Set default value for "remember me" checkbox based on existing cookie
  * Removed "remember me" cookie if the checkbox is unchecked during form submission
* **UI Components**:
  * **Main Navigation (`libs/portal-framework-ui/src/components/MainNavigation.tsx`)**:
    * Fixed the `href` generation logic for child navigation items, particularly handling index routes correctly
  * **Schema Form (`libs/portal-framework-ui/src/components/form/SchemaForm.tsx`)**:
    * Improved form instance handling within dialogs by adding an `isActiveDialog` check
  * **Desktop Sidebar (`libs/portal-framework-ui/src/components/layout/DesktopSidebar.tsx`)**:
    * Adjusted CSS classes for layout and transition effects
  * **User Navigation (`libs/portal-framework-ui/src/components/layout/UserNav.tsx`)**:
    * Updated avatar image `alt` text to use the user's full name
    * Improved avatar fallback logic to handle cases where first or last name might be missing
* **Dashboard Plugin**:
  * **New Components**:
    * Added `AvatarUpload.tsx` component for profile picture management, utilizing Uppy for drag-and-drop and file upload capabilities
    * Added `uploadAvatar.tsx` dialog configuration for the avatar upload feature
  * **Bio Widget (`libs/portal-plugin-dashboard/src/ui/widgets/account/bio.tsx`)**:
    * Integrated the avatar upload dialog trigger
    * Added functionality to refetch user identity after a successful avatar update
* **Core UI (`libs/portal-framework-ui/src/index.ts`)**:
  * Exported the new `useAccountUrl` hook
* **Dependencies & Lockfile (`package.json`, `pnpm-lock.yaml`)**:
  * Updated `pnpm` version in `package.json`
  * Added `@uppy/*` dependencies for file upload functionality
  * Updated/refined `@refinedev/*` package resolutions in the lockfile
  * Moved `tailwind-scrollbar` from devDependencies to dependencies in the lockfile
@pcfreak30 pcfreak30 force-pushed the libs/portal-plugin-dashboard branch from 2934068 to 7246267 Compare August 30, 2025 06:56
@pcfreak30 pcfreak30 merged commit 450c891 into develop Aug 30, 2025
1 of 2 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