diff --git a/package.json b/package.json index b6306b7..11d27d2 100644 --- a/package.json +++ b/package.json @@ -4,16 +4,16 @@ "private": true, "devDependencies": { "parcel-bundler": "^1.12.3", - "react-scripts": "3.0.1" - }, - "dependencies": { - "@sentry/browser": "^5.10.2", + "react-scripts": "3.0.1", "@types/jest": "^23.3.9", "@types/lodash": "^4.14.118", "@types/node": "^10.12.9", "@types/react": "^16.8.23", "@types/react-dom": "^16.8.4", - "@types/styled-components": "^4.4.1", + "@types/styled-components": "^4.4.1" + }, + "dependencies": { + "@sentry/browser": "^5.10.2", "axios": "^0.18.0", "lodash": "^4.17.11", "moment": "^2.15.2", diff --git a/src/components/CandleCount/CandleCount.tsx b/src/components/CandleCount/CandleCount.tsx index eaff09d..e6633cb 100644 --- a/src/components/CandleCount/CandleCount.tsx +++ b/src/components/CandleCount/CandleCount.tsx @@ -1,10 +1,9 @@ -import React, { Component, useState, useEffect, FC } from "react"; +import React, { useState, useEffect, FC } from "react"; import moment from "moment"; import { getTodayChanukahEvent } from "./CandleCountHelpers"; import { getCurrentPosition } from "./getCurrentPosition"; -import { pluralise } from "./helpers"; +import { pluralise } from "../../helpers"; import { Button } from "./Button"; -import { CandleCountContainer } from "./CandleCountContainer"; import { captureException } from "@sentry/browser"; import { useLocalStorage } from "../../hooks/useLocalStorage"; import styled from "styled-components"; @@ -14,17 +13,18 @@ const prevAskedForGeoKey = "askedGeo"; enum NoGeoReason { NotAsked, Disallowed, - AllowedButUnable + AllowedButUnable, } /** * Discriminated union allows attaching custom information for each state and * makes illegal states unrepresentable */ -type StateDiscrUnion = +type ChanukahState = | { label: "NoGeo"; reason: NoGeoReason } | { label: "ChanukahReqsInProgress" } // when cityName and lightingTime reqs are in progress | { label: "ChanukahReqsFailed" } + | { label: "8thDayChanukah" } | { label: "NotChanukah"; daysUntilChanukah: number } | { label: "ChanukahReqComplete"; @@ -38,7 +38,10 @@ const Text = styled.h3` margin-bottom: 0.5em; `; -function CandleCount() { +export const useGetCandleCount = (): [ + state: ChanukahState, + askLocationPermission: VoidFunction +] => { const maxAttempts = 3; const [attemptsCount, setAttemptsCount] = useState(0); @@ -48,11 +51,11 @@ function CandleCount() { const [askedPermission, setAskedPermission] = useState(!!previouslyAsked); - const initialState: StateDiscrUnion = previouslyAsked + const initialState: ChanukahState = previouslyAsked ? { label: "ChanukahReqsInProgress" } : { label: "NoGeo", reason: NoGeoReason.NotAsked }; - const [state, setState] = useState(initialState); + const [state, setState] = useState(initialState); useEffect(() => { if (askedPermission) { @@ -67,7 +70,7 @@ function CandleCount() { state.label === "ChanukahReqsFailed" && attemptsCount < maxAttempts ) { - setAttemptsCount(c => c + 1); + setAttemptsCount((c) => c + 1); setTimeout(() => { getLocationAndSetState(); @@ -93,7 +96,10 @@ function CandleCount() { if (tonightChanukah.label === "Chanukah") { setState({ ...tonightChanukah, label: "ChanukahReqComplete" }); - } else if (tonightChanukah.label === "NotChanukah") { + } else if ( + tonightChanukah.label === "NotChanukah" || + tonightChanukah.label === "8thDayChanukah" + ) { setState(tonightChanukah); } } catch (error) { @@ -111,6 +117,19 @@ function CandleCount() { } } + const askLocationPermission = () => setAskedPermission(true); + return [state, askLocationPermission]; +}; + +interface CandleCountProps { + state: ChanukahState; + askLocationPermission: VoidFunction; +} + +export const CandleCount: FC = ({ + state, + askLocationPermission, +}) => { switch (state.label) { case "NoGeo": const { reason } = state; @@ -118,7 +137,7 @@ function CandleCount() { switch (reason) { case NoGeoReason.NotAsked: return ( - ); @@ -156,6 +175,18 @@ function CandleCount() { first night of Chanukah! 🕎 ); + + case "8thDayChanukah": + return ( + <> + + It's the 8th day of Chanukah which means no candles are lit tonight + 😢 + + We hope to see you next year! 👋 + + ); + case "ChanukahReqComplete": const { candleLightingTime, cityName, candleCount } = state; const displayCityName = cityName || `your location`; @@ -176,46 +207,25 @@ function CandleCount() { The Chanukah lighting time on Fridays is just before the time of - lighting Shabbat candles, the exact time of which will depend on - the local custom in {displayCityName}. + Shabbat candles – the exact time of which depends on the local + custom in {displayCityName}. ); } } -} - -/** - * Class component wrapper for error boundary - */ - -export class CandleCountWithBoundary extends Component< - {}, - { hasError: boolean } -> { - state = { hasError: false }; - - static getDerivedStateFromError = () => ({ hasError: true }); - - render() { - return ( - - {!this.state.hasError && } - - ); - } -} +}; /** * @TODOs: - * - style geolocation button - * - style text + * - [x] style geolocation button + * - [x] style text * - add different nusachim - * - get from rabbi roselaar's links - * - add nusach selector buttons + * - [x] get from rabbi roselaar's links + * - [x] add nusach selector buttons * - make button clearer * - maybe have popup to point towards nusach selector - * - make chanukah in svg - * - make chanukah display right number of candles + * - [x] make chanukah in svg + * - [x] make chanukah display right number of candles * - add tests for new functions! */ diff --git a/src/components/CandleCount/CandleCountHelpers.ts b/src/components/CandleCount/CandleCountHelpers.ts index 22dd847..2b778c2 100644 --- a/src/components/CandleCount/CandleCountHelpers.ts +++ b/src/components/CandleCount/CandleCountHelpers.ts @@ -1,17 +1,17 @@ import axios from "axios"; -import { getInOrderOfPreference, momentDay } from "./helpers"; +import { getInOrderOfPreference, momentDay } from "../../helpers"; import { captureException } from "@sentry/browser"; -const chanukahRegex = /^Chanukah: (\d) Candles?: (\d\d?:\d\dpm)$/; +const candlesRegex = /^Chanukah: (\d) Candles?/; +const eighthDayRegex = /^Chanukah: 8th Day$/; function getLightingInfoFromHebcalItem(item: HebCalItem): LightingInfo { - const regexResult = item.title.match(chanukahRegex); + const regexResult = item.title.match(candlesRegex); if (regexResult) { return { count: parseInt(regexResult[1]), - // timeStr: regexResult[2], - lightingTime: new Date(item.date) + lightingTime: new Date(item.date), }; } else { throw new Error( @@ -26,7 +26,7 @@ function getLightingInfoFromHebcalItem(item: HebCalItem): LightingInfo { async function getLocationInfo({ latitude, - longitude + longitude, }: Coordinates): Promise { const apiKey = "AIzaSyBOjCfj4s39EnTIynqE9n8VTG4BxwGxDKI"; const url = `https://maps.googleapis.com/maps/api/geocode/json?latlng=${latitude},${longitude}&key=${apiKey}`; @@ -68,15 +68,20 @@ export async function getTodayChanukahEvent( // change the 0 number to test various scenarios const todayMoment = momentDay(today).add(0, "days"); - const chanukahDatedItems = data.items.filter(item => - item.title.match(chanukahRegex) + const chanukahDatedItems = data.items.filter((item) => + item.title.match(candlesRegex) ); const chanukahTodayOpt = - chanukahDatedItems.find(item => + chanukahDatedItems.find((item) => momentDay(item.date).isSame(todayMoment, "day") ) || null; + const eighthDay = data.items.find((item) => item.title.match(eighthDayRegex)); + const isEighthDay = eighthDay + ? momentDay(eighthDay.date).isSame(todayMoment, "day") + : false; + if (chanukahTodayOpt) { // It's Chanukah, bitches 🕎 @@ -94,8 +99,10 @@ export async function getTodayChanukahEvent( candleLightingTime: itsFriday ? { day: "Friday" } : { day: "Weekday", time: lightingInfo.lightingTime }, - cityName + cityName, }; + } else if (isEighthDay) { + return { label: "8thDayChanukah" }; } else { const diff = -todayMoment.diff( momentDay(chanukahDatedItems[0].date), diff --git a/src/components/CandleCount/helpers.ts b/src/components/CandleCount/helpers.ts deleted file mode 100644 index 25198f1..0000000 --- a/src/components/CandleCount/helpers.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useState, useEffect } from "react"; -import moment from "moment"; - -export function getInOrderOfPreference( - data: T[], - itemMatches: (item: T, value: V) => boolean, - ...preferredVals: V[] -): T | null { - return preferredVals.reduce((last: T | null, preferredValue: V) => { - if (last) { - return last; - } else { - const found = data.find(item => itemMatches(item, preferredValue)); - return found ?? null; - } - }, null); -} - -export const pluralise = (n: number) => (n === 1 ? "" : "s"); - -export const momentDay = (date: Date | string) => moment(date).startOf("day"); diff --git a/src/components/CandleCount/typings.d.ts b/src/components/CandleCount/typings.d.ts index f701941..7708a7b 100644 --- a/src/components/CandleCount/typings.d.ts +++ b/src/components/CandleCount/typings.d.ts @@ -53,6 +53,7 @@ type TonightChanukahData = cityName: string | null; candleLightingTime: { day: "Weekday"; time: Date } | { day: "Friday" }; } + | { label: "8thDayChanukah" } | { label: "NotChanukah"; daysUntilChanukah: number; diff --git a/src/components/MaozTzur.tsx b/src/components/MaozTzur.tsx index 92d6524..bfddb84 100644 --- a/src/components/MaozTzur.tsx +++ b/src/components/MaozTzur.tsx @@ -2,8 +2,8 @@ import React, { useEffect, useState } from "react"; import "./MaozTzur.css"; import { content } from "../content"; import { Selector } from "./Selector"; -import { CandleCountWithBoundary } from "./CandleCount/CandleCount"; -import Menorah from "./Menorah"; +import { CandleCount, useGetCandleCount } from "./CandleCount/CandleCount"; +import { Menorah, numToDayMap } from "./Menorah"; import Section from "./Section"; import Footer from "./Footer"; import { isNusach } from "../helpers"; @@ -31,9 +31,11 @@ export function MaozTzur() { const nusachim: Option[] = [ { label: "Ashkenaz", value: "ashkenaz" }, { label: "Sefardi", value: "sefardi" }, - { label: "Chabad", value: "chabad" } + { label: "Chabad", value: "chabad" }, ]; + const [state, askLocationPermission] = useGetCandleCount(); + useEffect(() => { console.log( "Hi there fellow developer 👋\nCheck out my other projects at https://github.com/Arrow7000" @@ -43,9 +45,18 @@ export function MaozTzur() { return (
- - - {content.map(section => ( + + + {content.map((section) => (
))}