├── src ├── react-app-env.d.ts ├── vite-env.d.ts ├── styles │ ├── global.css │ ├── Blog.css │ ├── AddAppointment.css │ ├── Appointments.css │ ├── Register.css │ ├── Navbar.css │ ├── Login.css │ ├── Services.css │ └── Home.css ├── index.tsx ├── services │ └── api.ts ├── components │ ├── ProtectedRoute.tsx │ └── Navbar.tsx ├── routes.tsx ├── App.tsx ├── pages │ ├── Blog.tsx │ ├── Appointments.tsx │ ├── AddAppointment.tsx │ ├── Login.tsx │ ├── Home.tsx │ ├── Register.tsx │ └── Services.tsx └── hooks │ └── UserContext.tsx ├── .env ├── public ├── Image1.jpg ├── index.html └── vite.svg ├── tsconfig.json ├── .gitignore ├── vite.config.ts ├── tsconfig.node.json ├── tsconfig.app.json ├── eslint.config.js ├── package.json └── README.md /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | REACT_APP_API_URL=https://bance-assetou-mindvision-capstonebackend.onrender.com -------------------------------------------------------------------------------- /public/Image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/assetou27/Bance_Assetou_MindVision_CapstoneFrontend/HEAD/public/Image1.jpg -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | .env 26 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | 7 | plugins: [react()], 8 | server: { 9 | proxy: { 10 | "/api": { 11 | target: "http://localhost:5000", 12 | changeOrigin: true, 13 | }, 14 | }, 15 | }, 16 | }) -------------------------------------------------------------------------------- /src/styles/global.css: -------------------------------------------------------------------------------- 1 | /* src/styles/global.css */ 2 | 3 | /* Basic reset and global font styles */ 4 | body { 5 | margin: 0; 6 | font-family: Arial, sans-serif; 7 | background-color: #f9f9f9; 8 | } 9 | 10 | /* Remove underline from links and use inherited color */ 11 | a { 12 | text-decoration: none; 13 | color: inherit; 14 | } 15 | 16 | /* Style for error messages */ 17 | .error { 18 | color: red; 19 | } 20 | 21 | /* Style for success messages */ 22 | .success { 23 | color: green; 24 | } 25 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | // src/index.tsx 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom/client'; 4 | import App from './App'; 5 | // Import global CSS for overall styling (fonts, background, etc.) 6 | import './styles/global.css'; 7 | 8 | // Find the root element in the HTML and create a React root 9 | const root = ReactDOM.createRoot(document.getElementById('root')!); 10 | 11 | // Render the App component inside React.StrictMode for extra checks in development 12 | root.render( 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/services/api.ts: -------------------------------------------------------------------------------- 1 | // src/services/api.ts 2 | import axios from 'axios'; 3 | 4 | /** 5 | * Creates an Axios instance for all API calls. 6 | * 7 | * We use an environment variable for the baseURL. If it's not set, 8 | * we fall back to your Render-deployed backend. 9 | * 10 | * If your routes are like "/api/auth", "/api/appointments", etc., 11 | * then you just need the domain here. 12 | */ 13 | const api = axios.create({ 14 | baseURL: process.env.REACT_APP_API_URL 15 | || 'https://bance-assetou-mindvision-capstonebackend.onrender.com', 16 | }); 17 | 18 | export default api; 19 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | MindVision Frontend 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/ProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | // src/components/ProtectedRoute.tsx 2 | import React, { useContext } from 'react'; 3 | import { Navigate } from 'react-router-dom'; 4 | import { UserContext } from '../hooks/UserContext'; 5 | 6 | // Define props to accept a single child component 7 | interface ProtectedRouteProps { 8 | children: JSX.Element; 9 | } 10 | 11 | export default function ProtectedRoute({ children }: ProtectedRouteProps) { 12 | const { user } = useContext(UserContext); 13 | 14 | // If there is no authenticated user, redirect to the Login page 15 | if (!user) { 16 | return ; 17 | } 18 | 19 | // If the user is authenticated, render the child component 20 | return children; 21 | } 22 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /src/styles/Blog.css: -------------------------------------------------------------------------------- 1 | /* src/styles/Blog.css */ 2 | 3 | /* Main container for the Blog page */ 4 | .blog-page { 5 | max-width: 800px; /* Limit content width */ 6 | margin: 0 auto; /* Center horizontally */ 7 | padding: 2rem; /* Spacing around content */ 8 | font-family: 'Segoe UI', sans-serif; 9 | } 10 | 11 | /* Page title styling */ 12 | .blog-page h1 { 13 | text-align: center; 14 | margin-bottom: 1.5rem; 15 | color: #222; 16 | } 17 | 18 | /* Error message styling */ 19 | .error { 20 | color: red; 21 | text-align: center; 22 | margin-bottom: 1rem; 23 | } 24 | 25 | /* Grid container for the list of blog posts */ 26 | .blog-list { 27 | display: grid; 28 | gap: 1.5rem; /* Space between blog cards */ 29 | } 30 | 31 | /* Individual blog card styling */ 32 | .blog-card { 33 | background-color: #fff; 34 | border-radius: 6px; 35 | box-shadow: 0 2px 8px rgba(0,0,0,0.1); 36 | padding: 1rem; 37 | } 38 | 39 | /* Blog card title styling */ 40 | .blog-card h2 { 41 | margin: 0 0 0.5rem; 42 | color: #333; 43 | } 44 | 45 | /* Blog card text styling */ 46 | .blog-card p { 47 | margin: 0.5rem 0; 48 | color: #555; 49 | } 50 | -------------------------------------------------------------------------------- /src/styles/AddAppointment.css: -------------------------------------------------------------------------------- 1 | /* src/styles/AddAppointment.css */ 2 | 3 | /* Container for the add appointment page */ 4 | .add-appointment-page { 5 | max-width: 600px; 6 | margin: 0 auto; 7 | padding: 2rem; 8 | font-family: 'Segoe UI', sans-serif; 9 | } 10 | 11 | .add-appointment-page h1 { 12 | text-align: center; 13 | margin-bottom: 1.5rem; 14 | color: #222; 15 | } 16 | 17 | .message { 18 | text-align: center; 19 | margin-bottom: 1rem; 20 | font-weight: bold; 21 | color: green; 22 | } 23 | 24 | /* Appointment form styling */ 25 | .appointment-form { 26 | display: flex; 27 | flex-direction: column; 28 | gap: 1rem; 29 | } 30 | 31 | .appointment-form label { 32 | font-weight: 600; 33 | color: #333; 34 | } 35 | 36 | .appointment-form input, 37 | .appointment-form textarea { 38 | padding: 0.75rem; 39 | border: 1px solid #ccc; 40 | border-radius: 6px; 41 | font-size: 1rem; 42 | } 43 | 44 | .appointment-form button { 45 | padding: 0.75rem; 46 | background-color: #6c5ce7; 47 | color: #fff; 48 | font-weight: bold; 49 | border: none; 50 | border-radius: 6px; 51 | cursor: pointer; 52 | transition: background 0.3s; 53 | } 54 | 55 | .appointment-form button:hover { 56 | background-color: #574b90; 57 | } 58 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mindvision-frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.17.0", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "@types/jest": "^27.5.2", 10 | "@types/node": "^16.18.40", 11 | "@types/react": "^18.2.20", 12 | "@types/react-dom": "^18.2.7", 13 | "axios": "^1.6.2", 14 | "bootstrap": "^5.3.2", 15 | "react": "^18.2.0", 16 | "react-bootstrap": "^2.9.1", 17 | "react-dom": "^18.2.0", 18 | "react-icons": "^4.12.0", 19 | "react-router-dom": "^6.20.0", 20 | "react-scripts": "5.0.1", 21 | "typescript": "^4.9.5", 22 | "web-vitals": "^2.1.4" 23 | }, 24 | "scripts": { 25 | "dev": "react-scripts start", 26 | "start": "react-scripts start", 27 | "build": "react-scripts build", 28 | "test": "react-scripts test", 29 | "eject": "react-scripts eject" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | }, 49 | "devDependencies": { 50 | "@types/react-router-dom": "^5.3.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/styles/Appointments.css: -------------------------------------------------------------------------------- 1 | /* src/styles/Appointments.css */ 2 | 3 | /* Container for the appointments page */ 4 | .appointments-page { 5 | max-width: 800px; 6 | margin: 0 auto; 7 | padding: 2rem; 8 | font-family: 'Segoe UI', sans-serif; 9 | } 10 | 11 | .appointments-page h1 { 12 | text-align: center; 13 | margin-bottom: 1.5rem; 14 | color: #222; 15 | } 16 | 17 | .error { 18 | color: red; 19 | text-align: center; 20 | margin-bottom: 1rem; 21 | } 22 | 23 | /* Button to add a new appointment */ 24 | .add-appointment-btn { 25 | display: block; 26 | margin: 0 auto 2rem; 27 | padding: 0.75rem 1.5rem; 28 | background-color: #6c5ce7; 29 | color: #fff; 30 | border: none; 31 | border-radius: 6px; 32 | cursor: pointer; 33 | font-weight: bold; 34 | transition: background 0.3s; 35 | } 36 | 37 | .add-appointment-btn:hover { 38 | background-color: #574b90; 39 | } 40 | 41 | /* List of appointments */ 42 | .appointments-list { 43 | display: grid; 44 | gap: 1rem; 45 | } 46 | 47 | /* Individual appointment card styling */ 48 | .appointment-card { 49 | background-color: #fff; 50 | border-radius: 6px; 51 | box-shadow: 0 2px 8px rgba(0,0,0,0.1); 52 | padding: 1rem; 53 | } 54 | 55 | .appointment-card h2 { 56 | margin: 0 0 0.5rem; 57 | color: #333; 58 | } 59 | 60 | .appointment-card p { 61 | margin: 0; 62 | color: #555; 63 | } 64 | -------------------------------------------------------------------------------- /src/routes.tsx: -------------------------------------------------------------------------------- 1 | // src/routes.tsx 2 | 3 | // Importing React library 4 | import React from 'react'; 5 | 6 | // Importing routing components from react-router-dom 7 | import { Routes, Route } from 'react-router-dom'; 8 | 9 | // Importing individual page components 10 | import Home from './pages/Home'; 11 | import Services from './pages/Services'; 12 | import Blog from './pages/Blog'; 13 | import Appointments from './pages/Appointments'; 14 | import AddAppointment from './pages/AddAppointment'; 15 | import Login from './pages/Login'; 16 | import Register from './pages/Register'; 17 | 18 | // Main routing component defining all application routes 19 | export default function AppRoutes() { 20 | return ( 21 | 22 | {/* Route for Home page */} 23 | } /> 24 | 25 | {/* Route for Services page */} 26 | } /> 27 | 28 | {/* Route for Blog page */} 29 | } /> 30 | 31 | {/* Route for listing Appointments */} 32 | } /> 33 | 34 | {/* Route for adding a new Appointment */} 35 | } /> 36 | 37 | {/* Route for Login page */} 38 | } /> 39 | 40 | {/* Route for Register page */} 41 | } /> 42 | 43 | ); 44 | } -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | // src/App.tsx 2 | import React from 'react'; 3 | // BrowserRouter is used to enable routing using the HTML5 History API. 4 | import { BrowserRouter } from 'react-router-dom'; 5 | 6 | // Import your custom routes component (renamed as AppRoutes) which defines all the application routes. 7 | import AppRoutes from './routes'; 8 | 9 | // Import the Navbar component that displays the navigation bar on every page. 10 | import Navbar from './components/Navbar'; 11 | 12 | // Import the UserProvider from your authentication context. 13 | // UserProvider makes authentication data (e.g., current user, login/logout functions) available to all components in the app. 14 | import { UserProvider } from './hooks/UserContext'; 15 | 16 | /** 17 | * App Component 18 | * ------------- 19 | * This is the root component of your application. 20 | * 21 | * It wraps the entire app with: 22 | * - BrowserRouter: Enables client-side routing. 23 | * - UserProvider: Provides global authentication state and functions. 24 | * - Navbar: Displays the navigation bar on every page. 25 | * - AppRoutes: Renders the appropriate page based on the current URL. 26 | */ 27 | export default function App() { 28 | return ( 29 | // BrowserRouter ensures that app can use the routing features provided by react-router-dom. 30 | 31 | {/* UserProvider makes the authentication state (user data, login/logout functions) available to all nested components */} 32 | 33 | {/* Navbar is placed outside the routes so that it appears on every page */} 34 | 35 | {/* AppRoutes contains the Route definitions and displays the appropriate page for each URL */} 36 | 37 | 38 | 39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MindVision Frontend 2 | 3 | Welcome to the **MindVision** coaching platform frontend! This React application provides the user interface for registration, login, booking appointments, viewing services, and more. 4 | 5 | ## Table of Contents 6 | - [Overview](#overview) 7 | - [Key Features](#key-features) 8 | - [Tech Stack](#tech-stack) 9 | - [Getting Started](#getting-started) 10 | - [Available Scripts](#available-scripts) 11 | - [Folder Structure](#folder-structure) 12 | - [Environment Variables](#environment-variables) 13 | - [Deployment](#deployment) 14 | - [Contact](#contact) 15 | 16 | --- 17 | 18 | ## Overview 19 | 20 | **MindVision Frontend** is built with React (using [Create React App](https://create-react-app.dev/) or Vite—whichever you used), React Router for client-side routing, and Axios for API calls. Users can: 21 | 22 | - **Register** for an account or **log in**. 23 | - **Book appointments** (protected route). 24 | - **View and manage** services and blog posts. 25 | - **Explore** coaching packages. 26 | 27 | The UI is styled with custom CSS, Bootstrap 28 | 29 | --- 30 | 31 | ## Key Features 32 | 33 | 1. **Authentication**: Users can sign up, log in, and log out. 34 | 2. **Appointments**: Create and view appointments if authenticated. 35 | 3. **Services**: Display various coaching packages and benefits. 36 | 4. **Responsive Design**: Looks good on both desktop and mobile. 37 | 5. **Protected Routes**: Certain pages require a valid JWT token to access. 38 | 39 | --- 40 | 41 | ## Tech Stack 42 | 43 | - **React** (create-react-app or Vite-based) 44 | - **React Router** for routing 45 | - **Axios** for HTTP requests 46 | - **CSS** (or Sass/Bootstrap) for styling 47 | 48 | --- 49 | ## Trello and typescript were used 50 | 51 | 52 | ## Netlify 53 | https://abmindvision.netlify.app/ 54 | 55 | 56 | 1. **Clone the repository**: 57 | ```bash 58 | git clone https://github.com/YourUser/MindVision_CapstoneFrontend.git 59 | cd MindVision_CapstoneFrontend 60 | 61 | -------------------------------------------------------------------------------- /src/pages/Blog.tsx: -------------------------------------------------------------------------------- 1 | // src/pages/Blog.tsx 2 | import React, { useEffect, useState } from 'react'; 3 | import api from '../services/api'; // Axios instance for backend calls 4 | import '../styles/Blog.css'; 5 | 6 | /** 7 | * Adjust the fields to match your actual data (e.g., 'title', 'content', 'author'). 8 | */ 9 | interface BlogPost { 10 | _id: string; 11 | title: string; 12 | content: string; 13 | author?: string; 14 | } 15 | 16 | /** 17 | * Blog Component 18 | * -------------- 19 | * Fetches a list of blog posts from the backend (GET /api/blog) 20 | * and displays them in a grid of blog cards. 21 | */ 22 | export default function Blog() { 23 | // State for storing the array of blog posts 24 | const [posts, setPosts] = useState([]); 25 | // State for capturing errors that occur during the fetch 26 | const [error, setError] = useState(null); 27 | 28 | /** 29 | * useEffect Hook 30 | * -------------- 31 | * Fetches blog posts once when the component mounts. 32 | */ 33 | useEffect(() => { 34 | const fetchPosts = async () => { 35 | try { 36 | // Adjust endpoint if your backend route is different 37 | const response = await api.get('/api/blog'); 38 | console.log(response.data); 39 | setPosts(response.data); 40 | } catch (err: any) { 41 | setError('Failed to fetch blog posts.'); 42 | } 43 | }; 44 | 45 | fetchPosts(); 46 | }, []); 47 | 48 | return ( 49 |
50 |

Blog

51 | 52 | {/* If there's an error, display it */} 53 | {error &&

{error}

} 54 | 55 | {/* Display each blog post in a "blog-card" */} 56 |
57 | {posts.map((post) => ( 58 |
59 |

{post.title}

60 | {post.author &&

by {post.author.name}

} 61 |

{post.content}

62 |
63 | ))} 64 |
65 |
66 | ); 67 | } 68 | -------------------------------------------------------------------------------- /src/pages/Appointments.tsx: -------------------------------------------------------------------------------- 1 | // src/pages/Appointments.tsx 2 | import React, { useEffect, useState } from 'react'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import api from '../services/api'; 5 | import '../styles/Appointments.css'; 6 | 7 | interface Appointment { 8 | _id: string; 9 | date: string; // stored as an ISO string from the backend 10 | notes: string; // renamed 'description' to 'notes' 11 | } 12 | 13 | export default function Appointments() { 14 | // State to hold the fetched appointments 15 | const [appointments, setAppointments] = useState([]); 16 | // State to capture errors during the fetch 17 | const [error, setError] = useState(null); 18 | 19 | const navigate = useNavigate(); 20 | 21 | // Fetch appointments when the component mounts 22 | useEffect(() => { 23 | const fetchAppointments = async () => { 24 | try { 25 | // GET /api/appointments 26 | const response = await api.get('/api/appointments'); 27 | setAppointments(response.data); 28 | } catch (err: any) { 29 | setError('Failed to fetch appointments.'); 30 | } 31 | }; 32 | fetchAppointments(); 33 | }, []); 34 | 35 | // Navigate to the "Add Appointment" page 36 | const handleAddAppointment = () => { 37 | navigate('/appointments/add'); 38 | }; 39 | 40 | return ( 41 |
42 |

Appointments

43 | {error &&

{error}

} 44 | 45 | 48 | 49 |
50 | {appointments.map((appt) => { 51 | // Convert the ISO date to a local date/time 52 | const dateObj = new Date(appt.date); 53 | const dateStr = dateObj.toLocaleDateString(); 54 | const timeStr = dateObj.toLocaleTimeString([], { 55 | hour: '2-digit', 56 | minute: '2-digit' 57 | }); 58 | 59 | return ( 60 |
61 |

62 | {dateStr} at {timeStr} 63 |

64 |

{appt.notes}

65 |
66 | ); 67 | })} 68 |
69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /src/pages/AddAppointment.tsx: -------------------------------------------------------------------------------- 1 | // src/pages/AddAppointment.tsx 2 | import React, { useState } from 'react'; 3 | import { useNavigate } from 'react-router-dom'; 4 | import api from '../services/api'; 5 | import '../styles/AddAppointment.css'; 6 | 7 | export default function AddAppointment() { 8 | // Local state for the appointment form 9 | const [formData, setFormData] = useState({ 10 | date: '', // e.g. "2025-04-29" 11 | time: '', // e.g. "11:30" 12 | description: '', // e.g. "Consultation about goals" 13 | }); 14 | 15 | // Error or success message 16 | const [message, setMessage] = useState(null); 17 | const navigate = useNavigate(); 18 | 19 | // Update form data on input changes 20 | const handleChange = ( 21 | e: React.ChangeEvent 22 | ) => { 23 | setFormData({ ...formData, [e.target.name]: e.target.value }); 24 | }; 25 | 26 | // Handle form submission 27 | const handleSubmit = async (e: React.FormEvent) => { 28 | e.preventDefault(); 29 | try { 30 | // POST to /api/appointments with the form data 31 | await api.post('/api/appointments', formData); 32 | setMessage('Appointment created successfully!'); 33 | // Redirect back to the appointments list after a short delay 34 | setTimeout(() => { 35 | navigate('/appointments'); 36 | }, 1000); 37 | } catch (err: any) { 38 | setMessage('Failed to create appointment. Please try again.'); 39 | } 40 | }; 41 | 42 | return ( 43 |
44 |

Add New Appointment

45 | {message &&

{message}

} 46 | 47 |
48 | 49 | 57 | 58 | 59 | 67 | 68 | 69 |