Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion apps/app/src/actions/people/create-employee-action.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"use server";

import { db } from "@bubba/db";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
import { authActionClient } from "../safe-action";
import { createEmployeeSchema } from "../schema";
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
import type { ActionResponse } from "../types";

const DEFAULT_TASKS = [
Expand Down Expand Up @@ -58,6 +58,23 @@ export const createEmployeeAction = authActionClient
},
});

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,
},
},
},
});
Comment on lines +61 to +76

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];
});


// Create or get the required task definitions first and store their IDs
const requiredTasks = await Promise.all(
DEFAULT_TASKS.map(async (task) => {
Expand Down
2 changes: 1 addition & 1 deletion apps/app/src/components/magic-link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ export function MagicLinkSignIn({ className }: Props) {

<Button
type="submit"
className="flex h-[40px] w-full space-x-2 bg-primary px-6 py-4 font-medium text-secondary active:scale-[0.98]"
className="flex h-[40px] w-full space-x-2 px-6 py-4 font-medium active:scale-[0.98]"
>
{isLoading ? (
<Loader2 className="h-4 w-4 animate-spin" />
Expand Down
18 changes: 18 additions & 0 deletions apps/portal/src/app/[locale]/(app)/(home)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { getI18n } from "@/app/locales/server";
import { SecondaryMenu } from "@bubba/ui/secondary-menu";

export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
const t = await getI18n();

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

<main className="mt-8">{children}</main>
</div>
);
}
32 changes: 32 additions & 0 deletions apps/portal/src/app/[locale]/(app)/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { getI18n } from "@/app/locales/server";
import type { Metadata } from "next";
import { setStaticParamsLocale } from "next-international/server";

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

return (
<div>
<h1>Employee Portal</h1>
</div>
);
}
Comment on lines +5 to +18

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>
);
}


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

return {
title: t("sidebar.dashboard"),
};
}
19 changes: 19 additions & 0 deletions apps/portal/src/app/[locale]/(app)/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { Header } from "@/app/components/header";
import { Sidebar } from "@/app/components/sidebar";

export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="relative">
<Sidebar />

<div className="mx-4 md:ml-[95px] md:mr-10 pb-8">
<Header />
<main>{children}</main>
</div>
</div>
);
}
55 changes: 0 additions & 55 deletions apps/portal/src/app/[locale]/(app)/page.tsx

This file was deleted.

17 changes: 17 additions & 0 deletions apps/portal/src/app/[locale]/(app)/unauthorized/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { auth } from "@/app/lib/auth";
import { headers } from "next/headers";

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>
);
}
Comment on lines +4 to +17

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>
);
}

23 changes: 22 additions & 1 deletion apps/portal/src/app/[locale]/(public)/auth/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { OtpSignIn } from "@/app/components/otp";
import { getI18n } from "@/app/locales/server";
import { Button } from "@bubba/ui/button";
import { ArrowRight } from "lucide-react";
import type { Metadata } from "next";
import Link from "next/link";

Expand Down Expand Up @@ -35,10 +37,29 @@ export default async function Page() {
</div>
</div>

<div className="pointer-events-auto mb-6 flex flex-col">
<div className="pointer-events-auto flex flex-col">
{defaultSignInOptions}
</div>
</div>

<div className="mt-8 rounded-md bg-gradient-to-r from-primary/10 via-primary/5 to-primary/5 p-4">
<h3 className="text-sm font-medium">{t("powered_by.title")}</h3>
<p className="mt-1 text-xs text-muted-foreground">
{t("powered_by.description")}
</p>
<Button variant="link" className="p-0 mt-2" asChild>
<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>
Comment on lines +51 to +61

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>

</div>
</div>
</div>
</>
Expand Down
11 changes: 11 additions & 0 deletions apps/portal/src/app/actions/logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"use server";

import { createSafeActionClient } from "next-safe-action";
import { headers } from "next/headers";
import { auth } from "../lib/auth";

export const logout = createSafeActionClient().action(async () => {
await auth.api.signOut({
headers: await headers(),
});
});
31 changes: 31 additions & 0 deletions apps/portal/src/app/components/header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { UserMenu } from "@/app/components/user-menu";
import { getI18n } from "@/app/locales/server";
import { Skeleton } from "@bubba/ui/skeleton";
import { headers } from "next/headers";
import { Suspense } from "react";
import { auth } from "../lib/auth";
import { MobileMenu } from "./mobile-menu";

export async function Header() {
const session = await auth.api.getSession({
headers: await headers(),
});

const t = await getI18n();

return (
<header className="-ml-4 -mr-4 md:m-0 z-10 px-4 md:px-0 md:border-b-[1px] flex justify-between pt-4 pb-2 md:pb-4 items-center todesktop:sticky todesktop:top-0 todesktop:bg-background todesktop:border-none sticky md:static top-0 backdrop-filter backdrop-blur-xl md:backdrop-filter md:backdrop-blur-none bg-opacity-70">
<MobileMenu />

<div className="flex space-x-2 ml-auto">
<div className="flex gap-2">Employee Portal</div>
</div>
Comment on lines +20 to +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.

🛠️ 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.


<div className="flex space-x-2 ml-auto">
<Suspense fallback={<Skeleton className="h-8 w-8 rounded-full" />}>
<UserMenu session={session} />
</Suspense>
</div>
</header>
);
}
31 changes: 31 additions & 0 deletions apps/portal/src/app/components/logout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
"use client";

import { useI18n } from "@/app/locales/client";
import { DropdownMenuItem } from "@bubba/ui/dropdown-menu";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { authClient } from "../lib/auth-client";

export function Logout() {
const t = useI18n();
const [isLoading, setLoading] = useState(false);
const router = useRouter();

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

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


return (
<DropdownMenuItem onClick={handleLogout}>
{isLoading ? "Loading..." : t("user_menu.sign_out")}
</DropdownMenuItem>
);
}
Loading