Skip to content
This repository has been archived by the owner on Aug 11, 2023. It is now read-only.

feat: Group and Admin Page Creation #43

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
56 changes: 25 additions & 31 deletions composables/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<User | null>()
const institution = ref<Institution | null>()

const authenticated = computed(() => !!email.value)
const authenticated = computed(() => !!user.value)

/**
* init populates the store with the current user data, and does nothing
Expand Down Expand Up @@ -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,

Expand Down
168 changes: 167 additions & 1 deletion composables/group.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,173 @@
import { acceptHMRUpdate, defineStore } from 'pinia'

export const useGroupStore = defineStore('group', () => {
return {}
const { $api } = useNuxtApp()

const groups = ref<Group[]>([])
const invites = ref<Record<string, GroupInviteLink[]>>({})

/**
* 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<Group[]>('/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<Group>('/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<Group>(`/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<Group>(`/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<GroupInviteLink[]>(`/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<GroupInviteLink>(`/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<GroupInviteLink>(`/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)
Expand Down
2 changes: 1 addition & 1 deletion layouts/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ useAsyncData(async () => {
<header>
<div mx-auto container>
<div flex items-center justify-between px5 py8>
<NuxtLink to="/">
<NuxtLink :to="`/@${auth.institution?.shortName}/dashboard`">
<CommonAppLogo h-8 />
</NuxtLink>

Expand Down
47 changes: 47 additions & 0 deletions pages/@[[institution]]/dashboard/index.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script setup lang="ts">
import Breadcrumb from 'primevue/breadcrumb'

definePageMeta({
layout: 'app',
middleware: 'auth',
})

interface MenuContent {
options: string
icon: string
}

const menu = [
{
options: 'Groups',
icon: 'i-fluent-emoji-graduation-cap',
},
{
options: 'Rewards',
icon: 'i-fluent-emoji-money-bag',
},
{
options: 'Visit Pet',
icon: 'i-fluent-emoji-paw-prints',
},
{
options: 'Calander',
icon: 'i-fluent-emoji-calendar',
},
] satisfies MenuContent[]
</script>

<template>
<div class="card justify-content-center flex flex-row">
<Breadcrumb :model="menu">
<template #item="{ item }">
<a>
<div flex>
<div :class="item.icon" />
{{ item.options }}
</div>
</a>
</template>
</Breadcrumb>
</div>
</template>
74 changes: 74 additions & 0 deletions pages/@[[institution]]/manage/groups.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<script setup lang="ts">
import Menu from 'primevue/menu'
import type { MenuItem } from 'primevue/menuitem'
import Toast from 'primevue/toast'
import Button from 'primevue/button'

definePageMeta({
layout: 'app',
middleware: 'god-mode',
})

const group = useGroupStore()
const headers = useRequestHeaders(['cookie'])
useAsyncData(async () => {
await group.init(headers.cookie)
return true
})

const groupsMenu = computed(() => {
const i: MenuItem[] = group.groups.map(grp => ({
label: `${grp.name} (${grp.shortName})`,
to: `${grp.shortName}`,
}))
i.push({ separator: true })
return i
})

function toCreate() {
navigateTo('new')
}
</script>

<template>
<div>
<Toast />

<div flex flex-col gap-8 lg:flex-row>
<div>
<h2 text-2xl font-semibold>
Groups
</h2>

<Menu :model="groupsMenu">
<template #end>
<Button text w-full @click="toCreate">
New
</Button>
</template>
</Menu>
</div>

<div flex-1>
<NuxtPage />
</div>
</div>
</div>
</template>

<style scoped>
:deep(a.router-link-active) {
background-color: var(--highlight-bg);
color: var(--highlight-text-color);
--at-apply: font-bold;
}

:deep(a.router-link-active:hover) {
background-color: var(--highlight-bg);
color: var(--highlight-text-color);
}

:deep(a.router-link-active>.menu-icon i) {
color: var(--highlight-text-color);
}
</style>
Loading