Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
}
}

/* Dark mode with class strategy */
.dark {
--foreground: #ededed;
--background: #0a0a0a;
}

body {
color: var(--foreground);
background: var(--background);
Expand Down
4 changes: 2 additions & 2 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ export default function RootLayout({
children: React.ReactNode;
}) {
return (
<html lang="en">
<body className="min-h-screen bg-gray-50">
<html lang="en" suppressHydrationWarning>
<body className="min-h-screen transition-colors duration-200">
<Providers>{children}</Providers>
</body>
</html>
Expand Down
3 changes: 2 additions & 1 deletion src/app/providers.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import React, { createContext, useContext, useState, useCallback, useEffect } from "react";
import { connectWallet, getPublicKey, isFreighterInstalled } from "@/lib/wallet";
import { ThemeProvider } from "@/components/ThemeProvider";

interface WalletContextType {
address: string | null;
Expand Down Expand Up @@ -54,7 +55,7 @@ export function Providers({ children }: { children: React.ReactNode }) {
disconnect,
}}
>
{children}
<ThemeProvider>{children}</ThemeProvider>
</WalletContext.Provider>
);
}
14 changes: 9 additions & 5 deletions src/components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,36 @@

import Link from "next/link";
import { ConnectWallet } from "./ConnectWallet";
import { ThemeToggle } from "./ThemeToggle";

export function Navbar() {
return (
<nav className="bg-white shadow-sm border-b">
<nav className="bg-white dark:bg-gray-900 shadow-sm border-b border-gray-200 dark:border-gray-700 transition-colors duration-200">
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div className="flex justify-between h-16 items-center">
<div className="flex items-center space-x-8">
<Link href="/" className="text-xl font-bold text-primary-700">
<Link href="/" className="text-xl font-bold text-primary-700 dark:text-primary-400">
SoroSave
</Link>
<div className="hidden sm:flex space-x-4">
<Link
href="/groups"
className="text-gray-600 hover:text-gray-900 px-3 py-2 text-sm font-medium"
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 text-sm font-medium transition-colors"
>
Groups
</Link>
<Link
href="/groups/new"
className="text-gray-600 hover:text-gray-900 px-3 py-2 text-sm font-medium"
className="text-gray-600 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 text-sm font-medium transition-colors"
>
Create Group
</Link>
</div>
</div>
<ConnectWallet />
<div className="flex items-center space-x-4">
<ThemeToggle />
<ConnectWallet />
</div>
</div>
</div>
</nav>
Expand Down
64 changes: 64 additions & 0 deletions src/components/ThemeProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
"use client";

import React, { createContext, useContext, useState, useEffect } from "react";

type Theme = "light" | "dark";

interface ThemeContextType {
theme: Theme;
toggleTheme: () => void;
setTheme: (theme: Theme) => void;
}

const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

export function useTheme() {
const context = useContext(ThemeContext);
if (context === undefined) {
throw new Error("useTheme must be used within a ThemeProvider");
}
return context;
}

export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setThemeState] = useState<Theme>("light");
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
// Check localStorage for saved theme preference
const savedTheme = localStorage.getItem("theme") as Theme | null;
if (savedTheme) {
setThemeState(savedTheme);
document.documentElement.classList.toggle("dark", savedTheme === "dark");
} else {
// Check system preference
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const initialTheme = prefersDark ? "dark" : "light";
setThemeState(initialTheme);
document.documentElement.classList.toggle("dark", prefersDark);
}
}, []);

const setTheme = (newTheme: Theme) => {
setThemeState(newTheme);
localStorage.setItem("theme", newTheme);
document.documentElement.classList.toggle("dark", newTheme === "dark");
};

const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
};

// Prevent hydration mismatch by not rendering until mounted
if (!mounted) {
return <>{children}</>;
}

return (
<ThemeContext.Provider value={{ theme, toggleTheme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
49 changes: 49 additions & 0 deletions src/components/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
"use client";

import { useTheme } from "./ThemeProvider";

export function ThemeToggle() {
const { theme, toggleTheme } = useTheme();

return (
<button
onClick={toggleTheme}
className="p-2 rounded-lg bg-gray-200 dark:bg-gray-700 text-gray-800 dark:text-gray-200 hover:bg-gray-300 dark:hover:bg-gray-600 transition-colors duration-200"
aria-label={theme === "light" ? "Switch to dark mode" : "Switch to light mode"}
>
{theme === "light" ? (
<!-- Moon icon for dark mode -->
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-5 h-5"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
/>
</svg>
) : (
<!-- Sun icon for light mode -->
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="w-5 h-5"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
/>
</svg>
)}
</button>
);
}
1 change: 1 addition & 0 deletions tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: 'class',
content: ["./src/**/*.{js,ts,jsx,tsx,mdx}"],
theme: {
extend: {
Expand Down