Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
7137395
feat: [US-005] - Simplify header to breadcrumbs only
scazan Mar 4, 2026
e9bced2
feat: [US-007] - Update mobile navigation to use unified sidebar
scazan Mar 4, 2026
cdb1786
format
scazan Mar 4, 2026
b49e9cf
feat: [US-001] - Update navigation config to support dynamic phase se…
scazan Mar 5, 2026
85555a2
feat: [US-002] - Create individual phase page component
scazan Mar 5, 2026
54692e3
feat: [US-003] - Convert Phases section to summary/overview view
scazan Mar 5, 2026
69f8150
feat: [US-004] - Update sidebar to show individual phase sub-items
scazan Mar 5, 2026
9150231
feat: [US-005] - Add Next and Back buttons to footer
scazan Mar 5, 2026
56a6b1e
feat: [US-006] - Add validation-based progress bar to footer
scazan Mar 5, 2026
f768835
feat: [US-007] - Add translation keys for stepper UI strings
scazan Mar 5, 2026
d78ffea
format
scazan Mar 5, 2026
b573212
Steppable sections
scazan Mar 5, 2026
c575313
Phase stepper fixes
scazan Mar 5, 2026
b62528f
Mobile footer
scazan Mar 5, 2026
5324b09
format
scazan Mar 5, 2026
fc32165
feat: [US-001] - Add Summary section to navigation config and content…
scazan Mar 6, 2026
f478307
feat: [US-002] - Conditionally show Summary in sidebar based on valid…
scazan Mar 6, 2026
b572bd3
chore: update PRD and progress for US-002
scazan Mar 6, 2026
a40404f
feat: [US-003] - Build SummarySectionContent with process stats
scazan Mar 6, 2026
a203dee
chore: update PRD and progress for US-003, US-004, US-005
scazan Mar 6, 2026
cf3afbf
fix: remove duplicate JSON translation keys and document participants…
scazan Mar 6, 2026
45bf4e7
Add in summary section
scazan Mar 6, 2026
983cf04
Format, show summary info on non-launched process
scazan Mar 6, 2026
38c2f01
Show validation dots
scazan Mar 6, 2026
087c1b3
Add in dots
scazan Mar 6, 2026
e0dc991
Fix disapearring phases
scazan Mar 6, 2026
f79b3b9
Match styles
scazan Mar 6, 2026
50b5b0c
Update styles
scazan Mar 6, 2026
5015052
Update text sizes
scazan Mar 7, 2026
c3a53fb
Update footer
scazan Mar 7, 2026
40ead4c
Add in LocaleChooser again
scazan Mar 7, 2026
e4a3524
Padding on footer
scazan Mar 7, 2026
13c1780
Allow rubrics
scazan Mar 8, 2026
043c12b
styles
scazan Mar 8, 2026
b524bd7
refactor components
scazan Mar 8, 2026
0bcca7b
More refactoring
scazan Mar 8, 2026
90086aa
Pull out components
scazan Mar 8, 2026
93763dc
Style tweaks
scazan Mar 8, 2026
6eaea77
Rename Participants
scazan Mar 8, 2026
25d565a
Style tweaks, fix mobile footer
scazan Mar 8, 2026
b48d32a
Add Sheet component
scazan Mar 8, 2026
ab17610
Scroll the sheet
scazan Mar 8, 2026
b3f14fe
format sheet
scazan Mar 8, 2026
d0ffc9c
Use sheet for mobile menu
scazan Mar 8, 2026
21df57e
Fix padding on Sheet
scazan Mar 8, 2026
9529454
Sheet classNames
scazan Mar 8, 2026
1f25496
Update dictionaries
scazan Mar 9, 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
Expand Up @@ -5,6 +5,7 @@ import { ProcessBuilderContent } from '@/components/decisions/ProcessBuilder/Pro
import { ProcessBuilderFooter } from '@/components/decisions/ProcessBuilder/ProcessBuilderFooter';
import { ProcessBuilderHeader } from '@/components/decisions/ProcessBuilder/ProcessBuilderHeader';
import { ProcessBuilderSidebar } from '@/components/decisions/ProcessBuilder/ProcessBuilderSectionNav';
import { ProcessBuilderShell } from '@/components/decisions/ProcessBuilder/ProcessBuilderShell';
import { ProcessBuilderStoreInitializer } from '@/components/decisions/ProcessBuilder/ProcessBuilderStoreInitializer';
import type { ProcessBuilderInstanceData } from '@/components/decisions/ProcessBuilder/stores/useProcessBuilderStore';

Expand Down Expand Up @@ -41,32 +42,34 @@ const EditDecisionPage = async ({
};

return (
<div className="bg-background relative flex h-dvh w-full flex-1 flex-col overflow-y-hidden">
<ProcessBuilderStoreInitializer
decisionProfileId={decisionProfile.id}
serverData={serverData}
isDraft={processInstance.status === 'draft'}
/>
<ProcessBuilderHeader instanceId={instanceId} slug={slug} />
<div className="flex min-h-0 grow flex-col overflow-y-auto md:flex-row md:overflow-y-hidden">
<ProcessBuilderSidebar
instanceId={instanceId}
<ProcessBuilderShell>
<div className="bg-background relative flex h-dvh w-full flex-1 flex-col overflow-y-hidden">
<ProcessBuilderStoreInitializer
decisionProfileId={decisionProfile.id}
serverData={serverData}
isDraft={processInstance.status === 'draft'}
/>
<main className="h-full grow overflow-y-auto">
<ProcessBuilderContent
decisionProfileId={decisionProfile.id}
<ProcessBuilderHeader instanceId={instanceId} slug={slug} />
<div className="flex min-h-0 grow flex-col overflow-y-auto md:flex-row md:overflow-y-hidden">
<ProcessBuilderSidebar
instanceId={instanceId}
decisionName={decisionProfile.name}
decisionProfileId={decisionProfile.id}
/>
</main>
<main className="h-full grow overflow-y-auto">
<ProcessBuilderContent
decisionProfileId={decisionProfile.id}
instanceId={instanceId}
decisionName={decisionProfile.name}
/>
</main>
</div>
<ProcessBuilderFooter
instanceId={instanceId}
slug={slug}
decisionProfileId={decisionProfile.id}
/>
</div>
<ProcessBuilderFooter
instanceId={instanceId}
slug={slug}
decisionProfileId={decisionProfile.id}
/>
</div>
</ProcessBuilderShell>
);
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { type SectionProps, getContentComponentFlat } from './contentRegistry';
import { type SectionId } from './navigationConfig';
import { useNavigationConfig } from './useNavigationConfig';
import { useProcessNavigation } from './useProcessNavigation';
import { useProcessPhases } from './useProcessPhases';

export function ProcessBuilderContent({
decisionProfileId,
Expand All @@ -16,7 +17,10 @@ export function ProcessBuilderContent({
}: SectionProps) {
const t = useTranslations();
const navigationConfig = useNavigationConfig(instanceId);
const { currentSection } = useProcessNavigation(navigationConfig);

const phases = useProcessPhases(instanceId, decisionProfileId);

const { currentSection } = useProcessNavigation(navigationConfig, phases);

const access = useUser();
const isAdmin = access.getPermissionsForProfile(decisionProfileId).admin;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,25 @@
import { trpc } from '@op/api/client';
import { ProcessStatus } from '@op/api/encoders';
import { Button } from '@op/ui/Button';
import { DialogTrigger } from '@op/ui/Dialog';
import { Popover } from '@op/ui/Popover';
import { SidebarTrigger } from '@op/ui/Sidebar';
import { toast } from '@op/ui/Toast';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import {
LuCheck,
LuCircle,
LuCircleAlert,
LuLogOut,
LuPlus,
LuSave,
} from 'react-icons/lu';
import { LuLogOut, LuSave } from 'react-icons/lu';

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

import { LaunchProcessModal } from './LaunchProcessModal';
import { ProgressIndicator } from './components/ProgressIndicator';
import { useProcessBuilderStore } from './stores/useProcessBuilderStore';
import type { ValidationSummary } from './validation/processBuilderValidation';
import { useNavigationConfig } from './useNavigationConfig';
import { useProcessNavigation } from './useProcessNavigation';
import { useProcessPhases } from './useProcessPhases';
import { useProcessBuilderValidation } from './validation/useProcessBuilderValidation';

const NAV_BTN_CLS =
'inline-flex h-10 items-center justify-center rounded-lg border border-neutral-gray1 px-3 text-sm text-primary shadow-[0px_0px_16px_0px_rgba(20,35,38,0.04)] transition-colors hover:bg-neutral-gray1';

export const ProcessBuilderFooter = ({
instanceId,
slug,
Expand All @@ -38,6 +36,14 @@ export const ProcessBuilderFooter = ({
const [isLaunchModalOpen, setIsLaunchModalOpen] = useState(false);

const validation = useProcessBuilderValidation(decisionProfileId);
const navigationConfig = useNavigationConfig(instanceId);

const phases = useProcessPhases(instanceId, decisionProfileId);

const { goNext, goBack, hasNext, hasPrev } = useProcessNavigation(
navigationConfig,
phases,
);

const { data: decisionProfile } = trpc.decision.getDecisionBySlug.useQuery(
{ slug },
Expand Down Expand Up @@ -93,42 +99,85 @@ export const ProcessBuilderFooter = ({

return (
<>
<footer className="sticky bottom-0 z-20 flex h-14 shrink-0 items-center justify-between border-t bg-white/80 px-4 backdrop-blur md:px-8">
<div className="flex items-center gap-2">
<Link
href={`/decisions/${slug}`}
className="inline-flex h-8 items-center gap-2 rounded-md border border-neutral-gray2 px-3 text-sm text-charcoal transition-colors hover:bg-neutral-gray1"
>
<LuLogOut className="size-4 rotate-180" />
{t('Exit')}
</Link>
</div>

<div className="flex items-center gap-2">
{validation.stepsRemaining > 0 && (
<StepsRemainingPopover validation={validation} />
)}
<Button
className="h-8 rounded-md"
onPress={handleLaunchOrSave}
isDisabled={
updateInstance.isPending ||
!validation.isReadyToLaunch ||
isTerminalStatus
}
>
{isDraft ? (
<LuPlus className="size-4" />
) : (
<LuSave className="size-4" />
<footer className="sticky bottom-0 z-20 shrink-0 border-t bg-white/80 px-8 py-2 backdrop-blur">
{/* Mobile: full-width progress bar overlaying top edge */}
<ProgressIndicator
percentage={validation.completionPercentage}
variant="strip"
/>

<div className="flex h-full items-center justify-between md:px-0">
{/* Left: Exit — matches sidebar width */}
<div className="flex items-center gap-2 md:w-60 md:shrink-0">
<Link
href={`/decisions/${slug}`}
className="inline-flex h-10 items-center gap-1 px-2 text-base text-charcoal transition-colors hover:bg-neutral-gray1"
>
<LuLogOut className="size-4 rotate-180" />
{t('Exit')}
</Link>
</div>

{/* Center + Right: content-width area after sidebar */}
<div className="hidden md:flex md:flex-1 md:items-center">
{/* Progress bar constrained to content width, centered like page content */}
<ProgressIndicator
percentage={validation.completionPercentage}
variant="bar"
/>

{/* Desktop action buttons */}
<div className="flex shrink-0 items-center gap-2">
{hasPrev && (
<button type="button" onClick={goBack} className={NAV_BTN_CLS}>
{t('Back')}
</button>
)}
{hasNext && (
<button type="button" onClick={goNext} className={NAV_BTN_CLS}>
{t('Next')}
</button>
)}
{(!isDraft ||
(validation.isReadyToLaunch && !isTerminalStatus)) && (
<Button
className="h-8 rounded-md"
onPress={handleLaunchOrSave}
isDisabled={updateInstance.isPending}
>
{!isDraft && <LuSave className="size-4" />}
<span className="hidden md:inline">
{isDraft ? t('Launch Process') : t('Update Process')}
</span>
</Button>
)}
</div>
</div>

{/* Mobile: Menu + Back + Next + Launch */}
<div className="flex items-center justify-end gap-2 md:hidden">
<SidebarTrigger />
{hasPrev && (
<button type="button" onClick={goBack} className={NAV_BTN_CLS}>
{t('Back')}
</button>
)}
{hasNext && (
<button type="button" onClick={goNext} className={NAV_BTN_CLS}>
{t('Next')}
</button>
)}
{(!isDraft ||
(validation.isReadyToLaunch && !isTerminalStatus)) && (
<Button
className="h-8 rounded-md"
onPress={handleLaunchOrSave}
isDisabled={updateInstance.isPending}
>
{isDraft ? t('Launch') : t('Update')}
</Button>
)}
<span className="md:hidden">
{isDraft ? t('Launch') : t('Update')}
</span>
<span className="hidden md:inline">
{isDraft ? t('Launch Process') : t('Update Process')}
</span>
</Button>
</div>
</div>
</footer>

Expand All @@ -143,53 +192,3 @@ export const ProcessBuilderFooter = ({
</>
);
};

const StepsRemainingPopover = ({
validation,
}: {
validation: ValidationSummary;
}) => {
const t = useTranslations();

return (
<DialogTrigger>
<Button
className="flex aspect-square h-8 gap-2 rounded-md md:aspect-auto"
color="warn"
>
<LuCircleAlert className="size-4 shrink-0" />
<span className="hidden md:block">
{t('{stepCount, plural, =1 {1 step} other {# steps}} remaining', {
stepCount: validation.stepsRemaining,
})}
</span>
</Button>
<Popover
placement="top end"
className="w-72 rounded-lg border bg-white p-4 shadow-lg"
>
<p className="mb-3 font-medium text-neutral-black">
{t('Complete these steps to launch')}
</p>
<ul className="space-y-3">
{validation.checklist.map((item) => (
<li key={item.id} className="flex items-center gap-2">
{item.isValid ? (
<LuCheck className="size-5 shrink-0 text-functional-green" />
) : (
<LuCircle className="size-5 shrink-0 text-neutral-gray4" />
)}
<span
className={
item.isValid ? 'text-functional-green' : 'text-neutral-black'
}
>
{t(item.labelKey)}
</span>
</li>
))}
</ul>
</Popover>
</DialogTrigger>
);
};
Loading
Loading