Remove development mode badge
This commit is contained in:
parent
c1bdc01129
commit
88c1db36fd
@ -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 mode—pages 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;
|
||||
@ -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;
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user