Skip to content

Commit

Permalink
feat(vue-demo): create state and country component (#270)
Browse files Browse the repository at this point in the history
  • Loading branch information
mdanilowicz authored Jul 3, 2023
1 parent e6a52ec commit 14d97c5
Show file tree
Hide file tree
Showing 15 changed files with 180 additions and 115 deletions.
5 changes: 5 additions & 0 deletions .changeset/orange-buses-bake.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"vue-demo-store": minor
---

Add state to the address forms
5 changes: 5 additions & 0 deletions .changeset/yellow-shrimps-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@shopware-pwa/api-client": patch
---

Change getContextCountryEndpoint request type to POST
2 changes: 1 addition & 1 deletion apps/e2e-tests/page-objects/CheckoutPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class CheckoutPage {
this.street = page.getByTestId("checkout-pi-street-address-input");
this.zipcode = page.getByTestId("checkout-pi-zip-code-input");
this.city = page.getByTestId("checkout-pi-city-input");
this.country = page.getByTestId("checkout-pi-country-input");
this.country = page.getByTestId("country-select");
this.submitButton = page.getByTestId("checkout-pi-submit-button");
this.termsBox = page.getByTestId("checkout-terms-box");
this.termCheckbox = page.getByTestId("checkout-t&c-checkbox-tos");
Expand Down
2 changes: 1 addition & 1 deletion apps/e2e-tests/page-objects/RegisterPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export class RegisterForm {
this.street = page.getByTestId("registration-street-input");
this.zipcode = page.getByTestId("registration-zipcode-input");
this.city = page.getByTestId("registration-city-input");
this.country = page.getByTestId("registration-country-select");
this.country = page.getByTestId("country-select");
this.submitButton = page.getByTestId("registration-submit-button");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,23 @@ vi.mock("../../../src/apiService");
const mockedApiInstance = defaultInstance;

describe("ContextService - getAvailableCountries", () => {
const mockedGet = vi.fn();
const mockedPost = vi.fn();
beforeEach(() => {
vi.resetAllMocks();
mockedApiInstance.invoke = {
get: mockedGet,
post: mockedPost,
} as any;
});
it("should return array with countries", async () => {
mockedGet.mockResolvedValueOnce({ data: { total: 2 } });
mockedPost.mockResolvedValueOnce({ data: { total: 2 } });

const result = await getAvailableCountries();
expect(mockedGet).toBeCalledTimes(1);
expect(mockedGet).toBeCalledWith("/store-api/country");
expect(mockedPost).toBeCalledTimes(1);
expect(mockedPost).toBeCalledWith("/store-api/country", {
associations: {
states: {},
},
});
expect(result.total).toEqual(2);
});
});
8 changes: 6 additions & 2 deletions packages/api-client/src/services/contextService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,9 +182,13 @@ export async function setCurrentLanguage(
export async function getAvailableCountries(
contextInstance: ShopwareApiInstance = defaultInstance
): Promise<EntityResult<"country", Country>> {
const { data } = await contextInstance.invoke.get<
const { data } = await contextInstance.invoke.post<
EntityResult<"country", Country>
>(getContextCountryEndpoint());
>(getContextCountryEndpoint(), {
associations: {
states: {},
},
});
return data;
}

Expand Down
12 changes: 11 additions & 1 deletion packages/composables/src/useCountries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ import {
provide,
} from "vue";
import { getAvailableCountries } from "@shopware-pwa/api-client";
import { Country } from "@shopware-pwa/types";
import { Country, CountryState } from "@shopware-pwa/types";
import { useShopwareContext } from "./useShopwareContext";

export type UseCountriesReturn = {
mountedCallback(): Promise<void>;
getCountries: ComputedRef<Country[]>;
fetchCountries(): Promise<void>;
getStatesForCountry(countryId: string): CountryState[] | null;
};

/**
Expand Down Expand Up @@ -43,11 +44,20 @@ export function useCountries(): UseCountriesReturn {
}
};

const getStatesForCountry = (countryId: string) => {
return (
getCountries.value.find((element: Country) => {
return element.id === countryId;
})?.states || null
);
};

onMounted(mountedCallback);

return {
mountedCallback,
fetchCountries,
getStatesForCountry,
getCountries,
};
}
Empty file.
34 changes: 6 additions & 28 deletions templates/vue-demo-store/components/account/AccountAddressForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ const props = withDefaults(
}
);
const { getCountries } = useCountries();
const { getSalutations } = useSalutations();
const { t } = useI18n();
const { pushError } = useNotifications();
const formData = reactive<CustomerAddress>({
countryId: props.address?.countryId ?? "",
countryStateId: props.address?.countryStateId ?? "",
salutationId: props.address?.salutationId ?? "",
firstName: props.address?.firstName ?? "",
lastName: props.address?.lastName ?? "",
Expand Down Expand Up @@ -130,33 +130,11 @@ useFocus(firstNameInputElement, { initialValue: true });
data-testid="account-address-form-lastname-input"
/>
</div>
<div class="col-span-6 sm:col-span-6">
<label
for="country"
class="block mb-2 text-sm font-medium text-gray-500"
>
{{ $t("form.country") }}
</label>
<select
id="country"
v-model="formData.countryId"
required
name="country"
autocomplete="country-name"
class="mt-1 block w-full py-2.5 px-3 border border-gray-300 bg-white rounded-md shadow-sm focus:outline-none focus:ring-brand-light focus:border-brand-light sm:text-sm"
data-testid="account-address-form-country-select"
>
<option
v-for="country in getCountries"
:key="country.id"
:value="country.id"
data-testid="account-address-form-country-select-option"
>
{{ country.name }}
</option>
</select>
</div>

<SharedCountryStateInput
v-model:countryId="formData.countryId"
v-model:stateId="formData.countryStateId"
class="col-span-6 sm:col-span-6"
/>
<div class="col-span-6">
<label
for="street-address"
Expand Down
53 changes: 16 additions & 37 deletions templates/vue-demo-store/components/account/AccountRegisterForm.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<script setup lang="ts">
import { useVuelidate } from "@vuelidate/core";
import { required, email, minLength } from "@vuelidate/validators";
import { required, email, minLength, requiredIf } from "@vuelidate/validators";
import { ClientApiError } from "@shopware-pwa/types";
const props = defineProps<{
customerGroupId?: string;
}>();
const { getSalutations } = useSalutations();
const { getCountries } = useCountries();
const { getStatesForCountry } = useCountries();
const { register, isLoggedIn } = useUser();
const { pushError } = useNotifications();
Expand All @@ -34,6 +34,7 @@ const initialState = {
zipcode: "",
city: "",
countryId: "",
countryStateId: "",
},
};
Expand Down Expand Up @@ -73,6 +74,11 @@ const rules = computed(() => ({
countryId: {
required,
},
countryStateId: {
required: requiredIf(() => {
return !!getStatesForCountry(state.billingAddress.countryId)?.length;
}),
},
},
}));
Expand Down Expand Up @@ -278,7 +284,7 @@ useBreadcrumbs([
</h3>
<div class="grid grid-cols-12 gap-5 mb-10">
<div class="col-span-12 md:col-span-4">
<label for="street">{{ $t("form.street") }} *</label>
<label for="street">{{ $t("form.streetAddress") }} *</label>
<input
id="Street"
v-model="state.billingAddress.street"
Expand Down Expand Up @@ -358,40 +364,13 @@ useBreadcrumbs([
</span>
</div>

<div class="col-span-12 md:col-span-4">
<label for="country">{{ $t("form.country") }} *</label>
<select
id="country"
v-model="state.billingAddress.countryId"
name="country"
class="appearance-none relative block w-full px-3 py-2 border placeholder-gray-500 text-gray-900 rounded-md focus:outline-none focus:ring-indigo-500 focus:z-10 sm:text-sm"
:class="[
$v.billingAddress.countryId.$error
? 'border-red-600 focus:border-red-600'
: 'border-gray-300 focus:border-indigo-500',
]"
:disabled="loading"
data-testid="registration-country-select"
@blur="$v.billingAddress.countryId.$touch()"
>
<option disabled selected value="">
{{ $t("form.chooseCountry") }}
</option>
<option
v-for="country in getCountries"
:key="country.id"
:value="country.id"
>
{{ country.name }}
</option>
</select>
<span
v-if="$v.salutationId.$error"
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
>
{{ $v.salutationId.$errors[0].$message }}
</span>
</div>
<SharedCountryStateInput
v-model:countryId="state.billingAddress.countryId"
v-model:stateId="state.billingAddress.countryStateId"
:country-id-validation="$v.billingAddress.countryId"
:state-id-validation="$v.billingAddress.countryStateId"
class="col-span-12 md:col-span-4"
/>
</div>
<div class="mb-5 text-right">
<button
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<script lang="ts" setup>
const emit = defineEmits<{
(e: "update:countryId", value: string): void;
(e: "update:stateId", value: string): void;
}>();
const props = withDefaults(
defineProps<{
countryId: string;
stateId: string | null;
countryIdValidation?: object | null;
stateIdValidation?: object | null;
}>(),
{
countryId: "",
stateId: "",
countryIdValidation: null,
stateIdValidation: null,
}
);
const { countryId, stateId } = useVModels(props, emit);
const { getCountries, getStatesForCountry } = useCountries();
const states = computed(() => {
return getStatesForCountry(countryId.value);
});
function onCountrySelectChanged() {
stateId.value = "";
}
</script>

<template>
<div class="flex gap-6">
<div class="w-full">
<label for="country" class="block text-sm font-medium text-gray-700">{{
$t("form.country")
}}</label>
<select
id="country"
v-model="countryId"
required
name="country"
autocomplete="country-name"
class="mt-1 block w-full p-2.5 border border-gray-300 text-gray-900 text-sm rounded-md shadow-sm focus:ring-brand-light focus:border-brand-light"
data-testid="country-select"
@change="onCountrySelectChanged"
@blur="countryIdValidation && countryIdValidation.$touch()"
>
<option disabled selected value="">
{{ $t("form.chooseCountry") }}
</option>
<option
v-for="country in getCountries"
:key="country.id"
:value="country.id"
>
{{ country.name }}
</option>
</select>
<span
v-if="countryIdValidation && countryIdValidation.$error"
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
>
{{ countryIdValidation.$errors[0].$message }}
</span>
</div>
<div v-if="states && states.length" class="w-full">
<label for="state" class="block text-sm font-medium text-gray-700">{{
$t("form.state")
}}</label>
<select
id="state"
v-model="stateId"
required
name="state"
autocomplete="state-name"
class="mt-1 block w-full p-2.5 border border-gray-300 text-gray-900 text-sm rounded-md shadow-sm focus:ring-brand-light focus:border-brand-light"
data-testid="checkout-pi-state-input"
@blur="stateIdValidation && stateIdValidation.$touch()"
>
<option disabled selected value="">
{{ $t("form.chooseState") }}
</option>

<option v-for="state in states" :key="state.id" :value="state.id">
{{ state.name }}
</option>
</select>
<span
v-if="stateIdValidation && stateIdValidation.$error"
class="pt-1 text-sm text-red-600 focus:ring-brand-primary border-gray-300"
>
{{ stateIdValidation.$errors[0].$message }}
</span>
</div>
</div>
</template>
4 changes: 3 additions & 1 deletion templates/vue-demo-store/i18n/de-DE/form.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"save": "Speichern",
"submit": "Senden",
"promoCodePlaceholder": "Promo-Code eingeben",
"searchPlaceholder": "Produkte suchen"
"searchPlaceholder": "Produkte suchen",
"chooseState": "Wählen Sie den Staat",
"state": "Zustand"
}
}
4 changes: 3 additions & 1 deletion templates/vue-demo-store/i18n/en-GB/form.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"save": "Save",
"submit": "Submit",
"promoCodePlaceholder": "Enter promo code",
"searchPlaceholder": "Search products"
"searchPlaceholder": "Search products",
"chooseState": "Choose state",
"state": "State"
}
}
4 changes: 3 additions & 1 deletion templates/vue-demo-store/i18n/pl-PL/form.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
"save": "Zapisz",
"submit": "Zatwierdź",
"promoCodePlaceholder": "Wpisz kod promocyjny",
"searchPlaceholder": "Szukaj produktów"
"searchPlaceholder": "Szukaj produktów",
"chooseState": "Wybierz stan",
"state": "Stan"
}
}
Loading

2 comments on commit 14d97c5

@vercel
Copy link

@vercel vercel bot commented on 14d97c5 Jul 3, 2023

Choose a reason for hiding this comment

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

Successfully deployed to the following URLs:

frontends-demo – ./templates/vue-demo-store

frontends-demo-git-main-shopware-frontends.vercel.app
frontends-demo-shopware-frontends.vercel.app
frontends-demo.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 14d97c5 Jul 3, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.