diff --git a/apps/web/src/app/(private)/dashboard/layout.tsx b/apps/web/src/app/(private)/dashboard/layout.tsx index 529680493..bd6e635f5 100644 --- a/apps/web/src/app/(private)/dashboard/layout.tsx +++ b/apps/web/src/app/(private)/dashboard/layout.tsx @@ -4,10 +4,8 @@ import { Container, TableBlankSlate, TableWithHeader, - Text, } from '@latitude-data/web-ui' import { AppTabs } from '$/app/(private)/AppTabs' -import { AppLayout } from '$/components/layouts' import { getCurrentUser } from '$/services/auth/getCurrentUser' import { getSession } from '$/services/auth/getSession' import { ROUTES } from '$/services/routes' @@ -15,7 +13,6 @@ import Link from 'next/link' import { redirect } from 'next/navigation' import { getActiveProjectsCached } from '../_data-access' -import { NAV_LINKS } from '../_lib/constants' import { ProjectsTable } from './_components/ProjectsTable' export default async function DashboardLayout({ @@ -26,52 +23,38 @@ export default async function DashboardLayout({ const data = await getSession() if (!data.session) return redirect(ROUTES.auth.login) - const { workspace, user } = await getCurrentUser() + const { workspace } = await getCurrentUser() const projects = await getActiveProjectsCached({ workspaceId: workspace.id }) - const breadcrumbs = [ - { - name: workspace.name, - }, - { - name: Projects, - }, - ] return ( - - - - {children} - - Add project - - } - table={ - <> - {projects.length > 0 && } - {projects.length === 0 && ( - - - Create your first project - - - } - /> - )} - - } - /> - - + + + {children} + + Add project + + } + table={ + <> + {projects.length > 0 && } + {projects.length === 0 && ( + + + Create your first project + + + } + /> + )} + + } + /> + ) } diff --git a/apps/web/src/app/(private)/datasets/layout.tsx b/apps/web/src/app/(private)/datasets/layout.tsx index 534a60393..70dc6ba7c 100644 --- a/apps/web/src/app/(private)/datasets/layout.tsx +++ b/apps/web/src/app/(private)/datasets/layout.tsx @@ -1,57 +1,39 @@ import { ReactNode } from 'react' import { DatasetsRepository } from '@latitude-data/core/repositories' -import { Container, TableWithHeader, Text } from '@latitude-data/web-ui' +import { Container, TableWithHeader } from '@latitude-data/web-ui' import { AppTabs } from '$/app/(private)/AppTabs' import { DatasetsTable } from '$/app/(private)/datasets/_components/DatasetsTable' -import { AppLayout } from '$/components/layouts' import { getCurrentUser } from '$/services/auth/getCurrentUser' import { ROUTES } from '$/services/routes' import Link from 'next/link' -import { NAV_LINKS } from '../_lib/constants' - export default async function DatasetsList({ children, }: Readonly<{ children: ReactNode }>) { - const { workspace, user } = await getCurrentUser() + const { workspace } = await getCurrentUser() const scope = new DatasetsRepository(workspace.id) const datasets = await scope.findAll().then((r) => r.unwrap()) return ( - Datasets, - }, - ]} - > - - - {children} - - - - Generate dataset - - - - Upload dataset - - - } - table={} - /> - - + + + {children} + + + Generate dataset + + + Upload dataset + + + } + table={} + /> + ) } diff --git a/apps/web/src/app/(private)/error.tsx b/apps/web/src/app/(private)/error.tsx index 38c23d74c..a99fa8a86 100644 --- a/apps/web/src/app/(private)/error.tsx +++ b/apps/web/src/app/(private)/error.tsx @@ -4,8 +4,6 @@ import { useEffect } from 'react' import { ErrorComponent, useSession } from '@latitude-data/web-ui/browser' import * as Sentry from '@sentry/nextjs' -import { NAV_LINKS } from '$/app/(private)/_lib/constants' -import { AppLayout } from '$/components/layouts' export default function Error({ error, @@ -20,20 +18,9 @@ export default function Error({ }, [error]) return ( - - - + ) } diff --git a/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/dashboard/layout.tsx b/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/dashboard/layout.tsx deleted file mode 100644 index 1ddac9c5e..000000000 --- a/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/dashboard/layout.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import { ReactNode } from 'react' - -import { Text } from '@latitude-data/web-ui' -import { getEvaluationByUuidCached } from '$/app/(private)/_data-access' -import { NAV_LINKS } from '$/app/(private)/_lib/constants' -import BreadcrumbLink from '$/components/BreadcrumbLink' -import { AppLayout } from '$/components/layouts' -import { getCurrentUser } from '$/services/auth/getCurrentUser' -import { ROUTES } from '$/services/routes' - -export default async function EvaluationLayout({ - params, - children, -}: { - params: { evaluationUuid: string } - children: ReactNode -}) { - const evaluation = await getEvaluationByUuidCached(params.evaluationUuid) - const session = await getCurrentUser() - - return ( - - ), - }, - { - name: {evaluation.name}, - }, - ]} - > - {children} - - ) -} diff --git a/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/editor/layout.tsx b/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/editor/layout.tsx index 6debb21ae..1b95a861e 100644 --- a/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/editor/layout.tsx +++ b/apps/web/src/app/(private)/evaluations/(evaluation)/[evaluationUuid]/editor/layout.tsx @@ -1,17 +1,12 @@ import { ReactNode } from 'react' import { getFreeRuns } from '@latitude-data/core/services/freeRunsManager/index' -import { Text } from '@latitude-data/web-ui' import { getEvaluationByUuidCached, getProviderApiKeysCached, } from '$/app/(private)/_data-access' -import { NAV_LINKS } from '$/app/(private)/_lib/constants' -import BreadcrumbLink from '$/components/BreadcrumbLink' -import { AppLayout } from '$/components/layouts' import providerApiKeyPresenter from '$/presenters/providerApiKeyPresenter' import { getCurrentUser } from '$/services/auth/getCurrentUser' -import { ROUTES } from '$/services/routes' import EvaluationEditorLayout from './_components/EvaluationEditorLayout' @@ -25,37 +20,17 @@ export default async function DocumentPage({ const { workspace } = await getCurrentUser() const evaluationUuid = params.evaluationUuid const evaluation = await getEvaluationByUuidCached(evaluationUuid) - const session = await getCurrentUser() const providerApiKeys = await getProviderApiKeysCached() const freeRunsCount = await getFreeRuns(workspace.id) return ( - - ), - }, - { - name: {evaluation.name}, - }, - ]} + - - {children} - - + {children} + ) } diff --git a/apps/web/src/app/(private)/evaluations/(root)/layout.tsx b/apps/web/src/app/(private)/evaluations/(root)/layout.tsx index eafb8664d..0a5d9397d 100644 --- a/apps/web/src/app/(private)/evaluations/(root)/layout.tsx +++ b/apps/web/src/app/(private)/evaluations/(root)/layout.tsx @@ -1,36 +1,19 @@ import { ReactNode } from 'react' -import { Text } from '@latitude-data/web-ui' import { getEvaluationTemplatesCached } from '$/app/(private)/_data-access' import Evaluations from '$/app/(private)/evaluations/_components/Evaluations' -import { AppLayout } from '$/components/layouts' -import { getCurrentUser } from '$/services/auth/getCurrentUser' - -import { NAV_LINKS } from '../../_lib/constants' export default async function EvaluationsLayout({ children, }: { children: ReactNode }) { - const session = await getCurrentUser() const evaluationTemplates = await getEvaluationTemplatesCached() return ( - Evaluations, - }, - ]} - > + <> {children} - + ) } diff --git a/apps/web/src/app/(private)/layout.tsx b/apps/web/src/app/(private)/layout.tsx index f571d65d4..b8d3bcd95 100644 --- a/apps/web/src/app/(private)/layout.tsx +++ b/apps/web/src/app/(private)/layout.tsx @@ -3,6 +3,7 @@ import { ReactNode } from 'react' import { SessionProvider } from '@latitude-data/web-ui/browser' import { createSupportUserIdentity } from '$/app/(private)/_lib/createSupportUserIdentity' import { SupportChat } from '$/components/IntercomSupportChat' +import { AppLayout } from '$/components/layouts' import { LatitudeWebsocketsProvider, SocketIOProvider, @@ -14,6 +15,7 @@ import { ROUTES } from '$/services/routes' import { redirect } from 'next/navigation' import { CSPostHogProvider, IdentifyUser } from '../providers' +import { NAV_LINKS } from './_lib/constants' export default async function PrivateLayout({ children, @@ -33,7 +35,9 @@ export default async function PrivateLayout({ - {children} + + {children} + diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/ArchivedCommitsList.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/ArchivedCommitsList.tsx index 9c61f1290..ad74a6f10 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/ArchivedCommitsList.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/ArchivedCommitsList.tsx @@ -8,7 +8,7 @@ import { DocumentVersion, } from '@latitude-data/core/browser' import { Text } from '@latitude-data/web-ui' -import useCommits from '$/stores/commitsStore' +import { useCommits } from '$/stores/commitsStore' import { CommitItem, CommitItemSkeleton, SimpleUser } from './CommitItem' import { CommitItemsWrapper } from './CommitItemsWrapper' diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/CurrentCommitsList.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/CurrentCommitsList.tsx index 04ef5b360..12151aafb 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/CurrentCommitsList.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/CurrentCommitsList.tsx @@ -6,7 +6,7 @@ import { type Commit, } from '@latitude-data/core/browser' import { ReactStateDispatch } from '@latitude-data/web-ui' -import useCommits from '$/stores/commitsStore' +import { useCommits } from '$/stores/commitsStore' import { CommitItem, CommitItemSkeleton, SimpleUser } from './CommitItem' import { CommitItemsWrapper } from './CommitItemsWrapper' diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/DeleteDraftCommitModal/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/DeleteDraftCommitModal/index.tsx index f4dc8cc2f..f77e5a1a6 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/DeleteDraftCommitModal/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CommitSelector/DeleteDraftCommitModal/index.tsx @@ -7,7 +7,7 @@ import { useCurrentProject, } from '@latitude-data/web-ui' import { ROUTES } from '$/services/routes' -import useCommits from '$/stores/commitsStore' +import { useCommits } from '$/stores/commitsStore' import { useRouter } from 'next/navigation' export default function DeleteDraftCommitModal({ diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CreateDraftCommitModal/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CreateDraftCommitModal/index.tsx index 453df5bfd..639723008 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CreateDraftCommitModal/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/CreateDraftCommitModal/index.tsx @@ -10,7 +10,7 @@ import { } from '@latitude-data/web-ui' import { useFormAction } from '$/hooks/useFormAction' import { ROUTES } from '$/services/routes' -import useCommits from '$/stores/commitsStore' +import { useCommits } from '$/stores/commitsStore' import { useRouter } from 'next/navigation' export default function DraftCommitModal({ diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/PublishDraftCommitModal/index.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/PublishDraftCommitModal/index.tsx index 55ab8436c..e5cc2ea1d 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/PublishDraftCommitModal/index.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/_components/Sidebar/PublishDraftCommitModal/index.tsx @@ -22,7 +22,7 @@ import { import { getChangedDocumentsInDraftAction } from '$/actions/commits/getChangedDocumentsInDraftAction' import { useCurrentTheme } from '$/hooks/useCurrentTheme' import { ROUTES } from '$/services/routes' -import useCommits from '$/stores/commitsStore' +import { useCommits } from '$/stores/commitsStore' import Link from 'next/link' import { useRouter } from 'next/navigation' import { useServerAction } from 'zsa-react' diff --git a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/layout.tsx b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/layout.tsx index 5dd7513e8..06f6cec5f 100644 --- a/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/layout.tsx +++ b/apps/web/src/app/(private)/projects/[projectId]/versions/[commitUuid]/documents/[documentUuid]/evaluations/[evaluationId]/layout.tsx @@ -3,13 +3,20 @@ import { ReactNode } from 'react' import { readMetadata } from '@latitude-data/compiler' import { EvaluationResultableType } from '@latitude-data/core/browser' import { env } from '@latitude-data/env' -import { Icon, TableWithHeader, Text, Tooltip } from '@latitude-data/web-ui' +import { + Breadcrumb, + BreadcrumbItem, + BreadcrumbSeparator, + Icon, + TableWithHeader, + Text, + Tooltip, +} from '@latitude-data/web-ui' import { findCommitCached, getProviderApiKeyCached, } from '$/app/(private)/_data-access' import BreadcrumbLink from '$/components/BreadcrumbLink' -import { Breadcrumb } from '$/components/layouts/AppLayout/Header' import { ROUTES } from '$/services/routes' import Link from 'next/link' @@ -65,50 +72,45 @@ export default async function ConnectedEvaluationLayout({
+ + + + + + + {evaluation.name} + + + {TYPE_TEXT[evaluation.configuration.type]} + + - ), - }, - { - name: ( -
- {evaluation.name} - - {TYPE_TEXT[evaluation.configuration.type]} - - - - - } - > - Go to evaluation - -
- ), - }, - ]} - /> + > + + + } + > + Go to evaluation +
+
+ } actions={ - - ), - }, - { - name: ( - - ), - }, - { - name: ( - - ), - }, - ]} - > - {children} - + {children} ) diff --git a/apps/web/src/app/(private)/settings/layout.tsx b/apps/web/src/app/(private)/settings/layout.tsx index eade7b6c7..0a24da804 100644 --- a/apps/web/src/app/(private)/settings/layout.tsx +++ b/apps/web/src/app/(private)/settings/layout.tsx @@ -1,11 +1,8 @@ import { ReactNode } from 'react' -import { Container, Text, TitleWithActions } from '@latitude-data/web-ui' +import { Container, TitleWithActions } from '@latitude-data/web-ui' import { AppTabs } from '$/app/(private)/AppTabs' -import { AppLayout } from '$/components/layouts' -import { getCurrentUser } from '$/services/auth/getCurrentUser' -import { NAV_LINKS } from '../_lib/constants' import Memberships from './_components/Memberships' import ProviderApiKeys from './_components/ProviderApiKeys' import WorkspaceApiKeys from './_components/WorkspaceApiKeys' @@ -16,31 +13,15 @@ export default async function SettingsLayout({ }: Readonly<{ children: ReactNode }>) { - const session = await getCurrentUser() - const breadcrumbs = [ - { - name: session.workspace.name, - }, - { - name: Settings, - }, - ] - return ( - - - - {children} - - - - - - - + + + {children} + + + + + + ) } diff --git a/apps/web/src/app/not-found.tsx b/apps/web/src/app/not-found.tsx index 7eeee4616..27c9ad043 100644 --- a/apps/web/src/app/not-found.tsx +++ b/apps/web/src/app/not-found.tsx @@ -1,28 +1,18 @@ import { Button } from '@latitude-data/web-ui' import { ErrorComponent } from '@latitude-data/web-ui/browser' -import { NAV_LINKS } from '$/app/(private)/_lib/constants' -import { AppLayout } from '$/components/layouts' -import { getSafeCurrentUser } from '$/services/auth/getCurrentUser' import { ROUTES } from '$/services/routes' import Link from 'next/link' export default async function GlobalNoFound() { - const session = await getSafeCurrentUser() return ( - - - - - } - /> - + + + + } + /> ) } diff --git a/apps/web/src/components/BreadcrumbLink/index.tsx b/apps/web/src/components/BreadcrumbLink/index.tsx index f9fc751a1..9e8dee297 100644 --- a/apps/web/src/components/BreadcrumbLink/index.tsx +++ b/apps/web/src/components/BreadcrumbLink/index.tsx @@ -11,9 +11,14 @@ export default function BreadcrumbLink({ showBackIcon?: boolean }) { return ( - + {showBackIcon && } - {name} + + {name} + ) } diff --git a/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Evaluations/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Evaluations/index.tsx new file mode 100644 index 000000000..2e3c69629 --- /dev/null +++ b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Evaluations/index.tsx @@ -0,0 +1,53 @@ +import { useMemo } from 'react' + +import { + BreadcrumbItem, + BreadcrumbItemSkeleton, + BreadcrumbSeparator, +} from '@latitude-data/web-ui' +import { EvaluationRoutes, ROUTES } from '$/services/routes' +import useEvaluations from '$/stores/evaluations' + +import { BreadcrumbSelector, BreadcrumbSelectorOption } from '../Selector' + +export function EvaluationBreadcrumbItems({ + segments, +}: { + segments: string[] +}) { + const evaluationUuid = segments[0]! + + const { data: evaluations, isLoading } = useEvaluations() + const currentEvaluation = useMemo( + () => evaluations?.find((evaluation) => evaluation.uuid === evaluationUuid), + [evaluations, evaluationUuid], + ) + + const options = useMemo(() => { + if (!evaluations) return [] + return evaluations.map((p) => ({ + label: p.name, + href: segments[1] + ? ROUTES.evaluations.detail({ uuid: p.uuid })[ + segments[1] as EvaluationRoutes + ].root + : ROUTES.evaluations.detail({ uuid: p.uuid }).root, + })) + }, [evaluations, segments]) + + return ( + <> + + + {isLoading ? ( + + ) : ( + + )} + + + ) +} diff --git a/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Projects/Versions/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Projects/Versions/index.tsx new file mode 100644 index 000000000..b6636cdc6 --- /dev/null +++ b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Projects/Versions/index.tsx @@ -0,0 +1,71 @@ +import { useMemo } from 'react' + +import { Commit, HEAD_COMMIT } from '@latitude-data/core/browser' +import { + Badge, + BreadcrumbItem, + BreadcrumbItemSkeleton, + BreadcrumbSeparator, + ClickToCopy, + Text, +} from '@latitude-data/web-ui' +import { useCommitsFromProject } from '$/stores/commitsStore' + +export function CommitBreadcrumbItems({ + segments, + projectId, +}: { + segments: string[] + projectId: number +}) { + const commitUuid = segments[0]! + + const { data: commits, isLoading } = useCommitsFromProject(projectId, { + commitStatus: undefined, + }) + + const headCommit = useMemo(() => { + if (!commits) return undefined + return commits.reduce((acc, c) => { + if (!acc) return c + if (!acc.mergedAt) return c + if (c.mergedAt && acc.mergedAt < c.mergedAt) return c + return acc + }, undefined) + }, [commits]) + + const currentCommit = useMemo(() => { + if (!commits) return undefined + if (commitUuid === HEAD_COMMIT) return headCommit + return commits.find((c) => c.uuid === commitUuid) + }, [commits, headCommit, commitUuid]) + + return ( + <> + + + {isLoading ? ( + + ) : ( +
+ + {currentCommit?.title ?? commitUuid} + + + + {commitUuid === HEAD_COMMIT + ? 'Live' + : currentCommit?.mergedAt + ? `v${currentCommit.version}` + : commitUuid.split('-')[0]} + + +
+ )} +
+ + ) +} diff --git a/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Projects/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Projects/index.tsx new file mode 100644 index 000000000..1eed50fc3 --- /dev/null +++ b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Projects/index.tsx @@ -0,0 +1,53 @@ +import { useMemo } from 'react' + +import { + BreadcrumbItem, + BreadcrumbItemSkeleton, + BreadcrumbSeparator, +} from '@latitude-data/web-ui' +import { ROUTES } from '$/services/routes' +import useProjects from '$/stores/projects' + +import { BreadcrumbSelector, BreadcrumbSelectorOption } from '../Selector' +import { CommitBreadcrumbItems } from './Versions' + +export function ProjectBreadcrumbItems({ segments }: { segments: string[] }) { + const projectId = Number(segments[0]) + + const { data: projects, isLoading } = useProjects() + const currentProject = useMemo( + () => projects?.find((project) => project.id === projectId), + [projects, projectId], + ) + + const options = useMemo(() => { + if (!projects) return [] + return projects.map((p) => ({ + label: p.name, + href: ROUTES.projects.detail({ id: p.id }).root, + })) + }, [projects]) + + return ( + <> + + + {isLoading ? ( + + ) : ( + + )} + + + {segments.length > 2 && segments[1] == 'versions' && ( + + )} + + ) +} diff --git a/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Root/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Root/index.tsx new file mode 100644 index 000000000..3292009c4 --- /dev/null +++ b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Root/index.tsx @@ -0,0 +1,49 @@ +import { BreadcrumbItem, BreadcrumbSeparator } from '@latitude-data/web-ui' +import { ROUTES } from '$/services/routes' + +import { EvaluationBreadcrumbItems } from '../Evaluations' +import { ProjectBreadcrumbItems } from '../Projects' +import { BreadcrumbSelector, BreadcrumbSelectorOption } from '../Selector' + +export function RootBreadcrumbItems({ segments }: { segments: string[] }) { + const options = { + projects: { + label: 'Projects', + href: ROUTES.projects.root, + }, + evaluations: { + label: 'Evaluations', + href: ROUTES.evaluations.root, + }, + datasets: { + label: 'Datasets', + href: ROUTES.datasets.root, + }, + settings: { + label: 'Settings', + href: ROUTES.settings.root, + }, + } as Record + + const rootSegment = segments[0] === 'dashboard' ? 'projects' : segments[0] // For some reason the root URL for projects is "dashboard" instead of "projects" + const selectedOption = rootSegment ? options[rootSegment] : undefined + + return ( + <> + + + + + + {segments.length > 1 && rootSegment === 'projects' && ( + + )} + {segments.length > 2 && rootSegment === 'evaluations' && ( + + )} + + ) +} diff --git a/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Selector.tsx b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Selector.tsx new file mode 100644 index 000000000..f3c431f51 --- /dev/null +++ b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/Selector.tsx @@ -0,0 +1,73 @@ +'use client' + +import { ReactNode, useState } from 'react' +import { isString } from 'lodash-es' + +import { Button, Icon, Popover, Text } from '@latitude-data/web-ui' +import Link from 'next/link' + +export type BreadcrumbSelectorOption = { + href: string + label: string | ReactNode +} + +export function BreadcrumbSelector({ + label, + options, +}: { + label: string | ReactNode + options: BreadcrumbSelectorOption[] +}) { + const [isOpen, setIsOpen] = useState(false) + + return ( + + + + + +
    + {options.map(({ href, label: optionLabel }, idx) => ( +
  • + + + +
  • + ))} +
+
+
+ ) +} diff --git a/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/index.tsx new file mode 100644 index 000000000..3487e5008 --- /dev/null +++ b/apps/web/src/components/layouts/AppLayout/Header/Breadcrumb/index.tsx @@ -0,0 +1,23 @@ +import { Breadcrumb, Icon } from '@latitude-data/web-ui' +import { ROUTES } from '$/services/routes' +import Link from 'next/link' +import { useSelectedLayoutSegments } from 'next/navigation' + +import { RootBreadcrumbItems } from './Root' + +export function HeaderBreadcrumb() { + const segments = useSelectedLayoutSegments() + + return ( + + + + + + + + ) +} diff --git a/apps/web/src/components/layouts/AppLayout/Header/UsageIndicator/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/UsageIndicator/index.tsx index 3c798ad53..4fa647700 100644 --- a/apps/web/src/components/layouts/AppLayout/Header/UsageIndicator/index.tsx +++ b/apps/web/src/components/layouts/AppLayout/Header/UsageIndicator/index.tsx @@ -104,7 +104,7 @@ export function UsageIndicator() { showBackground /> - + {data?.usage} / {data?.max} diff --git a/apps/web/src/components/layouts/AppLayout/Header/index.tsx b/apps/web/src/components/layouts/AppLayout/Header/index.tsx index 2c73c7aa0..cdb0eb83d 100644 --- a/apps/web/src/components/layouts/AppLayout/Header/index.tsx +++ b/apps/web/src/components/layouts/AppLayout/Header/index.tsx @@ -1,82 +1,12 @@ 'use client' -import { ReactNode } from 'react' - -import { Icon, SessionUser, Text, ThemeButton } from '@latitude-data/web-ui' -import { ROUTES } from '$/services/routes' -import Link from 'next/link' -import { Fragment } from 'react/jsx-runtime' +import { SessionUser, Text, ThemeButton } from '@latitude-data/web-ui' import AvatarDropdown from './AvatarDropdown' +import { HeaderBreadcrumb } from './Breadcrumb' import { RewardsButton } from './Rewards' import { UsageIndicator } from './UsageIndicator' -function BreadcrumbSeparator() { - return ( - - - - ) -} - -type IBreadCrumb = { - name: string | ReactNode -} - -export function Breadcrumb({ - breadcrumbs, - showLogo = false, -}: { - breadcrumbs: IBreadCrumb[] - showLogo?: boolean -}) { - return ( -
    - {showLogo ? ( -
  • - - - - -
  • - ) : null} - {breadcrumbs.map((breadcrumb, idx) => { - const isLast = idx === breadcrumbs.length - 1 - return ( - -
  • - {typeof breadcrumb.name === 'string' ? ( - - {breadcrumb.name as string} - - ) : ( - breadcrumb.name - )} -
  • - {!isLast ? ( -
  • - -
  • - ) : null} -
    - ) - })} -
- ) -} type INavigationLink = { label: string href?: string @@ -98,28 +28,24 @@ function NavLink({ label, href, onClick, _target }: INavigationLink) { export type AppHeaderProps = { navigationLinks: INavigationLink[] currentUser: SessionUser | undefined - breadcrumbs?: IBreadCrumb[] } export default function AppHeader({ - breadcrumbs = [], navigationLinks, currentUser, }: AppHeaderProps) { return ( -
-
- -
- - - -
+
+ +
+ + +
) diff --git a/apps/web/src/components/layouts/AppLayout/index.tsx b/apps/web/src/components/layouts/AppLayout/index.tsx index 1e6ee3b56..005bf9499 100644 --- a/apps/web/src/components/layouts/AppLayout/index.tsx +++ b/apps/web/src/components/layouts/AppLayout/index.tsx @@ -13,7 +13,6 @@ export type AppLayoutProps = AppHeaderProps & { export default function AppLayout({ children, currentUser, - breadcrumbs, navigationLinks, scrollable = true, }: AppLayoutProps) { @@ -23,11 +22,7 @@ export default function AppLayout({ 'overflow-y-auto custom-scrollbar': scrollable, })} > - +
{children}
diff --git a/apps/web/src/stores/commitsStore.ts b/apps/web/src/stores/commitsStore.ts index 7fab67f7d..604a62f0d 100644 --- a/apps/web/src/stores/commitsStore.ts +++ b/apps/web/src/stores/commitsStore.ts @@ -8,23 +8,26 @@ import useLatitudeAction from '$/hooks/useLatitudeAction' import { ROUTES } from '$/services/routes' import useSWR, { SWRConfiguration } from 'swr' -export default function useCommits( - opts: SWRConfiguration & { - onSuccessCreate?: (commit: Commit) => void - onSuccessDestroy?: (commit: Commit) => void - onSuccessPublish?: (commit: Commit) => void - commitStatus?: CommitStatus - } = {}, -) { - const { - onSuccessCreate, - onSuccessDestroy, - onSuccessPublish, - commitStatus = CommitStatus.Draft, - } = opts +type CommitOptions = SWRConfiguration & { + onSuccessCreate?: (commit: Commit) => void + onSuccessDestroy?: (commit: Commit) => void + onSuccessPublish?: (commit: Commit) => void + commitStatus?: CommitStatus +} + +export function useCommits(opts: CommitOptions = {}) { const { project } = useCurrentProject() + return useCommitsFromProject(project.id, opts) +} + +export function useCommitsFromProject( + projectId: number, + opts: CommitOptions = {}, +) { + const { onSuccessCreate, onSuccessDestroy, onSuccessPublish, commitStatus } = + opts const { toast } = useToast() - const route = ROUTES.api.projects.detail(project.id).commits.root + const route = ROUTES.api.projects.detail(projectId).commits.root const fetcher = useFetcher( commitStatus ? `${route}?status=${commitStatus}` : route, ) @@ -33,7 +36,7 @@ export default function useCommits( data = [], mutate, ...rest - } = useSWR(['commits', project.id, commitStatus], fetcher, opts) + } = useSWR(['commits', projectId, commitStatus], fetcher, opts) const { execute: createDraft, isPending: isCreating } = useLatitudeAction( createDraftCommitAction, { diff --git a/packages/web-ui/src/ds/atoms/Button/index.tsx b/packages/web-ui/src/ds/atoms/Button/index.tsx index ab44abadf..9314dc7c1 100644 --- a/packages/web-ui/src/ds/atoms/Button/index.tsx +++ b/packages/web-ui/src/ds/atoms/Button/index.tsx @@ -128,6 +128,7 @@ export type ButtonProps = ButtonHTMLAttributes & isLoading?: boolean fancy?: boolean lookDisabled?: boolean + ellipsis?: boolean } const Button = forwardRef(function Button( @@ -143,6 +144,7 @@ const Button = forwardRef(function Button( children, disabled, lookDisabled, + ellipsis, ...props }, ref, @@ -164,6 +166,7 @@ const Button = forwardRef(function Button( { 'w-full': fullWidth, 'opacity-50': lookDisabled, + 'overflow-hidden': ellipsis, }, )} ref={ref} @@ -174,6 +177,9 @@ const Button = forwardRef(function Button( className={cn( 'relative', buttonVariants({ variant, size, className, fanciness }), + { + 'overflow-hidden': ellipsis, + }, )} > {variant === 'shiny' && ( @@ -188,6 +194,7 @@ const Button = forwardRef(function Button(
{iconProps ? : null} diff --git a/packages/web-ui/src/ds/atoms/Icons/index.tsx b/packages/web-ui/src/ds/atoms/Icons/index.tsx index b421eeead..7c8deef24 100644 --- a/packages/web-ui/src/ds/atoms/Icons/index.tsx +++ b/packages/web-ui/src/ds/atoms/Icons/index.tsx @@ -5,6 +5,7 @@ import { ChevronDown, ChevronLeft, ChevronRight, + ChevronsUpDown, ChevronUp, CircleAlert, CircleHelp, @@ -61,6 +62,7 @@ const Icons = { chevronDown: ChevronDown, chevronRight: ChevronRight, chevronLeft: ChevronLeft, + chevronsUpDown: ChevronsUpDown, circleHelp: CircleHelp, clipboard: Copy, code: Code, diff --git a/packages/web-ui/src/ds/atoms/Tooltip/index.tsx b/packages/web-ui/src/ds/atoms/Tooltip/index.tsx index 876794bb2..45a02a4ea 100644 --- a/packages/web-ui/src/ds/atoms/Tooltip/index.tsx +++ b/packages/web-ui/src/ds/atoms/Tooltip/index.tsx @@ -16,7 +16,7 @@ const TooltipRoot = TooltipPrimitive.Root const TooltipTrigger = TooltipPrimitive.Trigger -type TooltipVariant = 'default' | 'destructive' +type TooltipVariant = 'default' | 'destructive' | 'inverse' type PropviderProps = ComponentPropsWithoutRef type RootProps = ComponentPropsWithoutRef type ContentProps = ComponentPropsWithoutRef< @@ -49,6 +49,7 @@ const TooltipContent = forwardRef< { 'bg-background border': variant === 'default', 'bg-destructive': variant === 'destructive', + 'bg-foreground text-background': variant === 'inverse', }, )} {...props} diff --git a/packages/web-ui/src/ds/molecules/Breadcrumb/Item.tsx b/packages/web-ui/src/ds/molecules/Breadcrumb/Item.tsx new file mode 100644 index 000000000..d93ab5a3d --- /dev/null +++ b/packages/web-ui/src/ds/molecules/Breadcrumb/Item.tsx @@ -0,0 +1,27 @@ +import { ReactNode } from 'react' + +import { cn } from '../../../lib/utils' +import { Skeleton } from '../../atoms' + +export function BreadcrumbItem({ + children, + noShrink, +}: { + children: ReactNode + noShrink?: boolean +}) { + return ( +
  • + {children} +
  • + ) +} + +export function BreadcrumbItemSkeleton({ className }: { className?: string }) { + return +} diff --git a/packages/web-ui/src/ds/molecules/Breadcrumb/Separator.tsx b/packages/web-ui/src/ds/molecules/Breadcrumb/Separator.tsx new file mode 100644 index 000000000..86d54a8e3 --- /dev/null +++ b/packages/web-ui/src/ds/molecules/Breadcrumb/Separator.tsx @@ -0,0 +1,17 @@ +export function BreadcrumbSeparator() { + return ( + + + + ) +} diff --git a/packages/web-ui/src/ds/molecules/Breadcrumb/index.tsx b/packages/web-ui/src/ds/molecules/Breadcrumb/index.tsx new file mode 100644 index 000000000..f5c043abb --- /dev/null +++ b/packages/web-ui/src/ds/molecules/Breadcrumb/index.tsx @@ -0,0 +1,12 @@ +import { ReactNode } from 'react' + +export function Breadcrumb({ children }: { children: ReactNode }) { + return ( +
      + {children} +
    + ) +} + +export * from './Item' +export * from './Separator' diff --git a/packages/web-ui/src/ds/molecules/ClicktoCopy/index.tsx b/packages/web-ui/src/ds/molecules/ClicktoCopy/index.tsx index 729af0f00..d1daf3c15 100644 --- a/packages/web-ui/src/ds/molecules/ClicktoCopy/index.tsx +++ b/packages/web-ui/src/ds/molecules/ClicktoCopy/index.tsx @@ -20,6 +20,7 @@ export function ClickToCopy({ side='right' align='center' delayDuration={250} + variant='inverse' trigger={
    } > - Click to copy: {copyValue} + Click to copy: {copyValue} ) } diff --git a/packages/web-ui/src/ds/molecules/index.ts b/packages/web-ui/src/ds/molecules/index.ts index 3136a1b91..8c7d847a6 100644 --- a/packages/web-ui/src/ds/molecules/index.ts +++ b/packages/web-ui/src/ds/molecules/index.ts @@ -18,3 +18,4 @@ export * from './CollapsibleBox' export * from './LoadingText' export * from './FakeProgress' export * from './ThemeButton' +export * from './Breadcrumb'