diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/components/MpMenuApp/MpMenuApp.tsx b/ext/cfx-ui/src/cfx/apps/mpMenu/components/MpMenuApp/MpMenuApp.tsx
index 415aaf4f39..85494df86d 100644
--- a/ext/cfx-ui/src/cfx/apps/mpMenu/components/MpMenuApp/MpMenuApp.tsx
+++ b/ext/cfx-ui/src/cfx/apps/mpMenu/components/MpMenuApp/MpMenuApp.tsx
@@ -1,3 +1,4 @@
+import { observer } from "mobx-react-lite";
import { Outlet } from 'react-router-dom';
import { NavBar } from 'cfx/apps/mpMenu/parts/NavBar/NavBar';
import { AuthFlyout } from 'cfx/apps/mpMenu/parts/AuthFlyout/AuthFlyout';
@@ -7,13 +8,14 @@ import { LegacyConnectingModal } from 'cfx/apps/mpMenu/parts/LegacyConnectingMod
import { LegacyUiMessageModal } from 'cfx/apps/mpMenu/parts/LegacyUiMessageModal/LegacyUiMessageModal';
import { ServerBoostModal } from 'cfx/apps/mpMenu/parts/ServerBoostModal/ServerBoostModal';
import { AcitivityItemMediaViewerProvider } from '../AcitivityItemMediaViewer/AcitivityItemMediaViewer.context';
+import { LegalAccepter } from "cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter";
+import { useLegalService } from "cfx/apps/mpMenu/services/legal/legal.service";
import { NavigationTracker } from './PageViewTracker';
import s from './MpMenuApp.module.scss';
-export function MpMenuApp() {
+function MpMenuUI() {
return (
-
-
+ <>
@@ -31,6 +33,22 @@ export function MpMenuApp() {
-
+ >
);
}
+
+export const MpMenuApp = observer(function MpMenuApp() {
+ const legalService = useLegalService();
+
+ const mainUI = legalService.hasUserAccepted
+ ?
+ : ;
+
+ return (
+
+
+
+ {mainUI}
+
+ );
+});
diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/index.tsx b/ext/cfx-ui/src/cfx/apps/mpMenu/index.tsx
index f998107c63..fad284fc84 100644
--- a/ext/cfx-ui/src/cfx/apps/mpMenu/index.tsx
+++ b/ext/cfx-ui/src/cfx/apps/mpMenu/index.tsx
@@ -40,8 +40,10 @@ import { IServersConnectService } from 'cfx/common/services/servers/serversConne
import { Handle404 } from './pages/404';
import { registerHomeScreenServerList } from './services/servers/list/HomeScreenServerList.service';
import { registerSentryService } from './services/sentry/sentry.service';
+import { registerLegalService } from "./services/legal/legal.service";
import { SentryLogProvider } from './services/sentry/sentryLogProvider';
-import { animationFrame, idleCallback, timeout } from 'cfx/utils/async';
+import { timeout } from 'cfx/utils/async';
+import { shutdownLoadingSplash } from "./utils/loadingSplash";
startBrowserApp({
defineServices(container) {
@@ -56,6 +58,8 @@ startBrowserApp({
MatomoAnalyticsProvider,
]);
+ registerLegalService(container);
+
registerSettingsService(container, {
settings: GAME_SETTINGS,
defaultSettingsCategoryId: DEFAULT_GAME_SETTINGS_CATEGORY_ID,
@@ -142,29 +146,9 @@ startBrowserApp({
mpMenu.invokeNative('executeCommand', 'nui_devtools mpMenu');
}
- mpMenu.invokeNative('getMinModeInfo');
+ mpMenu.showGameWindow();
// Not using await here so app won't wait for this to end
- timeout(1000).then(animationFrame).then(() => {
- const $loader = document.getElementById('loader');
- if (!$loader) {
- console.error('No #loader found, did it get deleted from index.html?');
- return;
- }
-
- $loader.classList.add('hide');
-
- const $loaderMask = $loader.querySelector('#loader-mask');
- if (!$loaderMask) {
- console.error('No #loader-mask found, did it get deleted from index.html?');
- return;
- }
-
- $loaderMask.addEventListener('animationend', async () => {
- await idleCallback(1000);
-
- $loader.parentNode?.removeChild($loader);
- });
- });
+ timeout(1000).then(shutdownLoadingSplash);
},
});
diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/mpMenu.ts b/ext/cfx-ui/src/cfx/apps/mpMenu/mpMenu.ts
index 4e0b81a8ee..424492c562 100644
--- a/ext/cfx-ui/src/cfx/apps/mpMenu/mpMenu.ts
+++ b/ext/cfx-ui/src/cfx/apps/mpMenu/mpMenu.ts
@@ -60,7 +60,9 @@ class NicknameStore {
export namespace mpMenu {
export function invokeNative(native: string, arg = '') {
- console.log('Invoking native', native, JSON.stringify(arg));
+ if (__CFXUI_DEV__) {
+ console.log('Invoking native', native, JSON.stringify(arg));
+ }
nuiWindow.invokeNative(native, arg);
}
@@ -79,6 +81,21 @@ export namespace mpMenu {
invokeNative('openUrl', url);
}
+ let showGameWindowRequested = false;
+ export function showGameWindow() {
+ if (showGameWindowRequested) {
+ return;
+ }
+
+ showGameWindowRequested = true;
+
+ invokeNative('getMinModeInfo');
+ }
+
+ export function exit() {
+ invokeNative('exit');
+ }
+
export const systemLanguages = [...new Set(nuiWindow.nuiSystemLanguages || ['en-us'])];
export const computerName = new AwaitableValue('');
@@ -152,7 +169,9 @@ export namespace mpMenu {
window.addEventListener('message', (event: MessageEvent) => {
- console.log('[WNDMSG]', event.data);
+ if (__CFXUI_DEV__) {
+ console.log('[WNDMSG]', event.data);
+ }
const { data } = event;
diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.module.scss b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.module.scss
new file mode 100644
index 0000000000..b207162a35
--- /dev/null
+++ b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.module.scss
@@ -0,0 +1,15 @@
+.root {
+ width: min(max(calc(#{ui.viewport-width()} * .5), 500px), #{ui.viewport-width()});
+ height: ui.viewport-height();
+
+ box-shadow: 0 0 0 2px ui.color-token('island-border') inset;
+
+ @include ui.border-radius();
+ background-color: ui.color-token('flyout-backdrop-background');
+
+ .iframe {
+ width: 100%;
+ height: 100%;
+ border: none;
+ }
+}
diff --git a/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.tsx b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.tsx
new file mode 100644
index 0000000000..345efb2bde
--- /dev/null
+++ b/ext/cfx-ui/src/cfx/apps/mpMenu/parts/LegalAccepter/LegalAccepter.tsx
@@ -0,0 +1,71 @@
+import { observer } from "mobx-react-lite";
+import { mpMenu } from "../../mpMenu";
+import { Pad } from "cfx/ui/Layout/Pad/Pad";
+import { Flex } from "cfx/ui/Layout/Flex/Flex";
+import { TextBlock } from "cfx/ui/Text/Text";
+import { FlexRestricter } from "cfx/ui/Layout/Flex/FlexRestricter";
+import { Button } from "cfx/ui/Button/Button";
+import { Icons } from "cfx/ui/Icons";
+import { Title } from "cfx/ui/Title/Title";
+import { Icon } from "cfx/ui/Icon/Icon";
+import { CurrentGameBrand } from "cfx/base/gameRuntime";
+import { useLegalService } from "cfx/apps/mpMenu/services/legal/legal.service";
+import s from './LegalAccepter.module.scss';
+
+export const LegalAccepter = observer(function TOSAccepter() {
+ const legalService = useLegalService();
+
+ return (
+
+ {/* The reason for the reverse order here is so that the "I Accept" button captures focus first when/if user press Tab key */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Terms of Service
+
+
+
+
+ Last modified: {legalService.CURRENT_TOS_VERSION}
+
+
+
+
+ Open the Terms of Service in your browser {Icons.externalLink}
+
+
+
+
+
+
+