@olinfo/quizms-firebase 4.1.3 → 5.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.
Files changed (57) hide show
  1. package/dist/cli.js +14 -14
  2. package/dist/cli.js.map +1 -1
  3. package/dist/package.json.js +1 -1
  4. package/dist/src/cli/definalize.js +17 -17
  5. package/dist/src/cli/definalize.js.map +1 -1
  6. package/dist/src/cli/export.js +42 -34
  7. package/dist/src/cli/export.js.map +1 -1
  8. package/dist/src/cli/files/firestore-indexes.json.js +1 -1
  9. package/dist/src/cli/files/firestore.rules.js +27 -27
  10. package/dist/src/cli/files/firestore.rules.js.map +1 -1
  11. package/dist/src/cli/files/functions-index.js +3 -3
  12. package/dist/src/cli/files/functions-index.js.map +1 -1
  13. package/dist/src/cli/files/storage.rules.js +3 -3
  14. package/dist/src/cli/files/storage.rules.js.map +1 -1
  15. package/dist/src/cli/import.js +65 -71
  16. package/dist/src/cli/import.js.map +1 -1
  17. package/dist/src/cli/init.js +1 -1
  18. package/dist/src/cli/init.js.map +1 -1
  19. package/dist/src/cli/update-scores.js +7 -7
  20. package/dist/src/cli/update-scores.js.map +1 -1
  21. package/dist/src/cli/utils/converters-admin.js +23 -22
  22. package/dist/src/cli/utils/converters-admin.js.map +1 -1
  23. package/dist/src/functions/api/index.js +18 -18
  24. package/dist/src/functions/api/index.js.map +1 -1
  25. package/dist/src/functions/api/queries.js +21 -21
  26. package/dist/src/functions/api/queries.js.map +1 -1
  27. package/dist/src/functions/api/student.js +46 -47
  28. package/dist/src/functions/api/student.js.map +1 -1
  29. package/dist/src/functions/api/teacher.js +92 -95
  30. package/dist/src/functions/api/teacher.js.map +1 -1
  31. package/dist/src/functions/submissions.js +11 -7
  32. package/dist/src/functions/submissions.js.map +1 -1
  33. package/dist/src/functions/update-scores.js +14 -14
  34. package/dist/src/functions/update-scores.js.map +1 -1
  35. package/dist/src/models/submission.js +13 -0
  36. package/dist/src/models/submission.js.map +1 -0
  37. package/dist/src/web/admin/announcements.js +45 -42
  38. package/dist/src/web/admin/announcements.js.map +1 -1
  39. package/dist/src/web/admin/dashboard.js +43 -43
  40. package/dist/src/web/admin/dashboard.js.map +1 -1
  41. package/dist/src/web/admin/export.js +59 -54
  42. package/dist/src/web/admin/export.js.map +1 -1
  43. package/dist/src/web/admin/provider.js +28 -28
  44. package/dist/src/web/admin/provider.js.map +1 -1
  45. package/dist/src/web/admin/school-table.js +20 -24
  46. package/dist/src/web/admin/school-table.js.map +1 -1
  47. package/dist/src/web/common/api.js +25 -25
  48. package/dist/src/web/common/api.js.map +1 -1
  49. package/dist/src/web/common/converters.js +38 -39
  50. package/dist/src/web/common/converters.js.map +1 -1
  51. package/dist/src/web/entry/student-statement.js +1 -1
  52. package/dist/src/web/entry/student-statement.js.map +1 -1
  53. package/dist/src/web/entry/student.js +80 -75
  54. package/dist/src/web/entry/student.js.map +1 -1
  55. package/dist/src/web/entry/teacher.js +63 -62
  56. package/dist/src/web/entry/teacher.js.map +1 -1
  57. package/package.json +35 -26
@@ -1,44 +1,43 @@
1
- import { jsx as t, jsxs as u, Fragment as f } from "react/jsx-runtime";
2
- import { useMemo as w, useCallback as p, useEffect as v } from "react";
3
- import { Title as F, StudentFormField as x } from "@olinfo/quizms/components";
4
- import { StudentProvider as I } from "@olinfo/quizms/student";
5
- import { useUserAgent as k } from "@olinfo/quizms/utils";
6
- import { Navbar as A, NavbarBrand as C, Form as W, SelectField as y, TextField as E, SubmitButton as H } from "@olinfo/react-components";
7
- import { signOut as N, getAuth as g, signInWithCustomToken as D } from "firebase/auth";
8
- import { waitForPendingWrites as h } from "firebase/firestore";
9
- import { isEqual as T } from "lodash-es";
1
+ import { jsx as e, jsxs as u, Fragment as p } from "react/jsx-runtime";
2
+ import { useMemo as b, useCallback as f, useEffect as S } from "react";
3
+ import { Title as x, StudentFormField as I } from "@olinfo/quizms/components";
4
+ import { StudentProvider as k } from "@olinfo/quizms/student";
5
+ import { useUserAgent as W } from "@olinfo/quizms/utils";
6
+ import { Navbar as A, NavbarBrand as C, Form as y, SelectField as D, TextField as H, SubmitButton as N } from "@olinfo/react-components";
7
+ import { signOut as E, getAuth as v, signInWithCustomToken as T } from "firebase/auth";
8
+ import { waitForPendingWrites as w } from "firebase/firestore";
10
9
  import { studentLogin as j } from "../common/api.js";
11
- import { useDb as S } from "../common/base-login.js";
12
- import { contestConverter as M, studentConverter as P, participationConverter as R } from "../common/converters.js";
13
- import { useWebsite as q } from "../common/website.js";
14
- import { useAuth as z } from "../hooks/auth.js";
15
- import { useCollection as B } from "../hooks/collection.js";
16
- import { useDocument as b } from "../hooks/document.js";
17
- import { StudentRestoring as O } from "./student-restoring.js";
10
+ import { useDb as g } from "../common/base-login.js";
11
+ import { contestConverter as M, studentConverter as P, venueConverter as R } from "../common/converters.js";
12
+ import { useWebsite as z } from "../common/website.js";
13
+ import { useAuth as B } from "../hooks/auth.js";
14
+ import { useCollection as O } from "../hooks/collection.js";
15
+ import { useDocument as h } from "../hooks/document.js";
16
+ import { StudentRestoring as q } from "./student-restoring.js";
18
17
  import { FirebaseStatement as G } from "./student-statement.js";
19
18
  import L from "virtual:quizms-firebase-header";
20
- function lt() {
21
- const e = z("student"), s = q(), [o] = B("contests", M, {
19
+ function me() {
20
+ const t = B("student"), s = z(), [i] = O("contests", M, {
22
21
  constraints: { id: s.contests }
23
22
  });
24
- return e ? /* @__PURE__ */ t(O, { user: e.user, children: /* @__PURE__ */ t(
23
+ return t ? /* @__PURE__ */ e(q, { user: t.user, children: /* @__PURE__ */ e(
25
24
  V,
26
25
  {
27
- contests: o,
28
- participationId: e.claims.participationId,
29
- studentId: e.claims.studentId
26
+ contests: i,
27
+ venueId: t.claims.venueId,
28
+ studentId: t.claims.studentId
30
29
  }
31
- ) }) : /* @__PURE__ */ t(U, { contests: o });
30
+ ) }) : /* @__PURE__ */ e(U, { contests: i });
32
31
  }
33
- function U({ contests: e }) {
34
- const s = S(), o = k(), d = p(
35
- async ({ contestId: r, token: a, ...n }) => {
36
- const l = await j({
37
- contestId: r,
32
+ function U({ contests: t }) {
33
+ const s = g(), i = W(), d = f(
34
+ async ({ contestId: o, token: a, ...r }) => {
35
+ const m = await j({
36
+ contestId: o,
38
37
  token: a,
39
- userData: n,
38
+ userData: r,
40
39
  extraData: {
41
- userAgent: o,
40
+ userAgent: i,
42
41
  windowWidth: window.innerWidth,
43
42
  windowHeight: window.innerHeight,
44
43
  screenWidth: window.screen.availWidth,
@@ -46,80 +45,86 @@ function U({ contests: e }) {
46
45
  pixelRatio: window.devicePixelRatio,
47
46
  darkMode: window.matchMedia("(prefers-color-scheme: dark)").matches
48
47
  }
49
- }), c = g(s.app);
50
- await D(c, l), o.hasFullscreen && await document.documentElement.requestFullscreen?.();
48
+ }), c = v(s.app);
49
+ await T(c, m), i.hasFullscreen && await document.documentElement.requestFullscreen?.();
51
50
  },
52
- [s, o]
53
- ), i = w(
51
+ [s, i]
52
+ ), n = b(
54
53
  () => ({
55
- contestId: e.length === 1 ? e[0].id : void 0
54
+ contestId: t.length === 1 ? t[0].id : void 0
56
55
  }),
57
- [e]
56
+ [t]
58
57
  );
59
- return /* @__PURE__ */ u(f, { children: [
60
- /* @__PURE__ */ t(A, { color: "bg-base-300 text-base-content", children: /* @__PURE__ */ t(C, { children: /* @__PURE__ */ t("div", { className: "flex items-center h-full font-bold", children: /* @__PURE__ */ t(F, {}) }) }) }),
61
- /* @__PURE__ */ u(W, { defaultValue: i, onSubmit: d, className: "p-4 pb-8", children: [
62
- /* @__PURE__ */ t("h1", { className: "mb-2 text-xl font-bold", children: "Accedi alla gara" }),
63
- /* @__PURE__ */ t(
64
- y,
58
+ return /* @__PURE__ */ u(p, { children: [
59
+ /* @__PURE__ */ e(A, { color: "bg-base-300 text-base-content", children: /* @__PURE__ */ e(C, { children: /* @__PURE__ */ e("div", { className: "flex items-center h-full font-bold", children: /* @__PURE__ */ e(x, {}) }) }) }),
60
+ /* @__PURE__ */ u(y, { defaultValue: n, onSubmit: d, className: "p-4 pb-8", children: [
61
+ /* @__PURE__ */ e("h1", { className: "mb-2 text-xl font-bold", children: "Accedi alla gara" }),
62
+ /* @__PURE__ */ e(
63
+ D,
65
64
  {
66
65
  field: "contestId",
67
66
  label: "Gara",
68
- options: Object.fromEntries(e.map((r) => [r.id, r.name])),
67
+ options: Object.fromEntries(t.map((o) => [o.id, o.name])),
69
68
  placeholder: "Seleziona una gara"
70
69
  }
71
70
  ),
72
- ({ contestId: r }) => {
73
- const a = e.find((n) => n.id === r);
71
+ ({ contestId: o }) => {
72
+ const a = t.find((r) => r.id === o);
74
73
  if (a)
75
- return /* @__PURE__ */ u(f, { children: [
76
- a.userData.map((n) => /* @__PURE__ */ t(x, { field: n }, n.name)),
77
- /* @__PURE__ */ t(E, { field: "token", label: "Codice prova", placeholder: "aaaaa-bbbbb-ccccc" }),
78
- /* @__PURE__ */ t(H, { children: "Inizia la prova" })
74
+ return /* @__PURE__ */ u(p, { children: [
75
+ a.userData.map((r) => /* @__PURE__ */ e(I, { field: r }, r.name)),
76
+ /* @__PURE__ */ e(H, { field: "token", label: "Codice prova", placeholder: "aaaaa-bbbbb-ccccc" }),
77
+ /* @__PURE__ */ e(N, { children: "Inizia la prova" })
79
78
  ] });
80
79
  }
81
80
  ] })
82
81
  ] });
83
82
  }
84
83
  function V({
85
- contests: e,
86
- participationId: s,
87
- studentId: o
84
+ contests: t,
85
+ venueId: s,
86
+ studentId: i
88
87
  }) {
89
- const d = S(), [i, r] = b(
90
- `participations/${s}/students`,
91
- o,
88
+ const d = g(), [n, o] = h(
89
+ `venues/${s}/students`,
90
+ i,
92
91
  P
93
- ), [a] = b("participations", s, R), n = w(
94
- () => e.find((m) => m.id === i.contestId),
95
- [e, i.contestId]
96
- ), l = p(async () => {
97
- await h(d);
98
- }, [d]), c = p(async () => {
99
- await h(d), await N(g(d.app)), window.location.reload();
92
+ ), [a] = h("venues", s, R), r = b(
93
+ () => t.find((l) => l.id === n.contestId),
94
+ [t, n.contestId]
95
+ ), m = f(async () => {
96
+ await o({
97
+ ...n,
98
+ participationWindow: { start: n.participationWindow.start, end: /* @__PURE__ */ new Date() }
99
+ }), await w(d);
100
+ }, [d, n, o]), c = f(async () => {
101
+ await w(d), await E(v(d.app)), window.location.reload();
100
102
  }, [d]);
101
- return v(() => {
102
- a.token !== i.token && c();
103
- }, [a.token, i.token, c]), /* @__PURE__ */ u(
104
- I,
103
+ return S(() => {
104
+ a.token !== n.token && c();
105
+ }, [a.token, n.token, c]), /* @__PURE__ */ u(
106
+ k,
105
107
  {
106
- contest: n,
107
- participation: a,
108
- student: i,
109
- setStudent: async (m) => {
110
- T(i, m) || await r({ ...m });
108
+ contest: r,
109
+ venue: a,
110
+ student: n,
111
+ setAnswer: async (l, F) => {
112
+ await o({
113
+ ...n,
114
+ answers: { ...n.answers, [l]: F }
115
+ });
111
116
  },
112
117
  logout: c,
113
- onSubmit: l,
118
+ submit: m,
114
119
  enforceFullscreen: !0,
115
120
  children: [
116
- /* @__PURE__ */ t(L, { contestId: n.id }),
117
- /* @__PURE__ */ t(G, {})
121
+ /* @__PURE__ */ e(L, { contestId: r.id }),
122
+ /* @__PURE__ */ e(G, {})
118
123
  ]
119
124
  }
120
125
  );
121
126
  }
122
127
  export {
123
- lt as default
128
+ me as default
124
129
  };
125
130
  //# sourceMappingURL=student.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"student.js","sources":["../../../../src/web/entry/student.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo } from \"react\";\n\nimport { StudentFormField, Title } from \"@olinfo/quizms/components\";\nimport type { Contest, Student } from \"@olinfo/quizms/models\";\nimport { StudentProvider } from \"@olinfo/quizms/student\";\nimport { useUserAgent } from \"@olinfo/quizms/utils\";\nimport {\n Form,\n Navbar,\n NavbarBrand,\n SelectField,\n SubmitButton,\n TextField,\n} from \"@olinfo/react-components\";\nimport { getAuth, signInWithCustomToken, signOut } from \"firebase/auth\";\nimport { waitForPendingWrites } from \"firebase/firestore\";\nimport { isEqual } from \"lodash-es\";\n\nimport { studentLogin } from \"~/web/common/api\";\nimport { useDb } from \"~/web/common/base-login\";\nimport {\n contestConverter,\n participationConverter,\n studentConverter,\n} from \"~/web/common/converters\";\nimport { useWebsite } from \"~/web/common/website\";\nimport { useAuth, useCollection, useDocument } from \"~/web/hooks\";\n\nimport { StudentRestoring } from \"./student-restoring\";\nimport { FirebaseStatement } from \"./student-statement\";\n// @ts-expect-error\nimport Header from \"virtual:quizms-firebase-header\";\n\nexport default function StudentEntry() {\n const auth = useAuth(\"student\");\n\n const website = useWebsite();\n const [contests] = useCollection(\"contests\", contestConverter, {\n constraints: { id: website.contests },\n });\n\n if (auth) {\n return (\n <StudentRestoring user={auth.user}>\n <StudentInner\n contests={contests}\n participationId={auth.claims.participationId}\n studentId={auth.claims.studentId}\n />\n </StudentRestoring>\n );\n }\n\n return <StudentForm contests={contests} />;\n}\n\ntype FormStudent = {\n contestId: string;\n token: string;\n} & Required<NonNullable<Student[\"userData\"]>>;\n\nfunction StudentForm({ contests }: { contests: Contest[] }) {\n const db = useDb();\n const ua = useUserAgent();\n\n const submit = useCallback(\n async ({ contestId, token, ...userData }: FormStudent) => {\n const authToken = await studentLogin({\n contestId,\n token,\n userData,\n extraData: {\n userAgent: ua,\n windowWidth: window.innerWidth,\n windowHeight: window.innerHeight,\n screenWidth: window.screen.availWidth,\n screenHeight: window.screen.availHeight,\n pixelRatio: window.devicePixelRatio,\n darkMode: window.matchMedia(\"(prefers-color-scheme: dark)\").matches,\n },\n });\n\n const auth = getAuth(db.app);\n await signInWithCustomToken(auth, authToken);\n if (ua.hasFullscreen) {\n await document.documentElement.requestFullscreen?.();\n }\n },\n [db, ua],\n );\n\n const defaultValue = useMemo(\n () => ({\n contestId: contests.length === 1 ? contests[0].id : undefined,\n }),\n [contests],\n );\n\n return (\n <>\n <Navbar color=\"bg-base-300 text-base-content\">\n <NavbarBrand>\n <div className=\"flex items-center h-full font-bold\">\n <Title />\n </div>\n </NavbarBrand>\n </Navbar>\n <Form defaultValue={defaultValue} onSubmit={submit} className=\"p-4 pb-8\">\n <h1 className=\"mb-2 text-xl font-bold\">Accedi alla gara</h1>\n <SelectField\n field=\"contestId\"\n label=\"Gara\"\n options={Object.fromEntries(contests.map((c) => [c.id, c.name]))}\n placeholder=\"Seleziona una gara\"\n />\n {({ contestId }) => {\n const contest = contests.find((c) => c.id === contestId);\n if (!contest) return;\n return (\n <>\n {contest.userData.map((field) => (\n <StudentFormField key={field.name} field={field} />\n ))}\n <TextField field=\"token\" label=\"Codice prova\" placeholder=\"aaaaa-bbbbb-ccccc\" />\n <SubmitButton>Inizia la prova</SubmitButton>\n </>\n );\n }}\n </Form>\n </>\n );\n}\n\nfunction StudentInner({\n contests,\n participationId,\n studentId,\n}: {\n contests: Contest[];\n participationId: string;\n studentId: string;\n}) {\n const db = useDb();\n\n const [student, setStudent] = useDocument(\n `participations/${participationId}/students`,\n studentId,\n studentConverter,\n );\n const [participation] = useDocument(\"participations\", participationId, participationConverter);\n\n const contest = useMemo(\n () => contests.find((c) => c.id === student.contestId)!,\n [contests, student.contestId],\n );\n\n const onSubmit = useCallback(async () => {\n await waitForPendingWrites(db);\n }, [db]);\n\n const logout = useCallback(async () => {\n await waitForPendingWrites(db);\n await signOut(getAuth(db.app));\n window.location.reload();\n }, [db]);\n\n useEffect(() => {\n if (participation.token !== student.token) {\n void logout();\n }\n }, [participation.token, student.token, logout]);\n\n const setStudentAndSubmission = async (newStudent: Student) => {\n if (!isEqual(student, newStudent)) {\n await setStudent({ ...newStudent });\n }\n };\n\n return (\n <StudentProvider\n contest={contest}\n participation={participation}\n student={student}\n setStudent={setStudentAndSubmission}\n logout={logout}\n onSubmit={onSubmit}\n enforceFullscreen={true}>\n <Header contestId={contest.id} />\n <FirebaseStatement />\n </StudentProvider>\n );\n}\n"],"names":["StudentEntry","auth","useAuth","website","useWebsite","contests","useCollection","contestConverter","jsx","StudentRestoring","StudentInner","StudentForm","db","useDb","ua","useUserAgent","submit","useCallback","contestId","token","userData","authToken","studentLogin","getAuth","signInWithCustomToken","defaultValue","useMemo","jsxs","Fragment","Navbar","NavbarBrand","Title","Form","SelectField","c","contest","field","StudentFormField","TextField","SubmitButton","participationId","studentId","student","setStudent","useDocument","studentConverter","participation","participationConverter","onSubmit","waitForPendingWrites","logout","signOut","useEffect","StudentProvider","newStudent","isEqual","Header","FirebaseStatement"],"mappings":"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"}
1
+ {"version":3,"file":"student.js","sources":["../../../../src/web/entry/student.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo } from \"react\";\n\nimport { StudentFormField, Title } from \"@olinfo/quizms/components\";\nimport type { Answer, Contest, Student } from \"@olinfo/quizms/models\";\nimport { StudentProvider } from \"@olinfo/quizms/student\";\nimport { useUserAgent } from \"@olinfo/quizms/utils\";\nimport {\n Form,\n Navbar,\n NavbarBrand,\n SelectField,\n SubmitButton,\n TextField,\n} from \"@olinfo/react-components\";\nimport { getAuth, signInWithCustomToken, signOut } from \"firebase/auth\";\nimport { waitForPendingWrites } from \"firebase/firestore\";\n\nimport { studentLogin } from \"~/web/common/api\";\nimport { useDb } from \"~/web/common/base-login\";\nimport { contestConverter, studentConverter, venueConverter } from \"~/web/common/converters\";\nimport { useWebsite } from \"~/web/common/website\";\nimport { useAuth, useCollection, useDocument } from \"~/web/hooks\";\n\nimport { StudentRestoring } from \"./student-restoring\";\nimport { FirebaseStatement } from \"./student-statement\";\n// @ts-expect-error\nimport Header from \"virtual:quizms-firebase-header\";\n\nexport default function StudentEntry() {\n const auth = useAuth(\"student\");\n\n const website = useWebsite();\n const [contests] = useCollection(\"contests\", contestConverter, {\n constraints: { id: website.contests },\n });\n\n if (auth) {\n return (\n <StudentRestoring user={auth.user}>\n <StudentInner\n contests={contests}\n venueId={auth.claims.venueId}\n studentId={auth.claims.studentId}\n />\n </StudentRestoring>\n );\n }\n\n return <StudentForm contests={contests} />;\n}\n\ntype FormStudent = {\n contestId: string;\n token: string;\n} & Required<NonNullable<Student[\"userData\"]>>;\n\nfunction StudentForm({ contests }: { contests: Contest[] }) {\n const db = useDb();\n const ua = useUserAgent();\n\n const submit = useCallback(\n async ({ contestId, token, ...userData }: FormStudent) => {\n const authToken = await studentLogin({\n contestId,\n token,\n userData,\n extraData: {\n userAgent: ua,\n windowWidth: window.innerWidth,\n windowHeight: window.innerHeight,\n screenWidth: window.screen.availWidth,\n screenHeight: window.screen.availHeight,\n pixelRatio: window.devicePixelRatio,\n darkMode: window.matchMedia(\"(prefers-color-scheme: dark)\").matches,\n },\n });\n\n const auth = getAuth(db.app);\n await signInWithCustomToken(auth, authToken);\n if (ua.hasFullscreen) {\n await document.documentElement.requestFullscreen?.();\n }\n },\n [db, ua],\n );\n\n const defaultValue = useMemo(\n () => ({\n contestId: contests.length === 1 ? contests[0].id : undefined,\n }),\n [contests],\n );\n\n return (\n <>\n <Navbar color=\"bg-base-300 text-base-content\">\n <NavbarBrand>\n <div className=\"flex items-center h-full font-bold\">\n <Title />\n </div>\n </NavbarBrand>\n </Navbar>\n <Form defaultValue={defaultValue} onSubmit={submit} className=\"p-4 pb-8\">\n <h1 className=\"mb-2 text-xl font-bold\">Accedi alla gara</h1>\n <SelectField\n field=\"contestId\"\n label=\"Gara\"\n options={Object.fromEntries(contests.map((c) => [c.id, c.name]))}\n placeholder=\"Seleziona una gara\"\n />\n {({ contestId }) => {\n const contest = contests.find((c) => c.id === contestId);\n if (!contest) return;\n return (\n <>\n {contest.userData.map((field) => (\n <StudentFormField key={field.name} field={field} />\n ))}\n <TextField field=\"token\" label=\"Codice prova\" placeholder=\"aaaaa-bbbbb-ccccc\" />\n <SubmitButton>Inizia la prova</SubmitButton>\n </>\n );\n }}\n </Form>\n </>\n );\n}\n\nfunction StudentInner({\n contests,\n venueId,\n studentId,\n}: {\n contests: Contest[];\n venueId: string;\n studentId: string;\n}) {\n const db = useDb();\n\n const [student, setStudent] = useDocument(\n `venues/${venueId}/students`,\n studentId,\n studentConverter,\n );\n const [venue] = useDocument(\"venues\", venueId, venueConverter);\n\n const contest = useMemo(\n () => contests.find((c) => c.id === student.contestId)!,\n [contests, student.contestId],\n );\n\n const submit = useCallback(async () => {\n await setStudent({\n ...student,\n participationWindow: { start: student.participationWindow!.start, end: new Date() },\n });\n await waitForPendingWrites(db);\n }, [db, student, setStudent]);\n\n const logout = useCallback(async () => {\n await waitForPendingWrites(db);\n await signOut(getAuth(db.app));\n window.location.reload();\n }, [db]);\n\n useEffect(() => {\n if (venue.token !== student.token) {\n void logout();\n }\n }, [venue.token, student.token, logout]);\n\n const setAnswer = async (problemId: string, answer: Answer) => {\n await setStudent({\n ...student,\n answers: { ...student.answers, [problemId]: answer },\n });\n };\n\n return (\n <StudentProvider\n contest={contest}\n venue={venue}\n student={student}\n setAnswer={setAnswer}\n logout={logout}\n submit={submit}\n enforceFullscreen={true}>\n <Header contestId={contest.id} />\n <FirebaseStatement />\n </StudentProvider>\n );\n}\n"],"names":["StudentEntry","auth","useAuth","website","useWebsite","contests","useCollection","contestConverter","jsx","StudentRestoring","StudentInner","StudentForm","db","useDb","ua","useUserAgent","submit","useCallback","contestId","token","userData","authToken","studentLogin","getAuth","signInWithCustomToken","defaultValue","useMemo","jsxs","Fragment","Navbar","NavbarBrand","Title","Form","SelectField","c","contest","field","StudentFormField","TextField","SubmitButton","venueId","studentId","student","setStudent","useDocument","studentConverter","venue","venueConverter","waitForPendingWrites","logout","signOut","useEffect","StudentProvider","problemId","answer","Header","FirebaseStatement"],"mappings":"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"}
@@ -1,59 +1,60 @@
1
- import { jsx as d } from "react/jsx-runtime";
2
- import { useCallback as g } from "react";
3
- import { TeacherProvider as h } from "@olinfo/quizms/teacher";
4
- import { signOut as w, getAuth as y } from "firebase/auth";
5
- import { writeBatch as f, doc as u, serverTimestamp as b } from "firebase/firestore";
6
- import { getStorage as C, getBytes as P, ref as R } from "firebase/storage";
7
- import { finalizeParticipation as A, stopParticipation as I, startParticipation as S } from "../common/api.js";
8
- import { useDb as l } from "../common/base-login.js";
9
- import { contestConverter as k, participationConverter as v, studentRestoreConvert as B, studentConverter as T, announcementConverter as $, variantConverter as j } from "../common/converters.js";
10
- import x from "../common/token-login.js";
11
- import { useWebsite as z } from "../common/website.js";
1
+ import { jsx as u } from "react/jsx-runtime";
2
+ import { useCallback as w } from "react";
3
+ import { TeacherProvider as C } from "@olinfo/quizms/teacher";
4
+ import { signOut as g, getAuth as y } from "firebase/auth";
5
+ import { writeBatch as f, doc as p, serverTimestamp as b } from "firebase/firestore";
6
+ import { getStorage as h, getBytes as R, ref as A } from "firebase/storage";
7
+ import { finalizeVenue as S, stopContestWindow as k, startContestWindow as B } from "../common/api.js";
8
+ import { useDb as v } from "../common/base-login.js";
9
+ import { contestConverter as I, venueConverter as l, studentRestoreConvert as T, studentConverter as W, announcementConverter as P, variantConverter as $ } from "../common/converters.js";
10
+ import j from "../common/token-login.js";
11
+ import { useWebsite as x } from "../common/website.js";
12
12
  import "lodash-es";
13
13
  import "react-error-boundary";
14
14
  import "swr";
15
- import { useCollection as c } from "../hooks/collection.js";
16
- import { useDocument as D } from "../hooks/document.js";
17
- import { FirebaseStatement as E } from "./student-statement.js";
18
- function rt() {
19
- return /* @__PURE__ */ d(x, { allowedRole: "teacher", children: ({ user: t, claims: e }) => /* @__PURE__ */ d(O, { user: t, schoolId: e.schoolId }) });
15
+ import { useCollection as d } from "../hooks/collection.js";
16
+ import { useDocument as z } from "../hooks/document.js";
17
+ import { FirebaseStatement as D } from "./student-statement.js";
18
+ function st() {
19
+ return /* @__PURE__ */ u(j, { allowedRole: "teacher", children: ({ user: t, claims: e }) => /* @__PURE__ */ u(E, { user: t, schoolId: e.schoolId }) });
20
20
  }
21
- function O({ user: t, schoolId: e }) {
22
- const r = l(), s = z(), [a] = c("contests", k, {
23
- constraints: { id: s.contests }
24
- }), [m] = c("participations", v, {
25
- constraints: { contestId: s.contests, schoolId: e }
26
- }), [n] = c("variants", j), i = g(async () => {
27
- await w(y(r.app)), window.location.reload();
28
- }, [r]);
29
- return /* @__PURE__ */ d(
30
- h,
21
+ function E({ user: t, schoolId: e }) {
22
+ const s = v(), r = x(), [i] = d("contests", I, {
23
+ constraints: { id: r.contests }
24
+ }), [c] = d("venues", l, {
25
+ constraints: { contestId: r.contests, schoolId: e }
26
+ }), [o] = d("variants", $), a = w(async () => {
27
+ await g(y(s.app)), window.location.reload();
28
+ }, [s]);
29
+ return /* @__PURE__ */ u(
30
+ C,
31
31
  {
32
- participations: m,
33
- contests: a,
34
- startParticipation: (...o) => S(t, ...o),
35
- stopParticipation: (...o) => I(t, ...o),
36
- finalizeParticipation: (...o) => A(t, ...o),
37
- variants: n,
38
- logout: i,
39
- statementComponent: () => /* @__PURE__ */ d(E, {}),
40
- getPdfStatements: (o, p) => F(r, o, p),
41
- useAnnouncements: L,
42
- useStudents: W,
43
- useStudentRestores: G
32
+ name: c[0]?.name ?? e,
33
+ venues: c,
34
+ contests: i,
35
+ startContestWindow: (...n) => B(t, ...n),
36
+ stopContestWindow: (...n) => k(t, ...n),
37
+ finalizeVenue: (...n) => S(t, ...n),
38
+ variants: o,
39
+ logout: a,
40
+ statementComponent: () => /* @__PURE__ */ u(D, {}),
41
+ getPdfStatements: (n, m) => O(s, n, m),
42
+ useAnnouncements: V,
43
+ useStudents: F,
44
+ useStudentRestores: L
44
45
  }
45
46
  );
46
47
  }
47
- async function F(t, e, r) {
48
- const s = C(t.app), a = await Promise.all(
49
- r.map(
50
- async (m) => [m, await P(R(s, `statements/${e}/${m}/statement.pdf`))]
48
+ async function O(t, e, s) {
49
+ const r = h(t.app), i = await Promise.all(
50
+ s.map(
51
+ async (c) => [c, await R(A(r, `statements/${e}/${c}/statement.pdf`))]
51
52
  )
52
53
  );
53
- return Object.fromEntries(a);
54
+ return Object.fromEntries(i);
54
55
  }
55
- function L(t) {
56
- const [e] = c("announcements", $, {
56
+ function V(t) {
57
+ const [e] = d("announcements", P, {
57
58
  arrayConstraints: {
58
59
  contestIds: t
59
60
  },
@@ -61,30 +62,30 @@ function L(t) {
61
62
  });
62
63
  return e;
63
64
  }
64
- function W(t) {
65
- return c(`participations/${t}/students`, T, {
65
+ function F(t) {
66
+ return d(`venues/${t}/students`, W, {
66
67
  orderBy: "createdAt"
67
68
  });
68
69
  }
69
- function G(t) {
70
- const e = l(), [r] = D("participations", t, v), [s] = c("studentRestores", B, {
71
- constraints: { participationId: t, token: r.token, status: "pending" },
70
+ function L(t) {
71
+ const e = v(), [s] = z("venues", t, l), [r] = d("studentRestores", T, {
72
+ constraints: { venueId: t, token: s.token ?? void 0, status: "pending" },
72
73
  orderBy: "createdAt"
73
- }), a = async (n, i) => {
74
- const o = f(e);
75
- for (const p of s)
76
- p.studentId === n && p.id !== i && o.update(u(e, "studentRestores", p.id), { status: "revoked" });
77
- await o.commit();
74
+ }), i = async (o, a) => {
75
+ const n = f(e);
76
+ for (const m of r)
77
+ m.studentId === o && m.id !== a && n.update(p(e, "studentRestores", m.id), { status: "revoked" });
78
+ await n.commit();
78
79
  };
79
- return [s, async (n) => {
80
- const i = f(e);
81
- i.update(u(e, `/participations/${t}/students`, n.studentId), {
82
- uid: n.id,
80
+ return [r, async (o) => {
81
+ const a = f(e);
82
+ a.update(p(e, `/venues/${t}/students`, o.studentId), {
83
+ uid: o.id,
83
84
  updatedAt: b()
84
- }), i.delete(u(e, "studentRestores", n.id)), await i.commit(), await a(n.studentId, n.id);
85
- }, a];
85
+ }), a.delete(p(e, "studentRestores", o.id)), await a.commit(), await i(o.studentId, o.id);
86
+ }, i];
86
87
  }
87
88
  export {
88
- rt as default
89
+ st as default
89
90
  };
90
91
  //# sourceMappingURL=teacher.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"teacher.js","sources":["../../../../src/web/entry/teacher.tsx"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport type { StudentRestore } from \"@olinfo/quizms/models\";\nimport { TeacherProvider } from \"@olinfo/quizms/teacher\";\nimport { getAuth, signOut, type User } from \"firebase/auth\";\nimport { doc, type Firestore, serverTimestamp, writeBatch } from \"firebase/firestore\";\nimport { getBytes, getStorage, ref } from \"firebase/storage\";\n\nimport { finalizeParticipation, startParticipation, stopParticipation } from \"~/web/common/api\";\nimport { useDb } from \"~/web/common/base-login\";\nimport {\n announcementConverter,\n contestConverter,\n participationConverter,\n studentConverter,\n studentRestoreConvert,\n variantConverter,\n} from \"~/web/common/converters\";\nimport TokenLogin from \"~/web/common/token-login\";\nimport { useWebsite } from \"~/web/common/website\";\nimport { useCollection, useDocument } from \"~/web/hooks\";\n\nimport { FirebaseStatement } from \"./student-statement\";\n\nexport default function TeacherEntry() {\n return (\n <TokenLogin allowedRole=\"teacher\">\n {({ user, claims }) => <TeacherInner user={user} schoolId={claims.schoolId} />}\n </TokenLogin>\n );\n}\n\nfunction TeacherInner({ user, schoolId }: { user: User; schoolId: string }) {\n const db = useDb();\n\n const website = useWebsite();\n const [contests] = useCollection(\"contests\", contestConverter, {\n constraints: { id: website.contests },\n });\n const [participations] = useCollection(\"participations\", participationConverter, {\n constraints: { contestId: website.contests, schoolId: schoolId },\n });\n const [variants] = useCollection(\"variants\", variantConverter);\n\n const logout = useCallback(async () => {\n await signOut(getAuth(db.app));\n window.location.reload();\n }, [db]);\n\n return (\n <TeacherProvider\n participations={participations}\n contests={contests}\n startParticipation={(...args) => startParticipation(user, ...args)}\n stopParticipation={(...args) => stopParticipation(user, ...args)}\n finalizeParticipation={(...args) => finalizeParticipation(user, ...args)}\n variants={variants}\n logout={logout}\n statementComponent={() => <FirebaseStatement />}\n getPdfStatements={(contestId, variantIds) => getPdfStatements(db, contestId, variantIds)}\n useAnnouncements={useAnnouncements}\n useStudents={useStudents}\n useStudentRestores={useStudentRestores}\n />\n );\n}\n\nasync function getPdfStatements(\n db: Firestore,\n contestId: string,\n variantIds: string[],\n): Promise<Record<string, ArrayBuffer>> {\n const storage = getStorage(db.app);\n\n const files = await Promise.all(\n variantIds.map(\n async (id) =>\n [id, await getBytes(ref(storage, `statements/${contestId}/${id}/statement.pdf`))] as const,\n ),\n );\n return Object.fromEntries(files);\n}\n\nfunction useAnnouncements(contestId: string) {\n const [announcements] = useCollection(\"announcements\", announcementConverter, {\n arrayConstraints: {\n contestIds: contestId,\n },\n orderBy: [\"createdAt\", \"desc\"],\n });\n return announcements;\n}\n\nfunction useStudents(participationId: string) {\n return useCollection(`participations/${participationId}/students`, studentConverter, {\n orderBy: \"createdAt\",\n });\n}\n\nfunction useStudentRestores(\n participationId: string,\n): readonly [\n StudentRestore[],\n (request: StudentRestore) => Promise<void>,\n (studentId: string) => Promise<void>,\n] {\n const db = useDb();\n\n const [participation] = useDocument(\"participations\", participationId, participationConverter);\n\n const [studentRestores] = useCollection(\"studentRestores\", studentRestoreConvert, {\n constraints: { participationId, token: participation.token, status: \"pending\" },\n orderBy: \"createdAt\",\n });\n\n const reject = async (studentId: string, excludeRestoreId?: string) => {\n const batch = writeBatch(db);\n\n for (const studentRestore of studentRestores) {\n if (studentRestore.studentId === studentId && studentRestore.id !== excludeRestoreId) {\n batch.update(doc(db, \"studentRestores\", studentRestore.id), { status: \"revoked\" });\n }\n }\n\n await batch.commit();\n };\n\n const approve = async (request: StudentRestore) => {\n const batch = writeBatch(db);\n batch.update(doc(db, `/participations/${participationId}/students`, request.studentId), {\n uid: request.id,\n updatedAt: serverTimestamp(),\n });\n batch.delete(doc(db, \"studentRestores\", request.id));\n await batch.commit();\n\n await reject(request.studentId, request.id);\n };\n\n return [studentRestores, approve, reject];\n}\n"],"names":["TeacherEntry","jsx","TokenLogin","user","claims","TeacherInner","schoolId","db","useDb","website","useWebsite","contests","useCollection","contestConverter","participations","participationConverter","variants","variantConverter","logout","useCallback","signOut","getAuth","TeacherProvider","args","startParticipation","stopParticipation","finalizeParticipation","FirebaseStatement","contestId","variantIds","getPdfStatements","useAnnouncements","useStudents","useStudentRestores","storage","getStorage","files","id","getBytes","ref","announcements","announcementConverter","participationId","studentConverter","participation","useDocument","studentRestores","studentRestoreConvert","reject","studentId","excludeRestoreId","batch","writeBatch","studentRestore","doc","request","serverTimestamp"],"mappings":"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"}
1
+ {"version":3,"file":"teacher.js","sources":["../../../../src/web/entry/teacher.tsx"],"sourcesContent":["import { useCallback } from \"react\";\n\nimport type { StudentRestore } from \"@olinfo/quizms/models\";\nimport { TeacherProvider } from \"@olinfo/quizms/teacher\";\nimport { getAuth, signOut, type User } from \"firebase/auth\";\nimport { doc, type Firestore, serverTimestamp, writeBatch } from \"firebase/firestore\";\nimport { getBytes, getStorage, ref } from \"firebase/storage\";\n\nimport { finalizeVenue, startContestWindow, stopContestWindow } from \"~/web/common/api\";\nimport { useDb } from \"~/web/common/base-login\";\nimport {\n announcementConverter,\n contestConverter,\n studentConverter,\n studentRestoreConvert,\n variantConverter,\n venueConverter,\n} from \"~/web/common/converters\";\nimport TokenLogin from \"~/web/common/token-login\";\nimport { useWebsite } from \"~/web/common/website\";\nimport { useCollection, useDocument } from \"~/web/hooks\";\n\nimport { FirebaseStatement } from \"./student-statement\";\n\nexport default function TeacherEntry() {\n return (\n <TokenLogin allowedRole=\"teacher\">\n {({ user, claims }) => <TeacherInner user={user} schoolId={claims.schoolId} />}\n </TokenLogin>\n );\n}\n\nfunction TeacherInner({ user, schoolId }: { user: User; schoolId: string }) {\n const db = useDb();\n\n const website = useWebsite();\n const [contests] = useCollection(\"contests\", contestConverter, {\n constraints: { id: website.contests },\n });\n const [venues] = useCollection(\"venues\", venueConverter, {\n constraints: { contestId: website.contests, schoolId: schoolId },\n });\n const [variants] = useCollection(\"variants\", variantConverter);\n\n const logout = useCallback(async () => {\n await signOut(getAuth(db.app));\n window.location.reload();\n }, [db]);\n\n return (\n <TeacherProvider\n name={venues[0]?.name ?? schoolId}\n venues={venues}\n contests={contests}\n startContestWindow={(...args) => startContestWindow(user, ...args)}\n stopContestWindow={(...args) => stopContestWindow(user, ...args)}\n finalizeVenue={(...args) => finalizeVenue(user, ...args)}\n variants={variants}\n logout={logout}\n statementComponent={() => <FirebaseStatement />}\n getPdfStatements={(contestId, variantIds) => getPdfStatements(db, contestId, variantIds)}\n useAnnouncements={useAnnouncements}\n useStudents={useStudents}\n useStudentRestores={useStudentRestores}\n />\n );\n}\n\nasync function getPdfStatements(\n db: Firestore,\n contestId: string,\n variantIds: string[],\n): Promise<Record<string, ArrayBuffer>> {\n const storage = getStorage(db.app);\n\n const files = await Promise.all(\n variantIds.map(\n async (id) =>\n [id, await getBytes(ref(storage, `statements/${contestId}/${id}/statement.pdf`))] as const,\n ),\n );\n return Object.fromEntries(files);\n}\n\nfunction useAnnouncements(contestId: string) {\n const [announcements] = useCollection(\"announcements\", announcementConverter, {\n arrayConstraints: {\n contestIds: contestId,\n },\n orderBy: [\"createdAt\", \"desc\"],\n });\n return announcements;\n}\n\nfunction useStudents(venueId: string) {\n return useCollection(`venues/${venueId}/students`, studentConverter, {\n orderBy: \"createdAt\",\n });\n}\n\nfunction useStudentRestores(\n venueId: string,\n): readonly [\n StudentRestore[],\n (request: StudentRestore) => Promise<void>,\n (studentId: string) => Promise<void>,\n] {\n const db = useDb();\n\n const [venue] = useDocument(\"venues\", venueId, venueConverter);\n\n const [studentRestores] = useCollection(\"studentRestores\", studentRestoreConvert, {\n constraints: { venueId, token: venue.token ?? undefined, status: \"pending\" },\n orderBy: \"createdAt\",\n });\n\n const reject = async (studentId: string, excludeRestoreId?: string) => {\n const batch = writeBatch(db);\n\n for (const studentRestore of studentRestores) {\n if (studentRestore.studentId === studentId && studentRestore.id !== excludeRestoreId) {\n batch.update(doc(db, \"studentRestores\", studentRestore.id), { status: \"revoked\" });\n }\n }\n\n await batch.commit();\n };\n\n const approve = async (request: StudentRestore) => {\n const batch = writeBatch(db);\n batch.update(doc(db, `/venues/${venueId}/students`, request.studentId), {\n uid: request.id,\n updatedAt: serverTimestamp(),\n });\n batch.delete(doc(db, \"studentRestores\", request.id));\n await batch.commit();\n\n await reject(request.studentId, request.id);\n };\n\n return [studentRestores, approve, reject];\n}\n"],"names":["TeacherEntry","jsx","TokenLogin","user","claims","TeacherInner","schoolId","db","useDb","website","useWebsite","contests","useCollection","contestConverter","venues","venueConverter","variants","variantConverter","logout","useCallback","signOut","getAuth","TeacherProvider","args","startContestWindow","stopContestWindow","finalizeVenue","FirebaseStatement","contestId","variantIds","getPdfStatements","useAnnouncements","useStudents","useStudentRestores","storage","getStorage","files","id","getBytes","ref","announcements","announcementConverter","venueId","studentConverter","venue","useDocument","studentRestores","studentRestoreConvert","reject","studentId","excludeRestoreId","batch","writeBatch","studentRestore","doc","request","serverTimestamp"],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,18 @@
1
1
  {
2
2
  "name": "@olinfo/quizms-firebase",
3
- "version": "4.1.3",
3
+ "version": "5.0.1",
4
4
  "type": "module",
5
5
  "author": "Alessandro Bortolin <bortolin.alessandro@outlook.it>",
6
6
  "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/olimpiadi-informatica/quizms.git",
10
+ "directory": "packages/quizms-firebase"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/olimpiadi-informatica/quizms/issues"
14
+ },
15
+ "homepage": "https://quizms.olinfo.it",
7
16
  "bin": {
8
17
  "quizms-firebase": "bin/bin.js"
9
18
  },
@@ -26,43 +35,43 @@
26
35
  "dist"
27
36
  ],
28
37
  "dependencies": {
29
- "@inquirer/prompts": "^8.2.1",
30
- "@olinfo/react-components": "^0.12.1",
31
- "@trpc/client": "^11.10.0",
32
- "@trpc/server": "^11.10.0",
38
+ "@inquirer/prompts": "^8.5.2",
39
+ "@olinfo/quizms": "^5.0.5",
40
+ "@olinfo/react-components": "^0.12.3",
41
+ "@trpc/client": "^11.18.0",
42
+ "@trpc/server": "^11.18.0",
33
43
  "@types/cli-progress": "^3.11.6",
34
44
  "@types/express": "^5.0.6",
35
- "@types/express-serve-static-core": "^5.1.1",
45
+ "@types/express-serve-static-core": "^5.1.2",
36
46
  "@types/lodash-es": "^4.17.12",
37
- "@types/picomatch": "^4.0.2",
38
- "ag-grid-community": "^35.1.0",
47
+ "@types/picomatch": "^4.0.3",
48
+ "ag-grid-community": "^35.3.1",
39
49
  "cli-progress": "^3.12.0",
40
50
  "clsx": "^2.1.1",
41
51
  "commander": "^14.0.3",
42
- "date-fns": "^4.1.0",
52
+ "date-fns": "^4.4.0",
43
53
  "express": "^5.2.1",
44
- "fastify": "^5.7.4",
45
- "firebase": "^12.9.0",
46
- "firebase-admin": "^13.6.1",
47
- "firebase-functions": "^7.0.5",
48
- "lodash-es": "^4.17.23",
54
+ "fastify": "^5.12.1",
55
+ "firebase": "^12.17.0",
56
+ "firebase-admin": "^13.10.0",
57
+ "firebase-functions": "^7.3.2",
58
+ "lodash-es": "^4.18.1",
49
59
  "lucide-react": "^0.562.0",
50
- "picomatch": "^4.0.3",
60
+ "picomatch": "^4.0.5",
51
61
  "react-error-boundary": "~6.0.3",
52
62
  "react-markdown": "^10.1.0",
53
63
  "rollup-plugin-node-externals": "^8.1.2",
54
64
  "rollup-preserve-directives": "^1.1.3",
55
65
  "superjson": "^2.2.6",
56
- "swr": "^2.4.0",
57
- "tinyglobby": "^0.2.15",
58
- "vite": "^7.3.1",
66
+ "swr": "^2.4.2",
67
+ "tinyglobby": "^0.2.17",
68
+ "vite": "^7.3.6",
59
69
  "vite-plugin-dts": "^4.5.4",
60
- "wouter": "^3.9.0",
61
- "zod": "^4.3.6",
62
- "@olinfo/quizms": "^4.1.3"
70
+ "wouter": "^3.10.0",
71
+ "zod": "^4.4.3"
63
72
  },
64
73
  "peerDependencies": {
65
- "react": "~19.1.4"
74
+ "react": "^19.2.4"
66
75
  },
67
76
  "postcss": {
68
77
  "plugins": {
@@ -70,11 +79,11 @@
70
79
  }
71
80
  },
72
81
  "devDependencies": {
73
- "@biomejs/biome": "^2.4.2",
74
- "@google-cloud/storage": "^7.19.0",
75
- "@types/react": "^19.2.14",
82
+ "@biomejs/biome": "^2.5.6",
83
+ "@google-cloud/storage": "^7.21.0",
84
+ "@types/react": "^19.2.18",
76
85
  "@types/wicg-file-system-access": "^2023.10.7",
77
- "typescript": "5.8.2",
86
+ "typescript": "5.9.3",
78
87
  "vite-tsconfig-paths": "^6.1.1"
79
88
  },
80
89
  "scripts": {