diff --git a/gs/frontend/mcc/package-lock.json b/gs/frontend/mcc/package-lock.json index d99df6328..e1dda8c9d 100644 --- a/gs/frontend/mcc/package-lock.json +++ b/gs/frontend/mcc/package-lock.json @@ -495,6 +495,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -541,6 +542,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1170,6 +1172,7 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-7.1.0.tgz", "integrity": "sha512-fNxRUk1KhjSbnbuBxlWSnBLKLBNun52ZBTcs22H/xEEzM6Ap81ZFTQ4bZBxVQGQgVY0xugKGoRcCbaKjLQ3XZA==", "license": "MIT", + "peer": true, "dependencies": { "@fortawesome/fontawesome-common-types": "7.1.0" }, @@ -2616,6 +2619,7 @@ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.10.4", "@babel/runtime": "^7.12.5", @@ -2788,6 +2792,7 @@ "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -2798,6 +2803,7 @@ "integrity": "sha512-6mDvHUFSjyT2B2yeNx2nUgMxh9LtOWvkhIU3uePn2I2oyNymUAX1NIsdgviM4CH+JSrp2D2hsMvJOkxY+0wNRA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -2808,6 +2814,7 @@ "integrity": "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw==", "devOptional": true, "license": "MIT", + "peer": true, "peerDependencies": { "@types/react": "^19.2.0" } @@ -2858,6 +2865,7 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -3225,6 +3233,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -3405,6 +3414,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.8.19", "caniuse-lite": "^1.0.30001751", @@ -3824,6 +3834,7 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4405,6 +4416,7 @@ "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@asamuzakjp/dom-selector": "^6.7.2", "cssstyle": "^5.3.1", @@ -5071,6 +5083,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", @@ -5154,6 +5167,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz", "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -5163,6 +5177,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz", "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -5666,6 +5681,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -5810,6 +5826,7 @@ "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5938,6 +5955,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.5.0", @@ -6052,6 +6070,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, diff --git a/gs/frontend/mcc/src/App.tsx b/gs/frontend/mcc/src/App.tsx index 6614a8675..851b94ae7 100644 --- a/gs/frontend/mcc/src/App.tsx +++ b/gs/frontend/mcc/src/App.tsx @@ -8,6 +8,7 @@ import Dashboard from "./pages/Dashboard"; import AROAdmin from "./pages/AROAdmin"; import LiveSession from "./pages/LiveSession"; import Login from "./pages/Login"; +import Signup from "./pages/Signup"; import PageNotFound from "./components/PageNotFound"; /** @@ -25,6 +26,7 @@ function App() { } /> } /> } /> + } /> } /> diff --git a/gs/frontend/mcc/src/components/auth/AuthIcons.tsx b/gs/frontend/mcc/src/components/auth/AuthIcons.tsx new file mode 100644 index 000000000..f62980bbf --- /dev/null +++ b/gs/frontend/mcc/src/components/auth/AuthIcons.tsx @@ -0,0 +1,128 @@ +/** + * @brief Collection of SVG icons used in authentication forms + */ + +export const EmailIcon = () => ( + + + +); + +export const LockIcon = () => ( + + + +); + +export const UserIcon = () => ( + + + +); + +export const BuildingIcon = () => ( + + + +); + +export const IDCardIcon = () => ( + + + +); + +export const LocationIcon = () => ( + + + + +); + +export const RocketIcon = () => ( + + + +); diff --git a/gs/frontend/mcc/src/components/auth/FormInput.tsx b/gs/frontend/mcc/src/components/auth/FormInput.tsx new file mode 100644 index 000000000..101d8c855 --- /dev/null +++ b/gs/frontend/mcc/src/components/auth/FormInput.tsx @@ -0,0 +1,57 @@ +import type { ReactNode } from "react"; + +interface FormInputProps { + id: string; + label: string; + type?: string; + placeholder: string; + icon?: ReactNode; + required?: boolean; + value?: string; + onChange?: (e: React.ChangeEvent) => void; +} + +/** + * @brief Reusable form input component with label and optional icon + * @param props FormInputProps + * @return tsx element of FormInput component + */ +function FormInput({ + id, + label, + type = "text", + placeholder, + icon, + required = false, + value, + onChange, +}: FormInputProps) { + return ( +
+ +
+ {icon && ( +
+ {icon} +
+ )} + +
+
+ ); +} + +export default FormInput; diff --git a/gs/frontend/mcc/src/index.css b/gs/frontend/mcc/src/index.css index 733c416e0..9a1f2bdf8 100644 --- a/gs/frontend/mcc/src/index.css +++ b/gs/frontend/mcc/src/index.css @@ -9,7 +9,7 @@ border-color: var(--border); } body { - overflow: hidden; + overflow-y: auto; display: flex; background-color: var(--background); font-family: var(--font-default); @@ -20,7 +20,7 @@ height: 100%; width: 100%; margin: 10px; - overflow: hidden; + overflow: visible; } } diff --git a/gs/frontend/mcc/src/pages/Login.tsx b/gs/frontend/mcc/src/pages/Login.tsx index 6dc1b79cd..89fb5e9b5 100644 --- a/gs/frontend/mcc/src/pages/Login.tsx +++ b/gs/frontend/mcc/src/pages/Login.tsx @@ -4,41 +4,134 @@ */ function Login() { return ( -
-
-

- Login +
+
+ {/* Rocket Icon */} +
+
+ + + +
+
+ + {/* Title */} +

+ UW Orbital MCC

+

+ Mission Control Center Access +

+
+ {/* Email Address */}
-
+ + {/* Password */}
-
+ + {/* Remember me and Forgot password */} +
+ + + Forgot password? +
+ + {/* Submit Button */}
+ + {/* Signup link */} +

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

+ + {/* Footer */} +

+ SECURE CONNECTION - MCC v2.0 +

); diff --git a/gs/frontend/mcc/src/pages/Signup.tsx b/gs/frontend/mcc/src/pages/Signup.tsx new file mode 100644 index 000000000..a32382bb4 --- /dev/null +++ b/gs/frontend/mcc/src/pages/Signup.tsx @@ -0,0 +1,261 @@ +import { useState } from "react"; +import FormInput from "../components/auth/FormInput"; +import { + EmailIcon, + LockIcon, + UserIcon, + BuildingIcon, + IDCardIcon, + LocationIcon, + RocketIcon, +} from "../components/auth/AuthIcons"; + +/** + * Configuration for form fields - modify this to add/remove/change fields easily + */ +const FORM_CONFIG = { + // Basic fields required for all users + basicFields: [ + { + id: "email", + label: "Email Address", + type: "email", + placeholder: "astronaut@uworbital.com", + icon: , + }, + { + id: "password", + label: "Password", + type: "password", + placeholder: "Create a secure password", + icon: , + }, + { + id: "confirmPassword", + label: "Confirm Password", + type: "password", + placeholder: "Re-enter your password", + icon: , + }, + ], + // Additional fields for operators + operatorFields: [ + { + id: "firstName", + label: "First Name", + type: "text", + placeholder: "John", + icon: , + }, + { + id: "lastName", + label: "Last Name", + type: "text", + placeholder: "Doe", + icon: , + }, + { + id: "institution", + label: "Institution", + type: "text", + placeholder: "University of Waterloo", + icon: , + }, + { + id: "identification", + label: "Identification Number", + type: "text", + placeholder: "ID-12345678", + icon: , + }, + { + id: "address", + label: "Address", + type: "text", + placeholder: "123 Orbital Drive, Waterloo, ON", + icon: , + }, + ], +}; + +/** + * @brief Signup component for user registration + * @return tsx element of Signup component + */ +function Signup() { + const [isOperator, setIsOperator] = useState(false); + const [formData, setFormData] = useState({ + email: "", + password: "", + confirmPassword: "", + firstName: "", + lastName: "", + institution: "", + identification: "", + address: "", + }); + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData((prev) => ({ + ...prev, + [name]: value, + })); + }; + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + + // Validate passwords match + if (formData.password !== formData.confirmPassword) { + alert("Passwords do not match!"); + return; + } + + // TODO: Implement actual signup logic here + console.log("Signup data:", { + ...formData, + role: isOperator ? "operator" : "user", + }); + + alert("Signup successful! (This is a placeholder)"); + }; + + return ( +
+
+ {/* Rocket Icon */} +
+
+ +
+
+ + {/* Title */} +

+ Join UW Orbital MCC +

+

+ Create your Mission Control Center account +

+ +
+ {/* Role Selection */} +
+ +
+ + +
+
+ + {/* Basic Fields - Always shown */} +
+ {FORM_CONFIG.basicFields.map((field) => ( + + ))} +
+ + {/* Operator Additional Fields - Conditionally shown */} + {isOperator && ( +
+

+ Additional information required for operator access +

+ {FORM_CONFIG.operatorFields.map((field) => ( + + ))} +
+ )} + + {/* Terms and Conditions */} +
+ + +
+ + {/* Submit Button */} + +
+ + {/* Login link */} +

+ Already have an account?{" "} + + Sign in + +

+ + {/* Footer */} +

+ SECURE CONNECTION - MCC v2.0 +

+
+
+ ); +} + +export default Signup;