create-aron-app 0.1.0 → 0.1.1
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.
- package/package.json +5 -2
- package/templates/_base/.cursor/agents/skills/clerk/SKILL.md +89 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/SKILL.md +142 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +30 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +88 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +165 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +208 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +14 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/SKILL.md +157 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +224 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +190 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +314 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +259 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +125 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +94 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +50 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +56 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +68 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +56 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +104 -0
- package/templates/_base/.cursor/agents/skills/clerk/clerk-webhooks/SKILL.md +131 -0
- package/templates/_base/.cursor/agents/skills/shadcn/SKILL.md +241 -0
- package/templates/_base/.cursor/agents/skills/shadcn/agents/openai.yml +5 -0
- package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn-small.png +0 -0
- package/templates/_base/.cursor/agents/skills/shadcn/assets/shadcn.png +0 -0
- package/templates/_base/.cursor/agents/skills/shadcn/cli.md +257 -0
- package/templates/_base/.cursor/agents/skills/shadcn/customization.md +202 -0
- package/templates/_base/.cursor/agents/skills/shadcn/evals/evals.json +47 -0
- package/templates/_base/.cursor/agents/skills/shadcn/mcp.md +94 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/composition.md +195 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/forms.md +192 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/icons.md +101 -0
- package/templates/_base/.cursor/agents/skills/shadcn/rules/styling.md +162 -0
- package/templates/_base/.cursor/commands/builder.md +0 -0
- package/templates/_base/.cursor/commands/pr.md +7 -0
- package/templates/_base/.cursor/rules/api_architecture.mdc +268 -0
- package/templates/_base/.cursor/rules/coding_standards.mdc +64 -0
- package/templates/_base/.cursor/rules/convex_rules.mdc +675 -0
- package/templates/_base/.cursor/rules/frontend_rules.mdc +268 -0
- package/templates/_base/.env.convex.example +3 -0
- package/templates/_base/.github/workflows/ci.yml +29 -0
- package/templates/_base/.nvmrc +1 -0
- package/templates/_base/.vscode/settings.json +9 -0
- package/templates/_base/apps/api/auth.config.ts +18 -0
- package/templates/_base/apps/api/functions.ts +99 -0
- package/templates/_base/apps/api/project.json +22 -0
- package/templates/_base/apps/api/schema.ts +11 -0
- package/templates/_base/apps/api/todos/crud.ts +81 -0
- package/templates/_base/apps/api/todos/schema.ts +11 -0
- package/templates/_base/apps/api/todos/types.ts +22 -0
- package/templates/_base/apps/api/tsconfig.json +23 -0
- package/templates/_base/apps/api/types.ts +16 -0
- package/templates/_base/biome.json +114 -0
- package/templates/_base/convex.json +4 -0
- package/templates/_base/emails/project.json +16 -0
- package/templates/_base/emails/tsconfig.json +5 -0
- package/templates/_base/emails/welcome_email.tsx +53 -0
- package/templates/_base/nx.json +29 -0
- package/templates/_base/package.json +73 -0
- package/templates/_base/scripts/sync_convex_env.ts +63 -0
- package/templates/_base/shared/assets/image.d.ts +4 -0
- package/templates/_base/shared/assets/src/styles/global.css +73 -0
- package/templates/_base/shared/assets/tsconfig.json +5 -0
- package/templates/_base/shared/ui/src/base/alert_dialog.tsx +139 -0
- package/templates/_base/shared/ui/src/base/badge.tsx +33 -0
- package/templates/_base/shared/ui/src/base/basic_data_table.tsx +61 -0
- package/templates/_base/shared/ui/src/base/button.tsx +69 -0
- package/templates/_base/shared/ui/src/base/button_group.tsx +82 -0
- package/templates/_base/shared/ui/src/base/card.tsx +79 -0
- package/templates/_base/shared/ui/src/base/checkbox.tsx +26 -0
- package/templates/_base/shared/ui/src/base/command.tsx +165 -0
- package/templates/_base/shared/ui/src/base/dialog.tsx +129 -0
- package/templates/_base/shared/ui/src/base/dropdown_menu.tsx +232 -0
- package/templates/_base/shared/ui/src/base/form.tsx +161 -0
- package/templates/_base/shared/ui/src/base/input.tsx +129 -0
- package/templates/_base/shared/ui/src/base/label.tsx +19 -0
- package/templates/_base/shared/ui/src/base/popover.tsx +46 -0
- package/templates/_base/shared/ui/src/base/radio_group.tsx +49 -0
- package/templates/_base/shared/ui/src/base/resizable.tsx +55 -0
- package/templates/_base/shared/ui/src/base/scroll_area.tsx +44 -0
- package/templates/_base/shared/ui/src/base/select.tsx +151 -0
- package/templates/_base/shared/ui/src/base/separator.tsx +32 -0
- package/templates/_base/shared/ui/src/base/sheet.tsx +130 -0
- package/templates/_base/shared/ui/src/base/side_bar.tsx +688 -0
- package/templates/_base/shared/ui/src/base/skeleton.tsx +7 -0
- package/templates/_base/shared/ui/src/base/spinner.tsx +20 -0
- package/templates/_base/shared/ui/src/base/switch.tsx +27 -0
- package/templates/_base/shared/ui/src/base/table.tsx +91 -0
- package/templates/_base/shared/ui/src/base/text_area.tsx +21 -0
- package/templates/_base/shared/ui/src/base/tooltip.tsx +31 -0
- package/templates/_base/shared/ui/src/base/utils.ts +17 -0
- package/templates/_base/shared/ui/src/hooks/use_keyboard_press.tsx +48 -0
- package/templates/_base/shared/ui/src/hooks/use_keyboard_release.tsx +48 -0
- package/templates/_base/shared/ui/src/hooks/use_mobile.tsx +25 -0
- package/templates/_base/shared/ui/src/hooks/use_mouse_click.tsx +44 -0
- package/templates/_base/shared/ui/src/hooks/use_mouse_location.tsx +55 -0
- package/templates/_base/shared/ui/src/hooks/use_outside_click.tsx +29 -0
- package/templates/_base/shared/ui/src/hooks/use_query_params.tsx +33 -0
- package/templates/_base/shared/ui/tsconfig.json +8 -0
- package/templates/_base/shared/utils/src/convex.ts +3 -0
- package/templates/_base/shared/utils/src/time.ts +12 -0
- package/templates/_base/shared/utils/tsconfig.json +5 -0
- package/templates/_base/skills-lock.json +35 -0
- package/templates/_base/tsconfig.base.json +34 -0
- package/templates/nextjs/.env.example +8 -0
- package/templates/nextjs/index.d.ts +6 -0
- package/templates/nextjs/next-env.d.ts +5 -0
- package/templates/nextjs/next.config.js +22 -0
- package/templates/nextjs/postcss.config.js +17 -0
- package/templates/nextjs/project.json +22 -0
- package/templates/nextjs/src/app/(auth)/layout.tsx +21 -0
- package/templates/nextjs/src/app/(auth)/not-allowed/page.tsx +22 -0
- package/templates/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +15 -0
- package/templates/nextjs/src/app/(dashboard)/layout.tsx +27 -0
- package/templates/nextjs/src/app/(dashboard)/page.tsx +5 -0
- package/templates/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +23 -0
- package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +16 -0
- package/templates/nextjs/src/app/app.css +3 -0
- package/templates/nextjs/src/app/layout.tsx +26 -0
- package/templates/nextjs/src/convex.ts +11 -0
- package/templates/nextjs/src/middleware.ts +18 -0
- package/templates/nextjs/src/providers/convex_provider.tsx +44 -0
- package/templates/nextjs/src/surfaces/home_surface.tsx +22 -0
- package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +97 -0
- package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +107 -0
- package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +90 -0
- package/templates/nextjs/src/ui/sidebar/nav_link.tsx +36 -0
- package/templates/nextjs/src/ui/sidebar/sidebar.tsx +125 -0
- package/templates/nextjs/src/utils/font.ts +9 -0
- package/templates/nextjs/tsconfig.json +42 -0
- package/templates/react-router/.env.example +8 -0
- package/templates/react-router/postcss.config.js +15 -0
- package/templates/react-router/project.json +23 -0
- package/templates/react-router/public/favicon.ico +0 -0
- package/templates/react-router/react-router.config.ts +9 -0
- package/templates/react-router/src/app.css +3 -0
- package/templates/react-router/src/components/error_boundary.tsx +33 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +76 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/user_menu.tsx +36 -0
- package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +22 -0
- package/templates/react-router/src/providers/api_auth_provider.tsx +38 -0
- package/templates/react-router/src/root.tsx +37 -0
- package/templates/react-router/src/routes/auth/layout.tsx +13 -0
- package/templates/react-router/src/routes/auth/sign-in.tsx +13 -0
- package/templates/react-router/src/routes/index.tsx +9 -0
- package/templates/react-router/src/routes/layout.tsx +26 -0
- package/templates/react-router/src/routes/todos/[id].tsx +22 -0
- package/templates/react-router/src/routes/todos/index.tsx +13 -0
- package/templates/react-router/src/routes.ts +12 -0
- package/templates/react-router/src/surfaces/home_surface.tsx +20 -0
- package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +87 -0
- package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +102 -0
- package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +81 -0
- package/templates/react-router/tsconfig.json +20 -0
- package/templates/react-router/vite.config.ts +40 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# extract-tag-endpoints.sh
|
|
3
|
+
#
|
|
4
|
+
# Extracts all endpoints for a given tag from an OpenAPI YAML spec (stdin),
|
|
5
|
+
# along with any $ref'd schemas/components.
|
|
6
|
+
#
|
|
7
|
+
# Usage:
|
|
8
|
+
# curl -s <spec-url> | bash extract-tag-endpoints.sh "Billing"
|
|
9
|
+
|
|
10
|
+
set -euo pipefail
|
|
11
|
+
|
|
12
|
+
TAG="${1:?Usage: extract-tag-endpoints.sh <tag-name>}"
|
|
13
|
+
TMPDIR_WORK=$(mktemp -d)
|
|
14
|
+
trap 'rm -rf "$TMPDIR_WORK"' EXIT
|
|
15
|
+
|
|
16
|
+
SPEC="$TMPDIR_WORK/spec.yml"
|
|
17
|
+
cat > "$SPEC"
|
|
18
|
+
|
|
19
|
+
# 1. Find all path+method blocks that have a matching tag
|
|
20
|
+
# Strategy: find line numbers of path entries (lines starting with " /"),
|
|
21
|
+
# then for each method block under that path, check if it contains the tag.
|
|
22
|
+
|
|
23
|
+
node - "$TAG" "$SPEC" <<'SCRIPT'
|
|
24
|
+
const fs = require("fs");
|
|
25
|
+
const tag = process.argv[2];
|
|
26
|
+
const specFile = process.argv[3];
|
|
27
|
+
const lines = fs.readFileSync(specFile, "utf8").split("\n");
|
|
28
|
+
|
|
29
|
+
const tagLower = tag.toLowerCase();
|
|
30
|
+
|
|
31
|
+
// Phase 1: Find all path definitions and their method blocks
|
|
32
|
+
// Paths start at indent 2 with " /"
|
|
33
|
+
// Methods start at indent 4 with " get:", " post:", etc.
|
|
34
|
+
const methods = ["get", "post", "put", "patch", "delete", "options", "head"];
|
|
35
|
+
const endpoints = [];
|
|
36
|
+
const refs = new Set();
|
|
37
|
+
|
|
38
|
+
let currentPath = null;
|
|
39
|
+
let currentMethod = null;
|
|
40
|
+
let blockStart = -1;
|
|
41
|
+
let blockLines = [];
|
|
42
|
+
let inPaths = false;
|
|
43
|
+
let inComponents = false;
|
|
44
|
+
|
|
45
|
+
// First pass: locate the "paths:" and "components:" top-level keys
|
|
46
|
+
let pathsStart = -1;
|
|
47
|
+
let pathsEnd = -1;
|
|
48
|
+
let componentsStart = -1;
|
|
49
|
+
|
|
50
|
+
for (let i = 0; i < lines.length; i++) {
|
|
51
|
+
const line = lines[i];
|
|
52
|
+
if (/^paths:\s*$/.test(line)) {
|
|
53
|
+
pathsStart = i;
|
|
54
|
+
} else if (pathsStart >= 0 && pathsEnd < 0 && /^\S/.test(line) && i > pathsStart) {
|
|
55
|
+
pathsEnd = i;
|
|
56
|
+
}
|
|
57
|
+
if (/^components:\s*$/.test(line)) {
|
|
58
|
+
componentsStart = i;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
if (pathsEnd < 0) pathsEnd = lines.length;
|
|
62
|
+
|
|
63
|
+
// Second pass: extract endpoints matching the tag
|
|
64
|
+
function flushBlock() {
|
|
65
|
+
if (!currentPath || !currentMethod || blockLines.length === 0) return;
|
|
66
|
+
|
|
67
|
+
// Check if this block has the target tag
|
|
68
|
+
let inTags = false;
|
|
69
|
+
let hasTag = false;
|
|
70
|
+
const blockRefs = [];
|
|
71
|
+
|
|
72
|
+
for (const bl of blockLines) {
|
|
73
|
+
const trimmed = bl.trim();
|
|
74
|
+
|
|
75
|
+
// Detect tags section
|
|
76
|
+
if (/^tags:\s*$/.test(trimmed)) {
|
|
77
|
+
inTags = true;
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
if (inTags) {
|
|
81
|
+
if (/^- /.test(trimmed)) {
|
|
82
|
+
const tagVal = trimmed.replace(/^- /, "").trim().replace(/^['"]|['"]$/g, "");
|
|
83
|
+
if (tagVal.toLowerCase() === tagLower) hasTag = true;
|
|
84
|
+
} else {
|
|
85
|
+
inTags = false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Collect $ref values
|
|
90
|
+
const refMatch = bl.match(/\$ref:\s*['"]?(#\/[^'"}\s]+)['"]?/);
|
|
91
|
+
if (refMatch) blockRefs.push(refMatch[1]);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (hasTag) {
|
|
95
|
+
// Extract summary, operationId, description
|
|
96
|
+
let summary = "";
|
|
97
|
+
let operationId = "";
|
|
98
|
+
let description = "";
|
|
99
|
+
let params = [];
|
|
100
|
+
let inDesc = false;
|
|
101
|
+
let inParams = false;
|
|
102
|
+
|
|
103
|
+
for (const bl of blockLines) {
|
|
104
|
+
const trimmed = bl.trim();
|
|
105
|
+
const indent = bl.length - bl.trimStart().length;
|
|
106
|
+
|
|
107
|
+
// Only capture operation-level keys (indent 6 = direct children of the method block)
|
|
108
|
+
if (indent === 6) {
|
|
109
|
+
const sumMatch = trimmed.match(/^summary:\s*(.+)/);
|
|
110
|
+
if (sumMatch) summary = sumMatch[1].replace(/^['"]|['"]$/g, "");
|
|
111
|
+
|
|
112
|
+
const opMatch = trimmed.match(/^operationId:\s*(.+)/);
|
|
113
|
+
if (opMatch) operationId = opMatch[1].replace(/^['"]|['"]$/g, "");
|
|
114
|
+
|
|
115
|
+
const descMatch = trimmed.match(/^description:\s*(.+)/);
|
|
116
|
+
if (descMatch && !inDesc) {
|
|
117
|
+
const val = descMatch[1].trim();
|
|
118
|
+
if (val === "|-" || val === "|" || val === ">-" || val === ">") {
|
|
119
|
+
inDesc = true;
|
|
120
|
+
} else {
|
|
121
|
+
description = val.replace(/^['"]|['"]$/g, "");
|
|
122
|
+
}
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (inDesc) {
|
|
128
|
+
// Continuation lines of description — grab first non-empty line
|
|
129
|
+
if (!description && trimmed.length > 0) {
|
|
130
|
+
description = trimmed;
|
|
131
|
+
}
|
|
132
|
+
// Stop when we hit the next operation-level key
|
|
133
|
+
if (indent === 6 && trimmed.length > 0 && !/^description:/.test(trimmed)) {
|
|
134
|
+
inDesc = false;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
endpoints.push({
|
|
140
|
+
method: currentMethod.toUpperCase(),
|
|
141
|
+
path: currentPath,
|
|
142
|
+
operationId,
|
|
143
|
+
summary,
|
|
144
|
+
description,
|
|
145
|
+
refs: blockRefs,
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
for (const r of blockRefs) refs.add(r);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (let i = pathsStart + 1; i < pathsEnd; i++) {
|
|
153
|
+
const line = lines[i];
|
|
154
|
+
|
|
155
|
+
// Path line: exactly 2 spaces + /
|
|
156
|
+
if (/^ {2}\/\S/.test(line)) {
|
|
157
|
+
flushBlock();
|
|
158
|
+
currentPath = line.trim().replace(/:$/, "");
|
|
159
|
+
currentMethod = null;
|
|
160
|
+
blockLines = [];
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Method line: exactly 4 spaces + method name
|
|
165
|
+
const methodMatch = line.match(/^ {4}(\w+):\s*$/);
|
|
166
|
+
if (methodMatch && methods.includes(methodMatch[1])) {
|
|
167
|
+
flushBlock();
|
|
168
|
+
currentMethod = methodMatch[1];
|
|
169
|
+
blockLines = [];
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (currentMethod) {
|
|
174
|
+
blockLines.push(line);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
flushBlock();
|
|
178
|
+
|
|
179
|
+
// Output endpoints
|
|
180
|
+
if (endpoints.length === 0) {
|
|
181
|
+
console.error(`No endpoints found for tag: "${tag}"`);
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
console.log(`## Endpoints for "${tag}" (${endpoints.length} total)\n`);
|
|
186
|
+
for (const ep of endpoints) {
|
|
187
|
+
console.log(`### \`${ep.method}\` \`${ep.path}\``);
|
|
188
|
+
if (ep.operationId) console.log(`- **operationId**: \`${ep.operationId}\``);
|
|
189
|
+
if (ep.summary) console.log(`- **summary**: ${ep.summary}`);
|
|
190
|
+
if (ep.description && ep.description !== ep.summary)
|
|
191
|
+
console.log(`- **description**: ${ep.description}`);
|
|
192
|
+
if (ep.refs.length > 0) {
|
|
193
|
+
console.log(`- **refs**: ${ep.refs.map(r => "\`" + r.split("/").pop() + "\`").join(", ")}`);
|
|
194
|
+
}
|
|
195
|
+
console.log();
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Output unique refs list
|
|
199
|
+
if (refs.size > 0) {
|
|
200
|
+
console.log(`## Referenced Components (${refs.size} unique)\n`);
|
|
201
|
+
const sorted = [...refs].sort();
|
|
202
|
+
for (const r of sorted) {
|
|
203
|
+
const name = r.split("/").pop();
|
|
204
|
+
const category = r.split("/").slice(0, -1).join("/").replace("#/", "");
|
|
205
|
+
console.log(`- \`${name}\` (${category})`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
SCRIPT
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
let input = "";
|
|
2
|
+
process.stdin.on("data", d => input += d);
|
|
3
|
+
process.stdin.on("end", () => {
|
|
4
|
+
const lines = input.replace(/\r/g, "").split("\n");
|
|
5
|
+
let inTags = false;
|
|
6
|
+
for (const line of lines) {
|
|
7
|
+
if (line === "tags:") { inTags = true; continue; }
|
|
8
|
+
if (inTags && line.length > 0 && line[0] !== " ") break;
|
|
9
|
+
if (inTags) {
|
|
10
|
+
const m = line.match(/^\s{2}- name:\s*(.+)/);
|
|
11
|
+
if (m) console.log(m[1]);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: clerk-custom-ui
|
|
3
|
+
description: Custom authentication flows and component appearance - hooks (useSignIn, useSignUp), themes, colors, fonts, CSS. Use for custom sign-in/sign-up flows, appearance styling, visual customization, branding.
|
|
4
|
+
allowed-tools: WebFetch
|
|
5
|
+
license: MIT
|
|
6
|
+
metadata:
|
|
7
|
+
author: clerk
|
|
8
|
+
version: "2.1.0"
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Custom UI
|
|
12
|
+
|
|
13
|
+
> **Prerequisite**: Ensure `ClerkProvider` wraps your app. See `setup/`.
|
|
14
|
+
>
|
|
15
|
+
> **Version**: Check `package.json` for the SDK version — see `clerk` skill for the version table. This determines which custom flow references to use below.
|
|
16
|
+
|
|
17
|
+
This skill covers two areas:
|
|
18
|
+
1. **Custom authentication flows** — build your own sign-in/sign-up UI with hooks
|
|
19
|
+
2. **Appearance customization** — theme, style, and brand Clerk's pre-built components
|
|
20
|
+
|
|
21
|
+
## Custom Flow References
|
|
22
|
+
|
|
23
|
+
| Task | Core 2 | Current |
|
|
24
|
+
|------|--------|---------|
|
|
25
|
+
| Custom sign-in (useSignIn) | `core-2/custom-sign-in.md` | `core-3/custom-sign-in.md` |
|
|
26
|
+
| Custom sign-up (useSignUp) | `core-2/custom-sign-up.md` | `core-3/custom-sign-up.md` |
|
|
27
|
+
| `<Show>` component | *(use `<SignedIn>`, `<SignedOut>`, `<Protect>`)* | `core-3/show-component.md` |
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Appearance Customization
|
|
32
|
+
|
|
33
|
+
Appearance customization applies to both Core 2 and the current SDK.
|
|
34
|
+
|
|
35
|
+
### Component Customization Options
|
|
36
|
+
|
|
37
|
+
| Task | Documentation |
|
|
38
|
+
|------|---------------|
|
|
39
|
+
| Appearance prop overview | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/overview |
|
|
40
|
+
| Options (structure, logo, buttons) | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/layout |
|
|
41
|
+
| Themes (pre-built dark/light) | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/themes |
|
|
42
|
+
| Variables (colors, fonts, spacing) | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/variables |
|
|
43
|
+
| CAPTCHA configuration | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/captcha |
|
|
44
|
+
| Bring your own CSS | https://clerk.com/docs/nextjs/guides/customizing-clerk/appearance-prop/bring-your-own-css |
|
|
45
|
+
|
|
46
|
+
### Appearance Pattern
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
<SignIn
|
|
50
|
+
appearance={{
|
|
51
|
+
variables: {
|
|
52
|
+
colorPrimary: '#0000ff',
|
|
53
|
+
borderRadius: '0.5rem',
|
|
54
|
+
},
|
|
55
|
+
options: {
|
|
56
|
+
logoImageUrl: '/logo.png',
|
|
57
|
+
socialButtonsVariant: 'iconButton',
|
|
58
|
+
},
|
|
59
|
+
}}
|
|
60
|
+
/>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> **Core 2 ONLY (skip if current SDK):** The `options` property was named `layout`. Use `layout: { logoImageUrl: '...', socialButtonsVariant: '...' }` instead of `options`.
|
|
64
|
+
|
|
65
|
+
### variables (colors, typography, borders)
|
|
66
|
+
|
|
67
|
+
| Property | Description |
|
|
68
|
+
|----------|-------------|
|
|
69
|
+
| `colorPrimary` | Primary color throughout |
|
|
70
|
+
| `colorBackground` | Background color |
|
|
71
|
+
| `borderRadius` | Border radius (default: `0.375rem`) |
|
|
72
|
+
|
|
73
|
+
**Opacity change:** `colorRing` and `colorModalBackdrop` now render at full opacity. Use explicit `rgba()` values if you need transparency.
|
|
74
|
+
|
|
75
|
+
> **Core 2 ONLY (skip if current SDK):** `colorRing` and `colorModalBackdrop` rendered at 15% opacity by default.
|
|
76
|
+
|
|
77
|
+
### options (structure, logo, social buttons)
|
|
78
|
+
|
|
79
|
+
| Property | Description |
|
|
80
|
+
|----------|-------------|
|
|
81
|
+
| `logoImageUrl` | URL to custom logo |
|
|
82
|
+
| `socialButtonsVariant` | `'blockButton'` \| `'iconButton'` \| `'auto'` |
|
|
83
|
+
| `socialButtonsPlacement` | `'top'` \| `'bottom'` |
|
|
84
|
+
| `showOptionalFields` | Show optional fields (default: `false`) |
|
|
85
|
+
|
|
86
|
+
> **Core 2 ONLY (skip if current SDK):** This property is called `layout`, not `options`. Also, `showOptionalFields` defaulted to `true`.
|
|
87
|
+
|
|
88
|
+
### Themes
|
|
89
|
+
|
|
90
|
+
Install themes from `@clerk/ui`:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
npm install @clerk/ui
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { dark } from '@clerk/ui/themes'
|
|
98
|
+
|
|
99
|
+
<ClerkProvider appearance={{ theme: dark }} />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
> **Core 2 ONLY (skip if current SDK):** Themes are imported from `@clerk/themes` (install `@clerk/themes` instead of `@clerk/ui`). Example: `import { dark } from '@clerk/themes'`.
|
|
103
|
+
|
|
104
|
+
**Theme stacking** — pass an array where the last theme takes precedence:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
import { dark, neobrutalism } from '@clerk/ui/themes'
|
|
108
|
+
|
|
109
|
+
<ClerkProvider appearance={{ theme: [dark, neobrutalism] }} />
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
**CSS `color-scheme` support** — the default theme respects the CSS `color-scheme` property for automatic light/dark mode toggling.
|
|
113
|
+
|
|
114
|
+
**Available themes:** `dark`, `neobrutalism`, `shadcn`, `simple`
|
|
115
|
+
|
|
116
|
+
#### shadcn Theme (HIGH PRIORITY)
|
|
117
|
+
|
|
118
|
+
> **IMPORTANT:** If the project has `components.json` (shadcn/ui installed), ALWAYS apply the shadcn theme as the FIRST customization step. This ensures Clerk components visually match the existing shadcn design system. Skip this only if the user explicitly requests a different theme.
|
|
119
|
+
|
|
120
|
+
**If the project uses shadcn/ui** (check for `components.json` in the project root), **always use the shadcn theme**:
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
import { shadcn } from '@clerk/ui/themes'
|
|
124
|
+
|
|
125
|
+
<ClerkProvider appearance={{ theme: shadcn }} />
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Also import shadcn CSS in your global styles:
|
|
129
|
+
```css
|
|
130
|
+
@import 'tailwindcss';
|
|
131
|
+
@import '@clerk/ui/themes/shadcn.css';
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
> **Core 2 ONLY (skip if current SDK):** Import from `@clerk/themes` and `@clerk/themes/shadcn.css`:
|
|
135
|
+
> ```typescript
|
|
136
|
+
> import { shadcn } from '@clerk/themes'
|
|
137
|
+
> ```
|
|
138
|
+
> ```css
|
|
139
|
+
> @import '@clerk/themes/shadcn.css';
|
|
140
|
+
> ```
|
|
141
|
+
|
|
142
|
+
## Workflow
|
|
143
|
+
|
|
144
|
+
1. Identify customization needs (custom flow or appearance)
|
|
145
|
+
2. For custom flows: check SDK version → read appropriate `core-2/` or `core-3/` reference
|
|
146
|
+
3. For appearance: WebFetch the appropriate documentation from table above
|
|
147
|
+
4. Apply appearance prop to your Clerk components or build custom flow with hooks
|
|
148
|
+
|
|
149
|
+
## Common Pitfalls
|
|
150
|
+
|
|
151
|
+
| Issue | Solution |
|
|
152
|
+
|-------|----------|
|
|
153
|
+
| Colors not applying | Use `colorPrimary` not `primaryColor` |
|
|
154
|
+
| Logo not showing | Put `logoImageUrl` inside `options: {}` (or `layout: {}` in Core 2) |
|
|
155
|
+
| Social buttons wrong | Add `socialButtonsVariant: 'iconButton'` in `options` (or `layout` in Core 2) |
|
|
156
|
+
| Styling not working | Use appearance prop, not direct CSS (unless with bring-your-own-css) |
|
|
157
|
+
| Hook returns different shape | Check SDK version — Core 2 and current have completely different `useSignIn`/`useSignUp` APIs |
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Custom Sign-In Flow (Core 2)
|
|
2
|
+
|
|
3
|
+
> This document covers the **older SDK** (`@clerk/nextjs` v5–v6, `@clerk/clerk-react` v5–v6, `@clerk/clerk-expo` v1–v2). For the current SDK, see `core-3/custom-sign-in.md`.
|
|
4
|
+
|
|
5
|
+
Build a custom sign-in experience using the `useSignIn()` hook.
|
|
6
|
+
|
|
7
|
+
## Hook API
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { useSignIn } from '@clerk/nextjs' // or @clerk/clerk-react, @clerk/clerk-expo
|
|
11
|
+
|
|
12
|
+
const { signIn, isLoaded, setActive } = useSignIn()
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
| Property | Type | Description |
|
|
16
|
+
|----------|------|-------------|
|
|
17
|
+
| `signIn` | `SignIn` | Sign-in object with methods |
|
|
18
|
+
| `isLoaded` | `boolean` | Whether the hook has loaded |
|
|
19
|
+
| `setActive` | `(params) => Promise` | Sets the active session |
|
|
20
|
+
|
|
21
|
+
## Sign-In Flow
|
|
22
|
+
|
|
23
|
+
### 1. Create Sign-In
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
const result = await signIn.create({
|
|
27
|
+
identifier: 'user@example.com',
|
|
28
|
+
password: 'securePassword123',
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 2. First Factor Verification
|
|
33
|
+
|
|
34
|
+
If additional verification is needed (email code, phone code):
|
|
35
|
+
|
|
36
|
+
```typescript
|
|
37
|
+
// Prepare first factor
|
|
38
|
+
await signIn.prepareFirstFactor({
|
|
39
|
+
strategy: 'email_code', // or 'phone_code'
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
// Attempt first factor
|
|
43
|
+
const result = await signIn.attemptFirstFactor({
|
|
44
|
+
strategy: 'email_code',
|
|
45
|
+
code: '123456',
|
|
46
|
+
})
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. Second Factor (MFA)
|
|
50
|
+
|
|
51
|
+
If the sign-in requires MFA:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
// Prepare second factor
|
|
55
|
+
await signIn.prepareSecondFactor({
|
|
56
|
+
strategy: 'email_code', // or 'phone_code'
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Attempt second factor
|
|
60
|
+
const result = await signIn.attemptSecondFactor({
|
|
61
|
+
strategy: 'totp', // or 'email_code', 'phone_code', 'backup_code'
|
|
62
|
+
code: '123456',
|
|
63
|
+
})
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 4. Finalize
|
|
67
|
+
|
|
68
|
+
Set the active session after successful authentication:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
await setActive({ session: signIn.createdSessionId })
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Password Reset
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
// 1. Start reset flow
|
|
78
|
+
await signIn.create({ strategy: 'reset_password_email_code', identifier: 'user@example.com' })
|
|
79
|
+
|
|
80
|
+
// or prepare after initial create:
|
|
81
|
+
await signIn.prepareFirstFactor({ strategy: 'reset_password_email_code' })
|
|
82
|
+
|
|
83
|
+
// 2. Verify reset code
|
|
84
|
+
await signIn.attemptFirstFactor({ strategy: 'reset_password_email_code', code: '123456' })
|
|
85
|
+
|
|
86
|
+
// 3. Set new password
|
|
87
|
+
await signIn.resetPassword({ password: 'newSecurePassword123' })
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### SSO (OAuth)
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
await signIn.authenticateWithRedirect({
|
|
94
|
+
strategy: 'oauth_google', // or 'oauth_github', etc.
|
|
95
|
+
redirectUrl: '/sso-callback',
|
|
96
|
+
redirectUrlComplete: '/',
|
|
97
|
+
})
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Error Handling
|
|
101
|
+
|
|
102
|
+
Use try/catch with `isClerkAPIResponseError()`:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { isClerkAPIResponseError } from '@clerk/nextjs/errors'
|
|
106
|
+
|
|
107
|
+
try {
|
|
108
|
+
await signIn.create({ identifier, password })
|
|
109
|
+
} catch (err) {
|
|
110
|
+
if (isClerkAPIResponseError(err)) {
|
|
111
|
+
err.errors.forEach((e) => {
|
|
112
|
+
console.log(e.code) // e.g. 'form_identifier_not_found'
|
|
113
|
+
console.log(e.message) // Human-readable message
|
|
114
|
+
console.log(e.longMessage) // Detailed message
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Complete Example: Email/Password with MFA
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
'use client'
|
|
124
|
+
import { useState } from 'react'
|
|
125
|
+
import { useSignIn } from '@clerk/nextjs'
|
|
126
|
+
import { isClerkAPIResponseError } from '@clerk/nextjs/errors'
|
|
127
|
+
import { useRouter } from 'next/navigation'
|
|
128
|
+
|
|
129
|
+
export default function SignInPage() {
|
|
130
|
+
const { signIn, isLoaded, setActive } = useSignIn()
|
|
131
|
+
const router = useRouter()
|
|
132
|
+
|
|
133
|
+
const [identifier, setIdentifier] = useState('')
|
|
134
|
+
const [password, setPassword] = useState('')
|
|
135
|
+
const [mfaCode, setMfaCode] = useState('')
|
|
136
|
+
const [step, setStep] = useState<'credentials' | 'mfa'>('credentials')
|
|
137
|
+
const [error, setError] = useState('')
|
|
138
|
+
|
|
139
|
+
if (!isLoaded) return <div>Loading...</div>
|
|
140
|
+
|
|
141
|
+
async function handleSignIn(e: React.FormEvent) {
|
|
142
|
+
e.preventDefault()
|
|
143
|
+
setError('')
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const result = await signIn.create({ identifier, password })
|
|
147
|
+
|
|
148
|
+
if (result.status === 'needs_second_factor') {
|
|
149
|
+
setStep('mfa')
|
|
150
|
+
return
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (result.status === 'complete') {
|
|
154
|
+
await setActive({ session: result.createdSessionId })
|
|
155
|
+
router.push('/')
|
|
156
|
+
}
|
|
157
|
+
} catch (err) {
|
|
158
|
+
if (isClerkAPIResponseError(err)) {
|
|
159
|
+
setError(err.errors[0]?.message || 'Sign in failed')
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function handleMFA(e: React.FormEvent) {
|
|
165
|
+
e.preventDefault()
|
|
166
|
+
setError('')
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const result = await signIn.attemptSecondFactor({
|
|
170
|
+
strategy: 'totp',
|
|
171
|
+
code: mfaCode,
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
if (result.status === 'complete') {
|
|
175
|
+
await setActive({ session: result.createdSessionId })
|
|
176
|
+
router.push('/')
|
|
177
|
+
}
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (isClerkAPIResponseError(err)) {
|
|
180
|
+
setError(err.errors[0]?.message || 'Verification failed')
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (step === 'mfa') {
|
|
186
|
+
return (
|
|
187
|
+
<form onSubmit={handleMFA}>
|
|
188
|
+
<input
|
|
189
|
+
type="text"
|
|
190
|
+
value={mfaCode}
|
|
191
|
+
onChange={(e) => setMfaCode(e.target.value)}
|
|
192
|
+
placeholder="Enter MFA code"
|
|
193
|
+
/>
|
|
194
|
+
{error && <p>{error}</p>}
|
|
195
|
+
<button type="submit">Verify</button>
|
|
196
|
+
</form>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<form onSubmit={handleSignIn}>
|
|
202
|
+
<input
|
|
203
|
+
type="email"
|
|
204
|
+
value={identifier}
|
|
205
|
+
onChange={(e) => setIdentifier(e.target.value)}
|
|
206
|
+
placeholder="Email"
|
|
207
|
+
/>
|
|
208
|
+
<input
|
|
209
|
+
type="password"
|
|
210
|
+
value={password}
|
|
211
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
212
|
+
placeholder="Password"
|
|
213
|
+
/>
|
|
214
|
+
{error && <p>{error}</p>}
|
|
215
|
+
<button type="submit">Sign In</button>
|
|
216
|
+
</form>
|
|
217
|
+
)
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## Docs
|
|
222
|
+
|
|
223
|
+
- [Custom sign-in flow](https://clerk.com/docs/custom-flows/overview)
|
|
224
|
+
- [useSignIn() reference](https://clerk.com/docs/references/react/use-sign-in)
|