@coinbase/create-cdp-app 0.0.9 → 0.0.11
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/README.md +8 -1
- package/dist/index.js +10 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/template-nextjs/README.md +85 -0
- package/template-nextjs/_gitignore +45 -0
- package/template-nextjs/env.example +3 -0
- package/template-nextjs/eslint.config.mjs +16 -0
- package/template-nextjs/next-env.d.ts +5 -0
- package/template-nextjs/next.config.ts +7 -0
- package/template-nextjs/package.json +28 -0
- package/template-nextjs/public/logo.svg +5 -0
- package/template-nextjs/src/app/globals.css +306 -0
- package/template-nextjs/src/app/icon.svg +5 -0
- package/template-nextjs/src/app/layout.tsx +22 -0
- package/template-nextjs/src/app/page.tsx +16 -0
- package/template-nextjs/src/components/ClientApp.tsx +27 -0
- package/template-nextjs/src/components/Header.tsx +61 -0
- package/template-nextjs/src/components/Icons.tsx +68 -0
- package/template-nextjs/src/components/Loading.tsx +15 -0
- package/template-nextjs/src/components/Providers.tsx +33 -0
- package/template-nextjs/src/components/SignInScreen.tsx +17 -0
- package/template-nextjs/src/components/SignedInScreen.tsx +64 -0
- package/template-nextjs/src/components/Transaction.tsx +128 -0
- package/template-nextjs/src/components/UserBalance.tsx +42 -0
- package/template-nextjs/tsconfig.json +27 -0
- package/{template-react-components → template-react}/README.md +13 -6
- package/{template-react-components → template-react}/index.html +1 -1
- package/template-react/public/eth.svg +25 -0
- package/template-react/public/logo.svg +5 -0
- package/{template-react-components → template-react}/src/Header.tsx +1 -1
- package/template-react/src/config.ts +9 -0
- package/{template-react-components → template-react}/src/main.tsx +2 -2
- package/template-react/src/theme.ts +32 -0
- package/template-react-components/public/vite.svg +0 -1
- package/template-react-components/src/config.ts +0 -3
- /package/{template-react-components → template-nextjs}/public/eth.svg +0 -0
- /package/{template-react-components/src → template-nextjs/src/components}/theme.ts +0 -0
- /package/{template-react-components → template-react}/_gitignore +0 -0
- /package/{template-react-components → template-react}/env.example +0 -0
- /package/{template-react-components → template-react}/eslint.config.js +0 -0
- /package/{template-react-components → template-react}/package.json +0 -0
- /package/{template-react-components → template-react}/src/App.tsx +0 -0
- /package/{template-react-components → template-react}/src/Icons.tsx +0 -0
- /package/{template-react-components → template-react}/src/Loading.tsx +0 -0
- /package/{template-react-components → template-react}/src/SignInScreen.tsx +0 -0
- /package/{template-react-components → template-react}/src/SignedInScreen.tsx +0 -0
- /package/{template-react-components → template-react}/src/Transaction.tsx +0 -0
- /package/{template-react-components → template-react}/src/UserBalance.tsx +0 -0
- /package/{template-react-components → template-react}/src/index.css +0 -0
- /package/{template-react-components → template-react}/src/vite-env.d.ts +0 -0
- /package/{template-react-components → template-react}/tsconfig.app.json +0 -0
- /package/{template-react-components → template-react}/tsconfig.json +0 -0
- /package/{template-react-components → template-react}/tsconfig.node.json +0 -0
- /package/{template-react-components → template-react}/vite.config.ts +0 -0
package/README.md
CHANGED
|
@@ -43,9 +43,16 @@ The CLI will guide you through the setup process:
|
|
|
43
43
|
|
|
44
44
|
Currently, `create-cdp-app` offers the following template:
|
|
45
45
|
|
|
46
|
-
- **React
|
|
46
|
+
- **React** (`react`): A React application template that includes:
|
|
47
47
|
- Vite for fast development and building
|
|
48
48
|
- TypeScript for type safety
|
|
49
49
|
- CDP React components for authentication
|
|
50
50
|
- Example transaction components
|
|
51
51
|
- Base Sepolia integration
|
|
52
|
+
- **Next.js** (`nextjs`): A Next.js application template that includes:
|
|
53
|
+
- Next.js 15 App Router
|
|
54
|
+
- CDP React components for authentication and wallet management
|
|
55
|
+
- Example transaction components for Base Sepolia
|
|
56
|
+
- Built-in TypeScript support
|
|
57
|
+
- ESLint with Next.js configuration
|
|
58
|
+
- Viem for type-safe Ethereum interactions
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
-
import { reset, blue,
|
|
5
|
+
import { reset, blue, green, red } from "kolorist";
|
|
6
6
|
import prompts from "prompts";
|
|
7
7
|
function prepareProjectDirectory(targetDir, shouldOverwrite) {
|
|
8
8
|
const root = path.join(process.cwd(), targetDir);
|
|
@@ -20,11 +20,8 @@ function customizePackageJson(templateDir, projectName) {
|
|
|
20
20
|
function customizeEnv(templateDir, projectId) {
|
|
21
21
|
const exampleEnvPath = path.join(templateDir, "env.example");
|
|
22
22
|
const exampleEnv = fs.readFileSync(exampleEnvPath, "utf-8");
|
|
23
|
-
const envContent = exampleEnv.replace(
|
|
24
|
-
|
|
25
|
-
`VITE_CDP_PROJECT_ID=${projectId}
|
|
26
|
-
`
|
|
27
|
-
);
|
|
23
|
+
const envContent = exampleEnv.replace(/(.*PROJECT_ID=).*(\r?\n|$)/, `$1${projectId}
|
|
24
|
+
`);
|
|
28
25
|
return envContent;
|
|
29
26
|
}
|
|
30
27
|
function copyFile(filePath, destPath) {
|
|
@@ -58,9 +55,14 @@ function detectPackageManager() {
|
|
|
58
55
|
}
|
|
59
56
|
const TEMPLATES = [
|
|
60
57
|
{
|
|
61
|
-
name: "react
|
|
62
|
-
display: "React
|
|
58
|
+
name: "react",
|
|
59
|
+
display: "React",
|
|
63
60
|
color: blue
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "nextjs",
|
|
64
|
+
display: "Next.js",
|
|
65
|
+
color: green
|
|
64
66
|
}
|
|
65
67
|
];
|
|
66
68
|
const defaultTargetDir = "cdp-app";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Prepare the project directory\n *\n * @param targetDir - The target directory for the project\n * @param shouldOverwrite - Whether to overwrite the existing directory\n * @returns The path to the prepared project directory\n */\nexport function prepareProjectDirectory(targetDir: string, shouldOverwrite: boolean): string {\n const root = path.join(process.cwd(), targetDir);\n\n if (shouldOverwrite) {\n emptyDir(root);\n } else if (!fs.existsSync(root)) {\n fs.mkdirSync(root, { recursive: true });\n }\n\n return root;\n}\n\n/**\n * Customize package.json for the new project\n *\n * @param templateDir - The directory containing the template files\n * @param projectName - The name of the project\n * @returns The customized package.json content\n */\nexport function customizePackageJson(templateDir: string, projectName: string): string {\n const packageJsonPath = path.join(templateDir, \"package.json\");\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, \"utf-8\"));\n packageJson.name = projectName;\n return JSON.stringify(packageJson, null, 2) + \"\\n\";\n}\n\n/**\n * Set up the .env file for the new project\n *\n * @param templateDir - The directory containing the template files\n * @param projectId - The project ID\n * @returns The customized .env content\n */\nexport function customizeEnv(templateDir: string, projectId: string): string {\n const exampleEnvPath = path.join(templateDir, \"env.example\");\n const exampleEnv = fs.readFileSync(exampleEnvPath, \"utf-8\");\n const envContent = exampleEnv.replace(\n /VITE_CDP_PROJECT_ID=.*(\\r?\\n|$)/,\n `VITE_CDP_PROJECT_ID=${projectId}\\n`,\n );\n return envContent;\n}\n\n/**\n * Copy a file or directory recursively\n *\n * @param filePath - The source path\n * @param destPath - The destination path\n */\nexport function copyFile(filePath: string, destPath: string): void {\n const stat = fs.statSync(filePath);\n if (stat.isDirectory()) {\n copyDir(filePath, destPath);\n } else {\n fs.copyFileSync(filePath, destPath);\n }\n}\n\n/**\n * Copy a directory recursively\n *\n * @param srcDir - The source directory path\n * @param destDir - The destination directory path\n */\nfunction copyDir(srcDir: string, destDir: string): void {\n fs.mkdirSync(destDir, { recursive: true });\n for (const file of fs.readdirSync(srcDir)) {\n const srcFile = path.resolve(srcDir, file);\n const destFile = path.resolve(destDir, file);\n copyFile(srcFile, destFile);\n }\n}\n\n/**\n * Check if a directory is empty\n *\n * @param dirPath - The path to the directory\n * @returns True if the directory is empty, false otherwise\n */\nexport function isDirEmpty(dirPath: string): boolean {\n const files = fs.readdirSync(dirPath);\n return files.length === 0 || (files.length === 1 && files[0] === \".git\");\n}\n\n/**\n * Empty a directory while preserving .git\n *\n * @param dirPath - The path to the directory\n */\nfunction emptyDir(dirPath: string): void {\n if (!fs.existsSync(dirPath)) {\n return;\n }\n for (const file of fs.readdirSync(dirPath)) {\n if (file === \".git\") {\n continue;\n }\n fs.rmSync(path.resolve(dirPath, file), { recursive: true, force: true });\n }\n}\n\n/**\n * Detect which package manager invoked the create command\n *\n * @returns The detected package manager or 'pnpm' as default\n */\nexport function detectPackageManager(): \"npm\" | \"pnpm\" | \"yarn\" {\n const userAgent = process.env.npm_config_user_agent;\n\n if (userAgent) {\n if (userAgent.startsWith(\"yarn\")) return \"yarn\";\n if (userAgent.startsWith(\"pnpm\")) return \"pnpm\";\n if (userAgent.startsWith(\"npm\")) return \"npm\";\n }\n\n return \"npm\"; // Default to npm if we can't detect\n}\n","#!/usr/bin/env node\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { blue, red, green, reset } from \"kolorist\";\nimport prompts from \"prompts\";\n\nimport {\n prepareProjectDirectory,\n isDirEmpty,\n customizePackageJson,\n copyFile,\n customizeEnv,\n detectPackageManager,\n} from \"./utils.js\";\n\n// Available templates for project creation\nconst TEMPLATES = [\n {\n name: \"react-components\",\n display: \"React Components\",\n color: blue,\n },\n];\n\nconst defaultTargetDir = \"cdp-app\";\n\nconst fileRenames: Record<string, string | undefined> = {\n _gitignore: \".gitignore\",\n};\n\ninterface ProjectOptions {\n projectName: string;\n template: string;\n targetDirectory: string;\n projectId: string;\n}\n\nconst uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Initialize a new CDP app project\n */\nasync function init(): Promise<void> {\n const { projectName, template, targetDirectory, projectId } = await getProjectDetails();\n\n console.log(`\\nScaffolding project in ${targetDirectory}...`);\n\n const root = prepareProjectDirectory(targetDirectory, false);\n const templateDir = path.resolve(fileURLToPath(import.meta.url), \"../..\", `template-${template}`);\n\n copyTemplateFiles(templateDir, root, projectName, projectId);\n printNextSteps(root);\n}\n\n/**\n * Get project details from command line arguments or prompt the user\n *\n * @returns The project details\n */\nasync function getProjectDetails(): Promise<ProjectOptions> {\n // Get target directory from command line args (first non-option argument)\n let targetDir = process.argv[2];\n const defaultProjectName = targetDir ?? defaultTargetDir;\n\n try {\n const result = await prompts(\n [\n {\n type: targetDir ? null : \"text\",\n name: \"projectName\",\n message: reset(\"Project name:\"),\n initial: defaultProjectName,\n onState: state => {\n targetDir = String(state.value).trim() || defaultProjectName;\n },\n },\n {\n type: \"select\",\n name: \"template\",\n message: reset(\"Select a template:\"),\n initial: 0,\n choices: TEMPLATES.map(template => ({\n title: template.color(template.display || template.name),\n value: template.name,\n })),\n },\n {\n type: \"text\",\n name: \"projectId\",\n message: reset(\n \"CDP Project ID (Find your project ID at https://portal.cdp.coinbase.com/projects/overview):\",\n ),\n validate: value => {\n if (!value || !uuidRegex.test(value)) {\n return \"Project ID is required\";\n }\n return true;\n },\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"corsConfirmation\",\n message: reset(\n \"Confirm you have whitelisted 'http://localhost:3000' at https://portal.cdp.coinbase.com/products/embedded-wallets/cors by typing 'y'\",\n ),\n validate: value => {\n if (value !== \"y\") {\n return \"You must whitelist your app domain for your app to be functional.\";\n }\n return true;\n },\n initial: \"\",\n },\n {\n type: () => (!fs.existsSync(targetDir) || isDirEmpty(targetDir) ? null : \"confirm\"),\n name: \"overwrite\",\n message: () =>\n (targetDir === \".\" ? \"Current directory\" : `Target directory \"${targetDir}\"`) +\n \" is not empty. Remove existing files and continue?\",\n },\n {\n type: (_, { overwrite }: { overwrite?: boolean }) => {\n if (overwrite === false) {\n throw new Error(red(\"✖\") + \" Operation cancelled\");\n }\n return null;\n },\n name: \"overwriteChecker\",\n },\n ],\n {\n onCancel: () => {\n throw new Error(red(\"✖\") + \" Operation cancelled\");\n },\n },\n );\n\n return {\n projectName: result.projectName,\n template: result.template,\n targetDirectory: targetDir,\n projectId: result.projectId,\n };\n } catch (cancelled: unknown) {\n if (cancelled instanceof Error) {\n console.log(cancelled.message);\n }\n process.exit(0);\n }\n}\n\n/**\n * Print next steps for the user\n *\n * @param projectRoot - The root directory of the project\n */\nfunction printNextSteps(projectRoot: string): void {\n const packageManager = detectPackageManager();\n\n console.log(green(\"\\nDone. Now run your app:\\n\"));\n if (projectRoot !== process.cwd()) {\n console.log(`cd ${path.relative(process.cwd(), projectRoot)}`);\n }\n const devCommand = packageManager === \"npm\" ? \"npm run dev\" : `${packageManager} dev`;\n console.log(`${packageManager} install`);\n console.log(devCommand);\n}\n\n/**\n * Copy template files to the project directory\n *\n * @param templateDir - The directory containing the template files\n * @param root - The root directory of the project\n * @param projectName - The name of the project\n * @param projectId - The project ID\n */\nfunction copyTemplateFiles(\n templateDir: string,\n root: string,\n projectName: string,\n projectId?: string,\n): void {\n const writeFileToTarget = (file: string, content?: string) => {\n const targetPath = path.join(root, fileRenames[file] ?? file);\n if (content) {\n fs.writeFileSync(targetPath, content);\n } else {\n copyFile(path.join(templateDir, file), targetPath);\n }\n };\n\n const files = fs.readdirSync(templateDir);\n for (const file of files) {\n if (file === \"package.json\") {\n const customizedPackageJson = customizePackageJson(templateDir, projectName);\n writeFileToTarget(file, customizedPackageJson);\n } else if (file === \"env.example\" && projectId) {\n const customizedEnv = customizeEnv(templateDir, projectId);\n writeFileToTarget(file);\n console.log(\"Copying project id to .env\");\n writeFileToTarget(\".env\", customizedEnv);\n } else {\n writeFileToTarget(file);\n }\n }\n}\n\ninit().catch(e => {\n console.error(e);\n process.exit(1);\n});\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils.ts","../src/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\n/**\n * Prepare the project directory\n *\n * @param targetDir - The target directory for the project\n * @param shouldOverwrite - Whether to overwrite the existing directory\n * @returns The path to the prepared project directory\n */\nexport function prepareProjectDirectory(targetDir: string, shouldOverwrite: boolean): string {\n const root = path.join(process.cwd(), targetDir);\n\n if (shouldOverwrite) {\n emptyDir(root);\n } else if (!fs.existsSync(root)) {\n fs.mkdirSync(root, { recursive: true });\n }\n\n return root;\n}\n\n/**\n * Customize package.json for the new project\n *\n * @param templateDir - The directory containing the template files\n * @param projectName - The name of the project\n * @returns The customized package.json content\n */\nexport function customizePackageJson(templateDir: string, projectName: string): string {\n const packageJsonPath = path.join(templateDir, \"package.json\");\n const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, \"utf-8\"));\n packageJson.name = projectName;\n return JSON.stringify(packageJson, null, 2) + \"\\n\";\n}\n\n/**\n * Set up the .env file for the new project\n *\n * @param templateDir - The directory containing the template files\n * @param projectId - The project ID\n * @returns The customized .env content\n */\nexport function customizeEnv(templateDir: string, projectId: string): string {\n const exampleEnvPath = path.join(templateDir, \"env.example\");\n const exampleEnv = fs.readFileSync(exampleEnvPath, \"utf-8\");\n // Replace the project ID in the env file\n const envContent = exampleEnv.replace(/(.*PROJECT_ID=).*(\\r?\\n|$)/, `$1${projectId}\\n`);\n return envContent;\n}\n\n/**\n * Copy a file or directory recursively\n *\n * @param filePath - The source path\n * @param destPath - The destination path\n */\nexport function copyFile(filePath: string, destPath: string): void {\n const stat = fs.statSync(filePath);\n if (stat.isDirectory()) {\n copyDir(filePath, destPath);\n } else {\n fs.copyFileSync(filePath, destPath);\n }\n}\n\n/**\n * Copy a directory recursively\n *\n * @param srcDir - The source directory path\n * @param destDir - The destination directory path\n */\nfunction copyDir(srcDir: string, destDir: string): void {\n fs.mkdirSync(destDir, { recursive: true });\n for (const file of fs.readdirSync(srcDir)) {\n const srcFile = path.resolve(srcDir, file);\n const destFile = path.resolve(destDir, file);\n copyFile(srcFile, destFile);\n }\n}\n\n/**\n * Check if a directory is empty\n *\n * @param dirPath - The path to the directory\n * @returns True if the directory is empty, false otherwise\n */\nexport function isDirEmpty(dirPath: string): boolean {\n const files = fs.readdirSync(dirPath);\n return files.length === 0 || (files.length === 1 && files[0] === \".git\");\n}\n\n/**\n * Empty a directory while preserving .git\n *\n * @param dirPath - The path to the directory\n */\nfunction emptyDir(dirPath: string): void {\n if (!fs.existsSync(dirPath)) {\n return;\n }\n for (const file of fs.readdirSync(dirPath)) {\n if (file === \".git\") {\n continue;\n }\n fs.rmSync(path.resolve(dirPath, file), { recursive: true, force: true });\n }\n}\n\n/**\n * Detect which package manager invoked the create command\n *\n * @returns The detected package manager or 'pnpm' as default\n */\nexport function detectPackageManager(): \"npm\" | \"pnpm\" | \"yarn\" {\n const userAgent = process.env.npm_config_user_agent;\n\n if (userAgent) {\n if (userAgent.startsWith(\"yarn\")) return \"yarn\";\n if (userAgent.startsWith(\"pnpm\")) return \"pnpm\";\n if (userAgent.startsWith(\"npm\")) return \"npm\";\n }\n\n return \"npm\"; // Default to npm if we can't detect\n}\n","#!/usr/bin/env node\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { blue, red, green, reset } from \"kolorist\";\nimport prompts from \"prompts\";\n\nimport {\n prepareProjectDirectory,\n isDirEmpty,\n customizePackageJson,\n copyFile,\n customizeEnv,\n detectPackageManager,\n} from \"./utils.js\";\n\n// Available templates for project creation\nconst TEMPLATES = [\n {\n name: \"react\",\n display: \"React\",\n color: blue,\n },\n {\n name: \"nextjs\",\n display: \"Next.js\",\n color: green,\n },\n];\n\nconst defaultTargetDir = \"cdp-app\";\n\nconst fileRenames: Record<string, string | undefined> = {\n _gitignore: \".gitignore\",\n};\n\ninterface ProjectOptions {\n projectName: string;\n template: string;\n targetDirectory: string;\n projectId: string;\n}\n\nconst uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n/**\n * Initialize a new CDP app project\n */\nasync function init(): Promise<void> {\n const { projectName, template, targetDirectory, projectId } = await getProjectDetails();\n\n console.log(`\\nScaffolding project in ${targetDirectory}...`);\n\n const root = prepareProjectDirectory(targetDirectory, false);\n const templateDir = path.resolve(fileURLToPath(import.meta.url), \"../..\", `template-${template}`);\n\n copyTemplateFiles(templateDir, root, projectName, projectId);\n printNextSteps(root);\n}\n\n/**\n * Get project details from command line arguments or prompt the user\n *\n * @returns The project details\n */\nasync function getProjectDetails(): Promise<ProjectOptions> {\n // Get target directory from command line args (first non-option argument)\n let targetDir = process.argv[2];\n const defaultProjectName = targetDir ?? defaultTargetDir;\n\n try {\n const result = await prompts(\n [\n {\n type: targetDir ? null : \"text\",\n name: \"projectName\",\n message: reset(\"Project name:\"),\n initial: defaultProjectName,\n onState: state => {\n targetDir = String(state.value).trim() || defaultProjectName;\n },\n },\n {\n type: \"select\",\n name: \"template\",\n message: reset(\"Select a template:\"),\n initial: 0,\n choices: TEMPLATES.map(template => ({\n title: template.color(template.display || template.name),\n value: template.name,\n })),\n },\n {\n type: \"text\",\n name: \"projectId\",\n message: reset(\n \"CDP Project ID (Find your project ID at https://portal.cdp.coinbase.com/projects/overview):\",\n ),\n validate: value => {\n if (!value || !uuidRegex.test(value)) {\n return \"Project ID is required\";\n }\n return true;\n },\n initial: \"\",\n },\n {\n type: \"text\",\n name: \"corsConfirmation\",\n message: reset(\n \"Confirm you have whitelisted 'http://localhost:3000' at https://portal.cdp.coinbase.com/products/embedded-wallets/cors by typing 'y'\",\n ),\n validate: value => {\n if (value !== \"y\") {\n return \"You must whitelist your app domain for your app to be functional.\";\n }\n return true;\n },\n initial: \"\",\n },\n {\n type: () => (!fs.existsSync(targetDir) || isDirEmpty(targetDir) ? null : \"confirm\"),\n name: \"overwrite\",\n message: () =>\n (targetDir === \".\" ? \"Current directory\" : `Target directory \"${targetDir}\"`) +\n \" is not empty. Remove existing files and continue?\",\n },\n {\n type: (_, { overwrite }: { overwrite?: boolean }) => {\n if (overwrite === false) {\n throw new Error(red(\"✖\") + \" Operation cancelled\");\n }\n return null;\n },\n name: \"overwriteChecker\",\n },\n ],\n {\n onCancel: () => {\n throw new Error(red(\"✖\") + \" Operation cancelled\");\n },\n },\n );\n\n return {\n projectName: result.projectName,\n template: result.template,\n targetDirectory: targetDir,\n projectId: result.projectId,\n };\n } catch (cancelled: unknown) {\n if (cancelled instanceof Error) {\n console.log(cancelled.message);\n }\n process.exit(0);\n }\n}\n\n/**\n * Print next steps for the user\n *\n * @param projectRoot - The root directory of the project\n */\nfunction printNextSteps(projectRoot: string): void {\n const packageManager = detectPackageManager();\n\n console.log(green(\"\\nDone. Now run your app:\\n\"));\n if (projectRoot !== process.cwd()) {\n console.log(`cd ${path.relative(process.cwd(), projectRoot)}`);\n }\n const devCommand = packageManager === \"npm\" ? \"npm run dev\" : `${packageManager} dev`;\n console.log(`${packageManager} install`);\n console.log(devCommand);\n}\n\n/**\n * Copy template files to the project directory\n *\n * @param templateDir - The directory containing the template files\n * @param root - The root directory of the project\n * @param projectName - The name of the project\n * @param projectId - The project ID\n */\nfunction copyTemplateFiles(\n templateDir: string,\n root: string,\n projectName: string,\n projectId?: string,\n): void {\n const writeFileToTarget = (file: string, content?: string) => {\n const targetPath = path.join(root, fileRenames[file] ?? file);\n if (content) {\n fs.writeFileSync(targetPath, content);\n } else {\n copyFile(path.join(templateDir, file), targetPath);\n }\n };\n\n const files = fs.readdirSync(templateDir);\n for (const file of files) {\n if (file === \"package.json\") {\n const customizedPackageJson = customizePackageJson(templateDir, projectName);\n writeFileToTarget(file, customizedPackageJson);\n } else if (file === \"env.example\" && projectId) {\n const customizedEnv = customizeEnv(templateDir, projectId);\n writeFileToTarget(file);\n console.log(\"Copying project id to .env\");\n writeFileToTarget(\".env\", customizedEnv);\n } else {\n writeFileToTarget(file);\n }\n }\n}\n\ninit().catch(e => {\n console.error(e);\n process.exit(1);\n});\n"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# CDP React App
|
|
2
|
+
|
|
3
|
+
This project was generated with [`@coinbase/create-cdp-app`](https://coinbase.github.io/cdp-web/modules/_coinbase_create-cdp-app.html) using the Next.js template.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
├── app/ # Next.js App Router directory
|
|
10
|
+
│ ├── favicon.ico # Application favicon
|
|
11
|
+
│ ├── globals.css # Global styles and theme variables
|
|
12
|
+
│ ├── layout.tsx # Root layout with providers and global UI
|
|
13
|
+
│ └── page.tsx # Home page component
|
|
14
|
+
│
|
|
15
|
+
└── components/ # Reusable React components
|
|
16
|
+
├── ClientApp.tsx # Client-side application wrapper
|
|
17
|
+
├── Header.tsx # Navigation header with authentication status
|
|
18
|
+
├── Icons.tsx # Reusable icon components
|
|
19
|
+
├── Loading.tsx # Loading state component
|
|
20
|
+
├── Providers.tsx # CDP and theme providers setup
|
|
21
|
+
├── SignInScreen.tsx # Authentication screen with CDP sign-in flow
|
|
22
|
+
├── SignedInScreen.tsx # Screen displayed after successful authentication
|
|
23
|
+
├── theme.ts # Theme configuration and styling constants
|
|
24
|
+
├── Transaction.tsx # Example transaction flow using CDP Hooks
|
|
25
|
+
└── UserBalance.tsx # Component to display user's wallet balance
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Getting Started
|
|
29
|
+
|
|
30
|
+
First, make sure you have your CDP Project ID:
|
|
31
|
+
|
|
32
|
+
1. Sign in or create an account on the [CDP Portal](https://portal.cdp.coinbase.com)
|
|
33
|
+
2. Copy your Project ID from the dashboard
|
|
34
|
+
3. Go to the [Embedded Wallets CORS settings](https://portal.cdp.coinbase.com/products/embedded-wallets/cors)
|
|
35
|
+
4. Click add origin and whitelist `http://localhost:3000` (or wherever your app will run)
|
|
36
|
+
|
|
37
|
+
Then, copy the `env.example` file to `.env`, and populate the `NEXT_PUBLIC_CDP_PROJECT_ID` with your project id.
|
|
38
|
+
|
|
39
|
+
Now you can start the development server:
|
|
40
|
+
|
|
41
|
+
Using npm:
|
|
42
|
+
```bash
|
|
43
|
+
# Install dependencies
|
|
44
|
+
npm install
|
|
45
|
+
|
|
46
|
+
# Start the development server
|
|
47
|
+
npm run dev
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Using yarn:
|
|
51
|
+
```bash
|
|
52
|
+
# Install dependencies
|
|
53
|
+
yarn
|
|
54
|
+
|
|
55
|
+
# Start the development server
|
|
56
|
+
yarn dev
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Using pnpm:
|
|
60
|
+
```bash
|
|
61
|
+
# Install dependencies
|
|
62
|
+
pnpm install
|
|
63
|
+
|
|
64
|
+
# Start the development server
|
|
65
|
+
pnpm dev
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Visit [http://localhost:3000](http://localhost:3000) to see your app.
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
This template comes with:
|
|
73
|
+
- Next.js 15 App Router
|
|
74
|
+
- CDP React components for authentication and wallet management
|
|
75
|
+
- Example transaction components for Base Sepolia
|
|
76
|
+
- Built-in TypeScript support
|
|
77
|
+
- ESLint with Next.js configuration
|
|
78
|
+
- Viem for type-safe Ethereum interactions
|
|
79
|
+
|
|
80
|
+
## Learn More
|
|
81
|
+
|
|
82
|
+
- [CDP Documentation](https://docs.cloud.coinbase.com/cdp/docs)
|
|
83
|
+
- [CDP React Documentation](https://docs.cloud.coinbase.com/cdp/docs/react-components)
|
|
84
|
+
- [CDP Portal](https://portal.cdp.coinbase.com)
|
|
85
|
+
- [Vite Documentation](https://vitejs.dev)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.*
|
|
7
|
+
.yarn/*
|
|
8
|
+
!.yarn/patches
|
|
9
|
+
!.yarn/plugins
|
|
10
|
+
!.yarn/releases
|
|
11
|
+
!.yarn/versions
|
|
12
|
+
|
|
13
|
+
# testing
|
|
14
|
+
/coverage
|
|
15
|
+
|
|
16
|
+
# next.js
|
|
17
|
+
/.next/
|
|
18
|
+
/out/
|
|
19
|
+
|
|
20
|
+
# production
|
|
21
|
+
/build
|
|
22
|
+
|
|
23
|
+
# misc
|
|
24
|
+
.DS_Store
|
|
25
|
+
*.pem
|
|
26
|
+
|
|
27
|
+
# debug
|
|
28
|
+
npm-debug.log*
|
|
29
|
+
yarn-debug.log*
|
|
30
|
+
yarn-error.log*
|
|
31
|
+
.pnpm-debug.log*
|
|
32
|
+
|
|
33
|
+
# env files (can opt-in for committing if needed)
|
|
34
|
+
.env*
|
|
35
|
+
|
|
36
|
+
# vercel
|
|
37
|
+
.vercel
|
|
38
|
+
|
|
39
|
+
# typescript
|
|
40
|
+
*.tsbuildinfo
|
|
41
|
+
next-env.d.ts
|
|
42
|
+
|
|
43
|
+
# env files
|
|
44
|
+
.env
|
|
45
|
+
.env.*
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { dirname } from "path";
|
|
2
|
+
import { fileURLToPath } from "url";
|
|
3
|
+
import { FlatCompat } from "@eslint/eslintrc";
|
|
4
|
+
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = dirname(__filename);
|
|
7
|
+
|
|
8
|
+
const compat = new FlatCompat({
|
|
9
|
+
baseDirectory: __dirname,
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
const eslintConfig = [
|
|
13
|
+
...compat.extends("next/core-web-vitals", "next/typescript"),
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cdp-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "next lint"
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"next": "15.4.3",
|
|
13
|
+
"react": "^19.1.0",
|
|
14
|
+
"react-dom": "^19.1.0",
|
|
15
|
+
"@coinbase/cdp-react": "latest",
|
|
16
|
+
"@coinbase/cdp-hooks": "latest",
|
|
17
|
+
"@coinbase/cdp-core": "latest",
|
|
18
|
+
"viem": "^2.8.6"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/node": "^20",
|
|
22
|
+
"@types/react": "^19.1.8",
|
|
23
|
+
"@types/react-dom": "^19.1.6",
|
|
24
|
+
"eslint": "^9.31.0",
|
|
25
|
+
"eslint-config-next": "15.4.3",
|
|
26
|
+
"typescript": "~5.8.3"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
|
|
2
|
+
<circle cx="64" cy="64" r="64" fill="#008080" />
|
|
3
|
+
<path fill="#FFF"
|
|
4
|
+
d="M78.91 76.225q.7 0 1.25.54l3.39 3.68q-2.82 3.49-6.93 5.35-4.11 1.85-9.87 1.85-5.15 0-9.26-1.76-4.12-1.76-7.03-4.89-2.91-3.14-4.46-7.49t-1.55-9.51q0-5.21 1.66-9.55 1.66-4.33 4.69-7.47 3.02-3.14 7.21-4.88 4.2-1.74 9.28-1.74 5.06 0 8.98 1.66t6.67 4.35l-2.88 4q-.25.39-.65.67-.4.29-1.11.29-.48 0-.99-.27t-1.12-.67-1.41-.88-1.85-.88q-1.06-.4-2.45-.67-1.39-.28-3.22-.28-3.1 0-5.68 1.11-2.57 1.1-4.43 3.2-1.86 2.09-2.88 5.12-1.02 3.02-1.02 6.89 0 3.91 1.1 6.95t2.99 5.12 4.45 3.18q2.56 1.11 5.5 1.11 1.76 0 3.19-.2 1.42-.19 2.62-.6 1.2-.42 2.27-1.08 1.08-.65 2.13-1.61.32-.29.67-.47.36-.17.74-.17" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--cdp-example-page-bg-color: #eaeaea;
|
|
3
|
+
--cdp-example-bg-overlay-color: rgba(0, 0, 0, 0.25);
|
|
4
|
+
--cdp-example-bg-skeleton-color: rgba(0, 0, 0, 0.1);
|
|
5
|
+
--cdp-example-text-color: #111111;
|
|
6
|
+
--cdp-example-text-secondary-color: #757575;
|
|
7
|
+
--cdp-example-accent-color: #0052ff;
|
|
8
|
+
--cdp-example-accent-hover-color: #0044d6;
|
|
9
|
+
--cdp-example-accent-foreground-color: #ffffff;
|
|
10
|
+
--cdp-example-bg-low-contrast-color: #eeeeee;
|
|
11
|
+
--cdp-example-card-bg-color: #ffffff;
|
|
12
|
+
--cdp-example-card-border-color: #dcdcdc;
|
|
13
|
+
--cdp-example-card-max-width: 30rem;
|
|
14
|
+
--cdp-example-base-font-size: 16px;
|
|
15
|
+
--cdp-example-font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
16
|
+
|
|
17
|
+
background-color: var(--cdp-example-page-bg-color);
|
|
18
|
+
color: var(--cdp-example-card-border-color);
|
|
19
|
+
color-scheme: light dark;
|
|
20
|
+
font-family: var(--cdp-example-font-family);
|
|
21
|
+
font-size: var(--cdp-example-base-font-size);
|
|
22
|
+
font-synthesis: none;
|
|
23
|
+
font-weight: 400;
|
|
24
|
+
line-height: 1.5;
|
|
25
|
+
text-rendering: optimizeLegibility;
|
|
26
|
+
-webkit-font-smoothing: antialiased;
|
|
27
|
+
-moz-osx-font-smoothing: grayscale;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@media (prefers-color-scheme: dark) {
|
|
31
|
+
:root {
|
|
32
|
+
--cdp-example-page-bg-color: #0a0b0d;
|
|
33
|
+
--cdp-example-bg-overlay-color: rgba(0, 0, 0, 0.25);
|
|
34
|
+
--cdp-example-bg-skeleton-color: rgba(255, 255, 255, 0.1);
|
|
35
|
+
--cdp-example-text-color: #fafafa;
|
|
36
|
+
--cdp-example-text-secondary-color: #8a919e;
|
|
37
|
+
--cdp-example-accent-color: #578bfa;
|
|
38
|
+
--cdp-example-accent-hover-color: #3e79f9;
|
|
39
|
+
--cdp-example-accent-foreground-color: #0a0b0d;
|
|
40
|
+
--cdp-example-bg-low-contrast-color: #32353d;
|
|
41
|
+
--cdp-example-card-bg-color: #141519;
|
|
42
|
+
--cdp-example-card-border-color: #24262a;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
::selection {
|
|
47
|
+
background-color: color(from var(--cdp-example-accent-color) srgb r g b / 0.3);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
html,
|
|
51
|
+
html * {
|
|
52
|
+
box-sizing: border-box;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
body {
|
|
56
|
+
margin: 0;
|
|
57
|
+
min-height: 100vh;
|
|
58
|
+
min-width: 320px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
body,
|
|
62
|
+
#root,
|
|
63
|
+
#root > div,
|
|
64
|
+
.flex-col-container {
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
place-items: center;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.flex-row-container {
|
|
72
|
+
align-items: center;
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: row;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.flex-grow {
|
|
78
|
+
flex-grow: 1;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.sr-only {
|
|
82
|
+
border-width: 0;
|
|
83
|
+
clip: rect(0, 0, 0, 0);
|
|
84
|
+
height: 1px;
|
|
85
|
+
margin: -1px;
|
|
86
|
+
overflow: hidden;
|
|
87
|
+
padding: 0;
|
|
88
|
+
position: absolute;
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
width: 1px;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
p {
|
|
94
|
+
margin: 0;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
a {
|
|
98
|
+
color: var(--cdp-example-accent-color);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
a:hover {
|
|
102
|
+
color: var(--cdp-example-accent-hover-color);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
hr {
|
|
106
|
+
border: 0 solid var(--cdp-example-card-border-color);
|
|
107
|
+
border-bottom-width: 1px;
|
|
108
|
+
margin: 1rem 0;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.app {
|
|
112
|
+
height: 100vh;
|
|
113
|
+
color: var(--cdp-example-text-color);
|
|
114
|
+
margin: 0 auto;
|
|
115
|
+
width: 100%;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
header {
|
|
119
|
+
color: var(--cdp-example-text-color);
|
|
120
|
+
background-color: var(--cdp-example-card-bg-color);
|
|
121
|
+
border-bottom: 1px solid var(--cdp-web-colors-border);
|
|
122
|
+
padding: 0.5rem 1rem;
|
|
123
|
+
width: 100%;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
header .wallet-address {
|
|
127
|
+
margin-right: 1rem;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.header-inner {
|
|
131
|
+
align-items: center;
|
|
132
|
+
display: flex;
|
|
133
|
+
flex-direction: column;
|
|
134
|
+
justify-content: space-between;
|
|
135
|
+
margin: 0 auto;
|
|
136
|
+
max-width: 75rem;
|
|
137
|
+
text-align: center;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.user-info {
|
|
141
|
+
justify-content: space-between;
|
|
142
|
+
width: 100%;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.user-icon {
|
|
146
|
+
flex-grow: 0;
|
|
147
|
+
flex-shrink: 0;
|
|
148
|
+
height: 1.25rem;
|
|
149
|
+
margin-right: 0.25rem;
|
|
150
|
+
width: auto;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.wallet-address {
|
|
154
|
+
font-family: monospace;
|
|
155
|
+
font-size: 0.875rem;
|
|
156
|
+
word-break: break-all;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.main {
|
|
160
|
+
padding: 0.5rem;
|
|
161
|
+
width: 100%;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.main-inner {
|
|
165
|
+
gap: 1rem;
|
|
166
|
+
width: 100%;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.main-inner > .card {
|
|
170
|
+
width: 100%;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
.site-title {
|
|
174
|
+
font-size: 1.2rem;
|
|
175
|
+
font-weight: 400;
|
|
176
|
+
line-height: 1.2;
|
|
177
|
+
margin: 0;
|
|
178
|
+
margin-bottom: 0.5rem;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.site-title br {
|
|
182
|
+
display: none;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.card {
|
|
186
|
+
align-items: center;
|
|
187
|
+
background-color: var(--cdp-example-card-bg-color);
|
|
188
|
+
border: 1px solid var(--cdp-web-colors-border);
|
|
189
|
+
border-radius: 1rem;
|
|
190
|
+
display: flex;
|
|
191
|
+
flex-direction: column;
|
|
192
|
+
gap: 1rem;
|
|
193
|
+
justify-content: space-between;
|
|
194
|
+
max-width: var(--cdp-example-card-max-width);
|
|
195
|
+
padding: 2rem 1rem;
|
|
196
|
+
text-align: center;
|
|
197
|
+
width: 100%;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.card-title {
|
|
201
|
+
font-size: 1.25rem;
|
|
202
|
+
font-weight: 500;
|
|
203
|
+
line-height: 1.2;
|
|
204
|
+
margin: 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.loading--balance {
|
|
208
|
+
border-radius: 9999em;
|
|
209
|
+
display: inline-block;
|
|
210
|
+
height: 2.25rem;
|
|
211
|
+
width: 7rem;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.loading--text {
|
|
215
|
+
height: 1rem;
|
|
216
|
+
border-radius: 9999em;
|
|
217
|
+
margin: 0.25rem 0;
|
|
218
|
+
width: 20rem;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.loading--btn {
|
|
222
|
+
border-radius: 9999em;
|
|
223
|
+
height: 3.5rem;
|
|
224
|
+
width: 9.375rem;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.user-balance {
|
|
228
|
+
font-size: 1.5rem;
|
|
229
|
+
font-weight: 400;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
.user-balance .flex-row-container {
|
|
233
|
+
justify-content: center;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
.balance-icon {
|
|
237
|
+
flex-grow: 0;
|
|
238
|
+
flex-shrink: 0;
|
|
239
|
+
height: 1.5rem;
|
|
240
|
+
margin-right: 0.5rem;
|
|
241
|
+
width: auto;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
.copy-address-button {
|
|
245
|
+
background-color: transparent;
|
|
246
|
+
border: 0;
|
|
247
|
+
color: var(--cdp-example-text-color);
|
|
248
|
+
cursor: pointer;
|
|
249
|
+
padding: 0;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
.copy-address-button:hover .user-icon--user,
|
|
253
|
+
.copy-address-button .user-icon--copy {
|
|
254
|
+
display: none;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
.copy-address-button .user-icon--user,
|
|
258
|
+
.copy-address-button:hover .user-icon--copy {
|
|
259
|
+
display: inline;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
.tx-button {
|
|
263
|
+
padding-left: 2rem;
|
|
264
|
+
padding-right: 2rem;
|
|
265
|
+
min-width: 11.75rem;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
@media (min-width: 540px) {
|
|
269
|
+
.header-inner {
|
|
270
|
+
flex-direction: row;
|
|
271
|
+
text-align: left;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.user-info {
|
|
275
|
+
width: auto;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
.site-title {
|
|
279
|
+
margin-bottom: 0;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.site-title br {
|
|
283
|
+
display: inline;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
.main {
|
|
287
|
+
padding: 1rem;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@media (min-width: 860px) {
|
|
292
|
+
:root {
|
|
293
|
+
--cdp-example-card-max-width: 35rem;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
.main-inner {
|
|
297
|
+
align-items: stretch;
|
|
298
|
+
flex-direction: row;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@media (min-width: 1920px) {
|
|
303
|
+
:root {
|
|
304
|
+
--cdp-base-font-size: 20px;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128">
|
|
2
|
+
<circle cx="64" cy="64" r="64" fill="#008080" />
|
|
3
|
+
<path fill="#FFF"
|
|
4
|
+
d="M78.91 76.225q.7 0 1.25.54l3.39 3.68q-2.82 3.49-6.93 5.35-4.11 1.85-9.87 1.85-5.15 0-9.26-1.76-4.12-1.76-7.03-4.89-2.91-3.14-4.46-7.49t-1.55-9.51q0-5.21 1.66-9.55 1.66-4.33 4.69-7.47 3.02-3.14 7.21-4.88 4.2-1.74 9.28-1.74 5.06 0 8.98 1.66t6.67 4.35l-2.88 4q-.25.39-.65.67-.4.29-1.11.29-.48 0-.99-.27t-1.12-.67-1.41-.88-1.85-.88q-1.06-.4-2.45-.67-1.39-.28-3.22-.28-3.1 0-5.68 1.11-2.57 1.1-4.43 3.2-1.86 2.09-2.88 5.12-1.02 3.02-1.02 6.89 0 3.91 1.1 6.95t2.99 5.12 4.45 3.18q2.56 1.11 5.5 1.11 1.76 0 3.19-.2 1.42-.19 2.62-.6 1.2-.42 2.27-1.08 1.08-.65 2.13-1.61.32-.29.67-.47.36-.17.74-.17" />
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import "./globals.css";
|
|
3
|
+
|
|
4
|
+
export const metadata: Metadata = {
|
|
5
|
+
title: "CDP Embedded Wallet Demo",
|
|
6
|
+
description: "A demo of the CDP Embedded Wallet",
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Root layout for the Next.js app
|
|
11
|
+
*
|
|
12
|
+
* @param props - { object } - The props for the RootLayout component
|
|
13
|
+
* @param props.children - { React.ReactNode } - The children to wrap
|
|
14
|
+
* @returns The wrapped children
|
|
15
|
+
*/
|
|
16
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
17
|
+
return (
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<body>{children}</body>
|
|
20
|
+
</html>
|
|
21
|
+
);
|
|
22
|
+
}
|