{"id":22733746,"url":"https://github.com/marcesdan/weather-app-front","last_synced_at":"2025-08-21T06:43:23.536Z","repository":{"id":214139285,"uuid":"735778173","full_name":"marcesdan/weather-app-front","owner":"marcesdan","description":null,"archived":false,"fork":false,"pushed_at":"2023-12-27T14:04:08.000Z","size":173,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-03-30T02:11:12.449Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://weather-app-front-indol.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/marcesdan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-12-26T03:53:13.000Z","updated_at":"2023-12-26T05:52:10.000Z","dependencies_parsed_at":"2025-02-05T04:31:21.496Z","dependency_job_id":"96a65ff6-3e64-4d43-9012-6c1618d67c76","html_url":"https://github.com/marcesdan/weather-app-front","commit_stats":null,"previous_names":["marcesdan/weather-app-front"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/marcesdan/weather-app-front","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcesdan%2Fweather-app-front","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcesdan%2Fweather-app-front/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcesdan%2Fweather-app-front/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcesdan%2Fweather-app-front/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/marcesdan","download_url":"https://codeload.github.com/marcesdan/weather-app-front/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/marcesdan%2Fweather-app-front/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":271436840,"owners_count":24759425,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-08-21T02:00:08.990Z","response_time":74,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-12-10T20:15:59.963Z","updated_at":"2025-08-21T06:43:23.475Z","avatar_url":"https://github.com/marcesdan.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Weather App\n\nApp full client-side que permita visualizar el pronóstico climático. Los datos son consumidos por una API desarrollada en Node / Express.\n\n- [**Demo de la App**](https://weather-app-front-indol.vercel.app) |\n  [**Repo de la App**](https://github.com/marcesdan/weather-app-front)\n\n- [**Demo de la API**](https://weather-app-api-pzz2.onrender.com) |\n  [**Repo de la API**](https://github.com/marcesdan/weather-app-api)\n\n## Funcionalidades\n\n- Clima actual en la ubicación actual.\n- Pronóstico de la ubicación actual, en las próximas horas.\n- Pronóstico de la ubicación actual, en los próximos días.\n- Clima actual de otras 5 ciudades seleccionables.\n- Actualización cada 10 minutos, cómo recomienda Open Weather Map\n\n## Se usaron las tecnologías\n\n- Vite / Vitest\n- Typescript, con uso de path aliases (@/components, @/store, etc)\n- Redux, con @redux/toolkit y soporte de tipos\n- Redux-Saga, con soporte de tipos\n- Redux-Persist (localStorage)\n- React-Router v6\n- Styled-Components 💅\n- Husky (precommit) + lint-staged + eslint + prettier\n\n## Se usa una estructura clásica de carpetas\n\n- 📂 src\n- ┣ 📂 components\n- ┃ ┣ 📂 atoms (presentacional)\n- ┃ ┣ 📂 molecules (presentacional)\n- ┃ ┣ 📂 organisms (seleccionan estado del store de redux)\n- ┃ ┣ 📂 layouts (estructura)\n- ┃ ┗ 📂 styles (styled-components 💅)\n- ┣ 📂 hooks\n- ┣ 📂 pages\n- ┣ 📂 services (para peticiones a APIs)\n- ┣ 📂 store\n- ┃ ┣ 📂 DucksSlice\n- ┃ ┃ ┣ 📜 reducers\n- ┃ ┃ ┣ 📜 sagas\n- ┃ ┃ ┣ 📜 selector\n- ┃ ┃ ┗ 📜 normalizers\n- ┗ 📂utils\n\nEn donde se hace uso de módulos exportadores, conocidos como \"Barrels\". Es decir, archivos `index` que se utilizan únicamente para exportar módulos y tipos desde su subcarpeta.\n\nPor ejemplo, en lugar de tener importaciones como estas:\n\n```typescript\nimport { ComponentA } from \"./components/ComponentA\";\nimport { ComponentB } from \"./components/ComponentB\";\n```\n\nSe tendría en cambio:\n\n```typescript\nimport { ComponentA, ComponentB } from \"@/components\";\n```\n\nSumado a los path aliases **@/\\***, esto provee una mejora en la experiencia de desarrollo y en la mantenibilidad de proyectos extensos.\n\nPueden verse estos módulos Barrels, por ejemplo en\n\n- [atoms](./src/components/atoms/index.ts)\n- [molecules](./src/components/molecules/index.ts)\n- [organisms](./src/components/organisms/index.ts)\n- [styles](./src/components/styles/index.ts)\n- [utils](./src/utils/index.ts)\n- [pages](./src/pages/index.ts)\n\n## Estilo\n\nDada la naturaleza de las soluciones que proponen react, redux y redux-saga (hooks, reducers, efectos secundarios, memorización, normalización, etc), hay una inclinación a la programación declarativa y funcional a lo largo de todo el proyecto.\n\n### Esto puede verse por ejemplo en\n\n#### Normalizadores, p. ej. en [normalizeGeocodingData](./src/store/CitiesSlice/normalizers.ts)\n\nDonde se normaliza un array de objetos a un objeto con clave única.\n\n```typescript\nexport const normalizeGeocodingData = (\n  geocodingData: Geocoding[]\n): NormalizedGeocodingMap =\u003e\n  geocodingData.reduce(\n    (acc, { name, country, local_names, ...rest }) =\u003e {\n      const city = local_names?.es ?? name;\n      acc[city + country] = { city, country, ...rest };\n      return acc;\n    },\n    {} as { [key: string]: NormalizedGeocoding }\n  );\n```\n\n#### Sagas, p. ej. en [updateWeatherDeamon](./src/store/WeatherSlice/sagas.ts)\n\nDonde se expresan los efectos secundarios de forma declarativa. Mapeando del estado normalizado a un array de efectos secundarios, ejecutándolos todos a la vez - ad eternum\n\n```typescript\nexport function* updateWeatherDeamon() {\n  while (true) {\n    const weather: Record\u003cstring, Weather\u003e = yield select(selectWeather);\n    yield all(\n      Object.values(weather).map(({ lat, lon, city }) =\u003e\n        put(weatherUpdate({ lat, lon, city }))\n      )\n    );\n    yield delay(600000); // 10 minutes\n  }\n}\n```\n\n#### Selectores, p. ej. [selectOtherCitiesWeather](./src/store/WeatherSlice/selectors.ts)\n\nDonde se usa la memorización de funciones para evitar cálculos innecesarios. Y se quita la ciudad actual del estado del store declarativamente.\n\n```typescript\n// El resto de las ciudades sin la ciudad actual\nexport const selectOtherCitiesWeather = createSelector(\n  [selectCurrentCity, selectSlice],\n  (currentCity, { weather: { [currentCity]: _, ...restWithoutCurrentCity } }) =\u003e\n    Object.values(restWithoutCurrentCity)\n);\n```\n\n## Gimnasia de tipos\n\nDada la gran cantidad de registros provenientes de la api de Open Weather Map, ip-api y geocoding; se puso esfuerzo en la declaración de tipos.\n\nPor ejemplo en:\n\n- [Weather](./src/store/WeatherSlice/types.ts)\n- [Geocoding](./src/services/weatherService/index.ts)\n- [GeolocationFromIpApi](./src/services/ipApiService/index.ts)\n- [FetchResult](./src/utils/safeFetch/index.ts)\n\nPara así tener asistencia en editores de código / IDEs, a lo largo de toda la aplicación (incluso en store y sagas), mejorando la experiencia de desarrollo. Cómo así también para evitar errores.\n\nSi bien fue costoso al comienzo, creo que fue de gran ayuda en el último tramo. Creo que sería muy dificil mantener un proyecto así, sin asistencia de tipos (ya sea con typescript, JSDoc, etc)\n\n## Scripts\n\n- `dev`/`start` - start dev server and open browser\n- `build` - build for production\n- `preview` - locally preview production build\n- `test` - launch test runner\n\n## Como correr la app en local\n\nPara correr en local, se debe tener ejecutada tambien la API en local, siguiendo los pasos del [**Readme**](https://github.com/marcesdan/weather-app-api/blob/master/README.md). Y luego, recién:\n\n1. Copia el `.env.example` con las variables de entorno a un nuevo archivo `.env`:\n\n```bash\n  cp .env.example .env\n```\n\n2. Instala las dependencias del proyecto:\n\n```bash\n  npm i\n```\n\n3. Inicia el servidor de desarrollo:\n\n```bash\n  npm run dev\n```\n\n4. Abre [http://localhost:4000](http://localhost:4000) en tu navegador.\n\n\u003cbr/\u003e\n\n\u003e Otra forma de ejecutar la app pero sin levantar la API también en local, puede ser editando en el .env la variable de entorno `VITE_WEATHER_API_URL` y apuntando a la API en producción:\n\n```bash\nVITE_WEATHER_API_URL=https://weather-app-api-pzz2.onrender.com/api/v1\n```\n\n\u003cbr/\u003e\n\n### Uses [Vite](https://vitejs.dev/), [Vitest](https://vitest.dev/), and [React Testing Library](https://github.com/testing-library/react-testing-library) to create a modern [React](https://react.dev/) app compatible with [Create React App](https://create-react-app.dev/)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcesdan%2Fweather-app-front","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmarcesdan%2Fweather-app-front","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmarcesdan%2Fweather-app-front/lists"}