added components

This commit is contained in:
Juthatip McDevitt 2024-03-21 21:32:34 -05:00
parent 52041ce92d
commit b1a34bfb6b
9 changed files with 569 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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,
}

View 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 }

View 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 }