ugly-app 0.1.41 → 0.1.42
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 +49 -6
- package/dist/cli/scaffold.js +2 -2
- package/dist/cli/scaffold.js.map +1 -1
- package/dist/cli/upgrade.d.ts.map +1 -1
- package/dist/cli/upgrade.js +1 -0
- package/dist/cli/upgrade.js.map +1 -1
- package/package.json +1 -1
- package/templates/client/pages/AITestPage.tsx +90 -55
- package/templates/client/pages/AuthDemoPage.tsx +41 -44
- package/templates/client/pages/HomePage.tsx +54 -77
- package/templates/gitignore +1 -0
package/README.md
CHANGED
|
@@ -29,7 +29,14 @@ npm run dev
|
|
|
29
29
|
Entry point for the server. Creates an Express + WebSocket server with typed RPC, auth, and all framework services.
|
|
30
30
|
|
|
31
31
|
```typescript
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
createApp,
|
|
34
|
+
createUserHelper,
|
|
35
|
+
getFeedbackHandlers,
|
|
36
|
+
type CallHandlers,
|
|
37
|
+
type RequestHandlers,
|
|
38
|
+
type HandlerContext,
|
|
39
|
+
} from 'ugly-app';
|
|
33
40
|
import { dbDefaults } from 'ugly-app/shared';
|
|
34
41
|
import { functions, requests } from '../shared/api';
|
|
35
42
|
import { collections } from '../shared/collections';
|
|
@@ -39,6 +46,7 @@ import type { User } from '../shared/types';
|
|
|
39
46
|
const userHelper = createUserHelper<User>(collections.user);
|
|
40
47
|
|
|
41
48
|
const calls = {
|
|
49
|
+
...getFeedbackHandlers(maintainBotUserId),
|
|
42
50
|
myFunction: async (ctx: HandlerContext, input) => {
|
|
43
51
|
return { greeting: `Hello, ${input.name}` };
|
|
44
52
|
},
|
|
@@ -421,8 +429,8 @@ await ctx.db.setDocFields(collections.note, id, { title: 'New title' });
|
|
|
421
429
|
// Partial update — returns null if document doesn't exist (no error)
|
|
422
430
|
const doc = await ctx.db.setDocFieldsOrIgnore(collections.note, id, { title });
|
|
423
431
|
|
|
424
|
-
// Partial update — creates the document if it doesn't exist
|
|
425
|
-
await ctx.db.setDocFieldsOrCreate(collections.note, id, { title });
|
|
432
|
+
// Partial update — creates the document if it doesn't exist (obj = default doc for insert)
|
|
433
|
+
await ctx.db.setDocFieldsOrCreate(collections.note, id, { title }, defaultDoc);
|
|
426
434
|
|
|
427
435
|
// MongoDB update operators ($inc, $addToSet, $pull, $unset, $set)
|
|
428
436
|
await ctx.db.setDocOp(collections.note, id, { $inc: { views: 1 } });
|
|
@@ -460,7 +468,8 @@ const key = ctx.db.cacheKey('prefix', id); // generate a cache key
|
|
|
460
468
|
### Helpers
|
|
461
469
|
|
|
462
470
|
```typescript
|
|
463
|
-
import {
|
|
471
|
+
import { createUserHelper } from 'ugly-app';
|
|
472
|
+
import { dbDefaults } from 'ugly-app/shared';
|
|
464
473
|
|
|
465
474
|
// dbDefaults() returns { version: 1, created: new Date(), updated: new Date() }
|
|
466
475
|
const doc = { id: newId(), ...dbDefaults(), title: 'Hello' };
|
|
@@ -547,9 +556,19 @@ registerTextGenProvider('myProvider', myTextGenImplementation);
|
|
|
547
556
|
registerImageGenProvider('myProvider', myImageGenImplementation);
|
|
548
557
|
```
|
|
549
558
|
|
|
550
|
-
### AI
|
|
559
|
+
### Client-side AI calls
|
|
560
|
+
|
|
561
|
+
The client can call AI through the server proxy without managing tokens directly:
|
|
551
562
|
|
|
552
|
-
|
|
563
|
+
```typescript
|
|
564
|
+
import { callTextGen, callJsonGen, callImageGen } from 'ugly-app/client';
|
|
565
|
+
|
|
566
|
+
const text = await callTextGen(messages, options);
|
|
567
|
+
const json = await callJsonGen(schema, messages);
|
|
568
|
+
const image = await callImageGen(prompt, options);
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
These call `POST /ai/request` (same-origin, avoids CORS). The server verifies the user JWT and forwards to `https://ugly.bot` using the `UGLY_BOT_TOKEN` env var.
|
|
553
572
|
|
|
554
573
|
---
|
|
555
574
|
|
|
@@ -622,6 +641,30 @@ configurator.setWorkerQueue(queue);
|
|
|
622
641
|
await enqueueTask('taskName', payload);
|
|
623
642
|
```
|
|
624
643
|
|
|
644
|
+
### Client error capture
|
|
645
|
+
|
|
646
|
+
```typescript
|
|
647
|
+
import { captureClientError, initClientLogger } from 'ugly-app/client';
|
|
648
|
+
|
|
649
|
+
initClientLogger(); // call once at startup — captures unhandled errors
|
|
650
|
+
captureClientError(error); // manually report an error to POST /api/client-error
|
|
651
|
+
```
|
|
652
|
+
|
|
653
|
+
### Feedback
|
|
654
|
+
|
|
655
|
+
```typescript
|
|
656
|
+
import { FeedbackButton, setFeedbackContext, clearFeedbackContext } from 'ugly-app/client';
|
|
657
|
+
|
|
658
|
+
// Render the built-in feedback button (bottom-right, always at [data-id="feedback-button"])
|
|
659
|
+
<FeedbackButton />
|
|
660
|
+
|
|
661
|
+
// Set contextual data attached to feedback submissions
|
|
662
|
+
setFeedbackContext({ page: 'editor', noteId: '123' });
|
|
663
|
+
clearFeedbackContext();
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Server-side, `getFeedbackHandlers(maintainBotUserId)` provides the RPC handlers for submitting and managing feedback. User feedback history is available at `GET /my_feedback` (requires auth cookie, returns markdown).
|
|
667
|
+
|
|
625
668
|
### Rate limiting
|
|
626
669
|
|
|
627
670
|
```typescript
|
package/dist/cli/scaffold.js
CHANGED
|
@@ -75,6 +75,8 @@ export async function scaffoldProject(projectName) {
|
|
|
75
75
|
// Generate .env from .env.example with a real JWT_SECRET and optional keys commented out
|
|
76
76
|
const envContent = generateEnv(path.join(destDir, '.env.example'), projectName);
|
|
77
77
|
await fs.writeFile(path.join(destDir, '.env'), envContent, 'utf-8');
|
|
78
|
+
console.log('[ugly-app] Installing dependencies...');
|
|
79
|
+
execSync('npm install', { cwd: destDir, stdio: 'inherit' });
|
|
78
80
|
console.log('[ugly-app] Initialising git repository...');
|
|
79
81
|
execSync('git init', { cwd: destDir, stdio: 'inherit' });
|
|
80
82
|
execSync('git add .', { cwd: destDir, stdio: 'inherit' });
|
|
@@ -82,8 +84,6 @@ export async function scaffoldProject(projectName) {
|
|
|
82
84
|
cwd: destDir,
|
|
83
85
|
stdio: 'inherit',
|
|
84
86
|
});
|
|
85
|
-
console.log('[ugly-app] Installing dependencies...');
|
|
86
|
-
execSync('npm install', { cwd: destDir, stdio: 'inherit' });
|
|
87
87
|
const portStart = 3000;
|
|
88
88
|
const projectId = nanoid(10).toLowerCase();
|
|
89
89
|
writeUglyAppConfig(destDir, { projectId, portStart });
|
package/dist/cli/scaffold.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"scaffold.js","sourceRoot":"","sources":["../../src/cli/scaffold.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/cli/upgrade.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"upgrade.d.ts","sourceRoot":"","sources":["../../src/cli/upgrade.ts"],"names":[],"mappings":"AA4BA,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAgIhD"}
|
package/dist/cli/upgrade.js
CHANGED
package/dist/cli/upgrade.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/cli/upgrade.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,MAAM,UAAU,CAAC;AAC1B,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AAEpC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC/D,MAAM,aAAa,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,iBAAiB,CAAC,CAAC;AAE9D,MAAM,YAAY,GAAG;IACnB,eAAe;IACf,gBAAgB;IAChB,oBAAoB;IACpB,mBAAmB;IACnB,oBAAoB;IACpB,eAAe;IACf,mBAAmB;IACnB,WAAW;IACX,sBAAsB;IACtB,mBAAmB;
|
|
1
|
+
{"version":3,"file":"upgrade.js","sourceRoot":"","sources":["../../src/cli/upgrade.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,7 +4,6 @@ import {
|
|
|
4
4
|
Card,
|
|
5
5
|
Input,
|
|
6
6
|
PageLayout,
|
|
7
|
-
Text,
|
|
8
7
|
callImageGen,
|
|
9
8
|
callTextGen,
|
|
10
9
|
} from 'ugly-app/client';
|
|
@@ -12,83 +11,102 @@ import type { ImageGenModel, TextGenModel } from 'ugly-app/shared';
|
|
|
12
11
|
import { imageGenModels, textGenModels } from 'ugly-app/shared';
|
|
13
12
|
|
|
14
13
|
type Mode = 'text' | 'image';
|
|
14
|
+
type LogEntry = { ts: number; msg: string; kind: 'info' | 'ok' | 'err' };
|
|
15
|
+
|
|
16
|
+
function fmt(ms: number): string {
|
|
17
|
+
return ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
18
|
+
}
|
|
15
19
|
|
|
16
20
|
export default function AITestPage(): React.ReactElement {
|
|
17
21
|
const [mode, setMode] = useState<Mode>('text');
|
|
18
22
|
const [prompt, setPrompt] = useState('');
|
|
19
23
|
const [textModel, setTextModel] = useState<TextGenModel>(textGenModels[0]!);
|
|
20
|
-
const [imageModel, setImageModel] = useState<ImageGenModel>(
|
|
21
|
-
imageGenModels[0]!,
|
|
22
|
-
);
|
|
24
|
+
const [imageModel, setImageModel] = useState<ImageGenModel>(imageGenModels[0]!);
|
|
23
25
|
const [result, setResult] = useState('');
|
|
24
26
|
const [imageUrl, setImageUrl] = useState('');
|
|
25
27
|
const [loading, setLoading] = useState(false);
|
|
26
|
-
const [
|
|
28
|
+
const [logs, setLogs] = useState<LogEntry[]>([]);
|
|
29
|
+
|
|
30
|
+
function addLog(msg: string, kind: LogEntry['kind'] = 'info'): number {
|
|
31
|
+
const ts = Date.now();
|
|
32
|
+
setLogs((prev) => [...prev, { ts, msg, kind }]);
|
|
33
|
+
return ts;
|
|
34
|
+
}
|
|
27
35
|
|
|
28
36
|
async function handleRun(): Promise<void> {
|
|
29
37
|
if (!prompt.trim()) return;
|
|
30
38
|
setLoading(true);
|
|
31
|
-
setError('');
|
|
32
39
|
setResult('');
|
|
33
40
|
setImageUrl('');
|
|
41
|
+
setLogs([]);
|
|
42
|
+
|
|
43
|
+
const started = Date.now();
|
|
44
|
+
const model = mode === 'text' ? textModel : imageModel;
|
|
45
|
+
addLog(`Using model: ${model}`);
|
|
46
|
+
addLog(`Sending ${mode} request…`);
|
|
47
|
+
|
|
34
48
|
try {
|
|
35
49
|
if (mode === 'text') {
|
|
36
50
|
const text = await callTextGen({
|
|
37
51
|
model: textModel,
|
|
38
52
|
messages: [{ role: 'user', content: prompt }],
|
|
39
53
|
});
|
|
54
|
+
const elapsed = Date.now() - started;
|
|
55
|
+
addLog(`Done in ${fmt(elapsed)} — ${text.length} chars`, 'ok');
|
|
40
56
|
setResult(text);
|
|
41
57
|
} else {
|
|
42
58
|
const url = await callImageGen({ model: imageModel, prompt });
|
|
59
|
+
const elapsed = Date.now() - started;
|
|
60
|
+
addLog(`Done in ${fmt(elapsed)}`, 'ok');
|
|
43
61
|
setImageUrl(url);
|
|
44
62
|
}
|
|
45
63
|
} catch (err) {
|
|
46
|
-
|
|
64
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
65
|
+
addLog(msg, 'err');
|
|
47
66
|
} finally {
|
|
48
67
|
setLoading(false);
|
|
49
68
|
}
|
|
50
69
|
}
|
|
51
70
|
|
|
52
71
|
const model = mode === 'text' ? textModel : imageModel;
|
|
53
|
-
const models: readonly string[] =
|
|
54
|
-
mode === 'text' ? textGenModels : imageGenModels;
|
|
72
|
+
const models: readonly string[] = mode === 'text' ? textGenModels : imageGenModels;
|
|
55
73
|
|
|
56
74
|
return (
|
|
57
75
|
<PageLayout
|
|
58
76
|
header={
|
|
59
|
-
<div className="px-6 py-4">
|
|
60
|
-
<a href="/" className="text-
|
|
77
|
+
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
78
|
+
<a href="/" className="text-sm text-gray-500 hover:text-gray-900 dark:hover:text-gray-100 transition-colors">
|
|
61
79
|
← Home
|
|
62
80
|
</a>
|
|
63
81
|
</div>
|
|
64
82
|
}
|
|
65
83
|
>
|
|
66
|
-
<div className="max-w-2xl mx-auto">
|
|
67
|
-
<
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
<div className="max-w-2xl mx-auto px-4 py-6">
|
|
85
|
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-6">AI Test</h1>
|
|
86
|
+
|
|
87
|
+
{/* Mode toggle */}
|
|
88
|
+
<div className="flex gap-1 p-1 bg-gray-100 dark:bg-gray-800 rounded-lg w-fit mb-6">
|
|
89
|
+
{(['text', 'image'] as Mode[]).map((m) => (
|
|
90
|
+
<button
|
|
91
|
+
key={m}
|
|
92
|
+
onClick={() => setMode(m)}
|
|
93
|
+
className={`px-4 py-1.5 rounded-md text-sm font-medium transition-colors capitalize ${
|
|
94
|
+
mode === m
|
|
95
|
+
? 'bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm'
|
|
96
|
+
: 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'
|
|
97
|
+
}`}
|
|
98
|
+
>
|
|
99
|
+
{m}
|
|
100
|
+
</button>
|
|
101
|
+
))}
|
|
84
102
|
</div>
|
|
85
103
|
|
|
86
|
-
<Card className="
|
|
104
|
+
<Card className="mb-4">
|
|
87
105
|
<div className="space-y-4">
|
|
88
106
|
<div>
|
|
89
|
-
<
|
|
107
|
+
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
|
|
90
108
|
Model
|
|
91
|
-
</
|
|
109
|
+
</label>
|
|
92
110
|
<select
|
|
93
111
|
value={model}
|
|
94
112
|
onChange={(e) =>
|
|
@@ -96,12 +114,10 @@ export default function AITestPage(): React.ReactElement {
|
|
|
96
114
|
? setTextModel(e.target.value as TextGenModel)
|
|
97
115
|
: setImageModel(e.target.value as ImageGenModel)
|
|
98
116
|
}
|
|
99
|
-
className="w-full border border-gray-
|
|
117
|
+
className="w-full border border-gray-200 dark:border-gray-700 rounded-lg px-3 py-2 text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
100
118
|
>
|
|
101
119
|
{models.map((m) => (
|
|
102
|
-
<option key={m} value={m}>
|
|
103
|
-
{m}
|
|
104
|
-
</option>
|
|
120
|
+
<option key={m} value={m}>{m}</option>
|
|
105
121
|
))}
|
|
106
122
|
</select>
|
|
107
123
|
</div>
|
|
@@ -110,42 +126,61 @@ export default function AITestPage(): React.ReactElement {
|
|
|
110
126
|
label="Prompt"
|
|
111
127
|
value={prompt}
|
|
112
128
|
onChange={setPrompt}
|
|
113
|
-
placeholder={
|
|
114
|
-
mode === 'text' ? 'Ask something...' : 'Describe an image...'
|
|
115
|
-
}
|
|
129
|
+
placeholder={mode === 'text' ? 'Ask something…' : 'Describe an image…'}
|
|
116
130
|
/>
|
|
117
131
|
|
|
118
|
-
<Button
|
|
119
|
-
{
|
|
132
|
+
<Button
|
|
133
|
+
onClick={handleRun}
|
|
134
|
+
disabled={loading || !prompt.trim()}
|
|
135
|
+
className="w-full"
|
|
136
|
+
>
|
|
137
|
+
{loading ? 'Running…' : 'Run'}
|
|
120
138
|
</Button>
|
|
121
139
|
</div>
|
|
122
140
|
</Card>
|
|
123
141
|
|
|
124
|
-
{
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
142
|
+
{/* Log panel — always visible once a run starts */}
|
|
143
|
+
{logs.length > 0 && (
|
|
144
|
+
<div className="mb-4 rounded-lg border border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900 p-3 font-mono text-xs space-y-1">
|
|
145
|
+
{logs.map((entry, i) => (
|
|
146
|
+
<div
|
|
147
|
+
key={i}
|
|
148
|
+
className={
|
|
149
|
+
entry.kind === 'err'
|
|
150
|
+
? 'text-red-500'
|
|
151
|
+
: entry.kind === 'ok'
|
|
152
|
+
? 'text-green-600 dark:text-green-400'
|
|
153
|
+
: 'text-gray-500 dark:text-gray-400'
|
|
154
|
+
}
|
|
155
|
+
>
|
|
156
|
+
{entry.kind === 'err' ? '✗' : entry.kind === 'ok' ? '✓' : '·'} {entry.msg}
|
|
157
|
+
</div>
|
|
158
|
+
))}
|
|
159
|
+
{loading && (
|
|
160
|
+
<div className="text-blue-500 animate-pulse">· waiting…</div>
|
|
161
|
+
)}
|
|
162
|
+
</div>
|
|
130
163
|
)}
|
|
131
164
|
|
|
165
|
+
{/* Text result */}
|
|
132
166
|
{result && (
|
|
133
|
-
<Card
|
|
134
|
-
<
|
|
167
|
+
<Card>
|
|
168
|
+
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">
|
|
135
169
|
Result
|
|
136
|
-
</
|
|
137
|
-
<pre className="text-sm whitespace-pre-wrap break-words
|
|
170
|
+
</p>
|
|
171
|
+
<pre className="text-sm whitespace-pre-wrap break-words text-gray-800 dark:text-gray-200 leading-relaxed">
|
|
138
172
|
{result}
|
|
139
173
|
</pre>
|
|
140
174
|
</Card>
|
|
141
175
|
)}
|
|
142
176
|
|
|
177
|
+
{/* Image result */}
|
|
143
178
|
{imageUrl && (
|
|
144
|
-
<Card
|
|
145
|
-
<
|
|
179
|
+
<Card>
|
|
180
|
+
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">
|
|
146
181
|
Result
|
|
147
|
-
</
|
|
148
|
-
<img src={imageUrl} alt="Generated" className="w-full rounded" />
|
|
182
|
+
</p>
|
|
183
|
+
<img src={imageUrl} alt="Generated" className="w-full rounded-lg" />
|
|
149
184
|
</Card>
|
|
150
185
|
)}
|
|
151
186
|
</div>
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Button, Card,
|
|
3
|
-
|
|
2
|
+
import { Button, Card, PageLayout, Text, useApp } from 'ugly-app/client';
|
|
3
|
+
|
|
4
|
+
function openLogin(): void {
|
|
5
|
+
window.open(
|
|
6
|
+
`https://ugly.bot/oauth?origin=${encodeURIComponent(window.location.origin)}`,
|
|
7
|
+
'ugly-bot-login',
|
|
8
|
+
`width=480,height=640,left=${Math.round(window.screenX + (window.outerWidth - 480) / 2)},top=${Math.round(window.screenY + (window.outerHeight - 640) / 2)}`,
|
|
9
|
+
);
|
|
10
|
+
function onMessage(event: MessageEvent): void {
|
|
11
|
+
if (event.origin !== 'https://ugly.bot') return;
|
|
12
|
+
const data = event.data as { type?: string; code?: string } | null;
|
|
13
|
+
if (!data || data.type !== 'ugly-bot-oauth' || !data.code) return;
|
|
14
|
+
window.removeEventListener('message', onMessage);
|
|
15
|
+
void fetch('/auth/verify', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: JSON.stringify({ code: data.code }),
|
|
19
|
+
}).then((res) => {
|
|
20
|
+
if (res.ok) window.location.reload();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
window.addEventListener('message', onMessage);
|
|
24
|
+
}
|
|
4
25
|
|
|
5
26
|
function AuthDemoAuthenticated(): React.ReactElement {
|
|
6
27
|
const app = useApp();
|
|
@@ -13,29 +34,21 @@ function AuthDemoAuthenticated(): React.ReactElement {
|
|
|
13
34
|
return (
|
|
14
35
|
<PageLayout
|
|
15
36
|
header={
|
|
16
|
-
<div className="px-6 py-4">
|
|
17
|
-
<a href="/" className="text-
|
|
37
|
+
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
38
|
+
<a href="/" className="text-sm text-gray-500 hover:text-gray-900 dark:hover:text-gray-100 transition-colors">
|
|
18
39
|
← Home
|
|
19
40
|
</a>
|
|
20
41
|
</div>
|
|
21
42
|
}
|
|
22
43
|
>
|
|
23
|
-
<div className="max-w-md mx-auto">
|
|
24
|
-
<
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<pre className="
|
|
30
|
-
{JSON.stringify(
|
|
31
|
-
{
|
|
32
|
-
userId: app.userId,
|
|
33
|
-
email: app.user.email,
|
|
34
|
-
phone: app.user.phone,
|
|
35
|
-
},
|
|
36
|
-
null,
|
|
37
|
-
2,
|
|
38
|
-
)}
|
|
44
|
+
<div className="max-w-md mx-auto px-4 py-6">
|
|
45
|
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4">Auth Demo</h1>
|
|
46
|
+
<Card>
|
|
47
|
+
<p className="text-xs font-medium text-gray-500 dark:text-gray-400 uppercase tracking-wide mb-2">
|
|
48
|
+
Logged in
|
|
49
|
+
</p>
|
|
50
|
+
<pre className="text-sm bg-gray-50 dark:bg-gray-900 p-3 rounded-lg overflow-auto text-gray-800 dark:text-gray-200">
|
|
51
|
+
{JSON.stringify({ userId: app.userId, email: app.user.email, phone: app.user.phone }, null, 2)}
|
|
39
52
|
</pre>
|
|
40
53
|
<Button variant="secondary" onClick={handleLogout} className="mt-4">
|
|
41
54
|
Logout
|
|
@@ -47,36 +60,21 @@ function AuthDemoAuthenticated(): React.ReactElement {
|
|
|
47
60
|
}
|
|
48
61
|
|
|
49
62
|
function AuthDemoUnauthenticated(): React.ReactElement {
|
|
50
|
-
const { openPopup } = useRouter();
|
|
51
|
-
|
|
52
|
-
function handleLogin(): void {
|
|
53
|
-
openPopup(
|
|
54
|
-
<LoginPopup
|
|
55
|
-
inline
|
|
56
|
-
|
|
57
|
-
onSuccess={() => { window.location.reload(); }}
|
|
58
|
-
/>,
|
|
59
|
-
{ mode: 'transient' },
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
63
|
return (
|
|
64
64
|
<PageLayout
|
|
65
65
|
header={
|
|
66
|
-
<div className="px-6 py-4">
|
|
67
|
-
<a href="/" className="text-
|
|
66
|
+
<div className="px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
67
|
+
<a href="/" className="text-sm text-gray-500 hover:text-gray-900 dark:hover:text-gray-100 transition-colors">
|
|
68
68
|
← Home
|
|
69
69
|
</a>
|
|
70
70
|
</div>
|
|
71
71
|
}
|
|
72
72
|
>
|
|
73
|
-
<div className="max-w-md mx-auto">
|
|
74
|
-
<
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
<Text>You are not logged in.</Text>
|
|
79
|
-
<Button onClick={handleLogin} className="mt-4">
|
|
73
|
+
<div className="max-w-md mx-auto px-4 py-6">
|
|
74
|
+
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-4">Auth Demo</h1>
|
|
75
|
+
<Card>
|
|
76
|
+
<Text className="text-gray-600 dark:text-gray-400">You are not logged in.</Text>
|
|
77
|
+
<Button onClick={openLogin} className="mt-4">
|
|
80
78
|
Login with ugly.bot
|
|
81
79
|
</Button>
|
|
82
80
|
</Card>
|
|
@@ -86,8 +84,7 @@ function AuthDemoUnauthenticated(): React.ReactElement {
|
|
|
86
84
|
}
|
|
87
85
|
|
|
88
86
|
export default function AuthDemoPage(): React.ReactElement {
|
|
89
|
-
const isLoggedIn = !!(window as unknown as { __AUTH_TOKEN__?: string })
|
|
90
|
-
.__AUTH_TOKEN__;
|
|
87
|
+
const isLoggedIn = !!(window as unknown as { __AUTH_TOKEN__?: string }).__AUTH_TOKEN__;
|
|
91
88
|
if (isLoggedIn) return <AuthDemoAuthenticated />;
|
|
92
89
|
return <AuthDemoUnauthenticated />;
|
|
93
90
|
}
|
|
@@ -1,6 +1,27 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Button, Card,
|
|
3
|
-
|
|
2
|
+
import { Button, Card, PageLayout, Text, useApp } from 'ugly-app/client';
|
|
3
|
+
|
|
4
|
+
function openLogin(): void {
|
|
5
|
+
window.open(
|
|
6
|
+
`https://ugly.bot/oauth?origin=${encodeURIComponent(window.location.origin)}`,
|
|
7
|
+
'ugly-bot-login',
|
|
8
|
+
`width=480,height=640,left=${Math.round(window.screenX + (window.outerWidth - 480) / 2)},top=${Math.round(window.screenY + (window.outerHeight - 640) / 2)}`,
|
|
9
|
+
);
|
|
10
|
+
function onMessage(event: MessageEvent): void {
|
|
11
|
+
if (event.origin !== 'https://ugly.bot') return;
|
|
12
|
+
const data = event.data as { type?: string; code?: string } | null;
|
|
13
|
+
if (!data || data.type !== 'ugly-bot-oauth' || !data.code) return;
|
|
14
|
+
window.removeEventListener('message', onMessage);
|
|
15
|
+
void fetch('/auth/verify', {
|
|
16
|
+
method: 'POST',
|
|
17
|
+
headers: { 'Content-Type': 'application/json' },
|
|
18
|
+
body: JSON.stringify({ code: data.code }),
|
|
19
|
+
}).then((res) => {
|
|
20
|
+
if (res.ok) window.location.reload();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
window.addEventListener('message', onMessage);
|
|
24
|
+
}
|
|
4
25
|
|
|
5
26
|
function HomePageAuthenticated(): React.ReactElement {
|
|
6
27
|
const app = useApp();
|
|
@@ -13,10 +34,8 @@ function HomePageAuthenticated(): React.ReactElement {
|
|
|
13
34
|
return (
|
|
14
35
|
<PageLayout
|
|
15
36
|
header={
|
|
16
|
-
<div className="flex items-center justify-between px-6 py-4">
|
|
17
|
-
<
|
|
18
|
-
My App
|
|
19
|
-
</Text>
|
|
37
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
38
|
+
<span className="text-base font-semibold text-gray-900 dark:text-gray-100">My App</span>
|
|
20
39
|
<Button variant="secondary" onClick={handleLogout}>
|
|
21
40
|
Logout
|
|
22
41
|
</Button>
|
|
@@ -29,27 +48,12 @@ function HomePageAuthenticated(): React.ReactElement {
|
|
|
29
48
|
}
|
|
30
49
|
|
|
31
50
|
function HomePageUnauthenticated(): React.ReactElement {
|
|
32
|
-
const { openPopup } = useRouter();
|
|
33
|
-
|
|
34
|
-
function handleLogin(): void {
|
|
35
|
-
openPopup(
|
|
36
|
-
<LoginPopup
|
|
37
|
-
inline
|
|
38
|
-
|
|
39
|
-
onSuccess={() => { window.location.reload(); }}
|
|
40
|
-
/>,
|
|
41
|
-
{ mode: 'transient' },
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
51
|
return (
|
|
46
52
|
<PageLayout
|
|
47
53
|
header={
|
|
48
|
-
<div className="flex items-center justify-between px-6 py-4">
|
|
49
|
-
<
|
|
50
|
-
|
|
51
|
-
</Text>
|
|
52
|
-
<Button variant="primary" onClick={handleLogin}>
|
|
54
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-gray-200 dark:border-gray-800">
|
|
55
|
+
<span className="text-base font-semibold text-gray-900 dark:text-gray-100">My App</span>
|
|
56
|
+
<Button variant="primary" onClick={openLogin}>
|
|
53
57
|
Login
|
|
54
58
|
</Button>
|
|
55
59
|
</div>
|
|
@@ -60,69 +64,42 @@ function HomePageUnauthenticated(): React.ReactElement {
|
|
|
60
64
|
);
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
function HomePageBody({
|
|
64
|
-
userId,
|
|
65
|
-
}: {
|
|
66
|
-
userId: string | null;
|
|
67
|
-
}): React.ReactElement {
|
|
67
|
+
function HomePageBody({ userId }: { userId: string | null }): React.ReactElement {
|
|
68
68
|
return (
|
|
69
|
-
<div className="max-w-2xl mx-auto">
|
|
70
|
-
<Card>
|
|
71
|
-
<Text size="xl" weight="bold">
|
|
72
|
-
|
|
69
|
+
<div className="max-w-2xl mx-auto px-4 py-6">
|
|
70
|
+
<Card className="mb-6">
|
|
71
|
+
<Text size="xl" weight="bold">Welcome</Text>
|
|
72
|
+
<Text className="mt-1 text-gray-500 dark:text-gray-400">
|
|
73
|
+
{userId ? `Logged in as: ${userId}` : 'This app was built with ugly-app.'}
|
|
73
74
|
</Text>
|
|
74
|
-
{userId ? (
|
|
75
|
-
<Text className="mt-2 text-gray-600 dark:text-gray-400">
|
|
76
|
-
Logged in as: {userId}
|
|
77
|
-
</Text>
|
|
78
|
-
) : (
|
|
79
|
-
<Text className="mt-2 text-gray-600 dark:text-gray-400">
|
|
80
|
-
This app was built with ugly-app.
|
|
81
|
-
</Text>
|
|
82
|
-
)}
|
|
83
75
|
</Card>
|
|
84
76
|
|
|
85
|
-
<div className="
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
<Text weight="medium">Query Params Demo →</Text>
|
|
105
|
-
<Text size="sm" className="text-gray-500 mt-1">
|
|
106
|
-
URL: /search?q=...
|
|
107
|
-
</Text>
|
|
108
|
-
</Card>
|
|
109
|
-
</a>
|
|
110
|
-
<a href="/ai-test" className="block">
|
|
111
|
-
<Card className="hover:shadow-md transition-shadow cursor-pointer">
|
|
112
|
-
<Text weight="medium">AI Model Test →</Text>
|
|
113
|
-
<Text size="sm" className="text-gray-500 mt-1">
|
|
114
|
-
Test text & image generation models
|
|
115
|
-
</Text>
|
|
116
|
-
</Card>
|
|
117
|
-
</a>
|
|
77
|
+
<div className="grid gap-3">
|
|
78
|
+
{[
|
|
79
|
+
{ href: '/auth-demo', label: 'Auth Demo', desc: 'Login, logout, and user info' },
|
|
80
|
+
{ href: '/user/example-user-id', label: 'Path Params Demo', desc: 'URL: /user/:userId' },
|
|
81
|
+
{ href: '/search?q=hello', label: 'Query Params Demo', desc: 'URL: /search?q=...' },
|
|
82
|
+
{ href: '/ai-test', label: 'AI Test', desc: 'Text & image generation models' },
|
|
83
|
+
].map(({ href, label, desc }) => (
|
|
84
|
+
<a key={href} href={href} className="block group">
|
|
85
|
+
<Card className="transition-shadow group-hover:shadow-md">
|
|
86
|
+
<div className="flex items-center justify-between">
|
|
87
|
+
<div>
|
|
88
|
+
<Text weight="medium">{label}</Text>
|
|
89
|
+
<Text size="sm" className="text-gray-500 mt-0.5">{desc}</Text>
|
|
90
|
+
</div>
|
|
91
|
+
<span className="text-gray-400 group-hover:text-gray-600 dark:group-hover:text-gray-300 transition-colors">→</span>
|
|
92
|
+
</div>
|
|
93
|
+
</Card>
|
|
94
|
+
</a>
|
|
95
|
+
))}
|
|
118
96
|
</div>
|
|
119
97
|
</div>
|
|
120
98
|
);
|
|
121
99
|
}
|
|
122
100
|
|
|
123
101
|
export default function HomePage(): React.ReactElement {
|
|
124
|
-
const isLoggedIn = !!(window as unknown as { __AUTH_TOKEN__?: string })
|
|
125
|
-
.__AUTH_TOKEN__;
|
|
102
|
+
const isLoggedIn = !!(window as unknown as { __AUTH_TOKEN__?: string }).__AUTH_TOKEN__;
|
|
126
103
|
if (isLoggedIn) return <HomePageAuthenticated />;
|
|
127
104
|
return <HomePageUnauthenticated />;
|
|
128
105
|
}
|
package/templates/gitignore
CHANGED