Skip to content

Commit fd36f05

Browse files
committed
feat: web app
- added the header on the web app with support for dark mode using the right primitives for nextjs: next-themes for the theme-provider instead of the default provider coming for react+vite - installed `turbopack-inline-svg-loader` to properly import and render the logo as an svg instead of an image in the web app header, similarly to vite - installed the `prettier-plugin-tailwindcss` in the web app
1 parent 2d27b88 commit fd36f05

23 files changed

Lines changed: 2299 additions & 141 deletions

apps/dashboard/src/App.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ import { ScrollToTopButton } from "@repo/ui/components/scroll-to-top-button";
1010
import { SidebarProvider } from "@repo/ui/components/ui/sidebar";
1111
import { Toaster } from "@repo/ui/components/ui/sonner";
1212
import { TooltipProvider } from "@repo/ui/components/ui/tooltip";
13-
import { ThemeProvider } from "@repo/ui/providers/theme-provider";
1413
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
1514
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
1615
import { createTRPCClient, httpBatchLink } from "@trpc/client";
1716

1817
import { FallBackRender } from "./components/errors/error-boundary";
1918
import { Wrapper } from "./components/layout/navigation-reset-wrapper";
2019
import { useExtensionWebsocket } from "./hooks/use-extension-websocket";
20+
import { ThemeProvider } from "./providers/theme-provider";
2121
import { TRPCProvider } from "./utils/trpc";
2222

2323
function makeQueryClient() {

apps/dashboard/src/components/layout/header/navbar-dropdowns.tsx

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1+
import { useTheme } from "@/providers/theme-provider";
12
import { ToggleThemeDropDown } from "@repo/ui/components/toggle-theme-dropdown";
23

34
import { AuthDropDown } from "./auth-dropdown";
45

56
export const NavbarDropDowns = () => {
7+
const { theme: providedTheme, setTheme, resolvedTheme } = useTheme();
8+
69
return (
710
<div className="flex gap-4 self-end">
8-
<ToggleThemeDropDown />
11+
<ToggleThemeDropDown
12+
providedTheme={providedTheme}
13+
resolvedTheme={resolvedTheme}
14+
setTheme={setTheme}
15+
/>
916
<AuthDropDown />
1017
</div>
1118
);
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
import { createContext, useContext, useEffect, useState } from "react";
2+
3+
import { ResolvedTheme, Theme } from "@repo/ui/types-schemas";
4+
5+
type ThemeProviderProps = {
6+
children: React.ReactNode;
7+
defaultTheme?: Theme;
8+
storageKey?: string;
9+
};
10+
11+
type ThemeProviderState = {
12+
theme: Theme;
13+
setTheme: (theme: Theme) => void;
14+
resolvedTheme: ResolvedTheme;
15+
};
16+
17+
const initialState: ThemeProviderState = {
18+
theme: "system",
19+
setTheme: () => null,
20+
resolvedTheme: "light",
21+
};
22+
23+
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
24+
25+
export const ThemeProvider = ({
26+
children,
27+
defaultTheme = "system",
28+
storageKey = "vite-ui-theme",
29+
...props
30+
}: ThemeProviderProps) => {
31+
const [theme, setTheme] = useState<Theme>(
32+
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
33+
);
34+
const [resolvedTheme, setResolvedTheme] = useState<ResolvedTheme>("light");
35+
36+
useEffect(() => {
37+
const root = window.document.documentElement;
38+
39+
root.classList.remove("light", "dark");
40+
41+
let newTheme: ResolvedTheme;
42+
43+
if (theme === "system") {
44+
newTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
45+
? "dark"
46+
: "light";
47+
} else {
48+
newTheme = theme;
49+
}
50+
51+
root.classList.add(newTheme);
52+
setResolvedTheme(newTheme);
53+
}, [theme]);
54+
55+
const value = {
56+
theme,
57+
resolvedTheme,
58+
setTheme: (theme: Theme) => {
59+
localStorage.setItem(storageKey, theme);
60+
setTheme(theme);
61+
},
62+
};
63+
64+
return (
65+
<ThemeProviderContext.Provider {...props} value={value}>
66+
{children}
67+
</ThemeProviderContext.Provider>
68+
);
69+
};
70+
71+
export const useTheme = () => {
72+
const context = useContext(ThemeProviderContext);
73+
74+
if (context === undefined)
75+
throw new Error("useTheme must be used within a ThemeProvider");
76+
77+
return context;
78+
};

apps/web/.prettierrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"plugins": ["prettier-plugin-tailwindcss"]
3+
}

apps/web/next.config.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@ import type { NextConfig } from "next";
22

33
const nextConfig: NextConfig = {
44
output: "standalone",
5+
turbopack: {
6+
rules: {
7+
"*.svg": {
8+
loaders: ["turbopack-inline-svg-loader"],
9+
condition: {
10+
content: /^[\s\S]{0,4000}$/, // <-- Inline SVGs smaller than ~4Kb
11+
},
12+
as: "*.js",
13+
},
14+
},
15+
},
516
};
617

718
export default nextConfig;

apps/web/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
"lint": "eslint . --fix"
1010
},
1111
"dependencies": {
12+
"@icons-pack/react-simple-icons": "^13.11.1",
1213
"@repo/common": "*",
1314
"@repo/trpc": "*",
1415
"@repo/ui": "*",
@@ -18,14 +19,18 @@
1819
"react-dom": "^19.2.0"
1920
},
2021
"devDependencies": {
22+
"@svgr/webpack": "^8.1.0",
2123
"@types/node": "^20.19.32",
2224
"@types/react": "^19.2.5",
2325
"@types/react-dom": "^19.2.3",
2426
"autoprefixer": "^10.4.24",
2527
"eslint": "^9.39.2",
2628
"eslint-config-next": "^16.0.3",
2729
"postcss": "^8.5.6",
30+
"prettier": "^3.8.1",
31+
"prettier-plugin-tailwindcss": "^0.6.14",
2832
"tailwindcss": "^4.1.17",
33+
"turbopack-inline-svg-loader": "^1.0.3",
2934
"typescript": "^5.9.3"
3035
},
3136
"overrides": {

apps/web/src/app/layout.tsx

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import type { Metadata } from "next";
2-
import { ThemeProvider } from "next-themes";
32
import { Inter } from "next/font/google";
43

4+
import { Footer } from "@/components/layout/footer";
5+
import { Header } from "@/components/layout/header/header";
6+
import { ThemeProvider } from "@/providers/theme-provider";
7+
58
import "@repo/ui/globals.css";
69

710
const inter = Inter({ subsets: ["latin"] });
@@ -18,9 +21,17 @@ export default function RootLayout({
1821
}>) {
1922
return (
2023
<html lang="en" suppressHydrationWarning>
21-
<body className={`${inter.className} antialiased`}>
22-
<ThemeProvider enableSystem attribute="class" defaultTheme="system">
24+
<head />
25+
<body className={`${inter.className} flex flex-col antialiased`}>
26+
<ThemeProvider
27+
enableSystem
28+
attribute="class"
29+
defaultTheme="system"
30+
disableTransitionOnChange
31+
>
32+
<Header />
2333
{children}
34+
<Footer />
2435
</ThemeProvider>
2536
</body>
2637
</html>

apps/web/src/app/page.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const Home = () => {
2-
return <div>MoonCode</div>;
2+
return <main className="flex-1 pt-20"></main>;
33
};
44

55
export default Home;

apps/web/src/assets/moon.svg

Lines changed: 16 additions & 0 deletions
Loading
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const Footer = () => {
2+
return <footer className="mt-auto">footer</footer>;
3+
};

0 commit comments

Comments
 (0)