Skip to content

Commit

Permalink
feat: remove prop drill, add messages (#647)
Browse files Browse the repository at this point in the history
  • Loading branch information
gregkonush authored Dec 24, 2024
1 parent a28e8b1 commit f3493d2
Show file tree
Hide file tree
Showing 12 changed files with 247 additions and 53 deletions.
21 changes: 21 additions & 0 deletions apps/findbobastore/components.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "src/app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils",
"ui": "@/components/ui",
"lib": "@/lib",
"hooks": "@/hooks"
},
"iconLibrary": "lucide"
}
5 changes: 4 additions & 1 deletion apps/findbobastore/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,17 @@
"lint": "next lint"
},
"dependencies": {
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"framer-motion": "^11.15.0",
"lucide-react": "^0.469.0",
"mapbox-gl": "^3.9.1",
"next": "15.1.2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-map-gl": "^7.1.8",
"tailwind-merge": "^2.5.5"
"tailwind-merge": "^2.5.5",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
Expand Down
71 changes: 59 additions & 12 deletions apps/findbobastore/src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,67 @@
@tailwind components;
@tailwind utilities;

:root {
--background: #ffffff;
--foreground: #171717;
}

@media (prefers-color-scheme: dark) {
@layer base {
:root {
--background: #0a0a0a;
--foreground: #ededed;
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 222.2 84% 4.9%;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 210 40% 98%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 212.7 26.8% 83.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}

body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
24 changes: 5 additions & 19 deletions apps/findbobastore/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
import type { Metadata } from 'next'
import { Geist, Geist_Mono } from 'next/font/google'
import { inter } from '@/lib/fonts'
import './globals.css'

const geistSans = Geist({
variable: '--font-geist-sans',
subsets: ['latin'],
})

const geistMono = Geist_Mono({
variable: '--font-geist-mono',
subsets: ['latin'],
})

export const metadata: Metadata = {
title: 'Find Boba Store',
description: 'Find Boba Store',
description: 'Find the best boba stores near you',
}

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body className={`${geistSans.variable} ${geistMono.variable} antialiased`}>{children}</body>
<html lang="en" className={inter.variable}>
<body className="font-sans antialiased">{children}</body>
</html>
)
}
5 changes: 2 additions & 3 deletions apps/findbobastore/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { MapView } from '@/components/map-view'

export default async function Home() {
const mapboxToken = process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN
export default function Home() {
return (
<main className="relative w-full h-screen">
<div className="absolute top-4 left-4 z-10 bg-slate-900/50 px-4 py-2 rounded-lg">
<h1 className="text-xl font-bold text-white">Find Boba Store</h1>
</div>
<MapView token={mapboxToken} />
<MapView />
</main>
)
}
20 changes: 20 additions & 0 deletions apps/findbobastore/src/components/error-message.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { AlertCircle } from 'lucide-react'
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert'
import { cn } from '@/lib/utils'
import type { ReactNode } from 'react'

interface ErrorMessageProps {
title: string
description?: ReactNode
className?: string
}

export function ErrorMessage({ title, description, className }: ErrorMessageProps) {
return (
<Alert variant="destructive" className={cn('border-red-900/50 bg-red-900/10', className)}>
<AlertCircle className="h-4 w-4" />
<AlertTitle>{title}</AlertTitle>
{description && <AlertDescription>{description}</AlertDescription>}
</Alert>
)
}
43 changes: 30 additions & 13 deletions apps/findbobastore/src/components/map-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import ReactMapGL, { Marker, NavigationControl, GeolocateControl } from 'react-m
import { motion, AnimatePresence } from 'framer-motion'
import { cn } from '@/lib/utils'
import { stores, type BobaStore } from '@/data/stores'
import { ErrorMessage } from '@/components/error-message'
import 'mapbox-gl/dist/mapbox-gl.css'

function MapOverlay({ store }: { store: BobaStore | null }) {
Expand Down Expand Up @@ -33,14 +34,15 @@ function MapOverlay({ store }: { store: BobaStore | null }) {
)
}

export const MapView = memo(function MapView({ token }: { token: string | undefined }) {
export const MapView = memo(function MapView() {
const [selectedStore, setSelectedStore] = useState<BobaStore | null>(null)
const [viewState, setViewState] = useState({
longitude: -122.4194,
latitude: 37.7749,
zoom: 12,
})
const [locationError, setLocationError] = useState<string | null>(null)
const [mapError, setMapError] = useState<{ message: string; details?: string } | null>(null)

const getUserLocation = useCallback(() => {
if (!navigator.geolocation) {
Expand All @@ -67,28 +69,26 @@ export const MapView = memo(function MapView({ token }: { token: string | undefi
)
}, [])

if (!token) {
return (
<div className="flex items-center justify-center w-full h-full bg-slate-900 text-slate-100">
<div className="p-4 bg-slate-800 rounded-lg">
<p className="text-lg">Mapbox token is missing</p>
</div>
</div>
)
}

return (
<div className="relative w-full h-full">
<ReactMapGL
{...viewState}
onMove={(evt) => setViewState(evt.viewState)}
mapboxAccessToken={token}
mapboxAccessToken={process.env.NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN}
style={{ width: '100%', height: '100%' }}
mapStyle="mapbox://styles/mapbox/dark-v11"
maxZoom={15}
onLoad={() => {
getUserLocation()
}}
onError={(evt) => {
const errorDetails = evt?.error?.message || JSON.stringify(evt, null, 2)
console.error('Map error:', errorDetails)
setMapError({
message: 'Failed to load map',
details: errorDetails,
})
}}
>
<NavigationControl />
<GeolocateControl positionOptions={{ enableHighAccuracy: true }} trackUserLocation />
Expand Down Expand Up @@ -124,7 +124,24 @@ export const MapView = memo(function MapView({ token }: { token: string | undefi
)}
</ReactMapGL>
<MapOverlay store={selectedStore} />
{locationError && <div className="absolute top-4 left-4 p-4 bg-slate-900/90 rounded-lg text-slate-100 text-sm">{locationError}</div>}
{locationError && (
<div className="absolute top-4 right-4 z-10">
<ErrorMessage title="Location Error" description={locationError} />
</div>
)}
{mapError && (
<div className="absolute top-4 right-4 z-10">
<ErrorMessage
title="Map Error"
description={
<div className="space-y-2">
<p>{mapError.message}</p>
{mapError.details && <pre className="text-xs bg-slate-900/50 p-2 rounded overflow-auto max-h-32">{mapError.details}</pre>}
</div>
}
/>
</div>
)}
</div>
)
})
38 changes: 38 additions & 0 deletions apps/findbobastore/src/components/ui/alert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import * as React from 'react'
import { cva, type VariantProps } from 'class-variance-authority'

import { cn } from '@/lib/utils'

const alertVariants = cva(
'relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7',
{
variants: {
variant: {
default: 'bg-background text-foreground',
destructive: 'border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive',
},
},
defaultVariants: {
variant: 'default',
},
},
)

const Alert = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>>(
({ className, variant, ...props }, ref) => (
<div ref={ref} role="alert" className={cn(alertVariants({ variant }), className)} {...props} />
),
)
Alert.displayName = 'Alert'

const AlertTitle = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLHeadingElement>>(({ className, ...props }, ref) => (
<h5 ref={ref} className={cn('mb-1 font-medium leading-none tracking-tight', className)} {...props} />
))
AlertTitle.displayName = 'AlertTitle'

const AlertDescription = React.forwardRef<HTMLParagraphElement, React.HTMLAttributes<HTMLParagraphElement>>(
({ className, ...props }, ref) => <div ref={ref} className={cn('text-sm [&_p]:leading-relaxed', className)} {...props} />,
)
AlertDescription.displayName = 'AlertDescription'

export { Alert, AlertTitle, AlertDescription }
7 changes: 7 additions & 0 deletions apps/findbobastore/src/lib/fonts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Inter } from 'next/font/google'

export const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
})
4 changes: 2 additions & 2 deletions apps/findbobastore/src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { type ClassValue, clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
Expand Down
Loading

0 comments on commit f3493d2

Please sign in to comment.