Add dedicated endpoint for footer info
This commit is contained in:
@@ -159,6 +159,8 @@ public class RequestUriUtils {
|
||||
|| trimmedUri.startsWith(
|
||||
"/api/v1/proprietary/ui-data/login") // Login page config (SSO providers +
|
||||
// enableLogin)
|
||||
|| trimmedUri.startsWith(
|
||||
"/api/v1/ui-data/footer-info") // Public footer configuration
|
||||
|| trimmedUri.startsWith("/v1/api-docs")
|
||||
|| trimmedUri.startsWith("/api/v1/invite/validate")
|
||||
|| trimmedUri.startsWith("/api/v1/invite/accept")
|
||||
|
||||
@@ -58,6 +58,21 @@ public class UIDataController {
|
||||
this.runtimePathConfig = runtimePathConfig;
|
||||
}
|
||||
|
||||
@GetMapping("/footer-info")
|
||||
@Operation(summary = "Get public footer configuration data")
|
||||
public ResponseEntity<FooterData> getFooterData() {
|
||||
FooterData data = new FooterData();
|
||||
data.setAnalyticsEnabled(applicationProperties.getSystem().getEnableAnalytics());
|
||||
data.setTermsAndConditions(applicationProperties.getLegal().getTermsAndConditions());
|
||||
data.setPrivacyPolicy(applicationProperties.getLegal().getPrivacyPolicy());
|
||||
data.setAccessibilityStatement(
|
||||
applicationProperties.getLegal().getAccessibilityStatement());
|
||||
data.setCookiePolicy(applicationProperties.getLegal().getCookiePolicy());
|
||||
data.setImpressum(applicationProperties.getLegal().getImpressum());
|
||||
|
||||
return ResponseEntity.ok(data);
|
||||
}
|
||||
|
||||
@GetMapping("/home")
|
||||
@Operation(summary = "Get home page data")
|
||||
public ResponseEntity<HomeData> getHomeData() {
|
||||
@@ -237,6 +252,16 @@ public class UIDataController {
|
||||
}
|
||||
|
||||
// Data classes
|
||||
@Data
|
||||
public static class FooterData {
|
||||
private Boolean analyticsEnabled;
|
||||
private String termsAndConditions;
|
||||
private String privacyPolicy;
|
||||
private String accessibilityStatement;
|
||||
private String cookiePolicy;
|
||||
private String impressum;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class HomeData {
|
||||
private boolean showSurveyFromDocker;
|
||||
|
||||
-2
@@ -150,7 +150,6 @@ public class ProprietaryUIDataController {
|
||||
|
||||
data.setFirstTimeSetup(isFirstTimeSetup);
|
||||
data.setShowDefaultCredentials(showDefaultCredentials);
|
||||
data.setEnableAnalytics(applicationProperties.getSystem().getEnableAnalytics());
|
||||
|
||||
OAUTH2 oauth = securityProps.getOauth2();
|
||||
|
||||
@@ -492,7 +491,6 @@ public class ProprietaryUIDataController {
|
||||
private boolean altLogin;
|
||||
private boolean firstTimeSetup;
|
||||
private boolean showDefaultCredentials;
|
||||
private Boolean enableAnalytics;
|
||||
}
|
||||
|
||||
@Data
|
||||
|
||||
+2
-1
@@ -299,7 +299,8 @@ public class UserAuthenticationFilter extends OncePerRequestFilter {
|
||||
contextPath + "/api/v1/auth/refresh",
|
||||
contextPath + "/api/v1/auth/me",
|
||||
contextPath + "/api/v1/invite/validate",
|
||||
contextPath + "/api/v1/invite/accept"
|
||||
contextPath + "/api/v1/invite/accept",
|
||||
contextPath + "/api/v1/ui-data/footer-info"
|
||||
};
|
||||
|
||||
for (String pattern : publicApiPatterns) {
|
||||
|
||||
@@ -54,17 +54,17 @@
|
||||
--cc-secondary-color: #b0b0b0;
|
||||
|
||||
--cc-btn-primary-bg: #4dabf7;
|
||||
--cc-btn-primary-color: #2d2d2d;
|
||||
--cc-btn-primary-color: #ffffff;
|
||||
--cc-btn-primary-border-color: #4dabf7;
|
||||
--cc-btn-primary-hover-bg: #3d3d3d;
|
||||
--cc-btn-primary-hover-color: #e5e5e5;
|
||||
--cc-btn-primary-hover-color: #ffffff;
|
||||
--cc-btn-primary-hover-border-color: #3d3d3d;
|
||||
|
||||
--cc-btn-secondary-bg: #3d3d3d;
|
||||
--cc-btn-secondary-color: #e5e5e5;
|
||||
--cc-btn-secondary-color: #ffffff;
|
||||
--cc-btn-secondary-border-color: #3d3d3d;
|
||||
--cc-btn-secondary-hover-bg: #4dabf7;
|
||||
--cc-btn-secondary-hover-color: #2d2d2d;
|
||||
--cc-btn-secondary-hover-color: #ffffff;
|
||||
--cc-btn-secondary-hover-border-color: #4dabf7;
|
||||
|
||||
--cc-separator-border-color: #555555;
|
||||
@@ -196,10 +196,6 @@
|
||||
color: var(--cc-primary-color) !important;
|
||||
}
|
||||
|
||||
#cc-main .cm__btn {
|
||||
color: var(--cc-btn-primary-color) !important;
|
||||
}
|
||||
|
||||
#cc-main .cm__footer {
|
||||
color: var(--cc-primary-color) !important;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Flex } from '@mantine/core';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCookieConsent } from '@app/hooks/useCookieConsent';
|
||||
import { useFooterInfo } from '@app/hooks/useFooterInfo';
|
||||
|
||||
interface FooterProps {
|
||||
privacyPolicy?: string;
|
||||
@@ -18,11 +19,26 @@ export default function Footer({
|
||||
accessibilityStatement,
|
||||
cookiePolicy,
|
||||
impressum,
|
||||
analyticsEnabled = false,
|
||||
analyticsEnabled,
|
||||
forceLightMode = false
|
||||
}: FooterProps) {
|
||||
const { t } = useTranslation();
|
||||
const { showCookiePreferences } = useCookieConsent({ analyticsEnabled, forceLightMode });
|
||||
const { footerInfo } = useFooterInfo();
|
||||
|
||||
console.log('[Footer] Props analyticsEnabled:', analyticsEnabled);
|
||||
console.log('[Footer] Fetched footerInfo:', footerInfo);
|
||||
|
||||
// Use props if provided, otherwise fall back to fetched footer info
|
||||
const finalAnalyticsEnabled = analyticsEnabled ?? footerInfo?.analyticsEnabled ?? false;
|
||||
const finalPrivacyPolicy = privacyPolicy ?? footerInfo?.privacyPolicy;
|
||||
const finalTermsAndConditions = termsAndConditions ?? footerInfo?.termsAndConditions;
|
||||
const finalAccessibilityStatement = accessibilityStatement ?? footerInfo?.accessibilityStatement;
|
||||
const finalCookiePolicy = cookiePolicy ?? footerInfo?.cookiePolicy;
|
||||
const finalImpressum = impressum ?? footerInfo?.impressum;
|
||||
|
||||
console.log('[Footer] Final analyticsEnabled:', finalAnalyticsEnabled);
|
||||
|
||||
const { showCookiePreferences } = useCookieConsent({ analyticsEnabled: finalAnalyticsEnabled, forceLightMode });
|
||||
|
||||
// Default URLs
|
||||
const defaultTermsUrl = "https://www.stirling.com/legal/terms-of-service";
|
||||
@@ -30,9 +46,9 @@ export default function Footer({
|
||||
const defaultAccessibilityUrl = "https://www.stirling.com/accessibility";
|
||||
|
||||
// Use provided URLs or fall back to defaults
|
||||
const finalTermsUrl = termsAndConditions || defaultTermsUrl;
|
||||
const finalPrivacyUrl = privacyPolicy || defaultPrivacyUrl;
|
||||
const finalAccessibilityUrl = accessibilityStatement || defaultAccessibilityUrl;
|
||||
const finalTermsUrl = finalTermsAndConditions || defaultTermsUrl;
|
||||
const finalPrivacyUrl = finalPrivacyPolicy || defaultPrivacyUrl;
|
||||
const finalAccessibilityUrl = finalAccessibilityStatement || defaultAccessibilityUrl;
|
||||
|
||||
// Helper to check if a value is valid (not null/undefined/empty string)
|
||||
const isValidLink = (link?: string) => link && link.trim().length > 0;
|
||||
@@ -87,27 +103,27 @@ export default function Footer({
|
||||
>
|
||||
{t('legal.accessibility', 'Accessibility')}
|
||||
</a>
|
||||
{isValidLink(cookiePolicy) && (
|
||||
{isValidLink(finalCookiePolicy) && (
|
||||
<a
|
||||
className="footer-link px-3"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={cookiePolicy}
|
||||
href={finalCookiePolicy}
|
||||
>
|
||||
{t('legal.cookie', 'Cookie Policy')}
|
||||
</a>
|
||||
)}
|
||||
{isValidLink(impressum) && (
|
||||
{isValidLink(finalImpressum) && (
|
||||
<a
|
||||
className="footer-link px-3"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={impressum}
|
||||
href={finalImpressum}
|
||||
>
|
||||
{t('legal.impressum', 'Impressum')}
|
||||
</a>
|
||||
)}
|
||||
{analyticsEnabled && (
|
||||
{finalAnalyticsEnabled && (
|
||||
<button
|
||||
className="footer-link px-3"
|
||||
id="cookieBanner"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
|
||||
export interface FooterInfo {
|
||||
analyticsEnabled?: boolean;
|
||||
termsAndConditions?: string;
|
||||
privacyPolicy?: string;
|
||||
accessibilityStatement?: string;
|
||||
cookiePolicy?: string;
|
||||
impressum?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook to fetch public footer configuration data.
|
||||
* This endpoint is always accessible without authentication.
|
||||
*/
|
||||
export function useFooterInfo() {
|
||||
const [footerInfo, setFooterInfo] = useState<FooterInfo | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<Error | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchFooterInfo = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
console.log('[useFooterInfo] Fetching footer info from /api/v1/ui-data/footer-info...');
|
||||
const response = await apiClient.get<FooterInfo>('/api/v1/ui-data/footer-info', {
|
||||
suppressErrorToast: true,
|
||||
} as any);
|
||||
console.log('[useFooterInfo] Footer info received:', response.data);
|
||||
setFooterInfo(response.data);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('[useFooterInfo] Failed to fetch footer info:', err);
|
||||
setError(err as Error);
|
||||
// Set defaults on error
|
||||
setFooterInfo({
|
||||
analyticsEnabled: false,
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchFooterInfo();
|
||||
}, []);
|
||||
|
||||
return { footerInfo, loading, error };
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import buildLoginSlides from '@app/components/shared/loginSlides';
|
||||
import styles from '@app/routes/authShared/AuthLayout.module.css';
|
||||
import { useLogoVariant } from '@app/hooks/useLogoVariant';
|
||||
import Footer from '@app/components/shared/Footer';
|
||||
import apiClient from '@app/services/apiClient';
|
||||
|
||||
interface AuthLayoutProps {
|
||||
children: React.ReactNode
|
||||
@@ -15,23 +14,9 @@ export default function AuthLayout({ children }: AuthLayoutProps) {
|
||||
const { t } = useTranslation();
|
||||
const cardRef = useRef<HTMLDivElement | null>(null);
|
||||
const [hideRightPanel, setHideRightPanel] = useState(false);
|
||||
const [enableAnalytics, setEnableAnalytics] = useState(false);
|
||||
const logoVariant = useLogoVariant();
|
||||
const imageSlides = useMemo(() => buildLoginSlides(logoVariant, t), [logoVariant, t]);
|
||||
|
||||
// Fetch enableAnalytics from login endpoint
|
||||
useEffect(() => {
|
||||
const fetchLoginData = async () => {
|
||||
try {
|
||||
const response = await apiClient.get('/api/v1/proprietary/ui-data/login');
|
||||
setEnableAnalytics(response.data.enableAnalytics ?? false);
|
||||
} catch (err) {
|
||||
console.error('[AuthLayout] Failed to fetch login data:', err);
|
||||
}
|
||||
};
|
||||
fetchLoginData();
|
||||
}, []);
|
||||
|
||||
// Force light mode on auth pages
|
||||
useEffect(() => {
|
||||
const htmlElement = document.documentElement;
|
||||
@@ -84,10 +69,7 @@ export default function AuthLayout({ children }: AuthLayoutProps) {
|
||||
)}
|
||||
</div>
|
||||
<div style={{ position: 'fixed', bottom: 0, left: 0, right: 0, width: '100%', zIndex: 10 }}>
|
||||
<Footer
|
||||
analyticsEnabled={enableAnalytics}
|
||||
forceLightMode={true}
|
||||
/>
|
||||
<Footer forceLightMode={true} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user