dashboard: add dark mode

This commit is contained in:
Carl-Gerhard Lindesvärd
2024-03-28 06:47:04 +01:00
parent 64701bf29f
commit a1fa48da75
37 changed files with 181 additions and 155 deletions

View File

@@ -106,7 +106,7 @@ export function EventEdit({ event, open, setOpen }: Props) {
setIcon(name); setIcon(name);
}} }}
className={cn( className={cn(
'flex inline-flex h-8 w-8 flex-shrink-0 cursor-pointer items-center justify-center rounded-md bg-slate-100 transition-all', 'inline-flex h-8 w-8 flex-shrink-0 cursor-pointer items-center justify-center rounded-md bg-slate-100 transition-all',
name === selectedIcon name === selectedIcon
? 'scale-110 ring-1 ring-black' ? 'scale-110 ring-1 ring-black'
: '[&_svg]:opacity-50' : '[&_svg]:opacity-50'
@@ -133,7 +133,7 @@ export function EventEdit({ event, open, setOpen }: Props) {
)} )}
> >
{SelectedIcon ? ( {SelectedIcon ? (
<SelectedIcon size={16} /> <SelectedIcon size={16} className={getText(color)} />
) : ( ) : (
<svg <svg
className={`${getText(color)} opacity-70`} className={`${getText(color)} opacity-70`}

View File

@@ -55,8 +55,9 @@ export function EventListItem(props: EventListItemProps) {
<button <button
onClick={() => setIsDetailsOpen(true)} onClick={() => setIsDetailsOpen(true)}
className={cn( className={cn(
'card flex w-full items-center justify-between rounded-lg p-4 transition-colors hover:bg-slate-50', 'card hover:bg-light-background flex w-full items-center justify-between rounded-lg p-4 transition-colors',
meta?.conversion && `bg-${meta.color}-50 hover:bg-${meta.color}-100` meta?.conversion &&
`bg-${meta.color}-50 dark:bg-${meta.color}-900 hover:bg-${meta.color}-100 dark:hover:bg-${meta.color}-700`
)} )}
> >
<div className="flex items-center gap-4 text-left text-sm"> <div className="flex items-center gap-4 text-left text-sm">

View File

@@ -49,7 +49,7 @@ export default function EventListener() {
setCounter(0); setCounter(0);
router.refresh(); router.refresh();
}} }}
className="flex h-8 items-center gap-2 rounded border border-border bg-white px-3 text-sm font-medium leading-none" className="flex h-8 items-center gap-2 rounded border border-border bg-background px-3 text-sm font-medium leading-none"
> >
<div className="relative"> <div className="relative">
<div <div

View File

@@ -40,8 +40,8 @@ function LinkWithIcon({
return ( return (
<Link <Link
className={cn( className={cn(
'flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium leading-none text-slate-800 transition-all transition-colors hover:bg-blue-100', 'text-text flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium leading-none transition-all transition-colors hover:bg-slate-100',
active && 'bg-blue-50', active && 'bg-slate-100',
className className
)} )}
href={href} href={href}

View File

@@ -46,7 +46,7 @@ export function LayoutSidebar({
/> />
<div <div
className={cn( className={cn(
'fixed left-0 top-0 z-30 flex h-screen w-72 flex-col border-r border-border bg-white transition-transform', 'fixed left-0 top-0 z-30 flex h-screen w-72 flex-col border-r border-border bg-background transition-transform',
'-translate-x-72 lg:-translate-x-0', // responsive '-translate-x-72 lg:-translate-x-0', // responsive
active && 'translate-x-0' // force active on mobile active && 'translate-x-0' // force active on mobile
)} )}
@@ -65,8 +65,8 @@ export function LayoutSidebar({
<div className="block h-32 shrink-0"></div> <div className="block h-32 shrink-0"></div>
</div> </div>
<div className="fixed bottom-0 left-0 right-0"> <div className="fixed bottom-0 left-0 right-0">
<div className="h-8 w-full bg-gradient-to-t from-white to-white/0"></div> <div className="h-8 w-full bg-gradient-to-t from-background to-background/0"></div>
<div className="flex flex-col gap-2 bg-white p-4 pt-0"> <div className="flex flex-col gap-2 bg-background p-4 pt-0">
<Link <Link
className={cn('flex gap-2', buttonVariants())} className={cn('flex gap-2', buttonVariants())}
href={`/${organizationId}/${projectId}/reports`} href={`/${organizationId}/${projectId}/reports`}

View File

@@ -12,7 +12,7 @@ export function StickyBelowHeader({
return ( return (
<div <div
className={cn( className={cn(
'top-0 z-20 rounded-lg border-b border-border bg-white md:sticky [[id=dashboard]_&]:top-16 [[id=dashboard]_&]:rounded-none', 'top-0 z-20 rounded-lg border-b border-border bg-background md:sticky [[id=dashboard]_&]:top-16 [[id=dashboard]_&]:rounded-none',
className className
)} )}
> >

View File

@@ -1,11 +1,9 @@
import { FullPageEmptyState } from '@/components/full-page-empty-state'; import { FullPageEmptyState } from '@/components/full-page-empty-state';
import { notFound } from 'next/navigation';
import { import {
getCurrentOrganizations, getCurrentOrganizations,
getCurrentProjects, getCurrentProjects,
getDashboardsByProjectId, getDashboardsByProjectId,
getProjectsByOrganizationSlug,
} from '@openpanel/db'; } from '@openpanel/db';
import { LayoutSidebar } from './layout-sidebar'; import { LayoutSidebar } from './layout-sidebar';

View File

@@ -1,3 +1,5 @@
import DarkModeToggle from '@/components/dark-mode-toggle';
import { import {
getCurrentProjects, getCurrentProjects,
getProjectsByOrganizationSlug, getProjectsByOrganizationSlug,
@@ -20,9 +22,14 @@ export default async function PageLayout({
return ( return (
<> <>
<div className="sticky top-0 z-20 flex h-16 flex-shrink-0 items-center justify-between border-b border-border bg-white px-4 pl-12 lg:pl-4"> <div className="sticky top-0 z-20 flex h-16 flex-shrink-0 items-center justify-between border-b border-border bg-background px-4 pl-12 lg:pl-4">
<div className="text-xl font-medium">{title}</div> <div className="text-xl font-medium">{title}</div>
{projects.length > 0 && <LayoutProjectSelector projects={projects} />} <div className="flex gap-2">
<div>
<DarkModeToggle className="hidden sm:flex" />
</div>
{projects.length > 0 && <LayoutProjectSelector projects={projects} />}
</div>
</div> </div>
<div>{children}</div> <div>{children}</div>
</> </>

View File

@@ -96,7 +96,7 @@ export function ProfileList({ data, count }: ProfileListProps) {
className="mt-4" className="mt-4"
variant="outline" variant="outline"
size="sm" size="sm"
onClick={() => setCursor(count / 10 - 1)} onClick={() => setCursor(Math.max(0, count / 10 - 1))}
> >
Go back Go back
</Button> </Button>

View File

@@ -22,14 +22,16 @@ export default async function Page({
return notFound(); return notFound();
} }
const invites = await getInvites(organization.id);
return ( return (
<PageLayout title={organization.name} organizationSlug={organizationSlug}> <PageLayout title={organization.name} organizationSlug={organizationSlug}>
<div className="grid grid-cols-1 gap-8 p-4"> <div className="grid gap-8 p-4 lg:grid-cols-2">
<EditOrganization organization={organization} /> <EditOrganization organization={organization} />
<MembersServer organizationSlug={organizationSlug} /> <div className="col-span-2">
<InvitesServer organizationSlug={organizationSlug} /> <MembersServer organizationSlug={organizationSlug} />
</div>
<div className="col-span-2">
<InvitesServer organizationSlug={organizationSlug} />
</div>
</div> </div>
</PageLayout> </PageLayout>
); );

View File

@@ -132,7 +132,7 @@ export function CreateOrganization() {
/> />
</TabsContent> </TabsContent>
<TabsContent value="other"> <TabsContent value="other">
<div className="rounded bg-white p-2 px-3 text-sm"> <div className="rounded bg-background p-2 px-3 text-sm">
🔑 You will get a secret to use for your API requests. 🔑 You will get a secret to use for your API requests.
</div> </div>
</TabsContent> </TabsContent>

View File

@@ -1,87 +0,0 @@
'use client';
import { useState } from 'react';
import { InputWithLabel } from '@/components/forms/input-with-label';
import { Logo } from '@/components/logo';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Button } from '@/components/ui/button';
import { Widget, WidgetBody } from '@/components/widget';
import { zodResolver } from '@hookform/resolvers/zod';
import { KeySquareIcon } from 'lucide-react';
import { signIn } from 'next-auth/react';
import Link from 'next/link';
import { usePathname, useRouter } from 'next/navigation';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
const validator = z.object({
email: z.string().email(),
password: z.string().min(6),
});
type IForm = z.infer<typeof validator>;
export default function Auth() {
const form = useForm<IForm>({
resolver: zodResolver(validator),
});
const router = useRouter();
const pathname = usePathname();
const [state, setState] = useState<string | null>(null);
return (
<div className="flex h-screen flex-col items-center justify-center p-4">
<Widget className="mb-4 w-full max-w-md">
<WidgetBody>
<div className="flex justify-center py-8">
<Logo />
</div>
<form
onSubmit={form.handleSubmit(async (values) => {
const res = await signIn('credentials', {
email: values.email,
password: values.password,
redirect: false,
}).catch(() => {
setState('Something went wrong. Please try again later');
});
if (res?.ok) {
router.refresh();
}
if (res?.status === 401) {
setState('Wrong email or password. Please try again');
}
})}
className="flex flex-col gap-4"
>
<InputWithLabel
label="Email"
placeholder="Your email"
error={form.formState.errors.email?.message}
{...form.register('email')}
/>
<InputWithLabel
label="Password"
placeholder="...and your password"
error={form.formState.errors.password?.message}
{...form.register('password')}
/>
{state !== null && (
<Alert variant="destructive">
<KeySquareIcon className="h-4 w-4" />
<AlertTitle>Failed</AlertTitle>
<AlertDescription>{state}</AlertDescription>
</Alert>
)}
<Button type="submit">Sign in</Button>
<Link href="/register" className="text-center text-sm">
No account?{' '}
<span className="font-medium text-blue-600">Sign up here!</span>
</Link>
</form>
</WidgetBody>
</Widget>
<p className="text-xs">Terms & conditions</p>
</div>
);
}

View File

@@ -22,7 +22,7 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}) { }) {
return ( return (
<html lang="en" className="light"> <html lang="en" className="dark">
<body <body
className={cn('grainy min-h-screen bg-slate-100 font-sans antialiased')} className={cn('grainy min-h-screen bg-slate-100 font-sans antialiased')}
> >

View File

@@ -9,6 +9,7 @@ import makeStore from '@/redux';
import { ClerkProvider, useAuth } from '@clerk/nextjs'; import { ClerkProvider, useAuth } from '@clerk/nextjs';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { httpLink } from '@trpc/client'; import { httpLink } from '@trpc/client';
import { ThemeProvider } from 'next-themes';
import { Provider as ReduxProvider } from 'react-redux'; import { Provider as ReduxProvider } from 'react-redux';
import { Toaster } from 'sonner'; import { Toaster } from 'sonner';
import superjson from 'superjson'; import superjson from 'superjson';
@@ -48,17 +49,24 @@ function AllProviders({ children }: { children: React.ReactNode }) {
} }
return ( return (
<ReduxProvider store={storeRef.current}> <ThemeProvider
<api.Provider client={trpcClient} queryClient={queryClient}> enableSystem
<QueryClientProvider client={queryClient}> attribute="class"
<TooltipProvider delayDuration={200}> defaultTheme="system"
{children} disableTransitionOnChange
<Toaster /> >
<ModalProvider /> <ReduxProvider store={storeRef.current}>
</TooltipProvider> <api.Provider client={trpcClient} queryClient={queryClient}>
</QueryClientProvider> <QueryClientProvider client={queryClient}>
</api.Provider> <TooltipProvider delayDuration={200}>
</ReduxProvider> {children}
<Toaster />
<ModalProvider />
</TooltipProvider>
</QueryClientProvider>
</api.Provider>
</ReduxProvider>
</ThemeProvider>
); );
} }

View File

@@ -0,0 +1,39 @@
'use client';
import * as React from 'react';
import { Button } from '@/components/ui/button';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { MoonIcon, SunIcon } from 'lucide-react';
import { useTheme } from 'next-themes';
export default function DarkModeToggle() {
const { setTheme } = useTheme();
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<SunIcon className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<MoonIcon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
Light
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -18,7 +18,7 @@ export function FullPageEmptyState({
return ( return (
<div className={cn('flex items-center justify-center p-4', className)}> <div className={cn('flex items-center justify-center p-4', className)}>
<div className="flex w-full max-w-xl flex-col items-center justify-center p-8"> <div className="flex w-full max-w-xl flex-col items-center justify-center p-8">
<div className="mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-white shadow-sm"> <div className="mb-6 flex h-24 w-24 items-center justify-center rounded-full bg-background shadow-sm">
<Icon size={60} strokeWidth={1} /> <Icon size={60} strokeWidth={1} />
</div> </div>

View File

@@ -85,7 +85,7 @@ export function OverviewLiveHistogram({
{staticArray.map((percent, i) => ( {staticArray.map((percent, i) => (
<div <div
key={i} key={i}
className="flex-1 animate-pulse rounded-md bg-slate-200" className="flex-1 animate-pulse rounded-md bg-slate-200 dark:bg-slate-800"
style={{ height: `${percent}%` }} style={{ height: `${percent}%` }}
/> />
))} ))}
@@ -147,7 +147,7 @@ function Wrapper({ open, children, count }: WrapperProps) {
<div className="absolute -top-3 right-0 text-xs text-muted-foreground"> <div className="absolute -top-3 right-0 text-xs text-muted-foreground">
NOW NOW
</div> </div>
{/* <div className="md:absolute top-0 left-0 md:card md:p-4 mr-2 md:bg-white/90 z-50"> */} {/* <div className="md:absolute top-0 left-0 md:card md:p-4 mr-2 md:bg-background/90 z-50"> */}
{children} {children}
</div> </div>
</div> </div>

View File

@@ -192,13 +192,13 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) {
return ( return (
<> <>
<div className="col-span-6 grid grid-cols-6 gap-1"> <div className="col-span-6 flex flex-wrap gap-4">
{reports.map((report, index) => ( {reports.map((report, index) => (
<button <button
key={index} key={index}
className={cn( className={cn(
'group relative col-span-3 scale-95 transition-all md:col-span-2 lg:col-span-1', 'group relative col-span-3 min-w-[170px] transition-all max-md:flex-1 md:col-span-2 lg:col-span-1 [&_[role="heading"]]:text-sm',
index === metric && 'z-10 scale-105 rounded-xl shadow-md' index === metric && 'z-10 scale-[101%] rounded-xl shadow-md'
)} )}
onClick={() => { onClick={() => {
setMetric(index); setMetric(index);

View File

@@ -20,7 +20,7 @@ export function WidgetHead({ className, ...props }: WidgetHeadProps) {
return ( return (
<WidgetHeadBase <WidgetHeadBase
className={cn( className={cn(
'flex flex-col p-0 [&_.title]:flex [&_.title]:items-center [&_.title]:justify-between [&_.title]:p-4', 'flex flex-col rounded-t-xl p-0 [&_.title]:flex [&_.title]:items-center [&_.title]:justify-between [&_.title]:p-4',
className className
)} )}
{...props} {...props}
@@ -85,7 +85,7 @@ export function WidgetButtons({
<div <div
ref={container} ref={container}
className={cn( className={cn(
'flex flex-wrap justify-start self-stretch px-4 transition-opacity [&_button.active]:border-b [&_button.active]:border-black [&_button.active]:opacity-100 [&_button]:whitespace-nowrap [&_button]:py-1 [&_button]:text-xs [&_button]:opacity-50', '-mt-2 flex flex-wrap justify-start self-stretch px-4 transition-opacity [&_button.active]:border-b-2 [&_button.active]:border-black [&_button.active]:opacity-100 [&_button]:whitespace-nowrap [&_button]:py-1 [&_button]:text-xs [&_button]:opacity-50',
className className
)} )}
style={{ gap }} style={{ gap }}

View File

@@ -25,7 +25,7 @@ export const ChartAnimationContainer = (
<div <div
{...props} {...props}
className={cn( className={cn(
'rounded-md border border-border bg-white p-8', 'rounded-md border border-border bg-background p-8',
props.className props.className
)} )}
/> />

View File

@@ -84,7 +84,10 @@ export function MetricCard({
<div className="flex items-center justify-between gap-2"> <div className="flex items-center justify-between gap-2">
<div className="flex min-w-0 items-center gap-2 text-left font-medium"> <div className="flex min-w-0 items-center gap-2 text-left font-medium">
<ColorSquare>{serie.event.id}</ColorSquare> <ColorSquare>{serie.event.id}</ColorSquare>
<span className="overflow-hidden text-ellipsis whitespace-nowrap"> <span
role="heading"
className="overflow-hidden text-ellipsis whitespace-nowrap"
>
{serie.name || serie.event.displayName || serie.event.name} {serie.name || serie.event.displayName || serie.event.name}
</span> </span>
</div> </div>

View File

@@ -39,9 +39,10 @@ export function ReportBarChart({ data }: ReportBarChartProps) {
<div <div
key={serie.name} key={serie.name}
className={cn( className={cn(
'relative flex w-full flex-1 items-center gap-4 overflow-hidden rounded px-2 py-3 even:bg-slate-50', 'relative flex w-full flex-1 items-center gap-4 overflow-hidden rounded px-2 py-3 even:bg-slate-50 dark:even:bg-slate-100',
'[&_[role=progressbar]]:shadow-sm [&_[role=progressbar]]:even:bg-white', '[&_[role=progressbar]]:shadow-sm [&_[role=progressbar]]:even:bg-background',
isClickable && 'cursor-pointer hover:!bg-slate-100' isClickable &&
'cursor-pointer hover:bg-slate-100 dark:hover:bg-slate-50'
)} )}
{...(isClickable ? { onClick: () => onClick(serie) } : {})} {...(isClickable ? { onClick: () => onClick(serie) } : {})}
> >

View File

@@ -39,7 +39,7 @@ export function ReportChartTooltip({
const hidden = sorted.slice(limit); const hidden = sorted.slice(limit);
return ( return (
<div className="flex min-w-[180px] flex-col gap-2 rounded-xl border bg-white p-3 text-sm shadow-xl"> <div className="flex min-w-[180px] flex-col gap-2 rounded-xl border bg-background p-3 text-sm shadow-xl">
{visible.map((item, index) => { {visible.map((item, index) => {
// If we have a <Cell /> component, payload can be nested // If we have a <Cell /> component, payload can be nested
const payload = item.payload.payload ?? item.payload; const payload = item.payload.payload ?? item.payload;

View File

@@ -50,6 +50,11 @@ export function ReportLineChart({
<ResponsiveContainer> <ResponsiveContainer>
{({ width, height }) => ( {({ width, height }) => (
<LineChart width={width} height={height} data={rechartData}> <LineChart width={width} height={height} data={rechartData}>
<CartesianGrid
strokeDasharray="3 3"
horizontal={true}
vertical={false}
/>
{references.map((ref) => ( {references.map((ref) => (
<ReferenceLine <ReferenceLine
key={ref.id} key={ref.id}
@@ -65,11 +70,6 @@ export function ReportLineChart({
fontSize={10} fontSize={10}
/> />
))} ))}
<CartesianGrid
strokeDasharray="3 3"
horizontal={true}
vertical={false}
/>
<YAxis <YAxis
width={getYAxisWidth(data.metrics.max)} width={getYAxisWidth(data.metrics.max)}
fontSize={12} fontSize={12}

View File

@@ -1,5 +1,8 @@
'use client'; 'use client';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { TriangleIcon } from 'lucide-react';
import type { IChartInput } from '@openpanel/validation'; import type { IChartInput } from '@openpanel/validation';
import { Funnel } from '../funnel'; import { Funnel } from '../funnel';
@@ -12,7 +15,18 @@ export const ChartSwitch = withChartProivder(function ChartSwitch(
props: ReportChartProps props: ReportChartProps
) { ) {
if (props.chartType === 'funnel') { if (props.chartType === 'funnel') {
return <Funnel {...props} />; return (
<>
<Alert>
<TriangleIcon className="h-4 w-4" />
<AlertTitle>Keep in mind</AlertTitle>
<AlertDescription>
Funnel chart is still experimental and might not work as expected.
</AlertDescription>
</Alert>
<Funnel {...props} />
</>
);
} }
return <Chart {...props} />; return <Chart {...props} />;

View File

@@ -99,7 +99,7 @@ export function FunnelSteps({
)} )}
key={step.event.id} key={step.event.id}
> >
<div className="card divide-y divide-border bg-white"> <div className="card divide-y divide-border bg-background">
<div className="p-4"> <div className="p-4">
<p className="text-muted-foreground">Step {index + 1}</p> <p className="text-muted-foreground">Step {index + 1}</p>
<h3 className="font-bold"> <h3 className="font-bold">
@@ -108,7 +108,7 @@ export function FunnelSteps({
</div> </div>
<div className="relative aspect-square"> <div className="relative aspect-square">
<FunnelChart from={step.prevPercent} to={step.percent} /> <FunnelChart from={step.prevPercent} to={step.percent} />
<div className="absolute left-0 right-0 top-0 flex flex-col bg-white/40 p-4"> <div className="absolute left-0 right-0 top-0 flex flex-col bg-background/40 p-4">
<div className="font-medium uppercase text-muted-foreground"> <div className="font-medium uppercase text-muted-foreground">
Sessions Sessions
</div> </div>

View File

@@ -135,7 +135,7 @@ export function ReportEvents() {
]} ]}
label="Segment" label="Segment"
> >
<button className="flex items-center gap-1 rounded-md border border-border bg-white p-1 px-2 text-xs font-medium leading-none"> <button className="flex items-center gap-1 rounded-md border border-border bg-background p-1 px-2 text-xs font-medium leading-none">
{event.segment === 'user' ? ( {event.segment === 'user' ? (
<> <>
<Users size={12} /> Unique users <Users size={12} /> Unique users

View File

@@ -54,7 +54,7 @@ export function FiltersCombobox({ event }: FiltersComboboxProps) {
); );
}} }}
> >
<button className="flex items-center gap-1 rounded-md border border-border bg-white p-1 px-2 text-xs font-medium leading-none"> <button className="flex items-center gap-1 rounded-md border border-border bg-background p-1 px-2 text-xs font-medium leading-none">
<FilterIcon size={12} /> Add filter <FilterIcon size={12} /> Add filter
</button> </button>
</Combobox> </Combobox>

View File

@@ -23,7 +23,7 @@ export function KeyValue({ href, onClick, name, value }: KeyValueProps) {
<div className="bg-black/5 p-1 px-2">{name}</div> <div className="bg-black/5 p-1 px-2">{name}</div>
<div <div
className={cn( className={cn(
'overflow-hidden text-ellipsis whitespace-nowrap bg-white p-1 px-2 font-mono text-blue-700 shadow-[inset_0_0_0_1px_#fff]', 'overflow-hidden text-ellipsis whitespace-nowrap bg-background p-1 px-2 font-mono text-blue-700',
clickable && 'group-hover:underline' clickable && 'group-hover:underline'
)} )}
> >

View File

@@ -101,7 +101,7 @@ const SheetFooter = ({
<div <div
className={cn( className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', 'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
'sticky bottom-0 left-0 right-0 mt-auto bg-white', 'sticky bottom-0 left-0 right-0 mt-auto bg-background',
className className
)} )}
{...props} {...props}

View File

@@ -75,7 +75,7 @@ const TableHead = React.forwardRef<
<th <th
ref={ref} ref={ref}
className={cn( className={cn(
'h-10 px-4 text-left align-middle font-medium text-muted-foreground shadow-[0_0_0_0.5px] shadow-border [&:has([role=checkbox])]:pr-0', 'h-10 border-b border-border bg-slate-50 px-4 text-left align-middle text-sm font-medium text-muted-foreground text-slate-500 shadow-[0_0_0_0.5px] shadow-border [&:has([role=checkbox])]:pr-0',
className className
)} )}
{...props} {...props}

View File

@@ -21,7 +21,7 @@ const TooltipContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
'z-50 overflow-hidden rounded-md border bg-black px-3 py-1.5 text-sm text-popover shadow-md 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', 'z-50 overflow-hidden rounded-md border bg-popover-foreground px-3 py-1.5 text-sm text-popover shadow-md 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',
className className
)} )}
{...props} {...props}

View File

@@ -150,7 +150,7 @@ export default function AddClient() {
/> />
</TabsContent> </TabsContent>
<TabsContent value="other"> <TabsContent value="other">
<div className="rounded bg-white p-2 px-3 text-sm"> <div className="rounded bg-background p-2 px-3 text-sm">
🔑 You will get a secret to use for your API requests. 🔑 You will get a secret to use for your API requests.
</div> </div>
</TabsContent> </TabsContent>

View File

@@ -7,6 +7,18 @@
--background: 0 0% 100%; --background: 0 0% 100%;
--foreground: 222.2 84% 4.9%; --foreground: 222.2 84% 4.9%;
--slate-50: 210 40% 98%;
--slate-100: 210 40% 96.1%;
--slate-200: 210 40% 91.4%;
--slate-300: 210 40% 83.9%;
--slate-400: 210 40% 65.1%;
--slate-500: 210 40% 46.9%;
--slate-600: 210 40% 30.6%;
--slate-700: 210 40% 17.5%;
--slate-800: 210 40% 11.2%;
--slate-900: 210 40% 8.2%;
--slate-950: 210 40% 7.2%;
--card: 0 0% 100%; --card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%; --card-foreground: 222.2 84% 4.9%;
@@ -36,9 +48,21 @@
} }
.dark { .dark {
--background: 222.2 84% 4.9%; --background: 222.86 32.05% 12.02%;
--foreground: 210 40% 98%; --foreground: 210 40% 98%;
--slate-950: 210 40% 98%;
--slate-900: 210 40% 96.1%;
--slate-800: 210 40% 91.4%;
--slate-700: 210 40% 83.9%;
--slate-600: 210 40% 65.1%;
--slate-500: 210 40% 46.9%;
--slate-400: 210 40% 30.6%;
--slate-300: 210 40% 17.5%;
--slate-200: 210 40% 11.2%;
--slate-100: 210 40% 8.2%;
--slate-50: 210 40% 7.2%;
--card: 222.2 84% 4.9%; --card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%; --card-foreground: 210 40% 98%;
@@ -120,9 +144,9 @@
} }
.card { .card {
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05); box-shadow: 0 1px 2px 0.5px rgba(0, 0, 0, 0.08);
border: 0 !important; border: 0 !important;
@apply bg-white rounded-xl; @apply rounded-xl bg-background;
} }
} }

View File

@@ -42,6 +42,7 @@ const twColorVariants = ['50', '100', '200', '700', '800', '900'];
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
const config = { const config = {
darkMode: 'class',
safelist: [ safelist: [
...colors.flatMap((color) => ...colors.flatMap((color) =>
['text', 'bg'].map((prefix) => `${prefix}-chart-${color}`) ['text', 'bg'].map((prefix) => `${prefix}-chart-${color}`)
@@ -53,6 +54,8 @@ const config = {
`bg-${color}-${variant}`, `bg-${color}-${variant}`,
`hover:bg-${color}-${variant}`, `hover:bg-${color}-${variant}`,
`border-${color}-${variant}`, `border-${color}-${variant}`,
`dark:bg-${color}-${variant}`,
`dark:hover:bg-${color}-${variant}`,
]; ];
}); });
}), }),
@@ -71,6 +74,19 @@ const config = {
ring: 'hsl(var(--ring))', ring: 'hsl(var(--ring))',
background: 'hsl(var(--background))', background: 'hsl(var(--background))',
foreground: 'hsl(var(--foreground))', foreground: 'hsl(var(--foreground))',
slate: {
50: 'hsl(var(--slate-50))',
100: 'hsl(var(--slate-100))',
200: 'hsl(var(--slate-200))',
300: 'hsl(var(--slate-300))',
400: 'hsl(var(--slate-400))',
500: 'hsl(var(--slate-500))',
600: 'hsl(var(--slate-600))',
700: 'hsl(var(--slate-700))',
800: 'hsl(var(--slate-800))',
900: 'hsl(var(--slate-900))',
950: 'hsl(var(--slate-950))',
},
primary: { primary: {
DEFAULT: 'hsl(var(--primary))', DEFAULT: 'hsl(var(--primary))',
foreground: 'hsl(var(--primary-foreground))', foreground: 'hsl(var(--primary-foreground))',

View File

@@ -260,7 +260,7 @@ export async function getEventList({
const { sb, getSql, join } = createSqlBuilder(); const { sb, getSql, join } = createSqlBuilder();
sb.limit = take; sb.limit = take;
sb.offset = (cursor ?? 0) * take; sb.offset = Math.max(0, (cursor ?? 0) * take);
sb.where.projectId = `project_id = '${projectId}'`; sb.where.projectId = `project_id = '${projectId}'`;
if (profileId) { if (profileId) {

View File

@@ -84,7 +84,7 @@ export async function getProfileList({
}; };
} }
sb.limit = take; sb.limit = take;
sb.offset = (cursor ?? 0) * take; sb.offset = Math.max(0, (cursor ?? 0) * take);
sb.orderBy.created_at = 'max_created_at DESC'; sb.orderBy.created_at = 'max_created_at DESC';
const data = await chQuery<IClickhouseProfile>(getSql()); const data = await chQuery<IClickhouseProfile>(getSql());
return data.map(transformProfile); return data.map(transformProfile);