diff --git a/dev_output.log b/dev_output.log
new file mode 100644
index 0000000..0355cf1
--- /dev/null
+++ b/dev_output.log
@@ -0,0 +1,30 @@
+
+> github-user-summary@0.1.0 dev
+> next dev
+
+▲ Next.js 16.1.6 (Turbopack)
+- Local: http://localhost:3000
+- Network: http://192.168.0.2:3000
+
+✓ Starting...
+✓ Ready in 1120ms
+○ Compiling /dashboard/settings ...
+[next-auth][warn][NEXTAUTH_URL]
+https://next-auth.js.org/warnings#nextauth_url
+ GET /dashboard/settings 307 in 5.9s (compile: 5.5s, render: 388ms)
+ GET / 200 in 769ms (compile: 692ms, render: 77ms)
+[next-auth][warn][NEXTAUTH_URL]
+https://next-auth.js.org/warnings#nextauth_url
+ GET /api/auth/session 200 in 2.4s (compile: 2.4s, render: 52ms)
+ GET / 200 in 106ms (compile: 6ms, render: 99ms)
+ GET /api/auth/session 200 in 40ms (compile: 27ms, render: 12ms)
+⚠ metadataBase property in metadata export is not set for resolving social open graph or twitter images, using "http://localhost:3000". See https://nextjs.org/docs/app/api-reference/functions/generate-metadata#metadatabase
+React expects the `children` prop of
tags to be a string, number, bigint, or object with a novel `toString` method but found an Array with length 4 instead. Browsers treat all child Nodes of tags as Text content and React expects to be able to convert `children` of tags to a single string value which is why Arrays of length greater than 1 are not supported. When using JSX it can be common to combine text nodes and value nodes. For example: hello {nameOfUser}. While not immediately apparent, `children` in this case is an Array with length 2. If your `children` prop is using this form try rewriting it using a template string: {`hello ${nameOfUser}`}.
+ GET /api/auth/session 200 in 41ms (compile: 28ms, render: 13ms)
+ GET /test-settings 200 in 3.8s (compile: 2.1s, render: 1679ms)
+ GET /test-settings 200 in 974ms (compile: 740ms, render: 235ms)
+[next-auth][warn][NEXTAUTH_URL]
+https://next-auth.js.org/warnings#nextauth_url
+ GET /api/auth/session 200 in 369ms (compile: 294ms, render: 75ms)
+ GET /api/auth/session 200 in 38ms (compile: 9ms, render: 30ms)
+✓ Compiled in 61ms
diff --git a/src/components/DashboardSettingsClient.tsx b/src/components/DashboardSettingsClient.tsx
index e6ef1ca..9c1526c 100644
--- a/src/components/DashboardSettingsClient.tsx
+++ b/src/components/DashboardSettingsClient.tsx
@@ -1,10 +1,11 @@
"use client";
-import { useMemo } from "react";
import { useState } from "react";
import { useSession } from "next-auth/react";
import LayoutEditor from "@/components/LayoutEditor";
+import DisplayOptionsSection from "@/components/DisplayOptionsSection";
+import ReadmeCardUrlSection from "@/components/ReadmeCardUrlSection";
import {
getDefaultCardSettings,
loadCardSettings,
@@ -12,19 +13,6 @@ import {
} from "@/lib/cardSettings";
import type { CardBlockId, CardDisplayOptions, CardLayout } from "@/lib/types";
-const toggles: Array<{ key: keyof CardDisplayOptions; label: string }> = [
- { key: "showCompany", label: "Company" },
- { key: "showLocation", label: "Location" },
- { key: "showWebsite", label: "Website" },
- { key: "showTwitter", label: "Twitter" },
- { key: "showJoinedDate", label: "Joined date" },
- { key: "showTopics", label: "Topics" },
- { key: "showContributionBreakdown", label: "Contribution breakdown" },
- { key: "showStreaks", label: "Streaks" },
- { key: "showInterests", label: "Interests" },
- { key: "showActivityBreakdown", label: "Activity breakdown" },
-];
-
export default function DashboardSettingsClient() {
const { data: session } = useSession();
const [layout, setLayout] = useState(
@@ -34,11 +22,6 @@ export default function DashboardSettingsClient() {
() => loadCardSettings().options,
);
const [status, setStatus] = useState("");
- const [readmeTheme, setReadmeTheme] = useState<"light" | "dark">("light");
- const [readmeCols, setReadmeCols] = useState<1 | 2>(1);
- const [includeStreak, setIncludeStreak] = useState(false);
- const [includeHeatmap, setIncludeHeatmap] = useState(false);
- const [copyState, setCopyState] = useState("");
const onSave = () => {
saveCardSettings(layout, options);
@@ -62,106 +45,6 @@ export default function DashboardSettingsClient() {
}));
};
- const readmeUrl = useMemo(() => {
- const username = session?.user?.login;
- if (!username) {
- return "";
- }
-
- const blockMap: Record<
- CardBlockId,
- "bio" | "stats" | "langs" | "repos" | null
- > = {
- avatar: null,
- bio: "bio",
- stats: "stats",
- topLanguages: "langs",
- topRepos: "repos",
- };
-
- const selected = layout.blocks
- .filter((block) => block.visible)
- .map((block) => blockMap[block.id])
- .filter((block): block is "bio" | "stats" | "langs" | "repos" =>
- Boolean(block),
- );
-
- const selectedBlocks: Array<
- "bio" | "stats" | "langs" | "repos" | "streak" | "heatmap"
- > = [...selected];
-
- if (includeStreak) {
- selectedBlocks.push("streak");
- }
-
- if (includeHeatmap) {
- selectedBlocks.push("heatmap");
- }
-
- const uniqueBlocks = Array.from(new Set(selectedBlocks));
-
- const layoutParts = layout.blocks
- .filter((block) => block.visible && blockMap[block.id])
- .map((block) => {
- const target = blockMap[block.id];
- if (!target) {
- return null;
- }
- return `${block.column}:${target}`;
- })
- .filter((value): value is string => Boolean(value));
-
- const hide = [];
- if (options.showContributionBreakdown === false) {
- hide.push("stars");
- }
- if (options.showActivityBreakdown === false) {
- hide.push("forks");
- }
-
- const params = new URLSearchParams();
- params.set("format", "png");
- params.set("theme", readmeTheme);
- params.set("cols", String(readmeCols));
- params.set(
- "blocks",
- uniqueBlocks.length > 0 ? uniqueBlocks.join(",") : "bio,stats,langs",
- );
- if (layoutParts.length > 0) {
- params.set("layout", layoutParts.join(","));
- }
- if (hide.length > 0) {
- params.set("hide", hide.join(","));
- }
- params.set("width", "600");
-
- const origin = typeof window !== "undefined" ? window.location.origin : "";
- return `${origin}/api/card/${encodeURIComponent(username)}?${params.toString()}`;
- }, [
- session?.user?.login,
- layout.blocks,
- options.showContributionBreakdown,
- options.showActivityBreakdown,
- readmeTheme,
- readmeCols,
- includeStreak,
- includeHeatmap,
- ]);
-
- const onCopyReadmeUrl = async () => {
- if (!readmeUrl) {
- setCopyState("Sign in to generate URL");
- return;
- }
-
- try {
- await navigator.clipboard.writeText(readmeUrl);
- setCopyState("Copied!");
- } catch {
- setCopyState("Copy failed");
- }
- };
-
return (