+
{messages[0]?.headers.subject}
{topRightComponent && (
diff --git a/apps/web/components/theme-provider.tsx b/apps/web/components/theme-provider.tsx
new file mode 100644
index 0000000000..0b818eab0f
--- /dev/null
+++ b/apps/web/components/theme-provider.tsx
@@ -0,0 +1,9 @@
+"use client";
+
+import * as React from "react";
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+import type { ThemeProviderProps } from "next-themes";
+
+export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
+ return
{children};
+}
diff --git a/apps/web/components/theme-toggle.tsx b/apps/web/components/theme-toggle.tsx
new file mode 100644
index 0000000000..0bc320b341
--- /dev/null
+++ b/apps/web/components/theme-toggle.tsx
@@ -0,0 +1,36 @@
+"use client";
+
+import * as React from "react";
+import { MoonIcon, SunIcon } from "lucide-react";
+import { useTheme } from "next-themes";
+import { cn } from "@/utils";
+
+export function ThemeToggle({ focus }: { focus?: boolean }) {
+ const { setTheme, theme } = useTheme();
+
+ const toggleTheme = () => setTheme(theme === "light" ? "dark" : "light");
+
+ return (
+
+ );
+}
diff --git a/apps/web/components/ui/alert.tsx b/apps/web/components/ui/alert.tsx
index 253655f612..c8f9a32f93 100644
--- a/apps/web/components/ui/alert.tsx
+++ b/apps/web/components/ui/alert.tsx
@@ -8,12 +8,12 @@ const alertVariants = cva(
{
variants: {
variant: {
- default: "bg-white text-slate-950 dark:bg-slate-950 dark:text-slate-50",
+ default: "text-slate-950 bg-background dark:text-slate-50",
destructive:
"border-red-500/50 text-red-500 dark:border-red-500 [&>svg]:text-red-500 dark:border-red-900/50 dark:text-red-900 dark:dark:border-red-900 dark:[&>svg]:text-red-900",
success:
"border-green-500/50 text-green-500 dark:border-green-500 [&>svg]:text-green-500 dark:border-green-900/50 dark:text-green-900 dark:dark:border-green-900 dark:[&>svg]:text-green-900",
- blue: "border-blue-500/50 text-blue-500 dark:border-blue-500 [&>svg]:text-blue-500 dark:border-blue-900/50 dark:text-blue-900 dark:dark:border-blue-900 dark:[&>svg]:text-blue-900",
+ blue: "border-blue-500/50 text-blue-500 dark:border-blue-400 [&>svg]:text-blue-500 dark:border-blue-400/50 dark:text-blue-300 dark:[&>svg]:text-blue-300",
},
},
defaultVariants: {
diff --git a/apps/web/components/ui/button.tsx b/apps/web/components/ui/button.tsx
index aabb218731..b0ea4ef775 100644
--- a/apps/web/components/ui/button.tsx
+++ b/apps/web/components/ui/button.tsx
@@ -6,25 +6,25 @@ import { Loader2 } from "lucide-react";
import { cn } from "@/utils";
const buttonVariants = cva(
- "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-white transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-slate-950 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 dark:ring-offset-slate-950 dark:focus-visible:ring-slate-300 text-nowrap",
+ "inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 text-nowrap",
{
variants: {
variant: {
- default:
- "bg-slate-900 text-slate-50 hover:bg-slate-900/90 dark:bg-slate-50 dark:text-slate-900 dark:hover:bg-slate-50/90",
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
- "bg-red-500 text-slate-50 hover:bg-red-500/90 dark:bg-red-900 dark:text-slate-50 dark:hover:bg-red-900/90",
+ "bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
- "border border-slate-200 bg-white hover:bg-slate-100 hover:text-slate-900 dark:border-slate-800 dark:bg-slate-950 dark:hover:bg-slate-800 dark:hover:text-slate-50",
+ "border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
- "bg-slate-100 text-slate-900 hover:bg-slate-100/80 dark:bg-slate-800 dark:text-slate-50 dark:hover:bg-slate-800/80",
- ghost:
- "hover:bg-slate-100 hover:text-slate-900 dark:hover:bg-slate-800 dark:hover:text-slate-50",
- link: "text-slate-900 underline-offset-4 hover:underline dark:text-slate-50",
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost: "text-foreground hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
green:
"bg-green-100 text-green-900 hover:bg-green-100/80 dark:bg-green-800 dark:text-green-50 dark:hover:bg-green-800/80",
red: "bg-red-100 text-red-900 hover:bg-red-100/80 dark:bg-red-800 dark:text-red-50 dark:hover:bg-red-800/80",
blue: "bg-blue-100 text-blue-900 hover:bg-blue-100/80 dark:bg-blue-800 dark:text-blue-50 dark:hover:bg-blue-800/80",
+ primaryBlue:
+ "bg-blue-600 text-white hover:bg-blue-700 dark:bg-blue-700 dark:hover:bg-blue-800",
},
size: {
default: "h-10 px-4 py-2",
diff --git a/apps/web/components/ui/calendar.tsx b/apps/web/components/ui/calendar.tsx
index 0a99b9f2a6..8b21538e5a 100644
--- a/apps/web/components/ui/calendar.tsx
+++ b/apps/web/components/ui/calendar.tsx
@@ -34,7 +34,7 @@ function Calendar({
table: "w-full border-collapse space-y-1",
head_row: "flex",
head_cell:
- "text-slate-500 rounded-md w-9 font-normal text-[0.8rem] dark:text-slate-400",
+ "text-muted-foreground rounded-md w-9 font-normal text-[0.8rem] dark:text-slate-400",
row: "flex w-full mt-2",
cell: "h-9 w-9 text-center text-sm p-0 relative [&:has([aria-selected].day-range-end)]:rounded-r-md [&:has([aria-selected].day-outside)]:bg-slate-100/50 [&:has([aria-selected])]:bg-slate-100 first:[&:has([aria-selected])]:rounded-l-md last:[&:has([aria-selected])]:rounded-r-md focus-within:relative focus-within:z-20 dark:[&:has([aria-selected].day-outside)]:bg-slate-800/50 dark:[&:has([aria-selected])]:bg-slate-800",
day: cn(
@@ -47,8 +47,8 @@ function Calendar({
day_today:
"bg-slate-100 text-slate-900 dark:bg-slate-800 dark:text-slate-50",
day_outside:
- "day-outside text-slate-500 opacity-50 aria-selected:bg-slate-100/50 aria-selected:text-slate-500 aria-selected:opacity-30 dark:text-slate-400 dark:aria-selected:bg-slate-800/50 dark:aria-selected:text-slate-400",
- day_disabled: "text-slate-500 opacity-50 dark:text-slate-400",
+ "day-outside text-muted-foreground opacity-50 aria-selected:bg-slate-100/50 aria-selected:text-muted-foreground aria-selected:opacity-30 dark:text-slate-400 dark:aria-selected:bg-slate-800/50 dark:aria-selected:text-slate-400",
+ day_disabled: "text-muted-foreground opacity-50 dark:text-slate-400",
day_range_middle:
"aria-selected:bg-slate-100 aria-selected:text-slate-900 dark:aria-selected:bg-slate-800 dark:aria-selected:text-slate-50",
day_hidden: "invisible",
diff --git a/apps/web/components/ui/card.tsx b/apps/web/components/ui/card.tsx
index 2146d040bf..cc65978ac6 100644
--- a/apps/web/components/ui/card.tsx
+++ b/apps/web/components/ui/card.tsx
@@ -9,7 +9,7 @@ const Card = React.forwardRef<
(({ className, ...props }, ref) => (
));
diff --git a/apps/web/components/ui/command.tsx b/apps/web/components/ui/command.tsx
index 15a4e97843..b608e17955 100644
--- a/apps/web/components/ui/command.tsx
+++ b/apps/web/components/ui/command.tsx
@@ -15,7 +15,7 @@ const Command = React.forwardRef<
{children}
@@ -55,7 +55,7 @@ const CommandInput = React.forwardRef<
{children}
-
+
Close
@@ -102,7 +102,7 @@ const DialogDescription = React.forwardRef<
>(({ className, ...props }, ref) => (
));
diff --git a/apps/web/components/ui/dropdown-menu.tsx b/apps/web/components/ui/dropdown-menu.tsx
index 46e87491e5..4e2771c103 100644
--- a/apps/web/components/ui/dropdown-menu.tsx
+++ b/apps/web/components/ui/dropdown-menu.tsx
@@ -47,7 +47,7 @@ const DropdownMenuSubContent = React.forwardRef<
(({ className, ...props }, ref) => (
));
diff --git a/apps/web/components/ui/sidebar.tsx b/apps/web/components/ui/sidebar.tsx
index 5994e31198..9f14366684 100644
--- a/apps/web/components/ui/sidebar.tsx
+++ b/apps/web/components/ui/sidebar.tsx
@@ -251,14 +251,17 @@ const Sidebar = React.forwardRef<
// Adjust the padding for floating and inset variants.
variant === "floating" || variant === "inset"
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]"
- : "group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
+ : "border-border group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l",
className,
)}
{...props}
>
{children}
@@ -333,7 +336,7 @@ const SidebarInset = React.forwardRef<
(({ className, ...props }, ref) => (
));
diff --git a/apps/web/components/ui/tabs.tsx b/apps/web/components/ui/tabs.tsx
index 083f9263e3..3ae2ff4a45 100644
--- a/apps/web/components/ui/tabs.tsx
+++ b/apps/web/components/ui/tabs.tsx
@@ -76,7 +76,7 @@ export function TabsList(props: {
@@ -94,7 +94,7 @@ export const TabsTrigger = (props: {
diff --git a/apps/web/components/ui/tooltip.tsx b/apps/web/components/ui/tooltip.tsx
index f160de08d0..fbd81fe0d3 100644
--- a/apps/web/components/ui/tooltip.tsx
+++ b/apps/web/components/ui/tooltip.tsx
@@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef<
ref={ref}
sideOffset={sideOffset}
className={cn(
- // "z-50 overflow-hidden rounded-md border border-slate-200 bg-white px-3 py-1.5 text-sm text-slate-950 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 dark:border-slate-800 dark:bg-slate-950 dark:text-slate-50",
+ // "z-50 overflow-hidden rounded-md border border-slate-200 px-3 py-1.5 text-sm text-slate-950 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 dark:border-slate-800 bg-background dark:text-slate-50",
"z-50 overflow-hidden rounded-md border border-slate-700 bg-gray-900 px-3 py-1.5 text-sm text-slate-50 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 dark:border-slate-100 dark:bg-slate-50 dark:text-slate-950",
className,
)}
diff --git a/apps/web/package.json b/apps/web/package.json
index 7701107ec1..dce40e7940 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -103,6 +103,7 @@
"next-auth": "5.0.0-beta.25",
"next-axiom": "^1.9.1",
"next-sanity": "9",
+ "next-themes": "0.4.4",
"nodemailer": "^6.9.16",
"nuqs": "^2.2.3",
"ollama-ai-provider": "^1.1.0",
diff --git a/apps/web/providers/AppProviders.tsx b/apps/web/providers/AppProviders.tsx
index 8d25930261..bde371f538 100644
--- a/apps/web/providers/AppProviders.tsx
+++ b/apps/web/providers/AppProviders.tsx
@@ -5,13 +5,16 @@ import { Provider } from "jotai";
import { NuqsAdapter } from "nuqs/adapters/next/app";
import { ComposeModalProvider } from "@/providers/ComposeModalProvider";
import { jotaiStore } from "@/store";
+import { ThemeProvider } from "@/components/theme-provider";
export function AppProviders(props: { children: React.ReactNode }) {
return (
-
-
- {props.children}
-
-
+
+
+
+ {props.children}
+
+
+
);
}
diff --git a/apps/web/styles/globals.css b/apps/web/styles/globals.css
index ab495a62db..2ad95d7cdb 100644
--- a/apps/web/styles/globals.css
+++ b/apps/web/styles/globals.css
@@ -4,34 +4,34 @@
@layer base {
:root {
- --background: 0 0% 100%;
- --foreground: 222.2 84% 4.9%;
+ --background: 0 0% 100%; /* white */
+ --foreground: 222.2 47.4% 11.2%; /* slate-900 */
- --muted: 210 40% 96.1%;
- --muted-foreground: 215.4 16.3% 46.9%;
+ --muted: 210 40% 96.1%; /* slate-100 */
+ --muted-foreground: 215.4 16.3% 46.9%; /* slate-500 */
- --popover: 0 0% 100%;
- --popover-foreground: 222.2 84% 4.9%;
+ --popover: 0 0% 100%; /* white */
+ --popover-foreground: 222.2 47.4% 11.2%; /* slate-900 */
- --card: 0 0% 100%;
- --card-foreground: 222.2 84% 4.9%;
+ --card: 0 0% 100%; /* white */
+ --card-foreground: 222.2 47.4% 11.2%; /* slate-900 */
- --border: 214.3 31.8% 91.4%;
- --input: 214.3 31.8% 91.4%;
+ --border: 214.3 31.8% 91.4%; /* slate-200 */
+ --input: 214.3 31.8% 91.4%; /* slate-200 */
- --primary: 222.2 47.4% 11.2%;
- --primary-foreground: 210 40% 98%;
+ --primary: 222.2 47.4% 11.2%; /* slate-900 */
+ --primary-foreground: 210 40% 98%; /* slate-50 */
- --secondary: 210 40% 96.1%;
- --secondary-foreground: 222.2 47.4% 11.2%;
+ --secondary: 210 40% 96.1%; /* slate-100 */
+ --secondary-foreground: 222.2 47.4% 11.2%; /* slate-900 */
- --accent: 210 40% 96.1%;
- --accent-foreground: 222.2 47.4% 11.2%;
+ --accent: 210 40% 96.1%; /* slate-100 */
+ --accent-foreground: 222.2 47.4% 11.2%; /* slate-900 */
- --destructive: 0 84.2% 60.2%;
+ --destructive: 0 100% 50%;
--destructive-foreground: 210 40% 98%;
- --ring: 222.2 84% 4.9%;
+ --ring: 215 20.2% 65.1%; /* slate-400 */
--radius: 0.5rem;
@@ -44,7 +44,32 @@
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
+
.dark {
+ --background: 240 10% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 240 10% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 240 10% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 0 0% 98%;
+ --primary-foreground: 240 5.9% 10%;
+ --secondary: 240 3.7% 15.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 240 3.7% 15.9%;
+ --muted-foreground: 240 5% 64.9%;
+ --accent: 240 3.7% 15.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 85.7% 97.3%;
+ --border: 240 3.7% 15.9%;
+ --input: 240 3.7% 15.9%;
+ --ring: 240 4.9% 83.9%;
+ --chart-1: 220 70% 50%;
+ --chart-2: 160 60% 45%;
+ --chart-3: 30 80% 55%;
+ --chart-4: 280 65% 60%;
+ --chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
@@ -52,10 +77,19 @@
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
- --sidebar-ring: 217.2 91.2% 59.8%;
+ --sidebar-ring: 240 4.9% 83.9%;
}
}
.content-container {
@apply px-2 sm:px-6;
}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 35d15ad3f5..d77d49ea37 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -354,6 +354,9 @@ importers:
next-sanity:
specifier: '9'
version: 9.4.7(@sanity/client@6.24.1)(@sanity/icons@3.5.6(react@18.3.1))(@sanity/types@3.68.3(@types/react@18.3.12))(@sanity/ui@2.10.12(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1)))(next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sanity@3.68.3(@emotion/is-prop-valid@1.2.2)(@types/babel__core@7.20.5)(@types/node@22.10.2)(@types/react@18.3.12)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.38.1))(styled-components@6.1.13(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(svelte@4.2.12)
+ next-themes:
+ specifier: 0.4.4
+ version: 0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nodemailer:
specifier: ^6.9.16
version: 6.9.16
@@ -9237,6 +9240,12 @@ packages:
sanity: ^3.37.1
styled-components: ^6.1
+ next-themes@0.4.4:
+ resolution: {integrity: sha512-LDQ2qIOJF0VnuVrrMSMLrWGjRMkq+0mpgl6e0juCLqdJ+oo8Q84JRWT6Wh11VDQKkMMe+dVzDKLWs5n87T+PkQ==}
+ peerDependencies:
+ react: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+ react-dom: ^16.8 || ^17 || ^18 || ^19 || ^19.0.0-rc
+
next@14.2.15:
resolution: {integrity: sha512-h9ctmOokpoDphRvMGnwOJAedT6zKhwqyZML9mDtspgf4Rh3Pn7UTYKqePNoDvhsWBAO5GoPNYshnAUGIazVGmw==}
engines: {node: '>=18.17.0'}
@@ -22497,6 +22506,11 @@ snapshots:
- react-dom
- svelte
+ next-themes@0.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
+ dependencies:
+ react: 18.3.1
+ react-dom: 18.3.1(react@18.3.1)
+
next@14.2.15(@babel/core@7.26.0)(@opentelemetry/api@1.9.0)(@playwright/test@1.49.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 14.2.15