@neevjs/client 0.0.1
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 +48 -0
- package/dist/index.d.mts +91 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +699 -0
- package/dist/index.mjs +656 -0
- package/package.json +52 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
AuthClient: () => AuthClient,
|
|
24
|
+
AuthPlugin: () => AuthPlugin,
|
|
25
|
+
CachePlugin: () => CachePlugin,
|
|
26
|
+
Form: () => Form,
|
|
27
|
+
LoggerPlugin: () => LoggerPlugin,
|
|
28
|
+
NeevContext: () => NeevContext,
|
|
29
|
+
NeevProvider: () => NeevProvider,
|
|
30
|
+
OfflinePlugin: () => OfflinePlugin,
|
|
31
|
+
Protected: () => Protected,
|
|
32
|
+
Table: () => Table,
|
|
33
|
+
createCachePlugin: () => createCachePlugin,
|
|
34
|
+
createClient: () => createClient,
|
|
35
|
+
createOfflinePlugin: () => createOfflinePlugin,
|
|
36
|
+
useAuth: () => useAuth,
|
|
37
|
+
useModel: () => useModel,
|
|
38
|
+
useNeevClient: () => useNeevClient,
|
|
39
|
+
useSyncStatus: () => useSyncStatus
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/core/AuthClient.ts
|
|
44
|
+
var AuthClient = class {
|
|
45
|
+
client;
|
|
46
|
+
userData = null;
|
|
47
|
+
constructor(client) {
|
|
48
|
+
this.client = client;
|
|
49
|
+
}
|
|
50
|
+
async login(email, password) {
|
|
51
|
+
const res = await this.client.request("/auth/login", {
|
|
52
|
+
method: "POST",
|
|
53
|
+
body: JSON.stringify({ email, password })
|
|
54
|
+
});
|
|
55
|
+
localStorage.setItem("neev_token", res.data.token);
|
|
56
|
+
this.userData = res.data.user;
|
|
57
|
+
}
|
|
58
|
+
async register(email, password, name) {
|
|
59
|
+
const res = await this.client.request("/auth/register", {
|
|
60
|
+
method: "POST",
|
|
61
|
+
body: JSON.stringify({ email, password, name })
|
|
62
|
+
});
|
|
63
|
+
localStorage.setItem("neev_token", res.data.token);
|
|
64
|
+
this.userData = res.data.user;
|
|
65
|
+
}
|
|
66
|
+
logout() {
|
|
67
|
+
localStorage.removeItem("neev_token");
|
|
68
|
+
this.userData = null;
|
|
69
|
+
}
|
|
70
|
+
async user() {
|
|
71
|
+
if (!this.isAuthenticated()) return null;
|
|
72
|
+
if (this.userData) return this.userData;
|
|
73
|
+
const res = await this.client.request("/auth/me");
|
|
74
|
+
this.userData = res.data;
|
|
75
|
+
return this.userData;
|
|
76
|
+
}
|
|
77
|
+
isAuthenticated() {
|
|
78
|
+
return !!localStorage.getItem("neev_token");
|
|
79
|
+
}
|
|
80
|
+
getToken() {
|
|
81
|
+
return localStorage.getItem("neev_token");
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/core/createClient.ts
|
|
86
|
+
function createClient(config = {}) {
|
|
87
|
+
const plugins = [];
|
|
88
|
+
const baseURL = config.baseURL ?? "";
|
|
89
|
+
async function request(url, options = {}) {
|
|
90
|
+
let req = { url, options };
|
|
91
|
+
for (const plugin of plugins) {
|
|
92
|
+
if (plugin.onRequest) {
|
|
93
|
+
req = await plugin.onRequest(req);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
const headers = {
|
|
97
|
+
"Content-Type": "application/json",
|
|
98
|
+
...req.options.headers ?? {}
|
|
99
|
+
};
|
|
100
|
+
let response;
|
|
101
|
+
try {
|
|
102
|
+
response = await fetch(baseURL + req.url, {
|
|
103
|
+
method: req.options.method ?? "GET",
|
|
104
|
+
body: req.options.body,
|
|
105
|
+
headers
|
|
106
|
+
});
|
|
107
|
+
let finalResponse = response;
|
|
108
|
+
for (const plugin of plugins) {
|
|
109
|
+
if (plugin.onResponse) {
|
|
110
|
+
finalResponse = await plugin.onResponse(finalResponse);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
if (!finalResponse.ok) {
|
|
114
|
+
const errorBody = await finalResponse.json().catch(() => ({ error: "Unknown error" }));
|
|
115
|
+
throw new Error(
|
|
116
|
+
typeof errorBody.error === "string" ? errorBody.error : `HTTP ${finalResponse.status}`
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return finalResponse.json();
|
|
120
|
+
} catch (err) {
|
|
121
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
122
|
+
for (const plugin of plugins) {
|
|
123
|
+
plugin.onError?.(error);
|
|
124
|
+
}
|
|
125
|
+
throw error;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function use(plugin) {
|
|
129
|
+
plugins.push(plugin);
|
|
130
|
+
plugin.setup?.(client);
|
|
131
|
+
}
|
|
132
|
+
const client = {
|
|
133
|
+
request,
|
|
134
|
+
use,
|
|
135
|
+
auth: null
|
|
136
|
+
// set below after client is defined
|
|
137
|
+
};
|
|
138
|
+
client.auth = new AuthClient(client);
|
|
139
|
+
return client;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// src/core/NeevProvider.tsx
|
|
143
|
+
var import_react = require("react");
|
|
144
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
145
|
+
var NeevContext = (0, import_react.createContext)(null);
|
|
146
|
+
function NeevProvider({ client, children }) {
|
|
147
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NeevContext.Provider, { value: client, children });
|
|
148
|
+
}
|
|
149
|
+
function useNeevClient() {
|
|
150
|
+
const client = (0, import_react.useContext)(NeevContext);
|
|
151
|
+
if (!client) {
|
|
152
|
+
throw new Error(
|
|
153
|
+
"[NeevJS] useNeevClient must be used inside <NeevProvider>. Make sure you have wrapped your app with <NeevProvider client={client}>."
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
return client;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// src/hooks/useModel.ts
|
|
160
|
+
var import_react2 = require("react");
|
|
161
|
+
var cache = /* @__PURE__ */ new Map();
|
|
162
|
+
function useModel(name) {
|
|
163
|
+
const client = useNeevClient();
|
|
164
|
+
const url = `/${name}`;
|
|
165
|
+
const [data, setData] = (0, import_react2.useState)(() => cache.get(url) ?? []);
|
|
166
|
+
const [loading, setLoading] = (0, import_react2.useState)(!cache.has(url));
|
|
167
|
+
const [error, setError] = (0, import_react2.useState)(null);
|
|
168
|
+
const mountedRef = (0, import_react2.useRef)(true);
|
|
169
|
+
(0, import_react2.useEffect)(() => {
|
|
170
|
+
mountedRef.current = true;
|
|
171
|
+
return () => {
|
|
172
|
+
mountedRef.current = false;
|
|
173
|
+
};
|
|
174
|
+
}, []);
|
|
175
|
+
const fetchData = (0, import_react2.useCallback)(async () => {
|
|
176
|
+
if (mountedRef.current) setLoading(true);
|
|
177
|
+
try {
|
|
178
|
+
const res = await client.request(url);
|
|
179
|
+
const rows = Array.isArray(res) ? res : res.data ?? [];
|
|
180
|
+
cache.set(url, rows);
|
|
181
|
+
if (mountedRef.current) {
|
|
182
|
+
setData(rows);
|
|
183
|
+
setError(null);
|
|
184
|
+
}
|
|
185
|
+
} catch (err) {
|
|
186
|
+
if (mountedRef.current) {
|
|
187
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
188
|
+
}
|
|
189
|
+
} finally {
|
|
190
|
+
if (mountedRef.current) setLoading(false);
|
|
191
|
+
}
|
|
192
|
+
}, [client, url]);
|
|
193
|
+
(0, import_react2.useEffect)(() => {
|
|
194
|
+
void fetchData();
|
|
195
|
+
}, [fetchData]);
|
|
196
|
+
const create = (0, import_react2.useCallback)(async (payload) => {
|
|
197
|
+
await client.request(url, {
|
|
198
|
+
method: "POST",
|
|
199
|
+
body: JSON.stringify(payload)
|
|
200
|
+
});
|
|
201
|
+
cache.delete(url);
|
|
202
|
+
await fetchData();
|
|
203
|
+
}, [client, url, fetchData]);
|
|
204
|
+
const update = (0, import_react2.useCallback)(async (id, payload) => {
|
|
205
|
+
await client.request(`${url}/${id}`, {
|
|
206
|
+
method: "PUT",
|
|
207
|
+
body: JSON.stringify(payload)
|
|
208
|
+
});
|
|
209
|
+
cache.delete(url);
|
|
210
|
+
await fetchData();
|
|
211
|
+
}, [client, url, fetchData]);
|
|
212
|
+
const remove = (0, import_react2.useCallback)(async (id) => {
|
|
213
|
+
await client.request(`${url}/${id}`, {
|
|
214
|
+
method: "DELETE"
|
|
215
|
+
});
|
|
216
|
+
cache.delete(url);
|
|
217
|
+
await fetchData();
|
|
218
|
+
}, [client, url, fetchData]);
|
|
219
|
+
return { data, loading, error, create, update, remove, refresh: fetchData };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// src/hooks/useAuth.ts
|
|
223
|
+
function useAuth() {
|
|
224
|
+
const client = useNeevClient();
|
|
225
|
+
const auth = client.auth;
|
|
226
|
+
return {
|
|
227
|
+
login: (email, password) => auth.login(email, password),
|
|
228
|
+
register: (email, password, name) => auth.register(email, password, name),
|
|
229
|
+
logout: () => auth.logout(),
|
|
230
|
+
user: () => auth.user(),
|
|
231
|
+
isAuthenticated: () => auth.isAuthenticated()
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/hooks/useSyncStatus.ts
|
|
236
|
+
var import_react3 = require("react");
|
|
237
|
+
var syncState = {
|
|
238
|
+
pendingCount: 0,
|
|
239
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
240
|
+
notify() {
|
|
241
|
+
this.listeners.forEach((fn) => fn());
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
function useSyncStatus() {
|
|
245
|
+
const [isOffline, setIsOffline] = (0, import_react3.useState)(!navigator.onLine);
|
|
246
|
+
const [pending, setPending] = (0, import_react3.useState)(syncState.pendingCount);
|
|
247
|
+
const [syncing, setSyncing] = (0, import_react3.useState)(false);
|
|
248
|
+
(0, import_react3.useEffect)(() => {
|
|
249
|
+
function onOnline() {
|
|
250
|
+
setIsOffline(false);
|
|
251
|
+
setSyncing(true);
|
|
252
|
+
setTimeout(() => setSyncing(false), 2e3);
|
|
253
|
+
}
|
|
254
|
+
function onOffline() {
|
|
255
|
+
setIsOffline(true);
|
|
256
|
+
}
|
|
257
|
+
function onSyncUpdate() {
|
|
258
|
+
setPending(syncState.pendingCount);
|
|
259
|
+
}
|
|
260
|
+
window.addEventListener("online", onOnline);
|
|
261
|
+
window.addEventListener("offline", onOffline);
|
|
262
|
+
syncState.listeners.add(onSyncUpdate);
|
|
263
|
+
return () => {
|
|
264
|
+
window.removeEventListener("online", onOnline);
|
|
265
|
+
window.removeEventListener("offline", onOffline);
|
|
266
|
+
syncState.listeners.delete(onSyncUpdate);
|
|
267
|
+
};
|
|
268
|
+
}, []);
|
|
269
|
+
return { isOffline, pending, syncing };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// src/components/Table.tsx
|
|
273
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
274
|
+
function Table({
|
|
275
|
+
model,
|
|
276
|
+
columns,
|
|
277
|
+
onEdit,
|
|
278
|
+
onDelete,
|
|
279
|
+
emptyMessage = "No records found."
|
|
280
|
+
}) {
|
|
281
|
+
const { data, loading, error } = useModel(model);
|
|
282
|
+
if (loading) {
|
|
283
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "neev-table-loading", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: "Loading..." }) });
|
|
284
|
+
}
|
|
285
|
+
if (error) {
|
|
286
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "neev-table-error", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("p", { children: [
|
|
287
|
+
"Error: ",
|
|
288
|
+
error.message
|
|
289
|
+
] }) });
|
|
290
|
+
}
|
|
291
|
+
if (data.length === 0) {
|
|
292
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "neev-table-empty", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("p", { children: emptyMessage }) });
|
|
293
|
+
}
|
|
294
|
+
const resolvedColumns = columns ?? Object.keys(data[0]).map((key) => ({
|
|
295
|
+
key,
|
|
296
|
+
label: key.charAt(0).toUpperCase() + key.slice(1)
|
|
297
|
+
}));
|
|
298
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "neev-table-wrapper", style: { overflowX: "auto" }, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("table", { className: "neev-table", style: { width: "100%", borderCollapse: "collapse" }, children: [
|
|
299
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("tr", { children: [
|
|
300
|
+
resolvedColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
301
|
+
"th",
|
|
302
|
+
{
|
|
303
|
+
style: {
|
|
304
|
+
textAlign: "left",
|
|
305
|
+
padding: "10px 12px",
|
|
306
|
+
borderBottom: "2px solid #e5e7eb",
|
|
307
|
+
fontWeight: 600,
|
|
308
|
+
color: "#374151",
|
|
309
|
+
background: "#f9fafb"
|
|
310
|
+
},
|
|
311
|
+
children: col.label ?? col.key
|
|
312
|
+
},
|
|
313
|
+
col.key
|
|
314
|
+
)),
|
|
315
|
+
(onEdit || onDelete) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
316
|
+
"th",
|
|
317
|
+
{
|
|
318
|
+
style: {
|
|
319
|
+
textAlign: "left",
|
|
320
|
+
padding: "10px 12px",
|
|
321
|
+
borderBottom: "2px solid #e5e7eb",
|
|
322
|
+
fontWeight: 600,
|
|
323
|
+
color: "#374151",
|
|
324
|
+
background: "#f9fafb"
|
|
325
|
+
},
|
|
326
|
+
children: "Actions"
|
|
327
|
+
}
|
|
328
|
+
)
|
|
329
|
+
] }) }),
|
|
330
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("tbody", { children: data.map((row, rowIndex) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
331
|
+
"tr",
|
|
332
|
+
{
|
|
333
|
+
style: { borderBottom: "1px solid #e5e7eb" },
|
|
334
|
+
children: [
|
|
335
|
+
resolvedColumns.map((col) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
336
|
+
"td",
|
|
337
|
+
{
|
|
338
|
+
style: { padding: "10px 12px", color: "#1f2937", verticalAlign: "middle" },
|
|
339
|
+
children: col.render ? col.render(row[col.key], row) : String(row[col.key] ?? "")
|
|
340
|
+
},
|
|
341
|
+
col.key
|
|
342
|
+
)),
|
|
343
|
+
(onEdit || onDelete) && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("td", { style: { padding: "10px 12px", verticalAlign: "middle" }, children: [
|
|
344
|
+
onEdit && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
345
|
+
"button",
|
|
346
|
+
{
|
|
347
|
+
onClick: () => onEdit(row),
|
|
348
|
+
style: {
|
|
349
|
+
marginRight: 8,
|
|
350
|
+
padding: "4px 10px",
|
|
351
|
+
fontSize: 13,
|
|
352
|
+
cursor: "pointer",
|
|
353
|
+
background: "#3b82f6",
|
|
354
|
+
color: "#fff",
|
|
355
|
+
border: "none",
|
|
356
|
+
borderRadius: 4
|
|
357
|
+
},
|
|
358
|
+
children: "Edit"
|
|
359
|
+
}
|
|
360
|
+
),
|
|
361
|
+
onDelete && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
362
|
+
"button",
|
|
363
|
+
{
|
|
364
|
+
onClick: () => onDelete(row),
|
|
365
|
+
style: {
|
|
366
|
+
padding: "4px 10px",
|
|
367
|
+
fontSize: 13,
|
|
368
|
+
cursor: "pointer",
|
|
369
|
+
background: "#ef4444",
|
|
370
|
+
color: "#fff",
|
|
371
|
+
border: "none",
|
|
372
|
+
borderRadius: 4
|
|
373
|
+
},
|
|
374
|
+
children: "Delete"
|
|
375
|
+
}
|
|
376
|
+
)
|
|
377
|
+
] })
|
|
378
|
+
]
|
|
379
|
+
},
|
|
380
|
+
String(row.id ?? rowIndex)
|
|
381
|
+
)) })
|
|
382
|
+
] }) });
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// src/components/Form.tsx
|
|
386
|
+
var import_react4 = require("react");
|
|
387
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
388
|
+
function Form({
|
|
389
|
+
model,
|
|
390
|
+
fields,
|
|
391
|
+
initialValues = {},
|
|
392
|
+
editId,
|
|
393
|
+
onSuccess,
|
|
394
|
+
onError,
|
|
395
|
+
submitLabel = "Submit",
|
|
396
|
+
children
|
|
397
|
+
}) {
|
|
398
|
+
const { create, update } = useModel(model);
|
|
399
|
+
const [submitting, setSubmitting] = (0, import_react4.useState)(false);
|
|
400
|
+
const [formError, setFormError] = (0, import_react4.useState)(null);
|
|
401
|
+
async function handleSubmit(e) {
|
|
402
|
+
e.preventDefault();
|
|
403
|
+
setSubmitting(true);
|
|
404
|
+
setFormError(null);
|
|
405
|
+
const formData = new FormData(e.currentTarget);
|
|
406
|
+
const payload = Object.fromEntries(formData.entries());
|
|
407
|
+
try {
|
|
408
|
+
if (editId !== void 0) {
|
|
409
|
+
await update(editId, payload);
|
|
410
|
+
} else {
|
|
411
|
+
await create(payload);
|
|
412
|
+
}
|
|
413
|
+
onSuccess?.();
|
|
414
|
+
} catch (err) {
|
|
415
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
416
|
+
setFormError(error.message);
|
|
417
|
+
onError?.(error);
|
|
418
|
+
} finally {
|
|
419
|
+
setSubmitting(false);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
const inputStyle = {
|
|
423
|
+
display: "block",
|
|
424
|
+
width: "100%",
|
|
425
|
+
padding: "8px 10px",
|
|
426
|
+
fontSize: 14,
|
|
427
|
+
border: "1px solid #d1d5db",
|
|
428
|
+
borderRadius: 6,
|
|
429
|
+
outline: "none",
|
|
430
|
+
boxSizing: "border-box",
|
|
431
|
+
marginTop: 4
|
|
432
|
+
};
|
|
433
|
+
const labelStyle = {
|
|
434
|
+
display: "block",
|
|
435
|
+
fontSize: 13,
|
|
436
|
+
fontWeight: 500,
|
|
437
|
+
color: "#374151",
|
|
438
|
+
marginBottom: 12
|
|
439
|
+
};
|
|
440
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("form", { onSubmit: (e) => void handleSubmit(e), style: { width: "100%" }, children: [
|
|
441
|
+
formError && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
442
|
+
"div",
|
|
443
|
+
{
|
|
444
|
+
style: {
|
|
445
|
+
padding: "8px 12px",
|
|
446
|
+
marginBottom: 12,
|
|
447
|
+
background: "#fee2e2",
|
|
448
|
+
color: "#dc2626",
|
|
449
|
+
borderRadius: 6,
|
|
450
|
+
fontSize: 13
|
|
451
|
+
},
|
|
452
|
+
children: formError
|
|
453
|
+
}
|
|
454
|
+
),
|
|
455
|
+
fields?.map((field) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("label", { style: labelStyle, children: [
|
|
456
|
+
field.label ?? field.name,
|
|
457
|
+
field.required && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: { color: "#ef4444", marginLeft: 2 }, children: "*" }),
|
|
458
|
+
field.type === "textarea" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
459
|
+
"textarea",
|
|
460
|
+
{
|
|
461
|
+
name: field.name,
|
|
462
|
+
placeholder: field.placeholder,
|
|
463
|
+
required: field.required,
|
|
464
|
+
defaultValue: String(initialValues[field.name] ?? ""),
|
|
465
|
+
style: { ...inputStyle, minHeight: 80, resize: "vertical" }
|
|
466
|
+
}
|
|
467
|
+
) : field.type === "select" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
468
|
+
"select",
|
|
469
|
+
{
|
|
470
|
+
name: field.name,
|
|
471
|
+
required: field.required,
|
|
472
|
+
defaultValue: String(initialValues[field.name] ?? ""),
|
|
473
|
+
style: inputStyle,
|
|
474
|
+
children: [
|
|
475
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: "", children: "Select..." }),
|
|
476
|
+
field.options?.map((opt) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("option", { value: opt.value, children: opt.label }, opt.value))
|
|
477
|
+
]
|
|
478
|
+
}
|
|
479
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
480
|
+
"input",
|
|
481
|
+
{
|
|
482
|
+
type: field.type ?? "text",
|
|
483
|
+
name: field.name,
|
|
484
|
+
placeholder: field.placeholder,
|
|
485
|
+
required: field.required,
|
|
486
|
+
defaultValue: String(initialValues[field.name] ?? ""),
|
|
487
|
+
style: inputStyle
|
|
488
|
+
}
|
|
489
|
+
)
|
|
490
|
+
] }, field.name)),
|
|
491
|
+
children,
|
|
492
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
493
|
+
"button",
|
|
494
|
+
{
|
|
495
|
+
type: "submit",
|
|
496
|
+
disabled: submitting,
|
|
497
|
+
style: {
|
|
498
|
+
marginTop: 8,
|
|
499
|
+
padding: "9px 20px",
|
|
500
|
+
background: submitting ? "#9ca3af" : "#111827",
|
|
501
|
+
color: "#fff",
|
|
502
|
+
border: "none",
|
|
503
|
+
borderRadius: 6,
|
|
504
|
+
fontSize: 14,
|
|
505
|
+
fontWeight: 600,
|
|
506
|
+
cursor: submitting ? "not-allowed" : "pointer",
|
|
507
|
+
width: "100%"
|
|
508
|
+
},
|
|
509
|
+
children: submitting ? "Saving..." : submitLabel
|
|
510
|
+
}
|
|
511
|
+
)
|
|
512
|
+
] });
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// src/components/Protected.tsx
|
|
516
|
+
var import_react5 = require("react");
|
|
517
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
518
|
+
function Protected({ children, fallback, role }) {
|
|
519
|
+
const { isAuthenticated, user } = useAuth();
|
|
520
|
+
const [checking, setChecking] = (0, import_react5.useState)(true);
|
|
521
|
+
const [allowed, setAllowed] = (0, import_react5.useState)(false);
|
|
522
|
+
(0, import_react5.useEffect)(() => {
|
|
523
|
+
async function check() {
|
|
524
|
+
if (!isAuthenticated()) {
|
|
525
|
+
setAllowed(false);
|
|
526
|
+
setChecking(false);
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
if (role) {
|
|
530
|
+
const u = await user();
|
|
531
|
+
setAllowed(u?.role === role);
|
|
532
|
+
} else {
|
|
533
|
+
setAllowed(true);
|
|
534
|
+
}
|
|
535
|
+
setChecking(false);
|
|
536
|
+
}
|
|
537
|
+
void check();
|
|
538
|
+
}, [isAuthenticated, role, user]);
|
|
539
|
+
if (checking) return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, {});
|
|
540
|
+
if (!allowed) {
|
|
541
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: fallback ?? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { padding: 24, textAlign: "center", color: "#6b7280" }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("p", { children: "You are not authorized to view this page." }) }) });
|
|
542
|
+
}
|
|
543
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children });
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// src/plugins/AuthPlugin.ts
|
|
547
|
+
var AuthPlugin = {
|
|
548
|
+
name: "auth",
|
|
549
|
+
onRequest(req) {
|
|
550
|
+
const token = localStorage.getItem("neev_token");
|
|
551
|
+
return {
|
|
552
|
+
...req,
|
|
553
|
+
options: {
|
|
554
|
+
...req.options,
|
|
555
|
+
headers: {
|
|
556
|
+
...req.options.headers ?? {},
|
|
557
|
+
...token ? { Authorization: `Bearer ${token}` } : {}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
// src/plugins/LoggerPlugin.ts
|
|
565
|
+
var LoggerPlugin = {
|
|
566
|
+
name: "logger",
|
|
567
|
+
onRequest(req) {
|
|
568
|
+
console.log(`[NeevJS] \u2192 ${req.options.method ?? "GET"} ${req.url}`);
|
|
569
|
+
return req;
|
|
570
|
+
},
|
|
571
|
+
onResponse(res) {
|
|
572
|
+
console.log(`[NeevJS] \u2190 ${res.status} ${res.url}`);
|
|
573
|
+
return res;
|
|
574
|
+
},
|
|
575
|
+
onError(err) {
|
|
576
|
+
console.error("[NeevJS] \u2717 Error:", err.message);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
// src/plugins/CachePlugin.ts
|
|
581
|
+
function createCachePlugin(options = {}) {
|
|
582
|
+
const ttl = options.ttl ?? 6e4;
|
|
583
|
+
const store = /* @__PURE__ */ new Map();
|
|
584
|
+
return {
|
|
585
|
+
name: "cache",
|
|
586
|
+
onRequest(req) {
|
|
587
|
+
const method = (req.options.method ?? "GET").toUpperCase();
|
|
588
|
+
if (method !== "GET") return req;
|
|
589
|
+
const entry = store.get(req.url);
|
|
590
|
+
if (entry && Date.now() < entry.expiresAt) {
|
|
591
|
+
const cached = new Response(JSON.stringify(entry.data), {
|
|
592
|
+
status: 200,
|
|
593
|
+
headers: { "Content-Type": "application/json", "X-Neev-Cache": "HIT" }
|
|
594
|
+
});
|
|
595
|
+
return { ...req, _cachedResponse: cached };
|
|
596
|
+
}
|
|
597
|
+
return req;
|
|
598
|
+
},
|
|
599
|
+
async onResponse(res) {
|
|
600
|
+
if (res.headers.get("X-Neev-Cache") === "HIT") return res;
|
|
601
|
+
const url = res.url;
|
|
602
|
+
if (!url) return res;
|
|
603
|
+
try {
|
|
604
|
+
const clone = res.clone();
|
|
605
|
+
const data = await clone.json();
|
|
606
|
+
store.set(url, { data, expiresAt: Date.now() + ttl });
|
|
607
|
+
} catch {
|
|
608
|
+
}
|
|
609
|
+
return res;
|
|
610
|
+
},
|
|
611
|
+
onError() {
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
var CachePlugin = createCachePlugin();
|
|
616
|
+
|
|
617
|
+
// src/plugins/OfflinePlugin.ts
|
|
618
|
+
var QUEUE_STORAGE_KEY = "neev_offline_queue";
|
|
619
|
+
function generateId() {
|
|
620
|
+
return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
621
|
+
}
|
|
622
|
+
function loadQueue() {
|
|
623
|
+
try {
|
|
624
|
+
const raw = localStorage.getItem(QUEUE_STORAGE_KEY);
|
|
625
|
+
return raw ? JSON.parse(raw) : [];
|
|
626
|
+
} catch {
|
|
627
|
+
return [];
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
function saveQueue(queue) {
|
|
631
|
+
localStorage.setItem(QUEUE_STORAGE_KEY, JSON.stringify(queue));
|
|
632
|
+
syncState.pendingCount = queue.length;
|
|
633
|
+
syncState.notify();
|
|
634
|
+
}
|
|
635
|
+
function createOfflinePlugin() {
|
|
636
|
+
let client;
|
|
637
|
+
async function processQueue() {
|
|
638
|
+
if (!navigator.onLine) return;
|
|
639
|
+
const queue = loadQueue();
|
|
640
|
+
if (queue.length === 0) return;
|
|
641
|
+
const remaining = [];
|
|
642
|
+
for (const action of queue) {
|
|
643
|
+
try {
|
|
644
|
+
await client.request(action.url, action.options);
|
|
645
|
+
} catch {
|
|
646
|
+
remaining.push({ ...action, retries: action.retries + 1 });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
saveQueue(remaining);
|
|
650
|
+
}
|
|
651
|
+
window.addEventListener("online", () => {
|
|
652
|
+
void processQueue();
|
|
653
|
+
});
|
|
654
|
+
return {
|
|
655
|
+
name: "offline",
|
|
656
|
+
setup(c) {
|
|
657
|
+
client = c;
|
|
658
|
+
},
|
|
659
|
+
onRequest(req) {
|
|
660
|
+
const method = (req.options.method ?? "GET").toUpperCase();
|
|
661
|
+
const isMutation = method !== "GET";
|
|
662
|
+
if (!navigator.onLine && isMutation) {
|
|
663
|
+
const queue = loadQueue();
|
|
664
|
+
const action = {
|
|
665
|
+
id: generateId(),
|
|
666
|
+
url: req.url,
|
|
667
|
+
options: req.options,
|
|
668
|
+
timestamp: Date.now(),
|
|
669
|
+
retries: 0
|
|
670
|
+
};
|
|
671
|
+
queue.push(action);
|
|
672
|
+
saveQueue(queue);
|
|
673
|
+
throw new Error("[NeevJS] Offline \u2014 action queued for sync when back online.");
|
|
674
|
+
}
|
|
675
|
+
return req;
|
|
676
|
+
}
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
var OfflinePlugin = createOfflinePlugin();
|
|
680
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
681
|
+
0 && (module.exports = {
|
|
682
|
+
AuthClient,
|
|
683
|
+
AuthPlugin,
|
|
684
|
+
CachePlugin,
|
|
685
|
+
Form,
|
|
686
|
+
LoggerPlugin,
|
|
687
|
+
NeevContext,
|
|
688
|
+
NeevProvider,
|
|
689
|
+
OfflinePlugin,
|
|
690
|
+
Protected,
|
|
691
|
+
Table,
|
|
692
|
+
createCachePlugin,
|
|
693
|
+
createClient,
|
|
694
|
+
createOfflinePlugin,
|
|
695
|
+
useAuth,
|
|
696
|
+
useModel,
|
|
697
|
+
useNeevClient,
|
|
698
|
+
useSyncStatus
|
|
699
|
+
});
|