From a1fa48da754226d92ad2a8b54d2050fb33c16e87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carl-Gerhard=20Lindesva=CC=88rd?= Date: Thu, 28 Mar 2024 06:47:04 +0100 Subject: [PATCH] dashboard: add dark mode --- .../[projectId]/events/event-edit.tsx | 4 +- .../[projectId]/events/event-list-item.tsx | 5 +- .../[projectId]/events/event-listener.tsx | 2 +- .../[projectId]/layout-menu.tsx | 4 +- .../[projectId]/layout-sidebar.tsx | 6 +- .../layout-sticky-below-header.tsx | 2 +- .../[organizationId]/[projectId]/layout.tsx | 2 - .../[projectId]/page-layout.tsx | 11 ++- .../profiles/profile-list/profile-list.tsx | 2 +- .../settings/organization/page.tsx | 12 +-- .../src/app/(app)/create-organization.tsx | 2 +- apps/dashboard/src/app/auth.tsx | 87 ------------------- apps/dashboard/src/app/layout.tsx | 2 +- apps/dashboard/src/app/providers.tsx | 30 ++++--- .../src/components/dark-mode-toggle.tsx | 39 +++++++++ .../src/components/full-page-empty-state.tsx | 2 +- .../overview/overview-live-histogram.tsx | 4 +- .../components/overview/overview-metrics.tsx | 6 +- .../components/overview/overview-widget.tsx | 4 +- .../report/chart/ChartAnimation.tsx | 2 +- .../components/report/chart/MetricCard.tsx | 5 +- .../report/chart/ReportBarChart.tsx | 7 +- .../report/chart/ReportChartTooltip.tsx | 2 +- .../report/chart/ReportLineChart.tsx | 10 +-- .../src/components/report/chart/index.tsx | 16 +++- .../src/components/report/funnel/Funnel.tsx | 4 +- .../report/sidebar/ReportEvents.tsx | 2 +- .../sidebar/filters/FiltersCombobox.tsx | 2 +- .../dashboard/src/components/ui/key-value.tsx | 2 +- apps/dashboard/src/components/ui/sheet.tsx | 2 +- apps/dashboard/src/components/ui/table.tsx | 2 +- apps/dashboard/src/components/ui/tooltip.tsx | 2 +- apps/dashboard/src/modals/AddClient.tsx | 2 +- apps/dashboard/src/styles/globals.css | 30 ++++++- apps/dashboard/tailwind.config.js | 16 ++++ packages/db/src/services/event.service.ts | 2 +- packages/db/src/services/profile.service.ts | 2 +- 37 files changed, 181 insertions(+), 155 deletions(-) delete mode 100644 apps/dashboard/src/app/auth.tsx create mode 100644 apps/dashboard/src/components/dark-mode-toggle.tsx diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-edit.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-edit.tsx index 32b6b835..6b158649 100644 --- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-edit.tsx +++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-edit.tsx @@ -106,7 +106,7 @@ export function EventEdit({ event, open, setOpen }: Props) { setIcon(name); }} 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 ? 'scale-110 ring-1 ring-black' : '[&_svg]:opacity-50' @@ -133,7 +133,7 @@ export function EventEdit({ event, open, setOpen }: Props) { )} > {SelectedIcon ? ( - + ) : ( setIsDetailsOpen(true)} className={cn( - 'card flex w-full items-center justify-between rounded-lg p-4 transition-colors hover:bg-slate-50', - meta?.conversion && `bg-${meta.color}-50 hover:bg-${meta.color}-100` + 'card hover:bg-light-background flex w-full items-center justify-between rounded-lg p-4 transition-colors', + meta?.conversion && + `bg-${meta.color}-50 dark:bg-${meta.color}-900 hover:bg-${meta.color}-100 dark:hover:bg-${meta.color}-700` )} >
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-listener.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-listener.tsx index b5673daa..74dd242b 100644 --- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-listener.tsx +++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/events/event-listener.tsx @@ -49,7 +49,7 @@ export default function EventListener() { setCounter(0); 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" >
-
-
+
+
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/layout.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/layout.tsx index 091c8640..b909cb61 100644 --- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/layout.tsx +++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/layout.tsx @@ -1,11 +1,9 @@ import { FullPageEmptyState } from '@/components/full-page-empty-state'; -import { notFound } from 'next/navigation'; import { getCurrentOrganizations, getCurrentProjects, getDashboardsByProjectId, - getProjectsByOrganizationSlug, } from '@openpanel/db'; import { LayoutSidebar } from './layout-sidebar'; diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/page-layout.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/page-layout.tsx index 016b94f0..c733754d 100644 --- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/page-layout.tsx +++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/page-layout.tsx @@ -1,3 +1,5 @@ +import DarkModeToggle from '@/components/dark-mode-toggle'; + import { getCurrentProjects, getProjectsByOrganizationSlug, @@ -20,9 +22,14 @@ export default async function PageLayout({ return ( <> -
+
{title}
- {projects.length > 0 && } +
+
+ +
+ {projects.length > 0 && } +
{children}
diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list/profile-list.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list/profile-list.tsx index 2f9d23b4..90f8764e 100644 --- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list/profile-list.tsx +++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/profiles/profile-list/profile-list.tsx @@ -96,7 +96,7 @@ export function ProfileList({ data, count }: ProfileListProps) { className="mt-4" variant="outline" size="sm" - onClick={() => setCursor(count / 10 - 1)} + onClick={() => setCursor(Math.max(0, count / 10 - 1))} > Go back diff --git a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/settings/organization/page.tsx b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/settings/organization/page.tsx index aef8fb8c..ec767905 100644 --- a/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/settings/organization/page.tsx +++ b/apps/dashboard/src/app/(app)/[organizationId]/[projectId]/settings/organization/page.tsx @@ -22,14 +22,16 @@ export default async function Page({ return notFound(); } - const invites = await getInvites(organization.id); - return ( -
+
- - +
+ +
+
+ +
); diff --git a/apps/dashboard/src/app/(app)/create-organization.tsx b/apps/dashboard/src/app/(app)/create-organization.tsx index 33191828..c2559edf 100644 --- a/apps/dashboard/src/app/(app)/create-organization.tsx +++ b/apps/dashboard/src/app/(app)/create-organization.tsx @@ -132,7 +132,7 @@ export function CreateOrganization() { /> -
+
🔑 You will get a secret to use for your API requests.
diff --git a/apps/dashboard/src/app/auth.tsx b/apps/dashboard/src/app/auth.tsx deleted file mode 100644 index 7fbda21d..00000000 --- a/apps/dashboard/src/app/auth.tsx +++ /dev/null @@ -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; -export default function Auth() { - const form = useForm({ - resolver: zodResolver(validator), - }); - const router = useRouter(); - const pathname = usePathname(); - const [state, setState] = useState(null); - return ( -
- - -
- -
-
{ - 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" - > - - - {state !== null && ( - - - Failed - {state} - - )} - - - No account?{' '} - Sign up here! - - -
-
-

Terms & conditions

-
- ); -} diff --git a/apps/dashboard/src/app/layout.tsx b/apps/dashboard/src/app/layout.tsx index c42e89cf..80b2dd37 100644 --- a/apps/dashboard/src/app/layout.tsx +++ b/apps/dashboard/src/app/layout.tsx @@ -22,7 +22,7 @@ export default function RootLayout({ children: React.ReactNode; }) { return ( - + diff --git a/apps/dashboard/src/app/providers.tsx b/apps/dashboard/src/app/providers.tsx index 562215ef..9c7868af 100644 --- a/apps/dashboard/src/app/providers.tsx +++ b/apps/dashboard/src/app/providers.tsx @@ -9,6 +9,7 @@ import makeStore from '@/redux'; import { ClerkProvider, useAuth } from '@clerk/nextjs'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { httpLink } from '@trpc/client'; +import { ThemeProvider } from 'next-themes'; import { Provider as ReduxProvider } from 'react-redux'; import { Toaster } from 'sonner'; import superjson from 'superjson'; @@ -48,17 +49,24 @@ function AllProviders({ children }: { children: React.ReactNode }) { } return ( - - - - - {children} - - - - - - + + + + + + {children} + + + + + + + ); } diff --git a/apps/dashboard/src/components/dark-mode-toggle.tsx b/apps/dashboard/src/components/dark-mode-toggle.tsx new file mode 100644 index 00000000..9368d229 --- /dev/null +++ b/apps/dashboard/src/components/dark-mode-toggle.tsx @@ -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 ( + + + + + + setTheme('light')}> + Light + + setTheme('dark')}> + Dark + + setTheme('system')}> + System + + + + ); +} diff --git a/apps/dashboard/src/components/full-page-empty-state.tsx b/apps/dashboard/src/components/full-page-empty-state.tsx index 4e0165db..79333ec2 100644 --- a/apps/dashboard/src/components/full-page-empty-state.tsx +++ b/apps/dashboard/src/components/full-page-empty-state.tsx @@ -18,7 +18,7 @@ export function FullPageEmptyState({ return (
-
+
diff --git a/apps/dashboard/src/components/overview/overview-live-histogram.tsx b/apps/dashboard/src/components/overview/overview-live-histogram.tsx index 983806c4..e8a00564 100644 --- a/apps/dashboard/src/components/overview/overview-live-histogram.tsx +++ b/apps/dashboard/src/components/overview/overview-live-histogram.tsx @@ -85,7 +85,7 @@ export function OverviewLiveHistogram({ {staticArray.map((percent, i) => (
))} @@ -147,7 +147,7 @@ function Wrapper({ open, children, count }: WrapperProps) {
NOW
- {/*
*/} + {/*
*/} {children}
diff --git a/apps/dashboard/src/components/overview/overview-metrics.tsx b/apps/dashboard/src/components/overview/overview-metrics.tsx index dd0db21e..aa6adbbf 100644 --- a/apps/dashboard/src/components/overview/overview-metrics.tsx +++ b/apps/dashboard/src/components/overview/overview-metrics.tsx @@ -192,13 +192,13 @@ export default function OverviewMetrics({ projectId }: OverviewMetricsProps) { return ( <> -
+
{reports.map((report, index) => ( diff --git a/apps/dashboard/src/components/ui/key-value.tsx b/apps/dashboard/src/components/ui/key-value.tsx index 070a5772..396c2737 100644 --- a/apps/dashboard/src/components/ui/key-value.tsx +++ b/apps/dashboard/src/components/ui/key-value.tsx @@ -23,7 +23,7 @@ export function KeyValue({ href, onClick, name, value }: KeyValueProps) {
{name}
diff --git a/apps/dashboard/src/components/ui/sheet.tsx b/apps/dashboard/src/components/ui/sheet.tsx index 2561925f..f7184385 100644 --- a/apps/dashboard/src/components/ui/sheet.tsx +++ b/apps/dashboard/src/components/ui/sheet.tsx @@ -101,7 +101,7 @@ const SheetFooter = ({
-
+
🔑 You will get a secret to use for your API requests.
diff --git a/apps/dashboard/src/styles/globals.css b/apps/dashboard/src/styles/globals.css index 8737cbb2..454a7a50 100644 --- a/apps/dashboard/src/styles/globals.css +++ b/apps/dashboard/src/styles/globals.css @@ -7,6 +7,18 @@ --background: 0 0% 100%; --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-foreground: 222.2 84% 4.9%; @@ -36,9 +48,21 @@ } .dark { - --background: 222.2 84% 4.9%; + --background: 222.86 32.05% 12.02%; --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-foreground: 210 40% 98%; @@ -120,9 +144,9 @@ } .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; - @apply bg-white rounded-xl; + @apply rounded-xl bg-background; } } diff --git a/apps/dashboard/tailwind.config.js b/apps/dashboard/tailwind.config.js index 311eb4ef..603f31ee 100644 --- a/apps/dashboard/tailwind.config.js +++ b/apps/dashboard/tailwind.config.js @@ -42,6 +42,7 @@ const twColorVariants = ['50', '100', '200', '700', '800', '900']; /** @type {import('tailwindcss').Config} */ const config = { + darkMode: 'class', safelist: [ ...colors.flatMap((color) => ['text', 'bg'].map((prefix) => `${prefix}-chart-${color}`) @@ -53,6 +54,8 @@ const config = { `bg-${color}-${variant}`, `hover:bg-${color}-${variant}`, `border-${color}-${variant}`, + `dark:bg-${color}-${variant}`, + `dark:hover:bg-${color}-${variant}`, ]; }); }), @@ -71,6 +74,19 @@ const config = { ring: 'hsl(var(--ring))', background: 'hsl(var(--background))', 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: { DEFAULT: 'hsl(var(--primary))', foreground: 'hsl(var(--primary-foreground))', diff --git a/packages/db/src/services/event.service.ts b/packages/db/src/services/event.service.ts index d1f11595..1ebcdb76 100644 --- a/packages/db/src/services/event.service.ts +++ b/packages/db/src/services/event.service.ts @@ -260,7 +260,7 @@ export async function getEventList({ const { sb, getSql, join } = createSqlBuilder(); sb.limit = take; - sb.offset = (cursor ?? 0) * take; + sb.offset = Math.max(0, (cursor ?? 0) * take); sb.where.projectId = `project_id = '${projectId}'`; if (profileId) { diff --git a/packages/db/src/services/profile.service.ts b/packages/db/src/services/profile.service.ts index 76411dc4..3fe47ee4 100644 --- a/packages/db/src/services/profile.service.ts +++ b/packages/db/src/services/profile.service.ts @@ -84,7 +84,7 @@ export async function getProfileList({ }; } sb.limit = take; - sb.offset = (cursor ?? 0) * take; + sb.offset = Math.max(0, (cursor ?? 0) * take); sb.orderBy.created_at = 'max_created_at DESC'; const data = await chQuery(getSql()); return data.map(transformProfile);