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
93 changes: 79 additions & 14 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ 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 {
getRegistrationLoaded,
getRegistration,
getAbleToRegister,
getAgreedLatestToU,
} from "../selectors/registrationSelectors";
import {
getOrgProperties,
getUserBasicInfo,
Expand All @@ -20,6 +26,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";
Expand Down Expand Up @@ -54,7 +65,11 @@ const Header = () => {

const healthStatus = useAppSelector(state => getHealthStatus(state));
const errorCounter = useAppSelector(state => getErrorCount(state));
const isAbleToRegister = useAppSelector(state => getAbleToRegister(state));
const agreedLatestToU = useAppSelector(state => getAgreedLatestToU(state));
const user = useAppSelector(state => getUserInformation(state));
const registrationLoaded = useAppSelector(state => getRegistrationLoaded(state));
const registration = useAppSelector(state => getRegistration(state));
const orgProperties = useAppSelector(state => getOrgProperties(state));
const displayTerms = (orgProperties["org.opencastproject.admin.display_terms"] || "false").toLowerCase() === "true";

Expand All @@ -66,6 +81,10 @@ const Header = () => {
setMenuHelp(false);
};

const hideNotificationMenu = () => {
setMenuNotify(false);
};

const showRegistrationModal = () => {
registrationModalRef.current?.open();
};
Expand Down Expand Up @@ -138,18 +157,24 @@ 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());
}, [dispatch]);

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 && registrationLoaded && registration == null) {
showRegistrationModal();
}
}, [user, registration, registrationLoaded]);
return (
<>
<header className="primary-header">
Expand Down Expand Up @@ -214,9 +239,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 || !isAbleToRegister) && (
<span id="error-count" className="badge">
{errorCounter}
{errorCounter + (!agreedLatestToU || !isAbleToRegister ? 1 : 0)}
</span>
)}
</BaseButton>
Expand All @@ -225,6 +250,10 @@ const Header = () => {
{displayMenuNotify && (
<MenuNotify
healthStatus={healthStatus}
registering={isAbleToRegister}
updatedToU={agreedLatestToU}
showRegistrationModal={showRegistrationModal}
hideNotificationMenu={hideNotificationMenu}
/>
)}
</div>
Expand Down Expand Up @@ -326,9 +355,18 @@ const MenuLang = ({ handleChangeLanguage }: { handleChangeLanguage: (code: strin

const MenuNotify = ({
healthStatus,
registering,
updatedToU,
showRegistrationModal,
hideNotificationMenu,
}: {
healthStatus: HealthStatus[],
registering: boolean,
updatedToU: boolean,
showRegistrationModal: () => void,
hideNotificationMenu: () => void,
}) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const navigate = useNavigate();

Expand All @@ -338,6 +376,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 */}
Expand All @@ -361,6 +406,26 @@ const MenuNotify = ({
)}
</li>
))}
{!registering &&
<li>
<ButtonLikeAnchor
onClick={() => showAdoptersRegistrationModal()}
>
<span className="wide-text">Registration</span>
<span className="multi-value multi-value-yellow">{t("ADOPTER_REGISTRATION.NOTIFICATION.UNREGISTERED")}</span>
</ButtonLikeAnchor>
</li>
}
{registering && !updatedToU &&
<li>
<ButtonLikeAnchor
onClick={() => showAdoptersRegistrationModal()}
>
<span className="wide-text">Registration</span>
<span className="multi-value multi-value-yellow">{t("ADOPTER_REGISTRATION.NOTIFICATION.UPDATED_TOU")}</span>
</ButtonLikeAnchor>
</li>
}
</ul>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,10 @@
"DELETE_SUBMIT_STATE": {
"TEXT": "Are you sure you want to delete your registration data?"
}
},
"NOTIFICATION": {
"UNREGISTERED": "Unregistered",
"UPDATED_TOU": "Updated ToU"
}
},
"EVENTS": {
Expand Down
12 changes: 12 additions & 0 deletions src/selectors/registrationSelectors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { RootState } from "../store";

/**
* This file contains selectors regarding information about the registration status
*/
export const getRegistrationLoaded = (state: RootState) => state.registration.loaded;
// Are we registered at all
export const getRegistration = (state: RootState) => state.registration.registration;
// Are we able to talk to register.opencast.org
export const getAbleToRegister = (state: RootState) => state.registration.ableToRegister;
// Does our registration match the latest ToU on the core
export const getAgreedLatestToU = (state: RootState) => state.registration.agreedToToU;
99 changes: 99 additions & 0 deletions src/slices/registrationSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
Comment thread
gregorydlogan marked this conversation as resolved.
import axios from "axios";
import { WritableDraft } from "immer";
import { createAppAsyncThunk } from "../createAsyncThunkWithTypes";

export type Registration = {
Comment thread
Arnei marked this conversation as resolved.
agreedToPolicy: boolean,
registered: boolean,
termsVersionAgreed: string,
}

export type RegistrationState = {
loaded: boolean,
registration: Registration | null,
latestToU: string,
ableToRegister: boolean,
agreedToToU: boolean,
error: boolean
};

type StateUpdate = {
registration: Registration | null,
latestToU: string,
};

// Initial state of health status in redux store
const initialState: RegistrationState = {
loaded: false,
registration: null,
latestToU: "uninitialized",
ableToRegister: 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: {},
// These are used for thunks
extraReducers: builder => {
builder
.addCase(fetchRegistration.fulfilled, (state, action: PayloadAction<
Registration
>) => {
state.registration = action.payload;
const updatedState = {
registration: state.registration,
latestToU: state.latestToU,
};
state.agreedToToU = agreedLatestTerms(state, updatedState);
state.loaded = true;
})
.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.ableToRegister = action.payload;
});
},
});

const agreedLatestTerms = (_state: WritableDraft<RegistrationState>, updatedState: StateUpdate) => {
if (null != updatedState.registration && "uninitialized" != updatedState.latestToU) {
return updatedState.registration.termsVersionAgreed === updatedState.latestToU;
}
return false;
};

// Export the slice reducer as the default export
export default registrationSlice.reducer;
2 changes: 2 additions & 0 deletions src/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import groups from "./slices/groupSlice";
import acls from "./slices/aclSlice";
import themes from "./slices/themeSlice";
import health from "./slices/healthSlice";
import registration from "./slices/registrationSlice";
import notifications from "./slices/notificationSlice";
import workflows from "./slices/workflowSlice";
import eventDetails from "./slices/eventDetailsSlice";
Expand Down Expand Up @@ -64,6 +65,7 @@ const reducers = combineReducers({
acls: persistReducer(aclsPersistConfig, acls),
themes: persistReducer(themesPersistConfig, themes),
health,
registration,
notifications,
workflows,
eventDetails,
Expand Down
3 changes: 3 additions & 0 deletions src/styles/components/_header.scss
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,9 @@
.dropdown-ul{
width: auto;
right: 8px;
.wide-text{
padding-right: 5px;
}
}
#error-count{
min-width: 10px;
Expand Down
Loading