asasvirtuais 0.1.0
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 +78 -0
- package/actions/draw.ts +110 -0
- package/components/OAuthCard.tsx +346 -0
- package/components/icons.tsx +11 -0
- package/components/markdown.tsx +18 -0
- package/components/stack/list.tsx +21 -0
- package/components/stack/menu.tsx +40 -0
- package/components/stack/nav.tsx +39 -0
- package/components/table/fixed.tsx +59 -0
- package/components/table/key-value.tsx +19 -0
- package/components/ui/color-mode.tsx +108 -0
- package/components/ui/provider.tsx +15 -0
- package/components/ui/toaster.tsx +43 -0
- package/components/ui/tooltip.tsx +46 -0
- package/hooks/useBoolean.tsx +11 -0
- package/hooks/useForwardAs.tsx +29 -0
- package/hooks/useHash copy.tsx +27 -0
- package/hooks/useHash.tsx +27 -0
- package/hooks/useIsMobile.tsx +6 -0
- package/hooks/useOAuthTokens.ts +97 -0
- package/hooks/useOpenRouterModels.ts +80 -0
- package/lib/auth0.ts +11 -0
- package/lib/blob.ts +3 -0
- package/lib/client-token-storage.ts +216 -0
- package/lib/oauth-tokens.ts +85 -0
- package/lib/react/context.tsx +20 -0
- package/lib/react/index.ts +1 -0
- package/lib/tools.ts +375 -0
- package/next-env.d.ts +5 -0
- package/next.config.ts +23 -0
- package/package.json +72 -0
- package/packages/blob.ts +97 -0
- package/packages/chat/components/chat/feed/index.tsx +76 -0
- package/packages/chat/components/chat/feed/story.tsx +18 -0
- package/packages/chat/components/chat/index.tsx +16 -0
- package/packages/chat/components/chat/story.tsx +74 -0
- package/packages/chat/components/debug/index.tsx +54 -0
- package/packages/chat/components/header/index.tsx +14 -0
- package/packages/chat/components/header/menu/index.tsx +63 -0
- package/packages/chat/components/header/story.tsx +33 -0
- package/packages/chat/components/header/title/index.tsx +35 -0
- package/packages/chat/components/index.ts +13 -0
- package/packages/chat/components/input/index.tsx +17 -0
- package/packages/chat/components/input/menu/index.tsx +35 -0
- package/packages/chat/components/input/send.tsx +21 -0
- package/packages/chat/components/input/story.tsx +35 -0
- package/packages/chat/components/input/textarea/index.tsx +20 -0
- package/packages/chat/components/message/file.tsx +103 -0
- package/packages/chat/components/message/menu/index.tsx +26 -0
- package/packages/chat/components/message/story.tsx +49 -0
- package/packages/chat/components/messages/index.tsx +23 -0
- package/packages/chat/components/messages/story.tsx +11 -0
- package/packages/chat/components/ui/prose.tsx +263 -0
- package/packages/chat/edit-message.tsx +49 -0
- package/packages/chat/header.tsx +118 -0
- package/packages/chat/index.ts +14 -0
- package/packages/chat/input.tsx +89 -0
- package/packages/chat/message-menu.tsx +57 -0
- package/packages/chat/message.tsx +44 -0
- package/packages/chat/messages.tsx +44 -0
- package/packages/chat/model-selector.tsx +172 -0
- package/packages/chat/scenarios.tsx +68 -0
- package/packages/chat/settings.tsx +98 -0
- package/packages/chat/temperature-slider.tsx +67 -0
- package/packages/chat/tool-results.tsx +32 -0
- package/packages/crud/core.ts +75 -0
- package/packages/crud/fetcher.ts +64 -0
- package/packages/crud/index.ts +2 -0
- package/packages/crud/next.ts +128 -0
- package/packages/crud/react.tsx +365 -0
- package/packages/env.ts +8 -0
- package/packages/fields.tsx +157 -0
- package/packages/firebase.ts +13 -0
- package/packages/firestore.ts +51 -0
- package/packages/form.tsx +66 -0
- package/packages/next.ts +64 -0
- package/packages/openrouter.ts +4 -0
- package/packages/react/context.tsx +21 -0
- package/packages/react/crud.tsx +372 -0
- package/packages/react/hooks.ts +90 -0
- package/packages/react/store.tsx +20 -0
- package/packages/replit-db.ts +219 -0
- package/packages/wretch.ts +22 -0
- package/packages/yaml.ts +163 -0
- package/pnpm-workspace.yaml +4 -0
- package/server/db.ts +15 -0
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import { z } from 'zod'
|
|
3
|
+
import {
|
|
4
|
+
useState,
|
|
5
|
+
useCallback,
|
|
6
|
+
useEffect,
|
|
7
|
+
useMemo,
|
|
8
|
+
createContext,
|
|
9
|
+
useContext,
|
|
10
|
+
} from "react"
|
|
11
|
+
import { createContextFromHook } from "@/packages/react/context"
|
|
12
|
+
import { useIndex, useAction } from "@/packages/react/hooks"
|
|
13
|
+
import { FieldsProvider, useFields } from "@/packages/fields"
|
|
14
|
+
import { FormProvider, useForm } from "@/packages/form"
|
|
15
|
+
import { TableInterface, ListProps, DatabaseInterface } from "./core"
|
|
16
|
+
|
|
17
|
+
export function database<Database extends DatabaseInterface> (
|
|
18
|
+
database: Database,
|
|
19
|
+
{
|
|
20
|
+
find,
|
|
21
|
+
create,
|
|
22
|
+
update,
|
|
23
|
+
remove,
|
|
24
|
+
list,
|
|
25
|
+
}: TableInterface<
|
|
26
|
+
z.infer<Database[keyof Database]["readable"]>,
|
|
27
|
+
z.infer<Database[keyof Database]["writable"]>
|
|
28
|
+
>
|
|
29
|
+
) {
|
|
30
|
+
type TableKey = keyof Database & string
|
|
31
|
+
|
|
32
|
+
function useTableProvider<Table extends TableKey>({
|
|
33
|
+
table,
|
|
34
|
+
asAbove,
|
|
35
|
+
}: {
|
|
36
|
+
table: Table
|
|
37
|
+
asAbove?: Record<string, z.infer<Database[Table]["readable"]>>
|
|
38
|
+
}) {
|
|
39
|
+
type Readable = z.infer<Database[Table]["readable"]>
|
|
40
|
+
type Writable = z.infer<Database[Table]["writable"]>
|
|
41
|
+
|
|
42
|
+
const index = useIndex<Readable>({ ...(asAbove ?? {}) })
|
|
43
|
+
|
|
44
|
+
const array = useMemo(
|
|
45
|
+
() => Object.values(index.index) as Readable[],
|
|
46
|
+
[index.index]
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
useEffect(function soBelow() {
|
|
50
|
+
index.setIndex((prev) => ({ ...prev, ...asAbove }))
|
|
51
|
+
}, [])
|
|
52
|
+
|
|
53
|
+
const methods = { find, create, update, remove, list } as TableInterface<
|
|
54
|
+
Readable,
|
|
55
|
+
Writable
|
|
56
|
+
>
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
...index,
|
|
60
|
+
array,
|
|
61
|
+
find: useAction(((props) => methods.find({ ...props, table }).then(res => {
|
|
62
|
+
index.set(res)
|
|
63
|
+
return res
|
|
64
|
+
})) as typeof find),
|
|
65
|
+
create: useAction(((props) => create({...props, table}).then(res => {
|
|
66
|
+
index.set(res)
|
|
67
|
+
return res
|
|
68
|
+
})) as typeof create),
|
|
69
|
+
update: useAction(((props) => update({...props, table}).then(res => {
|
|
70
|
+
index.set(res)
|
|
71
|
+
return res
|
|
72
|
+
})) as typeof update),
|
|
73
|
+
remove: useAction(((props) => remove({...props, table}).then(res => {
|
|
74
|
+
index.remove(res)
|
|
75
|
+
return res
|
|
76
|
+
})) as typeof remove),
|
|
77
|
+
list: useAction(((props) => list({ ...props, table }).then(arr => {
|
|
78
|
+
index.set(...arr)
|
|
79
|
+
return arr
|
|
80
|
+
})) as typeof list),
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function useDatabaseProvider(tables: {[T in TableKey]: Record<string, z.infer<Database[T]["readable"]>>}): {
|
|
85
|
+
[T in TableKey]: ReturnType<typeof useTableProvider<T>>
|
|
86
|
+
} {
|
|
87
|
+
return Object.fromEntries(
|
|
88
|
+
Object.entries(tables).map(([table, value]) => [
|
|
89
|
+
table, useTableProvider({ table: table as TableKey, asAbove: value })
|
|
90
|
+
])
|
|
91
|
+
) as {
|
|
92
|
+
[T in TableKey]: ReturnType<typeof useTableProvider<T>>
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const [DatabaseProvider, useDatabase] =
|
|
97
|
+
createContextFromHook(useDatabaseProvider)
|
|
98
|
+
|
|
99
|
+
function useTable<T extends TableKey>(name: T): ReturnType<typeof useTableProvider<T>> {
|
|
100
|
+
return useDatabase()[name] as ReturnType<typeof useTableProvider<T>>
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function useSingleProvider<Table extends TableKey>({
|
|
104
|
+
id,
|
|
105
|
+
table,
|
|
106
|
+
}: {
|
|
107
|
+
id: string
|
|
108
|
+
table: Table
|
|
109
|
+
}) {
|
|
110
|
+
const { index, find } = useTable(table)
|
|
111
|
+
const [single, setSingle] = useState<z.infer<Database[Table]["readable"]>>(
|
|
112
|
+
() => index[id]
|
|
113
|
+
)
|
|
114
|
+
useEffect(() => {
|
|
115
|
+
if (!single) find.trigger({ id }).then(setSingle)
|
|
116
|
+
}, [])
|
|
117
|
+
return {
|
|
118
|
+
single,
|
|
119
|
+
setSingle,
|
|
120
|
+
loading: find.loading,
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const SingleContext = createContext<
|
|
125
|
+
ReturnType<typeof useSingleProvider<any>> | undefined
|
|
126
|
+
>(undefined)
|
|
127
|
+
|
|
128
|
+
function SingleProvider<Table extends TableKey>({
|
|
129
|
+
children,
|
|
130
|
+
...props
|
|
131
|
+
}: {
|
|
132
|
+
id: string
|
|
133
|
+
table: Table
|
|
134
|
+
children: React.ReactNode
|
|
135
|
+
}) {
|
|
136
|
+
const value = useSingleProvider(props)
|
|
137
|
+
if (!value.single) return null
|
|
138
|
+
return (
|
|
139
|
+
<SingleContext.Provider value={value}>{children}</SingleContext.Provider>
|
|
140
|
+
)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const useSingle = <Table extends TableKey>(table: Table) =>
|
|
144
|
+
useContext(SingleContext) as ReturnType<typeof useSingleProvider<Table>>
|
|
145
|
+
|
|
146
|
+
function CreateForm<T extends TableKey>({
|
|
147
|
+
table,
|
|
148
|
+
defaults,
|
|
149
|
+
onSuccess,
|
|
150
|
+
children,
|
|
151
|
+
}: {
|
|
152
|
+
table: T
|
|
153
|
+
defaults?: Partial<z.infer<Database[T]["writable"]>>
|
|
154
|
+
onSuccess?: (result: z.infer<Database[T]["readable"]>) => void
|
|
155
|
+
children: ((props: {
|
|
156
|
+
loading: boolean
|
|
157
|
+
error: Error | null
|
|
158
|
+
result: z.infer<Database[T]["readable"]> | null
|
|
159
|
+
fields: Partial<z.infer<Database[T]["writable"]>>
|
|
160
|
+
setField: (key: keyof z.infer<Database[T]["writable"]>, value: any) => void
|
|
161
|
+
callback: (fields: Partial<z.infer<Database[T]["writable"]>>) => Promise<z.infer<Database[T]["readable"]>>
|
|
162
|
+
submit: (e?: React.FormEvent) => Promise<boolean>
|
|
163
|
+
}) => React.ReactNode)
|
|
164
|
+
}) {
|
|
165
|
+
type Readable = z.infer<Database[T]["readable"]>
|
|
166
|
+
type Writable = z.infer<Database[T]["writable"]>
|
|
167
|
+
|
|
168
|
+
const { create } = useTable(table)
|
|
169
|
+
|
|
170
|
+
const callback = useCallback(
|
|
171
|
+
async (fields: Writable) => {
|
|
172
|
+
const result = await create.trigger({ data: fields })
|
|
173
|
+
if (onSuccess) onSuccess(result as Readable)
|
|
174
|
+
return result
|
|
175
|
+
},
|
|
176
|
+
[create, onSuccess]
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
return (
|
|
180
|
+
<FieldsProvider<Writable> defaults={defaults || ({} as Writable)}>
|
|
181
|
+
{fieldsProps => (
|
|
182
|
+
<FormProvider<Writable, Readable> data={fieldsProps.fields} callback={callback}>
|
|
183
|
+
{formProps => children({...fieldsProps, ...formProps, callback})}
|
|
184
|
+
</FormProvider>
|
|
185
|
+
)}
|
|
186
|
+
</FieldsProvider>
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Helper component for UpdateForm render prop pattern
|
|
191
|
+
function UpdateFormRenderProp<T extends TableKey>({
|
|
192
|
+
children
|
|
193
|
+
}: {
|
|
194
|
+
children: (props: {
|
|
195
|
+
fields: Partial<z.infer<Database[T]["writable"]>>
|
|
196
|
+
setField: (key: keyof z.infer<Database[T]["writable"]>, value: any) => void
|
|
197
|
+
submit: (e?: React.FormEvent) => Promise<boolean>
|
|
198
|
+
loading: boolean
|
|
199
|
+
error?: Error | null
|
|
200
|
+
}) => React.ReactNode
|
|
201
|
+
}) {
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function UpdateForm<T extends TableKey>({
|
|
205
|
+
id,
|
|
206
|
+
table,
|
|
207
|
+
defaults,
|
|
208
|
+
onSuccess,
|
|
209
|
+
children,
|
|
210
|
+
}: {
|
|
211
|
+
table: T
|
|
212
|
+
id: string
|
|
213
|
+
defaults?: Partial<z.infer<Database[T]["writable"]>>
|
|
214
|
+
onSuccess?: (result: z.infer<Database[T]["readable"]>) => void
|
|
215
|
+
children: ((props: {
|
|
216
|
+
loading: boolean
|
|
217
|
+
error: Error | null
|
|
218
|
+
result: z.infer<Database[T]["readable"]> | null
|
|
219
|
+
fields: Partial<z.infer<Database[T]["writable"]>>
|
|
220
|
+
setField: (key: keyof z.infer<Database[T]["writable"]>, value: any) => void
|
|
221
|
+
callback: (fields: Partial<z.infer<Database[T]["writable"]>>) => z.infer<Database[T]["readable"]>
|
|
222
|
+
submit: (e?: React.FormEvent) => Promise<boolean>
|
|
223
|
+
}) => React.ReactNode)
|
|
224
|
+
}) {
|
|
225
|
+
type Readable = z.infer<Database[T]["readable"]>
|
|
226
|
+
type Writable = z.infer<Database[T]["writable"]>
|
|
227
|
+
|
|
228
|
+
const { update } = useTable(table)
|
|
229
|
+
|
|
230
|
+
/** receive the props values and process the result */
|
|
231
|
+
const callback = useCallback(
|
|
232
|
+
async (fields: Partial<Writable>) => {
|
|
233
|
+
const result = await update.trigger({ id, data: fields })
|
|
234
|
+
if (onSuccess)
|
|
235
|
+
onSuccess(result as Readable)
|
|
236
|
+
return result
|
|
237
|
+
},
|
|
238
|
+
[update, id, onSuccess]
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
return (
|
|
242
|
+
<FieldsProvider<Partial<Writable>>
|
|
243
|
+
defaults={defaults || ({} as Partial<Writable>)}
|
|
244
|
+
>
|
|
245
|
+
{fieldsProps => (
|
|
246
|
+
<FormProvider<Partial<Writable>, Readable>
|
|
247
|
+
data={fieldsProps.fields}
|
|
248
|
+
callback={callback}
|
|
249
|
+
>
|
|
250
|
+
{formProps => children({...fieldsProps, ...formProps, callback})}
|
|
251
|
+
</FormProvider>
|
|
252
|
+
)}
|
|
253
|
+
</FieldsProvider>
|
|
254
|
+
)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Helper component for FilterForm render prop pattern
|
|
258
|
+
function FilterFormRenderProp<T extends TableKey>({
|
|
259
|
+
children
|
|
260
|
+
}: {
|
|
261
|
+
children: (props: {
|
|
262
|
+
fields: Partial<ListProps<z.infer<Database[T]["readable"]>>>
|
|
263
|
+
setField: (key: keyof ListProps<z.infer<Database[T]["readable"]>>, value: any) => void
|
|
264
|
+
submit: (e?: React.FormEvent) => Promise<boolean>
|
|
265
|
+
loading: boolean
|
|
266
|
+
error?: Error | null
|
|
267
|
+
}) => React.ReactNode
|
|
268
|
+
}) {
|
|
269
|
+
const { fields, setField } = useFields<ListProps<z.infer<Database[T]["readable"]>>>()
|
|
270
|
+
const { submit, loading, error } = useForm<ListProps<z.infer<Database[T]["readable"]>>, z.infer<Database[T]["readable"]>[]>()
|
|
271
|
+
|
|
272
|
+
return <>{children({ fields, setField, submit, loading, error })}</>
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function FilterForm<T extends TableKey>({
|
|
276
|
+
table,
|
|
277
|
+
defaults,
|
|
278
|
+
onSuccess,
|
|
279
|
+
children,
|
|
280
|
+
}: {
|
|
281
|
+
table: T
|
|
282
|
+
defaults?: Partial<ListProps<z.infer<Database[T]["readable"]>>>
|
|
283
|
+
onSuccess?: (result: z.infer<Database[T]["readable"]>[]) => void
|
|
284
|
+
children: ((props: {
|
|
285
|
+
loading: boolean
|
|
286
|
+
error: Error | null
|
|
287
|
+
result: z.infer<Database[T]["readable"]>[] | null
|
|
288
|
+
fields: Partial<ListProps<z.infer<Database[T]["readable"]>>>
|
|
289
|
+
setField: (key: keyof ListProps<z.infer<Database[T]["readable"]>>, value: any) => void
|
|
290
|
+
callback: (fields: Partial<ListProps<z.infer<Database[T]["readable"]>>>) => Promise<z.infer<Database[T]["readable"]>[]>
|
|
291
|
+
submit: (e?: React.FormEvent) => Promise<boolean>
|
|
292
|
+
}) => React.ReactNode)
|
|
293
|
+
}) {
|
|
294
|
+
type Readable = z.infer<Database[T]["readable"]>
|
|
295
|
+
|
|
296
|
+
const { list } = useTable(table)
|
|
297
|
+
|
|
298
|
+
const callback = useCallback(
|
|
299
|
+
async (fields: Omit<ListProps<Readable>, "table">) => {
|
|
300
|
+
const result = await list.trigger(fields)
|
|
301
|
+
if (onSuccess) onSuccess(result ?? [])
|
|
302
|
+
return result
|
|
303
|
+
},
|
|
304
|
+
[list, onSuccess]
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<FieldsProvider<ListProps<Readable>>
|
|
309
|
+
defaults={(defaults || {}) as ListProps<Readable>}
|
|
310
|
+
>
|
|
311
|
+
{fieldsProps => (
|
|
312
|
+
<FormProvider<ListProps<Readable>, Readable[]>
|
|
313
|
+
data={{ ...fieldsProps.fields, table }}
|
|
314
|
+
callback={callback}
|
|
315
|
+
>
|
|
316
|
+
{formProps => children({...fieldsProps, ...formProps, callback})}
|
|
317
|
+
</FormProvider>
|
|
318
|
+
)}
|
|
319
|
+
</FieldsProvider>
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const useCreateForm = <T extends TableKey>(table: T) => {
|
|
324
|
+
return {
|
|
325
|
+
...useFields<z.infer<Database[T]["writable"]>>(),
|
|
326
|
+
...useForm<
|
|
327
|
+
z.infer<Database[T]["writable"]>,
|
|
328
|
+
z.infer<Database[T]["readable"]>
|
|
329
|
+
>(),
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const useUpdateForm = <T extends TableKey>(table: T) => {
|
|
333
|
+
return {
|
|
334
|
+
...useFields<Partial<z.infer<Database[T]["writable"]>>>(),
|
|
335
|
+
...useForm<
|
|
336
|
+
Partial<z.infer<Database[T]["writable"]>>,
|
|
337
|
+
z.infer<Database[T]["readable"]>
|
|
338
|
+
>(),
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
const useFiltersForm = <T extends TableKey>(table: T) => {
|
|
342
|
+
return {
|
|
343
|
+
...useFields<ListProps<z.infer<Database[T]["readable"]>>>(),
|
|
344
|
+
...useForm<
|
|
345
|
+
ListProps<z.infer<Database[T]["readable"]>>,
|
|
346
|
+
z.infer<Database[T]["readable"]>[]
|
|
347
|
+
>(),
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
return {
|
|
352
|
+
DatabaseProvider,
|
|
353
|
+
useDatabase,
|
|
354
|
+
useTable,
|
|
355
|
+
useTableProvider,
|
|
356
|
+
SingleProvider,
|
|
357
|
+
useSingle,
|
|
358
|
+
CreateForm,
|
|
359
|
+
UpdateForm,
|
|
360
|
+
FilterForm,
|
|
361
|
+
useCreateForm,
|
|
362
|
+
useUpdateForm,
|
|
363
|
+
useFiltersForm,
|
|
364
|
+
}
|
|
365
|
+
}
|
package/packages/env.ts
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { useCallback, useEffect, useRef, useState } from 'react'
|
|
3
|
+
import { Input, Text, Button, Image, HStack, Spinner } from '@chakra-ui/react'
|
|
4
|
+
import { createContext } from 'react'
|
|
5
|
+
import z from 'zod'
|
|
6
|
+
|
|
7
|
+
type FieldsProps<T> = { defaults: T }
|
|
8
|
+
|
|
9
|
+
function useFieldsProvider<T>(props: FieldsProps<T>) {
|
|
10
|
+
|
|
11
|
+
const [fields, setFields] = useState(props.defaults)
|
|
12
|
+
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setFields(props.defaults)
|
|
15
|
+
}, [])
|
|
16
|
+
|
|
17
|
+
const setField = useCallback(
|
|
18
|
+
<K extends keyof T>(name: K, value: T[K]) => {
|
|
19
|
+
setFields(prev => ({ ...prev, [name]: value }))
|
|
20
|
+
},
|
|
21
|
+
[setFields, fields]
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
fields,
|
|
26
|
+
setField,
|
|
27
|
+
setFields,
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const Context = createContext<ReturnType<typeof useFieldsProvider<any>> | undefined>(undefined)
|
|
32
|
+
|
|
33
|
+
export function FieldsProvider<T>({children, ...props}: FieldsProps<T> & {
|
|
34
|
+
children: React.ReactNode | ((value: ReturnType<typeof useFieldsProvider<T>>) => React.ReactNode)
|
|
35
|
+
}) {
|
|
36
|
+
|
|
37
|
+
const value = useFieldsProvider<T>(props)
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<Context.Provider value={value}>
|
|
41
|
+
{typeof children === 'function' ? children(value) : children}
|
|
42
|
+
</Context.Provider>
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const useFields = <T,>() => {
|
|
47
|
+
const context = React.useContext(Context)
|
|
48
|
+
if (context === undefined) {
|
|
49
|
+
throw new Error('useFields must be used within a FieldsProvider')
|
|
50
|
+
}
|
|
51
|
+
return context as ReturnType<typeof useFieldsProvider<T>>
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export const useField = <T,>(fieldName: keyof T) => {
|
|
55
|
+
const { fields, setField } = useFields<T>()
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
value: fields[fieldName],
|
|
59
|
+
setValue: setField
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
interface Field<T> {
|
|
64
|
+
type: string
|
|
65
|
+
View: (props: { type: z.ZodTypeAny, value: T }) => React.ReactElement
|
|
66
|
+
Edit: (props: { type: z.ZodTypeAny, value: T, setValue: (value: T) => void }) => React.ReactElement
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const string: Field<string> = {
|
|
70
|
+
type: 'string',
|
|
71
|
+
View({ type, value }) {
|
|
72
|
+
return <Text>{value}</Text>
|
|
73
|
+
},
|
|
74
|
+
Edit({ type, value, setValue }) {
|
|
75
|
+
return <Input value={value} onChange={e => setValue(e.target.value)} />
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const link: Field<string> = {
|
|
80
|
+
type: 'file',
|
|
81
|
+
View({ type, value }) {
|
|
82
|
+
return <Text>{value}</Text>
|
|
83
|
+
},
|
|
84
|
+
Edit({ type, value, setValue }) {
|
|
85
|
+
return (
|
|
86
|
+
<select value={value} onChange={e => setValue(e.target.value)}/>
|
|
87
|
+
)
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const file: Field<string> = {
|
|
92
|
+
type: 'file',
|
|
93
|
+
View({ type, value }) {
|
|
94
|
+
if (!value) return <Text>-</Text>
|
|
95
|
+
return <Image src={value} alt='file' boxSize='64px' objectFit='cover' />
|
|
96
|
+
},
|
|
97
|
+
Edit({ type, value, setValue }) {
|
|
98
|
+
const inputRef = useRef<HTMLInputElement | null>(null)
|
|
99
|
+
const [loading, setLoading] = useState(false)
|
|
100
|
+
|
|
101
|
+
async function handleFile(e: React.ChangeEvent<HTMLInputElement>) {
|
|
102
|
+
const file = e.target.files?.[0]
|
|
103
|
+
if (!file) return
|
|
104
|
+
setLoading(true)
|
|
105
|
+
try {
|
|
106
|
+
const fd = new FormData()
|
|
107
|
+
fd.append('file', file)
|
|
108
|
+
const res = await fetch('/api/upload', {
|
|
109
|
+
method: 'POST',
|
|
110
|
+
body: fd,
|
|
111
|
+
})
|
|
112
|
+
const json = await res.json()
|
|
113
|
+
if (json.url) setValue(json.url)
|
|
114
|
+
} finally {
|
|
115
|
+
setLoading(false)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return (
|
|
120
|
+
<HStack>
|
|
121
|
+
<input ref={inputRef} type='file' hidden onChange={handleFile} />
|
|
122
|
+
<Button onClick={() => inputRef.current?.click()}>{loading ? <Spinner size='xs'/> : 'Upload'}</Button>
|
|
123
|
+
{value ? <Image src={value} alt='preview' boxSize='64px' objectFit='cover' /> : null}
|
|
124
|
+
</HStack>
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function EditField<T, FieldName extends keyof T = keyof T, FieldType extends keyof typeof fields = keyof typeof fields>({attribute, field, type}: {attribute: FieldName, field: FieldType, type: z.ZodTypeAny}) {
|
|
130
|
+
|
|
131
|
+
const { value, setValue } = useField<T>(attribute)
|
|
132
|
+
|
|
133
|
+
const Edit = fields[field]['Edit']
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
// @ts-expect-error
|
|
137
|
+
<Edit value={value} setValue={setValue} type={type} />
|
|
138
|
+
)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function ViewField<T, FieldName extends keyof T, FieldType extends keyof typeof fields>({attribute, field, type}: {attribute: FieldName, field: FieldType, type: z.ZodTypeAny}) {
|
|
142
|
+
|
|
143
|
+
const { value } = useField<T>(attribute)
|
|
144
|
+
|
|
145
|
+
const View = fields[field]['View']
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
// @ts-expect-error
|
|
149
|
+
<View value={value} type={type} />
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export const fields = {
|
|
154
|
+
string,
|
|
155
|
+
link,
|
|
156
|
+
file: file,
|
|
157
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { initializeApp, cert, getApps } from 'firebase-admin/app'
|
|
2
|
+
import { getFirestore, } from 'firebase-admin/firestore'
|
|
3
|
+
|
|
4
|
+
const account = JSON.parse(process.env.SERVICE_ACCOUNT_JSON as string)
|
|
5
|
+
|
|
6
|
+
const app = getApps()[0] ?? initializeApp({
|
|
7
|
+
credential: cert(account),
|
|
8
|
+
projectId: 'asasvirtuais',
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
const firestore = getFirestore(app, 'asasvirtuais')
|
|
12
|
+
|
|
13
|
+
export { firestore }
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { DatabaseInterface, TableInterface } from './crud'
|
|
2
|
+
import z from 'zod'
|
|
3
|
+
import { firestore } from './firebase'
|
|
4
|
+
|
|
5
|
+
export function firestoreInterface<Schema extends DatabaseInterface, T extends keyof Schema & string>(defaultTable?: T): TableInterface<z.infer<Schema[T]['readable']>, z.infer<Schema[T]['writable']>> {
|
|
6
|
+
type Readable = z.infer<Schema[T]['readable']>
|
|
7
|
+
type Writable = z.infer<Schema[T]['writable']>
|
|
8
|
+
return {
|
|
9
|
+
async find({ table = defaultTable, id }) {
|
|
10
|
+
const docRef = firestore.collection(table as string).doc(id)
|
|
11
|
+
const docSnap = await docRef.get()
|
|
12
|
+
if (docSnap.exists) {
|
|
13
|
+
return { id: docSnap.id, ...docSnap.data() } as Readable
|
|
14
|
+
}
|
|
15
|
+
throw new Error(`Record not found in ${table} with id ${id}`)
|
|
16
|
+
},
|
|
17
|
+
async list({ table = defaultTable, query: q }) {
|
|
18
|
+
let queryRef: FirebaseFirestore.Query = firestore.collection(table as string)
|
|
19
|
+
|
|
20
|
+
if (q) {
|
|
21
|
+
for (const [key, value] of Object.entries(q)) {
|
|
22
|
+
queryRef = queryRef.where(key, '==', value)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const querySnapshot = await queryRef.get()
|
|
27
|
+
return querySnapshot.docs.map(doc => ({ id: doc.id, ...doc.data() } as unknown as Readable))
|
|
28
|
+
},
|
|
29
|
+
async create({ table = defaultTable, data }) {
|
|
30
|
+
const docRef = await firestore.collection(table as string).add(data)
|
|
31
|
+
return { id: docRef.id, ...data } as Readable
|
|
32
|
+
},
|
|
33
|
+
async update({ table = defaultTable, id, data }) {
|
|
34
|
+
const docRef = firestore.collection(table as string).doc(id)
|
|
35
|
+
await docRef.update(data)
|
|
36
|
+
const updatedDoc = await docRef.get()
|
|
37
|
+
return { id: updatedDoc.id, ...updatedDoc.data() } as Readable
|
|
38
|
+
},
|
|
39
|
+
async remove({ table = defaultTable, id }) {
|
|
40
|
+
const docRef = firestore.collection(table as string).doc(id)
|
|
41
|
+
const docSnap = await docRef.get()
|
|
42
|
+
if (!docSnap.exists) {
|
|
43
|
+
throw new Error(`Record not found in ${table} with id ${id}`)
|
|
44
|
+
}
|
|
45
|
+
const data = { id: docSnap.id, ...docSnap.data() } as Readable
|
|
46
|
+
await docRef.delete()
|
|
47
|
+
console.log(`[FIRESTORE] Record removed from ${table} with id ${id}`);
|
|
48
|
+
return data
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
import React, { createContext, useMemo } from 'react'
|
|
3
|
+
import { useCallback, useState } from 'react'
|
|
4
|
+
|
|
5
|
+
type Props<Fields, Result> = {
|
|
6
|
+
data: FormData | Fields
|
|
7
|
+
callback?: (fields: Fields) => Promise<Result>
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useFormProvider<Fields, Result>(props: React.PropsWithChildren<Props<Fields, Result>>) {
|
|
11
|
+
const [result, setResult] = useState<Result | null>(null)
|
|
12
|
+
const [loading, setLoading] = useState(false)
|
|
13
|
+
const [error, setError] = useState<Error | null>(null)
|
|
14
|
+
|
|
15
|
+
const values = useMemo(() => {
|
|
16
|
+
if (props.data instanceof FormData)
|
|
17
|
+
return Object.fromEntries(props.data.entries()) as Fields
|
|
18
|
+
else
|
|
19
|
+
return props.data
|
|
20
|
+
}, [props.data])
|
|
21
|
+
|
|
22
|
+
/** receives an event (optional) and processes the form submission using the fields state as props, stores the result or error in the respective states */
|
|
23
|
+
const submit = useCallback(
|
|
24
|
+
async (e?: any) => {
|
|
25
|
+
e?.preventDefault?.()
|
|
26
|
+
setLoading(true)
|
|
27
|
+
setError(null)
|
|
28
|
+
try {
|
|
29
|
+
if (props.callback && values)
|
|
30
|
+
await props.callback(values)
|
|
31
|
+
setResult(result)
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(err)
|
|
34
|
+
setError(err as Error)
|
|
35
|
+
throw err
|
|
36
|
+
} finally {
|
|
37
|
+
setLoading(false)
|
|
38
|
+
}
|
|
39
|
+
return false
|
|
40
|
+
},
|
|
41
|
+
[values, props.callback]
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
loading,
|
|
46
|
+
result,
|
|
47
|
+
error,
|
|
48
|
+
submit,
|
|
49
|
+
values,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const FormContext = createContext<ReturnType<typeof useFormProvider<any, any>> | undefined>(undefined)
|
|
54
|
+
|
|
55
|
+
export function FormProvider<Fields, Result>({children, ...props}: Props<Fields, Result> & {
|
|
56
|
+
children: (props: ReturnType<typeof useFormProvider<Fields, Result>>) => React.ReactNode
|
|
57
|
+
}) {
|
|
58
|
+
const context = useFormProvider<Fields, Result>(props)
|
|
59
|
+
return (
|
|
60
|
+
<FormContext.Provider value={context}>{children(context)}</FormContext.Provider>
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function useForm<Fields, Result>() {
|
|
65
|
+
return React.useContext(FormContext) as ReturnType<typeof useFormProvider<Fields, Result>>
|
|
66
|
+
}
|