diff --git a/.github/workflows/pr-checker.yml b/.github/workflows/pr-checker.yml index 790a14e..7671f2a 100644 --- a/.github/workflows/pr-checker.yml +++ b/.github/workflows/pr-checker.yml @@ -85,7 +85,3 @@ jobs: body: successMessage }); } - - - name: Fail if validation failed - if: env.PR_VALID == 'false' - run: exit 1 diff --git a/client/index.html b/client/index.html index ac9478c..4ce5f50 100644 --- a/client/index.html +++ b/client/index.html @@ -2,218 +2,230 @@ - - - - Bitbox - Where Projects Find Solutions Together - - - - - - - - - - + + + + Bitbox - Where Projects Find Solutions Together + + + + + + + + + + + -
- +
+ + integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" + crossorigin="anonymous"> - -
- - - - - - - - - - - - - - - + + + + + diff --git a/client/package-lock.json b/client/package-lock.json index 62002c9..933f7f1 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -56,6 +56,7 @@ "socket.io": "^4.7.5", "socket.io-client": "^4.8.1", "sort-by": "^0.0.2", + "twilio": "^5.3.5", "uuid": "^11.0.2", "yup": "^1.4.0" }, @@ -2118,6 +2119,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "dev": true, @@ -2562,6 +2575,12 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, "node_modules/call-bind": { "version": "1.0.7", "license": "MIT", @@ -3011,6 +3030,15 @@ "dev": true, "license": "MIT" }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.45", "dev": true, @@ -3996,6 +4024,8 @@ }, "node_modules/firebase": { "version": "10.14.1", + "resolved": "https://registry.npmjs.org/firebase/-/firebase-10.14.1.tgz", + "integrity": "sha512-0KZxU+Ela9rUCULqFsUUOYYkjh7OM1EWdIfG6///MtXd0t2/uUIf0iNV5i0KariMhRQ5jve/OY985nrAXFaZeQ==", "license": "Apache-2.0", "dependencies": { "@firebase/analytics": "0.10.8", @@ -4388,6 +4418,19 @@ "version": "0.5.8", "license": "MIT" }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/idb": { "version": "7.1.1", "license": "ISC" @@ -4953,6 +4996,40 @@ "node": ">=6" } }, + "node_modules/jsonwebtoken": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", + "integrity": "sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ==", + "license": "MIT", + "dependencies": { + "jws": "^3.2.2", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "dev": true, @@ -4967,6 +5044,27 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", + "integrity": "sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz", + "integrity": "sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA==", + "license": "MIT", + "dependencies": { + "jwa": "^1.4.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "dev": true, @@ -5070,6 +5168,42 @@ "version": "4.0.8", "license": "MIT" }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, "node_modules/lodash.memoize": { "version": "4.1.2", "license": "MIT" @@ -5079,6 +5213,12 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, "node_modules/lodash.reduce": { "version": "4.6.0", "license": "MIT" @@ -5304,7 +5444,6 @@ }, "node_modules/object-inspect": { "version": "1.13.2", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -5832,6 +5971,21 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "dev": true, @@ -7139,6 +7293,12 @@ "loose-envify": "^1.1.0" } }, + "node_modules/scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", + "license": "BSD-3-Clause" + }, "node_modules/scroll-into-view-if-needed": { "version": "3.1.0", "license": "MIT", @@ -7207,7 +7367,6 @@ }, "node_modules/side-channel": { "version": "1.0.6", - "dev": true, "license": "MIT", "dependencies": { "call-bind": "^1.0.7", @@ -7637,6 +7796,24 @@ "version": "2.8.0", "license": "0BSD" }, + "node_modules/twilio": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.3.5.tgz", + "integrity": "sha512-f/sA1Yd6TyIzfcq0u4QDGU+93afwswsJB+rf3T08tvBAMobBDVR3DfGREwJr5jp8xUic0qWa7GbJidk16NA4bg==", + "license": "MIT", + "dependencies": { + "axios": "^1.7.4", + "dayjs": "^1.11.9", + "https-proxy-agent": "^5.0.0", + "jsonwebtoken": "^9.0.2", + "qs": "^6.9.4", + "scmp": "^2.1.0", + "xmlbuilder": "^13.0.2" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/type-check": { "version": "0.4.0", "dev": true, @@ -8079,6 +8256,15 @@ } } }, + "node_modules/xmlbuilder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", + "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, "node_modules/xmlhttprequest-ssl": { "version": "2.1.2", "engines": { diff --git a/client/package.json b/client/package.json index 55fb2f1..a1d99b2 100644 --- a/client/package.json +++ b/client/package.json @@ -58,6 +58,7 @@ "socket.io": "^4.7.5", "socket.io-client": "^4.8.1", "sort-by": "^0.0.2", + "twilio": "^5.3.5", "uuid": "^11.0.2", "yup": "^1.4.0" }, diff --git a/client/src/App.css b/client/src/App.css index 3b19760..c2eaa83 100644 --- a/client/src/App.css +++ b/client/src/App.css @@ -61,4 +61,4 @@ body { .alert-bar { width: max-content; box-shadow: rgba(17, 17, 26, 0.1) 0px 4px 16px, rgba(17, 17, 26, 0.1) 0px 8px 24px, rgba(17, 17, 26, 0.1) 0px 16px 56px; -} \ No newline at end of file +} diff --git a/client/src/App.jsx b/client/src/App.jsx index e6f655a..96bf6d1 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -18,7 +18,7 @@ import ProjectState from "./context/ProjectState"; import ProfileState from "./context/ProfileState"; import CodeOfConduct from "./component/Footers/Codeofconduct"; import Feedback from "./component/Footers/Feedback"; -import Faqsec from "./component/Faqsec" +import FAQs from "./component/FAQs" import ContactUs from "./component/Footers/Contactus"; import PrivacyPolicy from "./component/Footers/Privacypolicy"; import TermOfUse from "./component/Footers/TermOfUse"; @@ -36,12 +36,15 @@ import NotFound from "./component/NotFound"; import ProgressBar from "./component/ProgressBar/ProgressBar"; import Cursor from './component/Cursor'; import ReadMoreBlog from './component/ReadMoreBlog'; +import AI from "./component/AIReview"; import AOS from "aos"; import "aos/dist/aos.css"; import Collab from "./component/Collab"; import CreateBlog from "./component/CreateBlog"; +import LoginOTP from "./component/LoginPhone"; import UploadProject from "./component/UploadProject"; + const Layout = ({ children, mode, setProgress, toggleMode, showAlert }) => { return (
@@ -54,6 +57,7 @@ const Layout = ({ children, mode, setProgress, toggleMode, showAlert }) => { blog="Blogs" discussion="Discussion" contributors="Contributors" + ai="AI" Feedback="Feedback" showAlert={showAlert} mode={mode} @@ -132,6 +136,7 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> @@ -141,17 +146,19 @@ function App() { } /> } /> } /> + } /> } /> } /> } /> } /> } /> + } /> } /> } /> } /> } /> } /> - } /> + } /> } /> diff --git a/client/src/assets/images/contact.png b/client/src/assets/images/contact.png new file mode 100644 index 0000000..476844f Binary files /dev/null and b/client/src/assets/images/contact.png differ diff --git a/client/src/assets/images/faqs.png b/client/src/assets/images/faqs.png new file mode 100644 index 0000000..56689eb Binary files /dev/null and b/client/src/assets/images/faqs.png differ diff --git a/client/src/component/Ai.jsx b/client/src/component/AIReview.jsx similarity index 94% rename from client/src/component/Ai.jsx rename to client/src/component/AIReview.jsx index 4bd4ada..daf0e36 100644 --- a/client/src/component/Ai.jsx +++ b/client/src/component/AIReview.jsx @@ -38,7 +38,9 @@ const Ai = () => { }; return ( -
+
+ {/* Heading */} +

AI Playground

{/* API Key Input */}
{ - if (project.title.trim() === "") { - toast.error("Title is required"); - return; - } - // Modify YouTube link - const modifiedYouTubeLink = modifyYouTubeLink(project.youTubeLink); - // Add project API call - addProject(project.title, project.description, project.gitHubLink, modifiedYouTubeLink, project.tags); // Pass modifiedYouTubeLink instead of project.youTubeLink - refClose.current.click(); - setProject({ title: "", description: "", gitHubLink: "", youTubeLink: "" }); - toast.success("Project Added Successfully"); - }; + // Separate state for each input + const [title, setTitle] = useState(""); + const [description, setDescription] = useState(""); + const [gitHubLink, setGitHubLink] = useState(""); + const [youTubeLink, setYouTubeLink] = useState(""); + const [tags, setTags] = useState(""); - const handleKeyDown = (e) => { - if (e.key === 'Enter') { - e.preventDefault(); // Prevent form submission on Enter key press - handleSubmit(); // Manually handle the click event - } - } + const refClose = useRef(null); - useEffect(() => { - const handleKeyPress = (event) => { - if (event.key === "Enter" && project.title.trim() !== "") { - handleSubmit(); + // Handle form submission + const handleSubmit = async (e) => { + e.preventDefault(); // Prevent default form submission + setIsLoading(true); + try { + if (title.trim() === "") { + toast.error("Title is required"); + return; } - }; - document.addEventListener("keypress", handleKeyPress); + // Validate GitHub Link + if (gitHubLink && !/^https:\/\/github\.com\//.test(gitHubLink)) { + toast.error("Please enter a valid GitHub URL."); + return; + } - return () => { - document.removeEventListener("keypress", handleKeyPress); - }; - // eslint-disable-next-line - }, [project.title]); // Only re-run the effect if project.title changes + // Validate YouTube Link + if (youTubeLink && !/^https:\/\/(www\.)?youtube\.com\/watch\?v=/.test(youTubeLink)) { + toast.error("Please enter a valid YouTube URL."); + return; + } - const onChange = (e) => { - // Able to write in the input field - setProject({ ...project, [e.target.name]: e.target.value }); + // Modify YouTube link to embed format + const modifiedYouTubeLink = modifyYouTubeLink(youTubeLink); + + // Call addProject function from context + await addProject(title, description, gitHubLink, modifiedYouTubeLink, tags); + toast.success("Project Added Successfully"); + // console.log(title + " " + description + " " + gitHubLink + " " + youTubeLink) + // Clear form + setTitle(""); + setDescription(""); + setGitHubLink(""); + setYouTubeLink(""); + setTags(""); + + // Close modal + refClose.current.click(); + } catch (error) { + toast.error("Failed to upload the project. Please try again."); + } finally { + setIsLoading(false); // End loading state + } }; - // Function to modify YouTube link + // Function to modify YouTube link (from watch to embed URL) const modifyYouTubeLink = (link) => { if (link.includes("youtube.com/watch?v=")) { const videoId = link.split("youtube.com/watch?v=")[1]; @@ -67,7 +74,6 @@ function AddProject({ mode }) { } }; - // Upload Project Modal const themeStyles = mode === 'dark' ? 'bg-gray-800 text-white' : 'bg-white text-gray-900'; return ( @@ -82,16 +88,9 @@ function AddProject({ mode }) { {isModalOpen && (
{/* Modal Content */} -
+
{/* Close Button in Top Right */} - @@ -109,9 +108,8 @@ function AddProject({ mode }) { type="text" id="title" name="title" - value={project.title} - onChange={onChange} - onKeyDown={handleKeyDown} + value={title} + onChange={(e) => setTitle(e.target.value)} className={`w-full mt-1 p-2 border rounded-md focus:ring-2 focus:ring-blue-500 focus:outline-none ${mode === 'dark' ? 'bg-gray-700 text-white border-gray-600' : 'bg-white text-gray-900 border-gray-300'}`} placeholder="Enter your project title" required @@ -126,11 +124,10 @@ function AddProject({ mode }) {
@@ -141,44 +138,42 @@ function AddProject({ mode }) { GitHub Link setGitHubLink(e.target.value)} className={`w-full mt-1 p-2 border rounded-md focus:ring-2 focus:ring-blue-500 focus:outline-none ${mode === 'dark' ? 'bg-gray-700 text-white border-gray-600' : 'bg-white text-gray-900 border-gray-300'}`} placeholder="Enter your project GitHub link" required />
- {/* Youtube Link */} + {/* YouTube Link */}
setYouTubeLink(e.target.value)} className={`w-full mt-1 p-2 border rounded-md focus:ring-2 focus:ring-blue-500 focus:outline-none ${mode === 'dark' ? 'bg-gray-700 text-white border-gray-600' : 'bg-white text-gray-900 border-gray-300'}`} placeholder="Enter your project YouTube link" required /> -
+
{/* Submit Button */}
@@ -186,9 +181,8 @@ function AddProject({ mode }) {
)}
-
- ) + ); } AddProject.propTypes = { @@ -196,4 +190,4 @@ AddProject.propTypes = { mode: PropTypes.string, }; -export default AddProject; \ No newline at end of file +export default AddProject; diff --git a/client/src/component/Blog.jsx b/client/src/component/Blog.jsx index 2806771..1b11a44 100644 --- a/client/src/component/Blog.jsx +++ b/client/src/component/Blog.jsx @@ -2,6 +2,8 @@ import { useEffect, useState, useRef } from "react"; import PropTypes from 'prop-types'; import { Search } from "lucide-react"; import { Link } from "react-router-dom"; +import { motion } from "framer-motion"; + import img1 from "../assets/blogs/1.webp"; import img2 from "../assets/blogs/2.jpeg"; @@ -24,6 +26,8 @@ BlogPage.propTypes = { }; export default function BlogPage(props) { + const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; + const [searchTerm, setSearchTerm] = useState(""); const [selectedCategory, setSelectedCategory] = useState("All"); const [blogPosts, setBlogPosts] = useState([]); @@ -61,7 +65,7 @@ export default function BlogPage(props) { const fetchData = async () => { try { - let response = await fetch("http://localhost:5000/api/blog/all-blog"); + let response = await fetch(`${VITE_SERVER_PORT}/api/blog/all-blog`); let data = await response.json(); setBlogPosts(data.blogs); console.log(data.blogs); @@ -109,59 +113,112 @@ export default function BlogPage(props) { useEffect(() => { fetchData(); + + // eslint-disable-next-line }, []); return ( -
+
{/* Hero Section */} -
-
-

Explore Our Blog

-

Discover stories, insights, and knowledge

+
+
+
+ + Dive into the BitBox Blog! + + + Where Projects Thrive and Connections Come Alive +
-
+
{/* Search and Filter Section */}
-
-
- setSearchTerm(e.target.value)} - /> - +
+
+
+ setSearchTerm(e.target.value)} + /> + +
+ +
+ + Create Blog + +
+
-
+
{categories.map((category) => ( - +
+

+ {category} +

+
+ ))}
-
- - Create blog - -
+
{/* Blog Grid */}
{filteredPosts.length > 0 ? ( filteredPosts.map((blogPost) => { - const postUrl = `http://localhost:5000/api/blog/getById/${blogPost._id}`; + const postUrl = `{VITE_SERVER_PORT}/api/blog/getById/${blogPost._id}`; return (
-
+
{blogPost.content.replace(/<[^>]+>/g, '')}
diff --git a/client/src/component/Collab.jsx b/client/src/component/Collab.jsx index 686923a..4b91d0c 100644 --- a/client/src/component/Collab.jsx +++ b/client/src/component/Collab.jsx @@ -3,7 +3,7 @@ import { io } from "socket.io-client"; import MonacoEditor from "@monaco-editor/react"; // Adjust this to your Socket.IO server URL -const SOCKET_SERVER_URL = "http://localhost:5000"; +const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; const Collab = () => { const [code, setCode] = useState("// Start coding collaboratively!\n"); @@ -12,7 +12,7 @@ const Collab = () => { // Initialize the Socket.IO client useEffect(() => { - const newSocket = io(SOCKET_SERVER_URL); + const newSocket = io(VITE_SERVER_PORT); setSocket(newSocket); newSocket.on("code_update", (newCode) => { diff --git a/client/src/component/Contributors.jsx b/client/src/component/Contributors.jsx index 6f5a62b..83d0063 100644 --- a/client/src/component/Contributors.jsx +++ b/client/src/component/Contributors.jsx @@ -222,6 +222,9 @@ StatCard.propTypes = { icon: PropTypes.node.isRequired, onClick: PropTypes.func, }; +Contributor.propTypes = { + mode: PropTypes.string.isRequired, +}; export default function Contributor(props) { diff --git a/client/src/component/CreateBlog.jsx b/client/src/component/CreateBlog.jsx index 717ff40..4b4bef2 100644 --- a/client/src/component/CreateBlog.jsx +++ b/client/src/component/CreateBlog.jsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import { useState } from 'react'; import './CreateBlog.css'; import axios from 'axios'; import { v4 as uuidv4 } from 'uuid'; @@ -7,6 +7,8 @@ import 'react-quill/dist/quill.snow.css'; import { useNavigate } from 'react-router-dom'; const CreateBlog = () => { + const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; + const [formData, setFormData] = useState({ id: '', title: '', @@ -42,7 +44,7 @@ const CreateBlog = () => { const blogPost = { ...formData, id: uniqueId }; // Add the ID to formData console.log(blogPost) try { - const response = await axios.post('http://localhost:5000/api/blog/post-blog', blogPost, { + const response = await axios.post(`${VITE_SERVER_PORT}/api/blog/post-blog`, blogPost, { headers: { 'Content-Type': 'application/json' } }); @@ -132,7 +134,7 @@ const CreateBlog = () => { />
- + {errors &&

{errors}

}
diff --git a/client/src/component/DiscussionForum.jsx b/client/src/component/DiscussionForum.jsx new file mode 100644 index 0000000..fc008cf --- /dev/null +++ b/client/src/component/DiscussionForum.jsx @@ -0,0 +1,206 @@ +import PropTypes from 'prop-types'; +import { useState, useEffect } from 'react'; + +const DiscussionForum = (props) => { + const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; + + const [questions, setQuestions] = useState([]); + + // Fetch questions from the backend API + useEffect(() => { + const fetchQuestions = async () => { + try { + const response = await fetch(`${VITE_SERVER_PORT}/api/discussion/getQuestion`); + if (response.ok) { + const data = await response.json(); + setQuestions(data); + } else { + console.error('Failed to fetch questions'); + } + } catch (error) { + console.error('Error fetching questions:', error); + } + }; + + fetchQuestions(); + + // eslint-disable-next-line + }, []); + + // Helper function to save a new question + const addQuestion = async (content) => { + const newQuestion = { + content, + answered: false, + answer: '', + }; + + try { + const response = await fetch(`${VITE_SERVER_PORT}/api/discussion/postQuestion`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(newQuestion), + }); + + if (response.ok) { + const savedQuestion = await response.json(); + setQuestions((prevQuestions) => [...prevQuestions, savedQuestion]); + } else { + console.error('Failed to add question'); + } + } catch (error) { + console.error('Error adding question:', error); + } + }; + + // Helper function to add an answer to a question + const addAnswer = async (questionId, answerContent) => { + try { + const response = await fetch(`${VITE_SERVER_PORT}/api/discussion/${questionId}/answer`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ answer: answerContent }), + }); + + if (response.ok) { + const updatedQuestion = await response.json(); + setQuestions((prevQuestions) => + prevQuestions.map((question) => + question._id === questionId ? { ...question, ...updatedQuestion } : question + ) + ); + } else { + console.error('Failed to add answer'); + } + } catch (error) { + console.error('Error adding answer:', error); + } + }; + + // Function to render the Question Card + const renderQuestionCard = (question, props) => { + return ( +
+

{question.content}

+ {question.answered ? ( +
+

Answer:

+

{question.answer}

+
+ ) : ( + + )} +
+ ); + }; + + // Function to render the Answer Form + const AnswerForm = ({ questionId }) => { + const [answer, setAnswer] = useState(''); + + const handleSubmit = (e) => { + e.preventDefault(); + if (answer.trim()) { + addAnswer(questionId, answer); + setAnswer(''); + } + }; + + return ( +
+ + +
- @@ -108,4 +147,4 @@ ContactUs.propTypes = { mode: PropTypes.string, }; -export default ContactUs; +export default ContactUs; \ No newline at end of file diff --git a/client/src/component/Footers/Privacypolicy.jsx b/client/src/component/Footers/Privacypolicy.jsx index 5942a3f..d700947 100644 --- a/client/src/component/Footers/Privacypolicy.jsx +++ b/client/src/component/Footers/Privacypolicy.jsx @@ -1,33 +1,37 @@ -import '../../css/Main.css'; -import PropTypes from 'prop-types'; +import "../../css/Main.css"; +import PropTypes from "prop-types"; export default function PrivacyPolicy(props) { const VITE_CLIENT_PORT = import.meta.env.VITE_CLIENT_PORT; return (
-

+

Privacy Policy

@@ -41,35 +45,42 @@ export default function PrivacyPolicy(props) { you.
-

+

Interpretation

- Words with capitalized initials have meanings defined under the following - conditions. These definitions shall apply whether they appear in singular - or plural. + Words with capitalized initials have meanings defined under the + following conditions. These definitions shall apply whether they + appear in singular or plural.

-

+

Definitions

  • - Account: A unique account created for you to access - our Service or parts of our Service. + Account: A unique account created for you to + access our Service or parts of our Service.
  • - Company: Refers to BitBox (also referred to as "the - Company", "We", "Us", or "Our"). + Company: Refers to BitBox (also referred to as + "the Company", "We", "Us", or "Our").
  • - Cookies: Small files placed on your device (computer, - mobile, or tablet) that store details of your browsing history. + Cookies: Small files placed on your device + (computer, mobile, or tablet) that store details of your browsing + history.
  • - Device: Any device that can access the Service, such - as a computer, cellphone, or tablet. + Device: Any device that can access the Service, + such as a computer, cellphone, or tablet.
  • Personal Data: Information that relates to an @@ -79,15 +90,15 @@ export default function PrivacyPolicy(props) { Service: Refers to the Website.
  • - Service Provider: Any person or company who processes - data on behalf of the Company. + Service Provider: Any person or company who + processes data on behalf of the Company.
  • - Usage Data: Data collected automatically (such as the - duration of a page visit). + Usage Data: Data collected automatically (such as + the duration of a page visit).
  • - Website: Refers to BitBox, accessible from{' '} + Website: Refers to BitBox, accessible from{" "}
  • - You: The individual using the Service, or the company - represented by that individual. + You: The individual using the Service, or the + company represented by that individual.
-

+

Collecting and Using Your Personal Data

-

+

Types of Data Collected

-

+

Personal Data

- We may ask you to provide personally identifiable information to contact - or identify you, including: + We may ask you to provide personally identifiable information to + contact or identify you, including:

  • Email address
  • @@ -139,8 +159,8 @@ export default function PrivacyPolicy(props) {

    We use cookies and similar tracking technologies (beacons, tags, and - scripts) to collect and analyze information to improve our Service. The - types of tracking we use include: + scripts) to collect and analyze information to improve our Service. + The types of tracking we use include:

    • @@ -154,15 +174,17 @@ export default function PrivacyPolicy(props) {
-
- - ); } PrivacyPolicy.propTypes = { VITE_CLIENT_PORT: PropTypes.string, - mode: PropTypes.string, - props: PropTypes.object, }; + +// Props Validation +PrivacyPolicy.propTypes = { + mode: PropTypes.string, + toggleMode: PropTypes.func, + showAlert: PropTypes.func, +}; \ No newline at end of file diff --git a/client/src/component/Footers/VisitorCount.jsx b/client/src/component/Footers/VisitorCount.jsx index bcedca6..5270f72 100644 --- a/client/src/component/Footers/VisitorCount.jsx +++ b/client/src/component/Footers/VisitorCount.jsx @@ -2,8 +2,10 @@ import { useEffect, useState } from "react"; // Function to increment the visitor count async function incrementVisitorCount() { + const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; + try { - const response = await fetch("http://localhost:5000/api/visitor/increment", { + const response = await fetch(`${VITE_SERVER_PORT}/api/visitor/increment`, { method: "POST", }); const data = await response.json(); @@ -17,7 +19,7 @@ async function incrementVisitorCount() { // Function to get the current visitor count async function getVisitorCount() { try { - const response = await fetch("http://localhost:5000/api/visitor/count", { + const response = await fetch(`${VITE_SERVER_PORT}/api/visitor/count`, { method: "GET", }); const data = await response.json(); diff --git a/client/src/component/GoogleTranslate.jsx b/client/src/component/GoogleTranslate.jsx new file mode 100644 index 0000000..aa5fb37 --- /dev/null +++ b/client/src/component/GoogleTranslate.jsx @@ -0,0 +1,42 @@ +// import { useEffect } from 'react'; + +// const GoogleTranslate = () => { +// useEffect(() => { +// // Google Translate widget settings +// window.gtranslateSettings = { +// default_language: "en", +// detect_browser_language: true, +// wrapper_selector: ".gtranslate_wrapper", +// }; + +// // Add the Google Translate script +// const script = document.createElement("script"); +// script.src = "https://cdn.gtranslate.net/widgets/latest/float.js"; +// script.async = true; +// document.body.appendChild(script); + +// // Clean up script on unmount +// return () => { +// document.body.removeChild(script); +// }; +// }, []); + +// return ( +//
+// {/* Google Translate dropdown will load here */} +//
+// ); +// }; + +// export default GoogleTranslate; diff --git a/client/src/component/Login.jsx b/client/src/component/Login.jsx index 99f9569..b4e709c 100644 --- a/client/src/component/Login.jsx +++ b/client/src/component/Login.jsx @@ -15,7 +15,8 @@ import { useAuth } from '../contexts/authContext'; const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; -const Login = ({ mode, isloggedin, setloggedin }) => { +const Login = ({ mode, loggedin, setloggedin }) => { + const [credentials, setCredentials] = useState({ email: "", password: "" }); const [loading, setLoading] = useState(false); const navigate = useNavigate(); @@ -59,11 +60,12 @@ const Login = ({ mode, isloggedin, setloggedin }) => { } const json = await response.json(); + console.log(json); if (json.success) { localStorage.setItem("token", json.authtoken); - toast.success("Login successful!"); - setloggedin(!isloggedin); + toast.success("Login Successfully!"); + setloggedin(!loggedin) navigate("/"); } else { toast.error(json.message || "Login failed! Invalid credentials."); @@ -181,13 +183,28 @@ const Login = ({ mode, isloggedin, setloggedin }) => { handleRememberMe(e)} />
- + + + + + @@ -259,7 +276,8 @@ const Login = ({ mode, isloggedin, setloggedin }) => { Login.propTypes = { mode: PropTypes.string.isRequired, - isloggedin: PropTypes.bool.isRequired, + showAlert: PropTypes.func.isRequired, + loggedin: PropTypes.bool.isRequired, setloggedin: PropTypes.func.isRequired, }; diff --git a/client/src/component/LoginPhone.jsx b/client/src/component/LoginPhone.jsx new file mode 100644 index 0000000..ffc96f4 --- /dev/null +++ b/client/src/component/LoginPhone.jsx @@ -0,0 +1,234 @@ +import PropTypes from "prop-types"; +import { useState } from "react"; +import { useNavigate, Link } from "react-router-dom"; +import { Input, Button, Spin } from "antd"; +import { + UserOutlined, + LockOutlined, + EyeInvisibleOutlined, + EyeTwoTone, + PhoneFilled +} from "@ant-design/icons"; +import "../css/Login.css"; +import toast from "react-hot-toast"; +import { useAuth } from '../contexts/authContext/index' + +const VITE_SERVER_PORT = + import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; + +const LoginOTP = ({ mode, showAlert, loggedin, setloggedin }) => { + const [number, setNumber] = useState("") + const [loading, setLoading] = useState(false); + const [otpSent, setOtpSent] = useState(false); + const [otp, setOtp] = useState(""); + const credentials = "" + + const navigate = useNavigate(); + + const handleSubmit = async (e) => { + e.preventDefault(); + setLoading(true); + + try { + + const response = await fetch(`${VITE_SERVER_PORT}/api/auth/sendotp`, { + method: "POST", + credentials: 'include', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({number: number}), + }); + + const res = await response.json() + + if(res.success){ + toast.success("OTP sent successfully") + setOtpSent(true); + } else { + toast.error("Error sending otp"); + console.log("error sending otp"); + } + } catch (error) { + toast.error(error) + } + + setLoading(false) + }; + + + const { userLoggedIn, setUserLoggedIn } = useAuth() + + const handleOtpSubmit = async (e) => { + e.preventDefault() + console.log("hii"); + + setLoading(true); + + try { + + const response = await fetch(`${VITE_SERVER_PORT}/api/auth/verifyotp`, { + method: "POST", + credentials: 'include', + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({otp: otp}), + }); + + const res = await response.json() + + if(!res.success){ + toast.error("incorrect OTP") + console.log("incorrect otp"); + } else { + // alert("User signed in successfully!"); + toast.success("User signed in successfully!") + setUserLoggedIn(true) + setloggedin(!loggedin) + navigate("/") + } + + } catch (error) { + console.log(error); + toast.error(error) + } + + setLoading(false) + + } + + return ( +
+ {userLoggedIn && navigate('/')} +
+
+
+

Login Using OTP

+ {/* Title Line */} + + + { + !otpSent && <> +
+ } + placeholder="Phone number" + name="phone" + value={number} + onChange={(e)=>setNumber(e.target.value)} + autoComplete="on" + className="h-10 text-xl" + style={{ + backgroundColor: mode === "dark" ? "black" : "white", + color: mode === "dark" ? "white" : "black", + }} + required + + /> +
+ + + + } + + { + otpSent && <> +
+ } + placeholder="OTP" + name="otp" + value={otp} + onChange={(e)=>setOtp(e.target.value)} + autoComplete="on" + className="h-10 text-xl" + style={{ + backgroundColor: mode === "dark" ? "black" : "white", + color: mode === "dark" ? "white" : "black", + }} + required + /> +
+ + + + } + +

+ Don't have an account? + + + {" "} Sign Up + +

+ + + +
+ +
+

+ WELCOME +
+ BACK! +

+

+ Please Sign In here +
+ with your real info +

+
+
+
+ ); +}; + +LoginOTP.propTypes = { + mode: PropTypes.string.isRequired, + showAlert: PropTypes.func.isRequired, + loggedin: PropTypes.bool.isRequired, + setloggedin: PropTypes.func.isRequired, +}; + +export default LoginOTP; diff --git a/client/src/component/Navbar.jsx b/client/src/component/Navbar.jsx index 456047e..4f8fdea 100644 --- a/client/src/component/Navbar.jsx +++ b/client/src/component/Navbar.jsx @@ -16,11 +16,10 @@ function Navbar(props) { const navigate = useNavigate(); const location = useLocation(); - // Get the current user from the context - const { currentUser } = useAuth(); - + const { currentUser, userLoggedIn, setUserLoggedIn } = useAuth(); // eslint-disable-next-line - const [isScrolled, setIsScrolled] = useState(false); + const [isScrolled, setIsScrolled] = useState(false); // State to keep track of whether page has been scrolled + // eslint-disable-next-line const [isOpen, setIsOpen] = useState(false); // State to control the sidebar visibility @@ -86,6 +85,7 @@ function Navbar(props) { try { doSignOut(); localStorage.removeItem("token"); + setUserLoggedIn(false) navigate("/login"); } catch (error) { console.error(error); @@ -165,7 +165,7 @@ function Navbar(props) { }`} id="navbarSupportedContent" > -
    +
    • +
    • + + {props.projects} + +
    • +
    • + + {props.ai} + +
    • -
    • - - {props.projects} - -
{/* Right Links for Mobile Screen */}
- {localStorage.getItem("token") ? ( + {localStorage.getItem("token") || userLoggedIn ? ( <>
    @@ -289,8 +299,8 @@ function Navbar(props) { >
  • - +

    My Project

  • - +

    Edit Profile

  • @@ -332,16 +333,11 @@ function Navbar(props) {
    @@ -359,10 +355,7 @@ function Navbar(props) { {/* Menu Button */}
  • -
- ) - } - + )} +
{/* Sidebar for Smaller devices */} @@ -465,6 +457,16 @@ function Navbar(props) { {props.community} +
  • + setIsSidebarOpen(false)}> + {props.projects} + +
  • +
  • + setIsSidebarOpen(false)}> + {props.ai} + +
  • setIsSidebarOpen(false)}> {props.discussion} @@ -475,11 +477,6 @@ function Navbar(props) { {props.blog}
  • -
  • - setIsSidebarOpen(false)}> - {props.projects} - -
  • @@ -497,7 +494,7 @@ Navbar.propTypes = { about: PropTypes.string, contributors: PropTypes.string, projects: PropTypes.string, - profile: PropTypes.string, + ai: PropTypes.string, mode: PropTypes.string, toggleMode: PropTypes.func, showAlert: PropTypes.func, diff --git a/client/src/component/Projects.jsx b/client/src/component/Projects.jsx index 67a7d6b..aa87422 100644 --- a/client/src/component/Projects.jsx +++ b/client/src/component/Projects.jsx @@ -4,6 +4,8 @@ import { useNavigate } from 'react-router-dom'; import axios from 'axios'; const Projects = ({ mode }) => { + const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; + const navigate = useNavigate(); const [projects, setProjects] = useState([]); // State to store fetched projects const [loading, setLoading] = useState(true); // State for loading state @@ -13,7 +15,7 @@ const Projects = ({ mode }) => { // Fetch the data from the API when the component mounts const fetchProjects = async () => { try { - const response = await axios.get('http://localhost:5000/api/showcaseProjects/all-projects'); + const response = await axios.get(`${VITE_SERVER_PORT}/api/showcaseProjects/all-projects`); setProjects(response.data); // Store the fetched projects in state setLoading(false); // Set loading to false after data is fetched } catch (err) { diff --git a/client/src/component/ReadMoreBlog.jsx b/client/src/component/ReadMoreBlog.jsx index 24af70a..a48b63b 100644 --- a/client/src/component/ReadMoreBlog.jsx +++ b/client/src/component/ReadMoreBlog.jsx @@ -22,11 +22,13 @@ const images = [ const ReadMoreBlog = (props) => { + const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; + const { id } = useParams(); const [blogPost, setBlogPost] = useState(null); const fetchData = async () => { try { - const response = await fetch(`http://localhost:5000/api/blog/getById/${id}`); + const response = await fetch(`${VITE_SERVER_PORT}/api/blog/getById/${id}`); const data = await response.json(); setBlogPost(data.blog); // Set the retrieved blog post to state diff --git a/client/src/component/UploadProject.jsx b/client/src/component/UploadProject.jsx index 9455a3a..1bf5f4b 100644 --- a/client/src/component/UploadProject.jsx +++ b/client/src/component/UploadProject.jsx @@ -3,6 +3,8 @@ import PropTypes from "prop-types"; import { useNavigate } from 'react-router-dom'; const UploadProject = ({ mode }) => { + const VITE_SERVER_PORT = import.meta.env.VITE_SERVER_PORT || "https://bitbox-uxbo.onrender.com"; + const navigate = useNavigate(); const [formData, setFormData] = useState({ title: '', @@ -32,7 +34,7 @@ const UploadProject = ({ mode }) => { e.preventDefault(); try { - const response = await fetch('http://localhost:5000/api/showcaseProjects/post-project', { + const response = await fetch(`${VITE_SERVER_PORT}/api/showcaseProjects/post-project`, { method: 'POST', headers: { 'Content-Type': 'application/json', diff --git a/client/src/contexts/authContext/index.jsx b/client/src/contexts/authContext/index.jsx index ecc4d83..6727e29 100644 --- a/client/src/contexts/authContext/index.jsx +++ b/client/src/contexts/authContext/index.jsx @@ -52,7 +52,8 @@ export function AuthProvider({ children }) { isEmailUser, isGoogleUser, currentUser, - setCurrentUser + setCurrentUser, + setUserLoggedIn }; return ( diff --git a/client/src/css/Contributers.css b/client/src/css/Contributers.css index 1c85c87..3fb840b 100644 --- a/client/src/css/Contributers.css +++ b/client/src/css/Contributers.css @@ -1,3 +1,8 @@ +/* Add this to your CSS */ +.contributor-card:hover { + box-shadow: 1px 1px 12px 4px blue !important; +} + .card { width: 280px; height: 280px; diff --git a/client/src/css/FAQs.css b/client/src/css/FAQs.css new file mode 100644 index 0000000..fa32cdc --- /dev/null +++ b/client/src/css/FAQs.css @@ -0,0 +1,106 @@ +/* Container for the FAQ section */ +.faq-container { + display: flex; + /* Using Flexbox to create a two-column layout */ + justify-content: space-between; + align-items: flex-start; + margin-top: 180px !important; + max-width: 1200px; + height: 750px; + margin: 40px auto; + padding: 30px; + /* background-color: #ffffff; */ + border-radius: 12px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1); + font-family: Arial, sans-serif; +} + +h1 { + margin-top: 30px; + font-size: 2rem; + margin-right: 40px; +} + +/* Left side - for image */ +.faq-left { + flex: 0 0 460px; + /* Width for the image section */ + display: flex; + justify-content: center; + /* Center the image horizontally */ +} + +/* Image styling */ +.faq-image-container { + margin-top: 40px; +} + +.faq-image { + width: 100%; + /* Makes the image responsive */ + max-width: 550px; + /* Maximum width for the image */ + height: 550px; + margin-left: 50px; + /* margin-top: 50px; */ +} + +/* Right side - for FAQ list */ +.faq-right { + flex: 1; + /* Takes up the remaining space */ + margin-left: 100px; + /* Adds space between image and FAQ list */ + padding: 15px; +} + +/* FAQ List styles */ +.faq-list { + list-style: none; + padding: 0; + /* margin: 0; */ +} + +/* Individual FAQ item styling */ +.faq-item { + margin-top: 20px; + border-bottom: 1px solid #ddd; + padding: 15px 0; + font-size: 16px; + font-weight: bold; +} + +/* Question styling */ +.faq-question { + display: flex; + justify-content: space-between; + cursor: pointer; + font-size: 1.1em; + color: #003a77; + margin: 0; + font-family: "Arial", sans-serif; +} + +.faq-question:hover { + color: #0056b3; +} + +/* Icon for showing + or - */ +.faq-icon { + font-size: 20px; +} + +/* Answer styling */ +.faq-answer { + margin-top: 10px; + color: #0056b3; + font-size: 0.95em; + line-height: 1.6; +} + +/* Optional: To make the entire FAQ item more visually engaging on hover */ +/* .faq-item:hover { + background-color: #f5faff; + transform: translateY(-2px); + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + } */ \ No newline at end of file diff --git a/client/src/css/Footer.css b/client/src/css/Footer.css index 510a8c8..65e95f6 100644 --- a/client/src/css/Footer.css +++ b/client/src/css/Footer.css @@ -12,13 +12,6 @@ margin: 0.5rem; } -.nav-item:hover { - text-decoration: underline; -} -.foot { - text-decoration: 3px underline; -} - .footer-container { display: flex; justify-content: space-around; @@ -151,7 +144,7 @@ h4 a:hover { } /* Nav and button styles */ -.nav_ul li { +nav_ul li { padding: 0 0.6rem; } @@ -258,7 +251,7 @@ h4 a:hover { } .Last-footer { - background: #1173eb; + background: #0d92f4; text-align: center; padding: 1rem 0; } @@ -350,4 +343,4 @@ h4 a:hover { .footer-icons i { padding: 0.3rem 0.4rem; } -} +} \ No newline at end of file diff --git a/client/src/css/Main.css b/client/src/css/Main.css index 212103a..5754e3b 100644 --- a/client/src/css/Main.css +++ b/client/src/css/Main.css @@ -46,7 +46,6 @@ ul { color: #374151; } - .lists { padding: 5px; margin-top: 2rem; @@ -67,14 +66,22 @@ ul { /* Contact Us */ .contactus-container { - margin-top: 100px; - margin-bottom: 40px; + margin-top: 120px; + margin-bottom: 60px; } .contactus-main-box { - border: 2px solid #ccc; - padding: 20px; + border: 2px solid #b9b2b2; + padding: 40px; + border-radius: 8px; + + max-width: 920px; + /* Max width, if needed */ + height: 500px; + padding: 40px; border-radius: 8px; + margin: 0 auto; + /* Centers the form */ } .header { @@ -87,7 +94,7 @@ ul { background-image: linear-gradient(120deg, #bfbdbd 0%, #e0eaef 100%); } -.image-bx { +.image-box { width: 60px; height: 60px; border-radius: 50%; @@ -132,6 +139,12 @@ ul { cursor: auto; } +/* Privacy Policy */ +.privacy-policy-container { + margin-top: 40px; + margin-bottom: 40px; +} + .paragraph { margin-bottom: 50px; } @@ -148,6 +161,12 @@ ul { margin-left: 30px; } +.privacy-policy-container { + padding-left: 15%; + padding-right: 15%; + margin-top: 110px; +} + /* Term of Use */ .container-T { border-radius: 4px; diff --git a/client/src/firebase/auth.js b/client/src/firebase/auth.js index 63cf30b..9c02876 100644 --- a/client/src/firebase/auth.js +++ b/client/src/firebase/auth.js @@ -1,4 +1,4 @@ -import { auth } from "./firebase"; +import { auth } from "./firebase.js"; import { createUserWithEmailAndPassword, signInWithEmailAndPassword, diff --git a/package-lock.json b/package-lock.json index 4413a3d..8d9465c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,5 +1,5 @@ { - "name": "Bitbox-gssoc", + "name": "GSSOC_Bitbox", "lockfileVersion": 3, "requires": true, "packages": {} diff --git a/server/.env.sample b/server/.env.sample index e69fe55..2d34f3d 100644 --- a/server/.env.sample +++ b/server/.env.sample @@ -1,29 +1,16 @@ -## FOR CLIENT -VITE_CLIENT_PORT = "" +# FOR CLIENT +VITE_CLIENT_PORT="" # FOR SERVER -VITE_SERVER_PORT = "" -MONGO_URI = "" - -# YOUR_EMAIL -EMAIL_USER = "" - -# To create a passkey on the phone or computer you’re on: - -# 1. Go to https://myaccount.google.com/signinoptions/passkeys. -# 2. Tap Create a passkey and then Continue.(You'll be required to unlock your device.) -# 3. A 16 character passkey is generated which you can use in below. -# 4. You can also use your password instead of passkey. -# 5. If you are using password, you need to enable less secure apps in your google account settings. -# 6. Go to https://myaccount.google.com/lesssecureapps and enable it. -# 7. If you are using passkey, you don't need to enable less secure apps. -# 8. If you are using passkey, you need to use the passkey in the EMAIL_PASS field. -# 9. If you are using password, you need to use the password in the EMAIL_PASS field. - -EMAIL_PASS = "" -GOOGLE_CLIENT_ID = "" -JWT_SECRET = "" - -EMAIL_ID= -PASS_KEY= -ADMIN_EMAIL_ID= \ No newline at end of file +VITE_SERVER_PORT="" +MONGO_URI="" +EMAIL_USER="" +EMAIL_PASS="" +GOOGLE_CLIENT_ID="" +GOOGLE_SECRET="" +ADMIN_EMAIL_ID="" +EMAIL_ID="" +PASS_KEY="" +TWILIO_ACCOUNT_SID="" +TWILIO_AUTH_TOKEN="" +JWT_SECRET="" diff --git a/server/Controllers/auth.js b/server/Controllers/auth.js index 1e084cb..87b9cf1 100644 --- a/server/Controllers/auth.js +++ b/server/Controllers/auth.js @@ -4,6 +4,8 @@ const nodemailer = require("nodemailer"); const crypto = require("crypto"); require('dotenv').config(); // Load environment variables from .env file +const client = require('twilio')(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN); + // Signup route const createUser = async (req, res) => { const VITE_CLIENT_PORT = process.env.VITE_CLIENT_PORT || "https://bitbox-in.netlify.app"; @@ -16,9 +18,9 @@ const createUser = async (req, res) => { const image = `https://api.dicebear.com/5.x/initials/svg?seed=${name}`; // Create a new user (save in your database) - const user = new User({ image: image, name, email, password: hashedPassword, verified: false }); + const user = new User({ imageUrl: image, name, email, password: hashedPassword, verified: false }); await user.save(); - + const verificationToken = crypto.randomBytes(32).toString("hex"); user.verificationToken = verificationToken; await user.save(); @@ -52,6 +54,7 @@ const createUser = async (req, res) => { } }); } catch (error) { + console.log(error); res.status(500).json({ success: false, message: 'An error occurred during signup' }); } }; @@ -174,9 +177,48 @@ const forgetpassword = async (req, res) => { } }; +const sendOtp = async (req, res)=> { + const { number } = req.body; + + const otp = Math.floor(100000 + Math.random() * 900000).toString(); + + const result = await client.messages.create({ + body: `Yout OTP is ${otp}`, + messagingServiceSid: "MG6cd72a2fcb56205180ce4878359484cc", + to: "+91" + number + }) + + res.cookie('otp', otp, { + maxAge: 24 * 60 * 60 * 1000, + httpOnly: true, + secure: true, + sameSite: 'None', + }); + + return res.status(200).json({success: true, message: "OTP sent successfully"}) +} + +const verifyOtp = async (req, res)=>{ + const {otp} = req.body; + + console.log(req.cookies); + + const correctOtp = await req.cookies.otp + + if(correctOtp != otp){ + return res.status(400).json({success: false, message: "Incorrect OTP"}) + } + + res.clearCookie('otp'); + + return res.status(200).json({success: true, message: "OTP verified successfully"}) +} + module.exports = { forgetpassword, createUser, verifyToken, ResetPasswordByEmail, + sendOtp, + verifyOtp }; diff --git a/server/Controllers/discussionController.js b/server/Controllers/discussionController.js new file mode 100644 index 0000000..3d72aa8 --- /dev/null +++ b/server/Controllers/discussionController.js @@ -0,0 +1,56 @@ +const Question = require('../Models/discussion.js'); + +const getQuestions = async (req, res) => { + try { + const questions = await Question.find(); + res.json(questions); + } catch (error) { + res.status(500).json({ error: 'Failed to retrieve questions' }); + } +}; + +// Save a new question to MongoDB +const saveQuestion = async (req, res) => { + try { + const { content } = req.body; + + const newQuestion = new Question({ + content, + }); + + await newQuestion.save(); + + res.status(201).json(newQuestion); + } catch (error) { + res.status(500).json({ error: 'Failed to save question' }); + } +}; + +const addAnswerToQuestion = async (req, res) => { + try { + const questionId = req.params.id; + const { answer } = req.body; + console.log(questionId) + console.log(answer) + + const updatedQuestion = await Question.findByIdAndUpdate( + questionId, + { answered: true, answer }, + { new: true } + ); + + if (updatedQuestion) { + res.json(updatedQuestion); + } else { + res.status(404).json({ error: 'Question not found' }); + } + } catch (error) { + res.status(500).json({ error: 'Failed to update question' }); + } +}; + +module.exports = { + getQuestions, + saveQuestion, + addAnswerToQuestion +}; diff --git a/server/Models/discussion.js b/server/Models/discussion.js new file mode 100644 index 0000000..c715ff9 --- /dev/null +++ b/server/Models/discussion.js @@ -0,0 +1,21 @@ +const mongoose = require('mongoose'); + +// Define the schema for a question +const discussionSchema = new mongoose.Schema({ + content: { + type: String, + required: true, + }, + answered: { + type: Boolean, + default: false, + }, + answer: { + type: String, + default: '', + }, +}, { timestamps: true }); + +const Question = mongoose.model('Question', discussionSchema); + +module.exports = Question; diff --git a/server/index.js b/server/index.js index 915eb2c..1178aac 100644 --- a/server/index.js +++ b/server/index.js @@ -4,12 +4,14 @@ const connectToMongo = require("./db"); const cors = require("cors"); const helmet = require("helmet"); const Avatar = require("./Models/Avatar"); +const cookieParser = require('cookie-parser'); require("dotenv").config(); // Load environment variables from .env file // Connect to MongoDB connectToMongo(); const app = express(); +app.use(cookieParser()); const httpServer = require("http").createServer(app); const io = new Server(httpServer, { cors: { @@ -33,6 +35,7 @@ app.use( cors({ origin: "*", // Update to specific origins in production methods: ["GET", "POST", "OPTIONS", "PUT"], + credentials: true, }) ); @@ -50,6 +53,8 @@ app.use("/api/blog", require("./routes/blog")); app.use("/api/visitor", require("./routes/visitor")); app.use("/api/showcaseProjects", require("./routes/projectsRoute")); app.use("/api/admin", require("./routes/adminRoute")) +app.use("/api/discussion", require("./routes/discussionRoutes")); + // Socket.io connection handling const users = {}; diff --git a/server/package-lock.json b/server/package-lock.json index 8634f66..6d583cf 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "bcrypt": "^5.1.1", "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", @@ -26,7 +27,8 @@ "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.15", "opensource-backend": "file:", - "socket.io": "^4.7.5" + "socket.io": "^4.7.5", + "twilio": "^5.3.5" }, "devDependencies": { "nodemon": "^3.1.7" @@ -902,6 +904,23 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "license": "MIT" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1161,6 +1180,18 @@ "color-support": "bin.js" } }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1218,6 +1249,28 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -1243,6 +1296,12 @@ "node": ">= 0.10" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -1269,6 +1328,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -1607,6 +1675,40 @@ "@firebase/vertexai-preview": "0.0.4" } }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -2987,6 +3089,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/pstree.remy": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", @@ -3127,6 +3235,12 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, + "node_modules/scmp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/scmp/-/scmp-2.1.0.tgz", + "integrity": "sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==", + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -3528,6 +3642,24 @@ "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "license": "0BSD" }, + "node_modules/twilio": { + "version": "5.3.5", + "resolved": "https://registry.npmjs.org/twilio/-/twilio-5.3.5.tgz", + "integrity": "sha512-f/sA1Yd6TyIzfcq0u4QDGU+93afwswsJB+rf3T08tvBAMobBDVR3DfGREwJr5jp8xUic0qWa7GbJidk16NA4bg==", + "license": "MIT", + "dependencies": { + "axios": "^1.7.4", + "dayjs": "^1.11.9", + "https-proxy-agent": "^5.0.0", + "jsonwebtoken": "^9.0.2", + "qs": "^6.9.4", + "scmp": "^2.1.0", + "xmlbuilder": "^13.0.2" + }, + "engines": { + "node": ">=14.0" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -3716,6 +3848,15 @@ } } }, + "node_modules/xmlbuilder": { + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-13.0.2.tgz", + "integrity": "sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==", + "license": "MIT", + "engines": { + "node": ">=6.0" + } + }, "node_modules/xtend": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", diff --git a/server/package.json b/server/package.json index 7f35ab6..0bf0e4e 100644 --- a/server/package.json +++ b/server/package.json @@ -19,6 +19,7 @@ "dependencies": { "bcrypt": "^5.1.1", "bcryptjs": "^2.4.3", + "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.4", "express": "^4.18.2", @@ -34,7 +35,8 @@ "multer": "^1.4.5-lts.1", "nodemailer": "^6.9.15", "opensource-backend": "file:", - "socket.io": "^4.7.5" + "socket.io": "^4.7.5", + "twilio": "^5.3.5" }, "devDependencies": { "nodemon": "^3.1.7" diff --git a/server/routes/auth.js b/server/routes/auth.js index 7cd79fc..198118d 100644 --- a/server/routes/auth.js +++ b/server/routes/auth.js @@ -15,6 +15,8 @@ const { verifyToken, createUser, ResetPasswordByEmail, + verifyOtp, + sendOtp } = require("../Controllers/auth"); // Configure Firebase OAuth2Client @@ -161,5 +163,7 @@ router.post("/forget", forgetpassword); router.post("/createUser", createUser); router.post("/verify/:token", verifyToken); router.post("/ResetByEmail", ResetPasswordByEmail); +router.post("/sendotp", sendOtp); +router.post("/verifyotp", verifyOtp); module.exports = router; diff --git a/server/routes/discussionRoutes.js b/server/routes/discussionRoutes.js new file mode 100644 index 0000000..997c492 --- /dev/null +++ b/server/routes/discussionRoutes.js @@ -0,0 +1,12 @@ +const express = require('express'); +const router = express.Router(); + +const { getQuestions, saveQuestion, addAnswerToQuestion } = require('../Controllers/discussionController'); + +router.get('/getQuestion', getQuestions); + +router.post('/postQuestion', saveQuestion); + +router.put('/:id/answer', addAnswerToQuestion); + +module.exports = router;