Skip to content

Commit ae316f8

Browse files
Added snapshot -> capture
1 parent 4f6668d commit ae316f8

33 files changed

+1265
-169
lines changed

apps/ui/components.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"$schema": "https://ui.shadcn.com/schema.json",
3+
"style": "new-york",
4+
"rsc": false,
5+
"tsx": true,
6+
"tailwind": {
7+
"config": "tailwind.config.js",
8+
"css": "src/index.css",
9+
"baseColor": "neutral",
10+
"cssVariables": true,
11+
"prefix": ""
12+
},
13+
"aliases": {
14+
"components": "@/components",
15+
"utils": "@/lib/utils",
16+
"ui": "@/components/ui",
17+
"lib": "@/lib",
18+
"hooks": "@/hooks"
19+
}
20+
}

apps/ui/package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@
1717
"@hookform/resolvers": "^3.9.0",
1818
"@microsoft/applicationinsights-react-js": "^17.3.1",
1919
"@microsoft/applicationinsights-web": "^3.3.1",
20+
"@radix-ui/react-icons": "^1.3.0",
21+
"@radix-ui/react-tooltip": "^1.1.2",
2022
"@tanstack/react-query": "^5.52.2",
2123
"@trpc/client": "11.0.0-rc.490",
2224
"@trpc/server": "11.0.0-rc.490",
25+
"class-variance-authority": "^0.7.0",
26+
"clsx": "^2.1.1",
2327
"date-fns": "^3.6.0",
2428
"date-fns-tz": "^3.1.3",
2529
"framer-motion": "^11.3.29",
30+
"lucide-react": "^0.438.0",
31+
"next-themes": "^0.3.0",
2632
"oslo": "^1.2.1",
2733
"react": "^18.3.1",
2834
"react-dom": "^18.3.1",
@@ -31,6 +37,9 @@
3137
"react-markdown": "^9.0.1",
3238
"react-router": "^6.26.1",
3339
"react-router-dom": "^6.26.1",
40+
"sonner": "^1.5.0",
41+
"tailwind-merge": "^2.5.2",
42+
"tailwindcss-animate": "^1.0.7",
3443
"vite-plugin-info": "^0.4.1",
3544
"zod": "^3.23.8",
3645
"zustand": "^4.5.5"

apps/ui/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Loading } from '@critter/react/loaders/Loading';
22
import { Suspense } from 'react';
33
import { RouterProvider } from 'react-router-dom';
4+
import { Toaster } from './components/feedback/Toaster';
45
import { router } from './router';
56
import { CritterAuthenticationProvider } from './services/authentication/CritterAuthenticationProvider';
67
import { BackstageProvider } from './services/backstage/BackstageProvider';
@@ -18,6 +19,7 @@ export const App = () => {
1819
<CritterAuthenticationProvider>
1920
<AppInsightsProvider>
2021
<APIProvider>
22+
<Toaster />
2123
<RouterProvider router={router} />
2224
</APIProvider>
2325
</AppInsightsProvider>

apps/ui/src/pages/snapshots/controls/Dock.tsx renamed to apps/ui/src/components/controls/Dock.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { Button } from '@critter/react/button/juicy';
44
import { Spinner } from '@critter/react/loaders/Spinner';
55
import { AnimatePresence } from 'framer-motion';
66
import { FC, Suspense, useEffect } from 'react';
7-
import { Thumbnail } from '../images/Thumbnail';
7+
import { Thumbnail } from '../../pages/snapshots/images/Thumbnail';
88

99
interface DockProps {
1010
images: string[];
@@ -96,7 +96,7 @@ export const Dock: FC<DockProps> = ({
9696
</Button>
9797
</div>
9898
)}
99-
<div className="flex items-center gap-2 h-24 bg-accent-300 rounded-2xl px-4 py-4 w-fit max-w-full">
99+
<div className="flex items-center gap-2 h-20 bg-accent-300 rounded-2xl px-4 py-4 w-fit max-w-full">
100100
<AnimatePresence initial={false}>
101101
{images.sort().map((image, index) => {
102102
return (
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { Toaster as Sonner } from 'sonner';
2+
3+
type ToasterProps = React.ComponentProps<typeof Sonner>;
4+
5+
const Toaster = ({ ...props }: ToasterProps) => {
6+
return (
7+
<Sonner
8+
className="toaster group"
9+
position="bottom-right"
10+
toastOptions={{
11+
classNames: {
12+
toast:
13+
'group toast font-sans group-[.toaster]:bg-accent-50 border group-[.toaster]:text-accent-900 group-[.toaster]:border-accent-900 group-[.toaster]:border-opacity-10 group-[.toaster]:shadow-lg',
14+
title: 'group-[.toast]:font-semibold',
15+
description: 'group-[.toast]:text-accent-700 group-[.toast]:font-semibold',
16+
actionButton: 'group-[.toast]:bg-accent-50 group-[.toast]:text-accent-900',
17+
cancelButton: 'group-[.toast]:bg-accent-50 group-[.toast]:text-accent-900'
18+
}
19+
}}
20+
{...props}
21+
/>
22+
);
23+
};
24+
export { Toaster };
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { cn } from '@critter/react/utils/cn';
2+
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
3+
import * as React from 'react';
4+
5+
const TooltipProvider = TooltipPrimitive.Provider;
6+
7+
const Tooltip = TooltipPrimitive.Root;
8+
9+
const TooltipTrigger = TooltipPrimitive.Trigger;
10+
11+
const TooltipContent = React.forwardRef<
12+
React.ElementRef<typeof TooltipPrimitive.Content>,
13+
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
14+
>(({ className, sideOffset = 4, ...props }, ref) => (
15+
<TooltipPrimitive.Content
16+
ref={ref}
17+
sideOffset={sideOffset}
18+
className={cn(
19+
'z-50 overflow-hidden rounded-lg bg-accent-300 pt-2 pb-2.5 shadow-[inset_0_-7px_0px_-0.25rem_rgba(0,0,0,0.15)] px-3 text-sm text-accent-950 animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
20+
className
21+
)}
22+
{...props}
23+
/>
24+
));
25+
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
26+
27+
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };
Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,25 @@
1+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/feedback/Tooltip';
2+
import { format } from 'date-fns';
13
import { formatInTimeZone } from 'date-fns-tz';
24
import { FC } from 'react';
35

46
interface TimestampProps {
57
date: Date;
68
}
9+
710
export const Timestamp: FC<TimestampProps> = ({ date }) => {
8-
return <span>{formatInTimeZone(date, 'America/Chicago', 'PP p')}</span>;
11+
return (
12+
<TooltipProvider>
13+
<Tooltip>
14+
<TooltipTrigger>
15+
<span>{formatInTimeZone(date, 'America/Chicago', 'PP p')}</span>
16+
</TooltipTrigger>
17+
<TooltipContent>
18+
<span className="flex gap-2 items-center">
19+
<span>Your time: {format(date, 'PP p')}</span>
20+
</span>
21+
</TooltipContent>
22+
</Tooltip>
23+
</TooltipProvider>
24+
);
925
};

apps/ui/src/index.css

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,68 @@ html {
2121
--accent-color-900: 120 55 12;
2222
--accent-color-950: 75 39 12;
2323
}
24+
25+
@layer base {
26+
:root {
27+
--background: 0 0% 100%;
28+
--foreground: 0 0% 3.9%;
29+
--card: 0 0% 100%;
30+
--card-foreground: 0 0% 3.9%;
31+
--popover: 0 0% 100%;
32+
--popover-foreground: 0 0% 3.9%;
33+
--primary: 0 0% 9%;
34+
--primary-foreground: 0 0% 98%;
35+
--secondary: 0 0% 96.1%;
36+
--secondary-foreground: 0 0% 9%;
37+
--muted: 0 0% 96.1%;
38+
--muted-foreground: 0 0% 45.1%;
39+
--accent: 0 0% 96.1%;
40+
--accent-foreground: 0 0% 9%;
41+
--destructive: 0 84.2% 60.2%;
42+
--destructive-foreground: 0 0% 98%;
43+
--border: 0 0% 89.8%;
44+
--input: 0 0% 89.8%;
45+
--ring: 0 0% 3.9%;
46+
--chart-1: 12 76% 61%;
47+
--chart-2: 173 58% 39%;
48+
--chart-3: 197 37% 24%;
49+
--chart-4: 43 74% 66%;
50+
--chart-5: 27 87% 67%;
51+
--radius: 0.5rem;
52+
}
53+
.dark {
54+
--background: 0 0% 3.9%;
55+
--foreground: 0 0% 98%;
56+
--card: 0 0% 3.9%;
57+
--card-foreground: 0 0% 98%;
58+
--popover: 0 0% 3.9%;
59+
--popover-foreground: 0 0% 98%;
60+
--primary: 0 0% 98%;
61+
--primary-foreground: 0 0% 9%;
62+
--secondary: 0 0% 14.9%;
63+
--secondary-foreground: 0 0% 98%;
64+
--muted: 0 0% 14.9%;
65+
--muted-foreground: 0 0% 63.9%;
66+
--accent: 0 0% 14.9%;
67+
--accent-foreground: 0 0% 98%;
68+
--destructive: 0 62.8% 30.6%;
69+
--destructive-foreground: 0 0% 98%;
70+
--border: 0 0% 14.9%;
71+
--input: 0 0% 14.9%;
72+
--ring: 0 0% 83.1%;
73+
--chart-1: 220 70% 50%;
74+
--chart-2: 160 60% 45%;
75+
--chart-3: 30 80% 55%;
76+
--chart-4: 280 65% 60%;
77+
--chart-5: 340 75% 55%;
78+
}
79+
}
80+
81+
@layer base {
82+
* {
83+
@apply border-border;
84+
}
85+
body {
86+
@apply bg-background text-foreground;
87+
}
88+
}

apps/ui/src/layouts/Main.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,16 @@ import { Menu, MenuItem } from '../components/menu/Menu';
1010

1111
import { Docs } from '@/components/assets/icons/Docs';
1212
import { Wordmark } from '@/components/assets/logos/Wordmark';
13+
import { Variables } from '@/services/backstage/config';
1314
import { usePermissions } from '@/services/permissions/hooks';
15+
import { useVariable } from '@critter/backstage';
1416
import { Loading } from '@critter/react/loaders/Loading';
1517
import { Suspense } from 'react';
1618
import { Outlet } from 'react-router';
1719

1820
export const Main = () => {
1921
const { editor } = usePermissions();
20-
22+
const docsUrl = useVariable<Variables>('docsUrl');
2123
return (
2224
<div className="h-screen min-h-screen max-h-screen w-screen flex flex-col">
2325
{!editor && (
@@ -40,30 +42,32 @@ export const Main = () => {
4042
<CritterCaptureClubLogo />
4143
</MenuItem>
4244
<MenuItem>
43-
<Button className="w-full" disabled>
45+
<Button className="w-full" disabled="Coming soon!">
4446
<Binoculars />
4547
All captures
4648
</Button>
4749
</MenuItem>
4850
<MenuItem>
49-
<Button className="w-full" disabled>
51+
<Button className="w-full" disabled="Coming soon!">
5052
<Binoculars />
5153
My captures
5254
</Button>
5355
</MenuItem>
5456
<MenuItem>
55-
<Link to="/snapshots/pending" className="w-full">
57+
<Link to="/snapshots/pending" className="w-full" disabled="Coming soon!">
5658
<Picture />
5759
Pending screenshots
5860
</Link>
5961
</MenuItem>
6062
<div className="flex-1" />
61-
<MenuItem>
62-
<Link to="/docs" className="w-full">
63-
<Docs />
64-
Documentation
65-
</Link>
66-
</MenuItem>
63+
{docsUrl && (
64+
<MenuItem>
65+
<Link to={docsUrl} className="w-full">
66+
<Docs />
67+
Documentation
68+
</Link>
69+
</MenuItem>
70+
)}
6771
<MenuItem>
6872
<Link to="/auth/signout" className="w-full">
6973
<SignOut />
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { Note } from '@/components/containers/Note';
2+
import { Timestamp } from '@/components/text/Timestamp';
3+
import { useCapture } from '@/services/api/capture';
4+
import { FC, useMemo, useState } from 'react';
5+
import { useParams } from 'react-router';
6+
import { Dock } from '../../components/controls/Dock';
7+
import { Main } from '../snapshots/images/Main';
8+
9+
export const Capture: FC = () => {
10+
const params = useParams();
11+
const id = useMemo(() => Number(params.id), [params.id]);
12+
const snapshot = useCapture(id);
13+
14+
const [mainImageIndex, setMainImageIndex] = useState<number | undefined>(
15+
snapshot.data.images.length > 0 ? 0 : undefined
16+
);
17+
18+
return (
19+
<div className="flex-1 bg-accent-100 p-8 flex flex-col gap-6 items-center">
20+
<nav className="w-full relative flex items-center justify-end">
21+
<Note className="w-fit z-10 absolute top-0 left-0 rounded-md min-w-96">
22+
<h2 className="text-2xl font-medium px-3 outline-none bg-accent-50 placeholder:opacity-25 placeholder:text-accent-900">
23+
{snapshot.data.name}
24+
</h2>
25+
<p className="px-3 py-1 text-sm">
26+
Captured by <strong>{snapshot.data.createdBy}</strong>
27+
</p>
28+
<p className="px-3 py-1 text-sm">
29+
Captured at{' '}
30+
<strong>
31+
<Timestamp date={new Date(snapshot.data.createdAt)} />
32+
</strong>
33+
</p>
34+
</Note>
35+
</nav>
36+
<div className="w-full relative flex-1">
37+
<div className="h-full flex items-center justify-center">
38+
{mainImageIndex !== undefined && (
39+
<Main key={snapshot.data.images[mainImageIndex].id} url={snapshot.data.images[mainImageIndex].url} />
40+
)}
41+
</div>
42+
</div>
43+
<Dock
44+
images={snapshot.data.images.map(i => i.url)}
45+
selectedIndex={mainImageIndex}
46+
setSelectedIndex={setMainImageIndex}
47+
/>
48+
</div>
49+
);
50+
};

0 commit comments

Comments
 (0)