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
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { getControl } from "./data/getControl";
import { getOrganizationControlProgress } from "./data/getOrganizationControlProgress";
import type { ControlProgressResponse } from "./data/getOrganizationControlProgress";
import { headers } from "next/headers";
import PageWithBreadcrumb from "@/components/pages/PageWithBreadcrumb";

interface PageProps {
params: Promise<{ controlId: string }>;
Expand Down Expand Up @@ -41,5 +42,17 @@ export default async function SingleControlPage({ params }: PageProps) {
byType: {},
};

return <SingleControl control={control} controlProgress={controlProgress} />;
return (
<PageWithBreadcrumb
breadcrumbs={[
{
label: "Controls",
href: `/${session.session.activeOrganizationId}/controls`,
},
{ label: control.name },
]}
>
<SingleControl control={control} controlProgress={controlProgress} />
</PageWithBreadcrumb>
);
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
"use client";

import { useI18n } from "@/locales/client";
import { Card, CardContent, CardHeader, CardTitle } from "@bubba/ui/card";
import { ControlsTable } from "./table/ControlsTable";
import type { Control } from "@bubba/db/types";
import { ControlsTable } from "./table/ControlsTable";

interface ControlsOverviewProps {
controls: (Control & {
Expand All @@ -16,19 +15,11 @@ interface ControlsOverviewProps {
organizationId: string;
}

export function ControlsOverview({
controls,
organizationId,
}: ControlsOverviewProps) {
export function ControlsOverview({ controls }: ControlsOverviewProps) {
const t = useI18n();

return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<h2 className="text-3xl font-bold tracking-tight">
{t("controls.overview.title")}
</h2>
</div>
<ControlsTable data={controls} />
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function ControlsTableColumns(): ColumnDef<ControlTableType>[] {
const control = row.original;
return (
<Link
href={`/${params.locale}/${params.orgId}/controls/${control.id}`}
href={`/${params.orgId}/controls/${control.id}`}
className="hover:underline"
>
{control.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function ControlsTableColumns(): ColumnDef<ControlTableType>[] {
return (
<div className="min-w-[200px]">
<Link
href={`/${params.locale}/${params.orgId}/controls/${control.id}`}
href={`/${params.orgId}/controls/${control.id}`}
className="hover:underline"
>
{control.name}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,6 @@ export default async function Layout({

return (
<div className="max-w-[1200px] mx-auto">
<SecondaryMenu
items={[
{
path: `/${session.session.activeOrganizationId}/controls`,
label: t("controls.title"),
},
]}
/>

<main className="mt-8">{children}</main>
</div>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { headers } from "next/headers";
import { redirect } from "next/navigation";
import { ControlsOverview } from "./components/ControlsOverview";
import { getAllOrganizationControls } from "./data/getAllOrganizationControls";
import PageWithBreadcrumb from "@/components/pages/PageWithBreadcrumb";

export async function generateMetadata() {
const t = await getI18n();
Expand All @@ -29,6 +30,8 @@ export default async function ControlsPage() {
});

return (
<ControlsOverview controls={controls} organizationId={organizationId} />
<PageWithBreadcrumb breadcrumbs={[{ label: "Controls" }]}>
<ControlsOverview controls={controls} organizationId={organizationId} />
</PageWithBreadcrumb>
);
}
242 changes: 121 additions & 121 deletions apps/portal/src/app/components/main-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ import { Button } from "@bubba/ui/button";
import { cn } from "@bubba/ui/cn";
import { Icons } from "@bubba/ui/icons";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@bubba/ui/tooltip";
import { useClickAway } from "@uidotdev/usehooks";
import { Reorder, motion, useMotionValue } from "framer-motion";
Expand All @@ -19,146 +19,146 @@ import { useState } from "react";
import { useLongPress } from "use-long-press";

const icons = {
"/": () => <Icons.Overview size={22} />,
"/": () => <Icons.Overview size={22} />,
};

interface ItemProps {
item: { path: string; name: string; disabled: boolean };
isActive: boolean;
onSelect?: () => void;
disabled: boolean;
item: { path: string; name: string; disabled: boolean };
isActive: boolean;
onSelect?: () => void;
disabled: boolean;
}

const Item = ({ item, isActive, onSelect, disabled }: ItemProps) => {
const Icon = icons[item.path as keyof typeof icons];
const linkDisabled = disabled || item.disabled;
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>
);
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 listVariant = {
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.04,
},
},
hidden: { opacity: 0 },
show: {
opacity: 1,
transition: {
staggerChildren: 0.04,
},
},
};

const itemVariant = {
hidden: { opacity: 0 },
show: { opacity: 1 },
hidden: { opacity: 0 },
show: { opacity: 1 },
};

type Props = {
initialItems?: { path: string; name: string; disabled: boolean }[];
onSelect?: () => void;
initialItems?: { path: string; name: string; disabled: boolean }[];
onSelect?: () => void;
};

export function MainMenu({ initialItems, onSelect }: Props) {
const t = useI18n();
const t = useI18n();

const defaultItems = [
{
path: "/",
name: "Dashboard",
disabled: false,
},
];
const defaultItems = [
{
path: "/",
name: "Frameworks",
disabled: false,
},
];

const [items, setItems] = useState(initialItems ?? defaultItems);
const pathname = usePathname();
const part = pathname?.split("/")[1];
const [items, setItems] = useState(initialItems ?? defaultItems);
const pathname = usePathname();
const part = pathname?.split("/")[1];

const hiddenItems = defaultItems.filter(
(item) => !items.some((i) => i.path === item.path),
);
const hiddenItems = defaultItems.filter(
(item) => !items.some((i) => i.path === item.path),
);

const onReorder = (
items: {
path: string;
name: string;
disabled: boolean;
}[],
) => {
setItems(items);
};
const onReorder = (
items: {
path: string;
name: string;
disabled: boolean;
}[],
) => {
setItems(items);
};

return (
<div className="mt-6">
<nav>
<Reorder.Group
axis="y"
onReorder={onReorder}
values={items}
className="flex flex-col gap-1.5"
>
{items
.filter((item) => !item.disabled)
.map((item) => {
const isActive =
(pathname === "/" && item.path === "/") ||
(pathname !== "/" && item.path.startsWith(`/${part}`));
return (
<div className="mt-6">
<nav>
<Reorder.Group
axis="y"
onReorder={onReorder}
values={items}
className="flex flex-col gap-1.5"
>
{items
.filter((item) => !item.disabled)
.map((item) => {
const isActive =
(pathname === "/" && item.path === "/") ||
(pathname !== "/" && item.path.startsWith(`/${part}`));

return (
<Item
key={item.path}
item={item}
isActive={isActive}
onSelect={onSelect}
disabled={item.disabled}
/>
);
})}
</Reorder.Group>
</nav>
</div>
);
return (
<Item
key={item.path}
item={item}
isActive={isActive}
onSelect={onSelect}
disabled={item.disabled}
/>
);
})}
</Reorder.Group>
</nav>
</div>
);
}