-
-
Notifications
You must be signed in to change notification settings - Fork 367
feat: Added hotkey shortcut to change themes #170
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
ff97b1b
2a2b4ce
7bb0ff2
0a3b318
9bb3427
2d9e216
5336cfa
4d2ec4d
05d4222
7af28a0
c1c65db
4440cb2
7d6e4ce
d5ed827
1b85d62
88261b5
cbd0ac4
cde4b7a
82c7d1e
8c107dd
e15a7a2
44d0915
a891bcd
2dbbea7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,54 @@ | ||||||||||||||||||||||||||||||
import { PaginatedFontsResponse } from "@/types/fonts"; | ||||||||||||||||||||||||||||||
import { FALLBACK_FONTS } from "@/utils/fonts"; | ||||||||||||||||||||||||||||||
import { fetchGoogleFonts } from "@/utils/fonts/google-fonts"; | ||||||||||||||||||||||||||||||
import { unstable_cache } from "next/cache"; | ||||||||||||||||||||||||||||||
import { NextRequest, NextResponse } from "next/server"; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const cachedFetchGoogleFonts = unstable_cache(fetchGoogleFonts, ["google-fonts-catalogue"], { | ||||||||||||||||||||||||||||||
tags: ["google-fonts-catalogue"], | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
export async function GET(request: NextRequest) { | ||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||
const { searchParams } = new URL(request.url); | ||||||||||||||||||||||||||||||
const query = searchParams.get("q")?.toLowerCase() || ""; | ||||||||||||||||||||||||||||||
const category = searchParams.get("category")?.toLowerCase(); | ||||||||||||||||||||||||||||||
const limit = Math.min(Number(searchParams.get("limit")) || 50, 100); | ||||||||||||||||||||||||||||||
const offset = Number(searchParams.get("offset")) || 0; | ||||||||||||||||||||||||||||||
Comment on lines
+14
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add input validation for query parameters The query parameter handling could be more robust. - const query = searchParams.get("q")?.toLowerCase() || "";
- const category = searchParams.get("category")?.toLowerCase();
- const limit = Math.min(Number(searchParams.get("limit")) || 50, 100);
- const offset = Number(searchParams.get("offset")) || 0;
+ const query = (searchParams.get("q") || "").toLowerCase();
+ const category = searchParams.get("category")?.toLowerCase();
+ const limitParam = searchParams.get("limit");
+ const offsetParam = searchParams.get("offset");
+
+ const limit = Math.min(
+ limitParam && !isNaN(Number(limitParam)) ? Number(limitParam) : 50,
+ 100
+ );
+ const offset = offsetParam && !isNaN(Number(offsetParam)) ? Number(offsetParam) : 0; 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
let googleFonts = FALLBACK_FONTS; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
try { | ||||||||||||||||||||||||||||||
googleFonts = await cachedFetchGoogleFonts(process.env.GOOGLE_FONTS_API_KEY); | ||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||
console.error("Error fetching Google Fonts:", error); | ||||||||||||||||||||||||||||||
console.log("Using fallback fonts"); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Filter fonts based on search query and category | ||||||||||||||||||||||||||||||
let filteredFonts = googleFonts; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if (query) { | ||||||||||||||||||||||||||||||
filteredFonts = filteredFonts.filter((font) => font.family.toLowerCase().includes(query)); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
if (category && category !== "all") { | ||||||||||||||||||||||||||||||
filteredFonts = filteredFonts.filter((font) => font.category === category); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const paginatedFonts = filteredFonts.slice(offset, offset + limit); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const response: PaginatedFontsResponse = { | ||||||||||||||||||||||||||||||
fonts: paginatedFonts, | ||||||||||||||||||||||||||||||
total: filteredFonts.length, | ||||||||||||||||||||||||||||||
offset, | ||||||||||||||||||||||||||||||
limit, | ||||||||||||||||||||||||||||||
hasMore: offset + limit < filteredFonts.length, | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return NextResponse.json(response); | ||||||||||||||||||||||||||||||
} catch (error) { | ||||||||||||||||||||||||||||||
console.error("Error in Google Fonts API:", error); | ||||||||||||||||||||||||||||||
return NextResponse.json({ error: "Failed to fetch fonts" }, { status: 500 }); | ||||||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -63,6 +63,7 @@ | |
} | ||
|
||
* { | ||
color-scheme: light dark; | ||
border-color: var(--color-border); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,62 @@ | ||
"use client"; | ||
|
||
import { ThemePresetButtons } from "@/components/home/theme-preset-buttons"; | ||
import { useTheme } from "@/components/theme-provider"; | ||
import { Button } from "@/components/ui/button"; | ||
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip"; | ||
import { useEditorStore } from "@/store/editor-store"; | ||
import { defaultPresets } from "@/utils/theme-presets"; | ||
import { Sun, Moon } from "lucide-react"; | ||
import Link from "next/link"; | ||
|
||
export default function NotFound() { | ||
const { theme, toggleTheme } = useTheme(); | ||
const { themeState, applyThemePreset } = useEditorStore(); | ||
const mode = themeState.currentMode; | ||
const presetNames = Object.keys(defaultPresets); | ||
return ( | ||
<div className="flex min-h-screen flex-col items-center justify-center"> | ||
<h1 className="text-4xl font-bold">404</h1> | ||
<p className="mt-4 text-lg">Page not found</p> | ||
<div className="bg-background flex min-h-screen flex-col items-center justify-center px-4"> | ||
<div className="fixed top-4 right-4 z-50"> | ||
<Tooltip> | ||
<TooltipTrigger asChild> | ||
<Button | ||
variant="ghost" | ||
size="sm" | ||
aria-label="Toggle theme" | ||
onClick={(e) => toggleTheme({ x: e.clientX, y: e.clientY })} | ||
> | ||
{theme === "light" ? <Sun className="h-5 w-5" /> : <Moon className="h-5 w-5" />} | ||
</Button> | ||
</TooltipTrigger> | ||
<TooltipContent side="bottom"> | ||
<p className="text-xs">Toggle theme</p> | ||
</TooltipContent> | ||
</Tooltip> | ||
</div> | ||
|
||
<span className="text-muted-foreground mb-6 text-[6rem] leading-none font-extrabold select-none"> | ||
404 | ||
</span> | ||
<h1 className="text-foreground mb-2 text-3xl font-bold">Oops, Lost in Space?</h1> | ||
<p className="text-muted-foreground mb-8 max-w-md text-center text-lg"> | ||
Go home or try switching the theme! | ||
</p> | ||
|
||
<Link | ||
href="/" | ||
className="bg-primary text-primary-foreground hover:bg-primary/80 mb-10 rounded-md px-6 py-2 font-semibold shadow transition-colors" | ||
> | ||
Back to Home | ||
</Link> | ||
|
||
<div className="flex w-full justify-center"> | ||
<ThemePresetButtons | ||
presetNames={presetNames} | ||
mode={mode} | ||
themeState={themeState} | ||
applyThemePreset={applyThemePreset} | ||
/> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
"use client"; | ||
|
||
import { useMounted } from "@/hooks/use-mounted"; | ||
import { useEditorStore } from "@/store/editor-store"; | ||
import { extractFontFamily, getDefaultWeights } from "@/utils/fonts"; | ||
import { loadGoogleFont } from "@/utils/fonts/google-fonts"; | ||
import { useEffect, useMemo } from "react"; | ||
|
||
export function DynamicFontLoader() { | ||
const { themeState } = useEditorStore(); | ||
const isMounted = useMounted(); | ||
|
||
const fontSans = themeState.styles.light["font-sans"]; | ||
const fontSerif = themeState.styles.light["font-serif"]; | ||
const fontMono = themeState.styles.light["font-mono"]; | ||
Comment on lines
+13
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Consider loading fonts from both light and dark theme styles. Currently, only fonts from the light theme are loaded. If dark and light themes use different fonts, users switching themes might experience loading delays or missing fonts. Consider loading fonts from both theme modes: const fontSans = themeState.styles.light["font-sans"];
const fontSerif = themeState.styles.light["font-serif"];
const fontMono = themeState.styles.light["font-mono"];
+ const darkFontSans = themeState.styles.dark["font-sans"];
+ const darkFontSerif = themeState.styles.dark["font-serif"];
+ const darkFontMono = themeState.styles.dark["font-mono"]; 🤖 Prompt for AI Agents
|
||
|
||
const currentFonts = useMemo(() => { | ||
return { | ||
sans: fontSans, | ||
serif: fontSerif, | ||
mono: fontMono, | ||
} as const; | ||
}, [fontSans, fontSerif, fontMono]); | ||
|
||
useEffect(() => { | ||
if (!isMounted) return; | ||
|
||
try { | ||
Object.entries(currentFonts).forEach(([_type, fontValue]) => { | ||
const fontFamily = extractFontFamily(fontValue); | ||
if (fontFamily) { | ||
const weights = getDefaultWeights(["400", "500", "600", "700"]); | ||
loadGoogleFont(fontFamily, weights); | ||
} | ||
}); | ||
} catch (e) { | ||
console.warn("DynamicFontLoader: Failed to load Google fonts:", e); | ||
} | ||
}, [isMounted, currentFonts]); | ||
|
||
return null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using stable caching alternatives
The
unstable_cache
API is experimental and may change or be removed in future Next.js versions. Consider using a stable caching solution like Redis, in-memory cache with LRU, or Next.js ISR for production stability.🤖 Prompt for AI Agents