create-eventus-app 0.1.2 → 0.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-eventus-app",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Scaffold an Eventus mini app.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,12 +10,10 @@
10
10
  },
11
11
  "dependencies": {
12
12
  "@eventusgo/sdk": "__EVENTUS_SDK_VERSION__",
13
- "qrcode": "1.5.4",
14
13
  "react": "18.3.1",
15
14
  "react-dom": "18.3.1"
16
15
  },
17
16
  "devDependencies": {
18
- "@types/qrcode": "1.5.5",
19
17
  "@eventusgo/vite-plugin": "__EVENTUS_VITE_PLUGIN_VERSION__",
20
18
  "@types/react": "18.3.28",
21
19
  "@types/react-dom": "18.3.7",
@@ -1,253 +1,66 @@
1
- import { useEffect, useState } from "react";
2
1
  import manifest from "virtual:eventus-manifest";
3
- import QRCode from "qrcode";
4
2
  import {
5
- eventus,
6
- type EventusContext,
7
- type EventusRuntimeSource,
8
- } from "@eventusgo/sdk";
9
- import {
10
- fetchDevPreviewDescriptor,
11
- isBridgeUnavailableError,
12
- type EventusDevPreviewDescriptor,
13
- } from "./dev-preview";
3
+ EventusContextProvider,
4
+ useEventusContext,
5
+ } from "@eventusgo/sdk/react";
14
6
 
15
- type LoadState =
16
- | { status: "idle" | "loading" }
17
- | { status: "ready"; context: EventusContext }
18
- | {
19
- status: "bridge-unavailable";
20
- preview: EventusDevPreviewDescriptor | null;
21
- }
22
- | { status: "error"; message: string };
7
+ import logoDark from "./assets/logo_black_transparent.png";
8
+ import logoLight from "./assets/logo_white_transparent.png";
23
9
 
24
10
  export default function App() {
25
- const [state, setState] = useState<LoadState>({ status: "loading" });
26
- const [runtimeSource, setRuntimeSource] = useState<
27
- EventusRuntimeSource | "bridge-unavailable" | "loading"
28
- >("loading");
29
-
30
- useEffect(() => {
31
- let cancelled = false;
32
- let receivedReadyEvent = false;
33
-
34
- const unsubscribeReady = eventus.on("ready", ({ context, source }) => {
35
- receivedReadyEvent = true;
36
- if (!cancelled) {
37
- setRuntimeSource(source);
38
- setState({ status: "ready", context });
39
- }
40
- });
41
-
42
- const unsubscribe = eventus.on("themeChange", ({ context }) => {
43
- if (!cancelled) {
44
- setState({ status: "ready", context });
45
- }
46
- });
47
-
48
- void (async () => {
49
- try {
50
- await eventus.ready();
51
- const context = await eventus.getContext();
52
-
53
- if (!cancelled && !receivedReadyEvent) {
54
- setRuntimeSource(window.Eventus ? "native" : "mock");
55
- setState({ status: "ready", context });
56
- }
57
- } catch (error) {
58
- if (receivedReadyEvent || cancelled) {
59
- return;
60
- }
61
-
62
- if (isBridgeUnavailableError(error)) {
63
- const preview = await fetchDevPreviewDescriptor();
64
- if (!cancelled && !receivedReadyEvent) {
65
- setRuntimeSource("bridge-unavailable");
66
- setState({
67
- status: "bridge-unavailable",
68
- preview,
69
- });
70
- }
71
- return;
72
- }
73
-
74
- if (!cancelled) {
75
- setState({
76
- status: "error",
77
- message:
78
- error instanceof Error
79
- ? error.message
80
- : "Unable to initialize the Eventus client.",
81
- });
82
- }
83
- }
84
- })();
85
-
86
- return () => {
87
- cancelled = true;
88
- unsubscribeReady();
89
- unsubscribe();
90
- };
91
- }, []);
92
-
93
- const runtimeLabel =
94
- runtimeSource === "native"
95
- ? "Native bridge detected"
96
- : runtimeSource === "mock"
97
- ? "Explicit mock mode active"
98
- : runtimeSource === "bridge-unavailable"
99
- ? "Open this mini app inside Eventus X"
100
- : "Detecting runtime…";
101
-
102
11
  return (
103
- <main className="app-shell">
104
- <section className="card">
105
- <p className="eyebrow">Eventus Mini App Toolkit</p>
106
- <h1>{manifest.name}</h1>
107
- <p className="subtitle">
108
- App ID <code>{manifest.appId}</code> · Version <code>{manifest.version}</code>
109
- </p>
110
-
111
- <div className="status-banner">{runtimeLabel}</div>
112
-
113
- {state.status === "loading" ? (
114
- <p>Connecting to the Eventus client…</p>
115
- ) : null}
116
-
117
- {state.status === "error" ? (
118
- <p className="error-text">{state.message}</p>
119
- ) : null}
120
-
121
- {state.status === "bridge-unavailable" ? (
122
- <DeveloperHandoff preview={state.preview} />
123
- ) : null}
124
-
125
- {state.status === "ready" ? (
126
- <dl className="details-grid">
127
- <div>
128
- <dt>Theme</dt>
129
- <dd>{state.context.theme}</dd>
130
- </div>
131
- <div>
132
- <dt>Locale</dt>
133
- <dd>{state.context.locale}</dd>
134
- </div>
135
- <div>
136
- <dt>User</dt>
137
- <dd>{state.context.user?.name ?? state.context.user?.id ?? "Anonymous"}</dd>
138
- </div>
139
- <div>
140
- <dt>Manifest Permissions</dt>
141
- <dd>{manifest.permissions?.join(", ") ?? "None"}</dd>
142
- </div>
143
- </dl>
144
- ) : null}
145
-
146
- {state.status === "ready" ? (
147
- <pre className="context-preview">
148
- {JSON.stringify(state.context, null, 2)}
149
- </pre>
150
- ) : null}
151
- </section>
152
- </main>
12
+ <EventusContextProvider>
13
+ <WelcomeScreen />
14
+ </EventusContextProvider>
153
15
  );
154
16
  }
155
17
 
156
- function DeveloperHandoff({
157
- preview,
158
- }: {
159
- preview: EventusDevPreviewDescriptor | null;
160
- }) {
161
- if (!preview) {
162
- return (
163
- <div className="handoff-panel">
164
- <h2>Open inside Eventus X</h2>
165
- <p>
166
- This mini app needs the Eventus bridge to access the real runtime.
167
- Open it from Eventus X to test it with production-like behavior.
168
- </p>
169
- </div>
170
- );
171
- }
18
+ function WelcomeScreen() {
19
+ const { context, runtimeSource } = useEventusContext();
20
+ const userName =
21
+ context.user?.name ??
22
+ context.user?.displayName ??
23
+ context.user?.id ??
24
+ "there";
25
+ const logo = context.theme === "dark" ? logoLight : logoDark;
26
+ const runtimeLabel =
27
+ runtimeSource === "native" ? "Connected to Eventus X" : "Mock preview";
172
28
 
173
29
  return (
174
- <div className="handoff-panel">
175
- <div className="handoff-copy">
176
- <h2>Live preview from your phone</h2>
177
- <p>
178
- Open Eventus X, go to <strong>Developer</strong>, and scan this QR
179
- code to launch the mini app with the real Eventus bridge.
180
- </p>
181
- <dl className="preview-grid">
182
- <div>
183
- <dt>Preview URL</dt>
184
- <dd>
185
- <code>{preview.urls.preview}</code>
186
- </dd>
30
+ <main className="welcome-shell">
31
+ <section className={`welcome-card theme-${context.theme}`}>
32
+ <div className="welcome-glow welcome-glow-primary" />
33
+ <div className="welcome-glow welcome-glow-secondary" />
34
+
35
+ <div className="welcome-brand">
36
+ <img src={logo} alt="Eventus" className="welcome-logo" />
37
+ <span className="welcome-badge">{runtimeLabel}</span>
38
+ </div>
39
+
40
+ <div className="welcome-copy">
41
+ <p className="welcome-eyebrow">{manifest.name}</p>
42
+ <h1>Welcome back, {userName}</h1>
43
+ <p className="welcome-subtitle">
44
+ Your mini app is ready inside Eventus. Start building a polished
45
+ experience for real users with the live bridge, theme, and locale.
46
+ </p>
47
+ </div>
48
+
49
+ <div className="welcome-chips">
50
+ <div className="welcome-chip">
51
+ <span>Theme</span>
52
+ <strong>{context.theme}</strong>
187
53
  </div>
188
- <div>
189
- <dt>Local URL</dt>
190
- <dd>
191
- <code>{preview.urls.local}</code>
192
- </dd>
54
+ <div className="welcome-chip">
55
+ <span>Locale</span>
56
+ <strong>{context.locale}</strong>
193
57
  </div>
194
- {preview.urls.network ? (
195
- <div>
196
- <dt>Network URL</dt>
197
- <dd>
198
- <code>{preview.urls.network}</code>
199
- </dd>
200
- </div>
201
- ) : null}
202
- <div>
203
- <dt>Permissions</dt>
204
- <dd>{preview.manifest.permissions?.join(", ") ?? "None"}</dd>
58
+ <div className="welcome-chip">
59
+ <span>App ID</span>
60
+ <strong>{manifest.appId}</strong>
205
61
  </div>
206
- </dl>
207
- <p className="hint-text">
208
- Your phone and computer must be on the same local network.
209
- </p>
210
- </div>
211
- <QrPanel payload={preview} />
212
- </div>
213
- );
214
- }
215
-
216
- function QrPanel({
217
- payload,
218
- }: {
219
- payload: EventusDevPreviewDescriptor;
220
- }) {
221
- const [svg, setSvg] = useState("");
222
-
223
- useEffect(() => {
224
- let cancelled = false;
225
-
226
- void (async () => {
227
- const nextSvg = await QRCode.toString(JSON.stringify(payload), {
228
- type: "svg",
229
- width: 220,
230
- margin: 1,
231
- });
232
- if (!cancelled) {
233
- setSvg(nextSvg);
234
- }
235
- })();
236
-
237
- return () => {
238
- cancelled = true;
239
- };
240
- }, [payload]);
241
-
242
- return (
243
- <div className="qr-panel">
244
- <div
245
- className="qr-code"
246
- aria-label="Eventus live preview QR code"
247
- dangerouslySetInnerHTML={{
248
- __html: svg || "<div class='qr-placeholder'>Generating QR…</div>",
249
- }}
250
- />
251
- </div>
62
+ </div>
63
+ </section>
64
+ </main>
252
65
  );
253
66
  }
@@ -2,7 +2,17 @@
2
2
  font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
3
3
  color: #102347;
4
4
  background:
5
- radial-gradient(circle at top, #d8f2ff 0%, #f3f9ff 35%, #eef3ff 100%);
5
+ radial-gradient(
6
+ circle at top left,
7
+ rgba(97, 180, 255, 0.28),
8
+ transparent 34%
9
+ ),
10
+ radial-gradient(
11
+ circle at 82% 18%,
12
+ rgba(58, 115, 255, 0.16),
13
+ transparent 26%
14
+ ),
15
+ linear-gradient(180deg, #e9f5ff 0%, #eef3ff 44%, #f8fbff 100%);
6
16
  line-height: 1.5;
7
17
  font-weight: 400;
8
18
  }
@@ -22,174 +32,162 @@ pre {
22
32
  font-family: "IBM Plex Mono", "SFMono-Regular", monospace;
23
33
  }
24
34
 
25
- .app-shell {
35
+ .welcome-shell {
26
36
  min-height: 100vh;
27
37
  display: grid;
28
38
  place-items: center;
29
39
  padding: 24px;
30
40
  }
31
41
 
32
- .card {
33
- width: min(720px, 100%);
42
+ .welcome-card {
43
+ position: relative;
44
+ isolation: isolate;
45
+ overflow: hidden;
46
+ width: min(760px, 100%);
34
47
  padding: 32px;
35
- border-radius: 24px;
36
- background: rgba(255, 255, 255, 0.84);
48
+ border-radius: 32px;
37
49
  border: 1px solid rgba(16, 35, 71, 0.08);
38
- box-shadow: 0 24px 80px rgba(38, 73, 134, 0.14);
39
- backdrop-filter: blur(16px);
50
+ box-shadow: 0 24px 80px rgba(38, 73, 134, 0.16);
51
+ backdrop-filter: blur(18px);
40
52
  }
41
53
 
42
- .eyebrow {
43
- margin: 0 0 8px;
44
- font-size: 0.85rem;
45
- letter-spacing: 0.12em;
46
- text-transform: uppercase;
47
- color: #3f6ea8;
48
- }
49
-
50
- h1 {
51
- margin: 0;
52
- font-size: clamp(2rem, 4vw, 3rem);
54
+ .theme-light {
55
+ background: rgba(255, 255, 255, 0.86);
56
+ color: #102347;
53
57
  }
54
58
 
55
- .subtitle {
56
- margin: 12px 0 0;
57
- color: #465d82;
59
+ .theme-dark {
60
+ background: rgba(10, 24, 54, 0.9);
61
+ color: #f2f6ff;
62
+ border-color: rgba(182, 211, 255, 0.12);
63
+ box-shadow: 0 28px 96px rgba(6, 14, 30, 0.42);
58
64
  }
59
65
 
60
- .status-banner {
61
- margin: 24px 0;
62
- display: inline-flex;
63
- padding: 8px 12px;
66
+ .welcome-glow {
67
+ position: absolute;
64
68
  border-radius: 999px;
65
- background: #eef6ff;
66
- color: #22548d;
67
- font-weight: 600;
69
+ filter: blur(12px);
70
+ opacity: 0.85;
71
+ pointer-events: none;
72
+ z-index: -1;
68
73
  }
69
74
 
70
- .handoff-panel {
71
- margin: 24px 0;
72
- display: grid;
73
- gap: 20px;
75
+ .welcome-glow-primary {
76
+ top: -80px;
77
+ right: -40px;
78
+ width: 220px;
79
+ height: 220px;
80
+ background: rgba(84, 169, 255, 0.24);
74
81
  }
75
82
 
76
- .handoff-copy h2 {
77
- margin: 0 0 8px;
78
- font-size: 1.5rem;
79
- }
80
-
81
- .handoff-copy p {
82
- margin: 0;
83
- color: #465d82;
83
+ .welcome-glow-secondary {
84
+ bottom: -90px;
85
+ left: -80px;
86
+ width: 260px;
87
+ height: 260px;
88
+ background: rgba(0, 229, 214, 0.16);
84
89
  }
85
90
 
86
- .preview-grid {
87
- margin: 20px 0 0;
88
- display: grid;
89
- gap: 12px;
90
- }
91
-
92
- .preview-grid div {
93
- padding: 14px 16px;
94
- border-radius: 16px;
95
- background: #f8fbff;
96
- border: 1px solid rgba(16, 35, 71, 0.08);
91
+ .welcome-brand {
92
+ display: flex;
93
+ align-items: center;
94
+ justify-content: space-between;
95
+ gap: 16px;
97
96
  }
98
97
 
99
- .preview-grid code {
100
- word-break: break-all;
98
+ .welcome-logo {
99
+ height: 34px;
100
+ width: auto;
101
101
  }
102
102
 
103
- .hint-text {
104
- margin-top: 16px;
105
- font-size: 0.95rem;
103
+ .welcome-badge {
104
+ display: inline-flex;
105
+ align-items: center;
106
+ gap: 8px;
107
+ padding: 10px 14px;
108
+ border-radius: 999px;
109
+ font-size: 0.82rem;
110
+ font-weight: 700;
111
+ letter-spacing: 0.04em;
112
+ text-transform: uppercase;
113
+ background: rgba(82, 137, 255, 0.12);
114
+ color: inherit;
106
115
  }
107
116
 
108
- .qr-panel {
109
- display: grid;
110
- place-items: center;
111
- padding: 20px;
112
- border-radius: 24px;
113
- background: linear-gradient(180deg, #fefefe 0%, #f3f7ff 100%);
114
- border: 1px solid rgba(16, 35, 71, 0.08);
117
+ .welcome-copy {
118
+ margin-top: 32px;
119
+ max-width: 560px;
115
120
  }
116
121
 
117
- .qr-code {
118
- width: min(260px, 100%);
119
- aspect-ratio: 1;
120
- display: grid;
121
- place-items: center;
122
+ .welcome-eyebrow {
123
+ margin: 0 0 10px;
124
+ font-size: 0.82rem;
125
+ letter-spacing: 0.16em;
126
+ text-transform: uppercase;
127
+ color: inherit;
128
+ opacity: 0.7;
122
129
  }
123
130
 
124
- .qr-code svg {
125
- width: 100%;
126
- height: auto;
131
+ h1 {
132
+ margin: 0;
133
+ font-size: clamp(2.4rem, 5vw, 4.4rem);
134
+ line-height: 0.96;
135
+ letter-spacing: -0.04em;
127
136
  }
128
137
 
129
- .qr-placeholder {
130
- display: grid;
131
- place-items: center;
132
- width: 100%;
133
- height: 100%;
134
- border-radius: 20px;
135
- background: #eef6ff;
136
- color: #22548d;
137
- font-weight: 600;
138
+ .welcome-subtitle {
139
+ margin: 18px 0 0;
140
+ font-size: 1.05rem;
141
+ line-height: 1.75;
142
+ opacity: 0.82;
138
143
  }
139
144
 
140
- .details-grid {
145
+ .welcome-chips {
141
146
  display: grid;
142
147
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
143
148
  gap: 16px;
144
- margin: 24px 0;
149
+ margin-top: 28px;
145
150
  }
146
151
 
147
- .details-grid div {
148
- padding: 16px;
149
- border-radius: 16px;
150
- background: #f8fbff;
152
+ .welcome-chip {
153
+ padding: 16px 18px;
154
+ border-radius: 20px;
151
155
  border: 1px solid rgba(16, 35, 71, 0.08);
156
+ background: rgba(255, 255, 255, 0.36);
157
+ box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.35);
152
158
  }
153
159
 
154
- dt {
155
- margin-bottom: 6px;
156
- font-size: 0.85rem;
157
- color: #6880a6;
158
- text-transform: uppercase;
159
- letter-spacing: 0.06em;
160
+ .theme-dark .welcome-chip {
161
+ background: rgba(255, 255, 255, 0.06);
162
+ border-color: rgba(182, 211, 255, 0.12);
163
+ box-shadow: none;
160
164
  }
161
165
 
162
- dd {
163
- margin: 0;
164
- font-weight: 600;
165
- }
166
-
167
- .context-preview {
168
- margin: 0;
169
- padding: 16px;
170
- border-radius: 16px;
171
- overflow: auto;
172
- background: #0d1d3a;
173
- color: #eef5ff;
166
+ .welcome-chip span {
167
+ display: block;
168
+ margin-bottom: 8px;
169
+ font-size: 0.8rem;
170
+ text-transform: uppercase;
171
+ letter-spacing: 0.06em;
172
+ opacity: 0.65;
174
173
  }
175
174
 
176
- .error-text {
177
- color: #b42318;
175
+ .welcome-chip strong {
176
+ font-size: 1rem;
177
+ font-weight: 700;
178
178
  }
179
179
 
180
180
  @media (max-width: 640px) {
181
- .card {
182
- padding: 24px;
181
+ .welcome-shell {
182
+ padding: 18px;
183
183
  }
184
184
 
185
- .handoff-panel {
186
- grid-template-columns: 1fr;
185
+ .welcome-card {
186
+ padding: 24px;
187
187
  }
188
- }
189
188
 
190
- @media (min-width: 641px) {
191
- .handoff-panel {
192
- grid-template-columns: minmax(0, 1.3fr) minmax(220px, 0.7fr);
193
- align-items: start;
189
+ .welcome-brand {
190
+ align-items: flex-start;
191
+ flex-direction: column;
194
192
  }
195
193
  }
@@ -1,102 +0,0 @@
1
- export type EventusDevPreviewDescriptor = {
2
- type: "eventus-dev-preview";
3
- version: 1;
4
- manifest: {
5
- name: string;
6
- appId: string;
7
- version: string;
8
- permissions?: string[];
9
- };
10
- urls: {
11
- local: string;
12
- network: string | null;
13
- preview: string;
14
- };
15
- };
16
-
17
- export function isBridgeUnavailableError(error: unknown): boolean {
18
- return (
19
- error instanceof Error && error.name === "EventusBridgeUnavailableError"
20
- );
21
- }
22
-
23
- export async function fetchDevPreviewDescriptor(): Promise<EventusDevPreviewDescriptor | null> {
24
- if (typeof fetch !== "function") {
25
- return null;
26
- }
27
-
28
- try {
29
- const response = await fetch("/__eventus__/preview.json", {
30
- headers: {
31
- Accept: "application/json",
32
- },
33
- });
34
- if (!response.ok) {
35
- return null;
36
- }
37
-
38
- const json = (await response.json()) as unknown;
39
- return parseDevPreviewDescriptor(json);
40
- } catch {
41
- return null;
42
- }
43
- }
44
-
45
- export function parseDevPreviewDescriptor(
46
- value: unknown,
47
- ): EventusDevPreviewDescriptor | null {
48
- if (!value || typeof value !== "object" || Array.isArray(value)) {
49
- return null;
50
- }
51
-
52
- const descriptor = value as Record<string, unknown>;
53
- const manifest = descriptor.manifest;
54
- const urls = descriptor.urls;
55
-
56
- if (
57
- descriptor.type !== "eventus-dev-preview" ||
58
- descriptor.version !== 1 ||
59
- !manifest ||
60
- typeof manifest !== "object" ||
61
- Array.isArray(manifest) ||
62
- !urls ||
63
- typeof urls !== "object" ||
64
- Array.isArray(urls)
65
- ) {
66
- return null;
67
- }
68
-
69
- const nextManifest = manifest as Record<string, unknown>;
70
- const nextUrls = urls as Record<string, unknown>;
71
- const permissions = nextManifest.permissions;
72
-
73
- if (
74
- typeof nextManifest.name !== "string" ||
75
- typeof nextManifest.appId !== "string" ||
76
- typeof nextManifest.version !== "string" ||
77
- typeof nextUrls.local !== "string" ||
78
- (nextUrls.network !== null && typeof nextUrls.network !== "string") ||
79
- typeof nextUrls.preview !== "string" ||
80
- (permissions !== undefined &&
81
- (!Array.isArray(permissions) ||
82
- permissions.some((permission) => typeof permission !== "string")))
83
- ) {
84
- return null;
85
- }
86
-
87
- return {
88
- type: "eventus-dev-preview",
89
- version: 1,
90
- manifest: {
91
- name: nextManifest.name,
92
- appId: nextManifest.appId,
93
- version: nextManifest.version,
94
- ...(permissions ? { permissions: permissions as string[] } : {}),
95
- },
96
- urls: {
97
- local: nextUrls.local,
98
- network: nextUrls.network as string | null,
99
- preview: nextUrls.preview,
100
- },
101
- };
102
- }