Remove development mode badge

This commit is contained in:
Flatlogic Bot 2026-06-09 13:10:22 +00:00
parent c1bdc01129
commit 88c1db36fd
3 changed files with 129 additions and 303 deletions

View File

@ -1,150 +0,0 @@
import React, { useState, useEffect } from 'react';
import useDevCompilationStatus from '../hooks/useDevCompilationStatus';
const DevModeBadge: React.FC = () => {
const [isVisible, setIsVisible] = useState(false);
const [isCollapsed, setIsCollapsed] = useState(true);
const compilationStatus = useDevCompilationStatus();
const [badgeStyles, setBadgeStyles] = useState<React.CSSProperties>({
position: 'fixed',
bottom: '20px',
left: '70px',
background: 'rgba(0, 0, 0, 0.85)',
color: 'white',
padding: '15px',
borderRadius: '8px',
fontFamily: 'sans-serif',
fontSize: '14px',
lineHeight: '1.5',
textAlign: 'left',
zIndex: 2147483647,
boxShadow: '0 4px 10px rgba(0, 0, 0, 0.3)',
whiteSpace: 'pre-wrap',
transition: 'width 0.3s cubic-bezier(0.25, 0.1, 0.25, 1), padding 0.3s ease-in-out, opacity 0.3s ease-in-out, background-color 0.3s ease-in-out', // Improved transition for width
opacity: 0,
pointerEvents: 'none',
width: '340px',
maxWidth: '340px',
height: 'auto',
overflow: 'hidden',
cursor: 'pointer',
});
const fullText = `🚧 Your app is running in development mode.
Current request is compiling and may take a few moments.
💡 Tip: Set up a stable environment to run your app in production modepages will load instantly without compilation delays.`;
const collapsedText = '🚧 DEV stage';
useEffect(() => {
if (compilationStatus === 'ready') {
setIsCollapsed(true);
} else {
setIsCollapsed(false);
}
}, [compilationStatus]);
useEffect(() => {
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev_stage') {
setIsVisible(true);
setBadgeStyles(prev => ({
...prev,
opacity: 1,
width: '120px',
maxWidth: '120px',
padding: '6px 10px',
borderRadius: '18px',
whiteSpace: 'nowrap',
fontSize: '12px',
cursor: 'pointer',
pointerEvents: 'auto',
}));
} else {
setIsVisible(false);
setBadgeStyles(prev => ({ ...prev, opacity: 0 }));
}
}, []);
useEffect(() => {
if (!isVisible) return;
if (isCollapsed) {
setBadgeStyles(prev => ({
...prev,
width: '140px',
maxWidth: '160px',
padding: '6px 20px',
borderRadius: '18px',
whiteSpace: 'nowrap',
fontSize: '12px',
}));
} else {
setBadgeStyles(prev => ({
...prev,
width: '340px',
maxWidth: '340px',
padding: '15px',
borderRadius: '8px',
whiteSpace: 'pre-wrap',
fontSize: '14px',
}));
}
}, [isCollapsed, isVisible]);
const handleToggleCollapse = (e: React.MouseEvent) => {
e.stopPropagation();
setIsCollapsed(prev => !prev);
};
if (!isVisible) {
return null;
}
return (
<div style={badgeStyles} onClick={isCollapsed ? handleToggleCollapse : undefined}>
<button
onClick={handleToggleCollapse}
style={{
position: 'absolute',
top: isCollapsed ? '3px' : '5px',
right: isCollapsed ? '2px' : '5px',
background: 'none',
border: 'none',
color: 'white',
fontSize: isCollapsed ? '10px' : '18px',
cursor: 'pointer',
padding: '2px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
lineHeight: '1',
width: '24px',
height: isCollapsed ? '24px' : '24px',
borderRadius: '50%',
backgroundColor: 'rgba(255, 255, 255, 0.1)',
transition: 'background-color 0.2s ease, font-size 0.2s ease, width 0.2s ease, height 0.2s ease',
}}
aria-label={isCollapsed ? "Expand message" : "Collapse message"}
>
{isCollapsed ? '+' : '×'}
</button>
{!isCollapsed && (
<div style={{ marginRight: '20px' }}>
{fullText}
</div>
)}
{isCollapsed && (
<div style={{ marginRight: '10px' }}>
{collapsedText}
</div>
)}
</div>
);
};
export default DevModeBadge;

View File

@ -1,44 +0,0 @@
import { useState, useEffect } from 'react';
import { useRouter } from 'next/router';
type CompilationStatus = 'ready' | 'compiling' | 'error' | 'initial';
const useDevCompilationStatus = (): CompilationStatus => {
const router = useRouter();
const [status, setStatus] = useState<CompilationStatus>('initial');
useEffect(() => {
if (process.env.NODE_ENV !== 'development') {
setStatus('ready');
return;
}
const handleRouteChangeStart = () => {
setStatus('compiling');
};
const handleRouteChangeComplete = () => {
setTimeout(() => setStatus('ready'), 300);
};
const handleRouteChangeError = () => {
setTimeout(() => setStatus('error'), 300);
};
router.events.on('routeChangeStart', handleRouteChangeStart);
router.events.on('routeChangeComplete', handleRouteChangeComplete);
router.events.on('routeChangeError', handleRouteChangeError);
setStatus('ready');
return () => {
router.events.off('routeChangeStart', handleRouteChangeStart);
router.events.off('routeChangeComplete', handleRouteChangeComplete);
router.events.off('routeChangeError', handleRouteChangeError);
};
}, [router]);
return status;
};
export default useDevCompilationStatus;

View File

@ -9,28 +9,35 @@ import '../css/main.css';
import axios from 'axios';
import { baseURLApi } from '../config';
import { useRouter } from 'next/router';
import ErrorBoundary from "../components/ErrorBoundary";
import DevModeBadge from '../components/DevModeBadge';
import ErrorBoundary from '../components/ErrorBoundary';
import 'intro.js/introjs.css';
import { appWithTranslation } from 'next-i18next';
import '../i18n';
import IntroGuide from '../components/IntroGuide';
import { appSteps, loginSteps, usersSteps, rolesSteps } from '../stores/introSteps';
import {
appSteps,
loginSteps,
usersSteps,
rolesSteps,
} from '../stores/introSteps';
// Initialize axios
axios.defaults.baseURL = process.env.NEXT_PUBLIC_BACK_API
? process.env.NEXT_PUBLIC_BACK_API
: baseURLApi;
? process.env.NEXT_PUBLIC_BACK_API
: baseURLApi;
axios.defaults.headers.common['Content-Type'] = 'application/json';
export type NextPageWithLayout<P = Record<string, unknown>, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode
}
export type NextPageWithLayout<P = Record<string, unknown>, IP = P> = NextPage<
P,
IP
> & {
getLayout?: (page: ReactElement) => ReactNode;
};
type AppPropsWithLayout = AppProps & {
Component: NextPageWithLayout
}
Component: NextPageWithLayout;
};
function MyApp({ Component, pageProps }: AppPropsWithLayout) {
// Use the layout defined at the page level, if available
@ -40,76 +47,82 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const [stepName, setStepName] = React.useState('');
const [steps, setSteps] = React.useState([]);
axios.interceptors.request.use(
config => {
const token = localStorage.getItem('token');
axios.interceptors.request.use(
(config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
} else {
delete config.headers.Authorization;
}
if (token) {
config.headers.Authorization = `Bearer ${token}`;
} else {
delete config.headers.Authorization;
}
return config;
},
error => {
return Promise.reject(error);
return config;
},
(error) => {
return Promise.reject(error);
},
);
// TODO: Remove this code in future releases
React.useEffect(() => {
const allowedOrigin = (() => {
if (!document.referrer) {
return null;
}
try {
return new URL(document.referrer).origin;
} catch (error) {
console.warn(
'[postMessage] Failed to parse parent origin from referrer',
error,
);
return null;
}
})();
const handleMessage = async (event: MessageEvent) => {
if (event.data === 'getLocation') {
event.source?.postMessage(
{ iframeLocation: window.location.pathname },
event.origin,
);
return;
}
if (event.data === 'getAuthToken') {
if (allowedOrigin && event.origin !== allowedOrigin) {
console.warn(
'[postMessage] Blocked getAuthToken from origin',
event.origin,
);
return;
}
);
const token = localStorage.getItem('token');
const user = localStorage.getItem('user');
event.source?.postMessage(
{ iframeAuthToken: token, iframeAuthUser: user },
event.origin,
);
return;
}
// TODO: Remove this code in future releases
React.useEffect(() => {
const allowedOrigin = (() => {
if (!document.referrer) {
return null;
}
try {
return new URL(document.referrer).origin;
} catch (error) {
console.warn('[postMessage] Failed to parse parent origin from referrer', error);
return null;
}
})();
if (event.data === 'getScreenshot') {
try {
const html2canvas = (await import('html2canvas')).default;
const canvas = await html2canvas(document.body, { useCORS: true });
const url = canvas.toDataURL('image/jpeg', 0.8);
event.source?.postMessage({ iframeScreenshot: url }, event.origin);
} catch (e) {
console.error('html2canvas failed', e);
event.source?.postMessage({ iframeScreenshot: null }, event.origin);
}
}
};
const handleMessage = async (event: MessageEvent) => {
if (event.data === 'getLocation') {
event.source?.postMessage(
{ iframeLocation: window.location.pathname },
event.origin,
);
return;
}
if (event.data === 'getAuthToken') {
if (allowedOrigin && event.origin !== allowedOrigin) {
console.warn('[postMessage] Blocked getAuthToken from origin', event.origin);
return;
}
const token = localStorage.getItem('token');
const user = localStorage.getItem('user');
event.source?.postMessage(
{ iframeAuthToken: token, iframeAuthUser: user },
event.origin,
);
return;
}
if (event.data === 'getScreenshot') {
try {
const html2canvas = (await import('html2canvas')).default;
const canvas = await html2canvas(document.body, { useCORS: true });
const url = canvas.toDataURL('image/jpeg', 0.8);
event.source?.postMessage({ iframeScreenshot: url }, event.origin);
} catch (e) {
console.error('html2canvas failed', e);
event.source?.postMessage({ iframeScreenshot: null }, event.origin);
}
}
};
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, []);
React.useEffect(() => {
// Tour is disabled by default in generated projects.
@ -117,31 +130,37 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
const isCompleted = (stepKey: string) => {
return localStorage.getItem(`completed_${stepKey}`) === 'true';
};
if (router.pathname === '/login' && !isCompleted('loginSteps')) {
if (router.pathname === '/login' && !isCompleted('loginSteps')) {
setSteps(loginSteps);
setStepName('loginSteps');
setStepsEnabled(true);
}else if (router.pathname === '/dashboard' && !isCompleted('appSteps')) {
setStepsEnabled(true);
} else if (router.pathname === '/dashboard' && !isCompleted('appSteps')) {
setTimeout(() => {
setSteps(appSteps);
setStepName('appSteps');
setStepsEnabled(true);
setStepsEnabled(true);
}, 1000);
} else if (router.pathname === '/users/users-list' && !isCompleted('usersSteps')) {
} else if (
router.pathname === '/users/users-list' &&
!isCompleted('usersSteps')
) {
setTimeout(() => {
setSteps(usersSteps);
setStepName('usersSteps');
setStepsEnabled(true);
setStepsEnabled(true);
}, 1000);
} else if (router.pathname === '/roles/roles-list' && !isCompleted('rolesSteps')) {
} else if (
router.pathname === '/roles/roles-list' &&
!isCompleted('rolesSteps')
) {
setTimeout(() => {
setSteps(rolesSteps);
setStepName('rolesSteps');
setStepsEnabled(true);
setStepsEnabled(true);
}, 1000);
} else {
setSteps([]);
setStepsEnabled(false);
setStepsEnabled(false);
}
}, [router.pathname]);
@ -149,37 +168,39 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
setStepsEnabled(false);
};
const title = 'Coaching SaaS Workspace'
const description = "A coaching workspace for client context, session memory, action items, resources, and client portal delivery."
const url = "https://flatlogic.com/"
const image = "https://project-screens.s3.amazonaws.com/screenshots/40234/app-hero-20260609-100604.png"
const imageWidth = '1920'
const imageHeight = '960'
const title = 'Coaching SaaS Workspace';
const description =
'A coaching workspace for client context, session memory, action items, resources, and client portal delivery.';
const url = 'https://flatlogic.com/';
const image =
'https://project-screens.s3.amazonaws.com/screenshots/40234/app-hero-20260609-100604.png';
const imageWidth = '1920';
const imageHeight = '960';
return (
<Provider store={store}>
{getLayout(
<>
<Head>
<meta name="description" content={description} />
<meta name='description' content={description} />
<meta property="og:url" content={url} />
<meta property="og:site_name" content="https://flatlogic.com/" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={image} />
<meta property="og:image:type" content="image/png" />
<meta property="og:image:width" content={imageWidth} />
<meta property="og:image:height" content={imageHeight} />
<meta property='og:url' content={url} />
<meta property='og:site_name' content='https://flatlogic.com/' />
<meta property='og:title' content={title} />
<meta property='og:description' content={description} />
<meta property='og:image' content={image} />
<meta property='og:image:type' content='image/png' />
<meta property='og:image:width' content={imageWidth} />
<meta property='og:image:height' content={imageHeight} />
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image:src" content={image} />
<meta property="twitter:image:width" content={imageWidth} />
<meta property="twitter:image:height" content={imageHeight} />
<meta property='twitter:card' content='summary_large_image' />
<meta property='twitter:title' content={title} />
<meta property='twitter:description' content={description} />
<meta property='twitter:image:src' content={image} />
<meta property='twitter:image:width' content={imageWidth} />
<meta property='twitter:image:height' content={imageHeight} />
<link rel="icon" href="/favicon.svg" />
<link rel='icon' href='/favicon.svg' />
</Head>
<ErrorBoundary>
@ -191,11 +212,10 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) {
stepsEnabled={stepsEnabled}
onExit={handleExit}
/>
{(process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'dev_stage') && <DevModeBadge />}
</>
</>,
)}
</Provider>
)
);
}
export default appWithTranslation(MyApp);