diff --git a/backend/CHANGELOG.md b/backend/CHANGELOG.md index 61c58ca..0fdd45c 100644 --- a/backend/CHANGELOG.md +++ b/backend/CHANGELOG.md @@ -17,4 +17,10 @@ All notable changes to this repository will be documented in this file. - Added build and deployment script +## [1.1.0] Fri, Dec 05, 2025 + +- Contact Us page Added +- 404 error handling +- NavBar CSS fixed + [Unreleased] \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index d58a734..907b997 100644 --- a/backend/main.py +++ b/backend/main.py @@ -21,11 +21,13 @@ submit, uploads, validate_image, + contact ) from tools.mcpserver import sse_app from utils.emoji_logger import get_logger + class MyApp(FastAPI): mongo_client: MongoClient database: Database @@ -122,6 +124,7 @@ async def lifespan(app: MyApp): app.include_router(media.router, prefix=API_PREFIX) app.include_router(chat.router, prefix=API_PREFIX) app.include_router(uploads.router, prefix=API_PREFIX) +app.include_router(contact.router, prefix=API_PREFIX) @app.get("/__routes") diff --git a/backend/routes/contact.py b/backend/routes/contact.py new file mode 100644 index 0000000..663963e --- /dev/null +++ b/backend/routes/contact.py @@ -0,0 +1,32 @@ +# routes/contact.py +from fastapi import APIRouter, HTTPException, Request +from pydantic import BaseModel, EmailStr +from datetime import datetime + +router = APIRouter() + + +class ContactForm(BaseModel): + name: str + email: EmailStr + message: str + + +@router.post("/contact") +async def save_contact(request: Request, form: ContactForm): + db = request.app.database # ← SAME as all other route files + + collection = db["contact_messages"] + + data = { + "name": form.name, + "email": form.email, + "message": form.message, + "created_at": datetime.utcnow(), + } + + result = collection.insert_one(data) + if not result.inserted_id: + raise HTTPException(status_code=500, detail="Failed to save message") + + return {"success": True, "id": str(result.inserted_id)} diff --git a/backend/routes/uploads.py b/backend/routes/uploads.py index 01e3eb8..a65846a 100644 --- a/backend/routes/uploads.py +++ b/backend/routes/uploads.py @@ -3,6 +3,9 @@ import uuid from pathlib import Path +from fastapi import Request + + from fastapi import APIRouter, File, Form, HTTPException, UploadFile from constants import AUDIO_DIR, IMAGE_DIR @@ -14,12 +17,16 @@ AUDIO_DIR.mkdir(parents=True, exist_ok=True) + @router.post("/uploads/tmp_media") async def upload_tmp_media( + request: Request, image: UploadFile = File(None), audio: UploadFile = File(None), filename: str | None = Form(None), # <-- 👈 accept provided name ): + base_url = str(request.base_url).rstrip("/") + if not image and not audio: raise HTTPException( status_code=400, @@ -53,7 +60,7 @@ async def upload_tmp_media( try: with img_path.open("wb") as out: shutil.copyfileobj(image.file, out) - saved_image_url = f"http://127.0.0.1:8000/assets/images/{img_name}" + saved_image_url = f"{base_url}/assets/images/{img_name}" saved_image_path = str(img_path.resolve()) saved_image_name = img_name except Exception as e: @@ -67,7 +74,7 @@ async def upload_tmp_media( try: with aud_path.open("wb") as out: shutil.copyfileobj(audio.file, out) - saved_audio_url = f"http://127.0.0.1:8000/assets/audios/{aud_name}" + saved_audio_url = f"{base_url}/assets/audios/{aud_name}" saved_audio_name = aud_name saved_audio_path = str(aud_path.resolve()) except Exception as e: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 64c7d29..f852faa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1,12 +1,12 @@ { "name": "tz-fabric", - "version": "1.0.2", + "version": "1.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "tz-fabric", - "version": "1.0.2", + "version": "1.1.0", "license": "MIT", "dependencies": { "axios": "^1.11.0", @@ -21,6 +21,7 @@ "devDependencies": { "@biomejs/biome": "^2.0.2", "@eslint/js": "^9.30.1", + "@types/node": "^24.10.1", "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", "@vitejs/plugin-react": "^4.6.0", @@ -1524,6 +1525,15 @@ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", + "dev": true, + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/pako": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", @@ -4995,6 +5005,12 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true + }, "node_modules/unified": { "version": "11.0.5", "resolved": "https://registry.npmjs.org/unified/-/unified-11.0.5.tgz", diff --git a/frontend/package.json b/frontend/package.json index 1d49f78..0ba38bb 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,6 +1,6 @@ { "name": "tz-fabric", - "version": "1.0.2", + "version": "1.1.0", "type": "module", "homepage": "https://threadzip.com", "description": "A AI enabled fabric analysis, chatbot integration with image search", @@ -59,6 +59,7 @@ "devDependencies": { "@biomejs/biome": "^2.0.2", "@eslint/js": "^9.30.1", + "@types/node": "^24.10.1", "@types/react": "^19.1.9", "@types/react-dom": "^19.1.7", "@vitejs/plugin-react": "^4.6.0", diff --git a/frontend/src/App.css b/frontend/src/App.css index 047edd0..c352019 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -17,7 +17,6 @@ height: 100%; margin: 0; font-family: Inter, "Segoe UI", Roboto, system-ui, -apple-system, Arial, sans-serif; - color: var(--text-color); } .app-wrapper { @@ -37,26 +36,31 @@ .site-header { width: 100%; - display: flex; + display: grid; align-items: center; - justify-content: space-between; padding: 4px 20px; background: rgba(255, 255, 255, 0.9); box-shadow: 0 6px 24px rgba(66, 66, 67, 0.767); + grid-template-columns: 20% 1fr 20%; + grid-template-areas: 'left center right'; } - - - - .header-left { + grid-area: left; display: flex; align-items: center; gap: 12px; } +.header-center { + grid-area: center; + text-transform: capitalize; + justify-self: center; +} - +.header-right { + grid-area: right; +} .logo-mark { width: 44px; @@ -74,26 +78,6 @@ letter-spacing: 0.1px; } -.site-nav { - display: flex; - gap: 22px; - align-items: center; -} - -.site-nav a { - color: var(--text-color); - text-decoration: none; - font-size: 14px; - font-weight: 500; - padding: 6px 8px; - border-radius: 8px; - transition: background .15s ease, transform .08s ease; -} - -.site-nav a:hover { - background: rgba(47, 107, 255, 0.08); - transform: translateY(-1px); -} .main-content { width: 100%; @@ -106,13 +90,20 @@ padding: 12px 14px; flex-direction: row; align-items: center; + display: flex; } - .site-nav { - gap: 12px; - } .brand-name { font-size: 15px; } +} + +.container { + height: 80vh; +} + +.center { + display: grid; + place-content: center; } \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 9de2c27..455bf7e 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,9 +1,9 @@ import React from "react"; -import { NavLink } from "react-router-dom"; import "./App.css"; -import "./styles/navbar.css"; import { Routing } from "./Routing"; import Footer from "./components/Footer"; +import { NavBar } from './components/NavBar'; +import { FaRegMoon } from 'react-icons/fa'; const App: React.FC = () => { return ( @@ -24,53 +24,11 @@ const App: React.FC = () => {
FabricAI
-
- - - - - +
+
- -
- {/*
*/} +
+
diff --git a/frontend/src/Routing.tsx b/frontend/src/Routing.tsx index cf8ad13..9a2dacc 100644 --- a/frontend/src/Routing.tsx +++ b/frontend/src/Routing.tsx @@ -6,6 +6,8 @@ import Home from "./pages/Home"; import ImageDescription from "./pages/ImageDescriptor"; import Chat from "./pages/FabricChat"; import ComingSoon from "./pages/ComingSoon"; +import { NotFound } from './components/NotFound'; +import {ContactUs} from './pages/Contact'; export const Routing = () => { return ( @@ -20,8 +22,9 @@ export const Routing = () => { } /> } /> } /> - } /> } /> + } /> + } /> ); }; diff --git a/frontend/src/styles/ComingSoon.css b/frontend/src/assets/styles/ComingSoon.css similarity index 100% rename from frontend/src/styles/ComingSoon.css rename to frontend/src/assets/styles/ComingSoon.css diff --git a/frontend/src/styles/Composer.css b/frontend/src/assets/styles/Composer.css similarity index 100% rename from frontend/src/styles/Composer.css rename to frontend/src/assets/styles/Composer.css diff --git a/frontend/src/styles/ContentGrid.css b/frontend/src/assets/styles/ContentGrid.css similarity index 100% rename from frontend/src/styles/ContentGrid.css rename to frontend/src/assets/styles/ContentGrid.css diff --git a/frontend/src/styles/DescriptionBox.css b/frontend/src/assets/styles/DescriptionBox.css similarity index 100% rename from frontend/src/styles/DescriptionBox.css rename to frontend/src/assets/styles/DescriptionBox.css diff --git a/frontend/src/styles/DrawerToggle.css b/frontend/src/assets/styles/DrawerToggle.css similarity index 100% rename from frontend/src/styles/DrawerToggle.css rename to frontend/src/assets/styles/DrawerToggle.css diff --git a/frontend/src/styles/EmptyState.css b/frontend/src/assets/styles/EmptyState.css similarity index 100% rename from frontend/src/styles/EmptyState.css rename to frontend/src/assets/styles/EmptyState.css diff --git a/frontend/src/styles/FabricChat.css b/frontend/src/assets/styles/FabricChat.css similarity index 100% rename from frontend/src/styles/FabricChat.css rename to frontend/src/assets/styles/FabricChat.css diff --git a/frontend/src/styles/FabricSearch.css b/frontend/src/assets/styles/FabricSearch.css similarity index 100% rename from frontend/src/styles/FabricSearch.css rename to frontend/src/assets/styles/FabricSearch.css diff --git a/frontend/src/styles/Footer.css b/frontend/src/assets/styles/Footer.css similarity index 98% rename from frontend/src/styles/Footer.css rename to frontend/src/assets/styles/Footer.css index cce3533..f1341ed 100644 --- a/frontend/src/styles/Footer.css +++ b/frontend/src/assets/styles/Footer.css @@ -4,10 +4,8 @@ background: #0c1220; /* deep navy like screenshot */ color: #d2d8e3; - border-radius: 8px; border: 1px solid rgba(255, 255, 255, 0.06); box-shadow: 0 8px 28px rgba(0, 0, 0, 0.25); - margin-top: 24px; } .footer__inner { diff --git a/frontend/src/styles/Home.css b/frontend/src/assets/styles/Home.css similarity index 100% rename from frontend/src/styles/Home.css rename to frontend/src/assets/styles/Home.css diff --git a/frontend/src/styles/ImageDescription.css b/frontend/src/assets/styles/ImageDescription.css similarity index 100% rename from frontend/src/styles/ImageDescription.css rename to frontend/src/assets/styles/ImageDescription.css diff --git a/frontend/src/styles/ImageDescriptorHeader.css b/frontend/src/assets/styles/ImageDescriptorHeader.css similarity index 100% rename from frontend/src/styles/ImageDescriptorHeader.css rename to frontend/src/assets/styles/ImageDescriptorHeader.css diff --git a/frontend/src/styles/ImagePreviewPanel.css b/frontend/src/assets/styles/ImagePreviewPanel.css similarity index 100% rename from frontend/src/styles/ImagePreviewPanel.css rename to frontend/src/assets/styles/ImagePreviewPanel.css diff --git a/frontend/src/styles/Loader.css b/frontend/src/assets/styles/Loader.css similarity index 100% rename from frontend/src/styles/Loader.css rename to frontend/src/assets/styles/Loader.css diff --git a/frontend/src/styles/Messages.css b/frontend/src/assets/styles/Messages.css similarity index 100% rename from frontend/src/styles/Messages.css rename to frontend/src/assets/styles/Messages.css diff --git a/frontend/src/styles/Notification.css b/frontend/src/assets/styles/Notification.css similarity index 100% rename from frontend/src/styles/Notification.css rename to frontend/src/assets/styles/Notification.css diff --git a/frontend/src/styles/SampleImageGalleryCard.css b/frontend/src/assets/styles/SampleImageGalleryCard.css similarity index 100% rename from frontend/src/styles/SampleImageGalleryCard.css rename to frontend/src/assets/styles/SampleImageGalleryCard.css diff --git a/frontend/src/styles/SearchBar.css b/frontend/src/assets/styles/SearchBar.css similarity index 100% rename from frontend/src/styles/SearchBar.css rename to frontend/src/assets/styles/SearchBar.css diff --git a/frontend/src/styles/SuggestionChips.css b/frontend/src/assets/styles/SuggestionChips.css similarity index 100% rename from frontend/src/styles/SuggestionChips.css rename to frontend/src/assets/styles/SuggestionChips.css diff --git a/frontend/src/styles/UploadPage.css b/frontend/src/assets/styles/UploadPage.css similarity index 100% rename from frontend/src/styles/UploadPage.css rename to frontend/src/assets/styles/UploadPage.css diff --git a/frontend/src/assets/styles/contact.css b/frontend/src/assets/styles/contact.css new file mode 100644 index 0000000..de45fd4 --- /dev/null +++ b/frontend/src/assets/styles/contact.css @@ -0,0 +1,77 @@ +.contact-container { + max-width: 420px; + margin: 60px auto; + padding: 24px; + border-radius: 12px; + background: #ffffff; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + font-family: system-ui, sans-serif; + color: #222; +} + +.contact-container h2 { + margin-bottom: 16px; + font-size: 24px; + text-align: center; + color: #222; +} + +.contact-container form { + display: flex; + flex-direction: column; + gap: 14px; +} + +.contact-container input, +.contact-container textarea { + width: 100%; + padding: 12px; + border: 1px solid #cfd3d7; + border-radius: 8px; + font-size: 15px; + outline: none; + transition: border 0.2s ease; +} + +.contact-container input:focus, +.contact-container textarea:focus { + border-color: #007bff; +} + +.contact-container textarea { + resize: vertical; + min-height: 120px; +} + +.contact-container button { + background: #007bff; + color: #fff; + padding: 12px; + border: none; + font-size: 16px; + border-radius: 8px; + cursor: pointer; + transition: background 0.2s ease; +} + +.contact-container button:hover { + background: #005fcc; +} + +.notice { + display: flex; + flex-flow: column; + align-items: center; + gap: 0.5rem; + + & span { + font-weight: bold; + } +} + +.status { + margin-top: 10px; + text-align: center; + font-size: 14px; + color: #444; +} \ No newline at end of file diff --git a/frontend/src/styles/navbar.css b/frontend/src/assets/styles/navbar.css similarity index 82% rename from frontend/src/styles/navbar.css rename to frontend/src/assets/styles/navbar.css index 9723114..c525d1a 100644 --- a/frontend/src/styles/navbar.css +++ b/frontend/src/assets/styles/navbar.css @@ -27,18 +27,19 @@ } .header-nav ul { - position: static !important; - display: flex !important; - flex-direction: row !important; + position: static; + display: flex; + flex-direction: row; align-items: center; gap: 1.5rem; - background: none !important; - padding: 0 !important; - transform: none !important; - opacity: 1 !important; - pointer-events: auto !important; - height: auto !important; - box-shadow: none !important; + background: none; + padding: 0; + transform: none; + opacity: 1; + pointer-events: auto; + height: auto; + box-shadow: none; + top: 1.5rem; } } diff --git a/frontend/src/components/Composer.tsx b/frontend/src/components/Composer.tsx index e3d25ba..4ee1c2b 100644 --- a/frontend/src/components/Composer.tsx +++ b/frontend/src/components/Composer.tsx @@ -1,7 +1,7 @@ // src/components/Composer.tsx import type React from "react"; import { useEffect, useRef, useState } from "react"; -import "../styles/Composer.css"; +import "@/assets/styles/Composer.css"; import Loader from "./Loader"; import SuggestionChips from "./SuggestionChips"; import { formatFileName } from "../utils/formatFilename"; diff --git a/frontend/src/components/DescriptionBox.tsx b/frontend/src/components/DescriptionBox.tsx index 1dc7c17..ff5df56 100644 --- a/frontend/src/components/DescriptionBox.tsx +++ b/frontend/src/components/DescriptionBox.tsx @@ -1,7 +1,7 @@ // src/components/DescriptionBox.jsx import { useState } from "react"; import { FaRegCopy } from "react-icons/fa"; -import "../styles/DescriptionBox.css"; +import "@/assets/styles/DescriptionBox.css"; import Loader from "./Loader"; const DescriptionBox = ({ diff --git a/frontend/src/components/DrawerToggle.tsx b/frontend/src/components/DrawerToggle.tsx index 147adfc..71a1ff2 100644 --- a/frontend/src/components/DrawerToggle.tsx +++ b/frontend/src/components/DrawerToggle.tsx @@ -1,4 +1,4 @@ -import "../styles/DrawerToggle.css"; +import "@/assets/styles/DrawerToggle.css"; const DrawerToggle = ({ showDrawer, setShowDrawer }) => { return ( diff --git a/frontend/src/components/EmptyState.tsx b/frontend/src/components/EmptyState.tsx index 6d07ce4..60c89d9 100644 --- a/frontend/src/components/EmptyState.tsx +++ b/frontend/src/components/EmptyState.tsx @@ -1,5 +1,5 @@ // src/components/EmptyState.tsx -import "../styles/EmptyState.css"; +import "@/assets/styles/EmptyState.css"; const samples = [ "Explain knit vs woven.", diff --git a/frontend/src/components/FabricSearchHeader.tsx b/frontend/src/components/FabricSearchHeader.tsx index 06e2394..03032d0 100644 --- a/frontend/src/components/FabricSearchHeader.tsx +++ b/frontend/src/components/FabricSearchHeader.tsx @@ -1,4 +1,4 @@ -import "../styles/ImageDescriptorHeader.css"; +import "@/assets/styles/ImageDescriptorHeader.css"; const Header = () => { return ( diff --git a/frontend/src/components/Footer.tsx b/frontend/src/components/Footer.tsx index 96decc5..094d547 100644 --- a/frontend/src/components/Footer.tsx +++ b/frontend/src/components/Footer.tsx @@ -1,5 +1,5 @@ import { Link } from "react-router-dom"; -import "../styles/Footer.css"; +import "@/assets/styles/Footer.css"; export default function Footer() { diff --git a/frontend/src/components/ImageDescriptorHeader.tsx b/frontend/src/components/ImageDescriptorHeader.tsx index b34e6de..21f3c6a 100644 --- a/frontend/src/components/ImageDescriptorHeader.tsx +++ b/frontend/src/components/ImageDescriptorHeader.tsx @@ -1,4 +1,4 @@ -import "../styles/ImageDescriptorHeader.css"; +import "@/assets/styles/ImageDescriptorHeader.css"; const Header = () => { return ( diff --git a/frontend/src/components/ImagePreviewPanel.tsx b/frontend/src/components/ImagePreviewPanel.tsx index 7107a97..f7d9eab 100644 --- a/frontend/src/components/ImagePreviewPanel.tsx +++ b/frontend/src/components/ImagePreviewPanel.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import "../styles/ImagePreviewPanel.css"; +import "@/assets/styles/ImagePreviewPanel.css"; const ImagePreviewPanel = ({ uploadedImageUrl, diff --git a/frontend/src/components/Loader.tsx b/frontend/src/components/Loader.tsx index 387b4b1..b943d43 100644 --- a/frontend/src/components/Loader.tsx +++ b/frontend/src/components/Loader.tsx @@ -1,4 +1,4 @@ -import "../styles/Loader.css"; +import "@/assets/styles/Loader.css"; const Loader = () => { return
; diff --git a/frontend/src/components/MessageList.tsx b/frontend/src/components/MessageList.tsx index c48b916..b4b5ba8 100644 --- a/frontend/src/components/MessageList.tsx +++ b/frontend/src/components/MessageList.tsx @@ -2,7 +2,7 @@ import { useEffect, useLayoutEffect, useRef } from "react"; import type { RefObject } from "react"; import type { Message } from "../services/chat_api"; import MessageBubble from "./MessageBubble"; -import "../styles/Messages.css"; +import "@/assets/styles/Messages.css"; interface Props { messages: Message[]; diff --git a/frontend/src/components/NavBar.tsx b/frontend/src/components/NavBar.tsx new file mode 100644 index 0000000..b879e8f --- /dev/null +++ b/frontend/src/components/NavBar.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { NavLink } from 'react-router-dom'; + +import "@/assets/styles/navbar.css"; +import { NAVBAR_MENU } from '../constants'; + + +export const NavBar: React.FC = () => { + const navClass = ({ isActive }) => (isActive ? "active" : ""); + return ( +
+ + + + + +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/components/NotFound.tsx b/frontend/src/components/NotFound.tsx new file mode 100644 index 0000000..ed31294 --- /dev/null +++ b/frontend/src/components/NotFound.tsx @@ -0,0 +1,11 @@ +import { NavLink } from 'react-router-dom'; + +export const NotFound = () => { + return ( +
+

404

+

The page you are looking for does not exist.

+ Go Home +
+ ); +} diff --git a/frontend/src/components/Notification.tsx b/frontend/src/components/Notification.tsx index c6f5499..e43c1dd 100644 --- a/frontend/src/components/Notification.tsx +++ b/frontend/src/components/Notification.tsx @@ -1,4 +1,4 @@ -import "../styles/Notification.css"; +import "@/assets/styles/Notification.css"; interface NotificationProps { message: string; diff --git a/frontend/src/components/SampleImageGalleryCard.tsx b/frontend/src/components/SampleImageGalleryCard.tsx index 99cf8cd..e45ad04 100644 --- a/frontend/src/components/SampleImageGalleryCard.tsx +++ b/frontend/src/components/SampleImageGalleryCard.tsx @@ -1,4 +1,4 @@ -import "../styles/SampleImageGalleryCard.css"; +import "@/assets/styles/SampleImageGalleryCard.css"; const sampleImages = [ { id: 1, name: "Sample 1", path: "../assets/sample1.jpeg" }, diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx index 1f5a987..b9bc127 100644 --- a/frontend/src/components/SearchBar.tsx +++ b/frontend/src/components/SearchBar.tsx @@ -1,6 +1,6 @@ // AnimatedSearchBox.tsx (replace current file) import { useId, useState } from "react"; -import "../styles/SearchBar.css"; +import "@/assets/styles/SearchBar.css"; type SelectedImage = File | string | null; diff --git a/frontend/src/components/SuggestionChips.tsx b/frontend/src/components/SuggestionChips.tsx index fe50756..4968d61 100644 --- a/frontend/src/components/SuggestionChips.tsx +++ b/frontend/src/components/SuggestionChips.tsx @@ -1,5 +1,5 @@ import { useEffect, useMemo, useState } from "react"; -import "../styles/SuggestionChips.css"; +import "@/assets/styles/SuggestionChips.css"; type Props = { hasImage: boolean; diff --git a/frontend/src/constants.ts b/frontend/src/constants.ts index d8f1722..9c0f9e4 100644 --- a/frontend/src/constants.ts +++ b/frontend/src/constants.ts @@ -1,3 +1,14 @@ export const BASE_URL = import.meta.env.VITE_API_URL || "http://localhost:8000"; export const API_PREFIX = import.meta.env.VITE_API_PREFIX || "/api/v1"; // New environment variable export const FULL_API_URL = `${BASE_URL}${API_PREFIX}` + + +export const NAVBAR_MENU = [ + { name: 'home', path: '/' }, + { name: 'analysis', path: '/analysis' }, + { name: 'upload', path: '/upload' }, + { name: 'list', path: '/view' }, + { name: 'search', path: '/search' }, + { name: 'chat', path: '/chat' }, + { name: 'about', path: '/about', enable: false }, +] \ No newline at end of file diff --git a/frontend/src/pages/AudioForm.tsx b/frontend/src/pages/AudioForm.tsx index c7a4f5f..885aa43 100644 --- a/frontend/src/pages/AudioForm.tsx +++ b/frontend/src/pages/AudioForm.tsx @@ -3,7 +3,7 @@ import { useLocation, useNavigate } from "react-router-dom"; import Loader from "../components/Loader"; import Notification from "../components/Notification"; import { useUploadAndRecord } from "../hooks/useUploadAndRecord"; -import "../styles/UploadPage.css"; +import "@/assets/styles/UploadPage.css"; import { generateFabricName } from "../utils/fabric-name"; type AudioMode = "upload" | "record"; diff --git a/frontend/src/pages/ComingSoon.tsx b/frontend/src/pages/ComingSoon.tsx index 05c7e68..0dd4fe4 100644 --- a/frontend/src/pages/ComingSoon.tsx +++ b/frontend/src/pages/ComingSoon.tsx @@ -1,9 +1,9 @@ import React from "react"; -import "../styles/ComingSoon.css"; +import "@/assets/styles/ComingSoon.css"; const ComingSoon: React.FC = () => { return ( -
+

Coming Soon

This page is under construction.

diff --git a/frontend/src/pages/Contact.tsx b/frontend/src/pages/Contact.tsx new file mode 100644 index 0000000..12a05a5 --- /dev/null +++ b/frontend/src/pages/Contact.tsx @@ -0,0 +1,72 @@ +import { useState } from "react"; +import "@/assets/styles/contact.css"; +import { FULL_API_URL } from '@/constants'; + +export const ContactUs = () => { + const [form, setForm] = useState({ name: "", email: "", message: "" }); + const [status, setStatus] = useState(""); + + const handleChange = (e) => + setForm({ ...form, [e.target.name]: e.target.value }); + + const handleSubmit = async (e) => { + e.preventDefault(); + setStatus("Sending..."); + + const res = await fetch(`${FULL_API_URL}/contact`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(form), + }); + + if (res.ok) { + setStatus("Message sent!"); + setForm({ name: "", email: "", message: "" }); + } else { + setStatus("Failed to send!"); + } + }; + + return ( +
+

Contact Us

+ + +
+ + + + +