Skip to content
Open
6 changes: 3 additions & 3 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@
"axios": "^1.7.5",
"core-js": "^3.37.1",
"roboto-fontface": "*",
"vue": "^3.4.31",
"vuetify": "^3.6.11"
"vue": "^3.4.31"
},
"devDependencies": {
"@babel/types": "^7.24.7",
Expand All @@ -39,6 +38,7 @@
"vite-plugin-vue-layouts": "^0.11.0",
"vite-plugin-vuetify": "^2.0.3",
"vue-router": "^4.4.0",
"vue-tsc": "^2.0.26"
"vue-tsc": "^2.0.26",
"vuetify": "^3.6.13"
}
}
6 changes: 3 additions & 3 deletions client/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion client/src/api/axios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import axiosClient from 'axios'

const accessToken = localStorage.getItem('TESTTRACKER_ACCESS_TOKEN')
export const axios = axiosClient.create({
baseURL: window.env.SERVER_DOMAIN_NAME_API,
baseURL: 'https://server.gent02.dev.grid.tf',
headers: {
Authorization: `Bearer ${accessToken}`,
},
Expand Down
31 changes: 31 additions & 0 deletions client/src/api/members.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { axios } from './axios'
import { inviteNewMember } from '../types/types'

async function search (searchInput:string) {
try {
return await axios.get(`/members/search/${searchInput}`)
} catch (error) {
console.error(error)
throw error
}
}

async function addMember (inviteNewMember:inviteNewMember) {
try {
await axios.post(`/dashboard/members/`, inviteNewMember)
} catch (error) {
console.error(error)
throw error
}
}
async function getMembers () {
try {
localStorage.setItem('TESTTRACKER_ACCESS_TOKEN', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI0OTM2NTM5LCJpYXQiOjE3MjQ5MjcyMzksImp0aSI6IjgwMDY5OTAzZDNjMzRjMzVhMzdjMWZmMjc3ZWMxMWQ0IiwidXNlcl9pZCI6MSwiZW1haWwiOiJuYWJpbGFAZ21haWwuY29tIn0.NWT-fAt4vCyefqDeCRRLqnG2q2sDBQJq7jrRRSmgcK4')
return await axios.get(`/api/members/all/`)
} catch (error) {
console.error(error)
throw error
}
}

export default { search, getMembers, addMember }
2 changes: 0 additions & 2 deletions client/src/components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AppFooter: typeof import('./components/AppFooter.vue')['default']
HelloWorld: typeof import('./components/HelloWorld.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
Test: typeof import('./components/test.vue')['default']
Expand Down
4 changes: 3 additions & 1 deletion client/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,11 @@ import App from './App.vue'

// Composables
import { createApp } from 'vue'
import router from './router'
import vuetify from './plugins/vuetify'

const app = createApp(App)

registerPlugins(app)

app.mount('#app')
app.use(router).use(vuetify).mount('#app')
217 changes: 217 additions & 0 deletions client/src/pages/AddMembers.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<template>
<div style="margin-left: 7cm; margin-right: 7cm;">
<v-container>
<v-row>
Comment on lines +2 to +4
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to load the navbar here

<p class="mt-2 text-h4 text-blue-darken-4" variant="h5">All members</p>
<v-spacer />
<v-btn
color="primary"
@click="addMemberDialog=true"
>
Invite member
</v-btn>
</v-row>

<v-dialog v-model="addMemberDialog" max-width="600px">
<v-card>
<v-form ref="form">
<v-card-title>
<br>
<v-divider />
</v-card-title>
<v-card-text>
<v-text-field
v-model="inviteMember.first_name"
density="compact"
placeholder="First Name"
prepend-inner-icon="mdi-account-outline"
:rules="nameRules"
variant="outlined"
/>
<v-text-field
v-model="inviteMember.last_name"
density="compact"
placeholder="Last Name"
prepend-inner-icon="mdi-account-outline"
:rules="nameRules"
variant="outlined"
/>
<v-text-field
v-model="inviteMember.email"
density="compact"
placeholder="Email"
prepend-inner-icon="mdi-email-outline"
:rules="emailRules"
variant="outlined"
/>
<p>Permission</p>
<v-select
v-model="selectedPermission"
:items="['Full access', 'Admin access']"
label="Permission"
@change="updatePermission"
/>
<v-divider />
<v-card-actions>
<v-spacer />
<v-btn color="info" @click="addMemberDialog = false">Close</v-btn>
<v-btn color="success" :disabled="loadingAdd||!isFormValid" :loading="loadingAdd" @click="AddMember">ADD+</v-btn>
</v-card-actions>
</v-card-text>
</v-form>
</v-card>
</v-dialog>

<v-row>
<p class="mt-2 text-h6 text-grey-darken-2 mb-8" variant="h6">There are {{ MembersCount }} members registered</p>
</v-row>
<br>

<v-row>
<h4 class="mb-2">Search Members</h4>
</v-row>

<v-row>
<v-text-field v-model="searchText" label="Search" variant="outlined" />
<v-btn color="primary" style="height: 56px; width:80px; padding: 0; margin: 0;" variant="outlined" @click="SearchMember">Search</v-btn>
</v-row>

<v-row>
<v-col
v-for="member in members"
:key="member.email"
cols="12"
md="4"
sm="6"
>
<v-card class="ma-2" outlined>
<!-- implement viewMember and the display -->
<v-card-subtitle>{{ member.full_name }}</v-card-subtitle>
<v-card-actions>
<v-btn @click="viewMember(member.id)">View Details</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-container>
</div>
</template>

<script lang="ts">
import { ref } from 'vue'
import api from '@/api/members'
import { emailRules, nameRules } from '@/utilities/validators'
import { inviteNewMember } from '@/types/types'

export default {
setup () {
// const props = defineProps({
// count: {
// type: Number,
// required: true,
// },
// members: {
// type: Array,
// required: true,
// },
// })
const selectedPermission = ref < string >('Full access')
const inviteMember = ref < inviteNewMember >({
first_name: '',
last_name: '',
email: '',
permission: 'full_access',
})

const addMemberDialog = ref(false)

const members = ref([])
const MembersCount = computed(() => members.value.length)

const searchText = ref('')

const loadingAdd = ref(false)

async function AddMember () {
try {
await api.addMember(inviteMember.value)
// description from the backend
// notifier.notify({
// title: 'success',
// description: 'member added successfully',
// showProgressBar: true,
// timeout: 7_000,
// type: 'success',
// })
} catch (error) {
console.error(error)
// description from the backend
// notifier.notify({
// title: 'Fail',
// description: 'cannot add member',
// showProgressBar: true,
// timeout: 7_000,
// type: 'error',
// })
} finally {
loadingAdd.value = false
}
}
const form = ref(null)

const isFormValid = computed(() => form.value ? (form.value as any).isValid : false)

const SearchMember = async () => {
try {
const response = await api.search(searchText.value)
members.value = response.data
} catch (error) {
console.error(error)
// description from the backend
// notifier.notify({
// title: 'Fail',
// description: 'no member found',
// showProgressBar: true,
// timeout: 7_000,
// type: 'error',
// })
}
}

async function GetMembers () {
try {
const response = await api.getMembers()
members.value = response.data
} catch (error) {
console.error(error)
}
}

const updatePermission = () => {
inviteMember.value.permission = selectedPermission.value === 'Full access' ? 'full_access' : 'admin_access'
}

onMounted(() => {
GetMembers()
})

return {
SearchMember,
searchText,
inviteMember,
addMemberDialog,
AddMember,
loadingAdd,
GetMembers,
emailRules,
nameRules,
isFormValid,
form,
selectedPermission,
updatePermission,
MembersCount,
members,
}
},
}
</script>
2 changes: 1 addition & 1 deletion client/src/plugins/vuetify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@ import { createVuetify } from 'vuetify'
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
export default createVuetify({
theme: {
defaultTheme: 'dark',
defaultTheme: 'light',
},
})
2 changes: 1 addition & 1 deletion client/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { createRouter, createWebHistory } from 'vue-router/auto'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{ path: '/', component: () => import('@/pages/DashboardView.vue') }
{ path: '/', component: () => import('@/pages/AddMembers.vue') },
],
})

Expand Down
1 change: 1 addition & 0 deletions client/src/typed-router.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ declare module 'vue-router/auto-routes' {
* Route name map generated by unplugin-vue-router
*/
export interface RouteNamedMap {
'/AddMembers': RouteRecordInfo<'/AddMembers', '/AddMembers', Record<never, never>, Record<never, never>>,
'/DashboardView': RouteRecordInfo<'/DashboardView', '/DashboardView', Record<never, never>, Record<never, never>>,
}
}
6 changes: 6 additions & 0 deletions client/src/types/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface inviteNewMember {
first_name: string;
last_name: string;
email: string;
permission: string;
}
28 changes: 28 additions & 0 deletions client/src/utilities/validators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
export const emailRules = [
(value: string) => {
if (value) return true
return 'You must enter an email.'
},
(value: string) => {
if (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value)) return true
return 'Email must be a valid email address.'
},
]
export const nameRules = [
(value: string) => {
if (value) return true
return 'You must enter a name.'
},
(value: string) => {
if (value?.length > 1) return true
return 'Name must be at least 1 character.'
},
(value: string) => {
if (value?.length < 49) return true
return 'Name must be at most 50 characters.'
},
(value: string) => {
if (/[^0-9]/.test(value)) return true
return 'Name can not contain digits.'
},
]
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dependencies": {
"vuetify": "3.7.0-beta.1"
}
}
Loading