Skip to content

feat: SDK general page design revamp#1480

Open
rohanchkrabrty wants to merge 2 commits intomainfrom
feat-general-revamp
Open

feat: SDK general page design revamp#1480
rohanchkrabrty wants to merge 2 commits intomainfrom
feat-general-revamp

Conversation

@rohanchkrabrty
Copy link
Copy Markdown
Contributor

@rohanchkrabrty rohanchkrabrty commented Mar 26, 2026

Summary

  • Revamp the General settings page UI under views-new/general/ using @raystack/apsara-v1 components
    • @raystack/apsara-v1 is just an alias for the @raystack/apsara@v1.0.0.rc.1. This is needed temporarily to not break existing pages.
  • Add reusable components: ViewContainer (page layout wrapper), ViewHeader (page title/description), ImageUpload (avatar/image upload with crop dialog)
  • Add DeleteOrganizationDialog with self-contained delete logic (form, mutation, validation)
  • Add client-demo Settings page (/:orgId/settings) with Apsara Sidebar and nested routes
  • Add "NEW UI" link per organization on the Home page

@rohanchkrabrty rohanchkrabrty self-assigned this Mar 26, 2026
@vercel
Copy link
Copy Markdown

vercel bot commented Mar 26, 2026

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

Project Deployment Actions Updated (UTC)
frontier Ready Ready Preview, Comment Mar 26, 2026 8:36pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 26, 2026

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added organization settings page with general configuration and management options
    • Added image upload capability for organization avatars
    • Added organization deletion functionality with confirmation dialog
  • Chores

    • Upgraded React framework from v18 to v19
    • Updated design system dependencies and styling framework

Walkthrough

This PR upgrades React and TypeScript types from v18 to v19 in the client-demo app, introduces new organization settings UI components (ImageUpload, ViewContainer, ViewHeader, GeneralView, DeleteOrganizationDialog) to the SDK, adds a Settings page with nested routing to the app, updates package dependencies, and modifies SheetFooter styling.

Changes

Cohort / File(s) Summary
React Version Upgrade
web/apps/client-demo/package.json
Updated react and react-dom from ^18.3.1 to ^19.2.1; updated @types/react and @types/react-dom to React 19 versions.
Client-Demo App Integration
web/apps/client-demo/src/App.tsx, web/apps/client-demo/src/Router.tsx, web/apps/client-demo/src/styles.css
Added global stylesheet imports, extended routing with nested /settings paths, added body margin/padding reset CSS.
Client-Demo Pages
web/apps/client-demo/src/pages/Home.tsx, web/apps/client-demo/src/pages/Settings.tsx, web/apps/client-demo/src/pages/settings/General.tsx
Added Settings page component with organization resolution and nested layout; added General sub-page; modified Home page to render settings links per organization.
SDK Package Configuration
web/sdk/package.json, web/sdk/tsup.config.ts
Removed @stitches/react dependency; upgraded peer dependencies to React ^19; added @raystack/apsara-v1 and @base-ui/react; marked react-dom as external in tsup build config.
SDK Image Upload Component
web/sdk/react/components/image-upload/*
Added new ImageUpload component with file input, image cropping dialog, canvas-based image processing, and CSS module styling.
SDK View Container Component
web/sdk/react/components/view-container/*
Added new ViewContainer component with configurable layout and CSS module for container/content styling.
SDK View Header Component
web/sdk/react/components/view-header/*
Added new ViewHeader component displaying title and optional description with CSS styling.
SDK General Settings View
web/sdk/react/views-new/general/*
Added GeneralView component with form validation (Yup), organization update/delete mutations, permission checks, DeleteOrganizationDialog with cascading delete confirmation, and associated CSS modules.
SDK Export Updates
web/sdk/react/index.ts
Added imports for new apsara stylesheets; exported new components: ImageUpload, ViewContainer, ViewHeader, GeneralView.
Existing Component Modification
web/sdk/admin/components/SheetFooter.tsx
Removed optional css prop from SheetFooterProps; removed style merging logic; updated borderTop color token to CSS variable.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • feat: move sdk billing #1425: Overlapping changes to the SDK General organization settings view and DeleteOrganizationDialog component; both modify the same GeneralView exports and routing surface.
  • feat: move sdk general #1414: Related modifications to organization General view components and SDK exports with similar route and settings UI patterns.

Suggested reviewers

  • rsbh
  • rohilsurana
✨ Finishing Touches
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat-general-revamp

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown
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: 7


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d4a8fab5-aa99-40ce-bd99-938852a3637f

📥 Commits

Reviewing files that changed from the base of the PR and between d05d9e5 and b6b1082.

⛔ Files ignored due to path filters (1)
  • web/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (24)
  • web/apps/client-demo/package.json
  • web/apps/client-demo/src/App.tsx
  • web/apps/client-demo/src/Router.tsx
  • web/apps/client-demo/src/pages/Home.tsx
  • web/apps/client-demo/src/pages/Settings.tsx
  • web/apps/client-demo/src/pages/settings/General.tsx
  • web/apps/client-demo/src/styles.css
  • web/sdk/admin/components/SheetFooter.tsx
  • web/sdk/package.json
  • web/sdk/react/components/image-upload/image-upload.module.css
  • web/sdk/react/components/image-upload/image-upload.tsx
  • web/sdk/react/components/image-upload/index.ts
  • web/sdk/react/components/view-container/index.ts
  • web/sdk/react/components/view-container/view-container.module.css
  • web/sdk/react/components/view-container/view-container.tsx
  • web/sdk/react/components/view-header/index.ts
  • web/sdk/react/components/view-header/view-header.tsx
  • web/sdk/react/index.ts
  • web/sdk/react/views-new/general/components/delete-organization-dialog.module.css
  • web/sdk/react/views-new/general/components/delete-organization-dialog.tsx
  • web/sdk/react/views-new/general/general-view.module.css
  • web/sdk/react/views-new/general/general-view.tsx
  • web/sdk/react/views-new/general/index.ts
  • web/sdk/tsup.config.ts

Comment on lines +63 to +66
<Link
to={`/${org.name}/settings`}
data-test-id={`[organization-new-ui-link-${org.name}]`}
>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use a stable, URL-safe identifier in the settings link path.
Line 64 currently builds the route from org.name, which is not guaranteed unique or path-safe. This can produce broken or ambiguous navigation.

💡 Suggested fix
-              <Link
-                to={`/${org.name}/settings`}
-                data-test-id={`[organization-new-ui-link-${org.name}]`}
-              >
+              <Link
+                to={`/${encodeURIComponent(org.id)}/settings`}
+                data-test-id={`[organization-new-ui-link-${org.id}]`}
+              >
📝 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
<Link
to={`/${org.name}/settings`}
data-test-id={`[organization-new-ui-link-${org.name}]`}
>
<Link
to={`/${encodeURIComponent(org.id)}/settings`}
data-test-id={`[organization-new-ui-link-${org.id}]`}
>

Comment on lines +16 to +22
useEffect(() => {
if (!orgId || organizations.length === 0) return;
const org = organizations.find(_org => _org.id === orgId || _org.name === orgId);
if (org && activeOrganization?.id !== org.id) {
setActiveOrganization(org);
}
}, [orgId, organizations, activeOrganization?.id, setActiveOrganization]);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle unmatched orgId explicitly to avoid stale organization context.

On Line 18–Line 21, when no org matches orgId, activeOrganization remains whatever was previously selected. That can render/update the wrong org in nested settings.

Proposed fix
+  const matchedOrg = organizations.find(
+    _org => _org.id === orgId || _org.name === orgId
+  );
+
   useEffect(() => {
     if (!orgId || organizations.length === 0) return;
-    const org = organizations.find(_org => _org.id === orgId || _org.name === orgId);
-    if (org && activeOrganization?.id !== org.id) {
-      setActiveOrganization(org);
+    if (matchedOrg && activeOrganization?.id !== matchedOrg.id) {
+      setActiveOrganization(matchedOrg);
     }
-  }, [orgId, organizations, activeOrganization?.id, setActiveOrganization]);
+  }, [orgId, organizations.length, matchedOrg, activeOrganization?.id, setActiveOrganization]);
+
+  if (orgId && organizations.length > 0 && !matchedOrg) {
+    return <Navigate to="/" replace />;
+  }

Comment on lines +8 to +10
export function SheetFooter({ children }: SheetFooterProps) {
// @ts-ignore
return <Flex style={{ ...styles.footer, ...css }}>{children}</Flex>;
return <Flex style={styles.footer}>{children}</Flex>;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect current implementation
sed -n '1,120p' web/sdk/admin/components/SheetFooter.tsx

# Check whether this pattern appears elsewhere (ts-ignore + Flex style usage)
rg -n -C2 '@ts-ignore|<Flex[^>]*\bstyle=' web/sdk/admin/components web/sdk/admin/views

# Compare with existing Flex css-prop usage in nearby code
rg -n -C1 '<Flex[^>]*\bcss=' web/sdk/admin/components web/sdk/admin/views

Repository: raystack/frontier

Length of output: 15370


🏁 Script executed:

# Inspect SheetHeader.tsx to see how it handles styles without `@ts-ignore`
cat -n web/sdk/admin/components/SheetHeader.tsx

# Check the type definitions for Flex component from apsara
rg -A 10 "export.*Flex.*Props|interface.*FlexProps|type.*FlexProps" --type tsx --type ts

Repository: raystack/frontier

Length of output: 1126


Remove @ts-ignore and type the styles object with CSSProperties.

The companion component SheetHeader.tsx in the same directory uses the exact same pattern without type suppression by importing CSSProperties and explicitly typing the styles object. Apply the same approach here to eliminate the suppression.

Proposed fix
-import React from "react";
+import React, { type CSSProperties } from "react";
 import { Flex } from "@raystack/apsara";
 
 type SheetFooterProps = {
   children?: React.ReactNode;
 };
 
 export function SheetFooter({ children }: SheetFooterProps) {
-  // `@ts-ignore`
   return <Flex style={styles.footer}>{children}</Flex>;
 }
 
-const styles = {
+const styles: { footer: CSSProperties } = {
   footer: {
     bottom: 0,
     left: 0,

Comment on lines +59 to +63
const height = ((crop?.height || 0) * image.height) / 100;
const width = ((crop?.width || 0) * image.width) / 100;
const x = ((crop?.x || 0) * image.width) / 100;
const y = ((crop?.y || 0) * image.width) / 100;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix Y-axis crop calculation (uses width instead of height).

Line 62 computes y using image.width; this skews vertical crop positioning.

Proposed fix
-    const y = ((crop?.y || 0) * image.width) / 100;
+    const y = ((crop?.y || 0) * image.height) / 100;
📝 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 height = ((crop?.height || 0) * image.height) / 100;
const width = ((crop?.width || 0) * image.width) / 100;
const x = ((crop?.x || 0) * image.width) / 100;
const y = ((crop?.y || 0) * image.width) / 100;
const height = ((crop?.height || 0) * image.height) / 100;
const width = ((crop?.width || 0) * image.width) / 100;
const x = ((crop?.x || 0) * image.width) / 100;
const y = ((crop?.y || 0) * image.height) / 100;

Comment on lines +176 to +184
function onFileChange(e: ChangeEvent<HTMLInputElement>) {
const files = e.target.files || [];
if (files.length > 0) {
const file = files[0];
const imageUrl = URL.createObjectURL(file);
setImgSrc(imageUrl);
setShowCropDialog(true);
e.target.files = null;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Revoke object URLs after upload flow to prevent memory leaks.

Line 180 creates a blob URL, but it’s never revoked on subsequent uploads or unmount.

Proposed fix
-import { useRef, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
...
   const [imgSrc, setImgSrc] = useState('');
+
+  useEffect(() => {
+    return () => {
+      if (imgSrc.startsWith('blob:')) URL.revokeObjectURL(imgSrc);
+    };
+  }, [imgSrc]);
...
   function onFileChange(e: ChangeEvent<HTMLInputElement>) {
     const files = e.target.files || [];
     if (files.length > 0) {
       const file = files[0];
       const imageUrl = URL.createObjectURL(file);
-      setImgSrc(imageUrl);
+      setImgSrc(prev => {
+        if (prev.startsWith('blob:')) URL.revokeObjectURL(prev);
+        return imageUrl;
+      });
       setShowCropDialog(true);
       e.target.files = null;
     }
   }
📝 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
function onFileChange(e: ChangeEvent<HTMLInputElement>) {
const files = e.target.files || [];
if (files.length > 0) {
const file = files[0];
const imageUrl = URL.createObjectURL(file);
setImgSrc(imageUrl);
setShowCropDialog(true);
e.target.files = null;
}
function onFileChange(e: ChangeEvent<HTMLInputElement>) {
const files = e.target.files || [];
if (files.length > 0) {
const file = files[0];
const imageUrl = URL.createObjectURL(file);
setImgSrc(prev => {
if (prev.startsWith('blob:')) URL.revokeObjectURL(prev);
return imageUrl;
});
setShowCropDialog(true);
e.target.files = null;
}
}

Comment on lines +45 to +61
const [isAcknowledged, setIsAcknowledged] = useState(false);

const { mutateAsync: deleteOrganization } = useMutation(
FrontierServiceQueries.deleteOrganization
);

const {
register,
handleSubmit,
watch,
setError,
formState: { errors, isSubmitting }
} = useForm({
resolver: yupResolver(deleteOrgSchema)
});

const deleteTitle = watch('title') ?? '';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reset confirmation state when dialog is reopened.

isAcknowledged and typed title persist across open/close cycles, which is risky for a destructive flow.

Proposed fix
-import { useState } from 'react';
+import { useEffect, useState } from 'react';
...
   const {
     register,
     handleSubmit,
     watch,
+    reset,
     setError,
     formState: { errors, isSubmitting }
   } = useForm({
     resolver: yupResolver(deleteOrgSchema)
   });
+
+  useEffect(() => {
+    if (open) {
+      reset({ title: '' });
+      setIsAcknowledged(false);
+    }
+  }, [open, reset]);
📝 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 [isAcknowledged, setIsAcknowledged] = useState(false);
const { mutateAsync: deleteOrganization } = useMutation(
FrontierServiceQueries.deleteOrganization
);
const {
register,
handleSubmit,
watch,
setError,
formState: { errors, isSubmitting }
} = useForm({
resolver: yupResolver(deleteOrgSchema)
});
const deleteTitle = watch('title') ?? '';
const [isAcknowledged, setIsAcknowledged] = useState(false);
const { mutateAsync: deleteOrganization } = useMutation(
FrontierServiceQueries.deleteOrganization
);
const {
register,
handleSubmit,
watch,
reset,
setError,
formState: { errors, isSubmitting }
} = useForm({
resolver: yupResolver(deleteOrgSchema)
});
useEffect(() => {
if (open) {
reset({ title: '' });
setIsAcknowledged(false);
}
}, [open, reset]);
const deleteTitle = watch('title') ?? '';

Comment on lines +119 to +124
onError: (error: Error) => {
toastManager.add({
title: 'Something went wrong',
description: error?.message || 'Failed to update',
type: 'error'
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid double error toasts on update failure.

Update failures are handled in both mutation onError and the catch block, producing duplicate error notifications.

Proposed fix
-  async function onUpdateSubmit(data: FormData) {
-    if (!organization?.id) return;
-
-    try {
-      const req = create(UpdateOrganizationRequestSchema, {
-        id: organization.id,
-        body: {
-          title: data.title,
-          name: data.name,
-          avatar: data.avatar
-        }
-      });
-      await updateOrganization(req);
-    } catch (error: unknown) {
-      toastManager.add({
-        title: 'Something went wrong',
-        description:
-          error instanceof Error
-            ? error.message
-            : `Failed to update ${t.organization({ case: 'lower' })}`,
-        type: 'error'
-      });
-    }
-  }
+  async function onUpdateSubmit(data: FormData) {
+    if (!organization?.id) return;
+    const req = create(UpdateOrganizationRequestSchema, {
+      id: organization.id,
+      body: {
+        title: data.title,
+        name: data.name,
+        avatar: data.avatar
+      }
+    });
+    await updateOrganization(req);
+  }

Also applies to: 159-167

@rohanchkrabrty rohanchkrabrty changed the title feat(sdk): general page design revamp with apsara-v1 feat: SDK general page design revamp Mar 27, 2026
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