create-teact 0.1.0-alpha.7 → 0.1.0-alpha.9

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.
Files changed (3) hide show
  1. package/README.md +32 -33
  2. package/index.ts +41 -442
  3. 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: `router`, `counter`, `full`, or `empty`
22
- 3. Lets you select features: storage, conversations, streaming, auth, i18n, payments
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 the project with `package.json`, `tsconfig.json`, `teact.config.ts`, `.env`, `.gitignore`, and `src/` files
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` | Multi-page bot with routing and pages directory (recommended) |
33
- | `counter` | Minimal counter bot with inline keyboard |
34
- | `full` | Router + all features pre-wired (storage, conversations, streaming, auth, i18n, payments) |
35
- | `empty` | Bare project with no pages |
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 | `teact.config.ts` with `storagePlugin`, Settings page with `useStorage` |
44
- | Conversations | `conversationsPlugin` in config |
45
- | Streaming | `streamPlugin` in config, StreamDemo page with `useStream` |
46
- | Auth | `authPlugin` in config |
47
- | i18n | Locale JSON files, LanguagePage with `useLocale`, `createI18n` in index |
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 Structure
53
+ ## Generated structure (varies by template)
51
54
 
52
55
  ```
53
56
  my-bot/
54
- ├── teact.config.ts # Plugin configuration
57
+ ├── teact.config.ts
55
58
  ├── package.json
56
59
  ├── tsconfig.json
57
- ├── .env # Bot token + provider token
60
+ ├── .env
58
61
  ├── .gitignore
59
62
  └── src/
60
- ├── index.tsx # Bot entry — router, commands, providers
63
+ ├── index.tsx
64
+ ├── commands.ts # (Showcase)
65
+ ├── api/ # (Showcase — PokeAPI)
66
+ ├── hooks/
61
67
  ├── pages/
62
- │ ├── MainMenu.tsx # Home page with feature buttons
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 template = await p.select({
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(template)) { p.cancel('Cancelled'); process.exit(0); }
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: 'Select features',
44
- options: [
45
- { value: 'storage', label: 'Storage', hint: 'persistent state with useStorage' },
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 has = (f: string) => template === 'full' || features.includes(f);
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: deps,
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
- let envContent = 'TELEGRAM_BOT_TOKEN=\n';
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 = buildFiles(template, features);
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
- const installSpin = p.spinner();
143
- installSpin.start(`Installing with ${pm}`);
144
- try {
145
- const proc = Bun.spawnSync([pm, 'install'], { cwd: dir, stderr: 'pipe', stdout: 'pipe' });
146
- installSpin.stop(proc.exitCode === 0 ? 'Dependencies installed' : 'Install failed — run manually');
147
- } catch {
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.7",
3
+ "version": "0.1.0-alpha.9",
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": "^0.1.0-alpha.9"
12
13
  },
13
14
  "license": "MIT"
14
15
  }