اذهب إلى المحتوى

السؤال

نشر

انا انتهيت من مشروعي Fullstack بستخدام Bun.js Express react

ورفعت مشروع على render واخدت رابط وحطيته ب fetch API

وفي cors قبل رفع على ريندر انا حاطت رابط مشروع

وMongoDB atlas IP عالمي

ورفعت مشروع على نتليفاي بعد بنائه ولم يعمل خادم

رابط خادم https://pinterest-clone-3ans.onrender.com

رابط مشروع https://www-pinterest-clone.netlify.app/

import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import "./profile.css";
import { FaDeleteLeft, FaPenClip } from "react-icons/fa6";
import { useNavigate } from "react-router-dom";

interface Image {
  _id: string;
  title: string;
  description: string;
  imageUrl: string;
}

const Profile = () => {
  const [images, setImages] = useState<Image[]>([]);
  const [loading, setLoading] = useState(false);

  const [modalOpen, setModalOpen] = useState(false);
  const [editingImage, setEditingImage] = useState<Image | null>(null);

  const [newTitle, setNewTitle] = useState("");
  const [newDescription, setNewDescription] = useState("");
  const [newFile, setNewFile] = useState<File | null>(null);
  const navigate = useNavigate();
  const token = localStorage.getItem("token");

  useEffect(() => {
    const token = localStorage.getItem("token");
    if (!token) {
      toast.error("You must log in first");
      navigate("/login");
      return;
    }
  });

  const fetchImages = async () => {
    if (!token) return;
    setLoading(true);
    try {
      const res = await fetch("https://pinterest-clone-3ans.onrender.com/api/image/imageme", {
        credentials: "include",
        method: "GET",
        headers: { Authorization: `Bearer ${token}` },
      });
      const data = await res.json();
      setImages(data);
    } catch (err) {
      console.error(err);
      toast.error("Failed to load images");
    } finally {
      setLoading(false);
    }
  };

  useEffect(() => {
    fetchImages();
  }, []);

  const handleDelete = async (id: string) => {
    if (!token) return;
    try {
      const res = await fetch(`https://pinterest-clone-3ans.onrender.com/api/image/${id}`, {
        credentials: "include",
        method: "DELETE",
        headers: { Authorization: `Bearer ${token}` },
      });
      const data = await res.json();
      if (res.ok) {
        toast.success(data.success);
        setImages(images.filter((img) => img._id !== id));
      } else toast.error(data.error);
    } catch (err) {
      console.error(err);
      toast.error("Failed to delete image");
    }
  };

  const openEditModal = (img: Image) => {
    setEditingImage(img);
    setNewTitle(img.title);
    setNewDescription(img.description);
    setModalOpen(true);
  };

  const handleUpdate = async () => {
    if (!token || !editingImage) return;
    try {
      const res = await fetch(
        `https://pinterest-clone-3ans.onrender.com/api/image/${editingImage._id}`,
        {
         credentials: "include",
          method: "PUT",
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${token}`,
          },
          body: JSON.stringify({
            title: newTitle,
            description: newDescription,
          }),
        }
      );
      const data = await res.json();
      if (res.ok) {
        toast.success(data.success);
        fetchImages();
        setModalOpen(false);
        setNewTitle("");
        setNewDescription("");
      } else toast.error(data.error);
    } catch (err) {
      console.error(err);
      toast.error("Failed to update image");
    }
  };

  const handleUpload = async () => {
    if (!token || !newTitle || !newDescription || !newFile) {
      return toast.error("All fields are required for upload");
    }

    if (newDescription.length > 60) {
      return toast.error("Maximum description: 60 characters");
    }

    const formData = new FormData();
    formData.append("title", newTitle);
    formData.append("description", newDescription);
    formData.append("image", newFile);

    try {
      const res = await fetch("https://pinterest-clone-3ans.onrender.com/api/image/upload", {
        credentials: "include",
        method: "POST",
        headers: { Authorization: `Bearer ${token}` },
        body: formData,
      });
      const data = await res.json();
      if (res.ok) {
        toast.success(data.success);
        fetchImages();
        setNewTitle("");
        setNewDescription("");
        setNewFile(null);
      } else toast.error(data.error);
    } catch (err) {
      console.error(err);
      toast.error("Failed to upload image");
    }
  };

  return (
    <div className="profile-page">
      <h2>Your Images</h2>

      <div className="upload-section">
        <input
          type="text"
          placeholder="Title"
          value={newTitle}
          onChange={(e) => setNewTitle(e.target.value)}
        />
        <input
          type="text"
          placeholder="Description"
          value={newDescription}
          onChange={(e) => setNewDescription(e.target.value)}
        />
        <input
          type="file"
          onChange={(e) => setNewFile(e.target.files?.[0] || null)}
        />
        <button onClick={handleUpload}>Upload Image</button>
      </div>

      {loading && <p className="no-images">Loading...</p>}
      {images.length === 0 && !loading && (
        <p className="no-images">No images uploaded yet.</p>
      )}

      <div className="image-grid">
        {images.map((img) => (
          <div key={img._id} className="image-card">
            <img src={img.imageUrl} alt={img.title} />
            <h3>{img.title}</h3>
            <p>{img.description}</p>
            <div className="image-actions">
              <button onClick={() => openEditModal(img)}>
                <FaPenClip /> Edit
              </button>
              <button onClick={() => handleDelete(img._id)}>
                <FaDeleteLeft /> Delete
              </button>
            </div>
          </div>
        ))}
      </div>

      {modalOpen && editingImage && (
        <div className="modal">
          <div className="modal-content">
            <h3>Edit Image</h3>
            <input
              type="text"
              value={newTitle}
              onChange={(e) => setNewTitle(e.target.value)}
            />
            <input
              type="text"
              value={newDescription}
              onChange={(e) => setNewDescription(e.target.value)}
            />
            <div className="modal-actions">
              <button onClick={handleUpdate}>Save</button>
              <button onClick={() => setModalOpen(false)}>Cancel</button>
            </div>
          </div>
        </div>
      )}
    </div>
  );
};

export default Profile;
import express from 'express';
import dotenv from 'dotenv';
import cors from 'cors';
import { connectDB } from './utils/db';
import userRouter from './routes/user.routes';
import imageRouter from './routes/image.routes';

dotenv.config();

const app = express();
const port = process.env.PORT || 3000;

connectDB();

app.use(cors({
  origin: 'https://www-pinterest-clone.netlify.app', 
  credentials: true,               
}));

app.use(express.json());

app.get('/', (req, res) => {
  res.send('Hello Bun!');
});

app.use('/api/auth', userRouter);
app.use('/api/image', imageRouter);

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

 

ء.png

Recommended Posts

  • 0
نشر
بتاريخ 1 دقيقة مضت قال Zen Eddin Allaham:

لا توجد اي مشاكل في Logs ومع ذالك خادم لا يعمل

الخادم يعمل، في حال لا يعمل، فستحصل على رسالة خطأ عند زيارة الرابط:

هل قمت بتعيين قيم لمتغيرات البيئة بشكل صحيح على الاستضافة؟ وبالأخص رابط قاعدة البيانات على Atlas

انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أجب على هذا السؤال...

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.

  • إعلانات

  • تابعنا على



×
×
  • أضف...