@kurly-growth/growthman 0.1.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.
- package/.claude/settings.local.json +10 -0
- package/CLAUDE.md +58 -0
- package/README.md +23 -0
- package/app/api/endpoints/[id]/route.ts +63 -0
- package/app/api/endpoints/bulk/route.ts +23 -0
- package/app/api/endpoints/import/route.ts +52 -0
- package/app/api/endpoints/route.ts +39 -0
- package/app/api/endpoints/sync/route.ts +80 -0
- package/app/api/mock/[...slug]/route.ts +82 -0
- package/app/favicon.ico +0 -0
- package/app/globals.css +125 -0
- package/app/layout.tsx +36 -0
- package/app/page.tsx +184 -0
- package/bin/cli.js +28 -0
- package/components/endpoint-edit-dialog.tsx +181 -0
- package/components/endpoint-table.tsx +133 -0
- package/components/openapi-upload-dialog.tsx +196 -0
- package/components/ui/button.tsx +62 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/dialog.tsx +143 -0
- package/components/ui/input.tsx +21 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/sonner.tsx +37 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/textarea.tsx +18 -0
- package/components.json +22 -0
- package/eslint.config.mjs +18 -0
- package/lib/openapi-parser.ts +270 -0
- package/lib/prisma.ts +9 -0
- package/lib/utils.ts +6 -0
- package/lib/validation.ts +19 -0
- package/next.config.ts +7 -0
- package/package.json +56 -0
- package/pnpm-workspace.yaml +4 -0
- package/postcss.config.mjs +7 -0
- package/prisma/schema.prisma +23 -0
- package/prisma/seed.ts +29 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/next.svg +1 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/tsconfig.json +34 -0
- package/types/endpoint.ts +10 -0
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
interface OpenAPISpec {
|
|
2
|
+
paths?: Record<string, Record<string, OpenAPIOperation>>
|
|
3
|
+
components?: {
|
|
4
|
+
schemas?: Record<string, OpenAPISchema>
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
interface OpenAPIOperation {
|
|
9
|
+
summary?: string
|
|
10
|
+
description?: string
|
|
11
|
+
responses?: Record<string, OpenAPIResponse>
|
|
12
|
+
requestBody?: {
|
|
13
|
+
content?: Record<string, { schema?: OpenAPISchema; example?: unknown }>
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface OpenAPIResponse {
|
|
18
|
+
description?: string
|
|
19
|
+
content?: Record<string, { schema?: OpenAPISchema; example?: unknown }>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface OpenAPISchema {
|
|
23
|
+
type?: string
|
|
24
|
+
format?: string
|
|
25
|
+
properties?: Record<string, OpenAPISchema>
|
|
26
|
+
items?: OpenAPISchema
|
|
27
|
+
example?: unknown
|
|
28
|
+
default?: unknown
|
|
29
|
+
enum?: unknown[]
|
|
30
|
+
$ref?: string
|
|
31
|
+
allOf?: OpenAPISchema[]
|
|
32
|
+
oneOf?: OpenAPISchema[]
|
|
33
|
+
anyOf?: OpenAPISchema[]
|
|
34
|
+
nullable?: boolean
|
|
35
|
+
required?: string[]
|
|
36
|
+
additionalProperties?: boolean | OpenAPISchema
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface ParsedEndpoint {
|
|
40
|
+
path: string
|
|
41
|
+
method: string
|
|
42
|
+
description: string | null
|
|
43
|
+
statusCode: number
|
|
44
|
+
responseBody: unknown
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function resolveRef(ref: string, spec: OpenAPISpec): OpenAPISchema | null {
|
|
48
|
+
// $ref format: "#/components/schemas/SchemaName"
|
|
49
|
+
const parts = ref.split('/')
|
|
50
|
+
if (parts[0] !== '#' || parts[1] !== 'components' || parts[2] !== 'schemas') {
|
|
51
|
+
return null
|
|
52
|
+
}
|
|
53
|
+
const schemaName = parts[3]
|
|
54
|
+
return spec.components?.schemas?.[schemaName] || null
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function generateMockValue(
|
|
58
|
+
schema: OpenAPISchema,
|
|
59
|
+
spec: OpenAPISpec,
|
|
60
|
+
visited: Set<string> = new Set()
|
|
61
|
+
): unknown {
|
|
62
|
+
// Handle $ref
|
|
63
|
+
if (schema.$ref) {
|
|
64
|
+
// Prevent circular references
|
|
65
|
+
if (visited.has(schema.$ref)) {
|
|
66
|
+
return {}
|
|
67
|
+
}
|
|
68
|
+
visited.add(schema.$ref)
|
|
69
|
+
const resolved = resolveRef(schema.$ref, spec)
|
|
70
|
+
if (resolved) {
|
|
71
|
+
return generateMockValue(resolved, spec, visited)
|
|
72
|
+
}
|
|
73
|
+
return {}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Handle allOf (merge schemas)
|
|
77
|
+
if (schema.allOf && schema.allOf.length > 0) {
|
|
78
|
+
const merged: Record<string, unknown> = {}
|
|
79
|
+
for (const subSchema of schema.allOf) {
|
|
80
|
+
const value = generateMockValue(subSchema, spec, visited)
|
|
81
|
+
if (typeof value === 'object' && value !== null) {
|
|
82
|
+
Object.assign(merged, value)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return merged
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Handle oneOf/anyOf (use first option)
|
|
89
|
+
if (schema.oneOf && schema.oneOf.length > 0) {
|
|
90
|
+
return generateMockValue(schema.oneOf[0], spec, visited)
|
|
91
|
+
}
|
|
92
|
+
if (schema.anyOf && schema.anyOf.length > 0) {
|
|
93
|
+
return generateMockValue(schema.anyOf[0], spec, visited)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Use example or default if available
|
|
97
|
+
if (schema.example !== undefined) return schema.example
|
|
98
|
+
if (schema.default !== undefined) return schema.default
|
|
99
|
+
if (schema.enum && schema.enum.length > 0) return schema.enum[0]
|
|
100
|
+
|
|
101
|
+
// Generate based on type and format
|
|
102
|
+
switch (schema.type) {
|
|
103
|
+
case 'string':
|
|
104
|
+
return generateStringValue(schema.format)
|
|
105
|
+
case 'number':
|
|
106
|
+
return generateNumberValue(schema.format)
|
|
107
|
+
case 'integer':
|
|
108
|
+
return generateIntegerValue(schema.format)
|
|
109
|
+
case 'boolean':
|
|
110
|
+
return true
|
|
111
|
+
case 'array':
|
|
112
|
+
if (schema.items) {
|
|
113
|
+
return [generateMockValue(schema.items, spec, visited)]
|
|
114
|
+
}
|
|
115
|
+
return []
|
|
116
|
+
case 'object':
|
|
117
|
+
return generateObjectValue(schema, spec, visited)
|
|
118
|
+
default:
|
|
119
|
+
// If no type but has properties, treat as object
|
|
120
|
+
if (schema.properties) {
|
|
121
|
+
return generateObjectValue(schema, spec, visited)
|
|
122
|
+
}
|
|
123
|
+
return null
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function generateStringValue(format?: string): string {
|
|
128
|
+
switch (format) {
|
|
129
|
+
case 'date':
|
|
130
|
+
return '2024-01-15'
|
|
131
|
+
case 'date-time':
|
|
132
|
+
return '2024-01-15T09:30:00Z'
|
|
133
|
+
case 'time':
|
|
134
|
+
return '09:30:00'
|
|
135
|
+
case 'email':
|
|
136
|
+
return 'user@example.com'
|
|
137
|
+
case 'uri':
|
|
138
|
+
case 'url':
|
|
139
|
+
return 'https://example.com'
|
|
140
|
+
case 'uuid':
|
|
141
|
+
return '550e8400-e29b-41d4-a716-446655440000'
|
|
142
|
+
case 'hostname':
|
|
143
|
+
return 'example.com'
|
|
144
|
+
case 'ipv4':
|
|
145
|
+
return '192.168.1.1'
|
|
146
|
+
case 'ipv6':
|
|
147
|
+
return '2001:0db8:85a3:0000:0000:8a2e:0370:7334'
|
|
148
|
+
case 'byte':
|
|
149
|
+
return 'SGVsbG8gV29ybGQ='
|
|
150
|
+
case 'binary':
|
|
151
|
+
return '<binary>'
|
|
152
|
+
case 'password':
|
|
153
|
+
return '********'
|
|
154
|
+
case 'phone':
|
|
155
|
+
return '+1-555-555-5555'
|
|
156
|
+
default:
|
|
157
|
+
return 'string'
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function generateNumberValue(format?: string): number {
|
|
162
|
+
switch (format) {
|
|
163
|
+
case 'float':
|
|
164
|
+
return 1.5
|
|
165
|
+
case 'double':
|
|
166
|
+
return 1.23456789
|
|
167
|
+
default:
|
|
168
|
+
return 0
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function generateIntegerValue(format?: string): number {
|
|
173
|
+
switch (format) {
|
|
174
|
+
case 'int32':
|
|
175
|
+
return 123
|
|
176
|
+
case 'int64':
|
|
177
|
+
return 1234567890
|
|
178
|
+
default:
|
|
179
|
+
return 0
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function generateObjectValue(
|
|
184
|
+
schema: OpenAPISchema,
|
|
185
|
+
spec: OpenAPISpec,
|
|
186
|
+
visited: Set<string>
|
|
187
|
+
): Record<string, unknown> {
|
|
188
|
+
const obj: Record<string, unknown> = {}
|
|
189
|
+
|
|
190
|
+
if (schema.properties) {
|
|
191
|
+
for (const [key, propSchema] of Object.entries(schema.properties)) {
|
|
192
|
+
obj[key] = generateMockValue(propSchema, spec, visited)
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return obj
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
export function parseOpenAPISpec(spec: OpenAPISpec): ParsedEndpoint[] {
|
|
200
|
+
const endpoints: ParsedEndpoint[] = []
|
|
201
|
+
|
|
202
|
+
if (!spec.paths) return endpoints
|
|
203
|
+
|
|
204
|
+
for (const [path, methods] of Object.entries(spec.paths)) {
|
|
205
|
+
for (const [method, operation] of Object.entries(methods)) {
|
|
206
|
+
// Skip non-HTTP method properties like 'parameters'
|
|
207
|
+
if (
|
|
208
|
+
!['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(
|
|
209
|
+
method.toLowerCase()
|
|
210
|
+
)
|
|
211
|
+
) {
|
|
212
|
+
continue
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const description = operation.summary || operation.description || null
|
|
216
|
+
|
|
217
|
+
// Find successful response (2xx)
|
|
218
|
+
let statusCode = 200
|
|
219
|
+
let responseBody: unknown = {}
|
|
220
|
+
|
|
221
|
+
if (operation.responses) {
|
|
222
|
+
// Sort response codes to prefer 200, then other 2xx
|
|
223
|
+
const responseCodes = Object.keys(operation.responses).sort((a, b) => {
|
|
224
|
+
if (a === '200') return -1
|
|
225
|
+
if (b === '200') return 1
|
|
226
|
+
return parseInt(a, 10) - parseInt(b, 10)
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
for (const code of responseCodes) {
|
|
230
|
+
const codeNum = parseInt(code, 10)
|
|
231
|
+
if (codeNum >= 200 && codeNum < 300) {
|
|
232
|
+
statusCode = codeNum
|
|
233
|
+
const response = operation.responses[code]
|
|
234
|
+
|
|
235
|
+
if (response.content) {
|
|
236
|
+
// Try application/json first, then any JSON-like content type
|
|
237
|
+
const jsonContent =
|
|
238
|
+
response.content['application/json'] ||
|
|
239
|
+
response.content['application/hal+json'] ||
|
|
240
|
+
response.content['application/vnd.api+json'] ||
|
|
241
|
+
Object.values(response.content).find((c) => c.schema)
|
|
242
|
+
|
|
243
|
+
if (jsonContent) {
|
|
244
|
+
if (jsonContent.example) {
|
|
245
|
+
responseBody = jsonContent.example
|
|
246
|
+
} else if (jsonContent.schema) {
|
|
247
|
+
responseBody = generateMockValue(jsonContent.schema, spec)
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
break
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Convert OpenAPI path params {id} to Express-style :id
|
|
257
|
+
const normalizedPath = path.replace(/\{(\w+)\}/g, ':$1')
|
|
258
|
+
|
|
259
|
+
endpoints.push({
|
|
260
|
+
path: normalizedPath,
|
|
261
|
+
method: method.toUpperCase(),
|
|
262
|
+
description,
|
|
263
|
+
statusCode,
|
|
264
|
+
responseBody,
|
|
265
|
+
})
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return endpoints
|
|
270
|
+
}
|
package/lib/prisma.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client'
|
|
2
|
+
|
|
3
|
+
const globalForPrisma = globalThis as unknown as {
|
|
4
|
+
prisma: PrismaClient | undefined
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export const prisma = globalForPrisma.prisma ?? new PrismaClient()
|
|
8
|
+
|
|
9
|
+
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
|
package/lib/utils.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const VALID_METHODS = ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'HEAD', 'OPTIONS']
|
|
2
|
+
|
|
3
|
+
export function validatePath(path: unknown): path is string {
|
|
4
|
+
if (!path || typeof path !== 'string') return false
|
|
5
|
+
if (!path.startsWith('/')) return false
|
|
6
|
+
if (path.length > 1000) return false
|
|
7
|
+
return true
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function validateMethod(method: unknown): method is string {
|
|
11
|
+
if (typeof method !== 'string') return false
|
|
12
|
+
return VALID_METHODS.includes(method.toUpperCase())
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validateStatusCode(statusCode: unknown): statusCode is number {
|
|
16
|
+
if (statusCode === undefined || statusCode === null) return true // optional
|
|
17
|
+
if (typeof statusCode !== 'number') return false
|
|
18
|
+
return statusCode >= 100 && statusCode < 600
|
|
19
|
+
}
|
package/next.config.ts
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kurly-growth/growthman",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Local mock API server with UI dashboard",
|
|
5
|
+
"bin": {
|
|
6
|
+
"growthman": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"prepack": "next build",
|
|
13
|
+
"postinstall": "prisma generate && prisma db push",
|
|
14
|
+
"dev": "next dev",
|
|
15
|
+
"dev:clean": "npx prisma db seed && next dev",
|
|
16
|
+
"build": "next build",
|
|
17
|
+
"start": "next start",
|
|
18
|
+
"lint": "eslint",
|
|
19
|
+
"db:seed": "npx prisma db seed",
|
|
20
|
+
"db:reset": "rm -f prisma/dev.db && npx prisma db push && npx prisma db seed"
|
|
21
|
+
},
|
|
22
|
+
"prisma": {
|
|
23
|
+
"seed": "npx tsx prisma/seed.ts"
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@monaco-editor/react": "^4.7.0",
|
|
27
|
+
"@prisma/client": "^6.19.1",
|
|
28
|
+
"@radix-ui/react-checkbox": "^1.3.3",
|
|
29
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
30
|
+
"@radix-ui/react-label": "^2.1.8",
|
|
31
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
32
|
+
"class-variance-authority": "^0.7.1",
|
|
33
|
+
"clsx": "^2.1.1",
|
|
34
|
+
"lucide-react": "^0.562.0",
|
|
35
|
+
"next": "16.1.1",
|
|
36
|
+
"next-themes": "^0.4.6",
|
|
37
|
+
"path-to-regexp": "^8.3.0",
|
|
38
|
+
"prisma": "^6.19.1",
|
|
39
|
+
"react": "19.2.3",
|
|
40
|
+
"react-dom": "19.2.3",
|
|
41
|
+
"sonner": "^2.0.7",
|
|
42
|
+
"tailwind-merge": "^3.4.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@tailwindcss/postcss": "^4",
|
|
46
|
+
"@types/node": "^20",
|
|
47
|
+
"@types/react": "^19",
|
|
48
|
+
"@types/react-dom": "^19",
|
|
49
|
+
"eslint": "^9",
|
|
50
|
+
"eslint-config-next": "16.1.1",
|
|
51
|
+
"tailwindcss": "^4",
|
|
52
|
+
"tsx": "^4.21.0",
|
|
53
|
+
"tw-animate-css": "^1.4.0",
|
|
54
|
+
"typescript": "^5"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
generator client {
|
|
2
|
+
provider = "prisma-client-js"
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
datasource db {
|
|
6
|
+
provider = "sqlite"
|
|
7
|
+
url = "file:./dev.db"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
model MockEndpoint {
|
|
11
|
+
id String @id @default(cuid())
|
|
12
|
+
path String // 예: /v1/missions
|
|
13
|
+
method String // GET, POST, PUT, DELETE 등
|
|
14
|
+
description String? // API 설명
|
|
15
|
+
statusCode Int @default(200) // 응답할 상태 코드
|
|
16
|
+
responseBody Json // 내려줄 가짜 데이터
|
|
17
|
+
|
|
18
|
+
createdAt DateTime @default(now())
|
|
19
|
+
updatedAt DateTime @updatedAt
|
|
20
|
+
|
|
21
|
+
@@unique([path, method]) // path와 method 조합이 유일해야 함
|
|
22
|
+
@@map("mock_endpoints") // 테이블 이름을 snake_case로 매핑
|
|
23
|
+
}
|
package/prisma/seed.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { PrismaClient } from '@prisma/client'
|
|
2
|
+
|
|
3
|
+
const prisma = new PrismaClient()
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
// 기존 데이터 삭제
|
|
7
|
+
await prisma.mockEndpoint.deleteMany()
|
|
8
|
+
console.log('Cleared all mock endpoints')
|
|
9
|
+
|
|
10
|
+
// 샘플 데이터 추가 (선택사항)
|
|
11
|
+
// await prisma.mockEndpoint.create({
|
|
12
|
+
// data: {
|
|
13
|
+
// path: '/v1/health',
|
|
14
|
+
// method: 'GET',
|
|
15
|
+
// description: 'Health check',
|
|
16
|
+
// statusCode: 200,
|
|
17
|
+
// responseBody: { status: 'ok' },
|
|
18
|
+
// },
|
|
19
|
+
// })
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
main()
|
|
23
|
+
.catch((e) => {
|
|
24
|
+
console.error(e)
|
|
25
|
+
process.exit(1)
|
|
26
|
+
})
|
|
27
|
+
.finally(async () => {
|
|
28
|
+
await prisma.$disconnect()
|
|
29
|
+
})
|
package/public/file.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
package/public/globe.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
package/public/next.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2017",
|
|
4
|
+
"lib": ["dom", "dom.iterable", "esnext"],
|
|
5
|
+
"allowJs": true,
|
|
6
|
+
"skipLibCheck": true,
|
|
7
|
+
"strict": true,
|
|
8
|
+
"noEmit": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"module": "esnext",
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"jsx": "react-jsx",
|
|
15
|
+
"incremental": true,
|
|
16
|
+
"plugins": [
|
|
17
|
+
{
|
|
18
|
+
"name": "next"
|
|
19
|
+
}
|
|
20
|
+
],
|
|
21
|
+
"paths": {
|
|
22
|
+
"@/*": ["./*"]
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"next-env.d.ts",
|
|
27
|
+
"**/*.ts",
|
|
28
|
+
"**/*.tsx",
|
|
29
|
+
".next/types/**/*.ts",
|
|
30
|
+
".next/dev/types/**/*.ts",
|
|
31
|
+
"**/*.mts"
|
|
32
|
+
],
|
|
33
|
+
"exclude": ["node_modules"]
|
|
34
|
+
}
|