From 9ec1d04b3c9596bffc306bddafac10e0e0e66155 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 15 Jul 2025 14:37:23 +0000 Subject: [PATCH 1/2] Add authentication page with sign in/up form and validation Co-authored-by: pt.alexandremarques --- apps/web/src/app/auth/README.md | 52 +++++++ apps/web/src/app/auth/layout.tsx | 14 ++ apps/web/src/app/auth/page.tsx | 242 +++++++++++++++++++++++++++++ apps/web/src/components/Navbar.tsx | 5 + 4 files changed, 313 insertions(+) create mode 100644 apps/web/src/app/auth/README.md create mode 100644 apps/web/src/app/auth/layout.tsx create mode 100644 apps/web/src/app/auth/page.tsx diff --git a/apps/web/src/app/auth/README.md b/apps/web/src/app/auth/README.md new file mode 100644 index 0000000..0409264 --- /dev/null +++ b/apps/web/src/app/auth/README.md @@ -0,0 +1,52 @@ +# Authentication Page + +This directory contains the authentication functionality for the Tech Companies Portugal application. + +## Features + +- **Sign In/Sign Up Toggle**: Users can switch between sign in and sign up modes +- **Form Validation**: Real-time validation with error messages +- **Loading States**: Visual feedback during form submission +- **Responsive Design**: Works on all device sizes +- **Consistent Styling**: Uses the existing design system components + +## Components Used + +- `RetroContainer`: Main container with retro styling +- `Card`: Form container with header and content sections +- `Input`: Form input fields with error states +- `Button`: Submit button with loading state +- `Label`: Form field labels + +## Design Patterns + +The authentication page follows the existing design patterns from the codebase: + +- **Retro Container**: Uses the `RetroContainer` component with default variant for the main form container +- **Typography**: Uses `font-mono` for consistent monospace font styling +- **Color Scheme**: Uses the existing CSS custom properties for colors +- **Spacing**: Follows the established spacing patterns with Tailwind classes +- **Interactive States**: Hover effects and focus states match the existing components + +## Form Validation + +- Email validation with regex pattern +- Password minimum length (6 characters) +- Password confirmation matching +- Required field validation +- Real-time error clearing when user starts typing + +## Navigation + +- "Sign In" button in the navbar links to `/auth` +- "Back to Home" button on the auth page +- Toggle between sign in and sign up modes + +## Future Enhancements + +- Integration with authentication service (Auth0, Firebase, etc.) +- Password reset functionality +- Social login options +- Remember me functionality +- Session management +- Protected route implementation \ No newline at end of file diff --git a/apps/web/src/app/auth/layout.tsx b/apps/web/src/app/auth/layout.tsx new file mode 100644 index 0000000..272a209 --- /dev/null +++ b/apps/web/src/app/auth/layout.tsx @@ -0,0 +1,14 @@ +import type { Metadata } from "next/types"; + +export const metadata: Metadata = { + title: "Authentication - Tech Companies Portugal", + description: "Sign in or sign up to access your account", +}; + +export default function AuthLayout({ + children, +}: { + children: React.ReactNode; +}) { + return children; +} \ No newline at end of file diff --git a/apps/web/src/app/auth/page.tsx b/apps/web/src/app/auth/page.tsx new file mode 100644 index 0000000..ee48e9c --- /dev/null +++ b/apps/web/src/app/auth/page.tsx @@ -0,0 +1,242 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Label } from "@/components/ui/label"; +import { RetroContainer } from "@/components/ui/retro-container"; + +export default function AuthPage() { + const [isSignIn, setIsSignIn] = useState(true); + const [isLoading, setIsLoading] = useState(false); + const [errors, setErrors] = useState>({}); + const [formData, setFormData] = useState({ + email: "", + password: "", + confirmPassword: "", + name: "", + }); + + const validateForm = () => { + const newErrors: Record = {}; + + if (!formData.email) { + newErrors.email = "Email is required"; + } else if (!/\S+@\S+\.\S+/.test(formData.email)) { + newErrors.email = "Please enter a valid email"; + } + + if (!formData.password) { + newErrors.password = "Password is required"; + } else if (formData.password.length < 6) { + newErrors.password = "Password must be at least 6 characters"; + } + + if (!isSignIn) { + if (!formData.name) { + newErrors.name = "Name is required"; + } + + if (!formData.confirmPassword) { + newErrors.confirmPassword = "Please confirm your password"; + } else if (formData.password !== formData.confirmPassword) { + newErrors.confirmPassword = "Passwords do not match"; + } + } + + setErrors(newErrors); + return Object.keys(newErrors).length === 0; + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!validateForm()) { + return; + } + + setIsLoading(true); + + try { + // TODO: Implement authentication logic + console.log("Form submitted:", { isSignIn, formData }); + + // Simulate API call + await new Promise(resolve => setTimeout(resolve, 1000)); + + // TODO: Handle successful authentication + console.log("Authentication successful"); + } catch (error) { + console.error("Authentication failed:", error); + // TODO: Handle authentication errors + } finally { + setIsLoading(false); + } + }; + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target; + setFormData(prev => ({ ...prev, [name]: value })); + + // Clear error when user starts typing + if (errors[name]) { + setErrors(prev => ({ ...prev, [name]: "" })); + } + }; + + return ( +
+
+ +
+ + + + + {isSignIn ? "Sign In" : "Sign Up"} + + + {isSignIn + ? "Welcome back! Sign in to your account" + : "Create a new account to get started" + } + + + + +
+ {!isSignIn && ( +
+ + + {errors.name && ( +

{errors.name}

+ )} +
+ )} + +
+ + + {errors.email && ( +

{errors.email}

+ )} +
+ +
+ + + {errors.password && ( +

{errors.password}

+ )} +
+ + {!isSignIn && ( +
+ + + {errors.confirmPassword && ( +

{errors.confirmPassword}

+ )} +
+ )} + + +
+ +
+

+ {isSignIn ? "Don't have an account? " : "Already have an account? "} + +

+
+ + {isSignIn && ( +
+ +
+ )} +
+
+
+
+ ); +} \ No newline at end of file diff --git a/apps/web/src/components/Navbar.tsx b/apps/web/src/components/Navbar.tsx index 1651c5f..95e3327 100644 --- a/apps/web/src/components/Navbar.tsx +++ b/apps/web/src/components/Navbar.tsx @@ -52,6 +52,11 @@ export default function Navbar() { /> + diff --git a/apps/web/src/components/Navbar.tsx b/apps/web/src/components/Navbar.tsx index 95e3327..851ed47 100644 --- a/apps/web/src/components/Navbar.tsx +++ b/apps/web/src/components/Navbar.tsx @@ -6,8 +6,11 @@ import logo from "../../public/assets/images/logo.png"; import ExploreButton from "./ExploreButton"; import FiltersPanelButton from "./FiltersPanelButton"; import { Button } from "./ui/button"; +import { useAuth } from "./hooks/useAuth"; export default function Navbar() { + const { user, signOut, loading } = useAuth(); + return (
- + {!loading && ( + <> + {user ? ( +
+ + {user.email} + + +
+ ) : ( + + )} + + )}