diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml new file mode 100644 index 0000000..9f30c79 --- /dev/null +++ b/.github/workflows/publish-web.yml @@ -0,0 +1,25 @@ +name: Build and Deploy web +on: + workflow_dispatch: + push: + branches: + - main +jobs: + build-and-deploy: + runs-on: ubuntu-latest + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v2.3.1 + with: + persist-credentials: false + + - name: Install and Build 🔧 + run: | + yarn install + NODE_ENV=production yarn expo build:web + - name: Deploy 🚀 + uses: JamesIves/github-pages-deploy-action@4.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + branch: gh-pages + folder: web-build diff --git a/package.json b/package.json index a01610d..bb6a497 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "bob", "version": "1.0.0", "main": "node_modules/expo/AppEntry.js", + "homepage": "/bob-the-algorithm", "scripts": { "start": "expo start", "android": "expo start --android", @@ -50,6 +51,7 @@ "@babel/core": "^7.12.9", "@types/chroma-js": "^2.1.3", "@types/react": "~17.0.21", + "@types/react-dom": "^18.0.3", "@types/react-native": "~0.64.12", "@types/styled-components-react-native": "^5.1.3", "babel-plugin-module-resolver": "^4.1.0", diff --git a/src/features/calendar/context.ts b/src/features/calendar/context.ts index 80027ad..41010f5 100644 --- a/src/features/calendar/context.ts +++ b/src/features/calendar/context.ts @@ -1,7 +1,20 @@ import { Calendar } from "expo-calendar"; import { createContext } from "react"; -type CalendarContextValue = { +type RejectedCalendarContextValue = { + status: 'rejected'; + date: Date; + setDate: (date: Date) => void; +} + +type UnavailableCalendarContextValue = { + status: 'unavailable'; + date: Date; + setDate: (date: Date) => void; +} + +type AcceptedCalendarContextValue = { + status: 'ready'; date: Date; setDate: (date: Date) => void; calendars: Calendar[]; @@ -11,6 +24,10 @@ type CalendarContextValue = { error?: any; } +type CalendarContextValue = RejectedCalendarContextValue + | UnavailableCalendarContextValue + | AcceptedCalendarContextValue + const CalendarContext = createContext(undefined as any); export type { CalendarContextValue }; diff --git a/src/features/calendar/hooks.ts b/src/features/calendar/hooks.ts index 46f9e85..5dcefd3 100644 --- a/src/features/calendar/hooks.ts +++ b/src/features/calendar/hooks.ts @@ -5,24 +5,39 @@ import { useAsync, useAsyncCallback } from "#/hooks/async"; import { createEventAsync, deleteEventAsync, getEventsAsync } from "expo-calendar"; import { PlanItem } from "#/types/plans"; +const emptyArray: never[] = []; +const emptyFn = () => undefined; + export const useCalendar = () => { - const { calendar } = useContext(CalendarContext); - return calendar; + const context = useContext(CalendarContext); + if (context.status !== 'ready') { + return undefined; + } + return context.calendar; } export const useCalendars = () => { - const { calendars } = useContext(CalendarContext); - return calendars; + const context = useContext(CalendarContext); + if (context.status !== 'ready') { + return emptyArray; + } + return context.calendars; } export const useSelectedCalendars = () => { - const { selected } = useContext(CalendarContext); - return selected; + const context = useContext(CalendarContext); + if (context.status !== 'ready') { + return emptyArray; + } + return context.selected; } export const useSetSelectedCalendars = () => { - const { setSelected } = useContext(CalendarContext); - return setSelected; + const context = useContext(CalendarContext); + if (context.status !== 'ready') { + return emptyFn; + } + return context.setSelected; } export const useDate = () => { @@ -40,6 +55,9 @@ export const useCommit = () => { const calendar = useCalendar(); const result = useAsyncCallback( async (plan: PlanItem[]) => { + if (!calendar) { + return; + } const end = set(date, { hours: 24, minutes: 0, @@ -64,7 +82,7 @@ export const useCommit = () => { }) } }, - [date], + [date, calendar], ); return result; diff --git a/src/features/calendar/provider.tsx b/src/features/calendar/provider.tsx index 29feee8..8450f70 100644 --- a/src/features/calendar/provider.tsx +++ b/src/features/calendar/provider.tsx @@ -3,6 +3,7 @@ import React, { ReactNode, useCallback, useMemo, useState } from "react"; import { useAsync } from "#/hooks/async"; import { CalendarContext } from "./context"; import AsyncStorage from '@react-native-async-storage/async-storage'; +import { Platform } from "react-native"; const SELECTED_STORAGE_KEY = 'selected_calendars'; @@ -15,6 +16,8 @@ type CalendarProviderProps = { type SetupResponse = { status: 'rejected'; +} | { + status: 'unavailable'; } | { status: 'ready'; calendar: Calendar; @@ -31,6 +34,9 @@ const CalendarProvider: React.FC = ({ const [value] = useAsync( async () => { const { status } = await requestCalendarPermissionsAsync(); + if (Platform.OS !== 'ios') { + return { status: 'unavailable' }; + } if (status !== 'granted') { return { status: 'rejected' }; } @@ -81,13 +87,22 @@ const CalendarProvider: React.FC = ({ [value, selectedIds], ); - if (!value || value.status !== 'ready') { + if (!value) { return <> } + if (value.status !== 'ready') { + return ( + + {children} + + ); + } + return ( any; set: (routine: Routine) => any; @@ -23,5 +23,4 @@ type RoutinesContextValue = { const RoutinesContext = createContext(undefined as any); -export type { Routine, RoutinesContextValue }; export { RoutinesContext }; diff --git a/src/ui/components/modal/index.tsx b/src/ui/components/modal/index.tsx index de0a7f6..220aef5 100644 --- a/src/ui/components/modal/index.tsx +++ b/src/ui/components/modal/index.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react'; -import { Modal as Wrapper } from 'react-native'; +import Wrapper from './react-modal'; import { Popup } from '../popup'; type ModalProps = { visible: boolean; diff --git a/src/ui/components/modal/react-modal.native.tsx b/src/ui/components/modal/react-modal.native.tsx new file mode 100644 index 0000000..703d961 --- /dev/null +++ b/src/ui/components/modal/react-modal.native.tsx @@ -0,0 +1,43 @@ +import ReactDOM from 'react-dom'; +import React, { useMemo, useEffect, ReactNode } from 'react'; + +interface Props { + visible: boolean; + children: ReactNode; +} + +const Modal: React.FC = ({ visible, children }) => { + const elm = useMemo(() => { + const newElm = document.createElement('div'); + newElm.style.position = 'fixed'; + newElm.style.display = 'flex'; + newElm.style.flexDirection = 'column'; + newElm.style.left = '0px'; + newElm.style.top = '0px'; + newElm.style.width = '100%'; + newElm.style.height = '100%'; + newElm.style.transition = 'transform 0.3s'; + newElm.style.transform = 'translateY(100%)'; + return newElm; + }, []); + useEffect(() => { + document.body.appendChild(elm); + return () => { + document.body.removeChild(elm); + }; + }, [elm]); + useEffect(() => { + if (visible) { + elm.style.transform = 'translateY(0)'; + } else { + elm.style.transform = 'translateY(100%)'; + } + }, [elm, visible]); + + return ReactDOM.createPortal( + <>{children}, + elm, + ); +}; + +export default Modal; diff --git a/src/ui/components/modal/react-modal.tsx b/src/ui/components/modal/react-modal.tsx new file mode 100644 index 0000000..ebc98c4 --- /dev/null +++ b/src/ui/components/modal/react-modal.tsx @@ -0,0 +1,3 @@ +import { Modal } from 'react-native'; + +export default Modal; diff --git a/yarn.lock b/yarn.lock index 2801737..9abd8f3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2269,6 +2269,13 @@ resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.5.tgz#75a2a8e7d8ab4b230414505d92335d1dcb53a6df" integrity sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ== +"@types/react-dom@^18.0.3": + version "18.0.3" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.0.3.tgz#a022ea08c75a476fe5e96b675c3e673363853831" + integrity sha512-1RRW9kst+67gveJRYPxGmVy8eVJ05O43hg77G2j5m76/RFJtMbcfAs2viQ2UNsvvDg8F7OfQZx8qQcl6ymygaQ== + dependencies: + "@types/react" "*" + "@types/react-native@^0.65": version "0.65.22" resolved "https://registry.yarnpkg.com/@types/react-native/-/react-native-0.65.22.tgz#58eaf2f64eb37fcb5fa9725547a8e043c4ecbc07"