├── desk-weather.png ├── mobile-weather.png ├── src ├── app │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ └── page.tsx ├── components │ ├── ClientProvider.tsx │ ├── Footer.tsx │ ├── WorldMap.tsx │ ├── LottieIcon.tsx │ ├── CurrentWeatherCard.tsx │ ├── ForecastCards.tsx │ ├── CityList.tsx │ ├── WeatherSearch.tsx │ ├── Sidebar.tsx │ └── TehranWeather.tsx ├── utils │ ├── axiosInstance.ts │ └── iconMapper.ts └── styles │ └── fonts.css ├── tablet.weather.png ├── public ├── lottie │ ├── mist.webm │ ├── rain.webm │ ├── snow.webm │ ├── sunny.webm │ ├── cloudy.webm │ ├── clear-night.webm │ ├── thunderstorm.webm │ └── partly-cloudy-day.webm ├── fonts │ ├── Vazir-FD-WOL.eot │ ├── Vazir-FD-WOL.ttf │ ├── Vazir-FD-WOL.woff │ ├── Vazir-FD-WOL.woff2 │ ├── Vazir-Bold-FD-WOL.eot │ ├── Vazir-Bold-FD-WOL.ttf │ ├── Vazir-Thin-FD-WOL.eot │ ├── Vazir-Thin-FD-WOL.ttf │ ├── Vazir-Bold-FD-WOL.woff │ ├── Vazir-Bold-FD-WOL.woff2 │ ├── Vazir-Light-FD-WOL.eot │ ├── Vazir-Light-FD-WOL.ttf │ ├── Vazir-Light-FD-WOL.woff │ ├── Vazir-Medium-FD-WOL.eot │ ├── Vazir-Medium-FD-WOL.ttf │ ├── Vazir-Thin-FD-WOL.woff │ ├── Vazir-Thin-FD-WOL.woff2 │ ├── Vazir-Light-FD-WOL.woff2 │ ├── Vazir-Medium-FD-WOL.woff │ └── Vazir-Medium-FD-WOL.woff2 └── images │ └── world.svg ├── next.config.mjs ├── postcss.config.mjs ├── .github ├── ISSUE_TEMPLATE │ ├── custom.md │ ├── feature_request.md │ └── bug_report.md └── PULL_REQUEST_TEMPLATE.md ├── redux ├── hooks.ts ├── slices │ ├── types │ │ ├── tehranWeatherTypes.ts │ │ ├── currentWeatheerTypes.ts │ │ ├── cityWeatherTypes.ts │ │ └── forecastTypes.ts │ ├── services │ │ └── weatherApi.ts │ ├── WeatherSlice.ts │ ├── currentWeatherSlice.ts │ ├── forecastSlice.ts │ ├── tehranWeatherSlice.ts │ └── cityWeatherSlice.ts └── store.ts ├── .gitignore ├── tailwind.config.ts ├── SECURITY.md ├── tsconfig.json ├── package.json ├── LICENSE ├── CONTRIBUTING.md ├── README.md └── CODE_OF_CONDUCT.md /desk-weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/desk-weather.png -------------------------------------------------------------------------------- /mobile-weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/mobile-weather.png -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /tablet.weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/tablet.weather.png -------------------------------------------------------------------------------- /public/lottie/mist.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/lottie/mist.webm -------------------------------------------------------------------------------- /public/lottie/rain.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/lottie/rain.webm -------------------------------------------------------------------------------- /public/lottie/snow.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/lottie/snow.webm -------------------------------------------------------------------------------- /public/lottie/sunny.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/lottie/sunny.webm -------------------------------------------------------------------------------- /public/lottie/cloudy.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/lottie/cloudy.webm -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {}; 3 | 4 | export default nextConfig; 5 | -------------------------------------------------------------------------------- /public/fonts/Vazir-FD-WOL.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-FD-WOL.eot -------------------------------------------------------------------------------- /public/fonts/Vazir-FD-WOL.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-FD-WOL.ttf -------------------------------------------------------------------------------- /public/fonts/Vazir-FD-WOL.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-FD-WOL.woff -------------------------------------------------------------------------------- /public/fonts/Vazir-FD-WOL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-FD-WOL.woff2 -------------------------------------------------------------------------------- /public/lottie/clear-night.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/lottie/clear-night.webm -------------------------------------------------------------------------------- /public/lottie/thunderstorm.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/lottie/thunderstorm.webm -------------------------------------------------------------------------------- /public/fonts/Vazir-Bold-FD-WOL.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Bold-FD-WOL.eot -------------------------------------------------------------------------------- /public/fonts/Vazir-Bold-FD-WOL.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Bold-FD-WOL.ttf -------------------------------------------------------------------------------- /public/fonts/Vazir-Thin-FD-WOL.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Thin-FD-WOL.eot -------------------------------------------------------------------------------- /public/fonts/Vazir-Thin-FD-WOL.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Thin-FD-WOL.ttf -------------------------------------------------------------------------------- /public/fonts/Vazir-Bold-FD-WOL.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Bold-FD-WOL.woff -------------------------------------------------------------------------------- /public/fonts/Vazir-Bold-FD-WOL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Bold-FD-WOL.woff2 -------------------------------------------------------------------------------- /public/fonts/Vazir-Light-FD-WOL.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Light-FD-WOL.eot -------------------------------------------------------------------------------- /public/fonts/Vazir-Light-FD-WOL.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Light-FD-WOL.ttf -------------------------------------------------------------------------------- /public/fonts/Vazir-Light-FD-WOL.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Light-FD-WOL.woff -------------------------------------------------------------------------------- /public/fonts/Vazir-Medium-FD-WOL.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Medium-FD-WOL.eot -------------------------------------------------------------------------------- /public/fonts/Vazir-Medium-FD-WOL.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Medium-FD-WOL.ttf -------------------------------------------------------------------------------- /public/fonts/Vazir-Thin-FD-WOL.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Thin-FD-WOL.woff -------------------------------------------------------------------------------- /public/fonts/Vazir-Thin-FD-WOL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Thin-FD-WOL.woff2 -------------------------------------------------------------------------------- /public/lottie/partly-cloudy-day.webm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/lottie/partly-cloudy-day.webm -------------------------------------------------------------------------------- /public/fonts/Vazir-Light-FD-WOL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Light-FD-WOL.woff2 -------------------------------------------------------------------------------- /public/fonts/Vazir-Medium-FD-WOL.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Medium-FD-WOL.woff -------------------------------------------------------------------------------- /public/fonts/Vazir-Medium-FD-WOL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/frau-azadeh/dashboard-weather/HEAD/public/fonts/Vazir-Medium-FD-WOL.woff2 -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/components/ClientProvider.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { Provider } from 'react-redux'; 4 | import { store } from '../../redux/store'; 5 | 6 | export default function ClientProvider({ children }: { children: React.ReactNode }) { 7 | return {children}; 8 | } 9 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | export default function Footer() { 2 | return ( 3 | 8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/utils/axiosInstance.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | const axiosInstance = axios.create({ 4 | baseURL: 'https://api.openweathermap.org/data/2.5/', 5 | params: { 6 | appid: '77055f3836356621dc9baee18992c5db', 7 | units: 'metric', 8 | }, 9 | }); 10 | 11 | export default axiosInstance; -------------------------------------------------------------------------------- /redux/hooks.ts: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'; 3 | import type { RootState, AppDispatch } from './store'; 4 | 5 | export const useAppDispatch: () => AppDispatch = useDispatch; 6 | export const useAppSelector: TypedUseSelectorHook = useSelector; -------------------------------------------------------------------------------- /redux/slices/types/tehranWeatherTypes.ts: -------------------------------------------------------------------------------- 1 | export interface WeatherData { 2 | dt: number; 3 | temp: number; 4 | icon: string; 5 | description: string; 6 | } 7 | 8 | export interface TehranWeatherState { 9 | forecast: WeatherData[]; 10 | currentWeather: WeatherData | null; 11 | status: 'idle' | 'loading' | 'succeeded' | 'failed'; 12 | } -------------------------------------------------------------------------------- /src/components/WorldMap.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | const WorldMap = () => { 4 | return ( 5 |
6 |
7 | world 8 |
9 |
10 | ) 11 | } 12 | 13 | export default WorldMap; 14 | -------------------------------------------------------------------------------- /redux/slices/types/currentWeatheerTypes.ts: -------------------------------------------------------------------------------- 1 | export interface CurrentWeather { 2 | name: string; 3 | main: { 4 | temp: number; 5 | humidity: number; 6 | }; 7 | weather: { 8 | description: string; 9 | icon: string; 10 | }[]; 11 | } 12 | 13 | export interface CurrentWeatherState { 14 | data: CurrentWeather | null; 15 | status: 'idle' | 'loading' | 'succeeded' | 'failed'; 16 | error: string | null; 17 | } -------------------------------------------------------------------------------- /redux/slices/types/cityWeatherTypes.ts: -------------------------------------------------------------------------------- 1 | export interface CityWeather { 2 | name: string; 3 | weather: { 4 | description: string; 5 | icon: string; 6 | }[]; 7 | main: { 8 | temp: number; 9 | humidity: number; 10 | }; 11 | } 12 | 13 | export interface CityWeatherState { 14 | cities: CityWeather[]; 15 | defaultCities: string[]; 16 | status: 'idle' | 'loading' | 'succeeded' | 'failed'; 17 | error: string | null; 18 | } -------------------------------------------------------------------------------- /redux/slices/types/forecastTypes.ts: -------------------------------------------------------------------------------- 1 | export interface ForecastItem { 2 | dt_txt: string; 3 | main: { 4 | temp: number; 5 | humidity: number; 6 | }; 7 | weather: { 8 | description: string; 9 | icon: string; 10 | }[]; 11 | } 12 | 13 | export interface ForecastResponse { 14 | list: ForecastItem[]; 15 | } 16 | 17 | export interface ForecastState { 18 | data: ForecastResponse | null; 19 | status: 'idle' | 'loading' | 'succeeded' | 'failed'; 20 | error: string | null; 21 | } -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please provide a brief description of the changes you made. 4 | 5 | ## Related Issue 6 | 7 | If this pull request is related to an issue, please link it here. 8 | 9 | ## Checklist 10 | 11 | - [ ] My code follows the style guidelines of this project. 12 | - [ ] I have performed a self-review of my code. 13 | - [ ] I have commented my code, particularly in hard-to-understand areas. 14 | - [ ] I have made corresponding changes to the documentation. 15 | - [ ] My changes generate no new warnings. 16 | -------------------------------------------------------------------------------- /src/components/LottieIcon.tsx: -------------------------------------------------------------------------------- 1 | interface LottieIconProps { 2 | icon: string; 3 | } 4 | 5 | const LottieIcon: React.FC = ({ icon }) => { 6 | return ( 7 | 17 | ); 18 | }; 19 | 20 | export default LottieIcon; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /src/styles/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Vazir'; 3 | src: url('../../public/fonts/Vazir-Bold-FD-WOL.woff2') format('woff2'); 4 | font-weight: 400; 5 | font-style: normal; 6 | } 7 | 8 | @font-face { 9 | font-family: 'Vazir'; 10 | src: url('../../public/fonts/Vazir-Bold-FD-WOL.woff2') format('woff2'); 11 | font-weight: 700; 12 | font-style: normal; 13 | } 14 | 15 | @font-face { 16 | font-family: 'Vazir'; 17 | src: url('../../public/fonts/Vazir-Bold-FD-WOL.woff2') format('woff2'); 18 | font-weight: 300; 19 | font-style: normal; 20 | } -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | @import 'leaflet/dist/leaflet.css'; 5 | :root { 6 | --background: #ffffff; 7 | --foreground: #171717; 8 | } 9 | 10 | @media (prefers-color-scheme: dark) { 11 | :root { 12 | --background: #0a0a0a; 13 | --foreground: #ededed; 14 | } 15 | } 16 | html{ 17 | background-color: #0f172a; 18 | color: #ffffff; 19 | } 20 | body { 21 | color: var(--foreground); 22 | background: var(--background); 23 | } 24 | 25 | @layer utilities { 26 | .text-balance { 27 | text-wrap: balance; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | colors: { 12 | primary:'#0f172a', 13 | secondary:'#1e293b', 14 | accent:'#facc15', 15 | textMain: '#ffffff', 16 | textSecondary: '#94a3b8', 17 | cardBg: '#2d3748' 18 | }, 19 | fontFamily:{ 20 | vazir:['Vazir'], 21 | } 22 | }, 23 | }, 24 | plugins: [], 25 | }; 26 | export default config; 27 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | 2 | import "./globals.css"; 3 | import "../styles/fonts.css"; 4 | import type { Metadata } from "next"; 5 | import { ReactNode } from "react"; 6 | import ClientProvider from '../components/ClientProvider'; 7 | 8 | 9 | export const metadata: Metadata = { 10 | title: "پیش بینی وضعیت آب و هوایی", 11 | description: "شما میتوانید وضعیت آب و هوایی را مشاهده نمایید.", 12 | }; 13 | 14 | export default function RootLayout({ children }: { children: ReactNode }) { 15 | 16 | return ( 17 | 18 | 19 | {children} 20 | 21 | 22 | ); 23 | } 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /src/utils/iconMapper.ts: -------------------------------------------------------------------------------- 1 | const weatherToLottieMap: Record = { 2 | '01d': 'sunny', 3 | '01n': 'clear-night', 4 | '02d': 'partly-cloudy-day', 5 | '02n': 'partly-cloudy-day', 6 | '03d': 'cloudy', 7 | '03n': 'cloudy', 8 | '04d': 'cloudy', 9 | '04n': 'cloudy', 10 | '09d': 'rain', 11 | '09n': 'rain', 12 | '10d': 'rain', 13 | '10n': 'rain', 14 | '11d': 'thunderstorm', 15 | '11n': 'thunderstorm', 16 | '13d': 'snow', 17 | '13n': 'snow', 18 | '50d': 'mist', 19 | '50n': 'mist', 20 | }; 21 | 22 | export const getLottieIcon = (weatherCode: string): string => { 23 | return weatherToLottieMap[weatherCode] || 'cloudy'; 24 | }; 25 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "baseUrl": "src", 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /redux/slices/services/weatherApi.ts: -------------------------------------------------------------------------------- 1 | import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; 2 | 3 | export const weatherApi = createApi({ 4 | reducerPath: 'weatherApi', 5 | baseQuery: fetchBaseQuery({ baseUrl: 'https://api.openweathermap.org/data/2.5/' }), 6 | endpoints: (builder) => ({ 7 | getCityWeather: builder.query({ 8 | query: (city: string) => `weather?q=${city}&appid=77055f3836356621dc9baee18992c5db&units=metric`, 9 | }), 10 | getForecast: builder.query({ 11 | query: (city: string) => `forecast?q=${city}&appid=77055f3836356621dc9baee18992c5db&units=metric`, 12 | }), 13 | getTehranWeather: builder.query({ 14 | query: () => `forecast?q=Tehran&appid=77055f3836356621dc9baee18992c5db&units=metric`, 15 | }), 16 | }), 17 | }); 18 | 19 | export const { useGetCityWeatherQuery, useGetForecastQuery, useGetTehranWeatherQuery } = 20 | weatherApi; -------------------------------------------------------------------------------- /redux/store.ts: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import { weatherApi } from './slices/services/weatherApi'; 3 | import cityWeatherReducer from './slices/cityWeatherSlice'; 4 | import currentWeatherReducer from './slices/currentWeatherSlice'; 5 | import forecastReducer from './slices/forecastSlice'; 6 | import tehranWeatherReducer from './slices/tehranWeatherSlice'; 7 | 8 | export const store = configureStore({ 9 | reducer: { 10 | cityWeather: cityWeatherReducer, 11 | currentWeather: currentWeatherReducer, 12 | forecast: forecastReducer, 13 | tehranWeather: tehranWeatherReducer, 14 | [weatherApi.reducerPath]: weatherApi.reducer, 15 | }, 16 | middleware: (getDefaultMiddleware) => 17 | getDefaultMiddleware().concat(weatherApi.middleware), 18 | }); 19 | 20 | export type RootState = ReturnType; 21 | export type AppDispatch = typeof store.dispatch; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dashboard-weather", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@reduxjs/toolkit": "^2.3.0", 13 | "@types/leaflet": "^1.9.14", 14 | "axios": "^1.7.8", 15 | "chart.js": "^4.4.6", 16 | "framer-motion": "^11.11.17", 17 | "leaflet": "^1.9.4", 18 | "next": "14.2.16", 19 | "react": "^18", 20 | "react-chartjs-2": "^5.2.0", 21 | "react-dom": "^18", 22 | "react-icons": "^5.3.0", 23 | "react-leaflet": "^4.2.1", 24 | "react-redux": "^9.1.2" 25 | }, 26 | "devDependencies": { 27 | "@types/geojson": "^7946.0.14", 28 | "@types/node": "^20", 29 | "@types/react": "^18", 30 | "@types/react-dom": "^18", 31 | "postcss": "^8", 32 | "tailwindcss": "^3.4.1", 33 | "typescript": "^5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/CurrentWeatherCard.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useAppSelector } from '../../redux/hooks'; 4 | import LottieIcon from '@/components/LottieIcon'; 5 | import { getLottieIcon } from '../../src/utils/iconMapper'; 6 | 7 | export default function CurrentWeatherCard() { 8 | const weather = useAppSelector((state) => state.currentWeather.data); 9 | 10 | if (!weather) return

No current weather data available

; 11 | 12 | return ( 13 |
14 |

{weather.name}

15 |
16 | 17 |
18 |

{weather.weather[0].description}

19 |

{weather.main.temp}°C

20 |
21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /redux/slices/WeatherSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; 2 | 3 | export const fetchWeather = createAsyncThunk( 4 | 'weather/fetchWeather', 5 | async (city: string) => { 6 | const response = await fetch( 7 | `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=YOUR_API_KEY&units=metric` 8 | ); 9 | return response.json(); 10 | } 11 | ); 12 | 13 | const weatherSlice = createSlice({ 14 | name: 'currentWeather', 15 | initialState: { data: null, status: 'idle' }, 16 | reducers: {}, 17 | extraReducers: (builder) => { 18 | builder 19 | .addCase(fetchWeather.pending, (state) => { 20 | state.status = 'loading'; 21 | }) 22 | .addCase(fetchWeather.fulfilled, (state, action) => { 23 | state.data = action.payload; 24 | state.status = 'succeeded'; 25 | }) 26 | .addCase(fetchWeather.rejected, (state) => { 27 | state.status = 'failed'; 28 | }); 29 | }, 30 | }); 31 | 32 | export default weatherSlice.reducer; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Azadeh Sharifi Soltani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/ForecastCards.tsx: -------------------------------------------------------------------------------- 1 | import { useAppSelector } from '../../redux/hooks'; 2 | import { ForecastItem } from '../../redux/slices/types/forecastTypes'; 3 | import LottieIcon from '@/components/LottieIcon'; 4 | import { getLottieIcon } from '../../src/utils/iconMapper'; 5 | export default function ForecastCards() { 6 | const forecast = useAppSelector((state) => state.forecast.data); 7 | 8 | if (!forecast || forecast.length === 0) { 9 | return

No forecast data available.

; 10 | } 11 | 12 | return ( 13 |
14 | {forecast.slice(0, 8).map((item: ForecastItem, index: number) => ( 15 |
19 |

20 | {new Date(item.dt_txt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} 21 |

22 | 23 |

{item.weather[0].description}

24 |

{item.main.temp}°C

25 |
26 | ))} 27 |
28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /src/components/CityList.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useAppSelector } from '../../redux/hooks'; 3 | import { getLottieIcon } from '../../src/utils/iconMapper'; 4 | import LottieIcon from '@/components/LottieIcon'; 5 | 6 | export default function CityList() { 7 | const cities = useAppSelector((state) => state.cityWeather.cities); 8 | const status = useAppSelector((state) => state.cityWeather.status); 9 | 10 | if (status === 'loading') { 11 | return

Loading cities...

; 12 | } 13 | 14 | if (!cities || cities.length === 0) { 15 | return

No cities available.

; 16 | } 17 | 18 | return ( 19 |
20 | {cities.map((city) => ( 21 |
22 |

{city.name}

23 | 24 |
25 | 26 |
27 | 28 |

{city.weather[0].description}

29 |

{Math.round(city.main.temp)}°C

30 |
31 | ))} 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useEffect } from 'react'; 4 | import { useAppDispatch, useAppSelector } from '../../redux/hooks'; 5 | import { fetchDefaultCitiesWeather } from '../../redux/slices/cityWeatherSlice'; 6 | 7 | import Sidebar from '@/components/Sidebar'; 8 | import CityList from '@/components/CityList'; 9 | import WeatherSearch from '@/components/WeatherSearch'; 10 | import TehranWeather from '@/components/TehranWeather'; 11 | import ForecastCards from '@/components/ForecastCards'; 12 | import CurrentWeatherCard from '@/components/CurrentWeatherCard'; 13 | import Footer from '@/components/Footer'; 14 | import WorldMap from '@/components/WorldMap'; 15 | 16 | export default function Home() { 17 | const dispatch = useAppDispatch(); 18 | const weather = useAppSelector((state)=> state.currentWeather.data); 19 | useEffect(() => { 20 | dispatch(fetchDefaultCitiesWeather()); 21 | }, [dispatch]); 22 | 23 | return ( 24 | <> 25 |
26 |
27 | 28 |
29 |
30 |
31 | 32 | 33 | {weather && } 34 | 35 |
36 |
37 | 38 | 39 |
40 |
41 |
42 |