Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: read only checkout shipping address #733

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
229d734
feat: read-only checkout shipping address
csandru-plenty Oct 10, 2024
064c88d
Merge branch 'main' into feat/read-only-checkout-shipping-address
csandru-plenty Oct 10, 2024
e48824a
Merge branch 'main' into feat/read-only-checkout-shipping-address
csandru-plenty Oct 10, 2024
f1ab104
Merge branch 'main' into feat/read-only-checkout-shipping-address
csandru-plenty Oct 11, 2024
6103ad5
feat: read-only checkout shipping address
csandru-plenty Oct 11, 2024
8d86241
feat: read only checkout shipping address
csandru-plenty Oct 14, 2024
644a8ef
Merge branch 'main' into feat/read-only-checkout-shipping-address
csandru-plenty Oct 14, 2024
3f7191a
Merge branch 'main' into feat/read-only-checkout-shipping-address
csandru-plenty Oct 15, 2024
3951691
Merge branch 'main' into feat/read-only-checkout-shipping-address
csandru-plenty Oct 16, 2024
fe3ea71
feat: read only checkout shipping address
csandru-plenty Oct 16, 2024
033b899
Merge branch 'main' into feat/read-only-checkout-shipping-address
csandru-plenty Oct 16, 2024
7217b41
Merge branch 'main' into feat/read-only-checkout-shipping-address
csandru-plenty Oct 18, 2024
9a281ad
feat: read only checkout shipping address
csandru-plenty Oct 18, 2024
c8dc278
feat: read only checkout shipping address
csandru-plenty Oct 18, 2024
988c01a
feat: read only checkout shipping address
csandru-plenty Oct 18, 2024
9e51522
feat: read only checkout shipping address
csandru-plenty Oct 18, 2024
4f2c0a4
feat: read only checkout shipping address
csandru-plenty Oct 18, 2024
45a1ed3
Merge branch 'main' into feat/read-only-checkout-shipping-address
csandru-plenty Oct 18, 2024
147806a
feat: read only checkout shipping address
csandru-plenty Oct 21, 2024
d280f8d
feat: read only checkout shipping address
csandru-plenty Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@
<ErrorMessage as="span" name="country" class="flex text-negative-700 text-sm mt-2" />
</label>

<label class="flex items-center gap-2">
<label v-if="!disabledShippingAsBilling" class="flex items-center gap-2">
<SfCheckbox data-testid="use-shipping-as-billing" v-model="shippingAsBilling" />
<span class="cursor-pointer select-none">{{ $t('form.useAsBillingLabel') }}</span>
</label>
Expand All @@ -148,7 +148,7 @@ const { address, addAddress = false } = defineProps<AddressFormProps>();

const { isGuest } = useCustomer();
const { data: countries } = useActiveShippingCountries();
const { shippingAsBilling } = useShippingAsBilling();
const { disabled: disabledShippingAsBilling, shippingAsBilling } = useShippingAsBilling();
const { addresses: shippingAddresses } = useAddressStore(AddressType.Shipping);
const { addresses: billingAddresses } = useAddressStore(AddressType.Billing);
const { set: setShippingAddress } = useCheckoutAddress(AddressType.Shipping);
Expand Down Expand Up @@ -184,7 +184,7 @@ if (!addAddress) {
}

const handleSaveShippingAsBilling = async (shippingAddressForm: Address) => {
if (shippingAsBilling.value) {
if (!disabledShippingAsBilling.value && shippingAsBilling.value) {
billingAddressToSave.value = isGuest.value
? (shippingAddresses.value[0] as Address)
: (shippingAddressForm as Address);
Expand Down Expand Up @@ -214,7 +214,7 @@ const handleShippingPrimaryAddress = async () => {
};

const handleBillingPrimaryAddress = async () => {
if (shippingAsBilling.value && billingAddresses.value.length > 0) {
if (!disabledShippingAsBilling.value && shippingAsBilling.value && billingAddresses.value.length > 0) {
await setBillingAddress(
addAddress || isGuest.value
? (billingAddresses.value[0] as Address)
Expand Down
4 changes: 3 additions & 1 deletion apps/web/composables/useAddressForm/useAddressForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export const useAddressForm = (type: AddressType) => {
const { data: customerData, getSession } = useCustomer();
const { data: cartData } = useCart();
const { send } = useNotification();
const { disabled: disabledShippingAsBilling } = useShippingAsBilling();

const state = useState('useAddressForm' + type, () => ({
isLoading: false,
Expand Down Expand Up @@ -76,6 +77,7 @@ export const useAddressForm = (type: AddressType) => {

const notifyIfShippingChanged = () => {
if (
!disabledShippingAsBilling.value &&
selectedMethod.value &&
shippingProviderGetters.getShippingProfileId(cartData.value).toString() !==
shippingProviderGetters.getParcelServicePresetId(selectedMethod.value)
Expand All @@ -86,8 +88,8 @@ export const useAddressForm = (type: AddressType) => {

const notifyIfBillingChanged = () => {
if (cartData.value.methodOfPaymentId !== customerData.value.basket.methodOfPaymentId) {
send({ message: $i18n.t('billing.methodChanged'), type: 'warning' });
cartData.value.methodOfPaymentId = customerData.value.basket.methodOfPaymentId;
if (!disabledShippingAsBilling.value) send({ message: $i18n.t('billing.methodChanged'), type: 'warning' });
}
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { AddressType } from '@plentymarkets/shop-api';
import { ApiError } from '@plentymarkets/shop-api';

export const useFetchAdddress = (type: AddressType) => {
const state = useState('useFetchAdddress' + type, () => ({
export const useFetchAddress = (type: AddressType) => {
const state = useState('useFetchAddress' + type, () => ({
loading: false,
}));

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
export const useShippingAsBilling = () => {
const route = useRoute();
const localePath = useLocalePath();

const state = useState('useShippingAsBilling', () => ({
disabled: route.fullPath.includes(localePath(paths.readonlyCheckout)),
shippingAsBilling: false,
}));

Expand Down
4 changes: 2 additions & 2 deletions apps/web/pages/checkout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -141,12 +141,12 @@ const {
} = useCheckoutPagePaymentAndShipping();

onNuxtReady(async () => {
useFetchAdddress(AddressType.Shipping)
useFetchAddress(AddressType.Shipping)
.fetchServer()
.then(() => persistShippingAddress())
.catch((error) => useHandleError(error));

useFetchAdddress(AddressType.Billing)
useFetchAddress(AddressType.Billing)
.fetchServer()
.then(() => persistBillingAddress())
.catch((error) => useHandleError(error));
Expand Down
186 changes: 108 additions & 78 deletions apps/web/pages/readonly-checkout.vue
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
<template>
<NuxtLayout
name="checkout"
page-type="static"
:back-label-desktop="t('backToCart')"
:back-label-mobile="t('back')"
:heading="t('checkout')"
>
<div v-if="cart" class="md:grid md:grid-cols-12 md:gap-x-6">
<div class="col-span-7 mb-10 md:mb-0">
<UiDivider class="w-screen md:w-auto -mx-4 md:mx-0" />
<UiDivider :class="dividerClass" />
<ContactInformation disabled />
<UiDivider class="w-screen md:w-auto -mx-4 md:mx-0" />
<AddressContainer disabled :type="AddressType.Shipping" :key="0" id="shipping-address" />
<UiDivider class="w-screen md:w-auto -mx-4 md:mx-0" />
<AddressContainer disabled :type="AddressType.Billing" :key="1" id="billing-address" />
<UiDivider class-name="w-screen md:w-auto -mx-4 md:mx-0" />
<UiDivider :class="dividerClass" />
<AddressContainer :disabled="false" :type="AddressType.Shipping" :key="0" id="shipping-address" />
<UiDivider :class="dividerClass" />
<AddressContainer :disabled="true" :type="AddressType.Billing" :key="1" id="billing-address" />
<UiDivider :class="dividerClass" />
<div class="relative">
<ShippingMethod :shipping-methods="shippingMethods" disabled />
<UiDivider class="w-screen md:w-auto -mx-4 md:mx-0" />
<UiDivider :class="dividerClass" />
<CheckoutPayment :payment-methods="paymentMethods" disabled />
</div>
<UiDivider class="w-screen md:w-auto -mx-4 md:mx-0 mb-10" />
<UiDivider :class="`${dividerClass} mb-10`" />
<div class="text-sm mx-4 md:pb-0">
<CheckoutGeneralTerms />
</div>
Expand All @@ -34,18 +35,12 @@
<UiButton
type="submit"
@click="order"
:disabled="createOrderLoading || cartLoading || executeOrderLoading"
:disabled="interactionDisabled"
size="lg"
class="w-full mb-4 md:mb-0 cursor-pointer"
>
<SfLoaderCircular
v-if="createOrderLoading || cartLoading || executeOrderLoading"
class="flex justify-center items-center"
size="sm"
/>
<span v-else>
{{ t('buy') }}
</span>
<SfLoaderCircular v-if="interactionDisabled" class="flex justify-center items-center" size="sm" />
<template v-else>{{ t('buy') }}</template>
</UiButton>
</OrderSummary>
</div>
Expand All @@ -55,23 +50,12 @@
</template>

<script lang="ts" setup>
import { AddressType, type PaymentMethod, orderGetters, shippingProviderGetters } from '@plentymarkets/shop-api';
import { AddressType, ApiError, Order, orderGetters, shippingProviderGetters } from '@plentymarkets/shop-api';
import { SfLoaderCircular } from '@storefront-ui/vue';

definePageMeta({
pageType: 'static',
});

const ID_CHECKBOX = '#terms-checkbox';

const { getSession } = useCustomer();
const { isAuthorized } = useCustomer();
const { data: cart, clearCartItems, loading: cartLoading } = useCart();
const { data: billingAddresses, getAddresses: getBillingAddresses } = useAddress(AddressType.Billing);
const {
data: shippingAddresses,
getAddresses: getShippingAddresses,
saveAddress: saveShippingAddress,
} = useAddress(AddressType.Shipping);
const { data: shippingMethodData, getShippingMethods } = useCartShippingMethods();
const { data: paymentMethodData, fetchPaymentMethods, savePaymentMethod } = usePaymentMethods();
const { loading: createOrderLoading, createOrder } = useMakeOrder();
Expand All @@ -82,72 +66,118 @@ const { send } = useNotification();
const { t } = useI18n();
const localePath = useLocalePath();
const { checkboxValue: termsAccepted, setShowErrors } = useAgreementCheckbox('checkoutGeneralTerms');
const { persistShippingAddress, persistBillingAddress } = useCheckout();
const { data: billingAddresses, getAddresses: getBillingAddresses } = useAddress(AddressType.Billing);
const { data: shippingAddresses, getAddresses: getShippingAddresses, saveAddress } = useAddress(AddressType.Shipping);
const { getActiveShippingCountries } = useActiveShippingCountries();

const loadAddresses = async () => {
await getBillingAddresses();
await getShippingAddresses();
const cartIsEmpty = computed(() => !cart.value?.items || cart.value?.items?.length === 0);
const shippingMethods = computed(() => shippingProviderGetters.getShippingProviders(shippingMethodData.value));
const paymentMethods = computed(() => paymentMethodData.value);
const interactionDisabled = computed(() => createOrderLoading.value || cartLoading.value || executeOrderLoading.value);
const dividerClass = 'w-screen md:w-auto -mx-4 md:mx-0';

if (shippingAddresses.value.length === 0) {
billingAddresses.value.length > 0
? await saveShippingAddress(billingAddresses.value[0])
: navigateTo(localePath(paths.cart));
}
const redirectToCart = () => {
send({ type: 'neutral', message: t('emptyCart') });
navigateTo(localePath(paths.cart));
};

await useCheckoutAddress(AddressType.Shipping).set(shippingAddresses.value[0], true);
await useCheckoutAddress(AddressType.Billing).set(billingAddresses.value[0], true);
await getShippingMethods();
const fetchShippingAndPaymentMethods = async () => {
try {
await Promise.all([
getActiveShippingCountries(),
getShippingMethods(),
fetchPaymentMethods().then(
async () =>
await savePaymentMethod(paymentMethodData?.value?.list?.find((method) => method.name === 'PayPal')?.id || 0),
),
]);
} catch (error) {
useHandleError(error as ApiError);
}
};

const redirectBack = () => {
if (cart.value.items?.length === 0) {
send({
type: 'neutral',
message: t('emptyCart'),
});
const handleAuthUserInit = async () => {
try {
await useFetchAddress(AddressType.Shipping).fetchServer();
await persistShippingAddress();

await useFetchAddress(AddressType.Billing).fetchServer();
await persistBillingAddress();

navigateTo(localePath(paths.cart));
return true;
await fetchShippingAndPaymentMethods();
} catch (error) {
useHandleError(error as ApiError);
}
return false;
};

await getSession();
redirectBack();
await loadAddresses();
await getShippingMethods();
await fetchPaymentMethods();
await savePaymentMethod(
paymentMethodData?.value?.list?.find((method: PaymentMethod) => method.name === 'PayPal')?.id ?? 0,
);
const setClientCheckoutAddress = async () => {
try {
await useCheckoutAddress(AddressType.Shipping).set(shippingAddresses.value[0], true);
await useCheckoutAddress(AddressType.Billing).set(billingAddresses.value[0], true);
} catch (error) {
useHandleError(error as ApiError);
}
};

const shippingMethods = computed(() => shippingProviderGetters.getShippingProviders(shippingMethodData.value));
const paymentMethods = computed(() => paymentMethodData.value);
const handleGuestUserInit = async () => {
try {
await getShippingAddresses();
await getBillingAddresses();

const validateTerms = (): boolean => {
if (!termsAccepted.value) {
scrollToHTMLObject(ID_CHECKBOX);
setShowErrors(true);
return false;
if (billingAddresses.value.length === 0) navigateTo(localePath(paths.cart));
if (shippingAddresses.value.length === 0) await saveAddress(billingAddresses.value[0], true);

await setClientCheckoutAddress();
await fetchShippingAndPaymentMethods();
} catch (error) {
useHandleError(error as ApiError);
}
};

onNuxtReady(async () => {
if (cartIsEmpty.value) {
redirectToCart();
return;
}
return true;

isAuthorized.value ? await handleAuthUserInit() : await handleGuestUserInit();
});

const scrollToTerms = () => {
scrollToHTMLObject(ID_CHECKBOX);
setShowErrors(true);
};

const order = async () => {
if (redirectBack() || !validateTerms()) return;
if (interactionDisabled.value) return;

const data = await createOrder({
paymentId: cart.value.methodOfPaymentId,
shippingPrivacyHintAccepted: shippingPrivacyAgreement.value,
});
if (cartIsEmpty.value) {
redirectToCart();
return;
}

await executeOrder({
mode: 'paypal',
plentyOrderId: Number.parseInt(orderGetters.getId(data)),
paypalTransactionId: route?.query?.orderId?.toString() ?? '',
});
if (!termsAccepted.value) {
scrollToTerms();
return;
}

clearCartItems();
try {
const data: Order = await createOrder({
paymentId: cart.value.methodOfPaymentId,
shippingPrivacyHintAccepted: shippingPrivacyAgreement.value,
});

if (data?.order?.id) navigateTo(localePath('/confirmation/' + data.order.id + '/' + data.order.accessKey));
await executeOrder({
mode: 'paypal',
plentyOrderId: Number.parseInt(orderGetters.getId(data)),
paypalTransactionId: route?.query?.orderId?.toString() ?? '',
});

clearCartItems();
if (data?.order?.id) navigateTo(localePath('/confirmation/' + data.order.id + '/' + data.order.accessKey));
} catch (error) {
useHandleError(error as ApiError);
}
};
</script>
1 change: 1 addition & 0 deletions docs/changelog/changelog_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

### New

- You can now modify Shipping Addresses during the read-only checkout process.
- Added a new request header for configId and added no cache to environment variables.
- Implement new notification design

Expand Down
Loading