dashboard: add dark mode
This commit is contained in:
@@ -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 ? (
|
||||
<SelectedIcon size={16} />
|
||||
<SelectedIcon size={16} className={getText(color)} />
|
||||
) : (
|
||||
<svg
|
||||
className={`${getText(color)} opacity-70`}
|
||||
|
||||
@@ -55,8 +55,9 @@ export function EventListItem(props: EventListItemProps) {
|
||||
<button
|
||||
onClick={() => 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`
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center gap-4 text-left text-sm">
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<div className="relative">
|
||||
<div
|
||||
|
||||
@@ -40,8 +40,8 @@ function LinkWithIcon({
|
||||
return (
|
||||
<Link
|
||||
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',
|
||||
active && 'bg-blue-50',
|
||||
'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-slate-100',
|
||||
className
|
||||
)}
|
||||
href={href}
|
||||
|
||||
@@ -46,7 +46,7 @@ export function LayoutSidebar({
|
||||
/>
|
||||
<div
|
||||
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
|
||||
active && 'translate-x-0' // force active on mobile
|
||||
)}
|
||||
@@ -65,8 +65,8 @@ export function LayoutSidebar({
|
||||
<div className="block h-32 shrink-0"></div>
|
||||
</div>
|
||||
<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="flex flex-col gap-2 bg-white p-4 pt-0">
|
||||
<div className="h-8 w-full bg-gradient-to-t from-background to-background/0"></div>
|
||||
<div className="flex flex-col gap-2 bg-background p-4 pt-0">
|
||||
<Link
|
||||
className={cn('flex gap-2', buttonVariants())}
|
||||
href={`/${organizationId}/${projectId}/reports`}
|
||||
|
||||
@@ -12,7 +12,7 @@ export function StickyBelowHeader({
|
||||
return (
|
||||
<div
|
||||
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
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import DarkModeToggle from '@/components/dark-mode-toggle';
|
||||
|
||||
import {
|
||||
getCurrentProjects,
|
||||
getProjectsByOrganizationSlug,
|
||||
@@ -20,9 +22,14 @@ export default async function PageLayout({
|
||||
|
||||
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>
|
||||
{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>{children}</div>
|
||||
</>
|
||||
|
||||
@@ -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
|
||||
</Button>
|
||||
|
||||
@@ -22,14 +22,16 @@ export default async function Page({
|
||||
return notFound();
|
||||
}
|
||||
|
||||
const invites = await getInvites(organization.id);
|
||||
|
||||
return (
|
||||
<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} />
|
||||
<MembersServer organizationSlug={organizationSlug} />
|
||||
<InvitesServer organizationSlug={organizationSlug} />
|
||||
<div className="col-span-2">
|
||||
<MembersServer organizationSlug={organizationSlug} />
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<InvitesServer organizationSlug={organizationSlug} />
|
||||
</div>
|
||||
</div>
|
||||
</PageLayout>
|
||||
);
|
||||
|
||||
@@ -132,7 +132,7 @@ export function CreateOrganization() {
|
||||
/>
|
||||
</TabsContent>
|
||||
<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.
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}) {
|
||||
return (
|
||||
<html lang="en" className="light">
|
||||
<html lang="en" className="dark">
|
||||
<body
|
||||
className={cn('grainy min-h-screen bg-slate-100 font-sans antialiased')}
|
||||
>
|
||||
|
||||
@@ -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 (
|
||||
<ReduxProvider store={storeRef.current}>
|
||||
<api.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TooltipProvider delayDuration={200}>
|
||||
{children}
|
||||
<Toaster />
|
||||
<ModalProvider />
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>
|
||||
</api.Provider>
|
||||
</ReduxProvider>
|
||||
<ThemeProvider
|
||||
enableSystem
|
||||
attribute="class"
|
||||
defaultTheme="system"
|
||||
disableTransitionOnChange
|
||||
>
|
||||
<ReduxProvider store={storeRef.current}>
|
||||
<api.Provider client={trpcClient} queryClient={queryClient}>
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<TooltipProvider delayDuration={200}>
|
||||
{children}
|
||||
<Toaster />
|
||||
<ModalProvider />
|
||||
</TooltipProvider>
|
||||
</QueryClientProvider>
|
||||
</api.Provider>
|
||||
</ReduxProvider>
|
||||
</ThemeProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user