diff --git a/composables/auth.ts b/composables/auth.ts index 43ef854..44ab508 100644 --- a/composables/auth.ts +++ b/composables/auth.ts @@ -3,15 +3,10 @@ import { acceptHMRUpdate, defineStore } from 'pinia' export const useAuthStore = defineStore('auth', () => { const { $api } = useNuxtApp() - const firstName = ref('') - const lastName = ref('') - const email = ref('') - const points = ref(0) - const pointsAwardedCount = ref(0) - const pointsAwardedResetTime = ref(new Date(Date.now())) - const godMode = ref(false) + const user = ref() + const institution = ref() - const authenticated = computed(() => !!email.value) + const authenticated = computed(() => !!user.value) /** * init populates the store with the current user data, and does nothing @@ -95,35 +90,34 @@ export const useAuthStore = defineStore('auth', () => { * @param data data to populate the store with */ function populate(data: User) { - firstName.value = data.firstName - lastName.value = data.lastName - email.value = data.email + user.value = { + ...data, - if (data.godMode) - godMode.value = data.godMode + } + institution.value = data.institution - if (data.points) - points.value = data.points + // firstName.value = data.firstName + // lastName.value = data.lastName + // email.value = data.email - if (data.pointsAwardedCount) - pointsAwardedCount.value = data.pointsAwardedCount + // if (data.godMode) + // godMode.value = data.godMode - if (data.pointsAwardedResetTime) { - const d = new Date(data.pointsAwardedResetTime) - if (d.getFullYear() !== 1) { // Zero value in Go time.TIme - pointsAwardedResetTime.value = d - } - } - } + // if (data.points) + // points.value = data.points + // if (data.pointsAwardedCount) + // pointsAwardedCount.value = data.pointsAwardedCount + + // if (data.pointsAwardedResetTime) { + // const d = new Date(data.pointsAwardedResetTime) + // if (d.getFullYear() !== 1) { // Zero value in Go time.TIme + // pointsAwardedResetTime.value = d + // } + // } + } return { - firstName, - lastName, - email, - points, - pointsAwardedCount, - pointsAwardedResetTime, - godMode, + ...user.value, authenticated, diff --git a/composables/group.ts b/composables/group.ts index 540be26..82dddf0 100644 --- a/composables/group.ts +++ b/composables/group.ts @@ -1,7 +1,173 @@ import { acceptHMRUpdate, defineStore } from 'pinia' export const useGroupStore = defineStore('group', () => { - return {} + const { $api } = useNuxtApp() + + const groups = ref([]) + const invites = ref>({}) + + /** + * init populates the store with groups available in iNProve and does nothing + * if the user is unauthenticated. + * + * init should be called in any root level layout (example: layouts/app.vue) + */ + async function init(cookie?: string) { + try { + const res = await $api('/groups', { + headers: cookie + ? { + cookie, + } + : undefined, + }) + groups.value = res + } + catch (e) { + console.error('[composables/group.ts] failed to init store', e) + return parseError(e) + } + } + + /** + * create a new group + * + * @param name name of the group + * @param shortName short name of the group, must be alphanumeric (incl. dashes) + * @param description description of group + * @returns ValidationError | undefined + */ + async function create(name: string, shortName: string, description: string) { + try { + const res = await $api('/groups', { + method: 'POST', + body: { + name, + shortName, + description, + }, + }) + groups.value.push(res) + } + catch (e) { + console.error('[composables/groups.ts] failed to create group', e) + return parseError(e) + } + } + + /** + * update a group + * + * @param prevName name of the previous group to display current information presented on the site + * @param prevshortName short name of the previous group used to update all information + * @param name name of the group + * @param shortName short name of the group, must be alphanumeric (incl. dashes) + * @param description description of group + * @returns ValidationError | undefined + */ + async function update(prevName: string, prevshortName: string, name: string, shortName: string, description: string) { + try { + const res = await $api(`/groups/${prevshortName}`, { + method: 'PUT', + body: { + name, + shortName, + description, + }, + }) + const idx = groups.value.findIndex(i => i.shortName === prevshortName) + if (idx === -1) + throw new Error('invariant') + + groups.value[idx] = res + } + catch (e) { + console.error('[composables/groups.ts] failed to update group', e) + return parseError(e) + } + } + + /** + * delete a group + * please be careful, this also deletes all resources belonging to the group + * + * @param shortName short name of the group to delete + * @returns ValidationError | undefined + */ + async function del(shortName: string) { + try { + await $api(`/groups/${shortName}`, { + method: 'DELETE', + }) + + groups.value = groups.value.filter(groups => groups.shortName !== shortName) + } + catch (e) { + console.error('[composables/groups.ts] failed to delete group', e) + return parseError(e) + } + } + + /** + * loadInvites populates the invites value on demand for a given group + * + * @param shortName short name of the group + * @returns ValidationError | undefined + */ + async function loadInvites(shortName: string) { + try { + const res = await $api(`/groups/${shortName}/invites`) + invites.value[shortName] = res + } + catch (err) { + console.error('[composables/groups.ts] failed to load invites', err) + return parseError(err) + } + } + + async function createInvite(shortName: string, role: string) { + try { + const res = await $api(`/groups/${shortName}/invites`, { + method: 'POST', + body: { + role, + }, + }) + invites.value[shortName].push(res) + } + catch (e) { + console.error('[composables/groups.ts] failed to create invite', e) + return parseError(e) + } + } + + async function delInvite(shortName: string, code: string) { + try { + await $api(`/groups/${shortName}/invites/${code}`, { + method: 'DELETE', + }) + + invites.value[shortName] = invites.value[shortName].filter(v => v.code !== code) + } + catch (err) { + console.error('[composables/groups.ts] failed to create invite', err) + return parseError(err) + } + } + + return { + groups, + invites, + + init, + create, + update, + del, + + loadInvites, + createInvite, + delInvite, + } }) if (import.meta.hot) diff --git a/layouts/app.vue b/layouts/app.vue index da28633..644bc15 100644 --- a/layouts/app.vue +++ b/layouts/app.vue @@ -41,7 +41,7 @@ useAsyncData(async () => {
- + diff --git a/pages/@[[institution]]/dashboard/index.vue b/pages/@[[institution]]/dashboard/index.vue new file mode 100644 index 0000000..709211e --- /dev/null +++ b/pages/@[[institution]]/dashboard/index.vue @@ -0,0 +1,47 @@ + + + diff --git a/pages/dashboard.vue b/pages/@[[institution]]/manage/add-rewards.vue similarity index 100% rename from pages/dashboard.vue rename to pages/@[[institution]]/manage/add-rewards.vue diff --git a/pages/@[[institution]]/manage/groups.vue b/pages/@[[institution]]/manage/groups.vue new file mode 100644 index 0000000..d5de713 --- /dev/null +++ b/pages/@[[institution]]/manage/groups.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/pages/@[[institution]]/manage/groups/[shortName].vue b/pages/@[[institution]]/manage/groups/[shortName].vue new file mode 100644 index 0000000..dde439a --- /dev/null +++ b/pages/@[[institution]]/manage/groups/[shortName].vue @@ -0,0 +1,267 @@ + + + diff --git a/pages/@[[institution]]/manage/groups/new.vue b/pages/@[[institution]]/manage/groups/new.vue new file mode 100644 index 0000000..0510ee1 --- /dev/null +++ b/pages/@[[institution]]/manage/groups/new.vue @@ -0,0 +1,100 @@ + + + diff --git a/pages/@[[institution]]/manage/index.vue b/pages/@[[institution]]/manage/index.vue new file mode 100644 index 0000000..3320880 --- /dev/null +++ b/pages/@[[institution]]/manage/index.vue @@ -0,0 +1,68 @@ + + + diff --git a/pages/@[[institution]]/manage/rewards.vue b/pages/@[[institution]]/manage/rewards.vue new file mode 100644 index 0000000..6ea2b49 --- /dev/null +++ b/pages/@[[institution]]/manage/rewards.vue @@ -0,0 +1,12 @@ + + + diff --git a/pages/login.vue b/pages/login.vue index e490bcb..52ab1fa 100644 --- a/pages/login.vue +++ b/pages/login.vue @@ -28,7 +28,7 @@ async function login() { formData.isLoading = true const err = await auth.login(formData.email, formData.password) if (!err) - return navigateTo('/dashboard') + return navigateTo(`/@${auth.institution?.shortName}/dashboard`) formData.error = err formData.isLoading = false } diff --git a/pages/s/[code].vue b/pages/s/[code].vue index 374328c..d2385f6 100644 --- a/pages/s/[code].vue +++ b/pages/s/[code].vue @@ -45,7 +45,7 @@ async function register() { formData.firstName, formData.lastName, formData.email, formData.password, ) if (!err) - return navigateTo('/dashboard') + return navigateTo(`/@${data.value?.institution.shortName}/dashboard`) formData.error = err formData.isLoading = false } diff --git a/utils/api.ts b/utils/api.ts index 03621eb..79059bb 100644 --- a/utils/api.ts +++ b/utils/api.ts @@ -6,6 +6,7 @@ interface Institution { } interface User { + institution: Institution | null | undefined firstName: string lastName: string email: string @@ -23,3 +24,19 @@ interface InstitutionInviteLink { } type InstitutionRoles = 'admin' | 'educator' | 'member' + +interface Group { + id: number + name: string + shortName: string + description: string +} + +interface GroupInviteLink { + id: string + code: string + role: GroupRoles + group: Group +} + +type GroupRoles = 'owner' | 'educator' | 'member'