Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 121 additions & 12 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ import ProductPage from './ProductPage'
import ProductsPage from './ProductsPage'
import CheckoutPage from './CheckoutPage'
import CheckoutSuccessPage from './CheckoutSuccessPage'
import LoginPage from './LoginPage'
import RegisterPage from './RegisterPage'
import UserOrdersPage from './UserOrdersPage'
import ProtectedRoute from './components/ProtectedRoute'
import { AuthProvider, useAuth } from './contexts/AuthContext'
import { GET_PRODUCTS } from './graphql/queries'

const currency = new Intl.NumberFormat('en-US', {
Expand Down Expand Up @@ -126,24 +131,63 @@ function Layout({
toggleCart,
updateQuantity,
}: LayoutProps) {
const { isAuthenticated, user, logout } = useAuth()

return (
<div className="shop">
<div className="cart-bar">
<div>
<p className="eyebrow">Your bag</p>
<p>{cartCount ? `${cartCount} item${cartCount > 1 ? 's' : ''}` : 'No items yet'}</p>
</div>
<button
type="button"
className="cart-toggle"
onClick={toggleCart}
data-testid="cart-toggle"
>
Bag
<span className="cart-pill" data-testid="cart-count" aria-live="polite">
{cartCount}
</span>
</button>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
{isAuthenticated ? (
<>
<Link
to="/account/orders"
style={{
color: 'inherit',
textDecoration: 'none',
fontSize: '0.875rem',
marginRight: '0.5rem',
}}
>
{user?.email}
</Link>
<button
type="button"
className="btn btn--ghost btn--small"
onClick={logout}
style={{ fontSize: '0.875rem' }}
>
Logout
</button>
</>
) : (
<Link
to="/login"
style={{
color: 'inherit',
textDecoration: 'none',
fontSize: '0.875rem',
marginRight: '0.5rem',
}}
>
Login
</Link>
)}
<button
type="button"
className="cart-toggle"
onClick={toggleCart}
data-testid="cart-toggle"
>
Bag
<span className="cart-pill" data-testid="cart-count" aria-live="polite">
{cartCount}
</span>
</button>
</div>
</div>

{isCartOpen && (
Expand Down Expand Up @@ -190,6 +234,7 @@ function Layout({
to="/checkout"
className="btn btn--primary btn--full"
style={{ textAlign: 'center', display: 'block', textDecoration: 'none' }}
onClick={toggleCart}
>
Proceed to checkout
</Link>
Expand Down Expand Up @@ -513,8 +558,72 @@ function App() {
</Layout>
}
/>
<Route
path="/login"
element={
<Layout
cartItems={cartItems}
cartCount={cartCount}
subtotal={subtotal}
shipping={shipping}
total={total}
freeShippingMessage={freeShippingMessage}
isCartOpen={isCartOpen}
toggleCart={toggleCart}
updateQuantity={updateQuantity}
>
<LoginPage />
</Layout>
}
/>
<Route
path="/register"
element={
<Layout
cartItems={cartItems}
cartCount={cartCount}
subtotal={subtotal}
shipping={shipping}
total={total}
freeShippingMessage={freeShippingMessage}
isCartOpen={isCartOpen}
toggleCart={toggleCart}
updateQuantity={updateQuantity}
>
<RegisterPage />
</Layout>
}
/>
<Route
path="/account/orders"
element={
<Layout
cartItems={cartItems}
cartCount={cartCount}
subtotal={subtotal}
shipping={shipping}
total={total}
freeShippingMessage={freeShippingMessage}
isCartOpen={isCartOpen}
toggleCart={toggleCart}
updateQuantity={updateQuantity}
>
<ProtectedRoute>
<UserOrdersPage />
</ProtectedRoute>
</Layout>
}
/>
</Routes>
)
}

export default App
function AppWithAuth() {
return (
<AuthProvider>
<App />
</AuthProvider>
)
}

export default AppWithAuth
23 changes: 18 additions & 5 deletions src/CheckoutSuccessPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect } from 'react'
import { Link, useParams } from 'react-router-dom'
import { useParams } from 'react-router-dom'
import { useQuery } from '@apollo/client/react'
import './App.css'
import { GET_CHECKOUT } from './graphql/queries'
Expand Down Expand Up @@ -77,9 +77,15 @@ export default function CheckoutSuccessPage({ onClearCart }: CheckoutSuccessPage
<div className="checkout-page__empty">
<h1>Checkout not found</h1>
<p>Sorry, we couldn't find your checkout information.</p>
<Link to="/products" className="btn btn--primary">
<button
type="button"
className="btn btn--primary"
onClick={() => {
window.location.href = '/products'
}}
>
Continue Shopping
</Link>
</button>
</div>
</div>
</div>
Expand Down Expand Up @@ -130,9 +136,16 @@ export default function CheckoutSuccessPage({ onClearCart }: CheckoutSuccessPage
</div>
</div>
<div className="checkout-page__success-actions">
<Link to="/products" className="btn btn--primary">
<button
type="button"
className="btn btn--primary"
onClick={() => {
// Direct navigation using window.location to ensure page updates
window.location.href = '/products'
}}
>
Continue Shopping
</Link>
</button>
</div>
</div>
</div>
Expand Down
101 changes: 101 additions & 0 deletions src/LoginPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { useState, type FormEvent } from 'react'
import { useNavigate, Link, useLocation } from 'react-router-dom'
import { useAuth } from './contexts/AuthContext'
import './App.css'

export default function LoginPage() {
const navigate = useNavigate()
const location = useLocation()
const { login } = useAuth()
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)

const from = location.state?.from?.pathname || '/account/orders'

const handleSubmit = async (e: FormEvent) => {
e.preventDefault()
setError('')
setLoading(true)

try {
await login(email, password)
navigate(from, { replace: true })
} catch (err) {
setError(err instanceof Error ? err.message : 'Login failed. Please try again.')
} finally {
setLoading(false)
}
}

return (
<div className="shop">
<div className="checkout-page">
<div className="checkout-page__content">
<div
className="checkout-page__form-section"
style={{ maxWidth: '500px', margin: '0 auto' }}
>
<h1 className="checkout-page__title">Login</h1>

<form className="checkout-form" onSubmit={handleSubmit}>
{error && (
<div className="checkout-form__error">
<p>{error}</p>
</div>
)}

<div className="checkout-form__group">
<label htmlFor="email" className="checkout-form__label">
Email
</label>
<input
id="email"
name="email"
type="email"
className="checkout-form__input"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
autoComplete="email"
/>
</div>

<div className="checkout-form__group">
<label htmlFor="password" className="checkout-form__label">
Password
</label>
<input
id="password"
name="password"
type="password"
className="checkout-form__input"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
autoComplete="current-password"
/>
</div>

<button
type="submit"
className="btn btn--primary btn--full checkout-form__submit"
disabled={loading}
>
{loading ? 'Logging in...' : 'Login'}
</button>

<p style={{ textAlign: 'center', marginTop: '1rem' }}>
Don't have an account?{' '}
<Link to="/register" style={{ color: 'inherit', textDecoration: 'underline' }}>
Register here
</Link>
</p>
</form>
</div>
</div>
</div>
</div>
)
}
7 changes: 4 additions & 3 deletions src/ProductsPage.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect } from 'react'
import { Link } from 'react-router-dom'
import { Link, useLocation } from 'react-router-dom'
import { useQuery } from '@apollo/client/react'
import './App.css'
import ProductCard from './ProductCard'
Expand All @@ -16,12 +16,13 @@ type ProductsQueryResult = {
}

export default function ProductsPage({ addToCart, isHighlighted }: ProductsPageProps) {
const location = useLocation()
const { loading, error, data } = useQuery<ProductsQueryResult>(GET_PRODUCTS)

// Scroll to top when products page loads
// Scroll to top when products page loads or location changes
useEffect(() => {
window.scrollTo({ top: 0, behavior: 'smooth' })
}, [])
}, [location.pathname])

if (loading) return <div className="products-page">Loading products...</div>
if (error) return <div className="products-page">Error loading products: {error.message}</div>
Expand Down
Loading