Skip to content

Commit 847e3c9

Browse files
authored
Frontend/compact states from server (#716)
### Requirements List - `yarn install --ignore-engines` ### Description List - Added endpoint calls to new server endpoints for fetching compact states - Added store actions to invoke new endpoint calls, including a new loading state - Updated `setCurrentCompact` action to fetch compact states as needed - Updated `setCurrentCompact` call sites to ensure async compatibility - Minor update in edit-user state list for consistency - Updated browserlist build dependency ### Draft Todo - [x] Deploy backend endpoint PR to development #699 - [x] Smoke test this frontend feature against live backend fetches - [x] Publish this PR ### Testing List - `yarn test:unit:all` should run without errors or warnings - `yarn serve` should run without errors or warnings - `yarn build` should run without errors or warnings - Code review - Test all state-select lists in app UI to make sure they are populated correctly - Logged-out public search UI - Logged-in Staff license search UI - Logged-in Staff user edit & invite UI - Compact-switching (for users with multiple compacts) - etc. Closes #663 <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Introduced API endpoints to fetch and deliver compact state data for both authenticated and public users. - Added a new constant for compact states with U.S. postal abbreviations. - Added a computed property to dynamically retrieve and update state options based on the current compact. - **Refactor** - Simplified compact configuration by streamlining member state details. - Enhanced conditional state updates in compact selection and improved asynchronous operations for smoother UI transitions. - **Tests** - Expanded test coverage to validate compact state fetching and ensure reliable user interactions. - Updated tests to utilize enumerated types for improved type safety. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
1 parent 481a4ed commit 847e3c9

File tree

21 files changed

+304
-160
lines changed

21 files changed

+304
-160
lines changed

webroot/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"@vue/compat": "3.4.21",
1717
"@vuepic/vue-datepicker": "^8.7.0",
1818
"axios": "1.8.2",
19+
"caniuse-lite": "^1.0.30001709",
1920
"click-outside-vue3": "^4.0.1",
2021
"core-js": "^3.23.0",
2122
"country-codes-flags-phone-codes": "^1.0.4",

webroot/src/app.config.ts

Lines changed: 3 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -214,117 +214,9 @@ export const stateList = [
214214
// = Compact configuration =
215215
// =============================
216216
export const compacts = {
217-
aslp: {
218-
memberStates: [
219-
'AL',
220-
'AK',
221-
'AR',
222-
'CO',
223-
'DE',
224-
'FL',
225-
'GA',
226-
'ID',
227-
'IN',
228-
'IA',
229-
'KS',
230-
'KY',
231-
'LA',
232-
'ME',
233-
'MD',
234-
'MN',
235-
'MS',
236-
'MO',
237-
'MT',
238-
'NE',
239-
'NH',
240-
'NC',
241-
'OH',
242-
'OK',
243-
'SC',
244-
'TN',
245-
'UT',
246-
'VT',
247-
'VA',
248-
'WA',
249-
'WV',
250-
'WI',
251-
'WY',
252-
],
253-
},
254-
octp: {
255-
memberStates: [
256-
'AL',
257-
'AZ',
258-
'AR',
259-
'CO',
260-
'DE',
261-
'GA',
262-
'IN',
263-
'IA',
264-
'KY',
265-
'LA',
266-
'ME',
267-
'MD',
268-
'MN',
269-
'MS',
270-
'MO',
271-
'MT',
272-
'NE',
273-
'NH',
274-
'NC',
275-
'OH',
276-
'SC',
277-
'SD',
278-
'TN',
279-
'UT',
280-
'VT',
281-
'VA',
282-
'WA',
283-
'WV',
284-
'WI',
285-
'WY',
286-
],
287-
},
288-
coun: {
289-
memberStates: [
290-
'AL',
291-
'AZ',
292-
'AR',
293-
'CO',
294-
'CT',
295-
'DE',
296-
'FL',
297-
'GA',
298-
'IN',
299-
'IA',
300-
'KS',
301-
'KY',
302-
'LA',
303-
'ME',
304-
'MD',
305-
'MN',
306-
'MS',
307-
'MO',
308-
'MT',
309-
'NE',
310-
'NH',
311-
'NJ',
312-
'NC',
313-
'ND',
314-
'OH',
315-
'OK',
316-
'SC',
317-
'SD',
318-
'TN',
319-
'UT',
320-
'VT',
321-
'VA',
322-
'WA',
323-
'WV',
324-
'WI',
325-
'WY',
326-
],
327-
},
217+
aslp: {},
218+
octp: {},
219+
coun: {},
328220
};
329221

330222
export default {

webroot/src/components/CompactSelector/CompactSelector.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,16 +136,16 @@ class CompactSelector extends mixins(MixinForm) {
136136
async handleCompactSelect(): Promise<void> {
137137
const selectedCompactType = this.formData.compact.value;
138138

139-
// Set the new compact type on the store
140-
this.$store.dispatch('user/setCurrentCompact', CompactSerializer.fromServer({ type: selectedCompactType }));
141-
142139
// If the current route is not matching the newly selected compact, then redirect
143140
if (this.routeCompactType && this.routeCompactType !== selectedCompactType) {
144141
await this.$router.push({
145142
name: (this.$route.name as RouteRecordName),
146143
params: { compact: selectedCompactType }
147144
});
148145
await this.$router.go(0); // This is the easiest way to force vue-router to reload components, which suits this compact-switch case
146+
} else {
147+
// Refresh the compact type on the store
148+
await this.$store.dispatch('user/setCurrentCompact', CompactSerializer.fromServer({ type: selectedCompactType }));
149149
}
150150
}
151151

webroot/src/components/Licensee/LicenseeList/LicenseeList.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,11 +199,11 @@ class LicenseeList extends Vue {
199199
}
200200
}
201201

202-
resetSearch(): void {
202+
async resetSearch(): Promise<void> {
203203
this.$store.dispatch('license/resetStoreSearch');
204204

205205
if (this.isPublicSearch) {
206-
this.$store.dispatch('user/setCurrentCompact', null);
206+
await this.$store.dispatch('user/setCurrentCompact', null);
207207
}
208208

209209
this.toggleSearch();

webroot/src/components/Licensee/LicenseeSearch/LicenseeSearch.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
Component,
1010
mixins,
1111
Prop,
12+
Watch,
1213
toNative
1314
} from 'vue-facing-decorator';
1415
import { reactive, computed } from 'vue';
@@ -18,6 +19,7 @@ import InputSelect from '@components/Forms/InputSelect/InputSelect.vue';
1819
import InputSubmit from '@components/Forms/InputSubmit/InputSubmit.vue';
1920
import SearchIcon from '@components/Icons/LicenseSearchAlt/LicenseSearchAlt.vue';
2021
import { CompactType, CompactSerializer } from '@models/Compact/Compact.model';
22+
import { State } from '@models/State/State.model';
2123
import { FormInput } from '@models/FormInput/FormInput.model';
2224
import Joi from 'joi';
2325

@@ -78,9 +80,12 @@ class LicenseeSearch extends mixins(MixinForm) {
7880
return this.isPublicSearch;
7981
}
8082

83+
get compactStates(): Array<State> {
84+
return this.userStore.currentCompact?.memberStates || [];
85+
}
86+
8187
get stateOptions(): Array<any> {
82-
const { currentCompact } = this.userStore;
83-
const compactMemberStates = (currentCompact?.memberStates || []).map((state) => ({
88+
const compactMemberStates = this.compactStates.map((state) => ({
8489
value: state.abbrev, name: state.name()
8590
}));
8691
const defaultSelectOption: any = { value: '' };
@@ -153,7 +158,6 @@ class LicenseeSearch extends mixins(MixinForm) {
153158
if (this.enableCompactSelect) {
154159
await this.$store.dispatch('user/setCurrentCompact', CompactSerializer.fromServer({ type: selectedCompactType.value }));
155160
state.value = '';
156-
state.valueOptions = this.stateOptions;
157161
}
158162
}
159163

@@ -194,6 +198,13 @@ class LicenseeSearch extends mixins(MixinForm) {
194198

195199
this.checkValidForAll();
196200
}
201+
202+
//
203+
// Watch
204+
//
205+
@Watch('compactStates') updateStateInput() {
206+
this.formData.state.valueOptions = this.stateOptions;
207+
}
197208
}
198209

199210
export default toNative(LicenseeSearch);

webroot/src/components/Page/PageContainer/PageContainer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ class PageContainer extends Vue {
6868
get isLoading(): boolean {
6969
return this.globalStore.isLoading
7070
|| this.userStore.isLoadingAccount
71+
|| this.userStore.isLoadingCompactStates
7172
|| this.userStore.isLoadingPrivilegePurchaseOptions;
7273
}
7374
}

webroot/src/components/StateUpload/StateUpload.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ describe('StateUpload component', async () => {
2727
const component = wrapper.vm;
2828

2929
component.$store.dispatch('user/setStoreUser', new StaffUser());
30-
component.$store.dispatch('user/setCurrentCompact', new Compact({ type: CompactType.ASLP }));
30+
await component.$store.dispatch('user/setCurrentCompact', new Compact({ type: CompactType.ASLP }));
3131
component.isFormSuccessful = true;
3232
await nextTick();
3333

webroot/src/components/Users/UserRowEdit/UserRowEdit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,8 @@ class UserRowEdit extends mixins(MixinForm) {
171171
}
172172

173173
get userOptionsState(): Array<PermissionOption> {
174-
const { userCompact } = this;
175-
let options = userCompact?.memberStates?.map((memberState: State) => ({
174+
const { currentCompact } = this;
175+
let options = currentCompact?.memberStates?.map((memberState: State) => ({
176176
value: (memberState.abbrev as unknown as string)?.toLowerCase() || '',
177177
name: memberState.name(),
178178
})) || [];

webroot/src/models/Compact/Compact.model.spec.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
// Created by InspiringApps on 8/27/2024.
66
//
77

8-
import { compacts as compactConfigs, FeeTypes } from '@/app.config';
8+
import { FeeTypes } from '@/app.config';
99
import { expect } from 'chai';
1010
import { Compact, CompactSerializer, CompactType } from '@models/Compact/Compact.model';
1111
import { PrivilegePurchaseOption } from '@models/PrivilegePurchaseOption/PrivilegePurchaseOption.model';
@@ -73,20 +73,15 @@ describe('Compact model', () => {
7373
const data = {
7474
id: 'test-id',
7575
type: CompactType.ASLP,
76+
memberStates: [new State()],
7677
};
7778
const compact = CompactSerializer.fromServer(data);
78-
const compactStates = compactConfigs[CompactType.ASLP].memberStates;
7979

8080
// Test field values
8181
expect(compact).to.be.an.instanceof(Compact);
8282
expect(compact.id).to.equal(data.id);
8383
expect(compact.type).to.equal(data.type);
84-
expect(compact.memberStates).to.be.an('array').with.length(33);
85-
86-
compact.memberStates.forEach((state, idx) => {
87-
expect(state).to.be.an.instanceof(State);
88-
expect(state.abbrev).to.equal(compactStates[idx]);
89-
});
84+
expect(compact.memberStates).to.be.an('array').with.length(1);
9085

9186
// Test methods
9287
expect(compact.name()).to.equal('Audiology and Speech Language Pathology');

webroot/src/models/Compact/Compact.model.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
// Created by InspiringApps on 8/27/2024.
66
//
77

8-
import { compacts as compactConfigs } from '@/app.config';
98
import { deleteUndefinedProperties } from '@models/_helpers';
109
import { PrivilegePurchaseOption } from '@models/PrivilegePurchaseOption/PrivilegePurchaseOption.model';
1110
import { State } from '@models/State/State.model';
@@ -83,15 +82,8 @@ export class CompactSerializer {
8382
const compactData = {
8483
id: json.id,
8584
type: json.type,
86-
memberStates: [] as Array<State>,
85+
memberStates: json.memberStates || [] as Array<State>,
8786
};
88-
const compactConfig: any = compactConfigs[json.type] || {};
89-
90-
if (Array.isArray(compactConfig.memberStates)) {
91-
compactConfig.memberStates.forEach((abbrev) => {
92-
compactData.memberStates.push(new State({ abbrev }));
93-
});
94-
}
9587

9688
return new Compact(compactData);
9789
}

0 commit comments

Comments
 (0)