@naeil/ui 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +60 -21
  2. package/dist/chunk-JBTDRVZ7.js +46 -0
  3. package/dist/chunk-SYI3IKIC.js +355 -0
  4. package/dist/components/index.d.ts +87 -0
  5. package/dist/components/index.js +832 -0
  6. package/dist/components/ui/index.d.ts +83 -0
  7. package/dist/components/ui/index.js +481 -0
  8. package/dist/lib/utils.d.ts +5 -0
  9. package/dist/lib/utils.js +7 -0
  10. package/package.json +52 -11
  11. package/src/app/globals.css +1 -1
  12. package/src/styles/theme.css +206 -0
  13. package/postcss.config.mjs +0 -7
  14. package/src/app/[locale]/(content)/baby-agent/page.tsx +0 -30
  15. package/src/app/[locale]/(content)/blog/page.tsx +0 -68
  16. package/src/app/[locale]/(content)/cc/page.tsx +0 -216
  17. package/src/app/[locale]/(content)/design/page.tsx +0 -703
  18. package/src/app/[locale]/(content)/layout.tsx +0 -7
  19. package/src/app/[locale]/(content)/pkm/page.tsx +0 -31
  20. package/src/app/[locale]/(content)/projects/page.tsx +0 -100
  21. package/src/app/[locale]/(content)/sa/page.tsx +0 -179
  22. package/src/app/[locale]/(landing)/page.tsx +0 -157
  23. package/src/app/[locale]/layout.tsx +0 -72
  24. package/src/app/[locale]/login/actions.ts +0 -10
  25. package/src/app/[locale]/login/oauth-buttons.tsx +0 -78
  26. package/src/app/[locale]/login/page.tsx +0 -85
  27. package/src/app/auth/callback/route.ts +0 -78
  28. package/src/app/favicon.ico +0 -0
  29. package/src/app/layout.tsx +0 -22
  30. package/src/app/sitemap.ts +0 -26
  31. package/src/components/accent-picker.tsx +0 -62
  32. package/src/components/auth-slot.tsx +0 -96
  33. package/src/components/footer-wrapper.tsx +0 -8
  34. package/src/components/footer.tsx +0 -193
  35. package/src/components/hero-scene.tsx +0 -948
  36. package/src/components/hero-section.tsx +0 -57
  37. package/src/components/locale-switcher.tsx +0 -74
  38. package/src/components/logo.tsx +0 -51
  39. package/src/components/nav-server-wrapper.tsx +0 -22
  40. package/src/components/nav-wrapper.tsx +0 -45
  41. package/src/components/nav.tsx +0 -550
  42. package/src/components/paraglider-cursor.tsx +0 -112
  43. package/src/components/project-layout.tsx +0 -194
  44. package/src/components/theme-provider.tsx +0 -10
  45. package/src/components/theme-toggle-icon.tsx +0 -62
  46. package/src/components/theme-toggle.tsx +0 -39
  47. package/src/components/typography.tsx +0 -27
  48. package/src/components/ui/avatar.tsx +0 -109
  49. package/src/components/ui/badge.tsx +0 -47
  50. package/src/components/ui/button.tsx +0 -65
  51. package/src/components/ui/card.tsx +0 -106
  52. package/src/components/ui/dialog.tsx +0 -158
  53. package/src/components/ui/dropdown-menu.tsx +0 -257
  54. package/src/components/ui/input.tsx +0 -21
  55. package/src/components/ui/sonner.tsx +0 -54
  56. package/src/components/workflow-diagram.tsx +0 -414
  57. package/src/i18n/config.ts +0 -8
  58. package/src/i18n/request.ts +0 -16
  59. package/src/i18n/routing.ts +0 -12
  60. package/src/lib/supabase/client.ts +0 -18
  61. package/src/lib/supabase/middleware.ts +0 -42
  62. package/src/lib/supabase/server.ts +0 -37
  63. package/src/lib/utils.ts +0 -6
  64. package/src/proxy.ts +0 -62
  65. package/src/tokens/.gitkeep +0 -0
  66. package/src/tokens/color.json +0 -56
  67. package/src/tokens/radius.json +0 -9
  68. package/src/tokens/semantic.json +0 -81
  69. package/src/tokens/shadow.json +0 -7
  70. package/src/tokens/spacing.json +0 -16
  71. package/src/tokens/typography.json +0 -34
  72. package/src/types/i18n.d.ts +0 -6
  73. package/src/utils/.gitkeep +0 -0
@@ -1,414 +0,0 @@
1
- "use client";
2
-
3
- import { useState, useRef, useCallback, useEffect } from "react";
4
-
5
- /* ------------------------------------------------------------------ */
6
- /* Types */
7
- /* ------------------------------------------------------------------ */
8
-
9
- export interface WorkflowNode {
10
- id: string;
11
- label: string;
12
- type: "source" | "process" | "storage" | "output" | "ai";
13
- /** Position in % (0–100) relative to container */
14
- position: { x: number; y: number };
15
- detail: {
16
- description: string;
17
- tech: string[];
18
- meta?: string;
19
- };
20
- }
21
-
22
- export interface WorkflowEdge {
23
- from: string;
24
- to: string;
25
- label?: string;
26
- animated?: boolean;
27
- }
28
-
29
- export interface WorkflowDiagramProps {
30
- nodes: WorkflowNode[];
31
- edges: WorkflowEdge[];
32
- accent: string;
33
- }
34
-
35
- /* ------------------------------------------------------------------ */
36
- /* Helpers */
37
- /* ------------------------------------------------------------------ */
38
-
39
- /** Nodes connected to the given node (any direction). */
40
- function connectedIds(nodeId: string, edges: WorkflowEdge[]): Set<string> {
41
- const ids = new Set<string>();
42
- ids.add(nodeId);
43
- for (const e of edges) {
44
- if (e.from === nodeId) ids.add(e.to);
45
- if (e.to === nodeId) ids.add(e.from);
46
- }
47
- return ids;
48
- }
49
-
50
- /** Build a cubic‑bezier `d` attribute (vertical flow). */
51
- function edgePath(
52
- x1: number,
53
- y1: number,
54
- x2: number,
55
- y2: number,
56
- ): string {
57
- const midY = (y1 + y2) / 2;
58
- return `M ${x1} ${y1} C ${x1} ${midY}, ${x2} ${midY}, ${x2} ${y2}`;
59
- }
60
-
61
- /* ------------------------------------------------------------------ */
62
- /* Sub‑components */
63
- /* ------------------------------------------------------------------ */
64
-
65
- function Tooltip({
66
- node,
67
- accent,
68
- containerRect,
69
- nodeRect,
70
- }: {
71
- node: WorkflowNode;
72
- accent: string;
73
- containerRect: DOMRect | null;
74
- nodeRect: DOMRect | null;
75
- }) {
76
- if (!containerRect || !nodeRect) return null;
77
-
78
- const tooltipWidth = 240;
79
- const gap = 12;
80
-
81
- // Position relative to container
82
- const nodeCenter = nodeRect.left + nodeRect.width / 2 - containerRect.left;
83
- const nodeBottom = nodeRect.bottom - containerRect.top + gap;
84
-
85
- // Keep within container bounds
86
- let left = nodeCenter - tooltipWidth / 2;
87
- if (left < 8) left = 8;
88
- if (left + tooltipWidth > containerRect.width - 8)
89
- left = containerRect.width - tooltipWidth - 8;
90
-
91
- return (
92
- <div
93
- className="pointer-events-none absolute z-20 w-[240px] rounded-lg border border-border bg-popover p-3 shadow-md transition-all duration-150"
94
- style={{ left, top: nodeBottom }}
95
- >
96
- <p className="mb-2 text-[13px] leading-snug text-foreground">
97
- {node.detail.description}
98
- </p>
99
- <div className="flex flex-wrap gap-1">
100
- {node.detail.tech.map((t) => (
101
- <span
102
- key={t}
103
- className="rounded px-1.5 py-0.5 font-mono text-[10px]"
104
- style={{
105
- background: `color-mix(in oklch, ${accent} 10%, transparent)`,
106
- color: accent,
107
- }}
108
- >
109
- {t}
110
- </span>
111
- ))}
112
- </div>
113
- {node.detail.meta && (
114
- <p className="mt-2 font-mono text-[10px] text-muted-foreground">
115
- {node.detail.meta}
116
- </p>
117
- )}
118
- </div>
119
- );
120
- }
121
-
122
- /* ------------------------------------------------------------------ */
123
- /* Mobile fallback — vertical pipeline */
124
- /* ------------------------------------------------------------------ */
125
-
126
- function MobilePipeline({
127
- nodes,
128
- edges,
129
- accent,
130
- }: WorkflowDiagramProps) {
131
- const [expanded, setExpanded] = useState<string | null>(null);
132
-
133
- // Topological-ish order: sort by y position
134
- const sorted = [...nodes].sort((a, b) => a.position.y - b.position.y);
135
-
136
- return (
137
- <div className="flex flex-col gap-0">
138
- {sorted.map((node, i) => {
139
- const isOpen = expanded === node.id;
140
- return (
141
- <div key={node.id} className="flex items-start gap-4">
142
- {/* Marker */}
143
- <div className="flex flex-col items-center">
144
- <div
145
- className="h-2.5 w-2.5 shrink-0 rounded-full border-2"
146
- style={{
147
- borderColor: accent,
148
- background:
149
- node.type === "ai" || node.type === "output"
150
- ? accent
151
- : "transparent",
152
- }}
153
- />
154
- {i < sorted.length - 1 && (
155
- <div className="w-px flex-1 min-h-[32px] bg-border" />
156
- )}
157
- </div>
158
-
159
- {/* Content */}
160
- <button
161
- type="button"
162
- className="mb-4 flex-1 text-left"
163
- onClick={() => setExpanded(isOpen ? null : node.id)}
164
- >
165
- <p className="text-[13px] font-medium text-foreground">
166
- {node.label}
167
- </p>
168
- {isOpen && (
169
- <div className="mt-2 rounded-lg border border-border bg-popover p-3">
170
- <p className="mb-2 text-[12px] leading-relaxed text-muted-foreground">
171
- {node.detail.description}
172
- </p>
173
- <div className="flex flex-wrap gap-1">
174
- {node.detail.tech.map((t) => (
175
- <span
176
- key={t}
177
- className="rounded px-1.5 py-0.5 font-mono text-[10px]"
178
- style={{
179
- background: `color-mix(in oklch, ${accent} 10%, transparent)`,
180
- color: accent,
181
- }}
182
- >
183
- {t}
184
- </span>
185
- ))}
186
- </div>
187
- {node.detail.meta && (
188
- <p className="mt-1.5 font-mono text-[10px] text-muted-foreground">
189
- {node.detail.meta}
190
- </p>
191
- )}
192
- </div>
193
- )}
194
- </button>
195
- </div>
196
- );
197
- })}
198
- </div>
199
- );
200
- }
201
-
202
- /* ------------------------------------------------------------------ */
203
- /* Main diagram (desktop) */
204
- /* ------------------------------------------------------------------ */
205
-
206
- export function WorkflowDiagram({
207
- nodes,
208
- edges,
209
- accent,
210
- }: WorkflowDiagramProps) {
211
- const containerRef = useRef<HTMLDivElement>(null);
212
- const nodeRefs = useRef<Map<string, HTMLDivElement>>(new Map());
213
- const [hovered, setHovered] = useState<string | null>(null);
214
- const [positions, setPositions] = useState<
215
- Map<string, { cx: number; cy: number }>
216
- >(new Map());
217
- const [containerRect, setContainerRect] = useState<DOMRect | null>(null);
218
- const [hoveredRect, setHoveredRect] = useState<DOMRect | null>(null);
219
-
220
- // Compute pixel centres after layout
221
- const measure = useCallback(() => {
222
- const cEl = containerRef.current;
223
- if (!cEl) return;
224
- const cRect = cEl.getBoundingClientRect();
225
- setContainerRect(cRect);
226
- const map = new Map<string, { cx: number; cy: number }>();
227
- nodeRefs.current.forEach((el, id) => {
228
- const r = el.getBoundingClientRect();
229
- map.set(id, {
230
- cx: r.left + r.width / 2 - cRect.left,
231
- cy: r.top + r.height / 2 - cRect.top,
232
- });
233
- });
234
- setPositions(map);
235
- }, []);
236
-
237
- useEffect(() => {
238
- measure();
239
- window.addEventListener("resize", measure);
240
- return () => window.removeEventListener("resize", measure);
241
- }, [measure]);
242
-
243
- // Re‑measure once after first paint (node refs populated)
244
- useEffect(() => {
245
- const t = setTimeout(measure, 50);
246
- return () => clearTimeout(t);
247
- }, [measure]);
248
-
249
- const connected = hovered ? connectedIds(hovered, edges) : null;
250
-
251
- const handleHover = (id: string | null) => {
252
- setHovered(id);
253
- if (id) {
254
- const el = nodeRefs.current.get(id);
255
- if (el) setHoveredRect(el.getBoundingClientRect());
256
- } else {
257
- setHoveredRect(null);
258
- }
259
- };
260
-
261
- const hoveredNode = hovered
262
- ? nodes.find((n) => n.id === hovered) ?? null
263
- : null;
264
-
265
- // Container height based on node Y spread
266
- const maxY = Math.max(...nodes.map((n) => n.position.y));
267
- const containerHeight = Math.max(400, (maxY / 100) * 600 + 60);
268
-
269
- return (
270
- <>
271
- {/* Desktop */}
272
- <div
273
- ref={containerRef}
274
- className="relative hidden md:block"
275
- style={{ height: containerHeight }}
276
- onMouseLeave={() => handleHover(null)}
277
- >
278
- {/* SVG edges */}
279
- <svg
280
- className="pointer-events-none absolute inset-0 h-full w-full"
281
- aria-hidden="true"
282
- >
283
- <defs>
284
- <marker
285
- id="arrowhead"
286
- markerWidth="6"
287
- markerHeight="5"
288
- refX="5"
289
- refY="2.5"
290
- orient="auto"
291
- >
292
- <polygon
293
- points="0 0, 6 2.5, 0 5"
294
- fill={`color-mix(in oklch, ${accent} 30%, transparent)`}
295
- />
296
- </marker>
297
- </defs>
298
- {edges.map((edge) => {
299
- const from = positions.get(edge.from);
300
- const to = positions.get(edge.to);
301
- if (!from || !to) return null;
302
-
303
- const isConnected =
304
- !connected ||
305
- (connected.has(edge.from) && connected.has(edge.to));
306
- const isDirectlyConnected =
307
- hovered === edge.from || hovered === edge.to;
308
-
309
- return (
310
- <g key={`${edge.from}-${edge.to}`}>
311
- <path
312
- d={edgePath(from.cx, from.cy, to.cx, to.cy)}
313
- fill="none"
314
- stroke={accent}
315
- strokeWidth={isDirectlyConnected ? 2 : 1.5}
316
- strokeOpacity={
317
- !connected ? 0.15 : isConnected ? 0.4 : 0.05
318
- }
319
- markerEnd="url(#arrowhead)"
320
- className="transition-all duration-200"
321
- strokeDasharray={edge.animated ? "6 4" : undefined}
322
- >
323
- {edge.animated && (
324
- <animate
325
- attributeName="stroke-dashoffset"
326
- from="0"
327
- to="-20"
328
- dur="1.5s"
329
- repeatCount="indefinite"
330
- />
331
- )}
332
- </path>
333
- {edge.label && (
334
- <text
335
- x={(from.cx + to.cx) / 2}
336
- y={(from.cy + to.cy) / 2 - 6}
337
- textAnchor="middle"
338
- className="fill-muted-foreground text-[9px] font-mono"
339
- opacity={!connected ? 0.4 : isConnected ? 0.7 : 0.1}
340
- >
341
- {edge.label}
342
- </text>
343
- )}
344
- </g>
345
- );
346
- })}
347
- </svg>
348
-
349
- {/* Nodes */}
350
- {nodes.map((node) => {
351
- const isDim = connected && !connected.has(node.id);
352
- const isActive = hovered === node.id;
353
-
354
- return (
355
- <div
356
- key={node.id}
357
- ref={(el) => {
358
- if (el) nodeRefs.current.set(node.id, el);
359
- }}
360
- className="absolute -translate-x-1/2 -translate-y-1/2 cursor-default select-none rounded-lg border px-4 py-2.5 font-mono text-[13px] font-medium transition-all duration-200"
361
- style={{
362
- left: `${node.position.x}%`,
363
- top: `${node.position.y}%`,
364
- borderColor: isDim
365
- ? "var(--border)"
366
- : node.type === "ai"
367
- ? `color-mix(in oklch, ${accent} ${isActive ? "50%" : "25%"}, transparent)`
368
- : node.type === "output"
369
- ? `color-mix(in oklch, ${accent} ${isActive ? "40%" : "18%"}, transparent)`
370
- : "var(--border)",
371
- background: isDim
372
- ? "var(--background)"
373
- : node.type === "ai"
374
- ? `color-mix(in oklch, ${accent} ${isActive ? "12%" : "6%"}, transparent)`
375
- : "var(--background)",
376
- opacity: isDim ? 0.3 : 1,
377
- transform: `translate(-50%, -50%) ${isActive ? "scale(1.04)" : "scale(1)"}`,
378
- zIndex: isActive ? 10 : 1,
379
- }}
380
- onMouseEnter={() => handleHover(node.id)}
381
- role="button"
382
- tabIndex={0}
383
- aria-label={`${node.label}: ${node.detail.description}`}
384
- onFocus={() => handleHover(node.id)}
385
- onBlur={() => handleHover(null)}
386
- onKeyDown={(e) => {
387
- if (e.key === "Enter" || e.key === " ") {
388
- handleHover(hovered === node.id ? null : node.id);
389
- }
390
- }}
391
- >
392
- {node.label}
393
- </div>
394
- );
395
- })}
396
-
397
- {/* Tooltip */}
398
- {hoveredNode && (
399
- <Tooltip
400
- node={hoveredNode}
401
- accent={accent}
402
- containerRect={containerRect}
403
- nodeRect={hoveredRect}
404
- />
405
- )}
406
- </div>
407
-
408
- {/* Mobile */}
409
- <div className="md:hidden">
410
- <MobilePipeline nodes={nodes} edges={edges} accent={accent} />
411
- </div>
412
- </>
413
- );
414
- }
@@ -1,8 +0,0 @@
1
- export const locales = ["en", "ko", "ja"] as const;
2
- export type Locale = (typeof locales)[number];
3
- export const defaultLocale: Locale = "en";
4
- export const localeNames: Record<Locale, string> = {
5
- en: "English",
6
- ko: "한국어",
7
- ja: "日本語",
8
- };
@@ -1,16 +0,0 @@
1
- import { getRequestConfig } from "next-intl/server";
2
- import { routing } from "./routing";
3
-
4
- export default getRequestConfig(async ({ requestLocale }) => {
5
- let locale = await requestLocale;
6
- if (
7
- !locale ||
8
- !routing.locales.includes(locale as (typeof routing.locales)[number])
9
- ) {
10
- locale = routing.defaultLocale;
11
- }
12
- return {
13
- locale,
14
- messages: (await import(`../../messages/${locale}.json`)).default,
15
- };
16
- });
@@ -1,12 +0,0 @@
1
- import { defineRouting } from "next-intl/routing";
2
- import { createNavigation } from "next-intl/navigation";
3
- import { locales, defaultLocale } from "./config";
4
-
5
- export const routing = defineRouting({
6
- locales,
7
- defaultLocale,
8
- localePrefix: "as-needed",
9
- });
10
-
11
- export const { Link, redirect, usePathname, useRouter, getPathname } =
12
- createNavigation(routing);
@@ -1,18 +0,0 @@
1
- import { createBrowserClient } from "@supabase/ssr";
2
-
3
- const COOKIE_DOMAIN =
4
- typeof window !== "undefined" && window.location.hostname.endsWith(".naeil.dev")
5
- ? ".naeil.dev"
6
- : undefined;
7
-
8
- export function createClient() {
9
- return createBrowserClient(
10
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
11
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
12
- {
13
- cookieOptions: {
14
- domain: COOKIE_DOMAIN,
15
- },
16
- },
17
- );
18
- }
@@ -1,42 +0,0 @@
1
- import { createServerClient } from "@supabase/ssr";
2
- import { type NextRequest, NextResponse } from "next/server";
3
-
4
- function getCookieDomain(request: NextRequest): string | undefined {
5
- const host = request.headers.get("host") || "";
6
- return host.endsWith(".naeil.dev") || host === "naeil.dev"
7
- ? ".naeil.dev"
8
- : undefined;
9
- }
10
-
11
- export async function updateSession(request: NextRequest) {
12
- let supabaseResponse = NextResponse.next({ request });
13
- const domain = getCookieDomain(request);
14
-
15
- const supabase = createServerClient(
16
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
17
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
18
- {
19
- cookies: {
20
- getAll() {
21
- return request.cookies.getAll();
22
- },
23
- setAll(cookiesToSet) {
24
- cookiesToSet.forEach(({ name, value }) =>
25
- request.cookies.set(name, value),
26
- );
27
- supabaseResponse = NextResponse.next({ request });
28
- cookiesToSet.forEach(({ name, value, options }) =>
29
- supabaseResponse.cookies.set(name, value, { ...options, domain }),
30
- );
31
- },
32
- },
33
- },
34
- );
35
-
36
- // Refresh the session — this calls Supabase Auth to validate/refresh tokens
37
- const {
38
- data: { user },
39
- } = await supabase.auth.getUser();
40
-
41
- return { response: supabaseResponse, user };
42
- }
@@ -1,37 +0,0 @@
1
- import { createServerClient } from "@supabase/ssr";
2
- import { cookies, headers } from "next/headers";
3
-
4
- function getCookieDomain(headerStore: Headers): string | undefined {
5
- const host = headerStore.get("host") || "";
6
- return host.endsWith(".naeil.dev") || host === "naeil.dev"
7
- ? ".naeil.dev"
8
- : undefined;
9
- }
10
-
11
- export async function createClient() {
12
- const cookieStore = await cookies();
13
- const headerStore = await headers();
14
- const domain = getCookieDomain(headerStore);
15
-
16
- return createServerClient(
17
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
18
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
19
- {
20
- cookies: {
21
- getAll() {
22
- return cookieStore.getAll();
23
- },
24
- setAll(cookiesToSet) {
25
- try {
26
- cookiesToSet.forEach(({ name, value, options }) =>
27
- cookieStore.set(name, value, { ...options, domain }),
28
- );
29
- } catch {
30
- // The `setAll` method was called from a Server Component.
31
- // This can be ignored if you have middleware refreshing sessions.
32
- }
33
- },
34
- },
35
- },
36
- );
37
- }
package/src/lib/utils.ts DELETED
@@ -1,6 +0,0 @@
1
- import { clsx, type ClassValue } from "clsx"
2
- import { twMerge } from "tailwind-merge"
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
6
- }
package/src/proxy.ts DELETED
@@ -1,62 +0,0 @@
1
- import { type NextRequest, NextResponse } from "next/server";
2
- import createIntlMiddleware from "next-intl/middleware";
3
- import { routing } from "./i18n/routing";
4
- import { updateSession } from "./lib/supabase/middleware";
5
-
6
- const intlMiddleware = createIntlMiddleware(routing);
7
-
8
- // Matches /sa/reports/<id> with optional locale prefix, but NOT /sa/reports
9
- const PROTECTED_ROUTE = /^\/(?:(?:en|ko|ja)\/)?sa\/reports\/[^/]+/;
10
-
11
- export default async function proxy(request: NextRequest) {
12
- // 1. Refresh Supabase session (updates auth cookies)
13
- const { response: supabaseResponse, user } = await updateSession(request);
14
-
15
- // 2. Capture URL info BEFORE intl middleware (which may mutate request.nextUrl)
16
- const { pathname } = request.nextUrl;
17
- const nextParam = request.nextUrl.searchParams.get("next");
18
-
19
- // 3. Check protected routes — redirect to login if unauthenticated
20
- if (PROTECTED_ROUTE.test(pathname) && !user) {
21
- const loginUrl = request.nextUrl.clone();
22
- loginUrl.pathname = "/login";
23
- loginUrl.searchParams.set("next", pathname);
24
- return NextResponse.redirect(loginUrl);
25
- }
26
-
27
- // 4. Run next-intl middleware for locale routing
28
- const intlResponse = intlMiddleware(request);
29
-
30
- // 5. If visiting /login with a ?next param, store it in a cookie for post-OAuth redirect
31
- // Skip if next points back to /login itself (RSC refetch sends next=/login which overwrites the real value)
32
- const loginMatch = pathname.match(/^\/(?:(?:en|ko|ja)\/)?login$/);
33
- const isLoginSelf = nextParam?.replace(/^\/(?:en|ko|ja)\//, '/') === '/login';
34
- if (loginMatch && nextParam && !isLoginSelf) {
35
- intlResponse.cookies.set("auth_redirect_to", nextParam, {
36
- path: "/",
37
- maxAge: 600,
38
- httpOnly: true,
39
- sameSite: "lax",
40
- });
41
- }
42
-
43
- // 6. Copy Supabase auth cookies onto the intl response
44
- supabaseResponse.cookies.getAll().forEach((cookie) => {
45
- intlResponse.cookies.set(cookie.name, cookie.value);
46
- });
47
-
48
- return intlResponse;
49
- }
50
-
51
- export const config = {
52
- matcher: [
53
- /*
54
- * Match all paths except:
55
- * - _next (Next.js internals)
56
- * - Static files (images, fonts, etc.)
57
- * - favicon.ico
58
- * - auth/callback (handled by route handler, not locale-prefixed)
59
- */
60
- "/((?!_next|auth/|.*\\..*|favicon\\.ico).*)",
61
- ],
62
- };
File without changes
@@ -1,56 +0,0 @@
1
- {
2
- "color": {
3
- "$description": "Primitive color palette — OKLCH color space",
4
- "gray": {
5
- "$description": "Pure neutral gray scale (chroma 0). Dark-first: 950=background, 50=text direction.",
6
- "50": { "$type": "color", "$value": "oklch(0.985 0 0)" },
7
- "100": { "$type": "color", "$value": "oklch(0.970 0 0)" },
8
- "200": { "$type": "color", "$value": "oklch(0.922 0 0)" },
9
- "300": { "$type": "color", "$value": "oklch(0.870 0 0)" },
10
- "400": { "$type": "color", "$value": "oklch(0.708 0 0)" },
11
- "500": { "$type": "color", "$value": "oklch(0.556 0 0)" },
12
- "600": { "$type": "color", "$value": "oklch(0.439 0 0)" },
13
- "700": { "$type": "color", "$value": "oklch(0.371 0 0)" },
14
- "800": { "$type": "color", "$value": "oklch(0.269 0 0)" },
15
- "900": { "$type": "color", "$value": "oklch(0.205 0 0)" },
16
- "950": { "$type": "color", "$value": "oklch(0.145 0 0)" }
17
- },
18
- "accent": {
19
- "$description": "Neutral blue accent scale. Default values — consumer projects may override.",
20
- "50": { "$type": "color", "$value": "oklch(0.970 0.014 254)" },
21
- "100": { "$type": "color", "$value": "oklch(0.932 0.032 255)" },
22
- "200": { "$type": "color", "$value": "oklch(0.882 0.059 254)" },
23
- "300": { "$type": "color", "$value": "oklch(0.809 0.105 254)" },
24
- "400": { "$type": "color", "$value": "oklch(0.707 0.165 254)" },
25
- "500": { "$type": "color", "$value": "oklch(0.623 0.214 259)" },
26
- "600": { "$type": "color", "$value": "oklch(0.546 0.245 263)" },
27
- "700": { "$type": "color", "$value": "oklch(0.488 0.243 264.376)" },
28
- "800": { "$type": "color", "$value": "oklch(0.424 0.199 265)" },
29
- "900": { "$type": "color", "$value": "oklch(0.379 0.146 265)" },
30
- "950": { "$type": "color", "$value": "oklch(0.282 0.091 267)" }
31
- },
32
- "destructive": {
33
- "$description": "Red scale for destructive/error states.",
34
- "50": { "$type": "color", "$value": "oklch(0.971 0.013 17)" },
35
- "100": { "$type": "color", "$value": "oklch(0.936 0.032 17)" },
36
- "200": { "$type": "color", "$value": "oklch(0.885 0.062 18)" },
37
- "300": { "$type": "color", "$value": "oklch(0.808 0.114 19)" },
38
- "400": { "$type": "color", "$value": "oklch(0.704 0.191 22.216)" },
39
- "500": { "$type": "color", "$value": "oklch(0.637 0.237 25)" },
40
- "600": { "$type": "color", "$value": "oklch(0.577 0.245 27.325)" },
41
- "700": { "$type": "color", "$value": "oklch(0.505 0.213 27)" },
42
- "800": { "$type": "color", "$value": "oklch(0.444 0.177 26)" },
43
- "900": { "$type": "color", "$value": "oklch(0.396 0.141 25)" },
44
- "950": { "$type": "color", "$value": "oklch(0.258 0.092 26)" }
45
- },
46
- "project": {
47
- "$description": "Per-project accent colors — theme-independent, used across light/dark.",
48
- "cc": { "$type": "color", "$value": "oklch(0.723 0.219 149)" },
49
- "pkm": { "$type": "color", "$value": "oklch(0.627 0.265 303)" },
50
- "naeilUi": { "$type": "color", "$value": "oklch(0.623 0.214 259)" },
51
- "baby": { "$type": "color", "$value": "oklch(0.769 0.188 70.08)" }
52
- },
53
- "white": { "$type": "color", "$value": "oklch(1 0 0)" },
54
- "black": { "$type": "color", "$value": "oklch(0 0 0)" }
55
- }
56
- }