Files
2026-01-17 14:17:42 +05:30

445 lines
15 KiB
TypeScript

'use client'
import { useState, useEffect } from 'react'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Button } from '@/components/ui/button'
import { Badge } from '@/components/ui/badge'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'
import { ArrowLeft, Plus, MapPin, Edit, Trash2, Star } from 'lucide-react'
import { motion } from 'framer-motion'
import { toast } from 'sonner'
import Link from 'next/link'
interface Address {
id: string
firstName: string
lastName: string
company?: string
address1: string
address2?: string
city: string
state: string
zipCode: string
country: string
phone?: string
isDefault: boolean
type: 'HOME' | 'WORK' | 'OTHER'
}
export default function AddressesPage() {
const [addresses, setAddresses] = useState<Address[]>([])
const [loading, setLoading] = useState(true)
const [dialogOpen, setDialogOpen] = useState(false)
const [editingAddress, setEditingAddress] = useState<Address | null>(null)
const [formData, setFormData] = useState({
firstName: '',
lastName: '',
company: '',
address1: '',
address2: '',
city: '',
state: '',
zipCode: '',
country: 'India',
phone: '',
isDefault: false,
type: 'HOME' as 'HOME' | 'WORK' | 'OTHER'
})
useEffect(() => {
fetchAddresses()
}, [])
const fetchAddresses = async () => {
try {
const response = await fetch('/api/user/addresses')
if (response.ok) {
const data = await response.json()
setAddresses(data.addresses || [])
}
} catch (error) {
console.error('Error fetching addresses:', error)
toast.error('Failed to load addresses')
} finally {
setLoading(false)
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
const requiredFields = ['firstName', 'lastName', 'address1', 'city', 'state', 'zipCode']
const missingFields = requiredFields.filter(field => !formData[field as keyof typeof formData])
if (missingFields.length > 0) {
toast.error('Please fill in all required fields')
return
}
try {
const url = editingAddress ? `/api/user/addresses/${editingAddress.id}` : '/api/user/addresses'
const method = editingAddress ? 'PUT' : 'POST'
const response = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
})
if (!response.ok) throw new Error('Failed to save address')
toast.success(editingAddress ? 'Address updated successfully' : 'Address added successfully')
setDialogOpen(false)
resetForm()
fetchAddresses()
} catch (error) {
toast.error('Failed to save address')
}
}
const handleEdit = (address: Address) => {
setEditingAddress(address)
setFormData({
firstName: address.firstName,
lastName: address.lastName,
company: address.company || '',
address1: address.address1,
address2: address.address2 || '',
city: address.city,
state: address.state,
zipCode: address.zipCode,
country: address.country,
phone: address.phone || '',
isDefault: address.isDefault,
type: address.type
})
setDialogOpen(true)
}
const handleDelete = async (addressId: string) => {
if (!confirm('Are you sure you want to delete this address?')) return
try {
const response = await fetch(`/api/user/addresses/${addressId}`, { method: 'DELETE' })
if (!response.ok) throw new Error('Failed to delete address')
toast.success('Address deleted successfully')
fetchAddresses()
} catch (error) {
toast.error('Failed to delete address')
}
}
const handleSetDefault = async (addressId: string) => {
try {
const response = await fetch(`/api/user/addresses/${addressId}/default`, { method: 'PUT' })
if (!response.ok) throw new Error('Failed to set default address')
toast.success('Default address updated')
fetchAddresses()
} catch (error) {
toast.error('Failed to update default address')
}
}
const resetForm = () => {
setFormData({
firstName: '',
lastName: '',
company: '',
address1: '',
address2: '',
city: '',
state: '',
zipCode: '',
country: 'India',
phone: '',
isDefault: false,
type: 'HOME' as 'HOME' | 'WORK' | 'OTHER'
})
setEditingAddress(null)
}
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value, type, checked } = e.target
setFormData(prev => ({
...prev,
[name]: type === 'checkbox' ? checked : value
}))
}
if (loading) {
return (
<div className="min-h-screen bg-white flex items-center justify-center">
<div className="w-16 h-16 border-4 border-gray-200 border-t-blue-500 rounded-full animate-spin"></div>
</div>
)
}
return (
<div className="min-h-screen bg-white">
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Header */}
<div className="mb-8">
<Button variant="ghost" asChild className="mb-4">
<Link href="/dashboard/profile">
<ArrowLeft className="h-4 w-4 mr-2" />
Back to Profile
</Link>
</Button>
<div className="flex items-center justify-between">
<div>
<h1 className="text-3xl font-bold text-gray-900">My Addresses</h1>
<p className="text-gray-600">Manage your shipping addresses</p>
</div>
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
<DialogTrigger asChild>
<Button onClick={resetForm}>
<Plus className="h-4 w-4 mr-2" />
Add Address
</Button>
</DialogTrigger>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>
{editingAddress ? 'Edit Address' : 'Add New Address'}
</DialogTitle>
<DialogDescription>
{editingAddress ? 'Update your address details' : 'Add a new shipping address'}
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="firstName">First Name *</Label>
<Input
id="firstName"
name="firstName"
value={formData.firstName}
onChange={handleInputChange}
required
/>
</div>
<div>
<Label htmlFor="lastName">Last Name *</Label>
<Input
id="lastName"
name="lastName"
value={formData.lastName}
onChange={handleInputChange}
required
/>
</div>
</div>
<div>
<Label htmlFor="company">Company (Optional)</Label>
<Input
id="company"
name="company"
value={formData.company}
onChange={handleInputChange}
/>
</div>
<div>
<Label htmlFor="address1">Address Line 1 *</Label>
<Input
id="address1"
name="address1"
value={formData.address1}
onChange={handleInputChange}
required
/>
</div>
<div>
<Label htmlFor="address2">Address Line 2 (Optional)</Label>
<Input
id="address2"
name="address2"
value={formData.address2}
onChange={handleInputChange}
/>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="city">City *</Label>
<Input
id="city"
name="city"
value={formData.city}
onChange={handleInputChange}
required
/>
</div>
<div>
<Label htmlFor="state">State *</Label>
<Input
id="state"
name="state"
value={formData.state}
onChange={handleInputChange}
required
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="zipCode">ZIP Code *</Label>
<Input
id="zipCode"
name="zipCode"
value={formData.zipCode}
onChange={handleInputChange}
required
/>
</div>
<div>
<Label htmlFor="phone">Phone</Label>
<Input
id="phone"
name="phone"
value={formData.phone}
onChange={handleInputChange}
/>
</div>
</div>
<div>
<Label htmlFor="type">Address Type</Label>
<Select value={formData.type} onValueChange={(value) => setFormData(prev => ({ ...prev, type: value as 'HOME' | 'WORK' | 'OTHER' }))}>
<SelectTrigger>
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="HOME">Home</SelectItem>
<SelectItem value="WORK">Work</SelectItem>
<SelectItem value="OTHER">Other</SelectItem>
</SelectContent>
</Select>
</div>
<div className="flex items-center space-x-2">
<input
type="checkbox"
id="isDefault"
name="isDefault"
checked={formData.isDefault}
onChange={handleInputChange}
className="rounded"
/>
<Label htmlFor="isDefault">Set as default address</Label>
</div>
<DialogFooter>
<Button type="button" variant="outline" onClick={() => setDialogOpen(false)}>
Cancel
</Button>
<Button type="submit">
{editingAddress ? 'Update Address' : 'Add Address'}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
</div>
</div>
{/* Addresses List */}
{addresses.length === 0 ? (
<Card>
<CardContent className="text-center py-12">
<MapPin className="h-16 w-16 text-gray-400 mx-auto mb-4" />
<h3 className="text-lg font-medium text-gray-900 mb-2">No addresses found</h3>
<p className="text-gray-500 mb-6">Add your first address to get started</p>
<Button onClick={() => setDialogOpen(true)}>
<Plus className="h-4 w-4 mr-2" />
Add Address
</Button>
</CardContent>
</Card>
) : (
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
{addresses.map((address, index) => (
<motion.div
key={address.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
>
<Card className="relative">
<CardHeader>
<div className="flex items-center justify-between">
<div className="flex items-center space-x-2">
<CardTitle className="text-lg">
{address.firstName} {address.lastName}
</CardTitle>
{address.isDefault && (
<Badge variant="default" className="bg-green-500">
<Star className="h-3 w-3 mr-1" />
Default
</Badge>
)}
</div>
<Badge variant="outline">{address.type}</Badge>
</div>
</CardHeader>
<CardContent>
<div className="space-y-2 text-sm text-gray-600">
{address.company && <p>{address.company}</p>}
<p>{address.address1}</p>
{address.address2 && <p>{address.address2}</p>}
<p>{address.city}, {address.state} {address.zipCode}</p>
<p>{address.country}</p>
{address.phone && <p>{address.phone}</p>}
</div>
<div className="flex items-center justify-between mt-4">
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => handleEdit(address)}
>
<Edit className="h-3 w-3 mr-1" />
Edit
</Button>
<Button
variant="outline"
size="sm"
onClick={() => handleDelete(address.id)}
className="text-red-600 hover:text-red-700"
>
<Trash2 className="h-3 w-3 mr-1" />
Delete
</Button>
</div>
{!address.isDefault && (
<Button
variant="ghost"
size="sm"
onClick={() => handleSetDefault(address.id)}
className="text-blue-600"
>
Set as Default
</Button>
)}
</div>
</CardContent>
</Card>
</motion.div>
))}
</div>
)}
</div>
</div>
)
}