From dac7c231b684907679a704f05757434ecf3cb937 Mon Sep 17 00:00:00 2001 From: delano Date: Thu, 27 Jun 2024 15:32:03 -0700 Subject: [PATCH 01/56] [#173] Simplify left nav links (dashboard) ALso consistent nav toolbar --- apps/ui/components/UserDropdown.vue | 10 +++--- apps/ui/layouts/dashboard.vue | 50 ++++++++++++++--------------- apps/ui/pages/dashboard/index.vue | 18 +++++++++-- apps/ui/pages/profile.vue | 6 ++-- apps/ui/pages/requests/index.vue | 21 +++++++++++- 5 files changed, 69 insertions(+), 36 deletions(-) diff --git a/apps/ui/components/UserDropdown.vue b/apps/ui/components/UserDropdown.vue index 00403df7..1ad4c14c 100644 --- a/apps/ui/components/UserDropdown.vue +++ b/apps/ui/components/UserDropdown.vue @@ -23,11 +23,11 @@ const items = computed(() => [ }, ], [ - { - label: "Profile", - icon: "i-heroicons-user", - to: "/profile", - }, + //{ + // label: "Profile", + // icon: "i-heroicons-user", + // to: "/profile", + //}, { label: "Settings", icon: "i-heroicons-cog-8-tooth", diff --git a/apps/ui/layouts/dashboard.vue b/apps/ui/layouts/dashboard.vue index 7577a2c2..39d63f9e 100644 --- a/apps/ui/layouts/dashboard.vue +++ b/apps/ui/layouts/dashboard.vue @@ -6,7 +6,7 @@ const { isHelpSlideoverOpen } = useDashboard() const links = [ { id: 'home', - label: 'Home', + label: 'Portal Home', icon: 'i-heroicons-home', to: '/dashboard', tooltip: { @@ -19,18 +19,18 @@ const links = [ icon: 'i-ph-phone', defaultOpen: route.path.startsWith('/requests'), to: '/requests', - children: [ - { - label: 'Request History', - to: '/requests', - exact: true - }, - { - label: 'New Request', - to: '/requests/new', - exact: true - }, - ], + //children: [ + // { + // label: 'Request History', + // to: '/requests', + // exact: true + // }, + // { + // label: 'New Request', + // to: '/requests/new', + // exact: true + // }, + //], tooltip: { text: 'Your history of requests', @@ -41,18 +41,18 @@ const links = [ label: 'Profile', to: '/profile', icon: 'i-heroicons-user', - children: [ - { - label: 'Address', - to: '/profile', - exact: true - }, - { - label: 'Pets', - to: '/profile/pets', - exact: true - }, - ], + //children: [ + // { + // label: 'Address', + // to: '/profile', + // exact: true + // }, + // { + // label: 'Pets', + // to: '/profile/pets', + // exact: true + // }, + //], tooltip: { text: 'Your profile details', } diff --git a/apps/ui/pages/dashboard/index.vue b/apps/ui/pages/dashboard/index.vue index 3954c06b..2ba03322 100644 --- a/apps/ui/pages/dashboard/index.vue +++ b/apps/ui/pages/dashboard/index.vue @@ -8,6 +8,19 @@ definePageMeta({ layout: 'dashboard', }) +const links = [[ + { + label: 'New Request', + icon: 'i-ph-plus-square-light', + to: '/requests/new', + }, + { + label: 'Request History', + icon: 'i-heroicons-calendar', + to: '/requests', + exact: true + }, +]] /** * Retrieves the authentication status, data, and token using the useAuth() function. @@ -63,8 +76,9 @@ onMounted(() => { - + + + diff --git a/apps/ui/pages/profile.vue b/apps/ui/pages/profile.vue index acf1c4a2..13f87839 100644 --- a/apps/ui/pages/profile.vue +++ b/apps/ui/pages/profile.vue @@ -11,13 +11,13 @@ definePageMeta({ const links = [[ { label: 'Delivery Information', - icon: 'i-heroicons-user-circle', + icon: 'i-heroicons-home', to: '/profile', exact: true }, { label: 'My Pets', - icon: 'i-heroicons-home', + icon: 'i-ph-paw-print', to: '/profile/pets', } ]] @@ -29,7 +29,7 @@ const links = [[ - + diff --git a/apps/ui/pages/requests/index.vue b/apps/ui/pages/requests/index.vue index 9126ad6c..8ea7a411 100644 --- a/apps/ui/pages/requests/index.vue +++ b/apps/ui/pages/requests/index.vue @@ -6,6 +6,20 @@ definePageMeta({ const q = ref(""); +const links = [[ + { + label: 'New Request', + icon: 'i-ph-plus-square-light', + to: '/requests/new', + }, + { + label: 'Request History', + icon: 'i-heroicons-calendar', + to: '/requests', + exact: true + }, +]] + const { status: authStatus, data: authData, @@ -40,7 +54,12 @@ onMounted(() => { @@ -16,6 +16,7 @@ import type { Branch, FoodRequestFormState } from '@/types/index'; import { Validator } from '@vueform/vueform'; +const toast = useToast(); /** * WARNING! ATTENTION! ACHTUNG! ATENCIÓN! 注意! ВНИМАНИЕ! توجه! @@ -58,8 +59,8 @@ const { // updates the summary based on the form data that's been // entered. This is a good way to keep the summary in sync // with the form data. -const addSummaryFunctionality = (form$: any) => { - console.log("Form mounted", 'form$'); +const onFormMounted = (form$: any) => { + console.log("Form mounted", form$); let summaryStep = form$.steps$.steps$.step4; @@ -69,6 +70,9 @@ const addSummaryFunctionality = (form$: any) => { // TODO: Generate the summary based on the form data // and update the summary element in the form. }); + + console.log(form$.el$("delivery_address.branch_locations")); + //form$.el$('delivery_address.branch_locations').value = profileInfo.value?.branch || null; }; const submitFoodRequest = async (form$: any, FormData: any) => { @@ -134,7 +138,7 @@ const validatedGoogleAddress = class extends Validator { return 'Please select a valid address from the dropdown.'; } - get isAsync() { +get isAsync() { return true; } } @@ -337,11 +341,18 @@ watch(() => props.autocomplete, (newValue, oldValue) => { } newValue.addListener("place_changed", () => { const place = newValue.getPlace(); - form$.value.el$("delivery_address.ext_address_id").value = place.place_id; - form$.value.el$("delivery_address.ext_address_details").value = { - place: place, - } - form$.value.el$("delivery_address.interactive_address").value = place.formatted_address; + + //if (!place.geometry || !place.geometry.location) { + + // Set the address selected flag to true + props.addressSelected.value = true; + form$.value.el$("delivery_address.ext_address_id").value = place.place_id; + form$.value.el$("delivery_address.ext_address_details").value = { + place: place, + } + form$.value.el$("delivery_address.interactive_address").value = place.formatted_address; + + }); }); @@ -352,6 +363,21 @@ onMounted(() => { const state = props.state; const branchLocations = form$.value.el$("delivery_address.branch_locations"); +// state.value = state || {} as FoodRequestFormState; +// state.value.delivery_address = state.value.delivery_address || {}; +// +// if (profileInfo.value) { +// state.value.delivery_address.branch_location = profileInfo.value?.branch || null; +// state.value.delivery_contact = state.value.delivery_contact || {}; +// state.value.delivery_contact.contact_name = profileInfo.value?.preferred_name || null; +// state.value.delivery_contact.contact_email = profileInfo.value?.email || null; +// state.value.delivery_contact.contact_phone = profileInfo.value?.phone_number || null; +// } + + console.log("state", state); + console.log("branch:", profileInfo.branch); + + fetchBranches(); console.log("parsedBranches", parsedBranches); @@ -384,7 +410,7 @@ onMounted(() => { container: 12, wrapper: 6, }, - default: state?.delivery_address?.branch_location, + default: profileInfo.branch || null, onChange: (value: string) => { const selectedBranch = branchesMap.value.get(value); if (selectedBranch && selectedBranch.latitude && selectedBranch.longitude) { @@ -404,7 +430,7 @@ onMounted(() => { rules: [], label: "A google.maps.places.PlaceResult object", hidden: true, - default: state?.delivery_address?.ext_address_details, + default: profileInfo.ext_address_details || null, }, interactive_address: { type: "text", @@ -424,7 +450,7 @@ onMounted(() => { wrapper: 6 }, floating: false, - default: state?.delivery_address?.interactive_address, // || "1234 Southview Drive SE, Medicine Hat, AB, Canada", + default: profileInfo.address || null, // || "1234 Southview Drive SE, Medicine Hat, AB, Canada", disabled: false, // Start disabled onBlur: (value: string) => { console.log("Selected address: ", value.value); @@ -513,7 +539,7 @@ onMounted(() => { label: 12, wrapper: 3, }, - default: state.delivery_contact?.contact_name || "Delbo Baggins", + default: profileInfo.preferred_name || null, conditions: [ ['delivery_contact.choose_contact', true], ], @@ -558,7 +584,7 @@ onMounted(() => { ['delivery_contact.preferred_method', ['Email']], ['delivery_contact.choose_contact', true], ], - default: state.delivery_contact?.contact_email || 'profile?.email', + default: profileInfo.email || null, }, alt_contact_email: { type: "text", @@ -578,12 +604,13 @@ onMounted(() => { }, contact_phone: { type: "text", - rules: ["required", "max:16"], + rules: ["required", "max:24"], label: "Contact phone", placeholder: "(123) 456-7890", + description: "If you want to update your phone number, please update it in your profile before submitting a request.", floating: false, - mask: "(000) 000-0000", - disabled: true, + mask: "+1 (000) 000-0000", + disabled: (!!profileInfo.phone_number), columns: { container: 12, label: 12, @@ -593,7 +620,7 @@ onMounted(() => { ['delivery_contact.preferred_method', ['Call', 'Text']], ['delivery_contact.choose_contact', true], ], - default: state.delivery_contact?.contact_phone || '123-456-7890', + default: profileInfo.phone_number || null, }, alt_contact_phone: { type: "text", diff --git a/apps/ui/pages/profile/index.vue b/apps/ui/pages/profile/index.vue index 8022f349..dd44d0bf 100644 --- a/apps/ui/pages/profile/index.vue +++ b/apps/ui/pages/profile/index.vue @@ -27,7 +27,7 @@ onMounted(() => { profile_id: profileInfo?.id, user_id: userInfo.id, branch_selection: profileInfo?.branch, //|| '5c3549e0-a728-4510-a64a-69bcd26d52d5', // Osoyoos - name: userInfo.name, + name: profileInfo.preferred_name || userInfo.name, email: userInfo.email, phone_number: profileInfo?.phone_number, address: profileInfo?.address, diff --git a/apps/ui/pages/requests/new.vue b/apps/ui/pages/requests/new.vue index dfef1141..69b693d9 100644 --- a/apps/ui/pages/requests/new.vue +++ b/apps/ui/pages/requests/new.vue @@ -53,9 +53,10 @@ const links = [[ ]] const { - status, - data: userInfo, -} = useAuth(); + profileInfo, + userInfo, + authToken, +} = useProfile(); /** * From f03e48dd64068912a1d56e47de33d56286cbc0c8 Mon Sep 17 00:00:00 2001 From: delano Date: Sun, 30 Jun 2024 12:35:00 -0700 Subject: [PATCH 35/56] [#173] Fix for default email --- apps/ui/components/requests/FoodRequestForm.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/ui/components/requests/FoodRequestForm.vue b/apps/ui/components/requests/FoodRequestForm.vue index ad40fb7e..a1eabf08 100644 --- a/apps/ui/components/requests/FoodRequestForm.vue +++ b/apps/ui/components/requests/FoodRequestForm.vue @@ -574,7 +574,7 @@ onMounted(() => { label: "Contact email", placeholder: "e.g. your email address", floating: false, - disabled: true, + disabled: (!!userInfo.email), columns: { container: 12, label: 12, @@ -584,7 +584,7 @@ onMounted(() => { ['delivery_contact.preferred_method', ['Email']], ['delivery_contact.choose_contact', true], ], - default: profileInfo.email || null, + default: userInfo.email || null, }, alt_contact_email: { type: "text", @@ -646,7 +646,7 @@ onMounted(() => { // // STEP 2 - Your Pets // - client_pets: clientPetsSchema, + client_pets: clientPetsSchema(), // // STEP 3 - Safe Drop From 7bdaa5c3b450986b6dfbb1b864df9ab3698d67b2 Mon Sep 17 00:00:00 2001 From: delano Date: Sun, 30 Jun 2024 13:28:49 -0700 Subject: [PATCH 36/56] [#173] Enhance form behavior for delivery address - Extract branch location change handler for clarity - Move branch location initialization logic to onMounted - Import clientPetsSchema dynamically based on default pet data - Add TODOs for implementing summary step functionality --- .../components/requests/FoodRequestForm.vue | 85 +++++++------------ apps/ui/modules/requests/clientPetsSchema.ts | 34 +------- 2 files changed, 36 insertions(+), 83 deletions(-) diff --git a/apps/ui/components/requests/FoodRequestForm.vue b/apps/ui/components/requests/FoodRequestForm.vue index a1eabf08..c4c718a3 100644 --- a/apps/ui/components/requests/FoodRequestForm.vue +++ b/apps/ui/components/requests/FoodRequestForm.vue @@ -49,31 +49,6 @@ const { authToken, } = useProfile(); -// Use the form's mounted event to add custom functionality to -// the confirmation step. This is where we can add a summary -// of the form data for the user to review before submitting. -// -// We need to do this at the time of mounting so that the -// entire form is rendered and available to us. We can then -// add a function to the confirmation step that dynamically -// updates the summary based on the form data that's been -// entered. This is a good way to keep the summary in sync -// with the form data. -const onFormMounted = (form$: any) => { - console.log("Form mounted", form$); - - let summaryStep = form$.steps$.steps$.step4; - - summaryStep.on("activate", (form$: any) => { - console.log("Summary step activated", form$); - - // TODO: Generate the summary based on the form data - // and update the summary element in the form. - }); - - console.log(form$.el$("delivery_address.branch_locations")); - //form$.el$('delivery_address.branch_locations').value = profileInfo.value?.branch || null; -}; const submitFoodRequest = async (form$: any, FormData: any) => { // Using form$.data WILL INCLUDE conditional elements and it @@ -143,6 +118,14 @@ get isAsync() { } } +const onBranchLocationChange = (selectedBranchId: string) => { + console.log("Branch selected: ", selectedBranchId); + const selectedBranch = branchesMap.value.get(selectedBranchId); + if (selectedBranch && selectedBranch.latitude && selectedBranch.longitude) { + props.updateAutocomplete(selectedBranch.latitude, selectedBranch.longitude); + } +} + const autocomplete = ref(null); /** @@ -170,16 +153,9 @@ const steps = { next: "Next: Contact", }, onActivate: (form$$: any) => { + const branchLocations = form$?.value?.el$("delivery_address.branch_locations"); console.log("Step 0 activated", form$$); - form$.value.el$("delivery_address.branch_locations").on("change", (selectedBranchId: string) => { - console.log("Branch selected: ", selectedBranchId); - const selectedBranch = branchesMap.value.get(selectedBranchId); - if (selectedBranch && selectedBranch.latitude && selectedBranch.longitude) { - props.updateAutocomplete(selectedBranch.latitude, selectedBranch.longitude); - } - }); - - + branchLocations.on("change", onBranchLocationChange); }, on: (form$: any, el: any) => { console.log("Step 0 on", form$, el); @@ -363,26 +339,8 @@ onMounted(() => { const state = props.state; const branchLocations = form$.value.el$("delivery_address.branch_locations"); -// state.value = state || {} as FoodRequestFormState; -// state.value.delivery_address = state.value.delivery_address || {}; -// -// if (profileInfo.value) { -// state.value.delivery_address.branch_location = profileInfo.value?.branch || null; -// state.value.delivery_contact = state.value.delivery_contact || {}; -// state.value.delivery_contact.contact_name = profileInfo.value?.preferred_name || null; -// state.value.delivery_contact.contact_email = profileInfo.value?.email || null; -// state.value.delivery_contact.contact_phone = profileInfo.value?.phone_number || null; -// } - - console.log("state", state); - console.log("branch:", profileInfo.branch); - - fetchBranches(); - console.log("parsedBranches", parsedBranches); - console.log("state", state); - console.log("branchLocations", branchLocations); schema.value = { // @@ -811,9 +769,30 @@ onMounted(() => { }, }; - }); + +// Use the form's mounted event to add custom functionality to +// the confirmation step. This is where we can add a summary +// of the form data for the user to review before submitting. +// +// We need to do this at the time of mounting so that the +// entire form is rendered and available to us. We can then +// add a function to the confirmation step that dynamically +// updates the summary based on the form data that's been +// entered. This is a good way to keep the summary in sync +// with the form data. +const onFormMounted = (form$: any) => { + let summaryStep = form$.steps$.steps$.step4; + + summaryStep.on("activate", (form$: any) => { + console.log("Summary step activated", form$); + + // TODO: Generate the summary based on the form data + // and update the summary element in the form. + }); +}; + diff --git a/apps/ui/components/requests/FoodRequestForm.vue b/apps/ui/components/requests/FoodRequestForm.vue index 9afaa6c0..ff0d3da4 100644 --- a/apps/ui/components/requests/FoodRequestForm.vue +++ b/apps/ui/components/requests/FoodRequestForm.vue @@ -341,6 +341,9 @@ onMounted(() => { const state = props.state; const branchLocations = form$.value.el$("delivery_address.branch_locations"); + const profilePets = profileInfo.pets || [] + const withControls = false; + const beforeText = "Please confirm the details for each of your pets you are including in this request."; fetchBranches(); @@ -623,7 +626,7 @@ onMounted(() => { // // STEP 2 - Your Pets // - client_pets: clientPetsSchema([defaultPetExample]), + client_pets: clientPetsSchema(profilePets, withControls, beforeText), // // STEP 3 - Safe Drop diff --git a/apps/ui/modules/requests/clientPetsSchema.ts b/apps/ui/modules/requests/clientPetsSchema.ts index f1709683..92795c97 100644 --- a/apps/ui/modules/requests/clientPetsSchema.ts +++ b/apps/ui/modules/requests/clientPetsSchema.ts @@ -8,11 +8,11 @@ import type { PetInfo } from '@/types'; * @param withControls - If `true` (default), the form will include controls for adding and removing pets. * @returns - A schema object that can be used to generate a form. */ -const clientPetsSchema = (defaultPets: PetInfo[] = [], withControls: boolean = true) => { +const clientPetsSchema = (defaultPets: PetInfo[] = [], withControls: boolean = true, beforeText: string = "") => { console.log("Creating client pets schema with default pets:", defaultPets); return { type: "object", - before: "Please confirm the details of each of your pets. ", + before: beforeText, schema: { pets: { type: "list", @@ -29,7 +29,6 @@ const clientPetsSchema = (defaultPets: PetInfo[] = [], withControls: boolean = t }, ElementLabel: { wrapper: 'text-[20px] font-semibold mb-4', - }, }, default: defaultPets, diff --git a/apps/ui/tailwind.config.ts b/apps/ui/tailwind.config.ts index 5da2b299..edc3a4e9 100644 --- a/apps/ui/tailwind.config.ts +++ b/apps/ui/tailwind.config.ts @@ -26,7 +26,11 @@ export default >{ extend: { fontFamily: { sans: ['DM Sans', ...defaultTheme.fontFamily.sans] - } + }, + colors: { + // This is a tester, not currently used + 'custom-cancel': '#f3f4f6', + }, }, darkMode: 'class', }, diff --git a/apps/ui/vueform.config.ts b/apps/ui/vueform.config.ts index 7a63fe88..c067b8da 100644 --- a/apps/ui/vueform.config.ts +++ b/apps/ui/vueform.config.ts @@ -36,5 +36,10 @@ export default defineConfig({ // builder, PluginMask, ], + addClasses: { + Vueform: { + form: 'afbcore-form', + }, + }, axios: httpClient, }) From de6760a4560aed01f8b91ffd01b5193404d8d795 Mon Sep 17 00:00:00 2001 From: delano Date: Mon, 1 Jul 2024 10:51:10 -0700 Subject: [PATCH 53/56] [#173] Fix up nuxt-auth config --- apps/ui/nuxt.config.ts | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/apps/ui/nuxt.config.ts b/apps/ui/nuxt.config.ts index 33f49658..35aed5df 100644 --- a/apps/ui/nuxt.config.ts +++ b/apps/ui/nuxt.config.ts @@ -148,12 +148,14 @@ export default defineNuxtConfig({ signUp: { path: "/register", method: "post" }, getSession: { path: "/current_user/", method: "get" }, }, - sessionDataType: { - id: "string", - email: "string", - name: "string", - role: "admin | guest | client | volunteer | branchmanager", - subscriptions: "{ id: number, status: 'ACTIVE' | 'INACTIVE' }[]", + session: { + dataType: { + id: "string", + email: "string", + name: "string", + role: "admin | guest | client | volunteer | branchmanager", + subscriptions: "{ id: number, status: 'ACTIVE' | 'INACTIVE' }[]", + }, }, token: { signInResponseTokenPointer: "/token", // json path in response @@ -161,18 +163,18 @@ export default defineNuxtConfig({ type: "Token", cookieName: 'auth.token', headerName: 'Authorization', - maxAgeInSeconds: 3600 * 24 * 30, + maxAgeInSeconds: 3600 * 24 * 30, // 30 days sameSiteAttribute: 'strict', }, }, - session: { + sessionRefresh: { // Whether to refresh the session every time the browser window is refocused. - enableRefreshOnWindowFocus: true, + enableOnWindowFocus: false, // Whether to refresh the session every `X` milliseconds. Set // this to `false` to turn it off. The session will only be // refreshed if a session already exists. - enableRefreshPeriodically: 60000 * 5, // 5 minutes + enablePeriodically: 60000 * 10, // 10 minutes }, }, From 8784c1c4fc7dd1f6758a2272d9edcfddd6b8ade2 Mon Sep 17 00:00:00 2001 From: delano Date: Mon, 1 Jul 2024 11:10:16 -0700 Subject: [PATCH 54/56] [#173] Add content to summary screen via computed --- .../components/requests/FoodRequestForm.vue | 127 ++++++++++++++---- 1 file changed, 103 insertions(+), 24 deletions(-) diff --git a/apps/ui/components/requests/FoodRequestForm.vue b/apps/ui/components/requests/FoodRequestForm.vue index ff0d3da4..de5ffa5b 100644 --- a/apps/ui/components/requests/FoodRequestForm.vue +++ b/apps/ui/components/requests/FoodRequestForm.vue @@ -14,7 +14,7 @@