added components
This commit is contained in:
parent
52041ce92d
commit
b1a34bfb6b
9 changed files with 569 additions and 0 deletions
31
weather_app/app/components/airPollution/AirPollution.tsx
Normal file
31
weather_app/app/components/airPollution/AirPollution.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
"use client"
|
||||
import { useGlobalContext } from '@/app/context/globalContext'
|
||||
import { air_QualityIndex } from '@/app/utils/Misc';
|
||||
import { Progress } from '@/components/ui/progress';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import React from 'react'
|
||||
import { FaThermometerEmpty } from "react-icons/fa";
|
||||
|
||||
const AirPollution = () => {
|
||||
const {airPollution} = useGlobalContext();
|
||||
if(!airPollution || !airPollution.list || !airPollution.list[0] || !airPollution.list[0].main){
|
||||
return <Skeleton className='h-[12rem] w-full col-span-2 md:col-span-full'/>
|
||||
}
|
||||
const airQualityIndex = airPollution.list[0].main.aqi * 10;
|
||||
console.log(airQualityIndex)
|
||||
//filter aqi
|
||||
const filterAQI = air_QualityIndex.find((item) => {
|
||||
return item.rating === airQualityIndex;
|
||||
})
|
||||
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-8 dark:bg-darkGgray shadow-sm dark:shadow-none pt-6 px-4 h-[12rem] border rounded-lg col-span-full sm-2:col-span-2 md:col-span-2 xl:col-span-2'>
|
||||
<h2 className='flex items-center gap-1 '><FaThermometerEmpty/> Air Pollution</h2>
|
||||
<Progress value={airQualityIndex} max={100} className='air-progress'/>
|
||||
<p>Air quality is {filterAQI?.desc}</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AirPollution
|
75
weather_app/app/components/daily_forcast/DailyForecast.tsx
Normal file
75
weather_app/app/components/daily_forcast/DailyForecast.tsx
Normal file
|
@ -0,0 +1,75 @@
|
|||
"use client"
|
||||
import { useGlobalContext } from '@/app/context/globalContext'
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import { CloudIcon, CloudRainIcon, SnowflakeIcon, SunIcon, Zap } from 'lucide-react';
|
||||
import React from 'react'
|
||||
import {
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
} from "@/components/ui/carousel"
|
||||
import moment from 'moment';
|
||||
import { kelvinToCelsius } from '@/app/utils/Misc';
|
||||
|
||||
const DailyForcast = () => {
|
||||
const {forecast, dailyForecast} = useGlobalContext();
|
||||
const {weather} = forecast;
|
||||
const {city, list} = dailyForecast;
|
||||
if(!dailyForecast || !city || !list){
|
||||
return <Skeleton className='h-[12rem] w-full' />
|
||||
}
|
||||
if(!forecast || !weather){
|
||||
return <Skeleton className='h-[12rem] w-full' />
|
||||
}
|
||||
const today = new Date();
|
||||
const todayString = today.toISOString().split("T")[0];
|
||||
|
||||
const todayForecast = list.filter((forecast: {dt_txt: string; main: {temp: number}}) => {
|
||||
return forecast.dt_txt.startsWith(todayString);
|
||||
})
|
||||
|
||||
const {main: weatherMain} = weather[0]
|
||||
const getIcon = () => {
|
||||
switch (weatherMain){
|
||||
case "Snow":
|
||||
return <SnowflakeIcon/>;
|
||||
case "Rain":
|
||||
return <CloudRainIcon/>;
|
||||
case "Clear":
|
||||
return <SunIcon/>;
|
||||
case "Clouds":
|
||||
return <CloudIcon/>;
|
||||
case "Thunderstorm":
|
||||
return <Zap/>;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className = 'flex flex-col justify-center gap-8 dark:bg-darkGgray shadow-sm dark:shadow-none pt-8 h-[12rem] border rounded-lg col-span-full sm-2:col-span-2 md:col-span-2 xl:col-span-2'>
|
||||
<div className='h-full flex gap-4 overflow-hidden'>
|
||||
{todayForecast.length < 1 ? (<div>Loading...</div>) :
|
||||
(
|
||||
<div className='w-full'>
|
||||
<Carousel>
|
||||
<CarouselContent>
|
||||
{
|
||||
todayForecast.map((forecast: {dt_txt: string; main: {temp: number}}) => {
|
||||
return <CarouselItem key={forecast.dt_txt} className='flex flex-col gap-4 basis-[6rem] cursor-grab items-center'>
|
||||
<p>{moment(forecast.dt_txt).format("hh:mm")}</p>
|
||||
<p>{getIcon()}</p>
|
||||
<p className='mt-4'>{kelvinToCelsius(forecast.main.temp)}°F </p>
|
||||
</CarouselItem>
|
||||
})
|
||||
}
|
||||
</CarouselContent>
|
||||
</Carousel>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default DailyForcast
|
34
weather_app/app/components/sunset/Sunset.tsx
Normal file
34
weather_app/app/components/sunset/Sunset.tsx
Normal file
|
@ -0,0 +1,34 @@
|
|||
"use client"
|
||||
import { useGlobalContext } from '@/app/context/globalContext'
|
||||
import { unixToTime } from '@/app/utils/Misc';
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import React from 'react'
|
||||
import { GiSunrise } from "react-icons/gi";
|
||||
import { GiSunset } from "react-icons/gi";
|
||||
|
||||
const Sunset = () => {
|
||||
const {forecast} = useGlobalContext();
|
||||
if(!forecast || !forecast.sys || !forecast?.sys?.sunset){
|
||||
return <Skeleton className='h-[12rem] w-full'/>
|
||||
}
|
||||
const times = forecast?.sys.sunset;
|
||||
const timezone = forecast?.timezone;
|
||||
const timeSunset = unixToTime(times, timezone)
|
||||
const timeSunrise = unixToTime(forecast?.sys?.sunrise, timezone)
|
||||
|
||||
|
||||
return (
|
||||
<div className='flex flex-col gap-5 justify-center dark:bg-darkGgray shadow-sm dark:shadow-none sm-2:col-span-2 px-5 h-[12rem] border rounded-lg'>
|
||||
<div className=''>
|
||||
<h2 className='flex items-center gap-1 '><GiSunrise/>Sunrise</h2>
|
||||
<p className='pt-2'>{timeSunrise} am</p>
|
||||
</div>
|
||||
<div className=''>
|
||||
<h2 className='flex items-center gap-1 '><GiSunset/> Sunset</h2>
|
||||
<p className='pt-2'>{timeSunset} pm</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Sunset
|
79
weather_app/app/components/temp/Temperature.tsx
Normal file
79
weather_app/app/components/temp/Temperature.tsx
Normal file
|
@ -0,0 +1,79 @@
|
|||
"use client"
|
||||
import { useGlobalContext } from '@/app/context/globalContext'
|
||||
import { kelvinToCelsius } from '@/app/utils/Misc';
|
||||
import { CloudIcon, CloudRainIcon, SnowflakeIcon, SunIcon, Zap } from 'lucide-react';
|
||||
import { IoNavigateOutline } from "react-icons/io5";
|
||||
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import moment from 'moment';
|
||||
|
||||
const Temperature = () => {
|
||||
const {forecast} = useGlobalContext();
|
||||
const {main, timezone, name, weather} = forecast;
|
||||
if(!forecast || !weather){
|
||||
return <div>Loading...</div>
|
||||
}
|
||||
const temp = kelvinToCelsius(main?.temp);
|
||||
const minTemp = kelvinToCelsius(main?.temp_min);
|
||||
const maxTemp = kelvinToCelsius(main?.temp_max);
|
||||
|
||||
//set time, day, weather
|
||||
const [localTime, setLocalTime] = useState<String>("");
|
||||
const [currentDay, setCurrentDay] = useState<String>("");
|
||||
const {main: weatherMain, description} = weather[0];
|
||||
const getIcon = () => {
|
||||
switch (weatherMain){
|
||||
case "Snow":
|
||||
return <SnowflakeIcon/>;
|
||||
case "Rain":
|
||||
return <CloudRainIcon/>;
|
||||
case "Clear":
|
||||
return <SunIcon/>;
|
||||
case "Clouds":
|
||||
return <CloudIcon/>;
|
||||
case "Thunderstorm":
|
||||
return <Zap/>;
|
||||
}
|
||||
};
|
||||
|
||||
//real time update
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const localMoment = moment().utcOffset(timezone / 60);
|
||||
const formatedTime = localMoment.format("hh:mm:ss");
|
||||
const day = localMoment.format("dddd")
|
||||
|
||||
setLocalTime(formatedTime);
|
||||
setCurrentDay(day);
|
||||
}, 1000);
|
||||
}, []);
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<div className='flex flex-col justify-between pt-6 px-4 pb-5 border rounded-lg dark:bg-darkGgray shadow-sm dark:shadow-none'>
|
||||
<p className='flex justify-between items-center'>
|
||||
<span className=''>{currentDay}</span>
|
||||
<span className=''>{localTime}</span>
|
||||
</p>
|
||||
<p className='flex pt-2 font-bold gap-1'>
|
||||
<span>{name}</span>
|
||||
<span className=''><IoNavigateOutline/></span>
|
||||
</p>
|
||||
<p className='py-10 text-8xl font-bold self-center'>{temp}°F</p>
|
||||
<div>
|
||||
<div>
|
||||
<span>{getIcon()}</span>
|
||||
<p className='pt-2 capitalize text-lg'>{description}</p>
|
||||
</div>
|
||||
<p className='flex gap-1'>
|
||||
<span>Low: {minTemp}°F</span>
|
||||
<span>Hight: {maxTemp}°F</span>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Temperature
|
12
weather_app/app/components/uvIndex/UVI.tsx
Normal file
12
weather_app/app/components/uvIndex/UVI.tsx
Normal file
|
@ -0,0 +1,12 @@
|
|||
"use client"
|
||||
import React from 'react'
|
||||
|
||||
const UVI = () => {
|
||||
return (
|
||||
<div className='flex flex-col gap-5 justify-center dark:bg-darkGgray shadow-sm dark:shadow-none sm-2:col-span-2 px-5 h-[12rem] border rounded-lg'>
|
||||
UVI
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default UVI
|
33
weather_app/app/components/wind/Wind.tsx
Normal file
33
weather_app/app/components/wind/Wind.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
"use client"
|
||||
import { useGlobalContext } from '@/app/context/globalContext'
|
||||
import { Skeleton } from '@/components/ui/skeleton';
|
||||
import React from 'react'
|
||||
import { LuWind } from "react-icons/lu";
|
||||
import { LiaCompass } from "react-icons/lia";
|
||||
|
||||
const Wind = () => {
|
||||
const {forecast} = useGlobalContext();
|
||||
const windSpeed = forecast?.wind?.speed * 2.2369;
|
||||
const windDirection = forecast?.wind?.deg;
|
||||
if(!windSpeed || !windDirection){
|
||||
return <Skeleton className='h-[12rem] w-full col-span-2 md:col-span-full'/>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex flex-col justify-center gap-5 dark:bg-darkGgray shadow-sm dark:shadow-none sm-2:col-span-2 px-4 h-[12rem] border rounded-lg'>
|
||||
<h2 className='flex items-center gap-1 '><LuWind/> Wind</h2>
|
||||
<div className='flex items-center text-center gap-2 justify-center'>
|
||||
<p className='text-7xl'><LiaCompass/></p>
|
||||
<p className='text-4xl'>{Math.round(windSpeed)}</p>
|
||||
<div className='flex flex-col justify-start text-start'>
|
||||
<p className='text-sm'>Wind</p>
|
||||
<p className='text-xs'>MPH</p>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Wind
|
262
weather_app/components/ui/carousel.tsx
Normal file
262
weather_app/components/ui/carousel.tsx
Normal file
|
@ -0,0 +1,262 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import useEmblaCarousel, {
|
||||
type UseEmblaCarouselType,
|
||||
} from "embla-carousel-react"
|
||||
import { ArrowLeft, ArrowRight } from "lucide-react"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { Button } from "@/components/ui/button"
|
||||
|
||||
type CarouselApi = UseEmblaCarouselType[1]
|
||||
type UseCarouselParameters = Parameters<typeof useEmblaCarousel>
|
||||
type CarouselOptions = UseCarouselParameters[0]
|
||||
type CarouselPlugin = UseCarouselParameters[1]
|
||||
|
||||
type CarouselProps = {
|
||||
opts?: CarouselOptions
|
||||
plugins?: CarouselPlugin
|
||||
orientation?: "horizontal" | "vertical"
|
||||
setApi?: (api: CarouselApi) => void
|
||||
}
|
||||
|
||||
type CarouselContextProps = {
|
||||
carouselRef: ReturnType<typeof useEmblaCarousel>[0]
|
||||
api: ReturnType<typeof useEmblaCarousel>[1]
|
||||
scrollPrev: () => void
|
||||
scrollNext: () => void
|
||||
canScrollPrev: boolean
|
||||
canScrollNext: boolean
|
||||
} & CarouselProps
|
||||
|
||||
const CarouselContext = React.createContext<CarouselContextProps | null>(null)
|
||||
|
||||
function useCarousel() {
|
||||
const context = React.useContext(CarouselContext)
|
||||
|
||||
if (!context) {
|
||||
throw new Error("useCarousel must be used within a <Carousel />")
|
||||
}
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
const Carousel = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement> & CarouselProps
|
||||
>(
|
||||
(
|
||||
{
|
||||
orientation = "horizontal",
|
||||
opts,
|
||||
setApi,
|
||||
plugins,
|
||||
className,
|
||||
children,
|
||||
...props
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const [carouselRef, api] = useEmblaCarousel(
|
||||
{
|
||||
...opts,
|
||||
axis: orientation === "horizontal" ? "x" : "y",
|
||||
},
|
||||
plugins
|
||||
)
|
||||
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
|
||||
const [canScrollNext, setCanScrollNext] = React.useState(false)
|
||||
|
||||
const onSelect = React.useCallback((api: CarouselApi) => {
|
||||
if (!api) {
|
||||
return
|
||||
}
|
||||
|
||||
setCanScrollPrev(api.canScrollPrev())
|
||||
setCanScrollNext(api.canScrollNext())
|
||||
}, [])
|
||||
|
||||
const scrollPrev = React.useCallback(() => {
|
||||
api?.scrollPrev()
|
||||
}, [api])
|
||||
|
||||
const scrollNext = React.useCallback(() => {
|
||||
api?.scrollNext()
|
||||
}, [api])
|
||||
|
||||
const handleKeyDown = React.useCallback(
|
||||
(event: React.KeyboardEvent<HTMLDivElement>) => {
|
||||
if (event.key === "ArrowLeft") {
|
||||
event.preventDefault()
|
||||
scrollPrev()
|
||||
} else if (event.key === "ArrowRight") {
|
||||
event.preventDefault()
|
||||
scrollNext()
|
||||
}
|
||||
},
|
||||
[scrollPrev, scrollNext]
|
||||
)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api || !setApi) {
|
||||
return
|
||||
}
|
||||
|
||||
setApi(api)
|
||||
}, [api, setApi])
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!api) {
|
||||
return
|
||||
}
|
||||
|
||||
onSelect(api)
|
||||
api.on("reInit", onSelect)
|
||||
api.on("select", onSelect)
|
||||
|
||||
return () => {
|
||||
api?.off("select", onSelect)
|
||||
}
|
||||
}, [api, onSelect])
|
||||
|
||||
return (
|
||||
<CarouselContext.Provider
|
||||
value={{
|
||||
carouselRef,
|
||||
api: api,
|
||||
opts,
|
||||
orientation:
|
||||
orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),
|
||||
scrollPrev,
|
||||
scrollNext,
|
||||
canScrollPrev,
|
||||
canScrollNext,
|
||||
}}
|
||||
>
|
||||
<div
|
||||
ref={ref}
|
||||
onKeyDownCapture={handleKeyDown}
|
||||
className={cn("relative", className)}
|
||||
role="region"
|
||||
aria-roledescription="carousel"
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</CarouselContext.Provider>
|
||||
)
|
||||
}
|
||||
)
|
||||
Carousel.displayName = "Carousel"
|
||||
|
||||
const CarouselContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { carouselRef, orientation } = useCarousel()
|
||||
|
||||
return (
|
||||
<div ref={carouselRef} className="overflow-hidden">
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex",
|
||||
orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
CarouselContent.displayName = "CarouselContent"
|
||||
|
||||
const CarouselItem = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => {
|
||||
const { orientation } = useCarousel()
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
role="group"
|
||||
aria-roledescription="slide"
|
||||
className={cn(
|
||||
"min-w-0 shrink-0 grow-0 basis-full",
|
||||
orientation === "horizontal" ? "pl-4" : "pt-4",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
})
|
||||
CarouselItem.displayName = "CarouselItem"
|
||||
|
||||
const CarouselPrevious = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<typeof Button>
|
||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||
const { orientation, scrollPrev, canScrollPrev } = useCarousel()
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute h-8 w-8 rounded-full",
|
||||
orientation === "horizontal"
|
||||
? "-left-12 top-1/2 -translate-y-1/2"
|
||||
: "-top-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className
|
||||
)}
|
||||
disabled={!canScrollPrev}
|
||||
onClick={scrollPrev}
|
||||
{...props}
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
<span className="sr-only">Previous slide</span>
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
CarouselPrevious.displayName = "CarouselPrevious"
|
||||
|
||||
const CarouselNext = React.forwardRef<
|
||||
HTMLButtonElement,
|
||||
React.ComponentProps<typeof Button>
|
||||
>(({ className, variant = "outline", size = "icon", ...props }, ref) => {
|
||||
const { orientation, scrollNext, canScrollNext } = useCarousel()
|
||||
|
||||
return (
|
||||
<Button
|
||||
ref={ref}
|
||||
variant={variant}
|
||||
size={size}
|
||||
className={cn(
|
||||
"absolute h-8 w-8 rounded-full",
|
||||
orientation === "horizontal"
|
||||
? "-right-12 top-1/2 -translate-y-1/2"
|
||||
: "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",
|
||||
className
|
||||
)}
|
||||
disabled={!canScrollNext}
|
||||
onClick={scrollNext}
|
||||
{...props}
|
||||
>
|
||||
<ArrowRight className="h-4 w-4" />
|
||||
<span className="sr-only">Next slide</span>
|
||||
</Button>
|
||||
)
|
||||
})
|
||||
CarouselNext.displayName = "CarouselNext"
|
||||
|
||||
export {
|
||||
type CarouselApi,
|
||||
Carousel,
|
||||
CarouselContent,
|
||||
CarouselItem,
|
||||
CarouselPrevious,
|
||||
CarouselNext,
|
||||
}
|
28
weather_app/components/ui/progress.tsx
Normal file
28
weather_app/components/ui/progress.tsx
Normal file
|
@ -0,0 +1,28 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as ProgressPrimitive from "@radix-ui/react-progress"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Progress = React.forwardRef<
|
||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
||||
>(({ className, value, ...props }, ref) => (
|
||||
<ProgressPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative h-2 w-full overflow-hidden rounded-full bg-secondary",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ProgressPrimitive.Indicator
|
||||
className="h-2 w-2 flex-1 bg-white transition-all shadow-lg shadow-white rounded-full ring-1 dark:ring-gray-500"
|
||||
style={{ marginLeft: `calc(${value}% - 1rem)` }}
|
||||
/>
|
||||
</ProgressPrimitive.Root>
|
||||
))
|
||||
Progress.displayName = ProgressPrimitive.Root.displayName
|
||||
|
||||
export { Progress }
|
15
weather_app/components/ui/skeleton.tsx
Normal file
15
weather_app/components/ui/skeleton.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Skeleton({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) {
|
||||
return (
|
||||
<div
|
||||
className={cn("animate-pulse rounded-md bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Skeleton }
|
Loading…
Add table
Reference in a new issue