Skip to content

Commit

Permalink
Merge branch 'release-candidate' into release
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonLantukh committed Apr 8, 2024
2 parents a1b0c58 + b928ce8 commit 4767769
Show file tree
Hide file tree
Showing 19 changed files with 99 additions and 44 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## [6.1.0](https://github.com/jwplayer/ott-web-app/compare/v6.0.0...v6.1.0) (2024-04-05)


### Features

* **project:** reload site on update ([d4c851b](https://github.com/jwplayer/ott-web-app/commit/d4c851bdccc155b4953c9948ffdab86c2bfacc5e))


### Bug Fixes

* **a11y:** focus lost when submitting a form ([9001a21](https://github.com/jwplayer/ott-web-app/commit/9001a21c9a4b0bd35db6c182e2f46b4480983dc8))
* **i18n:** add missing translations ([961bcd1](https://github.com/jwplayer/ott-web-app/commit/961bcd140b68c2eb8e128b898c23caa8ddcfd744))
* **payment:** missing feedback when submitting coupon ([5097e60](https://github.com/jwplayer/ott-web-app/commit/5097e607090bcdf098335dd39a6aa6ef82250ad7))
* **user:** redirect when no integration is configured ([88ce77c](https://github.com/jwplayer/ott-web-app/commit/88ce77c587883117eb6af4dec40481ddcbfe01b2))

## [6.0.0](https://github.com/jwplayer/ott-web-app/compare/v5.1.1...v6.0.0) (2024-03-25)


Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@jwp/ott",
"version": "6.0.0",
"version": "6.1.0",
"private": true,
"license": "Apache-2.0",
"repository": "https://github.com/jwplayer/ott-web-app.git",
Expand Down
32 changes: 11 additions & 21 deletions packages/common/src/controllers/CheckoutController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,32 +91,22 @@ export default class CheckoutController {
};

updateOrder = async (order: Order, paymentMethodId?: number, couponCode?: string | null): Promise<void> => {
let response;

try {
response = await this.checkoutService.updateOrder({ order, paymentMethodId, couponCode });
} catch (error: unknown) {
// TODO: we currently (falsely) assume that the only error caught is because the coupon is not valid, but there
// could be a network failure as well (JWPCheckoutService)
throw new FormValidationError({ couponCode: [i18next.t('account:checkout.coupon_not_valid')] });
}
const response = await this.checkoutService.updateOrder({ order, paymentMethodId, couponCode });

if (response.errors.length > 0) {
// clear the order when the order doesn't exist on the server
if (response.errors[0].includes(`Order with ${order.id} not found`)) {
useCheckoutStore.getState().setOrder(null);
if (response.responseData.order) {
useCheckoutStore.getState().setOrder(response.responseData?.order);
}

// TODO: this handles the `Coupon ${couponCode} not found` message (CleengCheckoutService)
if (response.errors[0].includes(`not found`)) {
throw new FormValidationError({ couponCode: [i18next.t('account:checkout.coupon_not_valid')] });
} catch (error: unknown) {
if (error instanceof Error) {
if (error.message === 'Order not found') {
useCheckoutStore.getState().setOrder(null);
} else if (error.message === 'Invalid coupon code') {
throw new FormValidationError({ couponCode: [i18next.t('account:checkout.coupon_not_valid')] });
}
}

throw new FormValidationError({ form: response.errors });
}

if (response.responseData.order) {
useCheckoutStore.getState().setOrder(response.responseData?.order);
throw new FormValidationError({ form: [i18next.t('error:unknown_error')] });
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,13 @@ import type {
PaymentWithPayPal,
SwitchSubscription,
UpdateOrder,
UpdateOrderResponse,
UpdatePaymentWithPayPal,
} from '../../../../types/checkout';
import CheckoutService from '../CheckoutService';
import { GET_CUSTOMER_IP } from '../../../modules/types';
import type { GetCustomerIP } from '../../../../types/get-customer-ip';
import type { ServiceResponse } from '../../../../types/service';

import CleengService from './CleengService';

Expand Down Expand Up @@ -83,7 +85,21 @@ export default class CleengCheckoutService extends CheckoutService {
};

updateOrder: UpdateOrder = async ({ order, ...payload }) => {
return this.cleengService.patch(`/orders/${order.id}`, JSON.stringify(payload), { authenticate: true });
const response = await this.cleengService.patch<ServiceResponse<UpdateOrderResponse>>(`/orders/${order.id}`, JSON.stringify(payload), {
authenticate: true,
});

if (response.errors.length) {
if (response.errors[0].includes(`Order with ${order.id} not found`)) {
throw new Error('Order not found');
}

if (response.errors[0].includes(`Coupon ${payload.couponCode} not found`)) {
throw new Error('Invalid coupon code');
}
}

return response;
};

getPaymentMethods: GetPaymentMethods = async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export default class CleengService {
return await resp.json();
} catch (error: unknown) {
return {
errors: Array.of(error as string),
errors: Array.of(error instanceof Error ? error.message : String(error)),
};
}
};
Expand Down
35 changes: 24 additions & 11 deletions packages/common/src/services/integrations/jwp/JWPCheckoutService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import type {
} from '../../../../types/checkout';
import CheckoutService from '../CheckoutService';
import type { ServiceResponse } from '../../../../types/service';
import { isCommonError } from '../../../utils/api';

@injectable()
export default class JWPCheckoutService extends CheckoutService {
Expand Down Expand Up @@ -71,6 +72,7 @@ export default class JWPCheckoutService extends CheckoutService {
totalPrice: payload.offer.customerPriceInclTax,
priceBreakdown: {
offerPrice: payload.offer.customerPriceInclTax,
// @TODO is this correct?
discountAmount: payload.offer.customerPriceInclTax,
discountedPrice: payload.offer.customerPriceInclTax,
paymentMethodFee: 0,
Expand Down Expand Up @@ -179,26 +181,37 @@ export default class JWPCheckoutService extends CheckoutService {
voucherCode: `${couponCode}`,
accessFeeId: order.id,
});
order.discount = {
applied: true,
type: 'coupon',
periods: response.data.discount_duration,

const discountAmount = order.totalPrice - response.data.amount;
const updatedOrder: Order = {
...order,
totalPrice: response.data.amount,
priceBreakdown: {
...order.priceBreakdown,
discountAmount,
discountedPrice: discountAmount,
},
discount: {
applied: true,
type: 'coupon',
periods: response.data.discount_duration,
},
};

const discountedAmount = order.totalPrice - response.data.amount;
order.totalPrice = response.data.amount;
order.priceBreakdown.discountAmount = discountedAmount;
order.priceBreakdown.discountedPrice = discountedAmount;
return {
errors: [],
responseData: {
message: 'successfully updated',
order: order,
order: updatedOrder,
success: true,
},
};
} catch {
throw new Error('Invalid coupon code');
} catch (error: unknown) {
if (isCommonError(error) && error.response.data.message === 'Voucher not found') {
throw new Error('Invalid coupon code');
}

throw new Error('An unknown error occurred');
}
};

Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/stores/AccountStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type AccountStore = {
};

export const useAccountStore = createStore<AccountStore>('AccountStore', (set, get) => ({
loading: true,
loading: false,
user: null,
subscription: null,
transactions: null,
Expand Down
2 changes: 1 addition & 1 deletion packages/ui-react/src/components/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ const Button: React.FC<Props> = ({
}

return (
<button className={buttonClassName(active)} onClick={onClick} type={type} disabled={disabled} aria-disabled={disabled} {...rest}>
<button className={buttonClassName(active)} onClick={disabled ? undefined : onClick} type={type} aria-disabled={disabled} {...rest}>
{content}
</button>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type Props = {
onCouponInputChange: React.ChangeEventHandler<HTMLInputElement>;
onRedeemCouponButtonClick: () => void;
onCloseCouponFormClick: () => void;
error?: string;
couponFormOpen: boolean;
couponFormError?: string;
couponFormApplied?: boolean;
Expand All @@ -45,6 +46,7 @@ const CheckoutForm: React.FC<Props> = ({
offerType,
onBackButtonClick,
onPaymentMethodChange,
error,
couponFormOpen,
couponInputValue,
couponFormError,
Expand Down Expand Up @@ -89,6 +91,7 @@ const CheckoutForm: React.FC<Props> = ({
const orderTitle = offerType === 'svod' ? (offer.period === 'month' ? t('checkout.monthly') : t('checkout.yearly')) : offer.offerTitle;
return (
<div>
{error ? <FormFeedback variant="error">{error}</FormFeedback> : null}
<h1 className={styles.title}>{t('checkout.payment_method')}</h1>
<div className={styles.order}>
<div className={styles.orderInfo}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ describe('<LoginForm>', () => {

await waitForWithFakeTimers();

expect(getByRole('button', { name: 'login.sign_in' })).toBeDisabled();
expect(getByRole('button', { name: 'login.sign_in' })).toHaveAttribute('aria-disabled', 'true');
});

test('calls the onSubmit callback when the form gets submitted', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ const Checkout = () => {
handleSubmit,
} = useForm({
initialValues: { couponCode: '', paymentMethodId: paymentMethods?.[0]?.id?.toString() || '' },
onSubmit: async ({ couponCode, paymentMethodId }) => {
onSubmit: ({ couponCode, paymentMethodId }) => {
setShowCouponCodeSuccess(false);

return await updateOrder.mutateAsync({ couponCode, paymentMethodId: parseInt(paymentMethodId) });
return updateOrder.mutateAsync({ couponCode, paymentMethodId: parseInt(paymentMethodId) });
},
onSubmitSuccess: ({ couponCode }): void => setShowCouponCodeSuccess(!!couponCode),
onSubmitError: ({ error }) => {
Expand Down Expand Up @@ -117,6 +117,7 @@ const Checkout = () => {
order={order}
offer={selectedOffer}
offerType={offerType}
error={errors.form}
onBackButtonClick={backButtonClickHandler}
paymentMethods={paymentMethods}
paymentMethodId={paymentMethodId}
Expand Down
2 changes: 1 addition & 1 deletion platforms/web/public/locales/en/account.json
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@
"password_helper_text": "Use a minimum of 8 characters (case sensitive) with at least one number",
"password_strength": {
"fair": "Fair",
"invalid": "Invalid",
"invalid": "",
"strong": "Strong",
"very_strong": "Very strong",
"weak": "Weak"
Expand Down
12 changes: 11 additions & 1 deletion platforms/web/public/locales/es/account.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
"credit_card": "Tarjeta de crédito",
"credit_card_name": "Nombre de la tarjeta de crédito",
"days_trial_one": "Se te cobrará mañana.",
"days_trial_many": "Se te cobrará después de {{count}} días.",
"days_trial_other": "Se te cobrará después de {{count}} días.",
"discount_period_one": "Durante el primer {{period}}",
"discount_period_many": "Durante los primeros {{count}} {{period}}",
"discount_period_other": "Durante los primeros {{count}} {{period}}",
"free_trial_discount": "Prueba gratuita",
"go_back_to_checkout": "Volver a la página de pago",
Expand All @@ -37,6 +39,7 @@
"paypal": "PayPal",
"paypal_instructions": "Al hacer clic en 'Continuar', serás redirigido al sitio de PayPal.",
"periods_trial_one": "Se te cobrará después de un {{period}}.",
"periods_trial_many": "Se te cobrará después de {{count}} {{period}}.",
"periods_trial_other": "Se te cobrará después de {{count}} {{period}}.",
"redeem_coupon": "Canjear cupón",
"save": "Guardar",
Expand All @@ -57,8 +60,10 @@
"benefits": {
"cancel_anytime": "Cancele en cualquier momento",
"first_days_free_one": "Primer día gratis",
"first_days_free_many": "Primeros {{count}} días gratis",
"first_days_free_other": "Primeros {{count}} días gratis",
"first_periods_free_one": "Primer {{period}} gratis",
"first_periods_free_many": "Primeros {{count}} {{period}} gratis",
"first_periods_free_other": "Primeros {{count}} {{period}} gratis",
"watch_on_all_devices": "Ver en todos los dispositivos"
},
Expand All @@ -71,6 +76,7 @@
"subscription": "Suscripción",
"title": "Elige un plan",
"tvod_access_one": "Acceso de {{count}} {{period}}",
"tvod_access_many": "Acceso de {{count}} {{period}}",
"tvod_access_other": "Acceso de {{count}} {{period}}",
"watch_this_on_platform": "Ver esto en {{siteName}}",
"yearly": "Anual",
Expand Down Expand Up @@ -106,14 +112,18 @@
},
"periods": {
"day_one": "día",
"day_many": "días",
"day_other": "días",
"month": "mensual",
"month_one": "mes",
"month_many": "meses",
"month_other": "meses",
"week_one": "semana",
"week_many": "semanas",
"week_other": "semanas",
"year": "anual",
"year_one": "año",
"year_many": "años",
"year_other": "años"
},
"personal_details": {
Expand Down Expand Up @@ -148,7 +158,7 @@
"password_helper_text": "Utiliza un mínimo de 8 caracteres (distingue entre mayúsculas y minúsculas) con al menos un número",
"password_strength": {
"fair": "Regular",
"invalid": "Invalida",
"invalid": "",
"strong": "Fuerte",
"very_strong": "Muy fuerte",
"weak": "Débil"
Expand Down
1 change: 1 addition & 0 deletions platforms/web/public/locales/es/search.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
"tip_two": "Hacer términos de búsqueda más generales",
"tip_three": "Prueba con diferentes términos de búsqueda",
"title_one": "{{count}} resultado para \"{{query}}\"",
"title_many": "{{count}} resultados para \"{{query}}\"",
"title_other": "{{count}} resultados para \"{{query}}\""
}
1 change: 1 addition & 0 deletions platforms/web/public/locales/es/user.json
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
"expiry_date": "Fecha de vencimiento",
"granted_subscription": "Suscripción otorgada",
"hidden_transactions_one": "Una transacción más",
"hidden_transactions_many": "{{count}} transacciones más",
"hidden_transactions_other": "{{count}} transacciones más",
"longer_than_usual": "El pago está tardando más de lo habitual. Por favor, inténtelo de nuevo más tarde.",
"monthly_subscription": "Suscripción mensual",
Expand Down
1 change: 1 addition & 0 deletions platforms/web/public/locales/es/video.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"sign_up_to_start_watching": "¡Regístrate para comenzar a ver!",
"start_watching": "Comenzar a ver",
"total_episodes_one": "{{count}} episodio",
"total_episodes_many": "{{count}} episodios",
"total_episodes_other": "{{count}} episodios",
"trailer": "Tráiler",
"watch_trailer": "Ver el tráiler"
Expand Down
5 changes: 4 additions & 1 deletion platforms/web/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ if (rootElement) {
console.info('Application - rootElement not found');
}

registerSW();
const refresh = registerSW({
immediate: true,
onNeedRefresh: () => refresh(true),
});
2 changes: 1 addition & 1 deletion platforms/web/test-e2e/tests/live_channel_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ Scenario('I can select an upcoming program on the same channel', async ({ I }) =
I.dontSee('LIVE', locate('div').inside(videoDetailsLocator));
I.see('On Channel 1', locate('div').inside(videoDetailsLocator));

I.seeElement(locate('button[disabled]').withText('Start watching'));
I.seeElement(locate('button[aria-disabled]').withText('Start watching'));

I.seeElement(channel1LiveProgramLocator);
await isLiveProgram(I, channel1LiveProgramLocator, 'channel 1');
Expand Down
1 change: 1 addition & 0 deletions platforms/web/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ export default ({ mode, command }: ConfigEnv): UserConfigExport => {
StylelintPlugin(),
svgr(),
VitePWA({
registerType: 'autoUpdate',
manifestFilename: 'manifest.json',
manifest: {
name: app.name,
Expand Down

0 comments on commit 4767769

Please sign in to comment.