Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"mini-css-extract-plugin": "^2.6.0",
"moment": "^2.22.2",
"moment-timezone": "^0.5.21",
"openstack-uicore-foundation": "4.2.14",
"openstack-uicore-foundation": "4.2.31",
"optimize-css-assets-webpack-plugin": "^6.0.1",
"path": "^0.12.7",
"react": "^16.8.4",
Expand Down Expand Up @@ -129,7 +129,7 @@
"lodash": "^4.17.14",
"moment": "^2.22.2",
"moment-timezone": "^0.5.21",
"openstack-uicore-foundation": "4.2.9",
"openstack-uicore-foundation": "4.2.31",
"react": "^16.8.4",
"react-bootstrap": "^0.31.5",
"react-datetime": "^2.16.2",
Expand Down
4 changes: 0 additions & 4 deletions src/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ export const GO_TO_LOGIN = 'GO_TO_LOGIN';
export const GET_MY_INVITATION = 'GET_MY_INVITATION';
export const CLEAR_MY_INVITATION = 'CLEAR_MY_INVITATION';
export const CLEAR_WIDGET_STATE = 'CLEAR_WIDGET_STATE';
export const UPDATE_CLOCK = 'UPDATE_CLOCK';
export const LOAD_PROFILE_DATA = 'LOAD_PROFILE_DATA';

export const SET_CURRENT_PROMO_CODE = 'SET_CURRENT_PROMO_CODE';
Expand Down Expand Up @@ -525,6 +524,3 @@ export const getMyInvitation = (summitId) => async (dispatch, getState, { apiBas
}
}

export const updateClock = (timestamp) => (dispatch) => {
dispatch(createAction(UPDATE_CLOCK)({ timestamp }));
};
13 changes: 10 additions & 3 deletions src/components/purchase-complete/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@
* limitations under the License.
**/

import React, { useEffect, useState, useMemo } from 'react';
import React, { useEffect, useState, useMemo, useCallback } from 'react';
import styles from './index.module.scss';
import { epochToMomentTimeZone } from 'openstack-uicore-foundation/lib/utils/methods';
import { useClockSelector } from 'openstack-uicore-foundation/lib/components/clock-context';
import ContentLoader from 'react-content-loader';
import { isEmptyString, ticketHasAccessLevel } from '../../utils/utils';
import { VirtualAccessLevel } from '../../utils/constants';
Expand All @@ -39,7 +40,6 @@ const PurchaseComplete = ({
goToMyOrders,
completedExtraQuestions,
summit,
nowUtc,
clearWidgetState,
closeWidget,
supportEmail,
Expand All @@ -54,7 +54,14 @@ const PurchaseComplete = ({
const [requireExtraQuestions, setRequireExtraQuestions] = useState(null);
const [extraQuestionsLoaded, setExtraQuestonsLoaded] = useState(false);
const isMultiOrder = useMemo(() => checkout?.tickets.length > 1, [checkout]);
const isActive = useMemo(() => summit.start_date <= nowUtc && summit.end_date >= nowUtc, [summit, nowUtc]);
// Re-runs every clock tick but the boolean only changes on summit
// active/inactive transitions (typically zero times per session).
const isActive = useClockSelector(
useCallback(
(nowUtc) => summit.start_date <= nowUtc && summit.end_date >= nowUtc,
[summit.start_date, summit.end_date]
)
);
const currentTicket = useMemo(
() => isMultiOrder ? checkout?.tickets.find(t => t?.owner?.email === user?.email) : checkout?.tickets.find(t => t?.owner),
[user]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,19 @@ import { Provider } from 'react-redux';
import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';

// Mock withReduxProvider as identity HOC
jest.mock('../../../utils/withReduxProvider', () => ({
withReduxProvider: (Component) => Component,
// Mock withWidgetProviders as identity HOC
jest.mock('../../../utils/withWidgetProviders', () => ({
withWidgetProviders: (Component) => Component,
__esModule: true,
default: (Component) => Component,
}));

// Stub the uicore clock-context so the selector runs once against a fixed timestamp
jest.mock('openstack-uicore-foundation/lib/components/clock-context', () => ({
useClockSelector: (selector) => selector(1000000),
ClockProvider: ({ children }) => children,
}));

// Mock action creators
const mockChangeStep = jest.fn();
const mockClearWidgetState = jest.fn();
Expand All @@ -24,7 +30,6 @@ const mockGetLoginCode = jest.fn();
const mockPasswordlessLogin = jest.fn();
const mockGoToLogin = jest.fn();
const mockGetMyInvitation = jest.fn(() => Promise.resolve());
const mockUpdateClock = jest.fn();
const mockLoadProfileData = jest.fn();
const mockApplyPromoCode = jest.fn();
const mockRemovePromoCode = jest.fn();
Expand Down Expand Up @@ -78,10 +83,6 @@ jest.mock('../../../actions', () => ({
mockGetMyInvitation(...args);
return Promise.resolve();
},
updateClock: (...args) => {
mockUpdateClock(...args);
return { type: 'NOOP' };
},
loadProfileData: (...args) => {
mockLoadProfileData(...args);
return { type: 'NOOP' };
Expand Down Expand Up @@ -117,10 +118,6 @@ jest.mock('openstack-uicore-foundation/lib/components/ajaxloader', () => {
return (props) => <div data-testid="ajax-loader" />;
});

jest.mock('openstack-uicore-foundation/lib/components/clock', () => {
return (props) => <div data-testid="clock" />;
});

jest.mock('openstack-uicore-foundation/lib/security/constants', () => ({
AUTH_ERROR_MISSING_AUTH_INFO: 'Missing Auth info',
AUTH_ERROR_MISSING_REFRESH_TOKEN: 'missing Refresh Token',
Expand Down Expand Up @@ -155,7 +152,7 @@ jest.mock('react-use', () => ({
useMeasure: () => [jest.fn(), { height: 100 }],
}));

// Import default (which is withReduxProvider(RegistrationForm) but our mock makes it identity)
// Import default (which is withWidgetProviders(RegistrationForm) but our mock makes it identity)
import RegistrationForm from '..';

const STEP_SELECT_TICKET_TYPE = 0;
Expand All @@ -181,7 +178,6 @@ const defaultReduxState = {
summitId: null,
userProfile: null,
},
nowUtc: 1000000,
promoCode: '',
promoCodeVerified: null,
promoCodeValidating: false,
Expand Down
36 changes: 24 additions & 12 deletions src/components/registration-form/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@
**/

import React, { useEffect, useState, useMemo, useCallback } from 'react';
import { connect } from "react-redux";
import { connect, shallowEqual } from "react-redux";
import PropTypes from 'prop-types';
import { withReduxProvider } from '../../utils/withReduxProvider';
import { withWidgetProviders } from '../../utils/withWidgetProviders';
import { useClockSelector } from 'openstack-uicore-foundation/lib/components/clock-context';
import { animated, config, useSpring } from "react-spring";
import { useMeasure } from "react-use";
import {
Expand All @@ -37,7 +38,6 @@ import {
removeReservedTicket,
reserveTicket,
clearWidgetState,
updateClock,
loadProfileData,
removePromoCode,
applyPromoCode,
Expand All @@ -50,7 +50,6 @@ import {
import usePromoCode from '../../hooks/usePromoCode';

import AjaxLoader from "openstack-uicore-foundation/lib/components/ajaxloader";
import Clock from "openstack-uicore-foundation/lib/components/clock";

import '../../styles/styles.scss';

Expand Down Expand Up @@ -88,6 +87,14 @@ try {
T.setTexts(require(`../../i18n/en.json`));
}

const isTicketCurrentlyAvailable = (tt, nowUtc) =>
// prepaid tickets are always available (sales windows don't apply)
tt.sub_type === TICKET_TYPE_SUBTYPE_PREPAID ||
// no sales window configured → ticket is open-ended, always available
(tt.sales_start_date === null && tt.sales_end_date === null) ||
// ticket is within its configured sales window
(nowUtc >= tt.sales_start_date && nowUtc <= tt.sales_end_date);


const RegistrationFormContent = (
{
Expand Down Expand Up @@ -144,8 +151,6 @@ const RegistrationFormContent = (
allowPromoCodes,
showCompanyInput,
companyDDLPlaceholder,
nowUtc,
updateClock,
completedExtraQuestions,
loadProfileData,
closeWidget,
Expand Down Expand Up @@ -202,7 +207,18 @@ const RegistrationFormContent = (

const { publicKey, provider } = getCurrentProvider(summitData);

const allowedTicketTypes = useMemo(() => hasTicketData ? ticketTypes.filter((tt) => tt.sub_type === TICKET_TYPE_SUBTYPE_PREPAID || (tt.sales_start_date === null && tt.sales_end_date === null) || (nowUtc >= tt.sales_start_date && nowUtc <= tt.sales_end_date)) : [], [hasTicketData, ticketTypes, nowUtc]);
// Re-runs every clock tick but only commits a new array when the filtered
// contents shift (a sale window opens/closes), so the form tree stops
// re-rendering once per second.
const allowedTicketTypes = useClockSelector(
useCallback(
(nowUtc) => hasTicketData
? ticketTypes.filter(tt => isTicketCurrentlyAvailable(tt, nowUtc))
: [],
[hasTicketData, ticketTypes]
),
shallowEqual
);

const noAvailableTickets = useMemo(() => isAuthenticated && hasTicketData && !ticketDataError && allowedTicketTypes.length === 0 && step !== STEP_COMPLETE, [isAuthenticated, hasTicketData, ticketDataError, allowedTicketTypes, step]);
const alreadyOwnedTickets = useMemo(() => isAuthenticated && hasTicketData && !ticketDataError && allowedTicketTypes.length > 0 && ownedTickets.length > 0, [isAuthenticated, hasTicketData, ticketDataError, allowedTicketTypes, ownedTickets]);
Expand Down Expand Up @@ -378,7 +394,6 @@ const RegistrationFormContent = (
return (
<div className="summit-registration-lite">
<AjaxLoader relative={true} color={'#ffffff'} show={widgetLoading || loading} size={80} />
<Clock onTick={(timestamp) => updateClock(timestamp)} timezone={summitData.time_zone_id} />

{profileData && ticketDataError && <TicketTaxesError ticketTaxesErrorMessage={ticketTaxesErrorMessage} retryTicketTaxes={() => handleGetTicketTypesAndTaxes(summitData?.id)} />}

Expand Down Expand Up @@ -518,7 +533,6 @@ const RegistrationFormContent = (
goToMyOrders={goToMyOrders}
goToExtraQuestions={goToExtraQuestions}
completedExtraQuestions={completedExtraQuestions}
nowUtc={nowUtc}
clearWidgetState={clearWidgetState}
closeWidget={closeWidget}
hasVirtualAccessLevel={hasVirtualAccessLevel}
Expand Down Expand Up @@ -552,7 +566,6 @@ const mapStateToProps = ({ registrationLiteState }) => ({
passwordlessCodeLifeTime: registrationLiteState.passwordless.otp_lifetime,
passwordlessCodeSent: registrationLiteState.passwordless.code_sent,
passwordlessCodeError: registrationLiteState.passwordless.error,
nowUtc: registrationLiteState.nowUtc,
promoCode: registrationLiteState.promoCode,
promoCodeVerified: registrationLiteState.promoCodeVerified,
promoCodeValidating: registrationLiteState.promoCodeValidating,
Expand All @@ -572,7 +585,6 @@ const RegistrationForm = connect(mapStateToProps, {
goToLogin,
getMyInvitation,
clearWidgetState,
updateClock,
loadProfileData,
applyPromoCode,
removePromoCode,
Expand Down Expand Up @@ -631,4 +643,4 @@ RegistrationForm.propTypes = {
};

export { RegistrationForm };
export default withReduxProvider(RegistrationForm);
export default withWidgetProviders(RegistrationForm);
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import React from 'react';
import { cleanup, fireEvent, render } from '@testing-library/react';
import '@testing-library/jest-dom';

// Mock withReduxProvider as identity HOC (modal default export wraps with it)
jest.mock('../../../utils/withReduxProvider', () => ({
withReduxProvider: (Component) => Component,
// Mock withWidgetProviders as identity HOC (modal default export wraps with it)
jest.mock('../../../utils/withWidgetProviders', () => ({
withWidgetProviders: (Component) => Component,
__esModule: true,
default: (Component) => Component,
}));
Expand Down
4 changes: 2 additions & 2 deletions src/components/registration-modal/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import React from 'react';
import PropTypes from 'prop-types';
import { RegistrationForm } from '../registration-form';
import { withReduxProvider } from '../../utils/withReduxProvider';
import { withWidgetProviders } from '../../utils/withWidgetProviders';
import styles from "../../styles/general.module.scss";

const RegistrationModal = ({ summitData, closeWidget, ...props }) => {
Expand Down Expand Up @@ -94,4 +94,4 @@ RegistrationModal.propTypes = {
companyDDLOptions2Show: PropTypes.number,
};

export default withReduxProvider(RegistrationModal);
export default withWidgetProviders(RegistrationModal);
10 changes: 0 additions & 10 deletions src/reducer.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ import {
CLEAR_MY_INVITATION,
CLEAR_WIDGET_STATE,
REQUESTED_TICKET_TYPES,
UPDATE_CLOCK,
LOAD_PROFILE_DATA,
SET_CURRENT_PROMO_CODE,
CLEAR_CURRENT_PROMO_CODE,
Expand All @@ -43,12 +42,8 @@ import {
} from './actions';

import { LOGOUT_USER } from 'openstack-uicore-foundation/lib/security/actions';
import moment from 'moment';
import { STEP_SELECT_TICKET_TYPE } from './utils/constants';

const localNowUtc = moment().unix();


const DEFAULT_STATE = {
reservation: null,
checkout: null,
Expand All @@ -72,7 +67,6 @@ const DEFAULT_STATE = {
summitId: null,
userProfile: null,
},
nowUtc: localNowUtc,
promoCode: '',
promoCodeVerified: null,
promoCodeValidating: false,
Expand Down Expand Up @@ -169,10 +163,6 @@ const RegistrationLiteReducer = (state = DEFAULT_STATE, action) => {
case CLEAR_MY_INVITATION: {
return { ...state, invitation: null };
}
case UPDATE_CLOCK: {
const { timestamp } = payload;
return { ...state, nowUtc: timestamp };
}
case CLEAR_CURRENT_PROMO_CODE: {
return { ...state, promoCode: '', promoCodeVerified: null, promoCodeValidating: false, promoCodeAllowsReassign: true }
}
Expand Down
Loading
Loading