-
Notifications
You must be signed in to change notification settings - Fork 420
Add onboarding images and enhance risk and vendor management layouts #229
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,88 @@ | ||
| import { auth } from "@/auth"; | ||
| import { AppOnboarding } from "@/components/app-onboarding"; | ||
| import { getI18n } from "@/locales/server"; | ||
| import { db } from "@bubba/db"; | ||
| import { SecondaryMenu } from "@bubba/ui/secondary-menu"; | ||
| import { cache, Suspense } from "react"; | ||
| import { EmployeeInviteSheet } from "@/components/sheets/add-employee-sheet"; | ||
|
|
||
| export default async function Layout({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| }) { | ||
| const t = await getI18n(); | ||
| const session = await auth(); | ||
| const user = session?.user; | ||
| const orgId = user?.organizationId; | ||
|
|
||
| const overview = await getEmployeesOverview(); | ||
|
|
||
| if (overview.length === 0) { | ||
| return ( | ||
| <div className="max-w-[1200px] m-auto"> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <div className="mt-8"> | ||
| <AppOnboarding | ||
| title={t("app_onboarding.employees.title")} | ||
| description={t("app_onboarding.employees.description")} | ||
| cta={t("app_onboarding.employees.cta")} | ||
| imageSrc="/onboarding/people-management.webp" | ||
| imageAlt="Employee Management" | ||
| sheetName="invite-user-sheet" | ||
| faqs={[ | ||
| { | ||
| questionKey: t("app_onboarding.employees.faqs.question_1"), | ||
| answerKey: t("app_onboarding.employees.faqs.answer_1"), | ||
| }, | ||
| { | ||
| questionKey: t("app_onboarding.employees.faqs.question_2"), | ||
| answerKey: t("app_onboarding.employees.faqs.answer_2"), | ||
| }, | ||
| { | ||
| questionKey: t("app_onboarding.employees.faqs.question_3"), | ||
| answerKey: t("app_onboarding.employees.faqs.answer_3"), | ||
| }, | ||
| { | ||
| questionKey: t("app_onboarding.employees.faqs.question_4"), | ||
| answerKey: t("app_onboarding.employees.faqs.answer_4"), | ||
| }, | ||
| ]} | ||
| /> | ||
| <EmployeeInviteSheet /> | ||
| </div> | ||
| </Suspense> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
|
|
||
| return ( | ||
| <div className="max-w-[1200px] m-auto"> | ||
| <SecondaryMenu | ||
| items={[ | ||
| { | ||
| path: `/${orgId}/employees`, | ||
| label: t("people.dashboard.title"), | ||
| }, | ||
| { path: `/${orgId}/employees/all`, label: t("people.all") }, | ||
| ]} | ||
| /> | ||
|
|
||
| <main className="mt-8">{children}</main> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const getEmployeesOverview = cache(async () => { | ||
| const session = await auth(); | ||
| const orgId = session?.user.organizationId; | ||
|
|
||
| const employees = await db.employee.findMany({ | ||
| where: { | ||
| organizationId: orgId, | ||
| }, | ||
| }); | ||
|
|
||
| return employees; | ||
| }); | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { auth } from "@/auth"; | ||
| import { getI18n } from "@/locales/server"; | ||
| import { SecondaryMenu } from "@bubba/ui/secondary-menu"; | ||
|
|
||
| export default async function Layout({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| }) { | ||
| const t = await getI18n(); | ||
| const session = await auth(); | ||
| const user = session?.user; | ||
| const orgId = user?.organizationId; | ||
|
|
||
| return ( | ||
| <div className="max-w-[1200px] m-auto"> | ||
| <SecondaryMenu | ||
| items={[ | ||
| { | ||
| path: `/${orgId}/employees`, | ||
| label: t("people.dashboard.title"), | ||
| }, | ||
| { path: `/${orgId}/employees/all`, label: t("people.all") }, | ||
| ]} | ||
| /> | ||
|
|
||
| <main className="mt-8">{children}</main> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,30 @@ | ||||||||||||||||||||||||||
| import { auth } from "@/auth"; | ||||||||||||||||||||||||||
| import { getI18n } from "@/locales/server"; | ||||||||||||||||||||||||||
| import { SecondaryMenu } from "@bubba/ui/secondary-menu"; | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| export default async function Layout({ | ||||||||||||||||||||||||||
| children, | ||||||||||||||||||||||||||
| }: { | ||||||||||||||||||||||||||
| children: React.ReactNode; | ||||||||||||||||||||||||||
| }) { | ||||||||||||||||||||||||||
| const t = await getI18n(); | ||||||||||||||||||||||||||
| const session = await auth(); | ||||||||||||||||||||||||||
| const user = session?.user; | ||||||||||||||||||||||||||
| const orgId = user?.organizationId; | ||||||||||||||||||||||||||
|
Comment on lines
+10
to
+13
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add error handling for session data The code currently doesn't handle cases where the session or user might be null, which could lead to runtime errors when accessing const t = await getI18n();
const session = await auth();
const user = session?.user;
- const orgId = user?.organizationId;
+ const orgId = user?.organizationId ?? ""; // Provide a fallback or handle the case when organizationId is undefined
+
+ if (!orgId) {
+ // Handle missing organization ID (redirect or show error message)
+ }📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||
| <div className="max-w-[1200px] m-auto"> | ||||||||||||||||||||||||||
| <SecondaryMenu | ||||||||||||||||||||||||||
| items={[ | ||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||
| path: `/${orgId}/employees`, | ||||||||||||||||||||||||||
| label: t("people.dashboard.title"), | ||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||
| { path: `/${orgId}/employees/all`, label: t("people.all") }, | ||||||||||||||||||||||||||
| ]} | ||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||
| <main className="mt-8">{children}</main> | ||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,27 +1,95 @@ | ||
| import { getI18n } from "@/locales/server"; | ||
| import { SecondaryMenu } from "@bubba/ui/secondary-menu"; | ||
| import { auth } from "@/auth"; | ||
| import { AppOnboarding } from "@/components/app-onboarding"; | ||
| import { db } from "@bubba/db"; | ||
| import { cache, Suspense } from "react"; | ||
| import { CreateRiskSheet } from "@/components/sheets/create-risk-sheet"; | ||
|
|
||
| export default async function Layout({ | ||
| children, | ||
| children, | ||
| }: { | ||
| children: React.ReactNode; | ||
| children: React.ReactNode; | ||
| }) { | ||
| const t = await getI18n(); | ||
| const session = await auth(); | ||
| const user = session?.user; | ||
| const orgId = user?.organizationId; | ||
|
|
||
| return ( | ||
| <div className="max-w-[1200px] m-auto"> | ||
| <SecondaryMenu | ||
| items={[ | ||
| { path: `/${orgId}/risk`, label: t("risk.dashboard.title") }, | ||
| { path: `/${orgId}/risk/register`, label: t("risk.register.title") }, | ||
| ]} | ||
| /> | ||
|
|
||
| <main className="mt-8">{children}</main> | ||
| </div> | ||
| ); | ||
| const t = await getI18n(); | ||
| const session = await auth(); | ||
| const user = session?.user; | ||
| const orgId = user?.organizationId; | ||
|
|
||
| const overview = await getRiskOverview(); | ||
|
|
||
| if (overview?.risks === 0) { | ||
| return ( | ||
| <div className="max-w-[1200px] m-auto"> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <div className="mt-8"> | ||
| <AppOnboarding | ||
| title={t("app_onboarding.risk_management.title")} | ||
| description={t("app_onboarding.risk_management.description")} | ||
| cta={t("app_onboarding.risk_management.cta")} | ||
| imageSrc="/onboarding/risk-management.webp" | ||
| imageAlt="Risk Management" | ||
| sheetName="create-risk-sheet" | ||
| faqs={[ | ||
| { | ||
| questionKey: t("app_onboarding.risk_management.faqs.question_1"), | ||
| answerKey: t("app_onboarding.risk_management.faqs.answer_1"), | ||
| }, | ||
| { | ||
| questionKey: t("app_onboarding.risk_management.faqs.question_2"), | ||
| answerKey: t("app_onboarding.risk_management.faqs.answer_2"), | ||
| }, | ||
| { | ||
| questionKey: t("app_onboarding.risk_management.faqs.question_3"), | ||
| answerKey: t("app_onboarding.risk_management.faqs.answer_3"), | ||
| }, | ||
| { | ||
| questionKey: t("app_onboarding.risk_management.faqs.question_4"), | ||
| answerKey: t("app_onboarding.risk_management.faqs.answer_4"), | ||
| }, | ||
| ]} | ||
| /> | ||
| <CreateRiskSheet /> | ||
| </div> | ||
| </Suspense> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="max-w-[1200px] m-auto"> | ||
| <Suspense fallback={<div>Loading...</div>}> | ||
| <SecondaryMenu | ||
| items={[ | ||
| { path: `/${orgId}/risk`, label: t("risk.dashboard.title") }, | ||
| { path: `/${orgId}/risk/register`, label: t("risk.register.title") }, | ||
| ]} | ||
| /> | ||
|
|
||
| <main className="mt-8">{children}</main> | ||
| </Suspense> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| const getRiskOverview = cache( | ||
| async () => { | ||
| const session = await auth(); | ||
|
|
||
| if (!session || !session.user.organizationId) { | ||
| return { risks: 0 }; | ||
| } | ||
|
|
||
| return await db.$transaction(async (tx) => { | ||
| const [risks] = await Promise.all([ | ||
| tx.risk.count({ | ||
| where: { organizationId: session.user.organizationId }, | ||
| }), | ||
| ]); | ||
|
|
||
| return { | ||
| risks, | ||
| }; | ||
| }); | ||
| }, | ||
| ); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| import { Button } from "@bubba/ui/button"; | ||
| import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@bubba/ui/card"; | ||
| import { Input } from "@bubba/ui/input"; | ||
| import { getI18n } from "@/locales/server"; | ||
|
|
||
| export default async function Loading() { | ||
| const t = await getI18n(); | ||
|
|
||
| return ( | ||
| <div className="space-y-12 grid grid-cols-1 md:grid-cols-2 gap-4"> | ||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>{t("settings.general.org_name")}</CardTitle> | ||
| <CardDescription> | ||
| {t("settings.general.org_name_description")} | ||
| </CardDescription> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <Input type="text" placeholder="Loading..." className="max-w-[300px]" /> | ||
| </CardContent> | ||
| <CardFooter className="flex justify-between"> | ||
| <div>{t("settings.general.org_name_tip")}</div> | ||
| <Button disabled aria-label={t("common.actions.save")}>{t("common.actions.save")}</Button> | ||
| </CardFooter> | ||
| </Card> | ||
|
|
||
| <Card> | ||
| <CardHeader> | ||
| <CardTitle>{t("settings.general.org_website")}</CardTitle> | ||
| <CardDescription> | ||
| {t("settings.general.org_website_description")} | ||
| </CardDescription> | ||
| </CardHeader> | ||
| <CardContent> | ||
| <Input type="url" placeholder="Loading..." className="max-w-[300px]" /> | ||
| </CardContent> | ||
| <CardFooter className="flex justify-between"> | ||
| <div>{t("settings.general.org_website_tip")}</div> | ||
| <Button disabled aria-label={t("common.actions.save")}>{t("common.actions.save")}</Button> | ||
| </CardFooter> | ||
| </Card> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
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 in getEmployeesOverview function
The function doesn't handle cases where the session might be null or organizationId is undefined, which could lead to runtime errors.
const getEmployeesOverview = cache(async () => { const session = await auth(); - const orgId = session?.user.organizationId; + const orgId = session?.user?.organizationId; + + if (!orgId) { + return []; // Return empty array if no organization ID is found + } const employees = await db.employee.findMany({ where: { organizationId: orgId, }, }); return employees; });📝 Committable suggestion