Skip to content

Commit

Permalink
feat: adding breadcrumb all around the app
Browse files Browse the repository at this point in the history
  • Loading branch information
carere committed Jan 23, 2025
1 parent e928552 commit 110d70e
Show file tree
Hide file tree
Showing 41 changed files with 627 additions and 293 deletions.
3 changes: 2 additions & 1 deletion packages/app-builder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@
"ui-design-system": "workspace:*",
"ui-icons": "workspace:*",
"zod": "^3.23.8",
"zustand": "^5.0.0"
"zustand": "^5.0.0",
"@swan-io/boxed": "^3.2.0"
}
}
79 changes: 79 additions & 0 deletions packages/app-builder/src/components/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { Link, useMatches } from '@remix-run/react';
import { Future } from '@swan-io/boxed';
import clsx from 'clsx';
import {
type FunctionComponent,
type PropsWithChildren,
useEffect,
useState,
} from 'react';
import { filter } from 'remeda';

type Links = {
Element: FunctionComponent | undefined;
pathname: string;
}[];

const Separator = () => (
<span className="text-s text-grey-80 font-bold">/</span>
);

const LinkWrapper = ({
isLast,
children,
pathname,
}: PropsWithChildren<{ isLast: boolean; pathname: string }>) => {
return isLast ? (
children
) : (
<Link
to={pathname}
className={clsx('transition-colors', {
'text-grey-50 hover:text-grey-00': !isLast,
})}
>
{children}
</Link>
);
};

export const BreadCrumbs = () => {
const matches = useMatches();
const [links, setLinks] = useState<Links>([]);

console.log('Matches', matches);

useEffect(() => {
Future.all(
matches.map(({ id, pathname }) =>
Future.fromPromise(
import(/* @vite-ignore */ `../${id}`) as Promise<{
BreadCrumb?: FunctionComponent;
}>,
).map((result) => ({
Element: result.isOk() ? result.value.BreadCrumb : undefined,
pathname,
})),
),
)
.map(filter((r) => r.Element !== undefined))
.then(setLinks);
}, [matches]);

return (
<div className="flex flex-row items-center gap-4">
{links.map(({ Element, pathname }, i) => {
const isLast = i === links.length - 1;

return Element ? (
<div className="text-s flex items-center gap-4 font-bold" key={i}>
<LinkWrapper isLast={isLast} pathname={pathname}>
<Element />
</LinkWrapper>
{!isLast ? <Separator /> : null}
</div>
) : null;
})}
</div>
);
};
22 changes: 15 additions & 7 deletions packages/app-builder/src/repositories/TestRunRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
} from '@app-builder/models/testrun';
import { toUUID } from '@app-builder/utils/short-uuid';
import { addDays } from 'date-fns';
import { sleep } from 'radash';
import { randomInteger } from 'remeda';
import short from 'short-uuid';

Expand All @@ -30,13 +31,13 @@ export interface TestRunRepository {

const testruns: TestRun[] = [
{
id: '989ed5e4-c7ca-4685-9535-a3e1ab9dfc75',
refIterationId: '6f6fe0d8-9a1a-4d5a-bdd7-fa7fcda1b4e3',
scenarioId: '6f6fe0d8-0804-4500-ae68-f4e56ea780d7',
testIterationId: '6f6fe0d8-bbc8-4df3-a913-c0064ed99e4e',
id: '84386e82-2da2-493a-a08f-0e6456756193',
refIterationId: '84386e82-45cf-4fdf-9112-9fab1a55e8be',
scenarioId: '84386e82-a26f-4317-b1e3-373efdf6f6b1',
testIterationId: '84386e82-4d57-4aea-9c62-d60d496319a6',
startDate: new Date().toISOString(),
endDate: addDays(new Date(), 1).toISOString(),
creatorId: '96762987-8895-4af2-9c0a-2dffde09985c',
creatorId: '34c74c92-4570-4623-9ea8-fa9374d11e4b',
status: 'up',
},
];
Expand Down Expand Up @@ -176,8 +177,15 @@ export const makeGetTestRunRepository2 = () => {
return Promise.resolve();
},
listTestRuns: () => Promise.resolve(testruns),
listDecisions: () => Promise.resolve(testrunDecisions),
listRuleExecutions: () => Promise.resolve(testrunRuleExecutions),
listDecisions: async () => {
await sleep(1500);
return testrunDecisions;
},
listRuleExecutions: async () => {
await sleep(4000);
//throw new Error('Some Timeout');
return testrunRuleExecutions;
},
launchTestRun: (args: TestRunCreateInput) => {
const testRun: TestRun = {
id: toUUID(short.generate()),
Expand Down
22 changes: 15 additions & 7 deletions packages/app-builder/src/routes/_builder+/analytics.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ErrorComponent, Page } from '@app-builder/components';
import { BreadCrumbs } from '@app-builder/components/Breadcrumbs';
import { isAnalyticsAvailable } from '@app-builder/services/feature-access';
import { serverServices } from '@app-builder/services/init.server';
import { notFound } from '@app-builder/utils/http/http-responses';
Expand Down Expand Up @@ -41,19 +42,26 @@ export async function loader({ request }: LoaderFunctionArgs) {
});
}

export const BreadCrumb = () => {
const { t } = useTranslation(['navigation']);

return (
<div className="flex items-center">
<Icon icon="analytics" className="me-2 size-6" />
<span className="line-clamp-1 text-start">
{t('navigation:analytics')}
</span>
</div>
);
};

export default function Analytics() {
const { t } = useTranslation(handle.i18n);
const { globalDashbord } = useLoaderData<typeof loader>();

return (
<Page.Main>
<Page.Header className="justify-between">
<div className="flex flex-row items-center">
<Icon icon="analytics" className="me-2 size-6" />
<span className="line-clamp-1 text-start">
{t('navigation:analytics')}
</span>
</div>
<BreadCrumbs />
</Page.Header>
<iframe
src={globalDashbord.src}
Expand Down
17 changes: 13 additions & 4 deletions packages/app-builder/src/routes/_builder+/api.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Page } from '@app-builder/components';
import { BreadCrumbs } from '@app-builder/components/Breadcrumbs';
import { serverServices } from '@app-builder/services/init.server';
import { downloadFile } from '@app-builder/utils/download-file';
import { getRoute } from '@app-builder/utils/routes';
Expand Down Expand Up @@ -39,17 +40,25 @@ export async function loader({ request }: LoaderFunctionArgs) {
return json({ openapi });
}

export const BreadCrumb = () => {
const { t } = useTranslation(['navigation']);

return (
<div className="flex items-center">
<Icon icon="world" className="me-2 size-6" />
<span className="line-clamp-1 text-start">{t('navigation:api')}</span>
</div>
);
};

export default function Api() {
const { t } = useTranslation(handle.i18n);
const { openapi } = useLoaderData<typeof loader>();

return (
<Page.Main>
<Page.Header className="justify-between">
<div className="flex flex-row items-center">
<Icon icon="world" className="me-2 size-6" />
<span className="line-clamp-1 text-start">{t('navigation:api')}</span>
</div>
<BreadCrumbs />
</Page.Header>
<Page.Container>
<Page.Content>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
Page,
TabLink,
} from '@app-builder/components';
import { BreadCrumbs } from '@app-builder/components/Breadcrumbs';
import { casesI18n } from '@app-builder/components/Cases';
import { CaseHistory } from '@app-builder/components/Cases/CaseHistory/CaseHistory';
import {
Expand All @@ -23,7 +24,7 @@ import {
} from '@app-builder/services/feature-access';
import { serverServices } from '@app-builder/services/init.server';
import { getRoute, type RouteID } from '@app-builder/utils/routes';
import { fromParams, fromUUID } from '@app-builder/utils/short-uuid';
import { fromParams } from '@app-builder/utils/short-uuid';
import {
defer,
type LoaderFunctionArgs,
Expand Down Expand Up @@ -129,30 +130,33 @@ export async function loader({ request, params }: LoaderFunctionArgs) {

export function useCurrentCase() {
return useRouteLoaderData(
'routes/_builder+/cases+/$caseId._layout' satisfies RouteID,
'routes/_builder+/cases+/$caseId+/_layout' satisfies RouteID,
) as SerializeFrom<typeof loader>;
}

export const BreadCrumb = () => {
const { caseDetail } = useLoaderData<typeof loader>();

return (
<div className="flex items-center gap-4">
<span className="line-clamp-2 text-start">{caseDetail.name}</span>
<CopyToClipboardButton toCopy={caseDetail.id}>
<span className="text-s line-clamp-1 max-w-40 font-normal">
<span className="font-medium">ID</span> {caseDetail.id}
</span>
</CopyToClipboardButton>
</div>
);
};

export default function CasePage() {
const { t } = useTranslation(handle.i18n);
const { caseDetail } = useLoaderData<typeof loader>();

return (
<Page.Main>
<Page.Header className="justify-between gap-8">
<div className="flex flex-row items-center gap-4">
<Page.BackLink
to={getRoute('/cases/inboxes/:inboxId', {
inboxId: fromUUID(caseDetail.inboxId),
})}
/>
<span className="line-clamp-2 text-start">{caseDetail.name}</span>
<CopyToClipboardButton toCopy={caseDetail.id}>
<span className="text-s line-clamp-1 max-w-40 font-normal">
<span className="font-medium">ID</span> {caseDetail.id}
</span>
</CopyToClipboardButton>
</div>
<Page.Header className="justify-between">
<BreadCrumbs />
<EditCaseStatus caseId={caseDetail.id} status={caseDetail.status} />
</Page.Header>
<div className="flex size-full flex-col overflow-hidden">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { Link } from '@remix-run/react';
import { type Namespace } from 'i18next';
import { Trans, useTranslation } from 'react-i18next';

import { useCurrentCase } from './$caseId._layout';
import { useCurrentCase } from './_layout';

export const handle = {
i18n: ['common', 'navigation', 'data', ...casesI18n] satisfies Namespace,
Expand All @@ -27,7 +27,7 @@ export default function CasePage() {
Link: (
<Link
className="hover:text-purple-65 text-purple-82 hover:underline"
to={getRoute('/decisions/')}
to={getRoute('/decisions')}
/>
),
}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UploadFile } from '@app-builder/routes/ressources+/cases+/upload-file';
import { type Namespace } from 'i18next';
import { Trans, useTranslation } from 'react-i18next';

import { useCurrentCase } from './$caseId._layout';
import { useCurrentCase } from './_layout';

export const handle = {
i18n: ['common', 'navigation', ...casesI18n] satisfies Namespace,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { formatDateTime, useFormatLanguage } from '@app-builder/utils/format';
import { type Namespace } from 'i18next';
import { useTranslation } from 'react-i18next';

import { useCurrentCase } from './$caseId._layout';
import { useCurrentCase } from './_layout';

export const handle = {
i18n: ['common', 'navigation', ...casesI18n] satisfies Namespace,
Expand Down
5 changes: 2 additions & 3 deletions packages/app-builder/src/routes/_builder+/cases+/_index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Page } from '@app-builder/components';
import { BreadCrumbs } from '@app-builder/components/Breadcrumbs';
import { CreateInbox } from '@app-builder/routes/ressources+/settings+/inboxes+/create';
import { isCreateInboxAvailable } from '@app-builder/services/feature-access';
import { serverServices } from '@app-builder/services/init.server';
Expand All @@ -8,7 +9,6 @@ import { json, type LoaderFunctionArgs, redirect } from '@remix-run/node';
import { useLoaderData } from '@remix-run/react';
import { useTranslation } from 'react-i18next';
import * as R from 'remeda';
import { Icon } from 'ui-icons';

export async function loader({ request }: LoaderFunctionArgs) {
const { authService } = serverServices;
Expand Down Expand Up @@ -36,8 +36,7 @@ export default function Cases() {
return (
<Page.Main>
<Page.Header>
<Icon icon="case-manager" className="me-2 size-6" />
{t('navigation:case_manager')}
<BreadCrumbs />
</Page.Header>
<Page.Container>
<Page.Content>
Expand Down
18 changes: 18 additions & 0 deletions packages/app-builder/src/routes/_builder+/cases+/_layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Outlet } from '@remix-run/react';
import { useTranslation } from 'react-i18next';
import { Icon } from 'ui-icons';

export const BreadCrumb = () => {
const { t } = useTranslation(['navigation', 'cases']);

return (
<div className="flex items-center">
<Icon icon="case-manager" className="me-2 size-6" />
{t('navigation:case_manager')}
</div>
);
};

export default function CasesLayout() {
return <Outlet />;
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export async function loader({ request, params }: LoaderFunctionArgs) {
} catch (error) {
// if inbox is deleted or user no longer have access, the user is redirected
if (isNotFoundHttpError(error) || isForbiddenHttpError(error)) {
return redirect(getRoute('/cases/'));
return redirect(getRoute('/cases'));
} else {
throw error;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Page } from '@app-builder/components';
import { BreadCrumbs } from '@app-builder/components/Breadcrumbs';
import { casesI18n } from '@app-builder/components/Cases';
import { CreateInbox } from '@app-builder/routes/ressources+/settings+/inboxes+/create';
import { isCreateInboxAvailable } from '@app-builder/services/feature-access';
Expand Down Expand Up @@ -30,15 +31,25 @@ export async function loader({ request }: LoaderFunctionArgs) {
});
}

export const BreadCrumb = () => {
const { t } = useTranslation(['navigation', 'cases']);

return (
<div className="flex items-center">
<Icon icon="case-manager" className="me-2 size-6" />
<span className="text-s">{t('navigation:case_manager')}</span>
</div>
);
};

export default function Cases() {
const { t } = useTranslation(handle.i18n);
const { inboxes, isCreateInboxAvailable } = useLoaderData<typeof loader>();

return (
<Page.Main>
<Page.Header>
<Icon icon="case-manager" className="me-2 size-6" />
{t('navigation:case_manager')}
<BreadCrumbs />
</Page.Header>
<div className="flex h-full flex-row overflow-hidden">
<div className="border-e-grey-90 bg-grey-100 flex h-full w-fit min-w-[200px] max-w-[300px] shrink-0 flex-col overflow-y-auto border-e p-4">
Expand Down
Loading

0 comments on commit 110d70e

Please sign in to comment.