Skip to content
Draft
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
6 changes: 6 additions & 0 deletions public/assets/phy/icons/redesign/warning.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/app/components/content/IsaacVideo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ export function IsaacVideo(props: IsaacVideoProps) {
return <div>
<div className="no-print content-value text-center">
{ embedSrc ?
<div className={classNames("content-video-container", {"ratio-16x9" : userConsent.cookieConsent?.youtubeCookieAccepted ?? false})}>
<div className={classNames("content-video-container", {"ratio-16x9" : userConsent.cookieConsent?.youtubeCookiesAccepted ?? false})}>
<YoutubeCookieHandler afterAcceptedElement={
<iframe ref={videoRef} className="mw-100" title={altTextToUse} src={embedSrc} allowFullScreen/>
} />
Expand Down
57 changes: 54 additions & 3 deletions src/app/components/elements/panels/UserProfile.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React from 'react';
import React, { useState } from 'react';
import {MyAccountTab} from './MyAccountTab';
import {FamilyNameInput, GivenNameInput} from '../inputs/NameInput';
import {EmailInput} from '../inputs/EmailInput';
Expand All @@ -10,6 +10,7 @@ import {
isTutorOrAbove,
SITE_TITLE,
siteSpecific,
useUserConsent,
validateCountryCode,
validateEmail,
validateName
Expand All @@ -22,8 +23,8 @@ import {UserAuthenticationSettingsDTO, UserContext} from "../../../../IsaacApiTy
import {DobInput} from "../inputs/DobInput";
import {AccountTypeMessage} from "../AccountTypeMessage";
import { ConfirmAccountDeletionRequestModal } from '../modals/AccountDeletionModal';
import { openActiveModal, store, useConfirmAccountDeletionRequestMutation } from '../../../state';
import { Button } from 'reactstrap';
import { cookieConsentSlice, DISABLE_EMAIL_VERIFICATION_WARNING_COOKIE, openActiveModal, showSuccessToast, store, useAppDispatch, useConfirmAccountDeletionRequestMutation, useRequestEmailVerificationMutation } from '../../../state';
import { Alert, Button } from 'reactstrap';
import { Link } from 'react-router-dom';
import classNames from 'classnames';

Expand All @@ -47,6 +48,12 @@ export const UserProfile = (props: UserProfileProps) => {
setBooleanNotation, displaySettings, setDisplaySettings, submissionAttempted
} = props;
const [confirmAccountDeletionRequest, {isLoading: _isLoading}] = useConfirmAccountDeletionRequestMutation();
const [sendVerificationEmail, {isSuccess: isVerificationEmailSent}] = useRequestEmailVerificationMutation();
const dispatch = useAppDispatch();

const {cookieConsent} = useUserConsent();
const emailVerificationWarningsDisabled = cookieConsent?.disableEmailVerificationWarningCookiesAccepted ?? false;
const [hasModifiedEmailVerificationState, setHasModifiedEmailVerificationState] = useState(false);

return <MyAccountTab
leftColumn={<>
Expand Down Expand Up @@ -109,6 +116,50 @@ export const UserProfile = (props: UserProfileProps) => {
submissionAttempted={submissionAttempted}
required={true}
/>
{userToUpdate?.emailVerificationStatus !== "VERIFIED" && <Alert color="warning" className="d-flex mt-2">
<i className="icon icon-warning icon-color-alert icon-sm me-3 mt-1" />
<div>
Your email address is unverified. This may affect your ability to receive important notifications.
<br/><br/>
<Button color="link primary-font-link" onClick={() => userToUpdate.email && sendVerificationEmail({email: userToUpdate.email})} disabled={isVerificationEmailSent}>
{isVerificationEmailSent ? "Verification email sent!" : "Click here to request a new verification email."}
</Button>
<br/>
<details>
<summary>Other options</summary>
<div className="ms-4 mt-3">
<p>
In some situations, schools or departments block email from external sites like ours, which may prevent you from receiving verification emails.
If this is the case, please contact your IT department to see if they can allow emails from {SITE_TITLE}.
We have a <Link to="/pages/emails">help page</Link> regarding our email setup that may be of use.
</p>
<p>
If your IT department is unable to assist, you can disable banner warnings on the site for this browser. We strongly recommend against this.
Unverified email addresses may lead to issues with account recovery and you may miss important security notifications from {SITE_TITLE}.
</p>
<p>
You can change this option at any time.
</p>
<Button disabled={hasModifiedEmailVerificationState} onClick={() => {
if (emailVerificationWarningsDisabled) {
dispatch(cookieConsentSlice.actions.removeCookie(DISABLE_EMAIL_VERIFICATION_WARNING_COOKIE));
} else {
dispatch(cookieConsentSlice.actions.acceptCookie(DISABLE_EMAIL_VERIFICATION_WARNING_COOKIE));
}
dispatch(emailVerificationWarningsDisabled
? showSuccessToast("Warnings restored", "Email verification warnings have been restored.")
: showSuccessToast("Warnings disabled", "Email verification warnings have been disabled for this browser."));
setHasModifiedEmailVerificationState(true);
}}>
{hasModifiedEmailVerificationState
? "Success!"
: emailVerificationWarningsDisabled ? "Restore warnings" : "Disable warnings"
}
</Button>
</div>
</details>
</div>
</Alert>}
{siteSpecific(<div className="section-divider-bold"/>, <hr className="text-center border-muted my-4"/>)}
{isAda &&
<CountryInput
Expand Down
29 changes: 12 additions & 17 deletions src/app/components/handlers/InterstitialCookieHandler.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react';
import React, { useState } from 'react';
import { Button } from 'reactstrap';
import classNames from 'classnames';
import { interstitialCookieSlice, useAppDispatch } from '../../state';
import { ANVIL_COOKIE, cookieConsentSlice, DESMOS_COOKIE, GEOGEBRA_COOKIE, useAppDispatch, YOUTUBE_COOKIE } from '../../state';
import { SOCIAL_LINKS, useUserConsent } from '../../services';
import { IsaacVideo } from '../content/IsaacVideo';

Expand All @@ -18,11 +18,6 @@ export interface InterstitialCookieHandlerProps {
}

export const InterstitialCookieHandler = (props: InterstitialCookieHandlerProps) => {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(interstitialCookieSlice.actions.setDefault());
}, []);

return props.accepted ? <>{props.afterAccepted}</> : <>{props.beforeAccepted}</>;
};

Expand All @@ -35,7 +30,7 @@ export const HomepageYoutubeCookieHandler = () => {
const userConsent = useUserConsent();

return <InterstitialCookieHandler
accepted={userConsent.cookieConsent?.youtubeCookieAccepted ?? false}
accepted={userConsent.cookieConsent?.youtubeCookiesAccepted ?? false}
beforeAccepted={<div className="homepage-video">
<div className="position-relative">
<div className="youtube-fade" />
Expand All @@ -55,7 +50,7 @@ export const HomepageYoutubeCookieHandler = () => {
</div>
</div>
<button className={`youtube-play w-100 h-100 p-0 m-0 ${videoActive ? "selected" : ""}`} type="button" onClick={() => {
dispatch(interstitialCookieSlice.actions.acceptYoutubeCookies());
dispatch(cookieConsentSlice.actions.acceptCookie(YOUTUBE_COOKIE));
setAutoplay(true);
}} onFocus={() => setVideoActive(true)} onBlur={() => setVideoActive(false)}>
</button>
Expand All @@ -74,13 +69,13 @@ export const YoutubeCookieHandler = ({afterAcceptedElement} : {afterAcceptedElem
const userConsent = useUserConsent();

return <InterstitialCookieHandler
accepted={userConsent.cookieConsent?.youtubeCookieAccepted ?? false}
accepted={userConsent.cookieConsent?.youtubeCookiesAccepted ?? false}
beforeAccepted={<div className="interstitial-cookie-page">
<h3>Allow YouTube content?</h3>
{youtubeCookieText}
<div className="w-100 d-flex justify-content-center">
<Button className="" onClick={() => {
dispatch(interstitialCookieSlice.actions.acceptYoutubeCookies());
dispatch(cookieConsentSlice.actions.acceptCookie(YOUTUBE_COOKIE));
}}>Accept</Button>
</div>
</div>}
Expand All @@ -93,13 +88,13 @@ export const AnvilCookieHandler = ({afterAcceptedElement} : {afterAcceptedElemen
const userConsent = useUserConsent();

return <InterstitialCookieHandler
accepted={userConsent.cookieConsent?.anvilCookieAccepted ?? false}
accepted={userConsent.cookieConsent?.anvilCookiesAccepted ?? false}
beforeAccepted={<div className="interstitial-cookie-page">
<h3>Allow Anvil content?</h3>
{anvilCookieText}
<div className="w-100 d-flex justify-content-center">
<Button onClick={() => {
dispatch(interstitialCookieSlice.actions.acceptAnvilCookies());
dispatch(cookieConsentSlice.actions.acceptCookie(ANVIL_COOKIE));
}}>Accept</Button>
</div>
</div>}
Expand All @@ -112,13 +107,13 @@ export const DesmosCookieHandler = ({afterAcceptedElement} : {afterAcceptedEleme
const userConsent = useUserConsent();

return <InterstitialCookieHandler
accepted={userConsent.cookieConsent?.desmosCookieAccepted ?? false}
accepted={userConsent.cookieConsent?.desmosCookiesAccepted ?? false}
beforeAccepted={<div className="interstitial-cookie-page">
<h3>Allow Desmos content?</h3>
{desmosCookieText}
<div className="w-100 d-flex justify-content-center">
<Button onClick={() => {
dispatch(interstitialCookieSlice.actions.acceptDesmosCookies());
dispatch(cookieConsentSlice.actions.acceptCookie(DESMOS_COOKIE));
}}>Accept</Button>
</div>
</div>}
Expand All @@ -131,13 +126,13 @@ export const GeogebraCookieHandler = ({afterAcceptedElement, onAccepted} : {afte
const userConsent = useUserConsent();

return <InterstitialCookieHandler
accepted={userConsent.cookieConsent?.geogebraCookieAccepted ?? false}
accepted={userConsent.cookieConsent?.geogebraCookiesAccepted ?? false}
beforeAccepted={<div className="interstitial-cookie-page">
<h3>Allow GeoGebra content?</h3>
{geogebraCookieText}
<div className="w-100 d-flex justify-content-center">
<Button onClick={() => {
dispatch(interstitialCookieSlice.actions.acceptGeogebraCookies());
dispatch(cookieConsentSlice.actions.acceptCookie(GEOGEBRA_COOKIE));
onAccepted?.();
}}>Accept</Button>
</div>
Expand Down
7 changes: 4 additions & 3 deletions src/app/components/navigation/DismissibleBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import classNames from "classnames";
import Cookies from "js-cookie";
import React, { ReactNode, useState } from "react";
import { Alert, Col, Row } from "reactstrap";
import { Alert, AlertProps, Col, Row } from "reactstrap";

interface DismissibleBannerProps {
interface DismissibleBannerProps extends AlertProps {
cookieName: string;
dismissText?: string; // undefined generates a close button
children: ReactNode;
theme: "light" | "info" | "warning" | "danger";
}

export const DismissibleBanner = (props: DismissibleBannerProps) => {
const { cookieName, children, dismissText, theme } = props;
const { cookieName, children, dismissText, theme, ...rest } = props;

const [show, setShown] = useState(() => {
const currentCookieValue = Cookies.get(cookieName);
Expand All @@ -28,6 +28,7 @@ export const DismissibleBanner = (props: DismissibleBannerProps) => {
}

return <Alert
{...rest}
color={theme}
className={classNames("mb-0 border-radius-0 mx-0 px-5 no-print", {"d-flex align-items-center": !dismissText})}
fade={false}
Expand Down
30 changes: 17 additions & 13 deletions src/app/components/navigation/EmailVerificationBanner.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,32 @@
import React, {useState} from 'react';
import React, {useMemo, useState} from 'react';
import {
selectors,
useAppSelector,
useRequestEmailVerificationMutation
} from "../../state";
import {Link} from "react-router-dom";
import {Button, Col, Container, Row} from 'reactstrap';
import {SITE_TITLE_SHORT, siteSpecific, WEBMASTER_EMAIL} from "../../services";
import {SITE_TITLE_SHORT, siteSpecific, useUserConsent, WEBMASTER_EMAIL} from "../../services";

export const EmailVerificationBanner = () => {
const [hidden, setHidden] = useState(false);
const {cookieConsent} = useUserConsent();
const isHiddenViaCookie = cookieConsent?.disableEmailVerificationWarningCookiesAccepted ?? false;
const user = useAppSelector(selectors.user.orNull);
const status = user?.loggedIn && user?.emailVerificationStatus || null;
const show = user?.loggedIn && status != "VERIFIED" && !hidden;
const show = useMemo(() => user?.loggedIn && status != "VERIFIED" && !hidden && !isHiddenViaCookie, [user, status, hidden, isHiddenViaCookie]);

const [sendVerificationEmail] = useRequestEmailVerificationMutation();
function clickVerify() {
if (user?.loggedIn && user.email) {
sendVerificationEmail({email: user.email});
void sendVerificationEmail({email: user.email});
}
setHidden(true);
}

return show ? <div className="banner d-print-none" id="email-status-banner">
return show && <div className="banner d-print-none" id="email-status-banner">
<Container className="py-3">

<Row style={{alignItems: "center"}}>
<Row className="align-items-center">
<Col xs={12} sm={2} md={1}>
<h3 className="text-center">
<img className={siteSpecific("mt-n2 mt-sm-0 mt-md-n1", "mt-n1 mt-sm-1")} src="/assets/common/icons/info.svg" style={{height: "1.5rem"}}
Expand All @@ -35,13 +36,16 @@ export const EmailVerificationBanner = () => {
</Col>
{(status == null || status == "NOT_VERIFIED") && <React.Fragment>
<Col xs={12} sm={10} md={8}>
<small>Your email address is not verified - please find our email in your inbox and follow the
verification link. You can <Button color="link primary-font-link" onClick={clickVerify} id="email-verification-request">
request a new verification email</Button> if necessary. To change your account email,
go to <Link to="/account">My account</Link>.
<small>
Your email address is not verified - please find our email in your inbox and follow the
verification link. You can{" "}
<Button color="link primary-font-link" onClick={clickVerify} id="email-verification-request">
request a new verification email
</Button>{" "}
if necessary. To change your account email, go to <Link to="/account">My account</Link>.
</small>
</Col>
<Col xs={12} md={3} className="text-center">
<Col xs={12} md={3} className="d-flex flex-column align-items-center text-center">
<Button
color={siteSpecific("keyline", "solid")} className="mt-3 mb-2 d-block d-md-inline-block banner-button"
onClick={() => setHidden(true)} id="email-verification-snooze"
Expand All @@ -62,5 +66,5 @@ export const EmailVerificationBanner = () => {
}
</Row>
</Container>
</div> : null;
</div>;
};
6 changes: 3 additions & 3 deletions src/app/services/userConsent.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { InterstitialCookieState } from '../state';
import { CookieConsentState } from '../state';
import { AppState, useAppSelector } from '../state';

interface UseUserConsentReturnType {
cookieConsent: InterstitialCookieState;
cookieConsent: CookieConsentState;
openAIConsent: boolean;
}

Expand All @@ -14,4 +14,4 @@ export function useUserConsent(): UseUserConsentReturnType {
openAIConsent: !!databaseRecordedConsent?.OPENAI,
cookieConsent
};
}
}
4 changes: 2 additions & 2 deletions src/app/state/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
gameboardsSlice,
adminUserSearchSlice,
userSlice,
interstitialCookieSlice,
cookieConsentSlice,
pageContextSlice,
topicSlice,
linkableSettingSlice,
Expand All @@ -50,7 +50,7 @@ export const rootReducer = combineReducers({
notifications,

// Cookies
cookieConsent: interstitialCookieSlice.reducer,
cookieConsent: cookieConsentSlice.reducer,

// Static Content
glossaryTerms,
Expand Down
Loading
Loading