-
Notifications
You must be signed in to change notification settings - Fork 41
Show registration modal only when unregistered #1600
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
base: r/19.x
Are you sure you want to change the base?
Changes from all commits
48ae5a7
c12f608
45f695b
92f70b3
094d207
298d1a8
6950c89
7bbefff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,11 @@ import languages from "../i18n/languages"; | |
| import opencastLogo from "../img/opencast-white.svg?url"; | ||
| import { setSpecificServiceFilter } from "../slices/tableFilterSlice"; | ||
| import { getErrorCount, getHealthStatus } from "../selectors/healthSelectors"; | ||
| import { | ||
| getRegistration, | ||
| getIsRegistering, | ||
| getAgreedLatestToU, | ||
| } from "../selectors/registrationSelectors"; | ||
| import { | ||
| getOrgProperties, | ||
| getUserBasicInfo, | ||
|
|
@@ -20,6 +25,11 @@ import HotKeyCheatSheet from "./shared/HotKeyCheatSheet"; | |
| import { useHotkeys } from "react-hotkeys-hook"; | ||
| import { useAppDispatch, useAppSelector } from "../store"; | ||
| import { HealthStatus, fetchHealthStatus } from "../slices/healthSlice"; | ||
| import { | ||
| fetchRegistration, | ||
| fetchLatestToU, | ||
| fetchIsUpToDate, | ||
| } from "../slices/registrationSlice"; | ||
| import { UserInfoState } from "../slices/userInfoSlice"; | ||
| import { Tooltip } from "./shared/Tooltip"; | ||
| import { HiOutlineTranslate } from "react-icons/hi"; | ||
|
|
@@ -54,7 +64,10 @@ const Header = () => { | |
|
|
||
| const healthStatus = useAppSelector(state => getHealthStatus(state)); | ||
| const errorCounter = useAppSelector(state => getErrorCount(state)); | ||
| const isUpToDate = useAppSelector(state => getIsRegistering(state)); | ||
| const agreedLatestToU = useAppSelector(state => getAgreedLatestToU(state)); | ||
| const user = useAppSelector(state => getUserInformation(state)); | ||
| const registration = useAppSelector(state => getRegistration(state)); | ||
| const orgProperties = useAppSelector(state => getOrgProperties(state)); | ||
| const displayTerms = (orgProperties["org.opencastproject.admin.display_terms"] || "false").toLowerCase() === "true"; | ||
|
|
||
|
|
@@ -66,6 +79,10 @@ const Header = () => { | |
| setMenuHelp(false); | ||
| }; | ||
|
|
||
| const hideNotificationMenu = () => { | ||
| setMenuNotify(false); | ||
| }; | ||
|
|
||
| const showRegistrationModal = () => { | ||
| registrationModalRef.current?.open(); | ||
| }; | ||
|
|
@@ -138,18 +155,22 @@ const Header = () => { | |
| }, []); | ||
|
|
||
| useEffect(() => { | ||
| if (!user) { return; } | ||
|
|
||
| const isAdmin = user.isAdmin || user.isOrgAdmin; | ||
| const isLocalhost = window.location.hostname === "localhost"; | ||
| const lastDismissed = localStorage.getItem("adopterModalDismissed"); | ||
| const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000; | ||
| const dismissedLongEnough = !lastDismissed || Date.now() - parseInt(lastDismissed) > THIRTY_DAYS; | ||
|
|
||
| if (isAdmin && !isLocalhost && dismissedLongEnough) { | ||
| showRegistrationModal(); | ||
| } | ||
| }, [user]); | ||
| dispatch(fetchRegistration()); | ||
| dispatch(fetchLatestToU()); | ||
| dispatch(fetchIsUpToDate()); | ||
|
|
||
| if (!user) { return; } | ||
|
|
||
| const isAdmin = user.isAdmin || user.isOrgAdmin; | ||
| const isLocalhost = window.location.hostname === "localhost"; | ||
| const lastDismissed = localStorage.getItem("adopterModalDismissed"); | ||
| const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000; | ||
| const dismissedLongEnough = !lastDismissed || Date.now() - parseInt(lastDismissed) > THIRTY_DAYS; | ||
|
|
||
| if (isAdmin && !isLocalhost && dismissedLongEnough && registration == null) { | ||
| showRegistrationModal(); | ||
| } | ||
| }, [user, registration, dispatch]); | ||
| return ( | ||
| <> | ||
| <header className="primary-header"> | ||
|
|
@@ -214,9 +235,9 @@ const Header = () => { | |
| <Tooltip active={!displayMenuNotify} title={t("SYSTEM_NOTIFICATIONS")}> | ||
| <BaseButton onClick={() => setMenuNotify(!displayMenuNotify)} className="nav-dd-element"> | ||
| <LuBell className="header-icon"/> | ||
| {errorCounter !== 0 && ( | ||
| {(errorCounter !== 0 || !agreedLatestToU || !isUpToDate) && ( | ||
| <span id="error-count" className="badge"> | ||
| {errorCounter} | ||
| {errorCounter + (!agreedLatestToU || !isUpToDate ? 1 : 0)} | ||
| </span> | ||
| )} | ||
| </BaseButton> | ||
|
|
@@ -225,6 +246,10 @@ const Header = () => { | |
| {displayMenuNotify && ( | ||
| <MenuNotify | ||
| healthStatus={healthStatus} | ||
| registering={isUpToDate} | ||
| updatedToU={agreedLatestToU} | ||
| showRegistrationModal={showRegistrationModal} | ||
| hideNotificationMenu={hideNotificationMenu} | ||
| /> | ||
| )} | ||
| </div> | ||
|
|
@@ -326,8 +351,16 @@ const MenuLang = ({ handleChangeLanguage }: { handleChangeLanguage: (code: strin | |
|
|
||
| const MenuNotify = ({ | ||
| healthStatus, | ||
| registering, | ||
| updatedToU, | ||
| showRegistrationModal, | ||
| hideNotificationMenu, | ||
| }: { | ||
| healthStatus: HealthStatus[], | ||
| registering: boolean, | ||
| updatedToU: boolean, | ||
| showRegistrationModal: () => void, | ||
| hideNotificationMenu: () => void, | ||
| }) => { | ||
| const dispatch = useAppDispatch(); | ||
| const navigate = useNavigate(); | ||
|
|
@@ -338,6 +371,13 @@ const MenuNotify = ({ | |
| navigate("/systems/services"); | ||
| }; | ||
|
|
||
| // show Adopter Registration Modal and hide drop down | ||
| const showAdoptersRegistrationModal = () => { | ||
| showRegistrationModal(); | ||
| hideNotificationMenu(); | ||
| }; | ||
|
|
||
|
|
||
| return ( | ||
| <ul className="dropdown-ul"> | ||
| {/* For each service in the serviceList (Background Services) one list item */} | ||
|
|
@@ -361,6 +401,26 @@ const MenuNotify = ({ | |
| )} | ||
| </li> | ||
| ))} | ||
| {!registering && | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not using tab indentation here |
||
| <li> | ||
| <ButtonLikeAnchor | ||
| onClick={() => showAdoptersRegistrationModal()} | ||
| > | ||
| <span className="wide-text">Registration</span> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Untranslated strings. |
||
| <span className="multi-value multi-value-yellow">Unregistered</span> | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. multi-value-yellow does not seem to exist (maybe I removed it in my cleaning frenzy?). |
||
| </ButtonLikeAnchor> | ||
| </li> | ||
| } | ||
| {registering && !updatedToU && | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So if I am unregistered and the ToU have been updated, there would be two notifications. Is that intended? |
||
| <li> | ||
| <ButtonLikeAnchor | ||
| onClick={() => showAdoptersRegistrationModal()} | ||
| > | ||
| <span className="wide-text">Registration</span> | ||
| <span className="multi-value multi-value-yellow">Updated ToU</span> | ||
| </ButtonLikeAnchor> | ||
| </li> | ||
| } | ||
| </ul> | ||
| ); | ||
| }; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { RootState } from "../store"; | ||
|
|
||
| /** | ||
| * This file contains selectors regarding information about the registration status | ||
| */ | ||
| // Are we registered at all | ||
| export const getRegistration = (state: RootState) => state.registration.registration; | ||
| // Are we able to talk to register.opencast.org | ||
| export const getIsRegistering = (state: RootState) => state.registration.isRegistering; | ||
| // Does our registration match the latest ToU on the core | ||
| export const getAgreedLatestToU = (state: RootState) => state.registration.agreedToToU; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| import { PayloadAction, createSlice } from "@reduxjs/toolkit"; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Inconsistent use of spaces and tabs in this file. |
||
| import axios from "axios"; | ||
| import { WritableDraft } from "immer"; | ||
| import { createAppAsyncThunk } from "../createAsyncThunkWithTypes"; | ||
|
|
||
| export type Registration = { | ||
|
Arnei marked this conversation as resolved.
|
||
| agreedToPolicy: boolean, | ||
| registered: boolean, | ||
| termsVersionAgreed: string, | ||
| } | ||
|
|
||
| export type RegistrationState = { | ||
| registration: Registration | null, | ||
| latestToU: string, | ||
| isRegistering: boolean, | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like |
||
| agreedToToU: boolean, | ||
| error: boolean | ||
| }; | ||
|
|
||
| type Temp = { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| registration: Registration | null, | ||
| latestToU: string, | ||
| }; | ||
|
|
||
| // Initial state of health status in redux store | ||
| const initialState: RegistrationState = { | ||
| registration: null, | ||
| latestToU: "uninitialized", | ||
| isRegistering: false, | ||
| agreedToToU: false, | ||
| error: false, | ||
| }; | ||
|
|
||
| // This is the registration itself | ||
| export const fetchRegistration = createAppAsyncThunk("registration/fetchRegistration", async () => { | ||
| const res = await axios.get<Registration>("/admin-ng/adopter/registration"); | ||
| return res.data; | ||
| }); | ||
|
|
||
| // This is the latest ToU ID. It's a string like APRIL_2020. | ||
| export const fetchLatestToU = createAppAsyncThunk("registration/fetchLatestToU", async () => { | ||
| const res = await axios.get<string>("/admin-ng/adopter/latestToU"); | ||
| return res.data; | ||
| }); | ||
|
|
||
| // This is whether the core can talk to register.opencast.org. | ||
| export const fetchIsUpToDate = createAppAsyncThunk("registration/isUpToDate", async () => { | ||
| const res = await axios.get<boolean>("/admin-ng/adopter/isUpToDate"); | ||
| return res.data; | ||
| }); | ||
|
|
||
| const registrationSlice = createSlice({ | ||
| name: "registration", | ||
| initialState, | ||
| reducers: { | ||
| setError(state, action: PayloadAction<{ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't see |
||
| error: RegistrationState["error"], | ||
| }>) { | ||
| state.error = action.payload.error; | ||
| }, | ||
| }, | ||
| // These are used for thunks | ||
| extraReducers: builder => { | ||
| builder | ||
| /* .addCase(fetchRegistration.pending, state => { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leftover code? |
||
| state.statusHealth = "loading"; | ||
| }) */ | ||
| .addCase(fetchRegistration.fulfilled, (state, action: PayloadAction< | ||
| Registration | ||
| >) => { | ||
| state.registration = action.payload; | ||
| const updatedState = { | ||
| registration: state.registration, | ||
| latestToU: state.latestToU, | ||
| }; | ||
| state.agreedToToU = agreedLatestTerms(state, updatedState); | ||
| }) | ||
| .addCase(fetchLatestToU.fulfilled, (state, action: PayloadAction< | ||
| string | ||
| >) => { | ||
| state.latestToU = action.payload; | ||
| const updatedState = { | ||
| registration: state.registration, | ||
| latestToU: state.latestToU, | ||
| }; | ||
| state.agreedToToU = agreedLatestTerms(state, updatedState); | ||
| }) | ||
| .addCase(fetchIsUpToDate.fulfilled, (state, action: PayloadAction< | ||
| boolean | ||
| >) => { | ||
| // This is true if the core can talk to https://register.opencast.org/, false otherwise | ||
| state.isRegistering = action.payload; | ||
| }) | ||
| /* .addCase(fetchHealthStatus.rejected, (state, action) => { | ||
| state.error = true; | ||
| }) */; | ||
| }, | ||
| }); | ||
|
|
||
| const agreedLatestTerms = (_state: WritableDraft<RegistrationState>, updatedState: Temp) => { | ||
| if (null != updatedState.registration && "uninitialized" != updatedState.latestToU) { | ||
| return updatedState.registration.termsVersionAgreed === updatedState.latestToU; | ||
| } | ||
| return false; | ||
| }; | ||
|
|
||
| export const { setError } = registrationSlice.actions; | ||
|
|
||
| // Export the slice reducer as the default export | ||
| export default registrationSlice.reducer; | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -161,6 +161,9 @@ | |||||||||||||
| .dropdown-ul{ | ||||||||||||||
| width: auto; | ||||||||||||||
| right: 8px; | ||||||||||||||
| .wide-text{ | ||||||||||||||
| padding-right: 5px; | ||||||||||||||
| } | ||||||||||||||
|
Comment on lines
+164
to
+166
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You're using 2 spaces, but the file uses 4. |
||||||||||||||
| } | ||||||||||||||
| #error-count{ | ||||||||||||||
| min-width: 10px; | ||||||||||||||
|
|
||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can cause an infinite loop.
dispatch(fetchRegistration());will eventually updateregistration, which triggers a rerun of theuseEffecthook, which causesdispatch(fetchRegistration());to be run again...I recommend putting the
dispatch(fetch....'s into their own useEffect hook that runs only once on mount. If you need to run them again, you should probably do so conditionally?