fix paths and frontend validation

This commit is contained in:
EthanHealy01
2025-11-23 23:52:22 +00:00
parent 7fd44cc2a0
commit ba6909565d
12 changed files with 400 additions and 17 deletions

View File

@@ -0,0 +1,34 @@
import { createContext, useContext, type ReactNode } from 'react';
interface AuthContextValue {
session: null;
user: null;
loading: boolean;
error: null;
signOut: () => Promise<void>;
refreshSession: () => Promise<void>;
}
const defaultValue: AuthContextValue = {
session: null,
user: null,
loading: false,
error: null,
signOut: async () => {},
refreshSession: async () => {},
};
const AuthContext = createContext<AuthContextValue>(defaultValue);
export function AuthProvider({ children }: { children: ReactNode }) {
return <AuthContext.Provider value={defaultValue}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthContextValue {
return useContext(AuthContext);
}
export function useAuthDebug(): AuthContextValue {
return useAuth();
}

View File

@@ -11,7 +11,7 @@ import {
ONBOARDING_SESSION_EVENT,
SERVER_LICENSE_REQUEST_EVENT,
type ServerLicenseRequestPayload,
} from '@core/constants/events';
} from '@app/constants/events';
import { useServerExperience } from '@app/hooks/useServerExperience';
interface InitialModalHandlers {

View File

@@ -1,6 +1,6 @@
import React from 'react';
import styles from './AnimatedSlideBackground.module.css';
import { AnimatedSlideBackgroundProps } from '../../../types/types';
import styles from '@app/components/onboarding/slides/AnimatedSlideBackground.module.css';
import { AnimatedSlideBackgroundProps } from '@app/types/types';
type CircleStyles = React.CSSProperties & {
'--circle-move-x'?: string;

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { useTranslation } from 'react-i18next';
import { SlideConfig } from '../../../types/types';
import { UNIFIED_CIRCLE_CONFIG } from './unifiedBackgroundConfig';
import { DesktopInstallTitle, type OSOption } from './DesktopInstallTitle';
import { SlideConfig } from '@app/types/types';
import { UNIFIED_CIRCLE_CONFIG } from '@app/components/onboarding/slides/unifiedBackgroundConfig';
import { DesktopInstallTitle, type OSOption } from '@app/components/onboarding/slides/DesktopInstallTitle';
export type { OSOption };

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { SlideConfig, LicenseNotice } from '../../../types/types';
import { UNIFIED_CIRCLE_CONFIG } from './unifiedBackgroundConfig';
import { SlideConfig, LicenseNotice } from '@app/types/types';
import { UNIFIED_CIRCLE_CONFIG } from '@app/components/onboarding/slides/unifiedBackgroundConfig';
interface PlanOverviewSlideProps {
isAdmin: boolean;

View File

@@ -1,9 +1,9 @@
import React from 'react';
import { Select } from '@mantine/core';
import styles from '../InitialOnboardingModal/InitialOnboardingModal.module.css';
import { SlideConfig } from '../../../types/types';
import { SlideConfig } from '@app/types/types';
import LocalIcon from '@app/components/shared/LocalIcon';
import { UNIFIED_CIRCLE_CONFIG } from './unifiedBackgroundConfig';
import { UNIFIED_CIRCLE_CONFIG } from '@app/components/onboarding/slides/unifiedBackgroundConfig';
import i18n from '@app/i18n';
interface SecurityCheckSlideProps {

View File

@@ -1,7 +1,7 @@
import React from 'react';
import { Trans } from 'react-i18next';
import { SlideConfig, LicenseNotice } from '../../../types/types';
import { UNIFIED_CIRCLE_CONFIG } from './unifiedBackgroundConfig';
import { SlideConfig, LicenseNotice } from '@app/types/types';
import { UNIFIED_CIRCLE_CONFIG } from '@app/components/onboarding/slides/unifiedBackgroundConfig';
import i18n from '@app/i18n';
interface ServerLicenseSlideProps {

View File

@@ -1,8 +1,8 @@
import React from 'react';
import { useTranslation, Trans } from 'react-i18next';
import { SlideConfig } from '../../../types/types';
import styles from '../InitialOnboardingModal/InitialOnboardingModal.module.css';
import { UNIFIED_CIRCLE_CONFIG } from './unifiedBackgroundConfig';
import { SlideConfig } from '@app/types/types';
import styles from '@app/components/onboarding/InitialOnboardingModal/InitialOnboardingModal.module.css';
import { UNIFIED_CIRCLE_CONFIG } from '@app/components/onboarding/slides/unifiedBackgroundConfig';
function WelcomeSlideTitle() {
const { t } = useTranslation();

View File

@@ -1,4 +1,4 @@
import { AnimatedCircleConfig } from '../../../types/types';
import { AnimatedCircleConfig } from '@app/types/types';
/**
* Unified circle background configuration used across all onboarding slides.

View File

@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
import {
UPGRADE_BANNER_ALERT_EVENT,
type UpgradeBannerAlertPayload,
} from '@core/constants/events';
} from '@app/constants/events';
export interface LicenseAlertState {
active: boolean;

View File

@@ -0,0 +1,157 @@
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useAppConfig } from '@app/contexts/AppConfigContext';
const SELF_REPORTED_ADMIN_KEY = 'stirling-self-reported-admin';
const FREE_TIER_LIMIT = 5;
type UserCountSource = 'admin' | 'estimate' | 'unknown';
export type ServerScenarioKey =
| 'unknown'
| 'licensed'
| 'no-login-user-under-limit-no-license'
| 'no-login-admin-under-limit-no-license'
| 'no-login-user-over-limit-no-license'
| 'no-login-admin-over-limit-no-license'
| 'login-user-under-limit-no-license'
| 'login-admin-under-limit-no-license'
| 'login-user-over-limit-no-license'
| 'login-admin-over-limit-no-license';
export interface ServerExperienceValue {
loginEnabled: boolean;
configIsAdmin: boolean;
effectiveIsAdmin: boolean;
selfReportedAdmin: boolean;
isAuthenticated: boolean;
isNewServer: boolean | null;
isNewUser: boolean | null;
premiumEnabled: boolean | null;
license: string | undefined;
runningProOrHigher: boolean | undefined;
runningEE: boolean | undefined;
hasPaidLicense: boolean;
licenseKeyValid: boolean | null;
licenseLoading: boolean;
licenseInfoAvailable: boolean;
totalUsers: number | null;
weeklyActiveUsers: number | null;
userCountLoading: boolean;
userCountError: string | null;
userCountSource: UserCountSource;
userCountResolved: boolean;
overFreeTierLimit: boolean | null;
freeTierLimit: number;
refreshUserCounts: () => Promise<void>;
setSelfReportedAdmin: (value: boolean) => void;
scenarioKey: ServerScenarioKey;
}
function readSelfReportedAdmin(): boolean {
if (typeof window === 'undefined') {
return false;
}
try {
return window.localStorage.getItem(SELF_REPORTED_ADMIN_KEY) === 'true';
} catch {
return false;
}
}
export function useServerExperience(): ServerExperienceValue {
const { config } = useAppConfig();
const [selfReportedAdmin, setSelfReportedAdminState] = useState<boolean>(readSelfReportedAdmin);
const loginEnabled = config?.enableLogin !== false;
const configIsAdmin = Boolean(config?.isAdmin);
const effectiveIsAdmin = configIsAdmin || (!loginEnabled && selfReportedAdmin);
const hasPaidLicense = config?.license === 'PRO' || config?.license === 'ENTERPRISE';
const setSelfReportedAdmin = useCallback((value: boolean) => {
setSelfReportedAdminState(value);
if (typeof window === 'undefined') {
return;
}
try {
if (value) {
window.localStorage.setItem(SELF_REPORTED_ADMIN_KEY, 'true');
} else {
window.localStorage.removeItem(SELF_REPORTED_ADMIN_KEY);
}
} catch {
// ignore storage write failures
}
}, []);
useEffect(() => {
if (typeof window === 'undefined') {
return;
}
const handleStorage = (event: StorageEvent) => {
if (event.key === SELF_REPORTED_ADMIN_KEY) {
setSelfReportedAdminState(event.newValue === 'true');
}
};
window.addEventListener('storage', handleStorage);
return () => window.removeEventListener('storage', handleStorage);
}, []);
useEffect(() => {
if (config?.isNewServer && !loginEnabled && !selfReportedAdmin) {
setSelfReportedAdmin(true);
}
}, [config?.isNewServer, loginEnabled, selfReportedAdmin, setSelfReportedAdmin]);
const scenarioKey: ServerScenarioKey = useMemo(() => {
if (hasPaidLicense) {
return 'licensed';
}
return 'unknown';
}, [hasPaidLicense]);
const value = useMemo<ServerExperienceValue>(() => ({
loginEnabled,
configIsAdmin,
effectiveIsAdmin,
selfReportedAdmin,
isAuthenticated: false,
isNewServer: config?.isNewServer ?? null,
isNewUser: config?.isNewUser ?? null,
premiumEnabled: config?.premiumEnabled ?? null,
license: config?.license,
runningProOrHigher: config?.runningProOrHigher,
runningEE: config?.runningEE,
hasPaidLicense,
licenseKeyValid: config?.premiumEnabled ?? null,
licenseLoading: false,
licenseInfoAvailable: false,
totalUsers: null,
weeklyActiveUsers: null,
userCountLoading: false,
userCountError: null,
userCountSource: 'unknown',
userCountResolved: false,
overFreeTierLimit: null,
freeTierLimit: FREE_TIER_LIMIT,
refreshUserCounts: async () => {},
setSelfReportedAdmin,
scenarioKey,
}), [
config?.isNewServer,
config?.isNewUser,
config?.license,
config?.premiumEnabled,
config?.runningEE,
config?.runningProOrHigher,
configIsAdmin,
effectiveIsAdmin,
hasPaidLicense,
loginEnabled,
scenarioKey,
selfReportedAdmin,
setSelfReportedAdmin,
]);
return value;
}

View File

@@ -0,0 +1,192 @@
import type { AppConfig } from '@app/contexts/AppConfigContext';
interface LicenseInfo {
licenseType: string;
enabled: boolean;
maxUsers: number;
hasKey: boolean;
}
interface WauResponse {
trackingSince: string;
daysOnline: number;
totalUniqueBrowsers: number;
weeklyActiveUsers: number;
}
interface AdminUsageResponse {
totalUsers?: number;
}
interface SimulationScenario {
label: string;
appConfig: AppConfig;
wau?: WauResponse;
adminUsage?: AdminUsageResponse;
licenseInfo: LicenseInfo;
}
const DEV_TESTING_MODE = false;
const SIMULATION_INDEX = 0;
const FREE_LICENSE_INFO: LicenseInfo = {
licenseType: 'NORMAL',
enabled: false,
maxUsers: 5,
hasKey: false,
};
const BASE_NO_LOGIN_CONFIG: AppConfig = {
enableAnalytics: true,
appVersion: '2.0.0',
serverCertificateEnabled: false,
enableAlphaFunctionality: false,
serverPort: 8080,
premiumEnabled: false,
runningProOrHigher: false,
runningEE: false,
enableLogin: false,
activeSecurity: false,
languages: [],
contextPath: '/',
license: 'NORMAL',
baseUrl: 'http://localhost',
enableEmailInvites: true,
};
const BASE_LOGIN_CONFIG: AppConfig = {
...BASE_NO_LOGIN_CONFIG,
enableLogin: true,
activeSecurity: true,
};
const SIMULATION_SCENARIOS: SimulationScenario[] = [
{
label: 'no-login-user-under-limit (no-license)',
appConfig: {
...BASE_NO_LOGIN_CONFIG,
},
wau: {
trackingSince: '2025-11-18T23:20:12.520884200Z',
daysOnline: 0,
totalUniqueBrowsers: 3,
weeklyActiveUsers: 3,
},
licenseInfo: { ...FREE_LICENSE_INFO },
},
{
label: 'no-login-admin-under-limit (no-license)',
appConfig: {
...BASE_NO_LOGIN_CONFIG,
},
wau: {
trackingSince: '2025-10-01T00:00:00Z',
daysOnline: 14,
totalUniqueBrowsers: 4,
weeklyActiveUsers: 4,
},
licenseInfo: { ...FREE_LICENSE_INFO },
},
{
label: 'no-login-user-over-limit (no-license)',
appConfig: {
...BASE_NO_LOGIN_CONFIG,
},
wau: {
trackingSince: '2025-09-01T00:00:00Z',
daysOnline: 30,
totalUniqueBrowsers: 12,
weeklyActiveUsers: 9,
},
licenseInfo: { ...FREE_LICENSE_INFO },
},
{
label: 'no-login-admin-over-limit (no-license)',
appConfig: {
...BASE_NO_LOGIN_CONFIG,
},
wau: {
trackingSince: '2025-08-15T00:00:00Z',
daysOnline: 45,
totalUniqueBrowsers: 18,
weeklyActiveUsers: 12,
},
licenseInfo: { ...FREE_LICENSE_INFO },
},
{
label: 'login-user-under-limit (no-license)',
appConfig: {
...BASE_LOGIN_CONFIG,
isAdmin: false,
},
adminUsage: {
totalUsers: 3,
},
licenseInfo: { ...FREE_LICENSE_INFO },
},
{
label: 'login-admin-under-limit (no-license)',
appConfig: {
...BASE_LOGIN_CONFIG,
isAdmin: true,
},
adminUsage: {
totalUsers: 4,
},
licenseInfo: { ...FREE_LICENSE_INFO },
},
{
label: 'login-user-over-limit (no-license)',
appConfig: {
...BASE_LOGIN_CONFIG,
isAdmin: false,
},
adminUsage: {
totalUsers: 12,
},
licenseInfo: { ...FREE_LICENSE_INFO },
},
{
label: 'login-admin-over-limit (no-license)',
appConfig: {
...BASE_LOGIN_CONFIG,
isAdmin: true,
},
adminUsage: {
totalUsers: 57,
},
licenseInfo: { ...FREE_LICENSE_INFO },
},
];
function getActiveScenario(): SimulationScenario | null {
if (!DEV_TESTING_MODE) {
return null;
}
const scenario = SIMULATION_SCENARIOS[SIMULATION_INDEX];
if (!scenario) {
console.warn('[Simulation] SIMULATION_INDEX out of range, using live backend.');
return null;
}
console.warn(`[Simulation] Using scenario #${SIMULATION_INDEX} (${scenario.label}).`);
return scenario;
}
export function getSimulatedAppConfig(): AppConfig | null {
return getActiveScenario()?.appConfig ?? null;
}
export function getSimulatedWauResponse(): WauResponse | null {
return getActiveScenario()?.wau ?? null;
}
export function getSimulatedAdminUsage(): AdminUsageResponse | null {
return getActiveScenario()?.adminUsage ?? null;
}
export function getSimulatedLicenseInfo(): LicenseInfo | null {
return getActiveScenario()?.licenseInfo ?? null;
}
export const DEV_TESTING_ENABLED = DEV_TESTING_MODE;