mirror of
https://github.com/tmdinosaurcenter/gas-form.git
synced 2025-04-10 14:11:27 -06:00
Added file validation to the form - images only!
This commit is contained in:
parent
95399cf45a
commit
8adaaa72ee
@ -8,7 +8,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@
|
|||||||
import { useToast } from "@/components/ui/use-toast"
|
import { useToast } from "@/components/ui/use-toast"
|
||||||
import { getVehicles, submitGasFillup } from "@/app/actions"
|
import { getVehicles, submitGasFillup } from "@/app/actions"
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
||||||
import { FuelIcon as GasPump } from "lucide-react"
|
import { FuelIcon as GasPump, Trash2 } from "lucide-react"
|
||||||
|
|
||||||
interface Vehicle {
|
interface Vehicle {
|
||||||
id: string
|
id: string
|
||||||
@ -17,14 +17,21 @@ interface Vehicle {
|
|||||||
|
|
||||||
export default function GasFillupForm() {
|
export default function GasFillupForm() {
|
||||||
const { toast } = useToast()
|
const { toast } = useToast()
|
||||||
|
|
||||||
|
// Form state variables
|
||||||
const [vehicle, setVehicle] = useState("")
|
const [vehicle, setVehicle] = useState("")
|
||||||
const [mileage, setMileage] = useState("")
|
const [mileage, setMileage] = useState("")
|
||||||
const [fuelAmount, setFuelAmount] = useState("")
|
const [fuelAmount, setFuelAmount] = useState("")
|
||||||
const [fuelCost, setFuelCost] = useState("")
|
const [fuelCost, setFuelCost] = useState("")
|
||||||
|
const [fuelReceipt, setFuelReceipt] = useState<File | null>(null) // Image file state
|
||||||
|
const [previewURL, setPreviewURL] = useState<string | null>(null) // Image preview state
|
||||||
|
|
||||||
|
// API-related state
|
||||||
const [vehicles, setVehicles] = useState<Vehicle[]>([])
|
const [vehicles, setVehicles] = useState<Vehicle[]>([])
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
|
|
||||||
|
// Fetch vehicles from API when the component loads
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function loadVehicles() {
|
async function loadVehicles() {
|
||||||
setIsLoading(true)
|
setIsLoading(true)
|
||||||
@ -41,10 +48,37 @@ export default function GasFillupForm() {
|
|||||||
setIsLoading(false)
|
setIsLoading(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loadVehicles()
|
loadVehicles()
|
||||||
}, [toast])
|
}, [toast])
|
||||||
|
|
||||||
|
// Allowed image formats for uploads (common phone formats)
|
||||||
|
const allowedFormats = ["image/png", "image/jpeg", "image/jpg", "image/heic", "image/heif", "image/webp"]
|
||||||
|
|
||||||
|
// Handle file selection and validation
|
||||||
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
const file = e.target.files?.[0] || null
|
||||||
|
|
||||||
|
if (file && !allowedFormats.includes(file.type)) {
|
||||||
|
toast({
|
||||||
|
title: "Invalid file type",
|
||||||
|
description: "Please upload a valid image file (PNG, JPG, JPEG, HEIC, or WebP).",
|
||||||
|
variant: "destructive",
|
||||||
|
})
|
||||||
|
setFuelReceipt(null) // Reset file selection
|
||||||
|
setPreviewURL(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setFuelReceipt(file)
|
||||||
|
|
||||||
|
// Create an object URL for preview
|
||||||
|
if (file) {
|
||||||
|
const url = URL.createObjectURL(file)
|
||||||
|
setPreviewURL(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
@ -57,6 +91,7 @@ export default function GasFillupForm() {
|
|||||||
cost: Number(fuelCost),
|
cost: Number(fuelCost),
|
||||||
isFillToFull: true,
|
isFillToFull: true,
|
||||||
missedFuelUp: false,
|
missedFuelUp: false,
|
||||||
|
file: fuelReceipt, // Attach uploaded file
|
||||||
})
|
})
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
@ -64,11 +99,13 @@ export default function GasFillupForm() {
|
|||||||
description: "Gas fillup has been recorded successfully.",
|
description: "Gas fillup has been recorded successfully.",
|
||||||
})
|
})
|
||||||
|
|
||||||
// Reset form
|
// Reset form state after successful submission
|
||||||
setVehicle("")
|
setVehicle("")
|
||||||
setMileage("")
|
setMileage("")
|
||||||
setFuelAmount("")
|
setFuelAmount("")
|
||||||
setFuelCost("")
|
setFuelCost("")
|
||||||
|
setFuelReceipt(null)
|
||||||
|
setPreviewURL(null)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast({
|
toast({
|
||||||
title: "Error",
|
title: "Error",
|
||||||
@ -83,14 +120,18 @@ export default function GasFillupForm() {
|
|||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-background p-4 md:p-8">
|
<div className="min-h-screen bg-background p-4 md:p-8">
|
||||||
<Card className="max-w-md mx-auto border-primary shadow-lg">
|
<Card className="max-w-md mx-auto border-primary shadow-lg">
|
||||||
|
{/* Form Header */}
|
||||||
<CardHeader className="space-y-1 text-center bg-primary text-white rounded-t-lg">
|
<CardHeader className="space-y-1 text-center bg-primary text-white rounded-t-lg">
|
||||||
<div className="flex justify-center mb-2">
|
<div className="flex justify-center mb-2">
|
||||||
<GasPump size={32} />
|
<GasPump size={32} />
|
||||||
</div>
|
</div>
|
||||||
<CardTitle className="text-2xl font-vollkorn">Gas Fill-up Form</CardTitle>
|
<CardTitle className="text-2xl font-vollkorn">Gas Fill-up Form</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
|
{/* Form Content */}
|
||||||
<CardContent className="p-6">
|
<CardContent className="p-6">
|
||||||
<form onSubmit={handleSubmit} className="space-y-6">
|
<form onSubmit={handleSubmit} className="space-y-6">
|
||||||
|
{/* Vehicle Selection */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="vehicle" className="text-mdc-dark">Vehicle</Label>
|
<Label htmlFor="vehicle" className="text-mdc-dark">Vehicle</Label>
|
||||||
<Select value={vehicle} onValueChange={setVehicle} disabled={isLoading || isSubmitting}>
|
<Select value={vehicle} onValueChange={setVehicle} disabled={isLoading || isSubmitting}>
|
||||||
@ -105,8 +146,81 @@ export default function GasFillupForm() {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mileage, Fuel, Cost Inputs */}
|
{/* Mileage Input */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="mileage" className="text-mdc-dark">Vehicle Mileage</Label>
|
||||||
|
<Input
|
||||||
|
id="mileage"
|
||||||
|
type="number"
|
||||||
|
value={mileage}
|
||||||
|
onChange={(e) => setMileage(e.target.value)}
|
||||||
|
required
|
||||||
|
min="0"
|
||||||
|
step="1"
|
||||||
|
className="border-mdc-blue focus:ring-secondary"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Fuel Amount Input */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="fuelAmount" className="text-mdc-dark">Fuel Amount (Gallons)</Label>
|
||||||
|
<Input
|
||||||
|
id="fuelAmount"
|
||||||
|
type="number"
|
||||||
|
value={fuelAmount}
|
||||||
|
onChange={(e) => setFuelAmount(e.target.value)}
|
||||||
|
required
|
||||||
|
min="0"
|
||||||
|
step="0.001"
|
||||||
|
className="border-mdc-blue focus:ring-secondary"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Fuel Cost Input */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="fuelCost" className="text-mdc-dark">Fuel Cost ($)</Label>
|
||||||
|
<Input
|
||||||
|
id="fuelCost"
|
||||||
|
type="number"
|
||||||
|
value={fuelCost}
|
||||||
|
onChange={(e) => setFuelCost(e.target.value)}
|
||||||
|
required
|
||||||
|
min="0"
|
||||||
|
step="0.01"
|
||||||
|
className="border-mdc-blue focus:ring-secondary"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* File Upload Input */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="fuelReceipt" className="text-mdc-dark">Upload Receipt (Optional)</Label>
|
||||||
|
<Input
|
||||||
|
id="fuelReceipt"
|
||||||
|
type="file"
|
||||||
|
accept=".png, .jpg, .jpeg, .heic, .heif, .webp"
|
||||||
|
onChange={handleFileChange}
|
||||||
|
className="border-mdc-blue focus:ring-secondary"
|
||||||
|
disabled={isSubmitting}
|
||||||
|
/>
|
||||||
|
{/* Show image preview if a file is selected */}
|
||||||
|
{previewURL && (
|
||||||
|
<div className="relative mt-2">
|
||||||
|
<img src={previewURL} alt="Receipt preview" className="w-full max-h-40 object-contain rounded-md" />
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className="absolute top-2 right-2 text-white bg-red-600 p-1 rounded-full"
|
||||||
|
onClick={() => { setFuelReceipt(null); setPreviewURL(null) }}
|
||||||
|
>
|
||||||
|
<Trash2 size={16} />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Submit Button */}
|
||||||
<Button type="submit" className="w-full bg-primary hover:bg-secondary text-mdc-dark font-semibold transition-colors" disabled={isSubmitting}>
|
<Button type="submit" className="w-full bg-primary hover:bg-secondary text-mdc-dark font-semibold transition-colors" disabled={isSubmitting}>
|
||||||
{isSubmitting ? "Submitting..." : "Submit"}
|
{isSubmitting ? "Submitting..." : "Submit"}
|
||||||
</Button>
|
</Button>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user