Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
0825a1e
Update page to use trpc
nourmalaeb Jan 21, 2026
3876161
Add navigation types
nourmalaeb Jan 21, 2026
43cec28
StepId used before it was defined
nourmalaeb Jan 21, 2026
10e510a
Process editor navigation hook
nourmalaeb Jan 21, 2026
71673b2
Use visibilityConfig in ProcessBuilderProvider
nourmalaeb Jan 21, 2026
3bd7633
Update page to use nav config
nourmalaeb Jan 21, 2026
2bccb59
Nav config and hook
nourmalaeb Jan 21, 2026
dded85b
Add placeholdre content sections
nourmalaeb Jan 21, 2026
38e5878
Loading skeleton for section content
nourmalaeb Jan 21, 2026
049fa9d
Remove lazy loading for now
nourmalaeb Jan 21, 2026
1cd12b7
Cleanup
nourmalaeb Jan 21, 2026
cb4464d
Replace placeholder content
nourmalaeb Jan 21, 2026
ab7e79f
Accommodate no sidebar for single-section steps
nourmalaeb Jan 21, 2026
86579a0
Handle invalid params
nourmalaeb Jan 21, 2026
a84684c
cleanup
nourmalaeb Jan 21, 2026
2f74e00
translations
nourmalaeb Jan 21, 2026
a54d87d
Use history in useQueryParams
nourmalaeb Jan 21, 2026
64e27f5
Update apps/app/src/components/decisions/ProcessBuilder/useProcessNav…
nourmalaeb Jan 22, 2026
2567049
Update apps/app/src/components/decisions/ProcessBuilder/useProcessNav…
nourmalaeb Jan 22, 2026
3228923
Update apps/app/src/components/decisions/ProcessBuilder/useProcessNav…
nourmalaeb Jan 22, 2026
0bff601
Update translations
nourmalaeb Jan 23, 2026
6075fd1
Fix imports, cleanup
nourmalaeb Jan 23, 2026
383c10d
remove unnecessary checks and imports
nourmalaeb Jan 23, 2026
0e8e822
Adds translations
nourmalaeb Jan 23, 2026
1a63cb4
naming convention
nourmalaeb Jan 23, 2026
446240f
Update page to render on server
nourmalaeb Jan 23, 2026
5b7548f
Styling section pages and adding content for scroll testing
nourmalaeb Jan 23, 2026
67550b1
Remove context and sidebar
nourmalaeb Jan 23, 2026
2b5c30c
Responsive styles
nourmalaeb Jan 23, 2026
b271e54
Close sidebar on navigation
nourmalaeb Jan 23, 2026
e9d8315
Styling
nourmalaeb Jan 23, 2026
fa1e409
Move sidebar provider to header component
nourmalaeb Jan 23, 2026
230f6e0
Typecheck
nourmalaeb Jan 23, 2026
6a32d9e
Tabs focus styles
nourmalaeb Jan 23, 2026
ca1d0c9
Simplify sidebar
nourmalaeb Jan 23, 2026
4dd8ed8
Section nav styles
nourmalaeb Jan 23, 2026
d85c01d
Update setp names
nourmalaeb Jan 23, 2026
7c7a2ac
Tab tweaks
nourmalaeb Jan 23, 2026
20d4154
Fix border on tabs pill variant
nourmalaeb Jan 23, 2026
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
@@ -1,44 +1,51 @@
import { createClient } from '@op/api/serverClient';
import { SidebarLayout } from '@op/ui/Sidebar';
import { notFound } from 'next/navigation';

import { ProcessBuilderContent } from '@/components/decisions/ProcessBuilder/ProcessBuilderContent';
import { ProcessBuilderHeader } from '@/components/decisions/ProcessBuilder/ProcessBuilderHeader';
import { ProcessBuilderProvider } from '@/components/decisions/ProcessBuilder/ProcessBuilderProvider';
import { ProcessBuilderSidebar } from '@/components/decisions/ProcessBuilder/ProcessBuilderSidebar';
import { ProcessBuilderSidebar } from '@/components/decisions/ProcessBuilder/ProcessBuilderSectionNav';
import {
DEFAULT_NAVIGATION_CONFIG,
type NavigationConfig,
} from '@/components/decisions/ProcessBuilder/navigationConfig';

const EditDecisionPage = async ({
params,
}: {
params: Promise<{ slug: string }>;
}) => {
const { slug } = await params;
const client = await createClient();
const { slug } = await params;

const decisionProfile = await client.decision.getDecisionBySlug({ slug });
// Get the decision profile to find the instance ID
const decisionProfile = await client.decision.getDecisionBySlug({
slug,
});

if (!decisionProfile || !decisionProfile.processInstance) {
if (!decisionProfile?.processInstance) {
notFound();
}

// TODO: Get navigation config from process instance or process type
const navigationConfig: NavigationConfig = DEFAULT_NAVIGATION_CONFIG;

return (
<ProcessBuilderProvider>
<div className="bg-background relative flex size-full flex-1 flex-col">
<ProcessBuilderHeader
steps={[
{ id: 'overview', label: 'Overview' },
{ id: 'phases', label: 'Phases' },
{ id: 'categories', label: 'Categories' },
{ id: 'voting', label: 'Voting' },
]}
processName={decisionProfile.name}
navigationConfig={navigationConfig}
/>
<SidebarLayout>
<ProcessBuilderSidebar />
<div className="flex-1 p-8">
{/* Main content area - will show section content based on query param */}
<h2>Editing: {decisionProfile.name}</h2>
{/* TODO: Add section-specific content components */}
</div>
</SidebarLayout>
</ProcessBuilderProvider>
<div className="flex grow flex-col overflow-y-auto sm:flex-row">
<ProcessBuilderSidebar navigationConfig={navigationConfig} />
<main className="grow">
<ProcessBuilderContent
decisionId={decisionProfile.id}
decisionName={decisionProfile.name}
navigationConfig={navigationConfig}
/>
</main>
</div>
</div>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
'use client';

import { type SectionProps, getContentComponent } from './contentRegistry';
import { type NavigationConfig } from './navigationConfig';
import { useProcessNavigation } from './useProcessNavigation';

export function ProcessBuilderContent({
decisionId,
decisionName,
navigationConfig,
}: SectionProps & { navigationConfig?: NavigationConfig }) {
const { currentStep, currentSection } =
useProcessNavigation(navigationConfig);

const ContentComponent = getContentComponent(
currentStep?.id,
currentSection?.id,
);

if (!ContentComponent) {
return <div>Section not found</div>;
}

return (
<ContentComponent decisionId={decisionId} decisionName={decisionName} />
);
}
Original file line number Diff line number Diff line change
@@ -1,57 +1,118 @@
'use client';

import { Button } from '@op/ui/Button';
import { useQueryState } from 'nuqs';
import { Key } from '@op/ui/RAC';
import {
Sidebar,
SidebarProvider,
SidebarTrigger,
useSidebar,
} from '@op/ui/Sidebar';
import { Tab, TabList, Tabs } from '@op/ui/Tabs';
import { LuChevronRight, LuCircleAlert, LuHouse, LuPlus } from 'react-icons/lu';

import { Link } from '@/lib/i18n';
import { Link, useTranslations } from '@/lib/i18n';

import { UserAvatarMenu } from '@/components/SiteHeader';

import { type NavigationConfig } from './navigationConfig';
import { useProcessNavigation } from './useProcessNavigation';

export const ProcessBuilderHeader = ({
steps,
processName,
navigationConfig,
}: {
steps?: { id: string; label: string }[];
processName?: string;
navigationConfig?: NavigationConfig;
}) => {
const [currentStep, setCurrentStep] = useQueryState('step');
return (
<header className="relative flex h-14 w-dvw items-center justify-between border-b">
<SidebarProvider>
<ProcessBuilderHeaderContent
processName={processName}
navigationConfig={navigationConfig}
/>

<MobileSidebar navigationConfig={navigationConfig} />
</SidebarProvider>
);
};

const ProcessBuilderHeaderContent = ({
processName,
navigationConfig,
}: {
processName?: string;
navigationConfig?: NavigationConfig;
}) => {
const t = useTranslations();
const { visibleSteps, currentStep, setStep } =
useProcessNavigation(navigationConfig);
const hasSteps = visibleSteps.length > 0;

const { setOpen } = useSidebar();

const handleSelectionChange = (key: Key) => {
setStep(String(key));
setOpen(false);
};

return (
<header className="relative sticky top-0 z-20 flex h-14 w-dvw shrink-0 items-center justify-between border-b bg-white">
<div className="relative z-10 flex items-center gap-2 pl-4 md:pl-8">
<Link href="/" className="flex items-center gap-2 text-primary">
{hasSteps && <SidebarTrigger className="size-4 md:hidden" />}

<Link
href="/"
className="hidden items-center gap-2 text-primary md:flex"
>
<LuHouse className="size-4" />
Home
{t('Home')}
</Link>
<LuChevronRight className="size-4" />
<span>New process</span>
<LuChevronRight className="hidden size-4 md:block" />

<span>{processName || t('New process')}</span>
</div>
<nav className="absolute z-0 hidden h-full w-full justify-center gap-2 md:flex">
{steps &&
steps?.length > 0 &&
steps.map((step) => (
<button
key={step.id}
className={`cursor-pointer border-b border-transparent px-2 text-neutral-gray4 hover:border-neutral-400 hover:text-black data-active:border-black data-active:text-black`}
id={step.id}
onClick={() => setCurrentStep(step.id)}
data-active={step.id === currentStep ? true : undefined}
{hasSteps && (
<nav className="absolute z-0 hidden h-full w-full justify-center md:flex">
<Tabs
selectedKey={currentStep?.id}
onSelectionChange={handleSelectionChange}
className="h-full"
>
<TabList
aria-label={t('Process steps')}
className="h-full border-none"
>
{step.label}
</button>
))}
</nav>
{visibleSteps.map((step) => (
<Tab key={step.id} id={step.id} className="h-full">
{t(step.labelKey)}
</Tab>
))}
</TabList>
</Tabs>
</nav>
)}
<div className="relative z-10 flex gap-4 pr-4 md:pr-8">
{steps && steps.length > 0 && (
{hasSteps && (
<div className="flex gap-2">
<Button
className="flex aspect-square h-8 gap-2 rounded-sm md:aspect-auto"
color="warn"
>
<LuCircleAlert className="size-4 shrink-0" />
<span className="hidden md:block">3 steps remaining</span>
<span className="hidden md:block">
{t(
'{stepCount, plural, =1 {1 step} other {# steps}} remaining',
{
stepCount: 3,
},
)}
</span>
</Button>
<Button className="h-8 rounded-sm">
<LuPlus className="size-4" />
Launch<span className="hidden md:inline"> Process</span>
{t('Launch')}
<span className="hidden md:inline"> {t('Process')}</span>
</Button>
</div>
)}
Expand All @@ -60,3 +121,58 @@ export const ProcessBuilderHeader = ({
</header>
);
};

const MobileSidebar = ({
navigationConfig,
}: {
navigationConfig?: NavigationConfig;
}) => {
const t = useTranslations();
const { visibleSteps, currentStep, setStep } =
useProcessNavigation(navigationConfig);
const hasSteps = visibleSteps.length > 0;
const { setOpen } = useSidebar();

const handleSelectionChange = (key: Key) => {
setStep(String(key));
setOpen(false);
};

if (!hasSteps) {
return null;
}
return (
<Sidebar mobileOnly>
<nav className="flex flex-col gap-2 px-4 py-2">
<Link href="/" className="flex h-8 items-center gap-2 px-4">
<LuHouse className="size-4" />
{t('Home')}
</Link>
<hr />

<Tabs
selectedKey={currentStep?.id}
onSelectionChange={handleSelectionChange}
className="h-full"
>
<TabList
aria-label={t('Process steps')}
className="w-full"
orientation="vertical"
>
{visibleSteps.map((step) => (
<Tab
key={step.id}
id={step.id}
variant="pill"
className="h-8 bg-transparent selected:bg-neutral-offWhite"
>
{t(step.labelKey)}
</Tab>
))}
</TabList>
</Tabs>
</nav>
</Sidebar>
);
};

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
'use client';

import { Key } from '@op/ui/RAC';
import { Tab, TabList, Tabs } from '@op/ui/Tabs';

import { useTranslations } from '@/lib/i18n';

import { type NavigationConfig } from './navigationConfig';
import { useProcessNavigation } from './useProcessNavigation';

export const ProcessBuilderSidebar = ({
navigationConfig,
}: {
navigationConfig?: NavigationConfig;
}) => {
const t = useTranslations();
const { visibleSections, currentSection, currentStep, setSection } =
useProcessNavigation(navigationConfig);

const handleSelectionChange = (key: Key) => {
setSection(String(key));
};

// Don't render sidebar for single-section steps
// These steps manage their own layout (e.g., template step with form builder)
if (visibleSections.length <= 1) {
return null;
}

return (
<nav className="h-12 shrink-0 overflow-x-auto overflow-y-hidden p-0 py-4 md:sticky md:top-0 md:h-full md:w-64 md:overflow-x-hidden md:overflow-y-auto md:border-r md:p-8">
<Tabs
key={currentStep?.id}
orientation="vertical"
selectedKey={currentSection?.id}
onSelectionChange={handleSelectionChange}
>
<TabList
aria-label={t('Section navigation')}
className="scrollbar-none flex w-full gap-4 border-none md:flex-col md:gap-1"
>
{visibleSections.map((section) => (
<Tab
key={section.id}
id={section.id}
variant="pill"
className="first:ml-4 last:mr-4 hover:bg-neutral-gray1 hover:text-charcoal focus-visible:outline-solid md:first:ml-0 md:last:mr-0 selected:text-charcoal md:selected:bg-neutral-offWhite"
>
{t(section.labelKey)}
</Tab>
))}
</TabList>
</Tabs>
</nav>
);
};
Loading