Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
48ae5a7
In theory this only opens the registration modal in the right circums…
gregorydlogan May 2, 2026
c12f608
Revert me for further work
gregorydlogan May 7, 2026
45f695b
Removing summary, since it's not needed here
gregorydlogan May 8, 2026
92f70b3
Switching to useEffect, suggested in the review
gregorydlogan May 8, 2026
094d207
Removing these since this part of the service does not care about this.
gregorydlogan May 8, 2026
298d1a8
Modal now appears properly, and creates 'Registration' notifications …
gregorydlogan Jun 10, 2026
6950c89
Merge remote-tracking branch 'upstream/r/19.x' into t/registration-modal
gregorydlogan Jul 13, 2026
7bbefff
Combining useEffects
gregorydlogan Jul 13, 2026
833d154
Moving these dispatches out to prevent possible infinite loops
gregorydlogan Jul 15, 2026
4720639
Fixing some tabs vs spaces
gregorydlogan Jul 15, 2026
f22acfe
Adding translation strings to notifications
gregorydlogan Jul 15, 2026
f1197ba
Replacing tabs with spaces
gregorydlogan Jul 15, 2026
2d69560
Renaming dumb type to something better
gregorydlogan Jul 15, 2026
8bfd332
Removing leftovers
gregorydlogan Jul 15, 2026
e025708
Renaming isRegistering to something a little more sane. The variable…
gregorydlogan Jul 15, 2026
20b62d1
These are not needed
gregorydlogan Jul 15, 2026
3247ac4
Only opening the modal if the registration data has loaded, otherwise…
gregorydlogan Jul 15, 2026
ecace10
Combining the last of the old utils script into the slice, moving sel…
gregorydlogan Jul 23, 2026
5a53c12
Harmonizing formatting to existing file structures
gregorydlogan Jul 23, 2026
5df46f5
Fixing incorrect indentation
gregorydlogan Aug 6, 2026
7b06339
Fixing bad rebase, we only need to dispatch these once and don't need…
gregorydlogan Aug 6, 2026
d68a1ec
These are no longer needed
gregorydlogan Aug 6, 2026
d622436
Moving statistics into its own thing, rather than slapping it into th…
gregorydlogan Aug 6, 2026
9e3731e
Renaming variables
gregorydlogan Aug 6, 2026
bf6b3af
Removing reference to multi-value-yellow, which appears to be gone no…
gregorydlogan Aug 6, 2026
af997aa
Translating notification title as well
gregorydlogan Aug 6, 2026
0d2d6aa
This is no longer needed with 7b06339e33 being applied
gregorydlogan Aug 6, 2026
aed8e9c
statisticsLoaded is no longer needed anywhere. The registration copy…
gregorydlogan Aug 6, 2026
ddb6697
Handling the endpoint spitting out 404s
gregorydlogan Aug 6, 2026
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
46 changes: 34 additions & 12 deletions src/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ 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,
getUserInformation,
Expand All @@ -19,6 +20,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 @@ -51,13 +57,29 @@ const Header = () => {
const healthStatus = useAppSelector(state => getHealthStatus(state));
const errorCounter = useAppSelector(state => getErrorCount(state));
const user = useAppSelector(state => getUserInformation(state));
const registration = useAppSelector(state => getRegistration(state));
const _isRegistering = useAppSelector(state => getIsRegistering(state));
const _agreedLatestToU = useAppSelector(state => getAgreedLatestToU(state));
const orgProperties = useAppSelector(state => getOrgProperties(state));
const displayTerms = (orgProperties["org.opencastproject.admin.display_terms"] || "false").toLowerCase() === "true";

const loadHealthStatus = async () => {
await dispatch(fetchHealthStatus());
};

if (registration == null) {
dispatch(fetchRegistration());
}
Comment thread
gregorydlogan marked this conversation as resolved.
Outdated
// dispatch(fetchLatestToU());
// dispatch(fetchIsUpToDate());

const _getLatestToU = async () => {
await dispatch(fetchLatestToU());
};
const _getIsUpToDate = async () => {
await dispatch(fetchIsUpToDate());
};
Comment thread
Arnei marked this conversation as resolved.
Outdated

const hideMenuHelp = () => {
setMenuHelp(false);
};
Expand Down Expand Up @@ -134,18 +156,18 @@ 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]);
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]);
return (
<>
<header className="primary-header">
Expand Down
11 changes: 11 additions & 0 deletions src/selectors/registrationSelectors.ts
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;
165 changes: 165 additions & 0 deletions src/slices/registrationSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
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.
adopterKey: string,
statisticsKey: string,
organisationName: string,
departmentName: string,
firstName: string,
lastName: string,
email: string,
country: string,
postalCode: string,
street: string,
streetNo: string,
contactMe: boolean,
systemType: string,
allowsStatistics: boolean,
allowsErrorReports: boolean,
dateCreated: string,
dateUpdated: string,
agreedToPolicy: boolean,
registered: boolean,
termsVersionAgreed: string,
deleteMe: boolean,
}

export type Summary = {
general: Registration,
statistics: {
statistics_key: string,
adopter_key: string,
job_count: number,
event_count: number,
series_count: number,
user_count: number,
ca_count: number,
total_minutes: number,
tenant_count: number,
hosts: {
cores: number,
max_load: number,
memory: number,
hostname: string,
disk_space: number,
services: string,
}[],
version: string
}
}

export type RegistrationState = {
registration: Registration | null,
summary: Summary | null,
latestToU: string,
isRegistering: boolean,
Comment thread
Arnei marked this conversation as resolved.
Outdated
agreedToToU: boolean,
error: boolean
};

type Temp = {
Comment thread
gregorydlogan marked this conversation as resolved.
Outdated
registration: Registration | null,
latestToU: string,
};

// Initial state of health status in redux store
const initialState: RegistrationState = {
registration: null,
summary: 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 summary
export const fetchSummary = createAppAsyncThunk("registration/fetchSummary", async () => {
const res = await axios.get<Summary>("/admin-ng/adopter/summary");
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<{
Comment thread
gregorydlogan marked this conversation as resolved.
Outdated
error: RegistrationState["error"],
}>) {
state.error = action.payload.error;
},
},
// These are used for thunks
extraReducers: builder => {
builder
/* .addCase(fetchRegistration.pending, state => {
Comment thread
gregorydlogan marked this conversation as resolved.
Outdated
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(fetchSummary.fulfilled, (state, action: PayloadAction<
Summary
>) => {
state.summary = action.payload;
})
.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;
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
Loading