-
Notifications
You must be signed in to change notification settings - Fork 3
feat(apollo-vertex): modular dashboard — foundation [1/5] #612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export default { | ||
| "*": { display: "hidden" }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| "use client"; | ||
|
|
||
| import dynamic from "next/dynamic"; | ||
|
|
||
| const DashboardTemplate = dynamic( | ||
| () => | ||
| import("@/templates/dashboard/DashboardTemplate").then( | ||
| (mod) => mod.DashboardTemplate, | ||
| ), | ||
| { ssr: false }, | ||
| ); | ||
|
|
||
| export default function DashboardMinimalPreviewPage() { | ||
| return ( | ||
| <div className="fixed inset-0 z-50 bg-background not-prose"> | ||
| <DashboardTemplate shellVariant="minimal" /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| "use client"; | ||
|
|
||
| import dynamic from "next/dynamic"; | ||
|
|
||
| const DashboardTemplate = dynamic( | ||
| () => | ||
| import("@/templates/dashboard/DashboardTemplate").then( | ||
| (mod) => mod.DashboardTemplate, | ||
| ), | ||
| { ssr: false }, | ||
| ); | ||
|
|
||
| export default function DashboardPreviewPage() { | ||
| return ( | ||
| <div className="fixed inset-0 z-50 bg-background not-prose"> | ||
| <DashboardTemplate /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| "use client"; | ||
|
|
||
| // Stub — full implementation is added in a later PR once all dependencies are available. | ||
| export function DashboardContent() { | ||
| return null; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| "use client"; | ||
|
|
||
| import { useState, type ReactNode } from "react"; | ||
| import { ecommerceDataset, type DashboardDataset } from "./dashboard-data"; | ||
| import { DashboardDataContext } from "./dashboard-data-context"; | ||
|
|
||
| export function DashboardDataProvider({ children }: { children: ReactNode }) { | ||
| const [data, setData] = useState<DashboardDataset>(ecommerceDataset); | ||
|
|
||
| return ( | ||
| <DashboardDataContext.Provider value={{ data, setDataset: setData }}> | ||
| {children} | ||
| </DashboardDataContext.Provider> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,135 @@ | ||
| "use client"; | ||
|
|
||
| import { useEffect, useState } from "react"; | ||
|
|
||
| type Phase = "logo" | "skeleton" | "done"; | ||
|
|
||
| interface DashboardLoadingProps { | ||
| children: React.ReactNode; | ||
| triggerReplay?: number; | ||
| } | ||
|
|
||
| function LogoPhase({ exiting }: { exiting: boolean }) { | ||
| return ( | ||
| <div | ||
| className={`absolute inset-0 flex flex-col items-center justify-center transition-all duration-500 ${ | ||
| exiting ? "opacity-0 scale-95" : "opacity-100 scale-100" | ||
| }`} | ||
| > | ||
| {/* Morphing glow */} | ||
| <div className="absolute"> | ||
| <div className="size-40 rounded-full bg-gradient-to-br from-insight-500/30 to-primary-400/30 blur-3xl animate-pulse" /> | ||
| </div> | ||
| <div className="absolute"> | ||
| <div | ||
| className="size-32 rounded-full bg-gradient-to-tr from-primary-400/20 to-insight-500/20 blur-2xl" | ||
| style={{ animation: "morph 4s ease-in-out infinite" }} | ||
| /> | ||
| </div> | ||
|
|
||
| {/* App icon */} | ||
| <div className="relative size-16 rounded-2xl bg-gradient-to-br from-insight-500 to-primary-400 border-2 border-white/10 flex items-center justify-center shadow-lg"> | ||
| <img | ||
| src="/UiPath.svg" | ||
| alt="UiPath" | ||
| className="size-8 brightness-0 invert" | ||
| /> | ||
| </div> | ||
|
|
||
| {/* Loading text */} | ||
| <p className="mt-6 text-sm text-muted-foreground animate-pulse"> | ||
| Creating your overview... | ||
| </p> | ||
|
|
||
| <style>{` | ||
| @keyframes morph { | ||
| 0%, 100% { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; transform: rotate(0deg) scale(1); } | ||
| 25% { border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; transform: rotate(45deg) scale(1.1); } | ||
| 50% { border-radius: 50% 60% 30% 60% / 30% 40% 70% 60%; transform: rotate(90deg) scale(0.95); } | ||
| 75% { border-radius: 60% 30% 50% 40% / 70% 50% 40% 60%; transform: rotate(135deg) scale(1.05); } | ||
| } | ||
| `}</style> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| function SkeletonPhase({ exiting }: { exiting: boolean }) { | ||
| return ( | ||
| <div | ||
| className={`absolute inset-0 p-6 transition-all duration-500 ${ | ||
| exiting ? "opacity-0 scale-[0.99]" : "opacity-100 scale-100" | ||
| }`} | ||
| > | ||
| <div className="space-y-2 mb-6"> | ||
| <div className="h-3 w-32 rounded-full bg-muted animate-pulse" /> | ||
| <div className="h-7 w-64 rounded-full bg-muted animate-pulse" /> | ||
| </div> | ||
| <div className="grid grid-cols-2 gap-1 h-[calc(100%-80px)]"> | ||
| <div className="flex flex-col gap-1"> | ||
| <div className="flex-1 rounded-2xl bg-muted/50 animate-pulse" /> | ||
| <div className="h-14 rounded-2xl bg-muted/50 animate-pulse" /> | ||
| </div> | ||
| <div className="grid grid-cols-2 grid-rows-2 gap-1"> | ||
| <div className="rounded-2xl bg-muted/50 animate-pulse" /> | ||
| <div className="rounded-2xl bg-muted/50 animate-pulse" /> | ||
| <div className="rounded-2xl bg-muted/50 animate-pulse" /> | ||
| <div className="rounded-2xl bg-muted/50 animate-pulse" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| export function DashboardLoading({ | ||
| children, | ||
| triggerReplay, | ||
| }: DashboardLoadingProps) { | ||
| const [phase, setPhase] = useState<Phase>("done"); | ||
| const [exiting, setExiting] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| if (triggerReplay === 0) return; | ||
| if (triggerReplay) { | ||
| setExiting(false); | ||
| setPhase("logo"); | ||
| } | ||
| }, [triggerReplay]); | ||
|
|
||
| useEffect(() => { | ||
| if (phase === "done") return; | ||
|
|
||
| if (phase === "logo") { | ||
| const timer = setTimeout(() => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why are we simulating loading? how will consumers handle these? will they use this file, or is it just for show?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair question. The intent was to show what a dataset-switch transition could feel like — a logo → skeleton → content sequence that plays when AI generates or switches a dashboard view. That said, your concern about how consumers would use this is valid, and the trigger was also accidentally wired to a constant so it never actually fired. We've removed it from this revision — it will come back later as a more intentional, documented pattern. |
||
| setExiting(true); | ||
| setTimeout(() => { | ||
| setExiting(false); | ||
| setPhase("skeleton"); | ||
| }, 500); | ||
| }, 2000); | ||
| return () => clearTimeout(timer); | ||
| } | ||
|
|
||
| if (phase === "skeleton") { | ||
| const timer = setTimeout(() => { | ||
| setExiting(true); | ||
| setTimeout(() => { | ||
| setPhase("done"); | ||
| }, 500); | ||
| }, 1000); | ||
| return () => clearTimeout(timer); | ||
| } | ||
| }, [phase]); | ||
|
|
||
| if (phase === "done") { | ||
| return ( | ||
| <div className="animate-in fade-in duration-500 h-full">{children}</div> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="relative h-full"> | ||
| {phase === "logo" && <LogoPhase exiting={exiting} />} | ||
| {phase === "skeleton" && <SkeletonPhase exiting={exiting} />} | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| import { createRootRoute, createRoute, Outlet } from "@tanstack/react-router"; | ||
| import { DashboardContent } from "./DashboardContent"; | ||
| import { DashboardShellWrapper } from "./DashboardShellWrapper"; | ||
|
|
||
| export const dashboardRootRoute = createRootRoute(); | ||
|
|
||
| // --- Sidebar variant routes --- | ||
|
|
||
| export const dashboardShellRoute = createRoute({ | ||
| getParentRoute: () => dashboardRootRoute, | ||
| path: "/preview/dashboard", | ||
| component: () => ( | ||
| <DashboardShellWrapper> | ||
| <Outlet /> | ||
| </DashboardShellWrapper> | ||
| ), | ||
| }); | ||
|
|
||
| export const dashboardIndexRoute = createRoute({ | ||
| getParentRoute: () => dashboardShellRoute, | ||
| path: "/", | ||
| component: DashboardContent, | ||
| }); | ||
|
|
||
| export const dashboardHomeRoute = createRoute({ | ||
| getParentRoute: () => dashboardShellRoute, | ||
| path: "/home", | ||
| component: DashboardContent, | ||
| }); | ||
|
|
||
| export const dashboardCatchAllRoute = createRoute({ | ||
| getParentRoute: () => dashboardShellRoute, | ||
| path: "$", | ||
| component: DashboardContent, | ||
| }); | ||
|
|
||
| // --- Minimal variant routes --- | ||
|
|
||
| export const dashboardMinimalShellRoute = createRoute({ | ||
| getParentRoute: () => dashboardRootRoute, | ||
| path: "/preview/dashboard-minimal", | ||
| component: () => ( | ||
| <DashboardShellWrapper variant="minimal"> | ||
| <Outlet /> | ||
| </DashboardShellWrapper> | ||
| ), | ||
| }); | ||
|
|
||
| export const dashboardMinimalIndexRoute = createRoute({ | ||
| getParentRoute: () => dashboardMinimalShellRoute, | ||
| path: "/", | ||
| component: DashboardContent, | ||
| }); | ||
|
|
||
| export const dashboardMinimalCatchAllRoute = createRoute({ | ||
| getParentRoute: () => dashboardMinimalShellRoute, | ||
| path: "$", | ||
| component: DashboardContent, | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes — these SVGs are used across several dashboard components (the overview card, insight card actions, prompt bar, expanded drilldown, and the AI Assistant panel). They've been updated to use "AI Assistant" branding in the latest revision.