-
Notifications
You must be signed in to change notification settings - Fork 426
feat: WIP for employee portal, styling of dashboard #68
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,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> | ||
| ); | ||
| } |
| 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
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 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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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"), | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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> | ||
| ); | ||
| } |
This file was deleted.
| 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
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 localization support and error handling. The component needs the following improvements:
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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| 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"; | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -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
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 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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </> | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| 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(), | ||
| }); | ||
| }); |
| 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
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 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> | ||
| ); | ||
| } | ||
| 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
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 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
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| <DropdownMenuItem onClick={handleLogout}> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| {isLoading ? "Loading..." : t("user_menu.sign_out")} | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| </DropdownMenuItem> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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.
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:
📝 Committable suggestion