maked fully responsive for all devices
This commit is contained in:
parent
1ed96799a0
commit
0f1d2c2ea9
10 changed files with 95 additions and 20 deletions
54
src/ErrorBoundary.jsx
Normal file
54
src/ErrorBoundary.jsx
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
import { AlertCircle, RefreshCw } from "lucide-react";
|
||||||
|
import { Component } from "react";
|
||||||
|
|
||||||
|
class GlobalErrorBoundary extends Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false, error: null };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error) {
|
||||||
|
return { hasError: true, error };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error, errorInfo) {
|
||||||
|
console.error("Global Error Caught:", error, errorInfo);
|
||||||
|
// You can log this to an external service like Sentry or LogRocket
|
||||||
|
}
|
||||||
|
|
||||||
|
handleRetry = () => {
|
||||||
|
this.setState({ hasError: false, error: null });
|
||||||
|
window.location.reload(); // Reload the app
|
||||||
|
};
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col items-center justify-center min-h-screen bg-background text-foreground p-6">
|
||||||
|
<div className="w-full max-w-md">
|
||||||
|
<div className="bg-card text-card-foreground rounded-lg shadow-lg p-8">
|
||||||
|
<div className="flex items-center justify-center mb-6">
|
||||||
|
<AlertCircle className="w-16 h-16 text-destructive" />
|
||||||
|
</div>
|
||||||
|
<h1 className="text-2xl font-bold text-primary-text text-center mb-4">Oops! Something went wrong</h1>
|
||||||
|
<p className="text-muted-foreground text-center mb-6">
|
||||||
|
{this.state.error?.message || "An unexpected error occurred."}
|
||||||
|
</p>
|
||||||
|
<button
|
||||||
|
onClick={this.handleRetry}
|
||||||
|
className="w-full flex items-center justify-center px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors"
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-5 h-5 mr-2" />
|
||||||
|
Try Again
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GlobalErrorBoundary;
|
||||||
29
src/Home.jsx
29
src/Home.jsx
|
|
@ -18,7 +18,7 @@ import { useToast } from './hooks/use-toast';
|
||||||
import useProject from './hooks/useProject';
|
import useProject from './hooks/useProject';
|
||||||
|
|
||||||
export const Home = () => {
|
export const Home = () => {
|
||||||
const { activeObject } = useContext(ActiveObjectContext);
|
const { activeObject, setActiveObject } = useContext(ActiveObjectContext);
|
||||||
const { canvas, selectedPanel } = useContext(CanvasContext);
|
const { canvas, selectedPanel } = useContext(CanvasContext);
|
||||||
const { user, setUser } = useContext(AuthContext);
|
const { user, setUser } = useContext(AuthContext);
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
|
|
@ -62,11 +62,11 @@ export const Home = () => {
|
||||||
if (token && !isLoading && data?.status === 201) {
|
if (token && !isLoading && data?.status === 201) {
|
||||||
navigate("/");
|
navigate("/");
|
||||||
}
|
}
|
||||||
if (projectData?.status === 404 || projectData?.status === 500) {
|
if (projectData?.status === 500 && id) {
|
||||||
navigate("/");
|
navigate("/");
|
||||||
toast({ variant: "destructive", title: projectData?.status, description: "No project found" });
|
toast({ variant: "destructive", title: projectData?.status, description: "No project found" });
|
||||||
}
|
}
|
||||||
if (projectData?.status === 200 & !projectLoading && projectData?.data?.object && canvas && selectedPanel === "project" && id) {
|
if (projectData && projectData?.status === 200 && !projectLoading && projectData?.data?.object && canvas && (selectedPanel === "project" || selectedPanel === "") && id) {
|
||||||
const isEmpty = (obj) => Object.values(obj).length === 0;
|
const isEmpty = (obj) => Object.values(obj).length === 0;
|
||||||
if (!isEmpty(projectData?.data?.object)) {
|
if (!isEmpty(projectData?.data?.object)) {
|
||||||
canvas.loadFromJSON(projectData?.data?.object);
|
canvas.loadFromJSON(projectData?.data?.object);
|
||||||
|
|
@ -75,8 +75,18 @@ export const Home = () => {
|
||||||
}
|
}
|
||||||
}, [navigate, isLoading, data, projectData, projectLoading, canvas, selectedPanel, id, toast]);
|
}, [navigate, isLoading, data, projectData, projectLoading, canvas, selectedPanel, id, toast]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!projectData && canvas) {
|
||||||
|
canvas.clear();
|
||||||
|
canvas.renderAll();
|
||||||
|
canvas.setBackgroundColor("#ffffff", canvas.renderAll.bind(canvas));
|
||||||
|
|
||||||
|
setActiveObject(null);
|
||||||
|
}
|
||||||
|
}, [projectData, canvas, setActiveObject])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className='relative flex flex-col'>
|
||||||
{
|
{
|
||||||
isLoading &&
|
isLoading &&
|
||||||
<div className='flex justify-center items-center h-screen'>
|
<div className='flex justify-center items-center h-screen'>
|
||||||
|
|
@ -91,20 +101,20 @@ export const Home = () => {
|
||||||
|
|
||||||
{
|
{
|
||||||
activeObject &&
|
activeObject &&
|
||||||
<div className="absolute left-[90px] right-[90px] z-[9999] rounded-md p-1 h-fit bg-white border-t border-gray-200 shadow-md my-1 w-[80%] mx-auto">
|
<div className="absolute left-0 xl:left-[90px] lg:left-[90px] md:left-[90px] sm:left-[80px] right-0 xl:right-[90px] lg:right-[90px] md:right-[90px] sm:right-[80px] z-[9999] rounded-md p-1 h-fit bg-white border-t border-gray-200 shadow-md my-1 w-[80%] mx-auto">
|
||||||
<TopBar />
|
<TopBar />
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
<div className="absolute z-[999] right-0 bottom-0 flex justify-center items-center h-20 bg-white border-t border-gray-200 shadow-md w-fit">
|
<div className="absolute z-[999] right-0 xl:bottom-0 lg:bottom-0 md:bottom-0 sm:bottom-0 bottom-10 flex justify-center items-center h-20 bg-white border-t border-gray-200 shadow-md w-fit">
|
||||||
<ActionButtons />
|
<ActionButtons />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-full mr-1">
|
<div className="h-full mr-1 hidden xl:block lg:block md:block sm:block">
|
||||||
<Sidebar />
|
<Sidebar />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute ml-20 z-[999] top-[15%]">
|
<div className="absolute ml-0 xl:ml-20 lg:ml-20 md:ml-20 z-[999] top-[15%]">
|
||||||
<EditorPanel />
|
<EditorPanel />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -118,6 +128,9 @@ export const Home = () => {
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
<div className='h-full z-[999] block xl:hidden lg:hidden md:hidden sm:hidden'>
|
||||||
|
<Sidebar />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -253,7 +253,7 @@ export default function Canvas() {
|
||||||
>
|
>
|
||||||
<AspectRatio
|
<AspectRatio
|
||||||
ratio={getRatioValue(canvasRatio)}
|
ratio={getRatioValue(canvasRatio)}
|
||||||
className="rounded-lg border-0 border-primary/10 transition-all duration-300 ease-in-out"
|
className="rounded-lg border-0 border-primary/10 transition-all duration-300 ease-in-out cursor-pointer"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
ref={canvasRef}
|
ref={canvasRef}
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ const sidebarItems = [
|
||||||
export function Sidebar() {
|
export function Sidebar() {
|
||||||
const { selectedPanel, setSelectedPanel } = useContext(CanvasContext);
|
const { selectedPanel, setSelectedPanel } = useContext(CanvasContext);
|
||||||
return (
|
return (
|
||||||
<div className="w-20 border-r bg-background flex flex-col items-center justify-center py-4 gap-6 md:pt-16 xl:pt-0 h-full">
|
<div className="w-full xl:w-20 lg:w-20 md:w-20 sm:w-20 border-r bg-background flex xl:flex-col lg:flex-col md:flex-col sm:flex-col items-center justify-center py-4 gap-6 md:pt-16 xl:pt-0 h-full overflow-x-scroll">
|
||||||
{sidebarItems.map((item) => {
|
{sidebarItems.map((item) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ const Unauthenticated = () => {
|
||||||
Sorry, you need to be authenticated to access this page. Please log in to continue.
|
Sorry, you need to be authenticated to access this page. Please log in to continue.
|
||||||
</p>
|
</p>
|
||||||
<a
|
<a
|
||||||
href="https://dashboard.planpostai.com/login"
|
href="https://dashboard.planpostai.com"
|
||||||
className="inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background bg-primary text-primary-foreground hover:bg-primary/90 h-10 py-2 px-4"
|
className="inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none ring-offset-background bg-primary text-primary-foreground hover:bg-primary/90 h-10 py-2 px-4"
|
||||||
>
|
>
|
||||||
Go Back to Dashboard
|
Go Back to Dashboard
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ const EditorPanel = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{selectedPanel !== "" && (
|
{selectedPanel !== "" && (
|
||||||
<div className="w-80 h-[450px] bg-background rounded-xl shadow-lg mx-4 my-auto overflow-y-scroll scrollbar-hide">
|
<div className="w-screen xl:w-80 lg:w-80 md:w-80 sm:w-80 h-[450px] bg-background rounded-xl shadow-lg mx-0 xl:mx-4 lg:mx-4 md:mx-4 sm:mx-4 my-auto overflow-y-scroll scrollbar-hide">
|
||||||
{renderPanel()}
|
{renderPanel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@ import { Loader, Trash, X } from 'lucide-react';
|
||||||
import { ScrollArea } from '../ui/scroll-area';
|
import { ScrollArea } from '../ui/scroll-area';
|
||||||
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
import { useQuery, useQueryClient } from '@tanstack/react-query';
|
||||||
import { getProjects } from '@/api/projectApi';
|
import { getProjects } from '@/api/projectApi';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
import useProject from '@/hooks/useProject';
|
import useProject from '@/hooks/useProject';
|
||||||
|
|
||||||
export const ProjectPanel = () => {
|
export const ProjectPanel = () => {
|
||||||
const { setSelectedPanel } = useContext(CanvasContext);
|
const { setSelectedPanel } = useContext(CanvasContext);
|
||||||
|
|
||||||
|
const params = useParams();
|
||||||
|
const { id } = params;
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const queryClient = useQueryClient(); // Initialize query client
|
const queryClient = useQueryClient(); // Initialize query client
|
||||||
|
|
||||||
|
|
@ -55,7 +58,7 @@ export const ProjectPanel = () => {
|
||||||
return (
|
return (
|
||||||
<div key={project?.id} className="flex flex-col gap-1 p-1 rounded-md border">
|
<div key={project?.id} className="flex flex-col gap-1 p-1 rounded-md border">
|
||||||
<div
|
<div
|
||||||
className="rounded-md flex p-1 flex-col gap-1 bg-red-50 hover:bg-red-100 cursor-pointer transition-all"
|
className={`rounded-md flex p-1 flex-col gap-1 bg-red-50 hover:bg-red-100 cursor-pointer transition-all ${project?.id === id ? "border-2 border-red-200" : ""} `}
|
||||||
onClick={() => handleNavigate(project.id)}
|
onClick={() => handleNavigate(project.id)}
|
||||||
>
|
>
|
||||||
<p className="font-bold text-sm truncate">{project?.name}</p>
|
<p className="font-bold text-sm truncate">{project?.name}</p>
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,8 @@ import { Input } from './ui/input';
|
||||||
import { Label } from './ui/label';
|
import { Label } from './ui/label';
|
||||||
import { Save, Trash2 } from 'lucide-react';
|
import { Save, Trash2 } from 'lucide-react';
|
||||||
import { Textarea } from './ui/textarea';
|
import { Textarea } from './ui/textarea';
|
||||||
import { useMutation } from '@tanstack/react-query';
|
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
import { Separator } from './ui/separator';
|
import { Separator } from './ui/separator';
|
||||||
import { deleteImage, uploadImage } from '@/api/uploadApi';
|
|
||||||
import useProject from '@/hooks/useProject';
|
import useProject from '@/hooks/useProject';
|
||||||
import { captureCanvas } from '@/lib/captureCanvas';
|
import { captureCanvas } from '@/lib/captureCanvas';
|
||||||
import useCanvasCapture from '@/hooks/useCanvasCapture';
|
import useCanvasCapture from '@/hooks/useCanvasCapture';
|
||||||
|
|
@ -90,7 +88,7 @@ const SaveCanvas = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleDeleteProject = () => {
|
const handleDeleteProject = () => {
|
||||||
projectDelete();
|
projectDelete(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ const useProject = () => {
|
||||||
// Fetch project data
|
// Fetch project data
|
||||||
const { data: projectData, isLoading } = useQuery({
|
const { data: projectData, isLoading } = useQuery({
|
||||||
queryKey: ["project", id],
|
queryKey: ["project", id],
|
||||||
queryFn: async () => await getProjectById(id),
|
queryFn: () => getProjectById(id),
|
||||||
enabled: !!id,
|
enabled: !!id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -47,21 +47,25 @@ const useProject = () => {
|
||||||
},
|
},
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
if (data?.status === 200) {
|
if (data?.status === 200) {
|
||||||
|
|
||||||
if (selectedPanel === "canvas") {
|
if (selectedPanel === "canvas") {
|
||||||
toast({
|
toast({
|
||||||
title: data?.status,
|
title: data?.status,
|
||||||
description: data?.message,
|
description: data?.message,
|
||||||
})
|
})
|
||||||
setSelectedPanel("");
|
|
||||||
queryClient.invalidateQueries({ queryKey: ["project", id] });
|
queryClient.invalidateQueries({ queryKey: ["project", id] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
canvas.clear();
|
canvas.clear();
|
||||||
canvas.renderAll();
|
canvas.renderAll();
|
||||||
canvas.setBackgroundColor("#ffffff", canvas.renderAll.bind(canvas));
|
canvas.setBackgroundColor("#ffffff", canvas.renderAll.bind(canvas));
|
||||||
|
setActiveObject(null);
|
||||||
|
navigate("/");
|
||||||
|
setSelectedPanel("");
|
||||||
}
|
}
|
||||||
setActiveObject(null);
|
setActiveObject(null);
|
||||||
navigate("/");
|
navigate("/");
|
||||||
|
setSelectedPanel("");
|
||||||
}
|
}
|
||||||
queryClient.invalidateQueries({ queryKey: ["project", id] });
|
queryClient.invalidateQueries({ queryKey: ["project", id] });
|
||||||
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
queryClient.invalidateQueries({ queryKey: ["projects"] });
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import OpenContextProvider from "./components/Context/openContext/OpenContextPro
|
||||||
import AuthContextProvider from "./components/Context/authContext/AuthProvider";
|
import AuthContextProvider from "./components/Context/authContext/AuthProvider";
|
||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
|
import GlobalErrorBoundary from "./ErrorBoundary";
|
||||||
|
|
||||||
// Create a client
|
// Create a client
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient();
|
||||||
|
|
@ -21,7 +22,9 @@ createRoot(document.getElementById("root")).render(
|
||||||
<OpenContextProvider>
|
<OpenContextProvider>
|
||||||
<BrowserRouter>
|
<BrowserRouter>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
<App />
|
<GlobalErrorBoundary>
|
||||||
|
<App />
|
||||||
|
</GlobalErrorBoundary>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</BrowserRouter>
|
</BrowserRouter>
|
||||||
</OpenContextProvider>
|
</OpenContextProvider>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue