-
) : leftPanelView === 'toolPicker' ? (
// Tool Picker View
-
+
+
{/* Close Button - Only show in preview mode */}
{onClose && previewFile && (
void;
+ show: (show?: boolean) => void;
+ };
+ }
+}
+
+interface CookieConsentConfig {
+ analyticsEnabled?: boolean;
+}
+
+export const useCookieConsent = ({ analyticsEnabled = false }: CookieConsentConfig = {}) => {
+ const { t } = useTranslation();
+ const [isInitialized, setIsInitialized] = useState(false);
+
+ useEffect(() => {
+ if (!analyticsEnabled) {
+ console.log('Cookie consent not enabled - analyticsEnabled is false');
+ return;
+ }
+
+ // Prevent double initialization
+ if (window.CookieConsent) {
+ setIsInitialized(true);
+ // Force show the modal if it exists but isn't visible
+ setTimeout(() => {
+ window.CookieConsent.show();
+ }, 100);
+ return;
+ }
+
+ // Load the cookie consent CSS files first
+ const mainCSS = document.createElement('link');
+ mainCSS.rel = 'stylesheet';
+ mainCSS.href = '/css/cookieconsent.css';
+ document.head.appendChild(mainCSS);
+
+ const customCSS = document.createElement('link');
+ customCSS.rel = 'stylesheet';
+ customCSS.href = '/css/cookieconsentCustomisation.css';
+ document.head.appendChild(customCSS);
+
+ // Load the cookie consent library
+ const script = document.createElement('script');
+ script.src = '/js/thirdParty/cookieconsent.umd.js';
+ script.onload = () => {
+ // Small delay to ensure DOM is ready
+ setTimeout(() => {
+
+ // Detect current theme and set appropriate mode
+ const detectTheme = () => {
+ const mantineScheme = document.documentElement.getAttribute('data-mantine-color-scheme');
+ const hasLightClass = document.documentElement.classList.contains('light');
+ const hasDarkClass = document.documentElement.classList.contains('dark');
+ const systemPrefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+
+ // Priority: Mantine attribute > CSS classes > system preference
+ let isDarkMode = false;
+
+ if (mantineScheme) {
+ isDarkMode = mantineScheme === 'dark';
+ } else if (hasLightClass) {
+ isDarkMode = false;
+ } else if (hasDarkClass) {
+ isDarkMode = true;
+ } else {
+ isDarkMode = systemPrefersDark;
+ }
+
+ // Always explicitly set or remove the class
+ document.documentElement.classList.toggle('cc--darkmode', isDarkMode);
+
+ return isDarkMode;
+ };
+
+ // Initial theme detection with slight delay to ensure DOM is ready
+ setTimeout(() => {
+ detectTheme();
+ }, 50);
+
+ // Check if CookieConsent is available
+ if (!window.CookieConsent) {
+ console.error('CookieConsent is not available on window object');
+ return;
+ }
+
+ // Listen for theme changes
+ const themeObserver = new MutationObserver((mutations) => {
+ mutations.forEach((mutation) => {
+ if (mutation.type === 'attributes' &&
+ (mutation.attributeName === 'data-mantine-color-scheme' ||
+ mutation.attributeName === 'class')) {
+ detectTheme();
+ }
+ });
+ });
+
+ themeObserver.observe(document.documentElement, {
+ attributes: true,
+ attributeFilter: ['data-mantine-color-scheme', 'class']
+ });
+
+
+ // Initialize cookie consent with full configuration
+ try {
+ window.CookieConsent.run({
+ autoShow: true,
+ hideFromBots: false,
+ guiOptions: {
+ consentModal: {
+ layout: "bar",
+ position: "bottom",
+ equalWeightButtons: true,
+ flipButtons: true
+ },
+ preferencesModal: {
+ layout: "box",
+ position: "right",
+ equalWeightButtons: true,
+ flipButtons: true
+ }
+ },
+ categories: {
+ necessary: {
+ readOnly: true
+ },
+ analytics: {}
+ },
+ language: {
+ default: "en",
+ translations: {
+ en: {
+ consentModal: {
+ title: t('cookieBanner.popUp.title', 'How we use Cookies'),
+ description: t('cookieBanner.popUp.description.1', 'We use cookies and other technologies to make Stirling PDF work better for you—helping us improve our tools and keep building features you\'ll love.') +
+ "
" +
+ t('cookieBanner.popUp.description.2', 'If you\'d rather not, clicking \'No Thanks\' will only enable the essential cookies needed to keep things running smoothly.'),
+ acceptAllBtn: t('cookieBanner.popUp.acceptAllBtn', 'Okay'),
+ acceptNecessaryBtn: t('cookieBanner.popUp.acceptNecessaryBtn', 'No Thanks'),
+ showPreferencesBtn: t('cookieBanner.popUp.showPreferencesBtn', 'Manage preferences'),
+ },
+ preferencesModal: {
+ title: t('cookieBanner.preferencesModal.title', 'Consent Preferences Center'),
+ acceptAllBtn: t('cookieBanner.preferencesModal.acceptAllBtn', 'Accept all'),
+ acceptNecessaryBtn: t('cookieBanner.preferencesModal.acceptNecessaryBtn', 'Reject all'),
+ savePreferencesBtn: t('cookieBanner.preferencesModal.savePreferencesBtn', 'Save preferences'),
+ closeIconLabel: t('cookieBanner.preferencesModal.closeIconLabel', 'Close modal'),
+ serviceCounterLabel: t('cookieBanner.preferencesModal.serviceCounterLabel', 'Service|Services'),
+ sections: [
+ {
+ title: t('cookieBanner.preferencesModal.subtitle', 'Cookie Usage'),
+ description: t('cookieBanner.preferencesModal.description.1', 'Stirling PDF uses cookies and similar technologies to enhance your experience and understand how our tools are used. This helps us improve performance, develop the features you care about, and provide ongoing support to our users.') +
+ "
" +
+ t('cookieBanner.preferencesModal.description.2', 'Stirling PDF cannot—and will never—track or access the content of the documents you use.') +
+ " " +
+ t('cookieBanner.preferencesModal.description.3', 'Your privacy and trust are at the core of what we do.') +
+ ""
+ },
+ {
+ title: t('cookieBanner.preferencesModal.necessary.title.1', 'Strictly Necessary Cookies') +
+ "" +
+ t('cookieBanner.preferencesModal.necessary.title.2', 'Always Enabled') +
+ "",
+ description: t('cookieBanner.preferencesModal.necessary.description', 'These cookies are essential for the website to function properly. They enable core features like setting your privacy preferences, logging in, and filling out forms—which is why they can\'t be turned off.'),
+ linkedCategory: "necessary"
+ },
+ {
+ title: t('cookieBanner.preferencesModal.analytics.title', 'Analytics'),
+ description: t('cookieBanner.preferencesModal.analytics.description', 'These cookies help us understand how our tools are being used, so we can focus on building the features our community values most. Rest assured—Stirling PDF cannot and will never track the content of the documents you work with.'),
+ linkedCategory: "analytics"
+ }
+ ]
+ }
+ }
+ }
+ }
+ });
+
+ // Force show after initialization
+ setTimeout(() => {
+ window.CookieConsent.show();
+
+ // Debug: Check if modal elements exist
+ const ccMain = document.getElementById('cc-main');
+ const consentModal = document.querySelector('.cm-wrapper');
+
+ }, 200);
+
+ } catch (error) {
+ console.error('Error initializing CookieConsent:', error);
+ }
+ setIsInitialized(true);
+ }, 100); // Small delay to ensure DOM is ready
+ };
+
+ script.onerror = () => {
+ console.error('Failed to load cookie consent library');
+ };
+
+ document.head.appendChild(script);
+
+ return () => {
+ // Cleanup script and CSS when component unmounts
+ if (document.head.contains(script)) {
+ document.head.removeChild(script);
+ }
+ if (document.head.contains(mainCSS)) {
+ document.head.removeChild(mainCSS);
+ }
+ if (document.head.contains(customCSS)) {
+ document.head.removeChild(customCSS);
+ }
+ };
+ }, [analyticsEnabled, t]);
+
+ const showCookiePreferences = () => {
+ if (isInitialized && window.CookieConsent) {
+ window.CookieConsent.show(true);
+ }
+ };
+
+ return {
+ showCookiePreferences
+ };
+};
diff --git a/frontend/src/index.css b/frontend/src/index.css
index ec2585e8c..b4bb41b3e 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -1,5 +1,11 @@
-body {
+html, body {
margin: 0;
+ padding: 0;
+ height: 100%;
+ overflow-x: hidden;
+}
+
+body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
@@ -11,3 +17,38 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}
+
+/* CSS Variables */
+:root {
+ --footer-height: 2rem;
+}
+
+/* Footer link styling - make buttons and links look identical */
+.footer-link {
+ color: var(--mantine-color-gray-6);
+ text-decoration: none;
+ transition: color 0.2s ease;
+ border: none;
+ background: none;
+ cursor: pointer;
+ font-family: inherit;
+ font-size: inherit;
+ padding: 0;
+}
+
+.footer-link:hover {
+ color: var(--mantine-color-blue-8);
+ text-decoration: underline;
+}
+
+.stirling-link {
+ color: var(--mantine-color-blue-6);
+ text-decoration: none;
+ font-weight: 500;
+ transition: color 0.2s ease;
+}
+
+.stirling-link:hover {
+ color: var(--mantine-color-blue-8);
+ text-decoration: underline;
+}
diff --git a/frontend/src/index.tsx b/frontend/src/index.tsx
index 9443f0be6..55fe7f046 100644
--- a/frontend/src/index.tsx
+++ b/frontend/src/index.tsx
@@ -7,6 +7,7 @@ import { ColorSchemeScript } from '@mantine/core';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
import './i18n'; // Initialize i18next
+import posthog from 'posthog-js';
import { PostHogProvider } from 'posthog-js/react';
// Compute initial color scheme
@@ -21,22 +22,39 @@ function getInitialScheme(): 'light' | 'dark' {
}
}
+posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, {
+ api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST,
+ defaults: '2025-05-24',
+ capture_exceptions: true, // This enables capturing exceptions using Error Tracking, set to false if you don't want this
+ debug: false,
+ opt_out_capturing_by_default: false, // We handle opt-out via cookie consent
+});
+
+function updatePosthogConsent(){
+ if(typeof(posthog) == "undefined") {
+ return;
+ }
+ const optIn = (window.CookieConsent as any).acceptedCategory('analytics');
+ optIn?
+ posthog.opt_in_capturing() : posthog.opt_out_capturing();
+
+ console.log("Updated analytics consent: ", optIn? "opted in" : "opted out");
+ }
+
+window.addEventListener("cc:onConsent", updatePosthogConsent);
+window.addEventListener("cc:onChange", updatePosthogConsent);
+
const container = document.getElementById('root');
if (!container) {
throw new Error("Root container missing in index.html");
}
+
const root = ReactDOM.createRoot(container); // Finds the root DOM element
root.render(
diff --git a/frontend/src/pages/HomePage.tsx b/frontend/src/pages/HomePage.tsx
index 12c1f4d7f..961662123 100644
--- a/frontend/src/pages/HomePage.tsx
+++ b/frontend/src/pages/HomePage.tsx
@@ -11,6 +11,7 @@ import Workbench from "../components/layout/Workbench";
import QuickAccessBar from "../components/shared/QuickAccessBar";
import RightRail from "../components/shared/RightRail";
import FileManager from "../components/FileManager";
+import Footer from "../components/shared/Footer";
export default function HomePage() {
@@ -38,17 +39,20 @@ export default function HomePage() {
// Note: File selection limits are now handled directly by individual tools
return (
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
);
-}
\ No newline at end of file
+}
diff --git a/frontend/src/styles/cookieconsent.css b/frontend/src/styles/cookieconsent.css
new file mode 100644
index 000000000..331632b13
--- /dev/null
+++ b/frontend/src/styles/cookieconsent.css
@@ -0,0 +1,59 @@
+/* Cookie Consent Modal Styling - Ensure proper z-index */
+
+/* Ensure cookie consent appears above everything */
+#cc-main {
+ z-index: 999999 !important;
+}
+
+/* Additional styling if needed */
+.cm-wrapper,
+.pm-wrapper {
+ z-index: 999999 !important;
+}
+
+/* Dark mode styling */
+.cc--darkmode .cm {
+ background: #2d2d2d !important;
+ color: #ffffff !important;
+ border-top: 1px solid #444 !important;
+}
+
+.cc--darkmode .pm {
+ background: #2d2d2d !important;
+ color: #ffffff !important;
+}
+
+.cc--darkmode .pm-overlay {
+ background: rgba(0, 0, 0, 0.7) !important;
+}
+
+/* Button styling */
+.cc--darkmode .cm__btn {
+ background: #444 !important;
+ color: #ffffff !important;
+ border: 1px solid #666 !important;
+}
+
+.cc--darkmode .cm__btn:hover {
+ background: #555 !important;
+}
+
+.cc--darkmode .pm__btn {
+ background: #444 !important;
+ color: #ffffff !important;
+ border: 1px solid #666 !important;
+}
+
+.cc--darkmode .pm__btn:hover {
+ background: #555 !important;
+}
+
+/* Ensure ScrollArea doesn't interfere */
+.mantine-ScrollArea-root {
+ position: relative !important;
+}
+
+/* Override any potential conflicts */
+[data-mantine-color-scheme="dark"] #cc-main {
+ color: #ffffff !important;
+}
\ No newline at end of file