mirror of
https://github.com/AnmolSaini16/mapcn
synced 2026-04-26 00:14:56 +02:00
31 lines
9.4 KiB
JSON
31 lines
9.4 KiB
JSON
{
|
|
"$schema": "https://ui.shadcn.com/schema/registry-item.json",
|
|
"name": "delivery-tracker",
|
|
"title": "Delivery Tracker",
|
|
"description": "Live order tracking with route progress, courier position, and order details.",
|
|
"dependencies": [
|
|
"lucide-react"
|
|
],
|
|
"registryDependencies": [
|
|
"map",
|
|
"card",
|
|
"badge",
|
|
"button"
|
|
],
|
|
"files": [
|
|
{
|
|
"path": "src/registry/blocks/delivery-tracker/page.tsx",
|
|
"content": "\"use client\";\n\nimport { useEffect, useMemo, useState } from \"react\";\nimport { Clock3, Utensils, Truck, UserRound } from \"lucide-react\";\n\nimport {\n Map,\n MapMarker,\n MapRoute,\n MarkerContent,\n MarkerTooltip,\n} from \"@/registry/map\";\nimport { Badge } from \"@/components/ui/badge\";\nimport { Button } from \"@/components/ui/button\";\nimport { Card, CardContent, CardHeader, CardTitle } from \"@/components/ui/card\";\n\ninterface DeliveryMeal {\n name: string;\n price: string;\n quantity: number;\n}\n\ninterface OsrmRouteData {\n coordinates: [number, number][];\n duration: number;\n distance: number;\n}\n\nconst deliveryMeals: DeliveryMeal[] = [\n {\n name: \"Spicy Tofu Grain Bowl\",\n price: \"$44.00\",\n quantity: 1,\n },\n {\n name: \"Herb Chicken Rice Box\",\n price: \"$58.00\",\n quantity: 2,\n },\n {\n name: \"Roasted Veggie Wrap\",\n price: \"$29.00\",\n quantity: 1,\n },\n];\n\nconst pickup = { lng: -122.466, lat: 37.716 };\nconst dropoff = { lng: -122.399, lat: 37.683 };\n\nfunction formatDistance(meters?: number) {\n if (!meters) return \"--\";\n if (meters < 1000) return `${Math.round(meters)} m`;\n return `${(meters / 1000).toFixed(1)} km`;\n}\n\nfunction formatDuration(seconds?: number) {\n if (!seconds) return \"--\";\n const minutes = Math.round(seconds / 60);\n if (minutes < 60) return `${minutes} min`;\n const hours = Math.floor(minutes / 60);\n const remainingMinutes = minutes % 60;\n return `${hours}h ${remainingMinutes}m`;\n}\n\nexport function DeliveryBlock() {\n const [routeData, setRouteData] = useState<OsrmRouteData | null>(null);\n const [loading, setLoading] = useState(true);\n\n useEffect(() => {\n async function fetchRoute() {\n setLoading(true);\n try {\n const response = await fetch(\n `https://router.project-osrm.org/route/v1/driving/${pickup.lng},${pickup.lat};${dropoff.lng},${dropoff.lat}?overview=full&geometries=geojson`\n );\n const data = await response.json();\n const route = data?.routes?.[0];\n if (!route?.geometry?.coordinates) return;\n\n setRouteData({\n coordinates: route.geometry.coordinates as [number, number][],\n duration: route.duration as number,\n distance: route.distance as number,\n });\n } catch (error) {\n console.error(\"Failed to fetch route:\", error);\n } finally {\n setLoading(false);\n }\n }\n\n fetchRoute();\n }, []);\n\n const progressCoordinates = useMemo(() => {\n const progressCount = Math.max(\n 2,\n Math.floor(\n (routeData?.coordinates?.length ?? 0) * (routeData ? 0.62 : 0.66)\n )\n );\n return routeData?.coordinates?.slice(0, progressCount) ?? [];\n }, [routeData]);\n\n const courierPosition = progressCoordinates[progressCoordinates.length - 1];\n\n return (\n <div className=\"p-8\">\n <div className=\"grid md:grid-cols-[1.05fr_1fr] bg-sidebar rounded-lg border md:h-[600px] max-w-7xl mx-auto\">\n <div className=\"p-5 md:p-6 flex flex-col\">\n <div className=\"space-y-1\">\n <h3 className=\"text-2xl font-semibold tracking-tight\">\n Track Delivery\n </h3>\n <p className=\"text-sm text-muted-foreground\">Mon Feb 10 - 2-3 PM</p>\n </div>\n\n <Card className=\"mt-5\">\n <CardHeader>\n <CardTitle className=\"font-medium\">\n Order items ({deliveryMeals.length})\n </CardTitle>\n </CardHeader>\n <CardContent className=\"space-y-5\">\n {deliveryMeals.map((meal) => (\n <div key={meal.name} className=\"flex items-center gap-3\">\n <div className=\"grid size-8 place-items-center rounded-full bg-muted text-xs font-medium\">\n <Utensils className=\"size-4 text-muted-foreground\" />\n </div>\n <div className=\"min-w-4 flex-1\">\n <p className=\"truncate text-sm font-medium pb-1\">\n {meal.name}\n </p>\n <p className=\"text-xs text-muted-foreground\">\n {meal.price}\n </p>\n </div>\n <Badge\n variant=\"secondary\"\n className=\"h-6 rounded-full px-2.5\"\n >\n x{meal.quantity}\n </Badge>\n </div>\n ))}\n <div className=\"flex items-center justify-between border-t border-border/60 pt-3 text-sm\">\n <span className=\"text-muted-foreground\">Bundle total</span>\n <span className=\"font-medium\">$189.00</span>\n </div>\n </CardContent>\n </Card>\n\n <div className=\"mt-4 grid gap-3 sm:grid-cols-2\">\n <Card>\n <CardContent className=\"space-y-2\">\n <p className=\"text-sm font-medium text-muted-foreground\">\n Pickup confirmed\n </p>\n <p className=\"text-sm font-medium\">Mon, Feb 10 at 1:48 PM</p>\n </CardContent>\n </Card>\n <Card>\n <CardContent className=\"space-y-2\">\n <p className=\"text-sm font-medium text-muted-foreground\">\n Remaining travel\n </p>\n <p className=\"text-sm font-medium\">\n {formatDuration(routeData?.duration)}\n </p>\n </CardContent>\n </Card>\n </div>\n\n <div className=\"mt-6 flex flex-wrap items-center gap-2\">\n <Button size=\"sm\" className=\"gap-1.5\">\n <Clock3 className=\"size-4\" />\n View timeline\n </Button>\n <Button variant=\"outline\" size=\"sm\" className=\"gap-1.5\">\n <UserRound className=\"size-4\" />\n Contact courier\n </Button>\n </div>\n </div>\n\n <div className=\"relative h-[400px] overflow-hidden rounded-xl md:h-full shadow-sm\">\n <Map\n loading={loading}\n center={[-122.435, 37.696]}\n zoom={12}\n minZoom={10}\n maxZoom={16}\n styles={{\n light: \"https://tiles.openfreemap.org/styles/bright\",\n dark: \"https://tiles.openfreemap.org/styles/dark\",\n }}\n >\n <MapRoute\n id=\"delivery-full-route\"\n coordinates={routeData?.coordinates ?? []}\n color=\"#5b6572\"\n width={5.2}\n opacity={0.3}\n interactive={false}\n />\n <MapRoute\n id=\"delivery-progress-route\"\n coordinates={progressCoordinates}\n color=\"#3b82f6\"\n width={6}\n opacity={0.95}\n interactive={false}\n />\n\n {courierPosition && (\n <MapMarker\n longitude={courierPosition[0]}\n latitude={courierPosition[1]}\n offset={[0, 10]}\n >\n <MarkerContent>\n <div className=\"relative grid size-9 place-items-center rounded-full bg-emerald-500 dark:bg-emerald-600\">\n <Truck className=\"size-4 text-white\" />\n </div>\n </MarkerContent>\n <MarkerTooltip>\n <div className=\"space-y-0.5 text-xs\">\n <p className=\"font-medium\">\n Order {formatDuration(routeData?.duration)} away\n </p>\n <p className=\"text-muted-foreground\">\n Route {formatDistance(routeData?.distance)}\n </p>\n </div>\n </MarkerTooltip>\n </MapMarker>\n )}\n\n <MapMarker longitude={pickup.lng} latitude={pickup.lat}>\n <MarkerContent>\n <div className=\"size-4 rounded-full border-2 border-white bg-emerald-500 shadow-sm\" />\n </MarkerContent>\n <MarkerTooltip>\n <p className=\"text-xs font-medium\">Origin</p>\n </MarkerTooltip>\n </MapMarker>\n\n <MapMarker longitude={dropoff.lng} latitude={dropoff.lat}>\n <MarkerContent>\n <div className=\"size-4 rounded-full border-2 border-white bg-rose-500 shadow-sm\" />\n </MarkerContent>\n <MarkerTooltip>\n <p className=\"text-xs font-medium\">Destination</p>\n </MarkerTooltip>\n </MapMarker>\n </Map>\n </div>\n </div>\n </div>\n );\n}\n",
|
|
"type": "registry:page",
|
|
"target": "app/delivery/page.tsx"
|
|
}
|
|
],
|
|
"meta": {
|
|
"iframeHeight": "680px"
|
|
},
|
|
"categories": [
|
|
"tracking",
|
|
"delivery"
|
|
],
|
|
"type": "registry:block"
|
|
} |