Skip to content

Commit 6eea1bf

Browse files
committed
feat: sync Customization Studio settings to URL for shareability
1 parent aae09c2 commit 6eea1bf

1 file changed

Lines changed: 40 additions & 0 deletions

File tree

app/customize/page.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import { useCallback, useEffect, useRef, useState, type ReactElement } from 'react';
4+
import { useRouter, useSearchParams } from 'next/navigation';
45
import { validateGitHubUsername } from '@/lib/validations';
56
import Link from 'next/link';
67
import { motion } from 'framer-motion';
@@ -56,6 +57,39 @@ export default function CustomizePage(): ReactElement {
5657
const hasUsername = trimmedUsername.length > 0;
5758
const isRandomTheme = theme === 'random';
5859

60+
const router = useRouter();
61+
const searchParams = useSearchParams();
62+
63+
// On mount: initialize state from URL search params
64+
/* eslint-disable react-hooks/set-state-in-effect */
65+
useEffect(() => {
66+
const u = searchParams.get('user') ?? '';
67+
const t = searchParams.get('theme') ?? 'dark';
68+
setUsername(u);
69+
setTheme(t);
70+
setBgHex(searchParams.get('bg') ?? '');
71+
setAccentHex(searchParams.get('accent') ?? '');
72+
setTextHex(searchParams.get('text') ?? '');
73+
setScale((searchParams.get('scale') as Scale) ?? 'linear');
74+
setSpeed(searchParams.get('speed') ?? '8s');
75+
setFont((searchParams.get('font') as Font) ?? 'Inter');
76+
setYear(searchParams.get('year') ?? '');
77+
setRadius(Number(searchParams.get('radius') ?? 8));
78+
setSize((searchParams.get('size') as BadgeSize) ?? 'medium');
79+
setHideTitle(searchParams.get('hide_title') === 'true');
80+
setHideBackground(searchParams.get('hide_background') === 'true');
81+
setHideStats(searchParams.get('hide_stats') === 'true');
82+
setViewMode((searchParams.get('view') as ViewMode) ?? 'default');
83+
setDeltaFormat((searchParams.get('delta_format') as DeltaFormat) ?? 'percent');
84+
setBadgeWidth(searchParams.get('width') ? Number(searchParams.get('width')) : '');
85+
setBadgeHeight(searchParams.get('height') ? Number(searchParams.get('height')) : '');
86+
setGrace(Number(searchParams.get('grace') ?? 1));
87+
setLanguage((searchParams.get('lang') as Language) ?? 'en');
88+
setTimezone((searchParams.get('tz') as Timezone) ?? 'UTC');
89+
// eslint-disable-next-line react-hooks/exhaustive-deps
90+
}, []);
91+
/* eslint-enable react-hooks/set-state-in-effect */
92+
5993
useEffect(() => {
6094
return () => {
6195
if (copyResetTimeoutRef.current !== null) {
@@ -114,6 +148,12 @@ export default function CustomizePage(): ReactElement {
114148
});
115149
const previewSrc = `/api/streak?${queryString}`;
116150

151+
// On change sync state to URL
152+
useEffect(() => {
153+
if (!queryString) return;
154+
router.replace(`/customize?${queryString}`, { scroll: false });
155+
}, [queryString, router]);
156+
117157
useEffect(() => {
118158
// eslint-disable-next-line react-hooks/set-state-in-effect
119159
setErrorMessage(null);

0 commit comments

Comments
 (0)