maked fully responsive for all devices

This commit is contained in:
Saimon8420 2025-02-12 13:24:33 +06:00
parent 1ed96799a0
commit 0f1d2c2ea9
10 changed files with 95 additions and 20 deletions

54
src/ErrorBoundary.jsx Normal file
View 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;

View file

@ -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>
) )
} }

View file

@ -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}

View file

@ -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 (

View file

@ -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

View file

@ -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>
)} )}

View file

@ -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>

View file

@ -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 (

View file

@ -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"] });

View file

@ -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}>
<GlobalErrorBoundary>
<App /> <App />
</GlobalErrorBoundary>
</QueryClientProvider> </QueryClientProvider>
</BrowserRouter> </BrowserRouter>
</OpenContextProvider> </OpenContextProvider>