import { useCallback, useEffect, useReducer, useState } from 'react' import { useLoadTrainSchedule } from './hooks/useLoadTrainSchedule' import { useLocalStorage } from "@uidotdev/usehooks"; import { actions, initialState, reducer, type Action, type State } from './state' import './App.css' import { IoSettingsSharp } from 'react-icons/io5'; import styled from "styled-components"; import { NewsWidget, TrainSchedule, WeatherWidget } from './containers'; import {useGiteaApi, useNewsApi, useRssFeed, useWeatherApi} from './hooks'; import {IssueWidget} from './containers/IssuesWidget'; import type {Station} from './types'; import {RssWidget} from './containers/RssWidget'; const Container = styled.div` display: flex; height: 100vh; width: 100vw; gap: 2rem; `; const Pane = styled.div` max-width: 45vw; `; const SettingContainer = styled.div` display: flex; flex-direction: column; gap: 1rem; padding: 1.5rem; background: #1e1e2f; color: #f1f1f1; border-radius: 12px; font-family: 'Inter', sans-serif; `; const FieldGroup = styled.div` display: flex; align-items: center; justify-content: space-between; background: #2a2a40; padding: 0.75rem 1rem; border-radius: 8px; `; const Label = styled.label` font-size: 0.95rem; color: #b0b0c0; `; const Select = styled.select` background: #3a3a55; color: #fff; border: none; padding: 0.5rem 0.75rem; border-radius: 6px; font-size: 0.95rem; outline: none; width: 60%; cursor: pointer; &:hover { background: #4a4a6a; } `; const Input = styled.input` background: #3a3a55; color: #fff; border: none; padding: 0.4rem 0.6rem; border-radius: 6px; width: 60px; text-align: center; outline: none; &:focus { background: #4a4a6a; } `; const TextArea = styled.textarea` background: #3a3a55; color: #fff; border: none; border-radius: 8px; padding: 0.75rem; width: calc(100% - 2em); height: 80px; resize: vertical; outline: none; &:focus { background: #4a4a6a; } `; export const Button = styled.button` background: #4a4a6a; color: #fff; border: none; padding: 0.6rem 1.2rem; border-radius: 8px; font-size: 0.95rem; font-weight: 500; cursor: pointer; transition: all 0.2s ease; font-family: 'Inter', sans-serif; &:hover { background: #5b5b7a; transform: translateY(-1px); } &:active { background: #3a3a55; transform: translateY(0); } &:disabled { background: #2a2a40; color: #999; cursor: not-allowed; } `; // Credit to https://www.benmvp.com/blog/sync-localstorage-react-usereducer-hook/ const usePersistReducer = () => { const [savedState, saveState] = useState( initialState, ) // wrap `reducer` with a memoized function that // syncs the `newState` to `localStorage` before // returning `newState`. memoizing is important! const reducerLocalStorage = useCallback( (state: State, action: Action) => { const newState = reducer(state, action) saveState(newState) return newState }, [saveState], ) // use wrapped reducer and the saved value from // `localStorage` as params to `useReducer`. // this will return `[state, dispatch]` return useReducer(reducerLocalStorage, savedState) } function App() { const [state, dispatch] = usePersistReducer(); const [settingOpened, setSettingOpened] = useState(false); const { reloadTrainSchedule } = useLoadTrainSchedule(state, dispatch); const { reloadNews } = useNewsApi({state, dispatch}); const { reloadWeather } = useWeatherApi({state, dispatch}); const { reloadIssues } = useGiteaApi({state, dispatch}) const { reloadRssFeed } = useRssFeed({state, dispatch}) const { selectedLocation } = state; const setSelectedLocation = (location: string) => { dispatch(actions.setSelectedLocation({ location })) } useEffect(() => { reloadNews(); reloadWeather(); reloadIssues(); reloadTrainSchedule(); reloadRssFeed(); }, [selectedLocation, state.config]) const mainContent = <>

Next trains in {state.selectedLocation}

{state.config.rssFollow.length > 0 ? <>

RSS Feed

: <> }

Weather

Issues

News

const { trainScheduleShow, trainDelayCompute, trainCancelCompute, rssFollow, } = state.config; const setTrainScheduleShow = (value: number) => dispatch(actions.setConfig({ setting: 'trainScheduleShow', value })); const setTrainDelayCompute = (value: number) => dispatch(actions.setConfig({ setting: 'trainDelayCompute', value })); const setTrainCancelCompute = (value: number) => dispatch(actions.setConfig({ setting: 'trainCancelCompute', value })); const setRssFollow = (value: string) => dispatch(actions.setConfig({ setting: 'rssFollow', value })); const settingContent = ( setTrainScheduleShow(parseInt(e.target.value))} /> min setTrainDelayCompute(parseInt(e.target.value))} /> min setTrainCancelCompute(parseInt(e.target.value))} /> min