diff --git a/app.vue b/app.vue index 903d733..3e03718 100644 --- a/app.vue +++ b/app.vue @@ -4,6 +4,8 @@ + + diff --git a/components/admin/common/left-panel.vue b/components/admin/common/left-panel.vue deleted file mode 100644 index 7bc973c..0000000 --- a/components/admin/common/left-panel.vue +++ /dev/null @@ -1,42 +0,0 @@ - - - diff --git a/components/admin/event/create-popup.vue b/components/admin/event/create-popup.vue new file mode 100644 index 0000000..2956bf8 --- /dev/null +++ b/components/admin/event/create-popup.vue @@ -0,0 +1,103 @@ + + + diff --git a/components/admin/event/page.vue b/components/admin/event/page.vue deleted file mode 100644 index d9afcd9..0000000 --- a/components/admin/event/page.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/components/admin/event/update-form.vue b/components/admin/event/update-form.vue new file mode 100644 index 0000000..9a27a51 --- /dev/null +++ b/components/admin/event/update-form.vue @@ -0,0 +1,91 @@ + + + diff --git a/components/admin/forbidden.vue b/components/admin/forbidden.vue new file mode 100644 index 0000000..0bf7456 --- /dev/null +++ b/components/admin/forbidden.vue @@ -0,0 +1,13 @@ + diff --git a/components/admin/home/events-table.vue b/components/admin/home/events-table.vue deleted file mode 100644 index 324fed8..0000000 --- a/components/admin/home/events-table.vue +++ /dev/null @@ -1,70 +0,0 @@ - - - diff --git a/components/admin/home/forbidden.vue b/components/admin/home/forbidden.vue deleted file mode 100644 index 8219fb5..0000000 --- a/components/admin/home/forbidden.vue +++ /dev/null @@ -1,21 +0,0 @@ - - - diff --git a/components/admin/home/page.vue b/components/admin/home/page.vue deleted file mode 100644 index f192faa..0000000 --- a/components/admin/home/page.vue +++ /dev/null @@ -1,36 +0,0 @@ - - - diff --git a/components/admin/login-screen.vue b/components/admin/login-screen.vue new file mode 100644 index 0000000..db4f2c9 --- /dev/null +++ b/components/admin/login-screen.vue @@ -0,0 +1,155 @@ + + + diff --git a/components/admin/members/page.vue b/components/admin/members/page.vue deleted file mode 100644 index 945a5c4..0000000 --- a/components/admin/members/page.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/components/admin/settings/page.vue b/components/admin/settings/page.vue deleted file mode 100644 index 945a5c4..0000000 --- a/components/admin/settings/page.vue +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/components/common/login-screen.vue b/components/app/login-screen.vue similarity index 92% rename from components/common/login-screen.vue rename to components/app/login-screen.vue index fe1b911..89ee88b 100644 --- a/components/common/login-screen.vue +++ b/components/app/login-screen.vue @@ -6,12 +6,6 @@ import { FirebaseError } from 'firebase/app' const opened = defineModel('opened') -const FIREBASE_ERROR_MESSAGES: Record = { - 'auth/user-disabled': 'Your account has been temporarily disabled. Please try again later.', - 'auth/weak-password': 'Your password is too weak. Please use a stronger password', - 'auth/wrong-password': 'Your password is incorrect. Please try again.', -} - const auth = useFirebaseAuth() const state = reactive({ diff --git a/components/app/services/event/page.vue b/components/app/services/event/page.vue index fb4faeb..8480c58 100644 --- a/components/app/services/event/page.vue +++ b/components/app/services/event/page.vue @@ -15,7 +15,7 @@ const state = ref({ scannerOpened: false, }) -const { $dayjs } = useNuxtApp() +const dayjs = useDayjs() const db = useDatabase() const { data: checkedInUsersList, pending: checkedInUsersListPending } = useDatabaseList<{ $value: number }>(dbRef(db, props.id)) @@ -45,7 +45,7 @@ function formattedDate(date: number) { if (formattedDateCache.has(date)) return formattedDateCache.get(date) - const data = $dayjs.unix(date).format('hh:mm A') + const data = dayjs.unix(date).format('hh:mm A') formattedDateCache.set(date, data) return data } @@ -54,7 +54,7 @@ async function toggle(admissionKey: string) { if (checkedInUsers.value[admissionKey]) await remove(dbRef(db, `${props.id}/${admissionKey}`)) else - await set(dbRef(db, `${props.id}/${admissionKey}`), $dayjs().unix()) + await set(dbRef(db, `${props.id}/${admissionKey}`), dayjs().unix()) } async function onScan(admissionKey: string) { @@ -70,7 +70,7 @@ async function onScan(admissionKey: string) { return } - await set(dbRef(db, `${props.id}/${admissionKey}`), $dayjs().unix()) + await set(dbRef(db, `${props.id}/${admissionKey}`), dayjs().unix()) f7.toast.show({ text: `Checked in ${attendee.name}`, @@ -83,7 +83,7 @@ async function onScan(admissionKey: string) { function searchAll(query: string, items: UnwrapRef) { const found = [] for (const idx in items) { - if (items[idx].name.toLowerCase().includes(query.toLowerCase()) || query.trim() === '') + if (items[idx].name?.toLowerCase().includes(query.toLowerCase()) || query.trim() === '') found.push(idx) } return found diff --git a/plugins/dayjs.ts b/composables/dayjs.ts similarity index 55% rename from plugins/dayjs.ts rename to composables/dayjs.ts index 127a56c..64f9c39 100644 --- a/plugins/dayjs.ts +++ b/composables/dayjs.ts @@ -1,12 +1,8 @@ import dayjs from 'dayjs' import RelativeTime from 'dayjs/plugin/relativeTime' -export default defineNuxtPlugin(() => { +export const useDayjs = createSharedComposable(() => { dayjs.extend(RelativeTime) - return { - provide: { - dayjs, - }, - } + return dayjs }) diff --git a/composables/event.ts b/composables/event.ts index 05b3879..bed1ed4 100644 --- a/composables/event.ts +++ b/composables/event.ts @@ -1,5 +1,5 @@ -import { useQuery, useQueryClient } from '@tanstack/vue-query' -import type { EventWithAttendees } from '~/shared/types' +import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query' +import type { Event, EventWithAttendees } from '~/shared/types' const queryKeyFactory = { events: ['events'], @@ -27,3 +27,54 @@ export function useEvent(id: MaybeRef) { }, }) } + +export function useCreateEventMutation() { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (body: Omit) => $api('/api/event', { method: 'post', body }), + onSuccess() { + queryClient.invalidateQueries({ + queryKey: queryKeyFactory.events, + }) + }, + }) +} + +export function useUpdateEventMutation(id: string) { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (body: Omit) => $api(`/api/event/${id}`, { method: 'put', body }), + onSuccess() { + queryClient.invalidateQueries({ + queryKey: queryKeyFactory.events, + }) + }, + }) +} + +export function useAddEventUsersMutation(id: string) { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: (body: { userId: string }[]) => $api(`/api/event/${id}/attendees`, { method: 'post', body }), + onSuccess() { + queryClient.invalidateQueries({ + queryKey: queryKeyFactory.event(id), + }) + }, + }) +} + +export function useDeleteEventMutation(id: string) { + const queryClient = useQueryClient() + return useMutation({ + mutationFn: () => $api(`/api/event/${id}`, { method: 'delete' }), + onSuccess() { + queryClient.refetchQueries({ + queryKey: queryKeyFactory.events, + }) + queryClient.removeQueries({ + queryKey: queryKeyFactory.event(id), + }) + }, + }) +} diff --git a/composables/fetch.ts b/composables/fetch.ts index 0f71220..b9db360 100644 --- a/composables/fetch.ts +++ b/composables/fetch.ts @@ -2,9 +2,6 @@ import { defu } from 'defu' import { getAuth } from 'firebase/auth' import type { UseFetchOptions } from '#app' -/** - * @deprecated Use contracts from @ts-rest/core instead - */ export const $api = $fetch.create({ async onRequest(context) { const token = await getAuth().currentUser?.getIdToken() @@ -18,9 +15,6 @@ export const $api = $fetch.create({ type ApiFetchRequest = Ref | T | (() => T) -/** - * @deprecated Use contracts from @ts-rest/core instead - */ export function useApiFetch(request: ApiFetchRequest, options: UseFetchOptions = {}) { const defaults: UseFetchOptions = { $fetch: $api, @@ -29,9 +23,6 @@ export function useApiFetch(request: ApiFetc return useFetch(request, params) } -/** - * @deprecated Use contracts from @ts-rest/core instead - */ export function useLazyApiFetch(request: ApiFetchRequest, options: Omit, 'lazy'> = {}) { const defaults: UseFetchOptions = { $fetch: $api, diff --git a/composables/login-screen.ts b/composables/login-screen.ts new file mode 100644 index 0000000..770f738 --- /dev/null +++ b/composables/login-screen.ts @@ -0,0 +1,5 @@ +export const FIREBASE_ERROR_MESSAGES: Record = { + 'auth/user-disabled': 'Your account has been temporarily disabled. Please try again later.', + 'auth/weak-password': 'Your password is too weak. Please use a stronger password', + 'auth/wrong-password': 'Your password is incorrect. Please try again.', +} diff --git a/composables/news.ts b/composables/news.ts index 82777ae..95d583e 100644 --- a/composables/news.ts +++ b/composables/news.ts @@ -1,5 +1,5 @@ import { useQuery } from '@tanstack/vue-query' -import type { NewsArticle } from '~/shared/types' +import type { Article } from '~/shared/types' const queryKeyFactory = { newsArticles: ['newsArticles'], @@ -9,7 +9,7 @@ export function useNewsArticles() { const firebaseCurrentUser = useCurrentUser() return useQuery({ queryKey: queryKeyFactory.newsArticles, - queryFn: () => $api('/api/news'), + queryFn: () => $api('/api/news'), enabled: computed(() => !!firebaseCurrentUser.value), // Only run when user exists }) } diff --git a/composables/sstaars.ts b/composables/sstaars.ts index c536f99..6b25d66 100644 --- a/composables/sstaars.ts +++ b/composables/sstaars.ts @@ -1,3 +1,4 @@ +import { useQuery } from '@tanstack/vue-query' import { useStorage } from '@vueuse/core' const queryKeyFactory = { @@ -9,13 +10,8 @@ export const useSstaarsStore = createGlobalState(() => { }) export function useAdmission(key: string) { - const app = useNuxtApp() - return app.$apiQuery.getAdmission.useQuery( - queryKeyFactory.admission(key), - () => ({ - params: { - key, - }, - }), - ) + return useQuery({ + queryKey: queryKeyFactory.admission(key), + queryFn: () => $api(`/api/admission/${key}`), + }) } diff --git a/composables/user.ts b/composables/user.ts index 6bfd870..b99996f 100644 --- a/composables/user.ts +++ b/composables/user.ts @@ -2,18 +2,26 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query' import type { User } from '~/shared/types' const queryKeyFactory = { - user: ['user'], + currentUser: ['current-user'], + users: ['users'], } export function useUser() { const firebaseCurrentUser = useCurrentUser() return useQuery({ - queryKey: queryKeyFactory.user, + queryKey: queryKeyFactory.currentUser, queryFn: () => $api(`/api/user/${firebaseCurrentUser.value?.uid}`), enabled: computed(() => !!firebaseCurrentUser.value), // Only run when user exists }) } +export function useUsers() { + return useQuery({ + queryKey: queryKeyFactory.users, + queryFn: () => $api(`/api/user`), + }) +} + export function useUserSignOutMutation() { const auth = useFirebaseAuth() const queryClient = useQueryClient() @@ -24,3 +32,9 @@ export function useUserSignOutMutation() { }, }) } + +export function useBulkCreateUserMutation() { + return useMutation({ + mutationFn: (body: Partial[]) => $api('/api/user/bulk', { method: 'post', body }), + }) +} diff --git a/eslint.config.js b/eslint.config.js index 64f05c1..95dff43 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -8,6 +8,7 @@ export default antfu({ yaml: true, }, { rules: { + 'vue/component-name-in-template-casing': ['error', 'PascalCase'], // https://github.com/antfu/eslint-config/pull/214 'node/prefer-global/process': [ 'off', diff --git a/middleware/reload-on-scope-change.global.ts b/middleware/reload-on-scope-change.global.ts new file mode 100644 index 0000000..6960303 --- /dev/null +++ b/middleware/reload-on-scope-change.global.ts @@ -0,0 +1,9 @@ +// https://github.com/nuxt/nuxt/issues/2561#issuecomment-358596224 + +export default defineNuxtRouteMiddleware((to, from) => { + if (process.server) + return + + if (to.fullPath.split('/')[1] !== from.fullPath.split('/')[1]) + window.location.href = to.fullPath +}) diff --git a/package.json b/package.json index d4f9646..4b1e208 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "dev": "nuxt dev", "generate": "nuxt generate", "preview": "nuxt preview", - "postinstall": "nuxt prepare", + "postinstall": "simple-git-hooks && nuxt prepare", "db:generate": "drizzle-kit generate:sqlite", "db:push": "drizzle-kit push:sqlite", "db:drop": "drizzle-kit drop", @@ -19,44 +19,47 @@ }, "devDependencies": { "@antfu/eslint-config": "latest", - "@cloudflare/workers-types": "^4.20231121.0", + "@cloudflare/workers-types": "^4.20231218.0", "@growthbook/growthbook": "^0.31.0", "@libsql/client": "^0.3.6", "@nuxt/devtools": "latest", - "@nuxt/ui": "^2.11.0", + "@nuxt/ui": "^2.11.1", "@paralleldrive/cuid2": "^2.2.2", "@simplewebauthn/browser": "^8.3.4", - "@simplewebauthn/server": "^8.3.5", - "@tanstack/query-persist-client-core": "^5.8.7", - "@tanstack/query-sync-storage-persister": "^5.8.7", - "@tanstack/vue-query": "^5.8.7", - "@ts-rest/core": "^3.30.5", - "@ts-rest/vue-query": "^3.30.5", - "@vite-pwa/assets-generator": "^0.0.11", - "@vite-pwa/nuxt": "^0.3.2", - "@vueuse/core": "^10.6.1", - "@vueuse/integrations": "^10.6.1", - "@vueuse/nuxt": "^10.6.1", + "@simplewebauthn/server": "^8.3.6", + "@tanstack/query-core": "^5.17.4", + "@tanstack/query-persist-client-core": "^5.17.4", + "@tanstack/vue-query": "^5.17.4", + "@types/papaparse": "^5.3.14", + "@unocss/reset": "^0.58.3", + "@vite-pwa/assets-generator": "^0.0.10", + "@vite-pwa/nuxt": "^0.1.2", + "@vueuse/core": "^10.7.1", + "@vueuse/integrations": "^10.7.1", + "@vueuse/nuxt": "^10.7.1", + "better-sqlite3": "^9.2.2", "dayjs": "^1.11.10", "dotenv": "^16.3.1", - "drizzle-kit": "^0.20.6", - "drizzle-orm": "^0.29.0", - "eslint": "^8.54.0", - "firebase": "^10.6.0", + "drizzle-kit": "^0.19.13", + "drizzle-orm": "^0.28.6", + "eslint": "^8.56.0", + "firebase": "^10.7.1", "framework7": "^8.3.0", "framework7-icons": "^5.0.5", "framework7-vue": "^8.3.0", "html5-qrcode": "^2.3.8", - "jose": "^5.1.1", - "lint-staged": "^15.1.0", + "idb-keyval": "^6.2.1", + "jose": "^5.2.0", + "lint-staged": "^15.2.0", "material-icons": "^1.13.12", - "nuxt": "^3.8.2", + "nuxt": "^3.8.0", "nuxt-vuefire": "^0.4.1", + "papaparse": "^5.4.1", "qrcode": "^1.5.3", - "simple-git": "^3.21.0", + "simple-git": "^3.22.0", "simple-git-hooks": "^2.9.0", "vue-tsc": "^1.8.22", - "vuefire": "^3.1.18", + "vuefire": "^3.1.17", "web-auth-library": "^1.0.3", "zod": "^3.22.4" }, diff --git a/pages/admin.vue b/pages/admin.vue new file mode 100644 index 0000000..4be60df --- /dev/null +++ b/pages/admin.vue @@ -0,0 +1,74 @@ + + + diff --git a/pages/admin/[...slug].vue b/pages/admin/[...slug].vue deleted file mode 100644 index cf73394..0000000 --- a/pages/admin/[...slug].vue +++ /dev/null @@ -1,87 +0,0 @@ - - - diff --git a/pages/admin/events/[id].vue b/pages/admin/events/[id].vue new file mode 100644 index 0000000..d0c6186 --- /dev/null +++ b/pages/admin/events/[id].vue @@ -0,0 +1,207 @@ + + + diff --git a/pages/admin/events/index.vue b/pages/admin/events/index.vue new file mode 100644 index 0000000..07619e3 --- /dev/null +++ b/pages/admin/events/index.vue @@ -0,0 +1,70 @@ + + + diff --git a/pages/admin/index.vue b/pages/admin/index.vue new file mode 100644 index 0000000..f08c920 --- /dev/null +++ b/pages/admin/index.vue @@ -0,0 +1,8 @@ + + + diff --git a/pages/admin/members/index.vue b/pages/admin/members/index.vue new file mode 100644 index 0000000..5f6ecab --- /dev/null +++ b/pages/admin/members/index.vue @@ -0,0 +1,13 @@ + + + diff --git a/pages/app/[...slug].vue b/pages/app/[...slug].vue index 2a80481..e639587 100644 --- a/pages/app/[...slug].vue +++ b/pages/app/[...slug].vue @@ -88,7 +88,7 @@ watch([authLoaded, auth], (values) => { ios-swipe-back preload-previous-page > - + -import 'tailwindcss/src/css/preflight.css' +import '@unocss/reset/tailwind-compat.css'