Skip to content

Commit

Permalink
Contact management - frontend changes: new - contactProfileView, cont…
Browse files Browse the repository at this point in the history
…actSearchParams type, contactService, updated - HeaderMenu, en-CA, application enums, router, authzStore
  • Loading branch information
sanjaytkbabu committed Jan 9, 2025
1 parent f1ecaed commit 82849d4
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 1 deletion.
2 changes: 1 addition & 1 deletion app/src/validators/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ const schema = {
email: Joi.string().max(255).required(),
firstName: Joi.string().max(255).required(),
lastName: Joi.string().max(255).required(),
phoneNumber: Joi.number().max(255).required(),
phoneNumber: Joi.number().required(),
contactApplicantRelationship: Joi.string()
.required()
.valid(...PROJECT_RELATIONSHIP_LIST),
Expand Down
7 changes: 7 additions & 0 deletions frontend/src/components/common/HeaderMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ const { getIsAuthenticated, getProfile } = storeToRefs(useAuthNStore());
// State
const { t } = useI18n();
const items = ref([
{
label: t('headerMenu.contactProfile'),
icon: 'pi pi-user',
command: () => {
router.push({ name: RouteName.CONTACT_PROFILE });
}
},
{
label: t('headerMenu.logout'),
icon: 'pi pi-sign-out',
Expand Down
17 changes: 17 additions & 0 deletions frontend/src/locales/en-CA.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"name": "Permit Connect Services"
},
"headerMenu": {
"contactProfile": "Contact profile",
"logout": "Log out"
},
"housing": {
Expand Down Expand Up @@ -129,5 +130,21 @@
"projectLocationDescriptionCard": "Is there anything else you would like to tell us about this project's location? (optional)",
"provincialPermitsCard": "Have you applied for any provincial permits for this project?",
"investigatePermitsCard": "Select all provincially issued permits you think you might need (optional)"
},
"contact": {
"profile": {
"contactProfile": "Contact profile",
"edit": "Edit",
"fillOutProfile": "Please fill out your contact profile",
"firstName": "First name",
"getStarted": "Get started",
"lastName": "Last name",
"phone": "Phone",
"email": "Email",
"relationshipToProject": "Relationship to project",
"preferredContact": "Preferred contact method",
"save": "Save",
"cancel": "Cancel"
}
}
}
7 changes: 7 additions & 0 deletions frontend/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ const routes: Array<RouteRecordRaw> = [
name: RouteName.HOME,
component: () => import('@/views/HomeView.vue')
},
{
path: '/contact/profile',
name: RouteName.CONTACT_PROFILE,
component: () => import('@/views/contact/ContactProfileView.vue'),
beforeEnter: accessHandler,
meta: { requiresAuth: true, access: [NavigationPermission.HOUSING_CONTACT_MANAGEMENT] }
},
{
path: '/developer',
name: RouteName.DEVELOPER,
Expand Down
25 changes: 25 additions & 0 deletions frontend/src/services/contactService.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { appAxios } from './interceptors';

import type { AxiosResponse } from 'axios';
import type { ContactSearchParameters } from '@/types';

const PATH = 'contact';

export default {
/**
* @function searchContacts
* Returns a list of users based on the provided filtering parameters
* @param {SearchUsersOptions} params SearchUsersOptions object containing the data to filter against
* @returns {Promise<AxiosResponse>} An axios response or empty array
*/
searchContacts(params: ContactSearchParameters): Promise<AxiosResponse> {
return appAxios().get(`${PATH}`, { params: params });
},
/**
* @function updateEnquiry
* @returns {Promise} An axios response
*/
updateContact(contactId: string, data?: any) {
return appAxios().put(`${PATH}/${contactId}`, data);
}
};
2 changes: 2 additions & 0 deletions frontend/src/store/authzStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { Permission } from '@/types';

export enum NavigationPermission {
HOUSING = 'housing',
HOUSING_CONTACT_MANAGEMENT = 'housing.contactmanagement',
HOUSING_DROPDOWN = 'housing.dropdown',
HOUSING_ENQUIRY = 'housing.enquiry',
HOUSING_ENQUIRY_INTAKE = 'housing.enquiry.intake',
Expand Down Expand Up @@ -57,6 +58,7 @@ const NavigationAuthorizationMap = [
group: GroupName.PROPONENT,
permissions: [
NavigationPermission.HOUSING,
NavigationPermission.HOUSING_CONTACT_MANAGEMENT,
NavigationPermission.HOUSING_DROPDOWN,
NavigationPermission.HOUSING_ENQUIRY_INTAKE,
NavigationPermission.HOUSING_SUBMISSION_INTAKE,
Expand Down
10 changes: 10 additions & 0 deletions frontend/src/types/ContactSearchParameters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export type ContactSearchParameters = {
contactApplicantRelationship?: string;
contactPreference?: string;
contactId?: string[];
email?: string;
firstName?: string;
lastName?: string;
phoneNumber?: string;
userId?: string[];
};
1 change: 1 addition & 0 deletions frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export type { BasicBCeIDAttribute } from './BasicBCeIDAttribute';
export type { BringForward } from './BringForward';
export type { BusinessBCeIDAttribute } from './BusinessBCeIDAttribute';
export type { Contact } from './Contact';
export type { ContactSearchParameters } from './ContactSearchParameters';
export type { Document } from './Document';
export type { Draft } from './Draft';
export type { Email } from './Email';
Expand Down
1 change: 1 addition & 0 deletions frontend/src/utils/enums/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export enum Regex {
}

export enum RouteName {
CONTACT_PROFILE = 'contact_profile',
DEVELOPER = 'developer',
FORBIDDEN = 'forbidden',
HOME = 'home',
Expand Down
175 changes: 175 additions & 0 deletions frontend/src/views/contact/ContactProfileView.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
<script setup lang="ts">
import { Form } from 'vee-validate';
import { onMounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { object, string, number } from 'yup';
import { FormNavigationGuard, InputText, Dropdown } from '@/components/form';
import { Button, Card, useToast } from '@/lib/primevue';
import contactService from '@/services/contactService';
import { CONTACT_PREFERENCE_LIST, PROJECT_RELATIONSHIP_LIST } from '@/utils/constants/housing';
import { RouteName } from '@/utils/enums/application';
import { setEmptyStringsToNull } from '@/utils/utils';
import type { Contact } from '@/types';
import type { Ref } from 'vue';
// State
const initialFormValues: Ref<any | undefined> = ref(undefined);
const editable: Ref<boolean> = ref(false);
// Form validation schema
const contactSchema = object({
phoneNumber: number().required().label('Phone'),
contactApplicantRelationship: string().required().label('Relationship to project'),
contactPreference: string().required().label('Preferred contact method')
});
// Actions
const { t } = useI18n();
const toast = useToast();
const router = useRouter();
const toHousing = (): void => {
router.push({ name: RouteName.HOUSING });
};
const onSubmit = async (values: any) => {
try {
const submitData: Contact = setEmptyStringsToNull(values);
const result = await contactService.updateContact(values.contactId, submitData);
if (result.status === 200) {
toast.success('Form saved');
editable.value = false;
} else toast.error('Failed to save enquiry');
} catch (e: any) {
toast.error('Failed to save enquiry', e);
}
};
onMounted(async () => {
try {
const contact = (await contactService.searchContacts({}))?.data[0];
initialFormValues.value = {
...contact
};
} catch (e: any) {
toast.error('Failed to load contact', e);
}
});
</script>

<template>
<div class="flex justify-content-center">
<h2>{{ t('contact.profile.fillOutProfile') }}</h2>
</div>

<div class="flex justify-content-center">
<Card style="width: 35rem">
<template #title>
<div class="flex justify-content-between">
<div style="display: inline">
<font-awesome-icon
icon="fa-solid fa-user"
class="mr-3 app-primary-color"
/>
<h3 style="display: inline">{{ t('contact.profile.contactProfile') }}</h3>
</div>
<Button
class="p-button-outlined"
:aria-label="t('contact.profile.edit')"
@click="editable = true"
>
<font-awesome-icon
class="pr-2"
icon="fa-solid fa-edit"
/>
{{ t('contact.profile.edit') }}
</Button>
</div>
</template>
<template #content>
<Form
v-if="initialFormValues"
:validation-schema="contactSchema"
:initial-values="initialFormValues"
@submit="onSubmit"
>
<FormNavigationGuard v-if="editable" />
<InputText
name="firstName"
:label="t('contact.profile.firstName')"
:disabled="true"
/>

<InputText
name="lastName"
:label="t('contact.profile.lastName')"
:disabled="true"
/>
<InputText
name="phoneNumber"
:label="t('contact.profile.phone')"
:disabled="!editable"
/>
<InputText
name="email"
:label="t('contact.profile.email')"
:disabled="true"
/>
<Dropdown
name="contactApplicantRelationship"
:label="t('contact.profile.relationshipToProject')"
:bold="false"
:disabled="!editable"
:options="PROJECT_RELATIONSHIP_LIST"
/>
<Dropdown
name="contactPreference"
:label="t('contact.profile.preferredContact')"
:bold="false"
:disabled="!editable"
:options="CONTACT_PREFERENCE_LIST"
/>

<div
v-if="editable"
class="field flex"
>
<div class="flex-auto">
<Button
class="mr-2"
:label="t('contact.profile.save')"
type="submit"
icon="pi pi-check"
/>
<Button
class="p-button-outlined mr-2"
:label="t('contact.profile.cancel')"
icon="pi pi-times"
@click="editable = false"
/>
</div>
</div>
</Form>
</template>
</Card>
</div>
<div
v-if="!editable"
class="flex justify-content-center"
>
<Button
class="mt-5"
:label="t('contact.profile.getStarted')"
@click="toHousing"
/>
</div>
</template>

<style lang="scss" scoped>
h3 {
margin-top: 1em;
}
</style>

0 comments on commit 82849d4

Please sign in to comment.