create-teact 0.1.0-alpha.7 → 0.1.0-alpha.8
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 +32 -33
- package/index.ts +41 -442
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Interactive project scaffolder for Teact. The fastest way to start a new Telegram bot.
|
|
4
4
|
|
|
5
|
+
Uses **`@teactjs/bot-templates`** for generated files — the same templates as `teact create` from `@teactjs/cli`.
|
|
6
|
+
|
|
5
7
|
## Usage
|
|
6
8
|
|
|
7
9
|
```bash
|
|
@@ -18,56 +20,52 @@ pnpm create teact my-bot
|
|
|
18
20
|
## What It Does
|
|
19
21
|
|
|
20
22
|
1. Prompts for a project name (if not provided as an argument)
|
|
21
|
-
2. Lets you pick a template:
|
|
22
|
-
3. Lets you select
|
|
23
|
+
2. Lets you pick a template: **Starter**, **Showcase**, **Counter**, or **Empty**
|
|
24
|
+
3. Lets you select plugins: storage, conversations, streaming, auth, i18n, payments (Showcase pre-selects all; Starter starts with none)
|
|
23
25
|
4. Asks which package manager to use (`bun`, `npm`, or `pnpm`)
|
|
24
|
-
5. Asks whether to install dependencies
|
|
25
|
-
6. Generates
|
|
26
|
-
7. Feature pages are wired into the router and main menu automatically
|
|
26
|
+
5. Asks whether to install dependencies (install output is shown in the terminal)
|
|
27
|
+
6. Generates `package.json`, `tsconfig.json`, `teact.config.ts`, `.env`, `.gitignore`, and `src/` files
|
|
27
28
|
|
|
28
29
|
## Templates
|
|
29
30
|
|
|
30
|
-
| Template | Description |
|
|
31
|
-
|
|
32
|
-
| `router` |
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `empty` |
|
|
31
|
+
| Template | CLI flag | Description |
|
|
32
|
+
|----------|-----------|-------------|
|
|
33
|
+
| **Starter** | `starter` or `router` | Main menu + About; add routes by enabling features |
|
|
34
|
+
| **Showcase** | `showcase` or `full` | Full demo (Pokedex, showcase pages, guards, commands with deep links) — features control which plugins are enabled |
|
|
35
|
+
| **Counter** | `counter` | Single-screen counter |
|
|
36
|
+
| **Empty** | `empty` | One message, no router |
|
|
36
37
|
|
|
37
38
|
## Features
|
|
38
39
|
|
|
39
|
-
When you pick the `router` template, you can opt into features:
|
|
40
|
-
|
|
41
40
|
| Feature | What it adds |
|
|
42
|
-
|
|
43
|
-
| Storage | `
|
|
44
|
-
| Conversations | `conversationsPlugin
|
|
45
|
-
| Streaming | `streamPlugin
|
|
46
|
-
| Auth | `authPlugin
|
|
47
|
-
| i18n | Locale JSON
|
|
48
|
-
| Payments | StorePage with `useInvoice`, `PAYMENT_PROVIDER_TOKEN` in `.env` |
|
|
41
|
+
|---------|----------------|
|
|
42
|
+
| Storage | `storagePlugin`, Settings page with `useStorage` |
|
|
43
|
+
| Conversations | `conversationsPlugin`; full component tour in Showcase when enabled |
|
|
44
|
+
| Streaming | `streamPlugin`, StreamDemo with `useStream` |
|
|
45
|
+
| Auth | `authPlugin`, guarded secret routes + login flow (Showcase) |
|
|
46
|
+
| i18n | Locale JSON, `LanguagePage`, `createI18n` |
|
|
47
|
+
| Payments | `StorePage` with `useInvoice`, `PAYMENT_PROVIDER_TOKEN` in `.env` |
|
|
48
|
+
|
|
49
|
+
## Showcase layout (when that template is selected)
|
|
50
|
+
|
|
51
|
+
Includes `src/commands.ts` (`/start` deep links, `/help` with inline buttons), nested Pokémon routes, `NotFoundPage`, example middleware, and `experimental: {}` on `createBot` — aligned with `examples/showcase-bot`.
|
|
49
52
|
|
|
50
|
-
## Generated
|
|
53
|
+
## Generated structure (varies by template)
|
|
51
54
|
|
|
52
55
|
```
|
|
53
56
|
my-bot/
|
|
54
|
-
├── teact.config.ts
|
|
57
|
+
├── teact.config.ts
|
|
55
58
|
├── package.json
|
|
56
59
|
├── tsconfig.json
|
|
57
|
-
├── .env
|
|
60
|
+
├── .env
|
|
58
61
|
├── .gitignore
|
|
59
62
|
└── src/
|
|
60
|
-
├── index.tsx
|
|
63
|
+
├── index.tsx
|
|
64
|
+
├── commands.ts # (Showcase)
|
|
65
|
+
├── api/ # (Showcase — PokeAPI)
|
|
66
|
+
├── hooks/
|
|
61
67
|
├── pages/
|
|
62
|
-
|
|
63
|
-
│ ├── About.tsx
|
|
64
|
-
│ ├── Settings.tsx # (storage)
|
|
65
|
-
│ ├── StreamDemo.tsx # (streaming)
|
|
66
|
-
│ ├── LanguagePage.tsx # (i18n)
|
|
67
|
-
│ └── StorePage.tsx # (payments)
|
|
68
|
-
└── locales/ # (i18n)
|
|
69
|
-
├── en.json
|
|
70
|
-
└── am.json
|
|
68
|
+
└── locales/ # (i18n)
|
|
71
69
|
```
|
|
72
70
|
|
|
73
71
|
## After Scaffolding
|
|
@@ -80,5 +78,6 @@ bun dev
|
|
|
80
78
|
|
|
81
79
|
## See Also
|
|
82
80
|
|
|
81
|
+
- [`@teactjs/bot-templates`](../bot-templates) — template implementation
|
|
83
82
|
- [`@teactjs/cli`](../cli) for the full CLI tool
|
|
84
83
|
- [Root README](../../README.md) for getting started
|
package/index.ts
CHANGED
|
@@ -3,9 +3,29 @@
|
|
|
3
3
|
import { mkdirSync, writeFileSync, existsSync } from 'fs';
|
|
4
4
|
import { resolve, join } from 'path';
|
|
5
5
|
import * as p from '@clack/prompts';
|
|
6
|
+
import {
|
|
7
|
+
buildDependencies,
|
|
8
|
+
buildEnvContent,
|
|
9
|
+
defaultFeaturesForTemplate,
|
|
10
|
+
getTemplateFiles,
|
|
11
|
+
normalizeTemplate,
|
|
12
|
+
TEMPLATE_SELECT_OPTIONS,
|
|
13
|
+
FEATURE_SELECT_OPTIONS,
|
|
14
|
+
type TemplateId,
|
|
15
|
+
} from '@teactjs/bot-templates';
|
|
6
16
|
|
|
7
17
|
const args = process.argv.slice(2);
|
|
8
18
|
|
|
19
|
+
async function runInstall(pm: string, cwd: string): Promise<number> {
|
|
20
|
+
const proc = Bun.spawn([pm, 'install'], {
|
|
21
|
+
cwd,
|
|
22
|
+
stdout: 'inherit',
|
|
23
|
+
stderr: 'inherit',
|
|
24
|
+
stdin: 'inherit',
|
|
25
|
+
});
|
|
26
|
+
return proc.exited.then(() => proc.exitCode ?? 1);
|
|
27
|
+
}
|
|
28
|
+
|
|
9
29
|
async function main() {
|
|
10
30
|
p.intro('create-teact');
|
|
11
31
|
|
|
@@ -27,29 +47,20 @@ async function main() {
|
|
|
27
47
|
process.exit(1);
|
|
28
48
|
}
|
|
29
49
|
|
|
30
|
-
const
|
|
50
|
+
const templateRaw = await p.select({
|
|
31
51
|
message: 'Pick a template',
|
|
32
|
-
options: [
|
|
33
|
-
{ value: 'router', label: 'Router', hint: 'multi-page bot with navigation (recommended)' },
|
|
34
|
-
{ value: 'counter', label: 'Counter', hint: 'simple stateful counter' },
|
|
35
|
-
{ value: 'full', label: 'Full-Stack', hint: 'router + all features pre-wired' },
|
|
36
|
-
{ value: 'empty', label: 'Empty', hint: 'bare minimum' },
|
|
37
|
-
],
|
|
52
|
+
options: [...TEMPLATE_SELECT_OPTIONS],
|
|
38
53
|
}) as string;
|
|
39
54
|
|
|
40
|
-
if (p.isCancel(
|
|
55
|
+
if (p.isCancel(templateRaw)) { p.cancel('Cancelled'); process.exit(0); }
|
|
56
|
+
|
|
57
|
+
const template: TemplateId = normalizeTemplate(templateRaw);
|
|
41
58
|
|
|
59
|
+
const initial = defaultFeaturesForTemplate(template);
|
|
42
60
|
const features = await p.multiselect({
|
|
43
|
-
message: '
|
|
44
|
-
options: [
|
|
45
|
-
|
|
46
|
-
{ value: 'conversations', label: 'Conversations', hint: 'multi-step flows' },
|
|
47
|
-
{ value: 'streaming', label: 'Streaming', hint: 'live text updates with useStream' },
|
|
48
|
-
{ value: 'auth', label: 'Auth', hint: 'role-based access + useAuthSession' },
|
|
49
|
-
{ value: 'i18n', label: 'Internationalization', hint: 'multi-language with i18next' },
|
|
50
|
-
{ value: 'payments', label: 'Payments', hint: 'Telegram payments with useInvoice' },
|
|
51
|
-
],
|
|
52
|
-
initialValues: template === 'full' ? ['storage', 'conversations', 'streaming', 'auth', 'i18n', 'payments'] : [],
|
|
61
|
+
message: 'Plugins & integrations (toggle what you need)',
|
|
62
|
+
options: [...FEATURE_SELECT_OPTIONS],
|
|
63
|
+
initialValues: initial,
|
|
53
64
|
required: false,
|
|
54
65
|
}) as string[];
|
|
55
66
|
|
|
@@ -74,20 +85,7 @@ async function main() {
|
|
|
74
85
|
|
|
75
86
|
mkdirSync(join(dir, 'src', 'pages'), { recursive: true });
|
|
76
87
|
|
|
77
|
-
const
|
|
78
|
-
|
|
79
|
-
const deps: Record<string, string> = {
|
|
80
|
-
'@teactjs/core': '^0.1.0-alpha.7',
|
|
81
|
-
'@teactjs/ui': '^0.1.0-alpha.7',
|
|
82
|
-
'@teactjs/telegram': '^0.1.0-alpha.7',
|
|
83
|
-
'@teactjs/cli': '^0.1.0-alpha.7',
|
|
84
|
-
react: '^19.0.0',
|
|
85
|
-
};
|
|
86
|
-
if (has('storage')) deps['@teactjs/storage'] = '^0.1.0-alpha.7';
|
|
87
|
-
if (has('i18n')) {
|
|
88
|
-
deps['i18next'] = '^23.0.0';
|
|
89
|
-
deps['react-i18next'] = '^15.0.0';
|
|
90
|
-
}
|
|
88
|
+
const { dependencies, devDependencies } = buildDependencies(template, features);
|
|
91
89
|
|
|
92
90
|
writeFileSync(join(dir, 'package.json'), JSON.stringify({
|
|
93
91
|
name,
|
|
@@ -99,12 +97,8 @@ async function main() {
|
|
|
99
97
|
build: 'teact build',
|
|
100
98
|
start: 'bun run dist/index.js',
|
|
101
99
|
},
|
|
102
|
-
dependencies
|
|
103
|
-
devDependencies
|
|
104
|
-
typescript: '^5.7.0',
|
|
105
|
-
'@types/bun': 'latest',
|
|
106
|
-
'@types/react': '^19.0.0',
|
|
107
|
-
},
|
|
100
|
+
dependencies,
|
|
101
|
+
devDependencies,
|
|
108
102
|
}, null, 2));
|
|
109
103
|
|
|
110
104
|
writeFileSync(join(dir, 'tsconfig.json'), JSON.stringify({
|
|
@@ -120,16 +114,13 @@ async function main() {
|
|
|
120
114
|
skipLibCheck: true,
|
|
121
115
|
resolveJsonModule: true,
|
|
122
116
|
},
|
|
123
|
-
include: ['src'],
|
|
117
|
+
include: ['src', 'teact.config.ts'],
|
|
124
118
|
}, null, 2));
|
|
125
119
|
|
|
126
|
-
|
|
127
|
-
if (has('payments')) envContent += 'PAYMENT_PROVIDER_TOKEN=\n';
|
|
128
|
-
|
|
129
|
-
writeFileSync(join(dir, '.env'), envContent);
|
|
120
|
+
writeFileSync(join(dir, '.env'), buildEnvContent(features));
|
|
130
121
|
writeFileSync(join(dir, '.gitignore'), 'node_modules/\ndist/\n.env\n.teact/\n*.log\n.DS_Store\n');
|
|
131
122
|
|
|
132
|
-
const files =
|
|
123
|
+
const files = getTemplateFiles(template, features);
|
|
133
124
|
for (const [filePath, content] of Object.entries(files)) {
|
|
134
125
|
const fullPath = join(dir, filePath);
|
|
135
126
|
mkdirSync(resolve(fullPath, '..'), { recursive: true });
|
|
@@ -139,13 +130,12 @@ async function main() {
|
|
|
139
130
|
spin.stop('Project generated');
|
|
140
131
|
|
|
141
132
|
if (shouldInstall) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
installSpin.stop('Install failed — run manually');
|
|
133
|
+
p.log.info(`Installing dependencies with ${pm}…`);
|
|
134
|
+
const code = await runInstall(pm, dir);
|
|
135
|
+
if (code === 0) {
|
|
136
|
+
p.log.success('Dependencies installed');
|
|
137
|
+
} else {
|
|
138
|
+
p.log.warn('Install failed — run the package manager manually in the project folder');
|
|
149
139
|
}
|
|
150
140
|
}
|
|
151
141
|
|
|
@@ -162,397 +152,6 @@ async function main() {
|
|
|
162
152
|
p.outro('Happy building!');
|
|
163
153
|
}
|
|
164
154
|
|
|
165
|
-
// ---- File generators ----
|
|
166
|
-
|
|
167
|
-
function buildFiles(template: string, features: string[]): Record<string, string> {
|
|
168
|
-
const has = (f: string) => template === 'full' || features.includes(f);
|
|
169
|
-
|
|
170
|
-
if (template === 'counter') return counterFiles();
|
|
171
|
-
if (template === 'empty') return emptyFiles();
|
|
172
|
-
|
|
173
|
-
return routerFiles(has);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
function emptyFiles(): Record<string, string> {
|
|
177
|
-
return {
|
|
178
|
-
'src/index.tsx': `import { createBot } from '@teactjs/core';
|
|
179
|
-
import { Message } from '@teactjs/ui';
|
|
180
|
-
import { TelegramAdapter } from '@teactjs/telegram';
|
|
181
|
-
|
|
182
|
-
function App() {
|
|
183
|
-
return <Message text="Hello from Teact!" />;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
const bot = createBot({
|
|
187
|
-
component: App,
|
|
188
|
-
adapter: new TelegramAdapter(),
|
|
189
|
-
commands: { start: { description: 'Start the bot' } },
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
bot.start();
|
|
193
|
-
`,
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function counterFiles(): Record<string, string> {
|
|
198
|
-
return {
|
|
199
|
-
'src/index.tsx': `import { useState } from 'react';
|
|
200
|
-
import { createBot } from '@teactjs/core';
|
|
201
|
-
import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
|
|
202
|
-
import { TelegramAdapter } from '@teactjs/telegram';
|
|
203
|
-
|
|
204
|
-
function App() {
|
|
205
|
-
const [count, setCount] = useState(0);
|
|
206
|
-
|
|
207
|
-
return (
|
|
208
|
-
<Message text={\`Count: \${count}\`}>
|
|
209
|
-
<InlineKeyboard>
|
|
210
|
-
<ButtonRow>
|
|
211
|
-
<Button text="-1" onClick={() => setCount(c => c - 1)} />
|
|
212
|
-
<Button text={\`[ \${count} ]\`} onClick={() => {}} />
|
|
213
|
-
<Button text="+1" onClick={() => setCount(c => c + 1)} />
|
|
214
|
-
</ButtonRow>
|
|
215
|
-
<ButtonRow>
|
|
216
|
-
<Button text="Reset" onClick={() => setCount(0)} />
|
|
217
|
-
</ButtonRow>
|
|
218
|
-
</InlineKeyboard>
|
|
219
|
-
</Message>
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const bot = createBot({
|
|
224
|
-
component: App,
|
|
225
|
-
adapter: new TelegramAdapter(),
|
|
226
|
-
commands: { start: { description: 'Start the counter' } },
|
|
227
|
-
});
|
|
228
|
-
|
|
229
|
-
bot.start();
|
|
230
|
-
`,
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function routerFiles(has: (f: string) => boolean): Record<string, string> {
|
|
235
|
-
const files: Record<string, string> = {};
|
|
236
|
-
|
|
237
|
-
// ---- teact.config.ts ----
|
|
238
|
-
const configPlugins: string[] = [];
|
|
239
|
-
const configImportsCore: string[] = ['defineConfig'];
|
|
240
|
-
const configImportsTelegram: string[] = [];
|
|
241
|
-
const configImportsStorage: string[] = [];
|
|
242
|
-
|
|
243
|
-
if (has('storage')) {
|
|
244
|
-
configImportsStorage.push('storagePlugin');
|
|
245
|
-
configPlugins.push("storagePlugin({ driver: 'file', path: '.teact/storage.json' })");
|
|
246
|
-
}
|
|
247
|
-
if (has('conversations')) {
|
|
248
|
-
configImportsTelegram.push('conversationsPlugin');
|
|
249
|
-
configPlugins.push('conversationsPlugin()');
|
|
250
|
-
}
|
|
251
|
-
if (has('streaming')) {
|
|
252
|
-
configImportsTelegram.push('streamPlugin');
|
|
253
|
-
configPlugins.push('streamPlugin()');
|
|
254
|
-
}
|
|
255
|
-
if (has('auth')) {
|
|
256
|
-
configImportsCore.push('authPlugin');
|
|
257
|
-
configPlugins.push('authPlugin({ admins: [] })');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
let configSrc = `import { ${configImportsCore.join(', ')} } from '@teactjs/core';\n`;
|
|
261
|
-
if (configImportsTelegram.length > 0) {
|
|
262
|
-
configSrc += `import { ${configImportsTelegram.join(', ')} } from '@teactjs/telegram';\n`;
|
|
263
|
-
}
|
|
264
|
-
if (configImportsStorage.length > 0) {
|
|
265
|
-
configSrc += `import { ${configImportsStorage.join(', ')} } from '@teactjs/storage';\n`;
|
|
266
|
-
}
|
|
267
|
-
configSrc += `\nexport default defineConfig({\n mode: 'polling',\n`;
|
|
268
|
-
if (configPlugins.length > 0) {
|
|
269
|
-
configSrc += `\n plugins: [\n ${configPlugins.join(',\n ')},\n ],\n`;
|
|
270
|
-
}
|
|
271
|
-
configSrc += `});\n`;
|
|
272
|
-
files['teact.config.ts'] = configSrc;
|
|
273
|
-
|
|
274
|
-
// ---- Routes and pages ----
|
|
275
|
-
const routes: Array<{ path: string; component: string; file: string }> = [
|
|
276
|
-
{ path: '/', component: 'MainMenu', file: './pages/MainMenu' },
|
|
277
|
-
{ path: '/about', component: 'About', file: './pages/About' },
|
|
278
|
-
];
|
|
279
|
-
const commands: Record<string, string> = {
|
|
280
|
-
start: "'/'",
|
|
281
|
-
about: "'/about'",
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
if (has('storage')) {
|
|
285
|
-
routes.push({ path: '/settings', component: 'Settings', file: './pages/Settings' });
|
|
286
|
-
commands['settings'] = "'/settings'";
|
|
287
|
-
}
|
|
288
|
-
if (has('streaming')) {
|
|
289
|
-
routes.push({ path: '/stream', component: 'StreamDemo', file: './pages/StreamDemo' });
|
|
290
|
-
commands['stream'] = "'/stream'";
|
|
291
|
-
}
|
|
292
|
-
if (has('i18n')) {
|
|
293
|
-
routes.push({ path: '/language', component: 'LanguagePage', file: './pages/LanguagePage' });
|
|
294
|
-
commands['language'] = "'/language'";
|
|
295
|
-
}
|
|
296
|
-
if (has('payments')) {
|
|
297
|
-
routes.push({ path: '/store', component: 'StorePage', file: './pages/StorePage' });
|
|
298
|
-
commands['store'] = "'/store'";
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// ---- src/index.tsx ----
|
|
302
|
-
let indexSrc = `import React from 'react';\n`;
|
|
303
|
-
indexSrc += `import { createBot, createRouter${has('i18n') ? ', createI18n' : ''} } from '@teactjs/core';\n`;
|
|
304
|
-
indexSrc += `import { TelegramAdapter } from '@teactjs/telegram';\n\n`;
|
|
305
|
-
|
|
306
|
-
for (const r of routes) {
|
|
307
|
-
indexSrc += `import { ${r.component} } from '${r.file}';\n`;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
if (has('i18n')) {
|
|
311
|
-
indexSrc += `\nimport en from './locales/en.json';\nimport am from './locales/am.json';\n`;
|
|
312
|
-
indexSrc += `\nconst i18n = createI18n({\n defaultLocale: 'en',\n resources: {\n en: { translation: en },\n am: { translation: am },\n },\n});\n`;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
indexSrc += `\nconst router = createRouter({\n`;
|
|
316
|
-
for (const r of routes) {
|
|
317
|
-
indexSrc += ` '${r.path}': ${r.component},\n`;
|
|
318
|
-
}
|
|
319
|
-
indexSrc += `});\n`;
|
|
320
|
-
|
|
321
|
-
indexSrc += `\nconst bot = createBot({\n adapter: new TelegramAdapter(),\n router,\n`;
|
|
322
|
-
if (has('i18n')) {
|
|
323
|
-
indexSrc += ` providers: ({ children }) => (\n <i18n.Provider>{children}</i18n.Provider>\n ),\n`;
|
|
324
|
-
}
|
|
325
|
-
indexSrc += ` commands: {\n`;
|
|
326
|
-
for (const [cmd, route] of Object.entries(commands)) {
|
|
327
|
-
indexSrc += ` ${cmd}: { description: '${cmd.charAt(0).toUpperCase() + cmd.slice(1)}', route: ${route} },\n`;
|
|
328
|
-
}
|
|
329
|
-
indexSrc += ` },\n});\n\nbot.start();\n`;
|
|
330
|
-
files['src/index.tsx'] = indexSrc;
|
|
331
|
-
|
|
332
|
-
// ---- MainMenu ----
|
|
333
|
-
const menuButtons: string[] = [];
|
|
334
|
-
menuButtons.push(` <Button text="ℹ️ About" onClick={() => navigate('/about')} />`);
|
|
335
|
-
if (has('storage')) menuButtons.push(` <Button text="⚙️ Settings" onClick={() => navigate('/settings')} />`);
|
|
336
|
-
if (has('streaming')) menuButtons.push(` <Button text="⚡ Stream Demo" onClick={() => navigate('/stream')} />`);
|
|
337
|
-
if (has('i18n')) menuButtons.push(` <Button text="🌐 Language" onClick={() => navigate('/language')} />`);
|
|
338
|
-
if (has('payments')) menuButtons.push(` <Button text="💎 Store" onClick={() => navigate('/store')} />`);
|
|
339
|
-
|
|
340
|
-
const menuRows: string[] = [];
|
|
341
|
-
for (let i = 0; i < menuButtons.length; i += 2) {
|
|
342
|
-
const pair = menuButtons.slice(i, i + 2);
|
|
343
|
-
menuRows.push(` <ButtonRow>\n${pair.join('\n')}\n </ButtonRow>`);
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
files['src/pages/MainMenu.tsx'] = `import { useNavigate } from '@teactjs/core';
|
|
347
|
-
import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
|
|
348
|
-
|
|
349
|
-
export function MainMenu() {
|
|
350
|
-
const navigate = useNavigate();
|
|
351
|
-
|
|
352
|
-
return (
|
|
353
|
-
<Message text="🤖 Welcome!\\n\\nPick an option:">
|
|
354
|
-
<InlineKeyboard>
|
|
355
|
-
${menuRows.join('\n')}
|
|
356
|
-
</InlineKeyboard>
|
|
357
|
-
</Message>
|
|
358
|
-
);
|
|
359
|
-
}
|
|
360
|
-
`;
|
|
361
|
-
|
|
362
|
-
// ---- About ----
|
|
363
|
-
files['src/pages/About.tsx'] = `import { useNavigate } from '@teactjs/core';
|
|
364
|
-
import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
|
|
365
|
-
|
|
366
|
-
export function About() {
|
|
367
|
-
const navigate = useNavigate();
|
|
368
|
-
|
|
369
|
-
return (
|
|
370
|
-
<Message text="ℹ️ About\\n\\nBuilt with Teact — React for Telegram bots.\\nhttps://github.com/leuliance/teact">
|
|
371
|
-
<InlineKeyboard>
|
|
372
|
-
<ButtonRow>
|
|
373
|
-
<Button text="🏠 Menu" onClick={() => navigate('/')} />
|
|
374
|
-
</ButtonRow>
|
|
375
|
-
</InlineKeyboard>
|
|
376
|
-
</Message>
|
|
377
|
-
);
|
|
378
|
-
}
|
|
379
|
-
`;
|
|
380
|
-
|
|
381
|
-
// ---- Settings (storage) ----
|
|
382
|
-
if (has('storage')) {
|
|
383
|
-
files['src/pages/Settings.tsx'] = `import { useNavigate } from '@teactjs/core';
|
|
384
|
-
import { useStorage } from '@teactjs/storage';
|
|
385
|
-
import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
|
|
386
|
-
|
|
387
|
-
export function Settings() {
|
|
388
|
-
const navigate = useNavigate();
|
|
389
|
-
const [theme, setTheme] = useStorage('theme', 'light');
|
|
390
|
-
const [notify, setNotify] = useStorage('notifications', true);
|
|
391
|
-
|
|
392
|
-
return (
|
|
393
|
-
<Message text={\`⚙️ Settings\\n\\n🎨 Theme: \${theme}\\n🔔 Notifications: \${notify ? 'On' : 'Off'}\`}>
|
|
394
|
-
<InlineKeyboard>
|
|
395
|
-
<ButtonRow>
|
|
396
|
-
<Button text={\`🎨 \${theme}\`} onClick={() => setTheme(t => t === 'light' ? 'dark' : 'light')} />
|
|
397
|
-
<Button text={\`🔔 \${notify ? 'On' : 'Off'}\`} onClick={() => setNotify(n => !n)} />
|
|
398
|
-
</ButtonRow>
|
|
399
|
-
<ButtonRow>
|
|
400
|
-
<Button text="🏠 Menu" onClick={() => navigate('/')} />
|
|
401
|
-
</ButtonRow>
|
|
402
|
-
</InlineKeyboard>
|
|
403
|
-
</Message>
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
`;
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
// ---- StreamDemo ----
|
|
410
|
-
if (has('streaming')) {
|
|
411
|
-
files['src/pages/StreamDemo.tsx'] = `import { useNavigate, useStream } from '@teactjs/core';
|
|
412
|
-
import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
|
|
413
|
-
|
|
414
|
-
const delay = (ms: number) => new Promise(r => setTimeout(r, ms));
|
|
415
|
-
|
|
416
|
-
async function* generateText() {
|
|
417
|
-
yield 'Generating';
|
|
418
|
-
await delay(500);
|
|
419
|
-
yield ' your ';
|
|
420
|
-
await delay(500);
|
|
421
|
-
yield 'response...\\n\\n';
|
|
422
|
-
await delay(600);
|
|
423
|
-
yield 'Hello from the stream!';
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
export function StreamDemo() {
|
|
427
|
-
const navigate = useNavigate();
|
|
428
|
-
const { text, isStreaming, stream } = useStream({ throttleMs: 400 });
|
|
429
|
-
|
|
430
|
-
return (
|
|
431
|
-
<Message text={text || '⚡ Stream Demo\\n\\nWatch text generate live!'}>
|
|
432
|
-
<InlineKeyboard>
|
|
433
|
-
<ButtonRow>
|
|
434
|
-
<Button
|
|
435
|
-
text={isStreaming ? '⏳ Streaming...' : '▶️ Start Stream'}
|
|
436
|
-
onClick={() => { if (!isStreaming) stream(generateText()); }}
|
|
437
|
-
/>
|
|
438
|
-
</ButtonRow>
|
|
439
|
-
<ButtonRow>
|
|
440
|
-
<Button text="🏠 Menu" onClick={() => navigate('/')} />
|
|
441
|
-
</ButtonRow>
|
|
442
|
-
</InlineKeyboard>
|
|
443
|
-
</Message>
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
`;
|
|
447
|
-
}
|
|
448
|
-
|
|
449
|
-
// ---- LanguagePage (i18n) ----
|
|
450
|
-
if (has('i18n')) {
|
|
451
|
-
files['src/locales/en.json'] = `{
|
|
452
|
-
"welcome": "Welcome!",
|
|
453
|
-
"about": "About this bot",
|
|
454
|
-
"settings": "Settings",
|
|
455
|
-
"language": "Language",
|
|
456
|
-
"back": "Back"
|
|
457
|
-
}
|
|
458
|
-
`;
|
|
459
|
-
files['src/locales/am.json'] = `{
|
|
460
|
-
"welcome": "እንኳን ደህና መጡ!",
|
|
461
|
-
"about": "ስለ ቦት",
|
|
462
|
-
"settings": "ቅንብሮች",
|
|
463
|
-
"language": "ቋንቋ",
|
|
464
|
-
"back": "ተመለስ"
|
|
465
|
-
}
|
|
466
|
-
`;
|
|
467
|
-
files['src/pages/LanguagePage.tsx'] = `import { useNavigate, useLocale } from '@teactjs/core';
|
|
468
|
-
import { Message, Button, InlineKeyboard, ButtonRow } from '@teactjs/ui';
|
|
469
|
-
|
|
470
|
-
export function LanguagePage() {
|
|
471
|
-
const navigate = useNavigate();
|
|
472
|
-
const { t, locale, setLocale } = useLocale();
|
|
473
|
-
|
|
474
|
-
return (
|
|
475
|
-
<Message text={\`🌐 \${t('language')}\\n\\nCurrent: \${locale}\`}>
|
|
476
|
-
<InlineKeyboard>
|
|
477
|
-
<ButtonRow>
|
|
478
|
-
<Button text="🇬🇧 English" onClick={() => setLocale('en')} />
|
|
479
|
-
<Button text="🇪🇹 አማርኛ" onClick={() => setLocale('am')} />
|
|
480
|
-
</ButtonRow>
|
|
481
|
-
<ButtonRow>
|
|
482
|
-
<Button text="🏠 Menu" onClick={() => navigate('/')} />
|
|
483
|
-
</ButtonRow>
|
|
484
|
-
</InlineKeyboard>
|
|
485
|
-
</Message>
|
|
486
|
-
);
|
|
487
|
-
}
|
|
488
|
-
`;
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// ---- StorePage (payments) ----
|
|
492
|
-
if (has('payments')) {
|
|
493
|
-
files['src/pages/StorePage.tsx'] = `import { useNavigate, useInvoice } from '@teactjs/core';
|
|
494
|
-
import { Message, Button, InlineKeyboard, ButtonRow, Alert } from '@teactjs/ui';
|
|
495
|
-
|
|
496
|
-
export function StorePage() {
|
|
497
|
-
const navigate = useNavigate();
|
|
498
|
-
const invoice = useInvoice({
|
|
499
|
-
title: 'Premium Access',
|
|
500
|
-
description: 'Unlock all features for 30 days',
|
|
501
|
-
payload: 'premium-30d',
|
|
502
|
-
providerToken: process.env.PAYMENT_PROVIDER_TOKEN ?? '',
|
|
503
|
-
currency: 'USD',
|
|
504
|
-
prices: [{ label: 'Premium (30 days)', amount: 499 }],
|
|
505
|
-
});
|
|
506
|
-
|
|
507
|
-
if (invoice.status === 'success') {
|
|
508
|
-
return (
|
|
509
|
-
<Message text={\`✅ Payment successful!\\n\\nCharge ID: \${invoice.receipt?.telegramPaymentChargeId}\`}>
|
|
510
|
-
<InlineKeyboard>
|
|
511
|
-
<ButtonRow>
|
|
512
|
-
<Button text="🏠 Menu" onClick={() => navigate('/')} />
|
|
513
|
-
</ButtonRow>
|
|
514
|
-
</InlineKeyboard>
|
|
515
|
-
</Message>
|
|
516
|
-
);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (invoice.status === 'failed') {
|
|
520
|
-
return (
|
|
521
|
-
<>
|
|
522
|
-
<Alert variant="error" title="Payment Failed">
|
|
523
|
-
{invoice.error ?? 'Unknown error'}
|
|
524
|
-
</Alert>
|
|
525
|
-
<Message text="Please check your payment configuration.">
|
|
526
|
-
<InlineKeyboard>
|
|
527
|
-
<ButtonRow>
|
|
528
|
-
<Button text="🔄 Retry" onClick={invoice.send} />
|
|
529
|
-
<Button text="🏠 Menu" onClick={() => navigate('/')} />
|
|
530
|
-
</ButtonRow>
|
|
531
|
-
</InlineKeyboard>
|
|
532
|
-
</Message>
|
|
533
|
-
</>
|
|
534
|
-
);
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
return (
|
|
538
|
-
<Message text={\`💎 Premium Access\\n\\nUnlock all features.\\n💰 Price: $4.99\${invoice.status === 'pending' ? '\\n\\n⏳ Processing...' : ''}\`}>
|
|
539
|
-
<InlineKeyboard>
|
|
540
|
-
<ButtonRow>
|
|
541
|
-
<Button text="💳 Buy — $4.99" onClick={invoice.send} />
|
|
542
|
-
</ButtonRow>
|
|
543
|
-
<ButtonRow>
|
|
544
|
-
<Button text="🏠 Menu" onClick={() => navigate('/')} />
|
|
545
|
-
</ButtonRow>
|
|
546
|
-
</InlineKeyboard>
|
|
547
|
-
</Message>
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
`;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
return files;
|
|
554
|
-
}
|
|
555
|
-
|
|
556
155
|
main().catch((err) => {
|
|
557
156
|
console.error(err);
|
|
558
157
|
process.exit(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-teact",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.8",
|
|
4
4
|
"description": "Create a new Teact bot project — scaffolds a ready-to-run Telegram bot",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
},
|
|
9
9
|
"files": ["index.ts", "README.md"],
|
|
10
10
|
"dependencies": {
|
|
11
|
-
"@clack/prompts": "^1.1.0"
|
|
11
|
+
"@clack/prompts": "^1.1.0",
|
|
12
|
+
"@teactjs/bot-templates": "workspace:*"
|
|
12
13
|
},
|
|
13
14
|
"license": "MIT"
|
|
14
15
|
}
|