Skip to content

feat: WIP for employee portal, styling of dashboard#68

Merged
carhartlewis merged 2 commits into
mainfrom
lewis/user-portal
Feb 19, 2025
Merged

feat: WIP for employee portal, styling of dashboard#68
carhartlewis merged 2 commits into
mainfrom
lewis/user-portal

Conversation

@carhartlewis
Copy link
Copy Markdown
Contributor

@carhartlewis carhartlewis commented Feb 19, 2025

Summary by CodeRabbit

  • New Features

    • Refreshed portal interface with updated layouts, navigation (header, sidebar, mobile and main menus), and enhanced authentication flows including sign-in, OTP verification, and secure logout.
    • New handling for unauthorized access ensures users are appropriately guided.
    • Automated linking of new employee records with corresponding portal accounts.
    • Enhanced email notifications with clearer instructions and updated language support across multiple locales.
  • Style

    • Updated button visuals and component styling for a more streamlined and responsive user interface.

@vercel
Copy link
Copy Markdown

vercel Bot commented Feb 19, 2025

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

Name Status Preview Comments Updated (UTC)
app ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 19, 2025 2:54pm
comp-portal ✅ Ready (Inspect) Visit Preview 💬 Add feedback Feb 19, 2025 2:54pm
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
web ⬜️ Skipped (Inspect) Feb 19, 2025 2:54pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 19, 2025

Walkthrough

This update introduces multiple enhancements across the codebase. In the app actions, the employee creation flow now also creates a corresponding portal user, and a new logout action has been added. New UI components and layouts have been implemented for the employee portal, including redesigned pages for localization and unauthorized access. Additional middleware logic enforces redirection based on session organization data. Authentication is updated with an extra field in the user configuration, localization files are revised with new sections, database schema changes support an optional organization ID, and email components have updated content and styling.

Changes

File(s) Change Summary
apps/app/src/actions/people/create-employee-action.ts
apps/portal/src/app/actions/logout.ts
Added portal user creation immediately after employee creation; introduced a new server-side logout action using a safe action client.
apps/app/src/components/magic-link.tsx
apps/portal/src/app/components/header.tsx
apps/portal/src/app/components/logout.tsx
apps/portal/src/app/components/main-menu.tsx
apps/portal/src/app/components/mobile-menu.tsx
apps/portal/src/app/components/otp.tsx
apps/portal/src/app/components/sidebar.tsx
apps/portal/src/app/components/user-menu.tsx
Updated UI elements with styling modifications, new header, logout, main-menu, mobile menu, OTP button icon update (with disabled state), and sidebar & user-menu components implemented for improved navigation and user interaction.
apps/portal/src/app/[locale]/…/(home)/layout.tsx
apps/portal/src/app/[locale]/…/(app)/page.tsx
apps/portal/src/app/[locale]/…/(app)/unauthorized/page.tsx
apps/portal/src/app/[locale]/…/(public)/auth/page.tsx
Introduced new layout components and pages including the Portal page with generated metadata, an unauthorized page for missing organization access, and UI enhancements (e.g., "powered by" section) for the authentication page; removed the old Home component handling session-based rendering.
apps/portal/src/app/lib/auth-client.ts
apps/portal/src/app/lib/auth.ts
apps/portal/src/middleware.ts
Applied a minor syntax fix in the auth client; added an optional organizationId field to the authentication configuration; and enhanced middleware with conditional redirection based on a session’s organization affiliation.
apps/portal/src/app/locales/{en,es,fr,no,pt}.ts Revised text instructions and button labels, and added new sections (powered_by, user_menu, and sidebar) for a more detailed compliance and navigation context.
packages/db/prisma/schema.prisma Added an optional organizationId field and established a relation between PortalUser and Organization with a cascade delete strategy.
packages/email/components/footer.tsx
packages/email/emails/otp.tsx
Updated the footer font size and content styling; refined the OTP email content with personalized messaging, clearer instructions, and an expiration notice for the OTP.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant CreateEmployeeAction
    participant Prisma
    Client->>CreateEmployeeAction: Request to create employee
    CreateEmployeeAction->>Prisma: Create employee record
    Prisma-->>CreateEmployeeAction: Employee data
    CreateEmployeeAction->>Prisma: Create associated portalUser
    Prisma-->>CreateEmployeeAction: PortalUser created
    CreateEmployeeAction-->>Client: Success response
Loading
sequenceDiagram
    participant User
    participant LogoutComponent
    participant AuthModule
    User->>LogoutComponent: Initiate logout
    LogoutComponent->>AuthModule: Call signOut with headers
    AuthModule-->>LogoutComponent: Confirmation
    LogoutComponent->>User: Redirect to login page
Loading
sequenceDiagram
    participant User
    participant Middleware
    User->>Middleware: Request page
    alt Session has organizationId and path is /unauthorized
        Middleware-->>User: Redirect to "/"
    else Session exists without organizationId and not on /unauthorized
        Middleware-->>User: Redirect to "/unauthorized"
    else
        Middleware-->>User: Allow request to proceed
    end
Loading

Possibly related PRs

  • Mariano/stuff #44: Involves related modifications to the Prisma schema and enhancements in user–organization relationships, aligning with the new portalUser creation feature.

Poem

Hopping through lines of code, I cheer,
Linking employees with portal friends so near.
Layouts and middleware now guide our way,
With logout and menus bright as day.
I nibble bugs with a carrot smile—
A rabbit’s joy in code for a while!
🥕🐇

✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

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

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

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

Documentation and Community

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

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

🧹 Nitpick comments (15)
apps/portal/src/app/[locale]/(app)/layout.tsx (1)

4-19: LGTM! Consider adding ARIA landmarks.

The layout structure and responsive styling look good. Consider adding ARIA landmarks to improve accessibility:

-    <div className="relative">
+    <div className="relative" role="region" aria-label="Main layout">
       <Sidebar />

       <div className="mx-4 md:ml-[95px] md:mr-10 pb-8">
         <Header />
-        <main>{children}</main>
+        <main role="main">{children}</main>
       </div>
     </div>
apps/portal/src/app/[locale]/(app)/(home)/layout.tsx (1)

4-18: LGTM! Consider adding ARIA landmarks.

The layout structure and i18n implementation look good. Consider adding ARIA landmarks to improve accessibility:

-    <div className="max-w-[1200px]">
+    <div className="max-w-[1200px]" role="region" aria-label="Home layout">
       <SecondaryMenu items={[{ path: "/", label: t("sidebar.dashboard") }]} />

-      <main className="mt-8">{children}</main>
+      <main role="main" className="mt-8">{children}</main>
     </div>
packages/email/components/footer.tsx (1)

16-18: Consider adding semantic markup for the address.

The address text could benefit from semantic HTML markup:

-      <Text className="text-[#B8B8B8] text-xs">
-        Bubba AI, Inc. 2261 Market Street, San Francisco, CA 94114
-      </Text>
+      <Text className="text-[#B8B8B8] text-xs">
+        <address>Bubba AI, Inc. 2261 Market Street, San Francisco, CA 94114</address>
+      </Text>
apps/portal/src/app/[locale]/(app)/(home)/page.tsx (1)

20-32: Enhance metadata for better SEO.

Add more metadata fields for improved SEO and social sharing.

Apply this diff:

   const { locale } = await params;
   setStaticParamsLocale(locale);
   const t = await getI18n();

   return {
     title: t("sidebar.dashboard"),
+    description: t("portal.description"),
+    openGraph: {
+      title: t("sidebar.dashboard"),
+      description: t("portal.description"),
+      type: "website",
+    },
   };
apps/portal/src/app/components/logout.tsx (1)

26-31: Improve accessibility and localization.

Add ARIA attributes and localize the loading text.

Apply this diff:

   return (
-    <DropdownMenuItem onClick={handleLogout}>
-      {isLoading ? "Loading..." : t("user_menu.sign_out")}
+    <DropdownMenuItem
+      onClick={handleLogout}
+      disabled={isLoading}
+      aria-busy={isLoading}
+    >
+      {isLoading ? t("common.loading") : t("user_menu.sign_out")}
     </DropdownMenuItem>
   );
apps/portal/src/app/components/mobile-menu.tsx (1)

24-30: Consider adding a close button in the sheet content.

For better UX, consider adding a close button in the sheet content, especially for accessibility.

 <SheetContent side="left" className="border-none rounded-none -ml-2">
+  <Button
+    variant="ghost"
+    size="icon"
+    onClick={() => setOpen(false)}
+    className="absolute right-4 top-4"
+    aria-label="Close menu"
+  >
+    <Icons.X size={16} />
+  </Button>
   <div className="ml-2 mb-8">
     <Icons.Logo />
   </div>
   <MainMenu onSelect={() => setOpen(false)} />
 </SheetContent>
apps/portal/src/app/locales/en.ts (1)

29-31: Consider adding more sidebar menu items.

The sidebar section currently only has a dashboard entry. Since this is a WIP PR, consider planning for additional menu items that will be needed for the employee portal.

apps/portal/src/app/locales/fr.ts (1)

14-20: Consider moving the URL to a configuration file.

The hardcoded URL https://trycomp.ai should be moved to a configuration file to make it easier to update across all locales if needed.

apps/portal/src/app/components/header.tsx (1)

17-17: Consider extracting CSS classes to a constant or using a CSS-in-JS solution.

The long string of CSS classes reduces readability. Consider extracting these to a constant or using a CSS-in-JS solution for better maintainability.

apps/portal/src/app/components/user-menu.tsx (2)

20-28: Add error handling for image loading.

Consider adding error handling for the avatar image to gracefully fallback to initials when the image fails to load.

 {session?.user?.image && (
   <AvatarImageNext
     src={session?.user?.image}
     alt={session?.user?.name ?? ""}
     width={32}
     height={32}
     quality={100}
+    onError={(e) => {
+      e.currentTarget.style.display = 'none';
+    }}
   />
 )}

30-33: Consider handling empty name edge case.

The current implementation might show an empty fallback when the user's name is empty.

 <span className="text-xs">
-  {session?.user?.name?.charAt(0)?.toUpperCase()}
+  {session?.user?.name?.charAt(0)?.toUpperCase() ?? '?'}
 </span>
apps/portal/src/middleware.ts (2)

39-43: Consider using URL utility functions.

For consistency and maintainability, consider using Next.js's URL utilities.

-const url = new URL("/", request.url);
+const url = request.nextUrl.clone();
+url.pathname = "/";

45-49: Add logging for unauthorized redirects.

Consider adding logging to track unauthorized access attempts for security monitoring.

 if (session && !session.user.organizationId && newUrl.pathname !== "/unauthorized") {
+  console.warn(`Unauthorized access attempt for user ${session.user.id}`);
   const url = new URL("/unauthorized", request.url);
   return NextResponse.redirect(url);
 }
packages/email/emails/otp.tsx (1)

50-50: Enhance email preview text.

Consider making the preview text more informative and engaging.

-<Preview>One-Time Password for Comp AI</Preview>
+<Preview>Your secure login code for Comp AI - Valid for 10 minutes</Preview>
apps/portal/src/app/components/main-menu.tsx (1)

124-132: Consider persisting menu item order.

The reordered items are only stored in component state and will reset on page refresh.

Consider persisting the order in localStorage or backend:

 const onReorder = (items: { path: string; name: string; disabled: boolean }[]) => {
   setItems(items);
+  // Persist order
+  localStorage.setItem('menu-order', JSON.stringify(items));
 };

+// In useState initialization
+const savedItems = typeof window !== 'undefined' 
+  ? JSON.parse(localStorage.getItem('menu-order') || 'null')
+  : null;
+const [items, setItems] = useState(savedItems ?? initialItems ?? defaultItems);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2efaf66 and 5771d18.

📒 Files selected for processing (27)
  • apps/app/src/actions/people/create-employee-action.ts (2 hunks)
  • apps/app/src/components/magic-link.tsx (1 hunks)
  • apps/portal/src/app/[locale]/(app)/(home)/layout.tsx (1 hunks)
  • apps/portal/src/app/[locale]/(app)/(home)/page.tsx (1 hunks)
  • apps/portal/src/app/[locale]/(app)/layout.tsx (1 hunks)
  • apps/portal/src/app/[locale]/(app)/page.tsx (0 hunks)
  • apps/portal/src/app/[locale]/(app)/unauthorized/page.tsx (1 hunks)
  • apps/portal/src/app/[locale]/(public)/auth/page.tsx (2 hunks)
  • apps/portal/src/app/actions/logout.ts (1 hunks)
  • apps/portal/src/app/components/header.tsx (1 hunks)
  • apps/portal/src/app/components/logout.tsx (1 hunks)
  • apps/portal/src/app/components/main-menu.tsx (1 hunks)
  • apps/portal/src/app/components/mobile-menu.tsx (1 hunks)
  • apps/portal/src/app/components/otp.tsx (2 hunks)
  • apps/portal/src/app/components/sidebar.tsx (1 hunks)
  • apps/portal/src/app/components/user-menu.tsx (1 hunks)
  • apps/portal/src/app/lib/auth-client.ts (1 hunks)
  • apps/portal/src/app/lib/auth.ts (1 hunks)
  • apps/portal/src/app/locales/en.ts (1 hunks)
  • apps/portal/src/app/locales/es.ts (1 hunks)
  • apps/portal/src/app/locales/fr.ts (1 hunks)
  • apps/portal/src/app/locales/no.ts (1 hunks)
  • apps/portal/src/app/locales/pt.ts (1 hunks)
  • apps/portal/src/middleware.ts (1 hunks)
  • packages/db/prisma/schema.prisma (2 hunks)
  • packages/email/components/footer.tsx (1 hunks)
  • packages/email/emails/otp.tsx (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/portal/src/app/[locale]/(app)/page.tsx
✅ Files skipped from review due to trivial changes (1)
  • apps/portal/src/app/lib/auth-client.ts
🔇 Additional comments (15)
apps/portal/src/app/actions/logout.ts (1)

1-11: LGTM! Clean implementation of the logout action.

The implementation follows Next.js best practices for server actions and maintains a single responsibility.

packages/email/components/footer.tsx (1)

8-14: LGTM! Clean implementation of the footer text.

The font sizes and link implementation look good.

apps/portal/src/app/locales/en.ts (1)

14-20:

Details

❓ Verification inconclusive

Review marketing copy and external link.

The marketing copy and external link in the powered_by section need attention:

  1. Verify the accuracy of compliance claims
  2. Consider security implications of the external link

🏁 Script executed:

#!/bin/bash
# Check if the external link is valid and secure
curl -I https://trycomp.ai

Length of output: 1510


Action: Revisit and verify the marketing copy and external link in the powered_by section.

  • The external link https://trycomp.ai has been verified via a curl check and is returning an HTTP/2 200 response with proper security headers (including HSTS), indicating it is secure and active.
  • The marketing copy ("Get SOC 2, ISO 27001, and GDPR compliant in weeks, not months...") should be confirmed for accuracy with the compliance/marketing teams to ensure the claims are correct and align with our product positioning.
apps/portal/src/app/locales/no.ts (1)

1-32: LGTM! Norwegian translations are well-structured.

The Norwegian translations maintain consistency with the English version while providing natural-sounding Norwegian text.

apps/portal/src/app/locales/pt.ts (1)

1-32: LGTM! Portuguese translations are well-structured.

The Portuguese translations maintain consistency with other locale files while providing natural-sounding Portuguese text.

apps/portal/src/app/locales/fr.ts (3)

3-4: LGTM! Text changes improve clarity and user experience.

The updated French translations are more concise and user-friendly.

Also applies to: 8-8, 10-11


21-23: LGTM! Sign out translation is appropriate.

The French translation for the sign out option is clear and consistent.


29-31: LGTM! Dashboard translation is appropriate.

The French translation for the dashboard label is clear and consistent.

apps/portal/src/app/locales/es.ts (1)

1-32: LGTM! Spanish translations are consistent with other locales.

The Spanish translations maintain consistency with other language files while providing natural and clear translations.

apps/portal/src/app/lib/auth.ts (1)

15-20:

Details

✅ Verification successful

Verify handling of users without organizationId.

The organizationId field is marked as optional. Ensure that the application properly handles cases where this field is not set.

Run the following script to check for potential issues:


🏁 Script executed:

#!/bin/bash
# Description: Check for proper handling of optional organizationId

# Search for direct references to organizationId
echo "Checking direct references to organizationId..."
rg "organizationId" -A 3

# Search for null checks on organizationId
echo "Checking null/undefined handling..."
rg "(\?\.|\|\||\?\?|===\s*(null|undefined)).*organizationId"

Length of output: 69037


Organization ID Handling Verified

After running searches across the codebase, it appears that the application consistently checks for and handles missing organizationId values. For example, middleware and various routes and actions appropriately check for the absence of organizationId (using conditions such as if (!organizationId)) and take corrective actions (e.g., redirection or error throwing). This verification confirms that marking the field as optional does not lead to unhandled scenarios when the field isn’t set.

No changes are required regarding organizationId handling.

apps/portal/src/app/components/header.tsx (1)

24-28: LGTM! Good use of Suspense for loading state.

The implementation of Suspense with a Skeleton fallback provides a good user experience during loading.

packages/email/emails/otp.tsx (1)

62-68: LGTM! Clear and secure communication.

The email content effectively communicates the OTP while emphasizing security best practices.

apps/portal/src/app/components/otp.tsx (1)

9-9: LGTM! UX improvements for the OTP form.

The changes enhance the user experience by:

  • Preventing multiple submissions during loading state
  • Using a more appropriate icon (ArrowRight) for the action

Also applies to: 91-91, 98-98

apps/app/src/components/magic-link.tsx (1)

113-113:

Details

❓ Verification inconclusive

Verify button visibility after background color removal.

The bg-primary class has been removed from the button. Please ensure the button remains visible against all possible parent background colors.


🏁 Script executed:

#!/bin/bash
# Search for potential parent background color classes
rg -A 2 -B 2 'className=.*bg-' apps/app/src/components/

Length of output: 44894


Verify Button Contrast on Various Backgrounds

Please ensure that the magic link button (in apps/app/src/components/magic-link.tsx, line 113) remains clearly visible after the removal of the bg-primary class. Because the button no longer has an explicit background color, test its appearance against all anticipated parent backgrounds (e.g., bg-card, bg-background, etc.) to verify sufficient contrast and overall visibility. Adjust additional styling (such as borders or shadows) if needed to maintain a strong visual cue for users.

packages/db/prisma/schema.prisma (1)

1020-1021: New Optional Organization Relationship in PortalUser

The addition of the optional organizationId field along with the corresponding relation to the Organization model is implemented correctly. This change allows a portal user to be linked with an organization, which aligns with the new employee portal requirements.

Key Points:

  • The field type String? properly indicates that the relation is optional.
  • The relation is set up with onDelete: Cascade, meaning that if an organization is deleted, any associated portal users will also be deleted. Ensure that this behavior aligns with your business requirements—if you want to preserve portal user records even after an organization is removed, consider using onDelete: SetNull instead.

Comment on lines +4 to +17
export default async function Unauthorized() {
const session = await auth.api.getSession({
headers: await headers(),
});

return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
We couldn't find an organization for you. Please contact your
administrator.
</main>
</div>
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add localization support and error handling.

The component needs the following improvements:

  1. The error message should be localized since it's in a locale route.
  2. Add error handling for the session fetch.
  3. Add proper heading and ARIA attributes for better accessibility.

Apply this diff to improve the component:

+import { getI18n } from "@/app/locales/server";

 export default async function Unauthorized() {
+  const t = await getI18n();
   const session = await auth.api.getSession({
     headers: await headers(),
-  });
+  }).catch((error) => {
+    console.error("Failed to fetch session:", error);
+    return null;
+  });

   return (
     <div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
-      <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
+      <main className="flex flex-col gap-8 row-start-2 items-center sm:items-start" role="main" aria-labelledby="unauthorized-title">
+        <h1 id="unauthorized-title" className="sr-only">{t("unauthorized.title")}</h1>
         {t("unauthorized.message")}
       </main>
     </div>
   );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default async function Unauthorized() {
const session = await auth.api.getSession({
headers: await headers(),
});
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
We couldn't find an organization for you. Please contact your
administrator.
</main>
</div>
);
}
import { getI18n } from "@/app/locales/server";
export default async function Unauthorized() {
const t = await getI18n();
const session = await auth.api.getSession({
headers: await headers(),
}).catch((error) => {
console.error("Failed to fetch session:", error);
return null;
});
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start" role="main" aria-labelledby="unauthorized-title">
<h1 id="unauthorized-title" className="sr-only">{t("unauthorized.title")}</h1>
{t("unauthorized.message")}
</main>
</div>
);
}

Comment on lines +1 to +4
import { Icons } from "@bubba/ui/icons";
import { cookies } from "next/headers";
import Link from "next/link";
import { MainMenu } from "./main-menu";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove unused import.

The cookies import is not used in this component.

 import { Icons } from "@bubba/ui/icons";
-import { cookies } from "next/headers";
 import Link from "next/link";
 import { MainMenu } from "./main-menu";
📝 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
import { Icons } from "@bubba/ui/icons";
import { cookies } from "next/headers";
import Link from "next/link";
import { MainMenu } from "./main-menu";
import { Icons } from "@bubba/ui/icons";
import Link from "next/link";
import { MainMenu } from "./main-menu";

Comment on lines +6 to +21
export async function Sidebar() {
return (
<aside className="h-screen flex-shrink-0 flex-col justify-between fixed top-0 ml-4 pb-4 items-center hidden md:flex">
<div className="flex flex-col items-center justify-center">
<div className="mt-2 todesktop:mt-[35px]">
<div className="mt-2 items-center justify-center">
<Link href="/">
<Icons.Logo />
</Link>
</div>
</div>
<MainMenu />
</div>
</aside>
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve accessibility and component structure.

  1. Add alt text to the logo for screen readers.
  2. Consider z-index for fixed positioning to prevent overlay issues.
  3. Remove async since no async operations are performed.

Apply this diff:

-export async function Sidebar() {
+export function Sidebar() {
   return (
-    <aside className="h-screen flex-shrink-0 flex-col justify-between fixed top-0 ml-4 pb-4 items-center hidden md:flex">
+    <aside className="h-screen flex-shrink-0 flex-col justify-between fixed top-0 ml-4 pb-4 items-center hidden md:flex z-10">
       <div className="flex flex-col items-center justify-center">
         <div className="mt-2 todesktop:mt-[35px]">
           <div className="mt-2 items-center justify-center">
-            <Link href="/">
+            <Link href="/" aria-label="Home">
               <Icons.Logo />
             </Link>
           </div>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function Sidebar() {
return (
<aside className="h-screen flex-shrink-0 flex-col justify-between fixed top-0 ml-4 pb-4 items-center hidden md:flex">
<div className="flex flex-col items-center justify-center">
<div className="mt-2 todesktop:mt-[35px]">
<div className="mt-2 items-center justify-center">
<Link href="/">
<Icons.Logo />
</Link>
</div>
</div>
<MainMenu />
</div>
</aside>
);
}
export function Sidebar() {
return (
<aside className="h-screen flex-shrink-0 flex-col justify-between fixed top-0 ml-4 pb-4 items-center hidden md:flex z-10">
<div className="flex flex-col items-center justify-center">
<div className="mt-2 todesktop:mt-[35px]">
<div className="mt-2 items-center justify-center">
<Link href="/" aria-label="Home">
<Icons.Logo />
</Link>
</div>
</div>
<MainMenu />
</div>
</aside>
);
}

Comment on lines +5 to +18
export default async function Portal({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
setStaticParamsLocale(locale);

return (
<div>
<h1>Employee Portal</h1>
</div>
);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add localization and improve semantic structure.

The portal page needs localization for the heading and better semantic HTML structure.

Apply this diff:

 export default async function Portal({
   params,
 }: {
   params: Promise<{ locale: string }>;
 }) {
   const { locale } = await params;
   setStaticParamsLocale(locale);
+  const t = await getI18n();

   return (
-    <div>
-      <h1>Employee Portal</h1>
+    <div className="container mx-auto p-4">
+      <h1 className="text-2xl font-bold mb-4">{t("portal.title")}</h1>
+      <main>
+        {/* Add your portal content here */}
+      </main>
     </div>
   );
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default async function Portal({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
setStaticParamsLocale(locale);
return (
<div>
<h1>Employee Portal</h1>
</div>
);
}
export default async function Portal({
params,
}: {
params: Promise<{ locale: string }>;
}) {
const { locale } = await params;
setStaticParamsLocale(locale);
const t = await getI18n();
return (
<div className="container mx-auto p-4">
<h1 className="text-2xl font-bold mb-4">{t("portal.title")}</h1>
<main>
{/* Add your portal content here */}
</main>
</div>
);
}

Comment on lines +14 to +24
const handleLogout = async () => {
setLoading(true);
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login"); // redirect to login page
},
},
});
setLoading(false);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling to logout process.

The logout handler needs error handling and proper loading state management.

Apply this diff:

   const handleLogout = async () => {
     setLoading(true);
-    await authClient.signOut({
-      fetchOptions: {
-        onSuccess: () => {
-          router.push("/login"); // redirect to login page
-        },
-      },
-    });
-    setLoading(false);
+    try {
+      await authClient.signOut({
+        fetchOptions: {
+          onSuccess: () => {
+            router.push("/login");
+          },
+        },
+      });
+    } catch (error) {
+      console.error("Logout failed:", error);
+      // TODO: Add error notification
+    } finally {
+      setLoading(false);
+    }
   };
📝 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 handleLogout = async () => {
setLoading(true);
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login"); // redirect to login page
},
},
});
setLoading(false);
};
const handleLogout = async () => {
setLoading(true);
try {
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login");
},
},
});
} catch (error) {
console.error("Logout failed:", error);
// TODO: Add error notification
} finally {
setLoading(false);
}
};

Comment on lines +20 to +22
<div className="flex space-x-2 ml-auto">
<div className="flex gap-2">Employee Portal</div>
</div>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider making the portal title configurable.

The hardcoded "Employee Portal" text should be moved to the localization files for consistency with other translated content.

Comment on lines +51 to +61
<Link
href="https://trycomp.ai"
target="_blank"
className="hover:underline hover:underline-offset-2"
>
<span className="mt-2 inline-flex items-center text-xs font-medium text-primary gap-2">
{t("powered_by.learn_more")}
<ArrowRight className="h-3 w-3" />
</span>
</Link>
</Button>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add security attributes for external link.

The external link should include security-related attributes to protect against potential vulnerabilities.

 <Link
   href="https://trycomp.ai"
   target="_blank"
+  rel="noopener noreferrer"
   className="hover:underline hover:underline-offset-2"
 >
📝 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
href="https://trycomp.ai"
target="_blank"
className="hover:underline hover:underline-offset-2"
>
<span className="mt-2 inline-flex items-center text-xs font-medium text-primary gap-2">
{t("powered_by.learn_more")}
<ArrowRight className="h-3 w-3" />
</span>
</Link>
</Button>
<Link
href="https://trycomp.ai"
target="_blank"
rel="noopener noreferrer"
className="hover:underline hover:underline-offset-2"
>
<span className="mt-2 inline-flex items-center text-xs font-medium text-primary gap-2">
{t("powered_by.learn_more")}
<ArrowRight className="h-3 w-3" />
</span>
</Link>
</Button>

Comment on lines +61 to +76
const portalUser = await db.portalUser.create({
data: {
id: employee.id,
name,
email,
organizationId: user.organizationId,
emailVerified: false,
createdAt: new Date(),
updatedAt: new Date(),
employees: {
connect: {
id: employee.id,
},
},
},
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Wrap employee and portal user creation in a transaction.

The current implementation could lead to data inconsistency if the portal user creation fails after the employee is created. This would result in an employee without a corresponding portal user.

Wrap both operations in a transaction:

-      const employee = await db.employee.create({
-        data: {
-          name,
-          email,
-          department,
-          organizationId: user.organizationId,
-          isActive: true,
-          externalEmployeeId,
-        },
-      });
-
-      const portalUser = await db.portalUser.create({
+      const [employee, portalUser] = await db.$transaction(async (tx) => {
+        const employee = await tx.employee.create({
+          data: {
+            name,
+            email,
+            department,
+            organizationId: user.organizationId,
+            isActive: true,
+            externalEmployeeId,
+          },
+        });
+
+        const portalUser = await tx.portalUser.create({
+          data: {
+            id: employee.id,
+            name,
+            email,
+            organizationId: user.organizationId,
+            emailVerified: false,
+            createdAt: new Date(),
+            updatedAt: new Date(),
+            employees: {
+              connect: {
+                id: employee.id,
+              },
+            },
+          },
+        });
+
+        return [employee, portalUser];
+      });
📝 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 portalUser = await db.portalUser.create({
data: {
id: employee.id,
name,
email,
organizationId: user.organizationId,
emailVerified: false,
createdAt: new Date(),
updatedAt: new Date(),
employees: {
connect: {
id: employee.id,
},
},
},
});
const [employee, portalUser] = await db.$transaction(async (tx) => {
const employee = await tx.employee.create({
data: {
name,
email,
department,
organizationId: user.organizationId,
isActive: true,
externalEmployeeId,
},
});
const portalUser = await tx.portalUser.create({
data: {
id: employee.id,
name,
email,
organizationId: user.organizationId,
emailVerified: false,
createdAt: new Date(),
updatedAt: new Date(),
employees: {
connect: {
id: employee.id,
},
},
},
});
return [employee, portalUser];
});

Comment on lines +21 to +23
const icons = {
"/": () => <Icons.Overview size={22} />,
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Expand icons mapping for all routes.

The icons object only includes mapping for the root route ("/"). Add mappings for all possible routes to prevent undefined icons.


🏁 Script executed:

#!/bin/bash
# Search for all defined routes in the codebase
rg -A 1 'path:\s*"/.+?"' apps/portal/src/app/

Length of output: 233


Action Required: Expand Icons Mapping for All Routes

The current implementation in apps/portal/src/app/components/main-menu.tsx only defines the icon for the root route ("/"), using <Icons.Overview size={22} />. Our search confirmed that this is the only defined mapping for route usage (e.g., in [locale]/(app)/(home)/layout.tsx the route for "/" is used), which suggests that additional routes still lack an icon assignment.

  • Update the icons object: Include explicit icon mappings for all routes that are rendered in the application.
  • Consider a default case: If a route does not have a unique icon, a default icon mapping might be added to avoid undefined icon errors.

Comment on lines +32 to +83
const Item = ({ item, isActive, onSelect, disabled }: ItemProps) => {
const Icon = icons[item.path as keyof typeof icons];
const linkDisabled = disabled || item.disabled;

return (
<TooltipProvider delayDuration={70}>
{linkDisabled ? (
<div className="w-[45px] h-[45px] flex items-center md:justify-center">
Coming
</div>
) : (
<Link prefetch href={item.path} onClick={() => onSelect?.()}>
<Tooltip>
<TooltipTrigger className="w-full">
<Reorder.Item
key={item.path}
value={item}
id={item.path}
layoutRoot
className={cn(
"relative border border-transparent md:w-[45px] h-[45px] flex items-center md:justify-center",
"hover:bg-accent hover:border-[#DCDAD2] hover:dark:border-[#2C2C2C]",
isActive &&
"bg-[#F2F1EF] dark:bg-secondary border-[#DCDAD2] dark:border-[#2C2C2C]",
)}
>
<motion.div
className="relative"
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<div>
<Icon />
<span className="flex md:hidden">{item.name}</span>
</div>
</motion.div>
</Reorder.Item>
</TooltipTrigger>
<TooltipContent
side="left"
className="px-3 py-1.5 text-xs hidden md:flex"
sideOffset={10}
>
{item.name}
</TooltipContent>
</Tooltip>
</Link>
)}
</TooltipProvider>
);
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Enhance accessibility of the Item component.

Add ARIA labels and roles to improve accessibility:

 <Link prefetch href={item.path} onClick={() => onSelect?.()}>
-  <Tooltip>
+  <Tooltip role="navigation">
     <TooltipTrigger className="w-full">
       <Reorder.Item
         key={item.path}
         value={item}
         id={item.path}
         layoutRoot
+        role="menuitem"
+        aria-label={item.name}
         className={cn(
📝 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 Item = ({ item, isActive, onSelect, disabled }: ItemProps) => {
const Icon = icons[item.path as keyof typeof icons];
const linkDisabled = disabled || item.disabled;
return (
<TooltipProvider delayDuration={70}>
{linkDisabled ? (
<div className="w-[45px] h-[45px] flex items-center md:justify-center">
Coming
</div>
) : (
<Link prefetch href={item.path} onClick={() => onSelect?.()}>
<Tooltip>
<TooltipTrigger className="w-full">
<Reorder.Item
key={item.path}
value={item}
id={item.path}
layoutRoot
className={cn(
"relative border border-transparent md:w-[45px] h-[45px] flex items-center md:justify-center",
"hover:bg-accent hover:border-[#DCDAD2] hover:dark:border-[#2C2C2C]",
isActive &&
"bg-[#F2F1EF] dark:bg-secondary border-[#DCDAD2] dark:border-[#2C2C2C]",
)}
>
<motion.div
className="relative"
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<div>
<Icon />
<span className="flex md:hidden">{item.name}</span>
</div>
</motion.div>
</Reorder.Item>
</TooltipTrigger>
<TooltipContent
side="left"
className="px-3 py-1.5 text-xs hidden md:flex"
sideOffset={10}
>
{item.name}
</TooltipContent>
</Tooltip>
</Link>
)}
</TooltipProvider>
);
};
const Item = ({ item, isActive, onSelect, disabled }: ItemProps) => {
const Icon = icons[item.path as keyof typeof icons];
const linkDisabled = disabled || item.disabled;
return (
<TooltipProvider delayDuration={70}>
{linkDisabled ? (
<div className="w-[45px] h-[45px] flex items-center md:justify-center">
Coming
</div>
) : (
<Link prefetch href={item.path} onClick={() => onSelect?.()}>
<Tooltip role="navigation">
<TooltipTrigger className="w-full">
<Reorder.Item
key={item.path}
value={item}
id={item.path}
layoutRoot
role="menuitem"
aria-label={item.name}
className={cn(
"relative border border-transparent md:w-[45px] h-[45px] flex items-center md:justify-center",
"hover:bg-accent hover:border-[#DCDAD2] hover:dark:border-[#2C2C2C]",
isActive &&
"bg-[#F2F1EF] dark:bg-secondary border-[#DCDAD2] dark:border-[#2C2C2C]"
)}
>
<motion.div
className="relative"
initial={{ opacity: 1 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<div>
<Icon />
<span className="flex md:hidden">{item.name}</span>
</div>
</motion.div>
</Reorder.Item>
</TooltipTrigger>
<TooltipContent
side="left"
className="px-3 py-1.5 text-xs hidden md:flex"
sideOffset={10}
>
{item.name}
</TooltipContent>
</Tooltip>
</Link>
)}
</TooltipProvider>
);
};

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