feat: enhance image loading and socket handling in Product and Images components, and update prerender logic in App and ProductDetailPage
This commit is contained in:
@@ -57,7 +57,7 @@ const config = require("./prerender/config.cjs");
|
|||||||
const shopConfig = require("./src/config.js").default;
|
const shopConfig = require("./src/config.js").default;
|
||||||
const { renderPage } = require("./prerender/renderer.cjs");
|
const { renderPage } = require("./prerender/renderer.cjs");
|
||||||
const { generateProductMetaTags, generateProductJsonLd } = require("./prerender/seo.cjs");
|
const { generateProductMetaTags, generateProductJsonLd } = require("./prerender/seo.cjs");
|
||||||
const { fetchProductDetails } = require("./prerender/data-fetching.cjs");
|
const { fetchProductDetails, saveProductImages } = require("./prerender/data-fetching.cjs");
|
||||||
|
|
||||||
// Import product component
|
// Import product component
|
||||||
const PrerenderProduct = require("./src/PrerenderProduct.js").default;
|
const PrerenderProduct = require("./src/PrerenderProduct.js").default;
|
||||||
|
|||||||
@@ -183,6 +183,11 @@ const renderPage = (
|
|||||||
content: ${JSON.stringify(renderedMarkup)},
|
content: ${JSON.stringify(renderedMarkup)},
|
||||||
timestamp: ${Date.now()}
|
timestamp: ${Date.now()}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// DEBUG: Multiple alerts throughout the loading process
|
||||||
|
// Debug alerts removed
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -239,8 +244,10 @@ const renderPage = (
|
|||||||
|
|
||||||
let newHtml;
|
let newHtml;
|
||||||
if (rootDivRegex.test(template)) {
|
if (rootDivRegex.test(template)) {
|
||||||
|
if (!suppressLogs) console.log(` 📝 Root div found, replacing with ${renderedMarkup.length} chars of markup`);
|
||||||
newHtml = template.replace(rootDivRegex, replacementHtml);
|
newHtml = template.replace(rootDivRegex, replacementHtml);
|
||||||
} else {
|
} else {
|
||||||
|
if (!suppressLogs) console.log(` ⚠️ No root div found, appending to body`);
|
||||||
newHtml = template.replace("<body>", `<body>${replacementHtml}`);
|
newHtml = template.replace("<body>", `<body>${replacementHtml}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
55
src/App.js
55
src/App.js
@@ -32,11 +32,15 @@ import Header from "./components/Header.js";
|
|||||||
import Footer from "./components/Footer.js";
|
import Footer from "./components/Footer.js";
|
||||||
import MainPageLayout from "./components/MainPageLayout.js";
|
import MainPageLayout from "./components/MainPageLayout.js";
|
||||||
|
|
||||||
// Lazy load all route components to reduce initial bundle size
|
// TEMPORARILY DISABLE ALL LAZY LOADING TO ELIMINATE CircularProgress
|
||||||
const Content = lazy(() => import(/* webpackChunkName: "content" */ "./components/Content.js"));
|
import Content from "./components/Content.js";
|
||||||
const ProductDetailWithSocket = lazy(() => import(/* webpackChunkName: "product-detail" */ "./components/ProductDetailWithSocket.js"));
|
import ProductDetailWithSocket from "./components/ProductDetailWithSocket.js";
|
||||||
const ProfilePageWithSocket = lazy(() => import(/* webpackChunkName: "profile" */ "./pages/ProfilePage.js"));
|
import ProfilePageWithSocket from "./pages/ProfilePage.js";
|
||||||
const ResetPassword = lazy(() => import(/* webpackChunkName: "reset-password" */ "./pages/ResetPassword.js"));
|
import ResetPassword from "./pages/ResetPassword.js";
|
||||||
|
// const Content = lazy(() => import(/* webpackChunkName: "content" */ "./components/Content.js"));
|
||||||
|
// const ProductDetailWithSocket = lazy(() => import(/* webpackChunkName: "product-detail" */ "./components/ProductDetailWithSocket.js"));
|
||||||
|
// const ProfilePageWithSocket = lazy(() => import(/* webpackChunkName: "profile" */ "./pages/ProfilePage.js"));
|
||||||
|
// const ResetPassword = lazy(() => import(/* webpackChunkName: "reset-password" */ "./pages/ResetPassword.js"));
|
||||||
|
|
||||||
// Lazy load admin pages - only loaded when admin users access them
|
// Lazy load admin pages - only loaded when admin users access them
|
||||||
const AdminPage = lazy(() => import(/* webpackChunkName: "admin" */ "./pages/AdminPage.js"));
|
const AdminPage = lazy(() => import(/* webpackChunkName: "admin" */ "./pages/AdminPage.js"));
|
||||||
@@ -201,6 +205,14 @@ const AppContent = ({ currentTheme, onThemeChange }) => {
|
|||||||
<Header active categoryId={categoryId} key={authVersion} />
|
<Header active categoryId={categoryId} key={authVersion} />
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<Suspense fallback={
|
<Suspense fallback={
|
||||||
|
// Use prerender fallback if available, otherwise show loading spinner
|
||||||
|
typeof window !== "undefined" && window.__PRERENDER_FALLBACK__ ? (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: window.__PRERENDER_FALLBACK__.content,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -211,6 +223,7 @@ const AppContent = ({ currentTheme, onThemeChange }) => {
|
|||||||
>
|
>
|
||||||
<CircularProgress color="primary" />
|
<CircularProgress color="primary" />
|
||||||
</Box>
|
</Box>
|
||||||
|
)
|
||||||
}>
|
}>
|
||||||
<CarouselProvider>
|
<CarouselProvider>
|
||||||
<Routes>
|
<Routes>
|
||||||
@@ -280,7 +293,17 @@ const AppContent = ({ currentTheme, onThemeChange }) => {
|
|||||||
</Box>
|
</Box>
|
||||||
{/* Conditionally render the Chat Assistant */}
|
{/* Conditionally render the Chat Assistant */}
|
||||||
{isChatOpen && (
|
{isChatOpen && (
|
||||||
<Suspense fallback={<CircularProgress size={20} />}>
|
<Suspense fallback={
|
||||||
|
typeof window !== "undefined" && window.__PRERENDER_FALLBACK__ ? (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: window.__PRERENDER_FALLBACK__.content,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CircularProgress size={20} />
|
||||||
|
)
|
||||||
|
}>
|
||||||
<ChatAssistant
|
<ChatAssistant
|
||||||
open={isChatOpen}
|
open={isChatOpen}
|
||||||
onClose={handleChatClose}
|
onClose={handleChatClose}
|
||||||
@@ -344,7 +367,17 @@ const AppContent = ({ currentTheme, onThemeChange }) => {
|
|||||||
|
|
||||||
{/* Development-only Theme Customizer Dialog */}
|
{/* Development-only Theme Customizer Dialog */}
|
||||||
{isDevelopment && isThemeCustomizerOpen && (
|
{isDevelopment && isThemeCustomizerOpen && (
|
||||||
<Suspense fallback={<CircularProgress size={20} />}>
|
<Suspense fallback={
|
||||||
|
typeof window !== "undefined" && window.__PRERENDER_FALLBACK__ ? (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: window.__PRERENDER_FALLBACK__.content,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<CircularProgress size={20} />
|
||||||
|
)
|
||||||
|
}>
|
||||||
<ThemeCustomizerDialog
|
<ThemeCustomizerDialog
|
||||||
open={isThemeCustomizerOpen}
|
open={isThemeCustomizerOpen}
|
||||||
onClose={() => setThemeCustomizerOpen(false)}
|
onClose={() => setThemeCustomizerOpen(false)}
|
||||||
@@ -382,6 +415,13 @@ const App = () => {
|
|||||||
<SocketProvider
|
<SocketProvider
|
||||||
url={config.apiBaseUrl}
|
url={config.apiBaseUrl}
|
||||||
fallback={
|
fallback={
|
||||||
|
typeof window !== "undefined" && window.__PRERENDER_FALLBACK__ ? (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: window.__PRERENDER_FALLBACK__.content,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -392,6 +432,7 @@ const App = () => {
|
|||||||
>
|
>
|
||||||
<CircularProgress color="primary" />
|
<CircularProgress color="primary" />
|
||||||
</Box>
|
</Box>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<AppContent
|
<AppContent
|
||||||
|
|||||||
@@ -75,8 +75,9 @@ class PrerenderProduct extends React.Component {
|
|||||||
Toolbar,
|
Toolbar,
|
||||||
{ sx: { minHeight: 64, py: { xs: 0.5, sm: 0 } } },
|
{ sx: { minHeight: 64, py: { xs: 0.5, sm: 0 } } },
|
||||||
React.createElement(
|
React.createElement(
|
||||||
Container,
|
Box,
|
||||||
{ maxWidth: { xs: false, sm: 'lg' }, sx: { display: 'flex', alignItems: 'center', px: { xs: 0, sm: 3 }, width: '100%' } },
|
{ sx: { display: 'flex', alignItems: 'center', px: { xs: 0, sm: 3 }, maxWidth: { xs: 'none', sm: '1200px' }, mx: { xs: 0, sm: 'auto' }, width: '100%' } },
|
||||||
|
// Desktop: simple layout, Mobile: column layout with SearchBar space
|
||||||
React.createElement(
|
React.createElement(
|
||||||
Box,
|
Box,
|
||||||
{
|
{
|
||||||
@@ -84,11 +85,10 @@ class PrerenderProduct extends React.Component {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: '100%',
|
flexDirection: { xs: 'column', sm: 'row' }
|
||||||
flexDirection: { xs: 'column', sm: 'row' },
|
|
||||||
overflow: 'hidden'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// First row: Logo (and ButtonGroup on mobile in SPA, but we don't need ButtonGroup in prerender)
|
||||||
React.createElement(
|
React.createElement(
|
||||||
Box,
|
Box,
|
||||||
{
|
{
|
||||||
@@ -96,44 +96,25 @@ class PrerenderProduct extends React.Component {
|
|||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: '100%',
|
justifyContent: { xs: 'flex-start', sm: 'flex-start' }, // Keep desktop simple
|
||||||
justifyContent: { xs: 'space-between', sm: 'flex-start' },
|
|
||||||
minHeight: { xs: 52, sm: 'auto' },
|
minHeight: { xs: 52, sm: 'auto' },
|
||||||
px: { xs: 0, sm: 0 },
|
px: { xs: 0, sm: 0 }
|
||||||
overflow: 'hidden'
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
React.createElement(Logo),
|
React.createElement(Logo)
|
||||||
// Invisible ButtonGroup placeholder for mobile layout
|
|
||||||
React.createElement(
|
|
||||||
Box,
|
|
||||||
{
|
|
||||||
sx: {
|
|
||||||
display: { xs: 'flex', sm: 'none' },
|
|
||||||
alignItems: 'flex-end',
|
|
||||||
transform: 'translateY(4px) translateX(9px)',
|
|
||||||
ml: 0,
|
|
||||||
visibility: 'hidden',
|
|
||||||
width: 120, // Approximate width of ButtonGroup
|
|
||||||
height: 40
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
// Invisible SearchBar placeholder for mobile - matches SPA layout
|
// Second row: SearchBar placeholder only on mobile
|
||||||
React.createElement(
|
React.createElement(
|
||||||
Box,
|
Box,
|
||||||
{
|
{
|
||||||
sx: {
|
sx: {
|
||||||
display: { xs: 'block', sm: 'none' },
|
display: { xs: 'block', sm: 'none' },
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: '100%',
|
|
||||||
mt: { xs: 1, sm: 0 },
|
mt: { xs: 1, sm: 0 },
|
||||||
mb: { xs: 0.5, sm: 0 },
|
mb: { xs: 0.5, sm: 0 },
|
||||||
px: { xs: 0, sm: 0 },
|
px: { xs: 0, sm: 0 },
|
||||||
height: 41, // Small TextField height
|
height: 41, // Small TextField height
|
||||||
visibility: 'hidden',
|
visibility: 'hidden'
|
||||||
overflow: 'hidden'
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class CartItem extends Component {
|
|||||||
this.setState({image:window.tinyPicCache[picid],loading:false, error: false})
|
this.setState({image:window.tinyPicCache[picid],loading:false, error: false})
|
||||||
}else{
|
}else{
|
||||||
this.setState({image: null, loading: true, error: false});
|
this.setState({image: null, loading: true, error: false});
|
||||||
if(this.props.socket){
|
if(this.props.socket && this.props.socket.connected){
|
||||||
this.props.socket.emit('getPic', { bildId:picid, size:'tiny' }, (res) => {
|
this.props.socket.emit('getPic', { bildId:picid, size:'tiny' }, (res) => {
|
||||||
if(res.success){
|
if(res.success){
|
||||||
window.tinyPicCache[picid] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
window.tinyPicCache[picid] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import LoupeIcon from '@mui/icons-material/Loupe';
|
|||||||
class Images extends Component {
|
class Images extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = { mainPic:0,pics:[]};
|
this.state = { mainPic:0,pics:[], needsSocketRetry: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount () {
|
componentDidMount () {
|
||||||
@@ -22,6 +22,15 @@ class Images extends Component {
|
|||||||
if (prevProps.fullscreenOpen !== this.props.fullscreenOpen) {
|
if (prevProps.fullscreenOpen !== this.props.fullscreenOpen) {
|
||||||
this.updatePics();
|
this.updatePics();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Retry loading images if socket just became available
|
||||||
|
const wasConnected = prevProps.socketB && prevProps.socketB.connected;
|
||||||
|
const isNowConnected = this.props.socketB && this.props.socketB.connected;
|
||||||
|
|
||||||
|
if (!wasConnected && isNowConnected && this.state.needsSocketRetry) {
|
||||||
|
this.setState({ needsSocketRetry: false });
|
||||||
|
this.updatePics();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePics = (newMainPic = this.state.mainPic) => {
|
updatePics = (newMainPic = this.state.mainPic) => {
|
||||||
@@ -49,10 +58,10 @@ class Images extends Component {
|
|||||||
pics.push(window.smallPicCache[bildId]);
|
pics.push(window.smallPicCache[bildId]);
|
||||||
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
||||||
}else if(window.tinyPicCache[bildId]){
|
}else if(window.tinyPicCache[bildId]){
|
||||||
pics.push(bildId);
|
pics.push(window.tinyPicCache[bildId]);
|
||||||
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
||||||
}else{
|
}else{
|
||||||
pics.push(bildId);
|
pics.push(`/assets/images/prod${bildId}.jpg`);
|
||||||
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
this.loadPic(this.props.fullscreenOpen ? 'large' : 'medium',bildId,newMainPic);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
@@ -67,7 +76,8 @@ class Images extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('pics',pics);
|
console.log('DEBUG: pics array contents:', pics);
|
||||||
|
console.log('DEBUG: pics array types:', pics.map(p => typeof p + ': ' + p));
|
||||||
this.setState({ pics, mainPic: newMainPic });
|
this.setState({ pics, mainPic: newMainPic });
|
||||||
}else{
|
}else{
|
||||||
if(this.state.pics.length > 0) this.setState({ pics:[], mainPic: newMainPic });
|
if(this.state.pics.length > 0) this.setState({ pics:[], mainPic: newMainPic });
|
||||||
@@ -75,6 +85,13 @@ class Images extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
loadPic = (size,bildId,index) => {
|
loadPic = (size,bildId,index) => {
|
||||||
|
// Check if socketB is available and connected before emitting
|
||||||
|
if (!this.props.socketB || !this.props.socketB.connected) {
|
||||||
|
console.log("Images: socketB not available, will retry when connected");
|
||||||
|
this.setState({ needsSocketRetry: true });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.props.socketB.emit('getPic', { bildId, size }, (res) => {
|
this.props.socketB.emit('getPic', { bildId, size }, (res) => {
|
||||||
if(res.success){
|
if(res.success){
|
||||||
const url = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
const url = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
||||||
|
|||||||
@@ -28,6 +28,43 @@ class Product extends Component {
|
|||||||
}else{
|
}else{
|
||||||
this.state = {image: null, loading: true, error: false};
|
this.state = {image: null, loading: true, error: false};
|
||||||
console.log("Product: Fetching image from socketB", this.props.socketB);
|
console.log("Product: Fetching image from socketB", this.props.socketB);
|
||||||
|
|
||||||
|
// Check if socketB is available and connected before emitting
|
||||||
|
if (this.props.socketB && this.props.socketB.connected) {
|
||||||
|
this.loadImage(bildId);
|
||||||
|
} else {
|
||||||
|
// Socket not available, set error state or wait
|
||||||
|
console.log("Product: socketB not available, will retry when connected");
|
||||||
|
this.state.error = true;
|
||||||
|
this.state.loading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
this.state = {image: null, loading: false, error: false};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this._isMounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
// Retry loading image if socket just became available
|
||||||
|
const wasConnected = prevProps.socketB && prevProps.socketB.connected;
|
||||||
|
const isNowConnected = this.props.socketB && this.props.socketB.connected;
|
||||||
|
|
||||||
|
if (!wasConnected && isNowConnected && this.state.error && this.props.pictureList) {
|
||||||
|
// Socket just connected and we had an error, retry loading
|
||||||
|
const bildId = this.props.pictureList.split(',')[0];
|
||||||
|
if (!window.smallPicCache[bildId]) {
|
||||||
|
this.setState({loading: true, error: false});
|
||||||
|
this.loadImage(bildId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadImage = (bildId) => {
|
||||||
|
if (this.props.socketB && this.props.socketB.connected) {
|
||||||
this.props.socketB.emit('getPic', { bildId, size:'small' }, (res) => {
|
this.props.socketB.emit('getPic', { bildId, size:'small' }, (res) => {
|
||||||
if(res.success){
|
if(res.success){
|
||||||
window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
window.smallPicCache[bildId] = URL.createObjectURL(new Blob([res.imageBuffer], { type: 'image/jpeg' }));
|
||||||
@@ -46,15 +83,8 @@ class Product extends Component {
|
|||||||
this.state.loading = false;
|
this.state.loading = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}else{
|
|
||||||
this.state = {image: null, loading: false, error: false};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this._isMounted = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillUnmount() {
|
componentWillUnmount() {
|
||||||
|
|||||||
@@ -356,7 +356,6 @@ class ProductDetailPage extends Component {
|
|||||||
if (!this.props.socket || !this.props.socket.connected) {
|
if (!this.props.socket || !this.props.socket.connected) {
|
||||||
// Socket not connected yet, but don't show error immediately on first load
|
// Socket not connected yet, but don't show error immediately on first load
|
||||||
// The componentDidUpdate will retry when socket connects
|
// The componentDidUpdate will retry when socket connects
|
||||||
console.log("Socket not connected yet, waiting for connection to load product data");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -542,7 +541,23 @@ class ProductDetailPage extends Component {
|
|||||||
const { product, loading, error, attributeImages, isSteckling, attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings } =
|
const { product, loading, error, attributeImages, isSteckling, attributes, komponentenLoaded, komponentenData, komponentenImages, totalKomponentenPrice, totalSavings } =
|
||||||
this.state;
|
this.state;
|
||||||
|
|
||||||
|
// Debug alerts removed
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
// Check if prerender fallback is available
|
||||||
|
if (typeof window !== "undefined" && window.__PRERENDER_FALLBACK__) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: window.__PRERENDER_FALLBACK__.content,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to loading message if no prerender content
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|||||||
Reference in New Issue
Block a user