Skip to content
Merged
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
2 changes: 1 addition & 1 deletion src/app/contexts/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ function useContextValue() {
const myOpenStaxUser = useMyOpenStaxUser(isVerified, fetchTime);
const value = React.useMemo(
() =>
model?.last_name
model.last_name
? {
accountId: model.id,
userName: `${model.first_name} ${model.last_name.substr(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
import React from 'react';
import useUserContext from '~/contexts/user';
import {useLocation} from 'react-router-dom';
import {assertDefined} from '~/helpers/data';
import Cookies from 'js-cookie';
import type {QueuedItemType} from './queue';

const DISMISSED_KEY = 'renewal_dialog_dismissed';
// const YESTERDAY = Date.now() - 60 * 60 * 24 * 1000;

function useCookieKey(key) {
function useCookieKey(key: string) {
return React.useReducer(
(_, value) => {
(_: string, value: string) => {
Cookies.set(key, value);
return value ? value : '0';
return value;
},
Cookies.get(key)
Cookies.get(key) ?? ''
);
}

function useDismissalCookie() {
function useDismissalCookie(): [boolean, () => void] {
const [cookieValue, setCookieValue] = useCookieKey(DISMISSED_KEY);
const clicked = React.useMemo(
() => +Number(cookieValue) > 0,
Expand All @@ -31,7 +32,7 @@ function useDismissalCookie() {
if (pathname === '/renewal-form') {
return false;
}
return !clicked && isFaculty && isAdopter;
return !clicked && isFaculty && Boolean(isAdopter);
},
[clicked, isFaculty, isAdopter, pathname]
);
Expand All @@ -55,12 +56,12 @@ function useDismissalCookie() {
[pathname, disable, clicked]
);

return [ready, disable];
return [ready, disable] as const;
}

function AdoptionContentBase({children, disable}) {
function AdoptionContentBase({children, disable}: {children: React.ReactNode; disable: () => void}) {
const {userModel} = useUserContext();
const {first_name: name} = userModel || {};
const {first_name: name} = assertDefined(userModel); // ready ensures this
const {pathname} = useLocation();
const href = `${window.location.origin}${pathname}`;
const renewalFormHref = `/renewal-form?from=popup&returnTo=${encodeURIComponent(href)}`;
Expand Down Expand Up @@ -92,10 +93,10 @@ function AdoptionContentBase({children, disable}) {
);
}

export default function useAdoptionMicrosurveyContent() {
export default function useAdoptionMicrosurveyContent(): [boolean, QueuedItemType] {
const [ready, disable] = useDismissalCookie();
const AdoptionContent = React.useCallback(
({children}) => (
({children}: {children: React.ReactNode}) => (
<AdoptionContentBase disable={disable}>
{children}
</AdoptionContentBase>
Expand Down
7 changes: 0 additions & 7 deletions src/app/layouts/default/microsurvey-popup/queue.d.ts

This file was deleted.

36 changes: 0 additions & 36 deletions src/app/layouts/default/microsurvey-popup/queue.js

This file was deleted.

47 changes: 47 additions & 0 deletions src/app/layouts/default/microsurvey-popup/queue.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import useStickyMicrosurveyContent from './sticky-content';
import useAdoptionMicrosurveyContent from './adoption-content';

type NextItemFunction = () => void;
export type QueuedItemType = ({
children,
onDone
}: {
children: React.ReactNode;
onDone?: NextItemFunction;
}) => React.JSX.Element;
type UseContentHook = () => [boolean, QueuedItemType];

function useEnqueueWhenReady(
useContent: UseContentHook,
queue: QueuedItemType[],
setQueue: (queue: QueuedItemType[]) => void
) {
const [ready, Item] = useContent();
const [hasQueued, setHasQueued] = React.useState(false);

React.useEffect(() => {
if (!hasQueued && ready && !queue.includes(Item)) {
setQueue([...queue, Item]);
setHasQueued(true);
}
// It doens't look like this (double-loading) is possible as it is set up now.
// if (!ready && queue.includes(Item)) {
// setQueue(queue.slice(1));
// }
}, [ready, queue, setQueue, Item, hasQueued]);
}

export default function useMSQueue(): [
QueuedItemType | null,
NextItemFunction
] {
const [queue, setQueue] = React.useState<QueuedItemType[]>([]);
const nextItem = React.useCallback(() => setQueue(queue.slice(1)), [queue]);
const QueuedItem = queue.length > 0 ? queue[0] : null;

useEnqueueWhenReady(useStickyMicrosurveyContent, queue, setQueue);
useEnqueueWhenReady(useAdoptionMicrosurveyContent, queue, setQueue);

return [QueuedItem, nextItem];
}
47 changes: 0 additions & 47 deletions src/app/layouts/default/microsurvey-popup/sticky-content.js

This file was deleted.

51 changes: 51 additions & 0 deletions src/app/layouts/default/microsurvey-popup/sticky-content.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React from 'react';
import RawHTML from '~/components/jsx-helpers/raw-html';
import {useStickyData, useSeenCounter, BannerInfo as StickyData} from '../shared';
import {assertDefined} from '~/helpers/data';
import type {QueuedItemType} from './queue';

const SEEN_ENOUGH = 3;

function StickyContent({stickyData, children}: {stickyData: StickyData; children: React.ReactNode}) {
const {body, link_url: url, link_text: text} = stickyData;

return (
<div
className="microsurvey-content"
data-analytics-view
data-analytics-nudge="donate"
data-nudge-placement="popup"
>
{children}
<RawHTML className="blurb" html={body} />
<a className="btn primary" href={url} data-nudge-action="interacted">
{text}
</a>
</div>
);
}

function useBoundStickyContent(stickyData: StickyData | undefined, incrementSeenCount: () => void) {
// Increment seen count on each fresh load
React.useEffect(
() => incrementSeenCount(),
[incrementSeenCount]
);

// ready ensures it will not be called unless stickyData is defined
return React.useCallback(
(props: {children: React.ReactNode}) => <StickyContent stickyData={assertDefined(stickyData)} {...props} />,
[stickyData]
);
}

export default function useStickyMicrosurveyContent(): [boolean, QueuedItemType] {
const stickyData = useStickyData();
const [hasBeenSeenEnough, incrementSeenCount] = useSeenCounter(SEEN_ENOUGH);
const BoundStickyContent = useBoundStickyContent(stickyData?.bannerInfo, incrementSeenCount);
const ready = Boolean(
stickyData?.mode === 'popup' && !hasBeenSeenEnough
);

return [ready, BoundStickyContent];
}
2 changes: 1 addition & 1 deletion src/app/layouts/default/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {useDataFromPromise} from '~/helpers/page-data-utils';
import PutAway from '~/components/put-away/put-away';
import './shared.scss';

type BannerInfo = {
export type BannerInfo = {
id: number;
heading: string;
body: string;
Expand Down
5 changes: 3 additions & 2 deletions src/app/models/accounts-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,21 @@ export type AccountsUserModel = {
uuid: string;
first_name: string;
last_name: string;
email: string;
email?: string;
school_name: string;
self_reported_role: string;
self_reported_school: string;
is_not_gdpr_location: boolean;
salesforce_contact_id: string;
is_instructor_verification_stale: boolean;
is_instructor_verification_stale?: boolean;
faculty_status: string;
contact_infos: {
type: string;
value: string;
is_verified: boolean;
is_guessed_preferred: boolean;
}[];
using_openstax: boolean;
};

declare global {
Expand Down
2 changes: 1 addition & 1 deletion src/app/models/usermodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type UserModelType = {
last_name: string;
instructorEligible: boolean;
pending_verification: boolean;
stale_verification: boolean;
stale_verification?: boolean;
incompleteSignup: boolean;
pendingInstructorAccess: boolean;
emailUnverified: boolean;
Expand Down
2 changes: 1 addition & 1 deletion src/app/pages/k12/k12-main/banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ export default function Banner({data}: {data: K12Data}) {
</div>
<div
className="right-bg clipped-image"
style={{backgroundImage: `url(${bannerImgUrl});`}}
style={{backgroundImage: `url(${bannerImgUrl})`}}
Copy link
Contributor

Choose a reason for hiding this comment

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

This was yesterday's hotfix. I just rolled it into this PR rather than make a separate one.

></div>
</section>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React from 'react';
import {render, screen} from '@testing-library/preact';
import userEvent from '@testing-library/user-event';
import MemoryRouter from '~/../../test/helpers/future-memory-router';
import useAdoptionMicrosurveyContent from '~/layouts/default/microsurvey-popup/adoption-content';
import * as UM from '~/models/usermodel';
import userModelData from '~/../../test/src/data/userModel';
import { UserContextProvider } from '~/contexts/user';

/* eslint-disable camelcase */
function userModelAdopter(isAdopter: boolean) {
const adopterFields = {
using_openstax: isAdopter,
faculty_status: isAdopter ? 'confirmed_faculty' : 'no_faculty_info'
};

return {...userModelData, accountsModel: {...userModelData.accountsModel, ...adopterFields}};
}
/* eslint-enable camelcase */

describe('microsurvey-popup/adoption-content', () => {
const user = userEvent.setup();
const saveError = console.error;

function Component() {
const [ready, AdoptionContent] = useAdoptionMicrosurveyContent();

return ready
? <AdoptionContent><div>the page</div></AdoptionContent>
: null;
}

function WrappedComponent({path}: {path: string}) {
return <UserContextProvider>
<MemoryRouter initialEntries={[path]}>
<Component />
</MemoryRouter>
</UserContextProvider>;
}
it('renders nothing if user is not an adopter', async () => {
jest.spyOn(UM, 'useUserModel').mockReturnValue({...userModelAdopter(false)});
render(<WrappedComponent path='/' />);
expect(document.body.textContent).toBe('');
});
it('renders nothing on the renewal-form even if user is an adopter', () => {
jest.spyOn(UM, 'useUserModel').mockReturnValue({...userModelAdopter(true)});
render(<WrappedComponent path='/renewal-form' />);
expect(document.body.textContent).toBe('');
});
it('renders the page on other paths if user is faculty/adopter', async () => {
jest.spyOn(UM, 'useUserModel').mockReturnValue({...userModelAdopter(true)});
render(<WrappedComponent path='/' />);
screen.getByText('the page');
const link = screen.getByRole('link');

console.error = jest.fn();
await user.click(link);
expect(console.error).toHaveBeenCalled(); // because navigation
console.error = saveError;
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React from 'react';
import {describe, expect, it} from '@jest/globals';
import {render} from '@testing-library/preact';
import ShellContextProvider from '../../../helpers/shell-context';
import ShellContextProvider from '../../../../helpers/shell-context';
import MemoryRouter from '~/../../test/helpers/future-memory-router';
import MicroSurvey from '~/layouts/default/microsurvey-popup/microsurvey-popup';
import useMSQueue from '~/layouts/default/microsurvey-popup/queue';
Expand Down
Loading