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.
Files changed (86) hide show
  1. package/README.md +78 -0
  2. package/actions/draw.ts +110 -0
  3. package/components/OAuthCard.tsx +346 -0
  4. package/components/icons.tsx +11 -0
  5. package/components/markdown.tsx +18 -0
  6. package/components/stack/list.tsx +21 -0
  7. package/components/stack/menu.tsx +40 -0
  8. package/components/stack/nav.tsx +39 -0
  9. package/components/table/fixed.tsx +59 -0
  10. package/components/table/key-value.tsx +19 -0
  11. package/components/ui/color-mode.tsx +108 -0
  12. package/components/ui/provider.tsx +15 -0
  13. package/components/ui/toaster.tsx +43 -0
  14. package/components/ui/tooltip.tsx +46 -0
  15. package/hooks/useBoolean.tsx +11 -0
  16. package/hooks/useForwardAs.tsx +29 -0
  17. package/hooks/useHash copy.tsx +27 -0
  18. package/hooks/useHash.tsx +27 -0
  19. package/hooks/useIsMobile.tsx +6 -0
  20. package/hooks/useOAuthTokens.ts +97 -0
  21. package/hooks/useOpenRouterModels.ts +80 -0
  22. package/lib/auth0.ts +11 -0
  23. package/lib/blob.ts +3 -0
  24. package/lib/client-token-storage.ts +216 -0
  25. package/lib/oauth-tokens.ts +85 -0
  26. package/lib/react/context.tsx +20 -0
  27. package/lib/react/index.ts +1 -0
  28. package/lib/tools.ts +375 -0
  29. package/next-env.d.ts +5 -0
  30. package/next.config.ts +23 -0
  31. package/package.json +72 -0
  32. package/packages/blob.ts +97 -0
  33. package/packages/chat/components/chat/feed/index.tsx +76 -0
  34. package/packages/chat/components/chat/feed/story.tsx +18 -0
  35. package/packages/chat/components/chat/index.tsx +16 -0
  36. package/packages/chat/components/chat/story.tsx +74 -0
  37. package/packages/chat/components/debug/index.tsx +54 -0
  38. package/packages/chat/components/header/index.tsx +14 -0
  39. package/packages/chat/components/header/menu/index.tsx +63 -0
  40. package/packages/chat/components/header/story.tsx +33 -0
  41. package/packages/chat/components/header/title/index.tsx +35 -0
  42. package/packages/chat/components/index.ts +13 -0
  43. package/packages/chat/components/input/index.tsx +17 -0
  44. package/packages/chat/components/input/menu/index.tsx +35 -0
  45. package/packages/chat/components/input/send.tsx +21 -0
  46. package/packages/chat/components/input/story.tsx +35 -0
  47. package/packages/chat/components/input/textarea/index.tsx +20 -0
  48. package/packages/chat/components/message/file.tsx +103 -0
  49. package/packages/chat/components/message/menu/index.tsx +26 -0
  50. package/packages/chat/components/message/story.tsx +49 -0
  51. package/packages/chat/components/messages/index.tsx +23 -0
  52. package/packages/chat/components/messages/story.tsx +11 -0
  53. package/packages/chat/components/ui/prose.tsx +263 -0
  54. package/packages/chat/edit-message.tsx +49 -0
  55. package/packages/chat/header.tsx +118 -0
  56. package/packages/chat/index.ts +14 -0
  57. package/packages/chat/input.tsx +89 -0
  58. package/packages/chat/message-menu.tsx +57 -0
  59. package/packages/chat/message.tsx +44 -0
  60. package/packages/chat/messages.tsx +44 -0
  61. package/packages/chat/model-selector.tsx +172 -0
  62. package/packages/chat/scenarios.tsx +68 -0
  63. package/packages/chat/settings.tsx +98 -0
  64. package/packages/chat/temperature-slider.tsx +67 -0
  65. package/packages/chat/tool-results.tsx +32 -0
  66. package/packages/crud/core.ts +75 -0
  67. package/packages/crud/fetcher.ts +64 -0
  68. package/packages/crud/index.ts +2 -0
  69. package/packages/crud/next.ts +128 -0
  70. package/packages/crud/react.tsx +365 -0
  71. package/packages/env.ts +8 -0
  72. package/packages/fields.tsx +157 -0
  73. package/packages/firebase.ts +13 -0
  74. package/packages/firestore.ts +51 -0
  75. package/packages/form.tsx +66 -0
  76. package/packages/next.ts +64 -0
  77. package/packages/openrouter.ts +4 -0
  78. package/packages/react/context.tsx +21 -0
  79. package/packages/react/crud.tsx +372 -0
  80. package/packages/react/hooks.ts +90 -0
  81. package/packages/react/store.tsx +20 -0
  82. package/packages/replit-db.ts +219 -0
  83. package/packages/wretch.ts +22 -0
  84. package/packages/yaml.ts +163 -0
  85. package/pnpm-workspace.yaml +4 -0
  86. 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
+ }
@@ -0,0 +1,8 @@
1
+ function env(name: string) {
2
+ const value = process.env[name]
3
+ if ( ! value )
4
+ throw new Error(`Missing environment variable: ${name}`)
5
+ return value as string
6
+ }
7
+
8
+ export default env
@@ -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
+ }