create-pollar-app-patrickkish 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/LICENSE +21 -0
- package/README.md +50 -0
- package/bin/index.js +41 -0
- package/lib/copy-template.js +34 -0
- package/lib/create.js +140 -0
- package/lib/package-manager.js +43 -0
- package/package.json +25 -0
- package/template/.env.example +5 -0
- package/template/.prettierignore +6 -0
- package/template/.prettierrc +6 -0
- package/template/README.md +38 -0
- package/template/app/components/LoginPage.tsx +45 -0
- package/template/app/components/WalletHome.tsx +105 -0
- package/template/app/globals.css +29 -0
- package/template/app/layout.tsx +36 -0
- package/template/app/page.tsx +15 -0
- package/template/app/providers.tsx +43 -0
- package/template/eslint.config.mjs +13 -0
- package/template/next.config.ts +5 -0
- package/template/package.json +31 -0
- package/template/postcss.config.mjs +5 -0
- package/template/tsconfig.json +23 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Kish
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# create-pollar-app-patrickkish
|
|
2
|
+
|
|
3
|
+
CLI that scaffolds a ready-to-run **Next.js 16 + Pollar** app. Closes [pollar-backoffice#2](https://github.com/pollar-xyz/pollar-backoffice/issues/2).
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Interactive (pnpm recommended)
|
|
9
|
+
pnpm dlx create-pollar-app-patrickkish my-app
|
|
10
|
+
|
|
11
|
+
# Non-interactive
|
|
12
|
+
npx create-pollar-app-patrickkish my-app --yes --pm pnpm --network testnet
|
|
13
|
+
npm create create-pollar-app-patrickkish@latest my-app -- --yes --pm npm
|
|
14
|
+
yarn create create-pollar-app-patrickkish my-app --yes --pm yarn
|
|
15
|
+
bunx create-pollar-app-patrickkish my-app --yes --pm bun
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Flags
|
|
19
|
+
|
|
20
|
+
| Flag | Description |
|
|
21
|
+
|------|-------------|
|
|
22
|
+
| `--yes` / `-y` | Skip prompts (defaults: detected package manager, testnet) |
|
|
23
|
+
| `--pm <npm\|pnpm\|yarn\|bun>` | Package manager for install |
|
|
24
|
+
| `--network <testnet\|mainnet>` | Stellar network written to `.env.local` |
|
|
25
|
+
|
|
26
|
+
When you run via `pnpm dlx`, the default package manager is **pnpm**. Same for `npm create`, `yarn create`, and `bunx`.
|
|
27
|
+
|
|
28
|
+
## Generated app
|
|
29
|
+
|
|
30
|
+
- `PollarProvider` + `NEXT_PUBLIC_POLLAR_API_KEY`
|
|
31
|
+
- Login with **WalletButton**
|
|
32
|
+
- Home screen opens SDK modals: balance, assets, send (USDC example), receive, history
|
|
33
|
+
- eslint + prettier, README with Deploy to Vercel button
|
|
34
|
+
|
|
35
|
+
## Develop this CLI
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pnpm install
|
|
39
|
+
node bin/index.js my-test-app --yes --pm pnpm
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Publish
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
npm publish --access public
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
MIT
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { createApp } from "../lib/create.js";
|
|
4
|
+
|
|
5
|
+
const args = process.argv.slice(2);
|
|
6
|
+
|
|
7
|
+
/** @type {import("../lib/create.js").CreateOptions} */
|
|
8
|
+
const options = {
|
|
9
|
+
projectName: undefined,
|
|
10
|
+
packageManager: undefined,
|
|
11
|
+
network: undefined,
|
|
12
|
+
yes: false,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
16
|
+
const arg = args[i];
|
|
17
|
+
if (arg === "--yes" || arg === "-y") {
|
|
18
|
+
options.yes = true;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg === "--pm" && args[i + 1]) {
|
|
22
|
+
options.packageManager = args[++i];
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (arg === "--network" && args[i + 1]) {
|
|
26
|
+
options.network = args[++i];
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (arg.startsWith("-")) {
|
|
30
|
+
console.error(`Unknown flag: ${arg}`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
if (!options.projectName) {
|
|
34
|
+
options.projectName = arg;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
createApp(options).catch((error) => {
|
|
39
|
+
console.error(error instanceof Error ? error.message : error);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { cp, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
const SKIP = new Set(["node_modules", ".next", ".git"]);
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} source
|
|
8
|
+
* @param {string} target
|
|
9
|
+
* @param {{ projectName: string; network: string }} vars
|
|
10
|
+
*/
|
|
11
|
+
export async function copyTemplate(source, target, vars) {
|
|
12
|
+
await cp(source, target, {
|
|
13
|
+
recursive: true,
|
|
14
|
+
filter: (src) => {
|
|
15
|
+
const base = path.basename(src);
|
|
16
|
+
return !SKIP.has(base);
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const pkgPath = path.join(target, "package.json");
|
|
21
|
+
const pkgRaw = await readFile(pkgPath, "utf8");
|
|
22
|
+
await writeFile(
|
|
23
|
+
pkgPath,
|
|
24
|
+
pkgRaw.replaceAll("{{PROJECT_NAME}}", vars.projectName),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
const envExamplePath = path.join(target, ".env.example");
|
|
28
|
+
const envExample = await readFile(envExamplePath, "utf8");
|
|
29
|
+
const envLocal = envExample.replace(
|
|
30
|
+
"NEXT_PUBLIC_POLLAR_NETWORK=testnet",
|
|
31
|
+
`NEXT_PUBLIC_POLLAR_NETWORK=${vars.network}`,
|
|
32
|
+
);
|
|
33
|
+
await writeFile(path.join(target, ".env.local"), envLocal);
|
|
34
|
+
}
|
package/lib/create.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { cp, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import prompts from "prompts";
|
|
6
|
+
import { detectPackageManager, installCommand, runCommand } from "./package-manager.js";
|
|
7
|
+
import { copyTemplate } from "./copy-template.js";
|
|
8
|
+
|
|
9
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const TEMPLATE_DIR = path.join(__dirname, "..", "template");
|
|
11
|
+
|
|
12
|
+
const PACKAGE_MANAGERS = ["pnpm", "npm", "yarn", "bun"];
|
|
13
|
+
const NETWORKS = ["testnet", "mainnet"];
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @typedef {Object} CreateOptions
|
|
17
|
+
* @property {string | undefined} projectName
|
|
18
|
+
* @property {string | undefined} packageManager
|
|
19
|
+
* @property {string | undefined} network
|
|
20
|
+
* @property {boolean} yes
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {CreateOptions} options
|
|
25
|
+
*/
|
|
26
|
+
export async function createApp(options) {
|
|
27
|
+
const detectedPm = detectPackageManager();
|
|
28
|
+
|
|
29
|
+
let projectName = options.projectName;
|
|
30
|
+
let packageManager = options.packageManager ?? (options.yes ? detectedPm : undefined);
|
|
31
|
+
let network = options.network ?? (options.yes ? "testnet" : undefined);
|
|
32
|
+
|
|
33
|
+
if (!options.yes) {
|
|
34
|
+
const response = await prompts(
|
|
35
|
+
[
|
|
36
|
+
{
|
|
37
|
+
type: projectName ? null : "text",
|
|
38
|
+
name: "projectName",
|
|
39
|
+
message: "Project name",
|
|
40
|
+
initial: "my-pollar-app",
|
|
41
|
+
validate: (value) =>
|
|
42
|
+
/^[a-z0-9-_]+$/i.test(value.trim()) || "Use letters, numbers, hyphens, or underscores",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
type: packageManager ? null : "select",
|
|
46
|
+
name: "packageManager",
|
|
47
|
+
message: "Package manager",
|
|
48
|
+
choices: PACKAGE_MANAGERS.map((pm) => ({
|
|
49
|
+
title: pm + (pm === "pnpm" ? " (recommended)" : ""),
|
|
50
|
+
value: pm,
|
|
51
|
+
})),
|
|
52
|
+
initial: Math.max(0, PACKAGE_MANAGERS.indexOf(detectedPm)),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
type: network ? null : "select",
|
|
56
|
+
name: "network",
|
|
57
|
+
message: "Stellar network",
|
|
58
|
+
choices: NETWORKS.map((value) => ({ title: value, value })),
|
|
59
|
+
initial: 0,
|
|
60
|
+
},
|
|
61
|
+
],
|
|
62
|
+
{
|
|
63
|
+
onCancel: () => {
|
|
64
|
+
console.log("\nCancelled.");
|
|
65
|
+
process.exit(0);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
projectName = projectName ?? response.projectName;
|
|
71
|
+
packageManager = packageManager ?? response.packageManager;
|
|
72
|
+
network = network ?? response.network;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
projectName = (projectName ?? "my-pollar-app").trim();
|
|
76
|
+
packageManager = packageManager ?? detectedPm;
|
|
77
|
+
network = network ?? "testnet";
|
|
78
|
+
|
|
79
|
+
if (!PACKAGE_MANAGERS.includes(packageManager)) {
|
|
80
|
+
throw new Error(`Unsupported package manager: ${packageManager}`);
|
|
81
|
+
}
|
|
82
|
+
if (!NETWORKS.includes(network)) {
|
|
83
|
+
throw new Error(`Unsupported network: ${network}`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
87
|
+
|
|
88
|
+
console.log(`\nCreating Pollar app in ${targetDir}\n`);
|
|
89
|
+
|
|
90
|
+
await mkdir(targetDir, { recursive: false }).catch((error) => {
|
|
91
|
+
if (error && typeof error === "object" && "code" in error && error.code === "EEXIST") {
|
|
92
|
+
throw new Error(`Directory already exists: ${targetDir}`);
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
await copyTemplate(TEMPLATE_DIR, targetDir, {
|
|
98
|
+
projectName,
|
|
99
|
+
network,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
console.log(`Installing dependencies with ${packageManager}…`);
|
|
103
|
+
await runCommand(installCommand(packageManager), targetDir);
|
|
104
|
+
|
|
105
|
+
console.log("Initializing git repository…");
|
|
106
|
+
await runCommand("git init", targetDir);
|
|
107
|
+
await runCommand("git branch -M main", targetDir).catch(() => undefined);
|
|
108
|
+
await runCommand("git add .", targetDir);
|
|
109
|
+
await runCommand('git commit -m "Initial commit from create-pollar-app-patrickkish"', targetDir).catch(
|
|
110
|
+
() => undefined,
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
console.log(`
|
|
114
|
+
Success! Created ${projectName}
|
|
115
|
+
|
|
116
|
+
Next steps:
|
|
117
|
+
cd ${projectName}
|
|
118
|
+
Set your Pollar API key in .env.local:
|
|
119
|
+
NEXT_PUBLIC_POLLAR_API_KEY=pub_testnet_...
|
|
120
|
+
Get a key at https://dashboard.pollar.xyz
|
|
121
|
+
|
|
122
|
+
${devCommand(packageManager)}
|
|
123
|
+
|
|
124
|
+
Open http://localhost:3000 — log in with WalletButton and try Send USDC from the home screen.
|
|
125
|
+
`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** @param {string} pm */
|
|
129
|
+
function devCommand(pm) {
|
|
130
|
+
switch (pm) {
|
|
131
|
+
case "pnpm":
|
|
132
|
+
return "pnpm dev";
|
|
133
|
+
case "yarn":
|
|
134
|
+
return "yarn dev";
|
|
135
|
+
case "bun":
|
|
136
|
+
return "bun run dev";
|
|
137
|
+
default:
|
|
138
|
+
return "npm run dev";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
|
|
3
|
+
/** @returns {"pnpm" | "npm" | "yarn" | "bun"} */
|
|
4
|
+
export function detectPackageManager() {
|
|
5
|
+
const userAgent = process.env.npm_config_user_agent ?? "";
|
|
6
|
+
if (userAgent.startsWith("pnpm")) return "pnpm";
|
|
7
|
+
if (userAgent.startsWith("yarn")) return "yarn";
|
|
8
|
+
if (userAgent.startsWith("bun")) return "bun";
|
|
9
|
+
return "npm";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/** @param {string} pm */
|
|
13
|
+
export function installCommand(pm) {
|
|
14
|
+
switch (pm) {
|
|
15
|
+
case "pnpm":
|
|
16
|
+
return "pnpm install";
|
|
17
|
+
case "yarn":
|
|
18
|
+
return "yarn install";
|
|
19
|
+
case "bun":
|
|
20
|
+
return "bun install";
|
|
21
|
+
default:
|
|
22
|
+
return "npm install";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {string} command
|
|
28
|
+
* @param {string} cwd
|
|
29
|
+
*/
|
|
30
|
+
export function runCommand(command, cwd) {
|
|
31
|
+
return new Promise((resolve, reject) => {
|
|
32
|
+
const child = spawn(command, {
|
|
33
|
+
cwd,
|
|
34
|
+
stdio: "inherit",
|
|
35
|
+
shell: true,
|
|
36
|
+
});
|
|
37
|
+
child.on("error", reject);
|
|
38
|
+
child.on("close", (code) => {
|
|
39
|
+
if (code === 0) resolve(undefined);
|
|
40
|
+
else reject(new Error(`Command failed (${code}): ${command}`));
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-pollar-app-patrickkish",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a Next.js app with Pollar wallet pre-wired",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=20"
|
|
9
|
+
},
|
|
10
|
+
"packageManager": "pnpm@10.28.2",
|
|
11
|
+
"bin": {
|
|
12
|
+
"create-pollar-app-patrickkish": "./bin/index.js"
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"bin",
|
|
16
|
+
"lib",
|
|
17
|
+
"template"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"test:cli": "node bin/index.js test-app --yes --pm pnpm --network testnet"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"prompts": "^2.4.2"
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Pollar App
|
|
2
|
+
|
|
3
|
+
A Next.js app scaffolded with [create-pollar-app-patrickkish](https://github.com/PatrickKish1/create-pollar-app-patrickkish). Pollar wallet login, send, receive, balance, assets, and history are provided by the official SDK.
|
|
4
|
+
|
|
5
|
+
## Get started in 3 steps
|
|
6
|
+
|
|
7
|
+
1. **Set your API key** — copy `.env.local` and paste your testnet key from [dashboard.pollar.xyz](https://dashboard.pollar.xyz):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
NEXT_PUBLIC_POLLAR_API_KEY=pub_testnet_...
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
2. **Install and run:**
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pnpm install # or npm / yarn / bun
|
|
17
|
+
pnpm dev
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
3. **Open** [http://localhost:3000](http://localhost:3000) — log in with **WalletButton**, then use **Send USDC** on the home screen.
|
|
21
|
+
|
|
22
|
+
## Deploy to Vercel
|
|
23
|
+
|
|
24
|
+
[](https://vercel.com/new?env=NEXT_PUBLIC_POLLAR_API_KEY,NEXT_PUBLIC_POLLAR_NETWORK&envDescription=Pollar%20publishable%20key%20and%20Stellar%20network&project-name=pollar-app)
|
|
25
|
+
|
|
26
|
+
After deploy, set `NEXT_PUBLIC_POLLAR_API_KEY` in Vercel project settings.
|
|
27
|
+
|
|
28
|
+
## Stack
|
|
29
|
+
|
|
30
|
+
- Next.js 16 (App Router), React 19, TypeScript, Tailwind 4
|
|
31
|
+
- `@pollar/core@^0.9.0`, `@pollar/react@^0.9.0`
|
|
32
|
+
|
|
33
|
+
## Environment
|
|
34
|
+
|
|
35
|
+
| Variable | Required | Description |
|
|
36
|
+
|----------|----------|-------------|
|
|
37
|
+
| `NEXT_PUBLIC_POLLAR_API_KEY` | Yes | Pollar publishable key (`pub_testnet_...`) |
|
|
38
|
+
| `NEXT_PUBLIC_POLLAR_NETWORK` | No | `testnet` (default) or `mainnet` |
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { WalletButton } from "@pollar/react";
|
|
4
|
+
|
|
5
|
+
const keyConfigured =
|
|
6
|
+
!!process.env.NEXT_PUBLIC_POLLAR_API_KEY &&
|
|
7
|
+
!process.env.NEXT_PUBLIC_POLLAR_API_KEY.includes("xxxx");
|
|
8
|
+
|
|
9
|
+
export function LoginPage() {
|
|
10
|
+
return (
|
|
11
|
+
<main className="mx-auto flex w-full max-w-md flex-1 flex-col items-center justify-center gap-8 px-6 py-16 text-center">
|
|
12
|
+
<div className="flex flex-col gap-3">
|
|
13
|
+
<span className="inline-flex w-fit self-center rounded-full bg-brand-tint px-3 py-1 text-xs font-medium text-brand">
|
|
14
|
+
Powered by Pollar
|
|
15
|
+
</span>
|
|
16
|
+
<h1 className="text-4xl font-bold tracking-tight">
|
|
17
|
+
<span className="text-brand">Pollar</span> Wallet
|
|
18
|
+
</h1>
|
|
19
|
+
<p className="text-base leading-7 text-zinc-500">
|
|
20
|
+
Log in to send, receive, and manage assets on Stellar. Login providers are configured in
|
|
21
|
+
your Pollar dashboard.
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div className="flex w-full flex-col items-center gap-3">
|
|
26
|
+
<WalletButton />
|
|
27
|
+
{!keyConfigured && (
|
|
28
|
+
<p className="rounded-lg border border-amber-300 bg-amber-50 px-4 py-3 text-sm text-amber-900">
|
|
29
|
+
Set <code className="font-mono">NEXT_PUBLIC_POLLAR_API_KEY</code> in{" "}
|
|
30
|
+
<code className="font-mono">.env.local</code> from{" "}
|
|
31
|
+
<a
|
|
32
|
+
href="https://dashboard.pollar.xyz"
|
|
33
|
+
target="_blank"
|
|
34
|
+
rel="noopener noreferrer"
|
|
35
|
+
className="font-medium underline"
|
|
36
|
+
>
|
|
37
|
+
dashboard.pollar.xyz
|
|
38
|
+
</a>{" "}
|
|
39
|
+
and restart the dev server.
|
|
40
|
+
</p>
|
|
41
|
+
)}
|
|
42
|
+
</div>
|
|
43
|
+
</main>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { WalletButton, usePollar } from "@pollar/react";
|
|
4
|
+
|
|
5
|
+
type ActionId = "balance" | "assets" | "send" | "receive" | "history";
|
|
6
|
+
|
|
7
|
+
const ACTIONS: {
|
|
8
|
+
id: ActionId;
|
|
9
|
+
title: string;
|
|
10
|
+
description: string;
|
|
11
|
+
highlight?: boolean;
|
|
12
|
+
}[] = [
|
|
13
|
+
{
|
|
14
|
+
id: "balance",
|
|
15
|
+
title: "Balance",
|
|
16
|
+
description: "View native and token balances",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "assets",
|
|
20
|
+
title: "Assets",
|
|
21
|
+
description: "Manage trustlines and enabled assets",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: "send",
|
|
25
|
+
title: "Send USDC",
|
|
26
|
+
description: "Send USDC or any enabled asset on testnet",
|
|
27
|
+
highlight: true,
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "receive",
|
|
31
|
+
title: "Receive",
|
|
32
|
+
description: "Share your address and QR code",
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: "history",
|
|
36
|
+
title: "History",
|
|
37
|
+
description: "Browse past transactions",
|
|
38
|
+
},
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function shortenAddress(address: string) {
|
|
42
|
+
if (address.length <= 12) return address;
|
|
43
|
+
return `${address.slice(0, 6)}…${address.slice(-4)}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function WalletHome() {
|
|
47
|
+
const {
|
|
48
|
+
walletAddress,
|
|
49
|
+
openWalletBalanceModal,
|
|
50
|
+
openEnabledAssetsModal,
|
|
51
|
+
openSendModal,
|
|
52
|
+
openReceiveModal,
|
|
53
|
+
openTxHistoryModal,
|
|
54
|
+
} = usePollar();
|
|
55
|
+
|
|
56
|
+
const handlers: Record<ActionId, () => void> = {
|
|
57
|
+
balance: openWalletBalanceModal,
|
|
58
|
+
assets: openEnabledAssetsModal,
|
|
59
|
+
send: openSendModal,
|
|
60
|
+
receive: openReceiveModal,
|
|
61
|
+
history: openTxHistoryModal,
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<div className="flex w-full flex-1 flex-col">
|
|
66
|
+
<header className="flex items-center justify-between border-b border-zinc-200 px-6 py-4">
|
|
67
|
+
<div>
|
|
68
|
+
<p className="text-sm font-semibold text-zinc-900">Pollar Wallet</p>
|
|
69
|
+
{walletAddress && (
|
|
70
|
+
<p className="font-mono text-xs text-zinc-500">{shortenAddress(walletAddress)}</p>
|
|
71
|
+
)}
|
|
72
|
+
</div>
|
|
73
|
+
<WalletButton />
|
|
74
|
+
</header>
|
|
75
|
+
|
|
76
|
+
<main className="mx-auto flex w-full max-w-2xl flex-1 flex-col gap-8 px-6 py-10">
|
|
77
|
+
<div>
|
|
78
|
+
<h1 className="text-3xl font-bold tracking-tight">Your wallet</h1>
|
|
79
|
+
<p className="mt-2 text-zinc-500">
|
|
80
|
+
All actions below use Pollar's native SDK modals — no custom send/receive UI.
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
83
|
+
|
|
84
|
+
<ul className="grid gap-4 sm:grid-cols-2">
|
|
85
|
+
{ACTIONS.map((action) => (
|
|
86
|
+
<li key={action.id}>
|
|
87
|
+
<button
|
|
88
|
+
type="button"
|
|
89
|
+
onClick={handlers[action.id]}
|
|
90
|
+
className={`flex h-full w-full flex-col items-start gap-2 rounded-2xl border px-5 py-4 text-left transition-colors hover:border-brand hover:bg-brand-tint/40 ${
|
|
91
|
+
action.highlight
|
|
92
|
+
? "border-brand bg-brand-tint/30"
|
|
93
|
+
: "border-zinc-200 bg-white"
|
|
94
|
+
}`}
|
|
95
|
+
>
|
|
96
|
+
<span className="text-base font-semibold text-zinc-900">{action.title}</span>
|
|
97
|
+
<span className="text-sm leading-6 text-zinc-500">{action.description}</span>
|
|
98
|
+
</button>
|
|
99
|
+
</li>
|
|
100
|
+
))}
|
|
101
|
+
</ul>
|
|
102
|
+
</main>
|
|
103
|
+
</div>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--background: #ffffff;
|
|
5
|
+
--foreground: #171717;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
@theme inline {
|
|
9
|
+
--color-background: var(--background);
|
|
10
|
+
--color-foreground: var(--foreground);
|
|
11
|
+
--font-sans: var(--font-geist-sans);
|
|
12
|
+
--font-mono: var(--font-geist-mono);
|
|
13
|
+
--color-brand: #0560a9;
|
|
14
|
+
--color-brand-dark: #044a82;
|
|
15
|
+
--color-brand-tint: #e7f0f9;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
@media (prefers-color-scheme: dark) {
|
|
19
|
+
:root {
|
|
20
|
+
--background: #0a0a0a;
|
|
21
|
+
--foreground: #ededed;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
background: var(--background);
|
|
27
|
+
color: var(--foreground);
|
|
28
|
+
font-family: var(--font-sans), system-ui, sans-serif;
|
|
29
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Metadata } from "next";
|
|
2
|
+
import { Geist, Geist_Mono } from "next/font/google";
|
|
3
|
+
import "./globals.css";
|
|
4
|
+
import { Providers } from "./providers";
|
|
5
|
+
|
|
6
|
+
const geistSans = Geist({
|
|
7
|
+
variable: "--font-geist-sans",
|
|
8
|
+
subsets: ["latin"],
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const geistMono = Geist_Mono({
|
|
12
|
+
variable: "--font-geist-mono",
|
|
13
|
+
subsets: ["latin"],
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const metadata: Metadata = {
|
|
17
|
+
title: "Pollar App",
|
|
18
|
+
description: "Next.js app with Pollar wallet — send, receive, and manage assets on Stellar.",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default function RootLayout({
|
|
22
|
+
children,
|
|
23
|
+
}: Readonly<{
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
}>) {
|
|
26
|
+
return (
|
|
27
|
+
<html
|
|
28
|
+
lang="en"
|
|
29
|
+
className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`}
|
|
30
|
+
>
|
|
31
|
+
<body className="flex min-h-full flex-col">
|
|
32
|
+
<Providers>{children}</Providers>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
35
|
+
);
|
|
36
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { usePollar } from "@pollar/react";
|
|
4
|
+
import { LoginPage } from "./components/LoginPage";
|
|
5
|
+
import { WalletHome } from "./components/WalletHome";
|
|
6
|
+
|
|
7
|
+
export default function Home() {
|
|
8
|
+
const { isAuthenticated } = usePollar();
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex flex-1 flex-col bg-white text-zinc-900">
|
|
12
|
+
{isAuthenticated ? <WalletHome /> : <LoginPage />}
|
|
13
|
+
</div>
|
|
14
|
+
);
|
|
15
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { useState, useSyncExternalStore, type ReactNode } from "react";
|
|
4
|
+
import { PollarProvider, type PollarConfig } from "@pollar/react";
|
|
5
|
+
import type { PollarClientConfig, StellarNetwork } from "@pollar/core";
|
|
6
|
+
import "@pollar/react/styles.css";
|
|
7
|
+
|
|
8
|
+
const apiKey = process.env.NEXT_PUBLIC_POLLAR_API_KEY ?? "";
|
|
9
|
+
const stellarNetwork =
|
|
10
|
+
(process.env.NEXT_PUBLIC_POLLAR_NETWORK as StellarNetwork | undefined) ?? "testnet";
|
|
11
|
+
|
|
12
|
+
export function Providers({ children }: { children: ReactNode }) {
|
|
13
|
+
const [clientConfig] = useState<PollarClientConfig>(() => ({
|
|
14
|
+
apiKey,
|
|
15
|
+
stellarNetwork,
|
|
16
|
+
}));
|
|
17
|
+
|
|
18
|
+
const isClient = useSyncExternalStore(
|
|
19
|
+
() => () => {},
|
|
20
|
+
() => true,
|
|
21
|
+
() => false,
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
if (!isClient) {
|
|
25
|
+
return <div className="flex min-h-screen items-center justify-center bg-white" />;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const appConfig: PollarConfig = {
|
|
29
|
+
application: { name: "Pollar App" },
|
|
30
|
+
styles: {
|
|
31
|
+
accentColor: "#0560a9",
|
|
32
|
+
emailEnabled: true,
|
|
33
|
+
embeddedWallets: true,
|
|
34
|
+
smartWallet: true,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<PollarProvider client={clientConfig} appConfig={appConfig}>
|
|
40
|
+
{children}
|
|
41
|
+
</PollarProvider>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
import prettier from "eslint-config-prettier";
|
|
5
|
+
|
|
6
|
+
const eslintConfig = defineConfig([
|
|
7
|
+
...nextVitals,
|
|
8
|
+
...nextTs,
|
|
9
|
+
prettier,
|
|
10
|
+
globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
|
|
11
|
+
]);
|
|
12
|
+
|
|
13
|
+
export default eslintConfig;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{PROJECT_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "next dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "next start",
|
|
9
|
+
"lint": "eslint . && prettier --check .",
|
|
10
|
+
"format": "prettier --write ."
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@pollar/core": "^0.9.0",
|
|
14
|
+
"@pollar/react": "^0.9.0",
|
|
15
|
+
"next": "16.2.9",
|
|
16
|
+
"react": "19.2.4",
|
|
17
|
+
"react-dom": "19.2.4"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"@tailwindcss/postcss": "^4",
|
|
21
|
+
"@types/node": "^20",
|
|
22
|
+
"@types/react": "^19",
|
|
23
|
+
"@types/react-dom": "^19",
|
|
24
|
+
"eslint": "^9",
|
|
25
|
+
"eslint-config-next": "16.2.9",
|
|
26
|
+
"eslint-config-prettier": "^10.1.5",
|
|
27
|
+
"prettier": "^3.6.2",
|
|
28
|
+
"tailwindcss": "^4",
|
|
29
|
+
"typescript": "^5"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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": [{ "name": "next" }],
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./*"]
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
|
22
|
+
"exclude": ["node_modules"]
|
|
23
|
+
}
|