mirror of
https://github.com/pacnpal/thrilltrack-explorer.git
synced 2025-12-24 12:31:14 -05:00
Refactor: Implement Code Splitting
This commit is contained in:
184
src/App.tsx
184
src/App.tsx
@@ -1,4 +1,5 @@
|
||||
import * as React from "react";
|
||||
import { lazy, Suspense } from "react";
|
||||
import { Toaster } from "@/components/ui/toaster";
|
||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||
@@ -9,47 +10,57 @@ import { AuthProvider } from "@/hooks/useAuth";
|
||||
import { AuthModalProvider } from "@/contexts/AuthModalContext";
|
||||
import { LocationAutoDetectProvider } from "@/components/providers/LocationAutoDetectProvider";
|
||||
import { Footer } from "@/components/layout/Footer";
|
||||
import { PageLoader } from "@/components/loading/PageSkeletons";
|
||||
|
||||
// Core routes (eager-loaded for best UX)
|
||||
import Index from "./pages/Index";
|
||||
import Parks from "./pages/Parks";
|
||||
import ParkDetail from "./pages/ParkDetail";
|
||||
import RideDetail from "./pages/RideDetail";
|
||||
import Rides from "./pages/Rides";
|
||||
import Manufacturers from "./pages/Manufacturers";
|
||||
import ManufacturerDetail from "./pages/ManufacturerDetail";
|
||||
import ManufacturerRides from "./pages/ManufacturerRides";
|
||||
import ManufacturerModels from "./pages/ManufacturerModels";
|
||||
import RideModelDetail from "./pages/RideModelDetail";
|
||||
import RideModelRides from "./pages/RideModelRides";
|
||||
import Designers from "./pages/Designers";
|
||||
import DesignerDetail from "./pages/DesignerDetail";
|
||||
import DesignerRides from "./pages/DesignerRides";
|
||||
import ParkOwners from "./pages/ParkOwners";
|
||||
import PropertyOwnerDetail from "./pages/PropertyOwnerDetail";
|
||||
import OwnerParks from "./pages/OwnerParks";
|
||||
import Operators from "./pages/Operators";
|
||||
import OperatorDetail from "./pages/OperatorDetail";
|
||||
import OperatorParks from "./pages/OperatorParks";
|
||||
import Auth from "./pages/Auth";
|
||||
import Profile from "./pages/Profile";
|
||||
import ParkRides from "./pages/ParkRides";
|
||||
import UserSettings from "./pages/UserSettings";
|
||||
import Search from "./pages/Search";
|
||||
import NotFound from "./pages/NotFound";
|
||||
import Terms from "./pages/Terms";
|
||||
import Privacy from "./pages/Privacy";
|
||||
import SubmissionGuidelines from "./pages/SubmissionGuidelines";
|
||||
import Admin from "./pages/Admin";
|
||||
import AdminDashboard from "./pages/AdminDashboard";
|
||||
import AdminModeration from "./pages/AdminModeration";
|
||||
import AdminReports from "./pages/AdminReports";
|
||||
import AdminSystemLog from "./pages/AdminSystemLog";
|
||||
import AdminUsers from "./pages/AdminUsers";
|
||||
import AdminSettings from "./pages/AdminSettings";
|
||||
import BlogIndex from "./pages/BlogIndex";
|
||||
import BlogPost from "./pages/BlogPost";
|
||||
import AdminBlog from "./pages/AdminBlog";
|
||||
import ForceLogout from "./pages/ForceLogout";
|
||||
import AuthCallback from "./pages/AuthCallback";
|
||||
import Auth from "./pages/Auth";
|
||||
|
||||
// Detail routes (lazy-loaded)
|
||||
const ParkDetail = lazy(() => import("./pages/ParkDetail"));
|
||||
const RideDetail = lazy(() => import("./pages/RideDetail"));
|
||||
const ParkRides = lazy(() => import("./pages/ParkRides"));
|
||||
const Manufacturers = lazy(() => import("./pages/Manufacturers"));
|
||||
const ManufacturerDetail = lazy(() => import("./pages/ManufacturerDetail"));
|
||||
const ManufacturerRides = lazy(() => import("./pages/ManufacturerRides"));
|
||||
const ManufacturerModels = lazy(() => import("./pages/ManufacturerModels"));
|
||||
const RideModelDetail = lazy(() => import("./pages/RideModelDetail"));
|
||||
const RideModelRides = lazy(() => import("./pages/RideModelRides"));
|
||||
const Designers = lazy(() => import("./pages/Designers"));
|
||||
const DesignerDetail = lazy(() => import("./pages/DesignerDetail"));
|
||||
const DesignerRides = lazy(() => import("./pages/DesignerRides"));
|
||||
const ParkOwners = lazy(() => import("./pages/ParkOwners"));
|
||||
const PropertyOwnerDetail = lazy(() => import("./pages/PropertyOwnerDetail"));
|
||||
const OwnerParks = lazy(() => import("./pages/OwnerParks"));
|
||||
const Operators = lazy(() => import("./pages/Operators"));
|
||||
const OperatorDetail = lazy(() => import("./pages/OperatorDetail"));
|
||||
const OperatorParks = lazy(() => import("./pages/OperatorParks"));
|
||||
const BlogIndex = lazy(() => import("./pages/BlogIndex"));
|
||||
const BlogPost = lazy(() => import("./pages/BlogPost"));
|
||||
const Terms = lazy(() => import("./pages/Terms"));
|
||||
const Privacy = lazy(() => import("./pages/Privacy"));
|
||||
const SubmissionGuidelines = lazy(() => import("./pages/SubmissionGuidelines"));
|
||||
|
||||
// Admin routes (lazy-loaded - heavy bundle)
|
||||
const AdminDashboard = lazy(() => import("./pages/AdminDashboard"));
|
||||
const AdminModeration = lazy(() => import("./pages/AdminModeration"));
|
||||
const AdminReports = lazy(() => import("./pages/AdminReports"));
|
||||
const AdminSystemLog = lazy(() => import("./pages/AdminSystemLog"));
|
||||
const AdminUsers = lazy(() => import("./pages/AdminUsers"));
|
||||
const AdminBlog = lazy(() => import("./pages/AdminBlog"));
|
||||
const AdminSettings = lazy(() => import("./pages/AdminSettings"));
|
||||
|
||||
// User routes (lazy-loaded)
|
||||
const Profile = lazy(() => import("./pages/Profile"));
|
||||
const UserSettings = lazy(() => import("./pages/UserSettings"));
|
||||
const AuthCallback = lazy(() => import("./pages/AuthCallback"));
|
||||
|
||||
// Utility routes (lazy-loaded)
|
||||
const NotFound = lazy(() => import("./pages/NotFound"));
|
||||
const ForceLogout = lazy(() => import("./pages/ForceLogout"));
|
||||
|
||||
const queryClient = new QueryClient({
|
||||
defaultOptions: {
|
||||
@@ -73,50 +84,61 @@ function AppContent() {
|
||||
<Sonner />
|
||||
<div className="min-h-screen flex flex-col">
|
||||
<div className="flex-1">
|
||||
<Routes>
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="/parks" element={<Parks />} />
|
||||
<Route path="/parks/:slug" element={<ParkDetail />} />
|
||||
<Route path="/parks/:parkSlug/rides" element={<ParkRides />} />
|
||||
<Route path="/parks/:parkSlug/rides/:rideSlug" element={<RideDetail />} />
|
||||
<Route path="/rides" element={<Rides />} />
|
||||
<Route path="/search" element={<Search />} />
|
||||
<Route path="/blog" element={<BlogIndex />} />
|
||||
<Route path="/blog/:slug" element={<BlogPost />} />
|
||||
<Route path="/manufacturers" element={<Manufacturers />} />
|
||||
<Route path="/manufacturers/:slug" element={<ManufacturerDetail />} />
|
||||
<Route path="/manufacturers/:manufacturerSlug/rides" element={<ManufacturerRides />} />
|
||||
<Route path="/manufacturers/:manufacturerSlug/models" element={<ManufacturerModels />} />
|
||||
<Route path="/manufacturers/:manufacturerSlug/models/:modelSlug" element={<RideModelDetail />} />
|
||||
<Route path="/manufacturers/:manufacturerSlug/models/:modelSlug/rides" element={<RideModelRides />} />
|
||||
<Route path="/designers" element={<Designers />} />
|
||||
<Route path="/designers/:slug" element={<DesignerDetail />} />
|
||||
<Route path="/designers/:designerSlug/rides" element={<DesignerRides />} />
|
||||
<Route path="/owners" element={<ParkOwners />} />
|
||||
<Route path="/owners/:slug" element={<PropertyOwnerDetail />} />
|
||||
<Route path="/owners/:ownerSlug/parks" element={<OwnerParks />} />
|
||||
<Route path="/operators" element={<Operators />} />
|
||||
<Route path="/operators/:slug" element={<OperatorDetail />} />
|
||||
<Route path="/operators/:operatorSlug/parks" element={<OperatorParks />} />
|
||||
<Route path="/auth" element={<Auth />} />
|
||||
<Route path="/auth/callback" element={<AuthCallback />} />
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
<Route path="/profile/:username" element={<Profile />} />
|
||||
<Route path="/settings" element={<UserSettings />} />
|
||||
<Route path="/admin" element={<AdminDashboard />} />
|
||||
<Route path="/admin/moderation" element={<AdminModeration />} />
|
||||
<Route path="/admin/reports" element={<AdminReports />} />
|
||||
<Route path="/admin/system-log" element={<AdminSystemLog />} />
|
||||
<Route path="/admin/users" element={<AdminUsers />} />
|
||||
<Route path="/admin/blog" element={<AdminBlog />} />
|
||||
<Route path="/admin/settings" element={<AdminSettings />} />
|
||||
<Route path="/terms" element={<Terms />} />
|
||||
<Route path="/privacy" element={<Privacy />} />
|
||||
<Route path="/submission-guidelines" element={<SubmissionGuidelines />} />
|
||||
<Route path="/force-logout" element={<ForceLogout />} />
|
||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
<Suspense fallback={<PageLoader />}>
|
||||
<Routes>
|
||||
{/* Core routes - eager loaded */}
|
||||
<Route path="/" element={<Index />} />
|
||||
<Route path="/parks" element={<Parks />} />
|
||||
<Route path="/rides" element={<Rides />} />
|
||||
<Route path="/search" element={<Search />} />
|
||||
<Route path="/auth" element={<Auth />} />
|
||||
|
||||
{/* Detail routes - lazy loaded */}
|
||||
<Route path="/parks/:slug" element={<ParkDetail />} />
|
||||
<Route path="/parks/:parkSlug/rides" element={<ParkRides />} />
|
||||
<Route path="/parks/:parkSlug/rides/:rideSlug" element={<RideDetail />} />
|
||||
<Route path="/manufacturers" element={<Manufacturers />} />
|
||||
<Route path="/manufacturers/:slug" element={<ManufacturerDetail />} />
|
||||
<Route path="/manufacturers/:manufacturerSlug/rides" element={<ManufacturerRides />} />
|
||||
<Route path="/manufacturers/:manufacturerSlug/models" element={<ManufacturerModels />} />
|
||||
<Route path="/manufacturers/:manufacturerSlug/models/:modelSlug" element={<RideModelDetail />} />
|
||||
<Route path="/manufacturers/:manufacturerSlug/models/:modelSlug/rides" element={<RideModelRides />} />
|
||||
<Route path="/designers" element={<Designers />} />
|
||||
<Route path="/designers/:slug" element={<DesignerDetail />} />
|
||||
<Route path="/designers/:designerSlug/rides" element={<DesignerRides />} />
|
||||
<Route path="/owners" element={<ParkOwners />} />
|
||||
<Route path="/owners/:slug" element={<PropertyOwnerDetail />} />
|
||||
<Route path="/owners/:ownerSlug/parks" element={<OwnerParks />} />
|
||||
<Route path="/operators" element={<Operators />} />
|
||||
<Route path="/operators/:slug" element={<OperatorDetail />} />
|
||||
<Route path="/operators/:operatorSlug/parks" element={<OperatorParks />} />
|
||||
<Route path="/blog" element={<BlogIndex />} />
|
||||
<Route path="/blog/:slug" element={<BlogPost />} />
|
||||
<Route path="/terms" element={<Terms />} />
|
||||
<Route path="/privacy" element={<Privacy />} />
|
||||
<Route path="/submission-guidelines" element={<SubmissionGuidelines />} />
|
||||
|
||||
{/* User routes - lazy loaded */}
|
||||
<Route path="/auth/callback" element={<AuthCallback />} />
|
||||
<Route path="/profile" element={<Profile />} />
|
||||
<Route path="/profile/:username" element={<Profile />} />
|
||||
<Route path="/settings" element={<UserSettings />} />
|
||||
|
||||
{/* Admin routes - lazy loaded */}
|
||||
<Route path="/admin" element={<AdminDashboard />} />
|
||||
<Route path="/admin/moderation" element={<AdminModeration />} />
|
||||
<Route path="/admin/reports" element={<AdminReports />} />
|
||||
<Route path="/admin/system-log" element={<AdminSystemLog />} />
|
||||
<Route path="/admin/users" element={<AdminUsers />} />
|
||||
<Route path="/admin/blog" element={<AdminBlog />} />
|
||||
<Route path="/admin/settings" element={<AdminSettings />} />
|
||||
|
||||
{/* Utility routes - lazy loaded */}
|
||||
<Route path="/force-logout" element={<ForceLogout />} />
|
||||
{/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */}
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Routes>
|
||||
</Suspense>
|
||||
</div>
|
||||
<Footer />
|
||||
</div>
|
||||
|
||||
23
src/components/admin/MarkdownEditorLazy.tsx
Normal file
23
src/components/admin/MarkdownEditorLazy.tsx
Normal file
@@ -0,0 +1,23 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { EditorSkeleton } from '@/components/loading/PageSkeletons';
|
||||
|
||||
const MarkdownEditor = lazy(() =>
|
||||
import('./MarkdownEditor').then(module => ({ default: module.MarkdownEditor }))
|
||||
);
|
||||
|
||||
export interface MarkdownEditorProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onSave?: (value: string) => Promise<void>;
|
||||
autoSave?: boolean;
|
||||
height?: number;
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
export function MarkdownEditorLazy(props: MarkdownEditorProps) {
|
||||
return (
|
||||
<Suspense fallback={<EditorSkeleton />}>
|
||||
<MarkdownEditor {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
72
src/components/loading/PageSkeletons.tsx
Normal file
72
src/components/loading/PageSkeletons.tsx
Normal file
@@ -0,0 +1,72 @@
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
|
||||
export const PageLoader = () => (
|
||||
<div className="min-h-screen flex items-center justify-center">
|
||||
<div className="text-center space-y-4">
|
||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-primary mx-auto" />
|
||||
<p className="text-muted-foreground">Loading...</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const ParkDetailSkeleton = () => (
|
||||
<div className="container mx-auto px-4 py-8 space-y-6">
|
||||
<Skeleton className="h-64 w-full rounded-lg" />
|
||||
<Skeleton className="h-10 w-2/3" />
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||
{[...Array(4)].map((_, i) => (
|
||||
<Skeleton key={i} className="h-20" />
|
||||
))}
|
||||
</div>
|
||||
<Skeleton className="h-40 w-full" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const RideCardGridSkeleton = () => (
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{[...Array(6)].map((_, i) => (
|
||||
<Skeleton key={i} className="h-64 rounded-lg" />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
export const AdminFormSkeleton = () => (
|
||||
<div className="space-y-4 p-6">
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<Skeleton className="h-32 w-full" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<Skeleton className="h-10 w-24" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const EditorSkeleton = () => (
|
||||
<div className="space-y-2">
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<Skeleton className="h-64 w-full" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const UploadPlaceholder = () => (
|
||||
<div className="border-2 border-dashed rounded-lg p-8 text-center">
|
||||
<div className="mx-auto h-12 w-12 rounded-full border-2 border-muted-foreground/20 flex items-center justify-center">
|
||||
<Skeleton className="h-6 w-6" />
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-muted-foreground">Loading uploader...</p>
|
||||
</div>
|
||||
);
|
||||
|
||||
export const DialogSkeleton = () => (
|
||||
<Card>
|
||||
<CardContent className="p-6 space-y-4">
|
||||
<Skeleton className="h-8 w-48" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<Skeleton className="h-32 w-full" />
|
||||
<Skeleton className="h-10 w-full" />
|
||||
<div className="flex gap-2 justify-end">
|
||||
<Skeleton className="h-10 w-20" />
|
||||
<Skeleton className="h-10 w-20" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
@@ -4,7 +4,7 @@ import { Label } from '@/components/ui/label';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Image as ImageIcon, ImagePlus, X } from 'lucide-react';
|
||||
import { UppyPhotoUpload } from './UppyPhotoUpload';
|
||||
import { UppyPhotoUploadLazy } from './UppyPhotoUploadLazy';
|
||||
import { getCloudflareImageUrl } from '@/lib/cloudflareImageUtils';
|
||||
|
||||
export type ImageType = 'logo' | 'banner' | 'card';
|
||||
@@ -125,7 +125,7 @@ export function EntityImageUploader({
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-sm text-muted-foreground">{spec.description}</p>
|
||||
<UppyPhotoUpload
|
||||
<UppyPhotoUploadLazy
|
||||
onUploadComplete={(urls) => handleUploadComplete(type, urls)}
|
||||
maxFiles={1}
|
||||
variant="compact"
|
||||
|
||||
@@ -8,7 +8,7 @@ import { Button } from '@/components/ui/button';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { UppyPhotoUpload } from './UppyPhotoUpload';
|
||||
import { UppyPhotoUploadLazy } from './UppyPhotoUploadLazy';
|
||||
import { PhotoCaptionEditor, PhotoWithCaption } from './PhotoCaptionEditor';
|
||||
import { supabase } from '@/integrations/supabase/client';
|
||||
import { useAuth } from '@/hooks/useAuth';
|
||||
@@ -340,7 +340,7 @@ export function UppyPhotoSubmissionUpload({
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
<UppyPhotoUpload
|
||||
<UppyPhotoUploadLazy
|
||||
onFilesSelected={handleFilesSelected}
|
||||
deferUpload={true}
|
||||
maxFiles={10}
|
||||
|
||||
15
src/components/upload/UppyPhotoSubmissionUploadLazy.tsx
Normal file
15
src/components/upload/UppyPhotoSubmissionUploadLazy.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { UploadPlaceholder } from '@/components/loading/PageSkeletons';
|
||||
import { UppyPhotoSubmissionUploadProps } from '@/types/submissions';
|
||||
|
||||
const UppyPhotoSubmissionUpload = lazy(() =>
|
||||
import('./UppyPhotoSubmissionUpload').then(module => ({ default: module.UppyPhotoSubmissionUpload }))
|
||||
);
|
||||
|
||||
export function UppyPhotoSubmissionUploadLazy(props: UppyPhotoSubmissionUploadProps) {
|
||||
return (
|
||||
<Suspense fallback={<UploadPlaceholder />}>
|
||||
<UppyPhotoSubmissionUpload {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
35
src/components/upload/UppyPhotoUploadLazy.tsx
Normal file
35
src/components/upload/UppyPhotoUploadLazy.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { lazy, Suspense } from 'react';
|
||||
import { UploadPlaceholder } from '@/components/loading/PageSkeletons';
|
||||
import React from 'react';
|
||||
|
||||
const UppyPhotoUpload = lazy(() =>
|
||||
import('./UppyPhotoUpload').then(module => ({ default: module.UppyPhotoUpload }))
|
||||
);
|
||||
|
||||
export interface UppyPhotoUploadLazyProps {
|
||||
onUploadComplete?: (urls: string[]) => void;
|
||||
onFilesSelected?: (files: File[]) => void;
|
||||
onUploadStart?: () => void;
|
||||
onUploadError?: (error: Error) => void;
|
||||
maxFiles?: number;
|
||||
maxSizeMB?: number;
|
||||
allowedFileTypes?: string[];
|
||||
metadata?: Record<string, any>;
|
||||
variant?: string;
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
disabled?: boolean;
|
||||
showPreview?: boolean;
|
||||
size?: 'default' | 'compact' | 'large';
|
||||
enableDragDrop?: boolean;
|
||||
showUploadModal?: boolean;
|
||||
deferUpload?: boolean;
|
||||
}
|
||||
|
||||
export function UppyPhotoUploadLazy(props: UppyPhotoUploadLazyProps) {
|
||||
return (
|
||||
<Suspense fallback={<UploadPlaceholder />}>
|
||||
<UppyPhotoUpload {...props} />
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
@@ -12,8 +12,8 @@ import { Label } from '@/components/ui/label';
|
||||
import { Card } from '@/components/ui/card';
|
||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table';
|
||||
import { Badge } from '@/components/ui/badge';
|
||||
import { UppyPhotoUpload } from '@/components/upload/UppyPhotoUpload';
|
||||
import { MarkdownEditor } from '@/components/admin/MarkdownEditor';
|
||||
import { UppyPhotoUploadLazy } from '@/components/upload/UppyPhotoUploadLazy';
|
||||
import { MarkdownEditorLazy } from '@/components/admin/MarkdownEditorLazy';
|
||||
import { generateSlugFromName } from '@/lib/slugUtils';
|
||||
import { extractCloudflareImageId } from '@/lib/cloudflareImageUtils';
|
||||
import { Edit, Trash2, Eye, Plus } from 'lucide-react';
|
||||
@@ -306,7 +306,7 @@ export default function AdminBlog() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>Featured Image</Label>
|
||||
<UppyPhotoUpload
|
||||
<UppyPhotoUploadLazy
|
||||
onUploadComplete={(urls) => {
|
||||
if (urls.length > 0) {
|
||||
const url = urls[0];
|
||||
@@ -330,7 +330,7 @@ export default function AdminBlog() {
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="content">Content (Markdown) *</Label>
|
||||
<MarkdownEditor
|
||||
<MarkdownEditorLazy
|
||||
value={content}
|
||||
onChange={setContent}
|
||||
onSave={async (value) => {
|
||||
|
||||
Reference in New Issue
Block a user