├── .nvmrc ├── app ├── favicon.ico ├── Page.module.css ├── page.tsx └── layout.tsx ├── components ├── UI │ ├── Search │ │ └── Search.module.css │ ├── Settings │ │ └── Settings.module.css │ ├── BackToTop │ │ ├── BackToTop.module.css │ │ ├── BackToTop.test.tsx │ │ └── BackToTop.tsx │ ├── Analytics │ │ └── Analytics.tsx │ ├── Radar │ │ ├── Radar.test.tsx │ │ └── Radar.module.css │ ├── DetailsGrid │ │ ├── MoonPhase │ │ │ └── MoonPhase.tsx │ │ ├── FeelsLike │ │ │ └── FeelsLike.tsx │ │ ├── Visibility │ │ │ └── Visibility.tsx │ │ ├── SnowDepth │ │ │ ├── SnowDepth.test.tsx │ │ │ └── SnowDepth.tsx │ │ ├── CloudCover │ │ │ ├── CloudCover.tsx │ │ │ └── CloudCover.test.tsx │ │ ├── DetailCard │ │ │ ├── DetailCard.test.tsx │ │ │ └── DetailCard.tsx │ │ ├── Humidity │ │ │ └── Humidity.tsx │ │ ├── AirQuality │ │ │ └── AirQuality.tsx │ │ ├── Precipitation │ │ │ ├── Precipitation.test.tsx │ │ │ └── Precipitation.tsx │ │ └── Pressure │ │ │ └── Pressure.tsx │ ├── Satellite │ │ └── Satellite.module.css │ ├── Icon │ │ └── Icon.test.tsx │ └── CurrentConditions │ │ └── CurrentConditions.module.css └── Layout │ ├── ErrorBoundary │ └── index.ts │ ├── Header │ ├── Header.module.css │ ├── Header.tsx │ └── Header.test.tsx │ └── Footer │ └── Footer.module.css ├── .github ├── dependabot.yml ├── CODEOWNERS ├── FUNDING.yml └── workflows │ └── assertions.yml ├── prettier.config.mjs ├── .editorconfig ├── lib ├── utils │ └── index.ts ├── constants │ ├── config.ts │ ├── radar.ts │ └── index.ts ├── store │ ├── hooks.ts │ ├── StoreProvider.tsx │ ├── services │ │ └── radarApi.ts │ ├── index.ts │ └── selectors.ts └── hooks │ ├── useCloudCover.ts │ ├── useHumidity.ts │ ├── useUVIndex.ts │ ├── useFeelsLike.ts │ ├── useAirQuality.ts │ ├── useSunriseSunset.ts │ ├── useWindData.ts │ ├── useWeatherData.ts │ ├── useMoonPhase.ts │ ├── useVisibility.ts │ ├── useLastUpdated.ts │ ├── useSnowDepth.ts │ ├── useSnowDepth.test.tsx │ ├── usePressure.ts │ ├── useCurrentConditions.ts │ ├── usePrecipitation.ts │ └── useSettings.ts ├── .vscode ├── extensions.json └── settings.json ├── next-env.d.ts ├── public └── icons │ ├── moon-new.svg │ ├── not-available.svg │ ├── thermometer-mercury.svg │ ├── thermometer-mercury-cold.svg │ ├── moon-full.svg │ ├── pressure-low-alt.svg │ ├── pressure-high-alt.svg │ ├── fahrenheit.svg │ ├── hurricane.svg │ ├── moon-waxing-crescent.svg │ ├── lightning-bolt.svg │ ├── moon-first-quarter.svg │ ├── moon-waning-crescent.svg │ ├── celsius.svg │ ├── cloudy.svg │ ├── moon-waxing-gibbous.svg │ ├── raindrop.svg │ ├── code-red.svg │ ├── moon-last-quarter.svg │ ├── code-orange.svg │ ├── code-green.svg │ ├── code-yellow.svg │ ├── moon-waning-gibbous.svg │ ├── clear-night.svg │ ├── clear-day.svg │ ├── star.svg │ ├── thermometer-glass.svg │ ├── wind-beaufort-0.svg │ ├── wind.svg │ ├── raindrops.svg │ ├── barometer.svg │ ├── horizon.svg │ ├── moonrise.svg │ ├── wind-beaufort-1.svg │ ├── moonset.svg │ ├── wind-beaufort-4.svg │ ├── sunrise.svg │ ├── wind-beaufort-7.svg │ ├── sunset.svg │ ├── wind-beaufort-11.svg │ ├── flag-small-craft-advisory.svg │ ├── mist.svg │ ├── glove.svg │ ├── flag-storm-warning.svg │ ├── thunderstorms.svg │ ├── compass.svg │ ├── uv-index-1.svg │ ├── wind-beaufort-10.svg │ ├── uv-index-4.svg │ ├── uv-index.svg │ ├── wind-beaufort-2.svg │ ├── uv-index-7.svg │ ├── wind-beaufort-5.svg │ ├── uv-index-11.svg │ ├── humidity.svg │ ├── umbrella.svg │ ├── wind-beaufort-12.svg │ ├── wind-beaufort-6.svg │ ├── snowflake.svg │ ├── wind-alert.svg │ ├── wind-beaufort-9.svg │ ├── thermometer-glass-fahrenheit.svg │ ├── partly-cloudy-night.svg │ ├── uv-index-10.svg │ ├── wind-beaufort-3.svg │ ├── uv-index-2.svg │ ├── uv-index-5.svg │ ├── thermometer.svg │ ├── flag-gale-warning.svg │ ├── partly-cloudy-day.svg │ ├── fog.svg │ ├── extreme.svg │ ├── overcast.svg │ ├── uv-index-6.svg │ ├── uv-index-9.svg │ ├── solar-eclipse.svg │ ├── thermometer-glass-celsius.svg │ ├── wind-beaufort-8.svg │ ├── uv-index-3.svg │ ├── raindrop-measure.svg │ ├── beanie.svg │ ├── time-afternoon.svg │ ├── time-late-night.svg │ ├── uv-index-8.svg │ ├── flag-hurricane-warning.svg │ ├── fog-night.svg │ ├── smoke-particles.svg │ ├── rainbow.svg │ ├── fog-day.svg │ ├── time-late-morning.svg │ ├── time-morning.svg │ ├── thermometer-fahrenheit.svg │ ├── time-late-afternoon.svg │ ├── thunderstorms-night.svg │ └── thunderstorms-day.svg ├── test-utils ├── msw │ ├── server.ts │ └── handlers.ts ├── index.ts ├── render.tsx └── testStore.ts ├── lefthook.yml ├── next.config.ts ├── postcss.config.mjs ├── .prettierignore ├── vitest.config.ts ├── .gitignore ├── tsconfig.json ├── nixpacks.toml ├── sonar-project.properties ├── LICENSE ├── README.md └── eslint.config.mjs /.nvmrc: -------------------------------------------------------------------------------- 1 | 24.11 2 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gregrickaby/local-weather/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /components/UI/Search/Search.module.css: -------------------------------------------------------------------------------- 1 | .searchbar { 2 | flex: 1; 3 | min-width: 0; 4 | } 5 | -------------------------------------------------------------------------------- /components/Layout/ErrorBoundary/index.ts: -------------------------------------------------------------------------------- 1 | export {default as ErrorBoundary} from './ErrorBoundary' 2 | -------------------------------------------------------------------------------- /components/UI/Settings/Settings.module.css: -------------------------------------------------------------------------------- 1 | .settings { 2 | /* Removed fixed positioning - now inline with controls */ 3 | } 4 | -------------------------------------------------------------------------------- /components/UI/BackToTop/BackToTop.module.css: -------------------------------------------------------------------------------- 1 | .backtotop { 2 | bottom: 12px; 3 | position: fixed; 4 | right: 12px; 5 | z-index: 100; 6 | } 7 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | day: 'monday' 8 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Code Owners 2 | 3 | # 4 | 5 | - @gregrickaby 6 | -------------------------------------------------------------------------------- /prettier.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | tabWidth: 2, 3 | useTabs: false, 4 | singleQuote: true, 5 | bracketSpacing: false, 6 | semi: false, 7 | trailingComma: 'none' 8 | } 9 | export default config 10 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /lib/utils/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Re-export utility functions for convenient access 3 | */ 4 | 5 | export * from './calculations' 6 | export * from './conditions' 7 | export * from './formatting' 8 | export * from './satellite' 9 | export * from './slug' 10 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "EditorConfig.EditorConfig", 4 | "dbaeumer.vscode-eslint", 5 | "esbenp.prettier-vscode", 6 | "sonarsource.sonarlint-vscode", 7 | "GitHub.copilot", 8 | "GitHub.copilot-chat" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | import "./.next/dev/types/routes.d.ts"; 4 | 5 | // NOTE: This file should not be edited 6 | // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. 7 | -------------------------------------------------------------------------------- /public/icons/moon-new.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/not-available.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # Support this app by using the funding links below. 2 | # Developed and maintained by Greg Rickaby with the help of amazing contributors and tools. 3 | # Your support helps fund hosting, maintenance, and continued open-source development. 4 | 5 | github: gregrickaby 6 | buy_me_a_coffee: gregrickaby 7 | -------------------------------------------------------------------------------- /test-utils/msw/server.ts: -------------------------------------------------------------------------------- 1 | import {setupServer} from 'msw/node' 2 | import {handlers} from './handlers' 3 | 4 | /** 5 | * MSW server instance for Node.js test environment 6 | * 7 | * This configures MSW to intercept HTTP requests in tests. 8 | * 9 | * @see https://mswjs.io/docs/integrations/node 10 | */ 11 | export const server = setupServer(...handlers) 12 | -------------------------------------------------------------------------------- /lefthook.yml: -------------------------------------------------------------------------------- 1 | # Refer for explanation to following link: https://lefthook.dev/ 2 | 3 | pre-commit: 4 | parallel: true 5 | commands: 6 | eslint: 7 | glob: '*.{js,jsx,ts,tsx}' 8 | run: npx eslint {staged_files} --fix 9 | prettier: 10 | glob: '*.{mjs,cjs,js,jsx,ts,tsx,md,css,json,yml,yaml}' 11 | run: npx prettier {staged_files} --ignore-path .prettierignore --write 12 | -------------------------------------------------------------------------------- /public/icons/thermometer-mercury.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/thermometer-mercury-cold.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /components/Layout/Header/Header.module.css: -------------------------------------------------------------------------------- 1 | .header { 2 | margin: var(--mantine-spacing-md) 0; 3 | 4 | @media screen and (min-width: 768px) { 5 | margin: var(--mantine-spacing-xl) 0; 6 | } 7 | } 8 | 9 | .title { 10 | text-align: center; 11 | margin-bottom: var(--mantine-spacing-md); 12 | 13 | @media screen and (min-width: 768px) { 14 | text-align: left; 15 | margin-bottom: var(--mantine-spacing-xl); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /components/Layout/Header/Header.tsx: -------------------------------------------------------------------------------- 1 | import config from '@/lib/constants/config' 2 | import {Title} from '@mantine/core' 3 | import classes from './Header.module.css' 4 | 5 | /** 6 | * Header component. 7 | */ 8 | export default function Header() { 9 | return ( 10 |
11 | 12 | {config.siteName} 13 | 14 |
15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /public/icons/moon-full.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/icons/pressure-low-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type {NextConfig} from 'next' 2 | 3 | const nextConfig: NextConfig = { 4 | cacheComponents: true, 5 | experimental: { 6 | turbopackFileSystemCacheForDev: true, 7 | optimizePackageImports: [ 8 | '@mantine/core', 9 | '@mantine/hooks', 10 | '@tabler/icons-react' 11 | ] 12 | }, 13 | logging: { 14 | fetches: { 15 | fullUrl: true 16 | } 17 | } 18 | } 19 | 20 | export default nextConfig 21 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: { 3 | 'postcss-preset-mantine': { 4 | autoRem: true 5 | }, 6 | 'postcss-simple-vars': { 7 | variables: { 8 | 'mantine-breakpoint-xs': '36em', 9 | 'mantine-breakpoint-sm': '48em', 10 | 'mantine-breakpoint-md': '62em', 11 | 'mantine-breakpoint-lg': '75em', 12 | 'mantine-breakpoint-xl': '88em' 13 | } 14 | } 15 | } 16 | } 17 | export default config 18 | -------------------------------------------------------------------------------- /public/icons/pressure-high-alt.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/constants/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | siteName: 'Local Weather', 3 | siteDescription: 'View the current weather conditions', 4 | metaDescription: 5 | 'View the current weather conditions and long-range forecast for your local area.', 6 | siteUrl: 'https://weather.gregrickaby.com/', 7 | siteAuthor: 'Greg Rickaby', 8 | authorUrl: 'https://gregrickaby.com', 9 | githubUrl: 'https://github.com/gregrickaby/local-weather' 10 | } 11 | 12 | export default config 13 | -------------------------------------------------------------------------------- /lib/store/hooks.ts: -------------------------------------------------------------------------------- 1 | import {useDispatch, useSelector, useStore} from 'react-redux' 2 | import type {AppDispatch, AppStore, RootState} from './index' 3 | 4 | /** 5 | * Typed Redux hooks for use throughout the app. 6 | * 7 | * Use these instead of plain `useDispatch` and `useSelector`. 8 | */ 9 | export const useAppDispatch = useDispatch.withTypes() 10 | export const useAppSelector = useSelector.withTypes() 11 | export const useAppStore = useStore.withTypes() 12 | -------------------------------------------------------------------------------- /components/UI/Analytics/Analytics.tsx: -------------------------------------------------------------------------------- 1 | import Script from 'next/script' 2 | 3 | /** 4 | * Analytics component that conditionally loads external analytics scripts. 5 | */ 6 | export function Analytics() { 7 | const shouldLoadAnalytics = 8 | process.env.NODE_ENV === 'production' && 9 | process.env.ENABLE_ANALYTICS !== 'false' 10 | 11 | if (!shouldLoadAnalytics) { 12 | return null 13 | } 14 | 15 | return ( 16 |