QH4UB73NUR2XPHZQ2RGJBKKUBN43RKC7ZJBCFPP4ESUIIEDDR5XQC B4MTB6UOH5VPZQ7KDQ23TZSR3CIFGVGVBEFL26LMFAQ5RL7CXPRQC NJNMO72S7VIUV22JXB4IFPZMHWTJAOTP6EC6Z4QSKYIROSXT52MQC RB2ETNIFLQUA6OA66DAEOXZ25ENMQGNKX5CZRSKEYHTD6BQ6NTFQC WRPIYG3EUHZR6N6T74ZXZDXATRMIRLXAQ24UNUNSVTVYGMT2VDSQC JXG3FCXYBDKMUD77DOM7RCIJYKB7BILC43OHHDZBE7YQRGAMUCCAC O2BZOX7MS4JCDS3C6EJQXAWUEQV6HVDCIF2FIN2BCJNRLIU6ZVKAC ENNZIQJG4XJ62QCNRMLNAXN7ICTPCHQFZTURX6QSUYYWNADFJHXQC 5IDB3IWSB6LFW4U772Y7BH5Y3FQOQ7IFWLVXDZE5XS6SKJITFV4QC U256ZALIPTVWLNACYPIMWLNEYDQWP7CHF4Y4CGMILQTONJHMGQVQC RSF6UAJKG7CEKILSVXI6C4YZXY7PIYZM2EMA2IXKQ7SADKNVSH7QC IR75ZMX32SFFMDNV2I2L22X5JTWCOC4UUBCSPU7S6VHR6HFV6ADQC OUR4PAOTXXKXQPMAR5TIYX7MBRRJS2WVTZS7SN4SOGML7SPJIJGQC 2J37EVJMX255K3XEJHTZGRPEIRMAQ62JQWOA7JU3YTZUB6PUPWVQC EA5BFM5GMM7KNMDLTVOSUKVKMSIDD72TAFVHDVGEOUY5VELECU3QC PT4276XCOP5NJ3GRFJLIBZKVNVAOATAY5PLWV7FWK6RZW5FTEP5AC QMEYU4MWLTSWPWEEOFRLK2IKE64BY3V5X73323WPLCGPP3TPDYGAC TKGBRIQT7XCPJ3LA5JAEMMGMPFWQWINMSDRW76V2IMZZGT5AWTYAC SAESJLLYCQJUIHKFYFV53AWHFOSGI5SKLVS7DPTQO6BKGITPYPUQC TUA4HMUDRRXLVOH4WPID2ZJGEIJTSCMM5OBP3E26ECYHSHG3IBDQC QU5FW67RGCWOWT2YFM4NYMJFFHWIRPQANQBBAHBKZUY7UYMCSIMQC 3LMXT7Z6SIGLQ2OMH7OKPJPWNPN2CSGD3BKUD2NMJVCX2CSAMFYQC AAALU5A2FQQTNV7ZVAFCU2JTRUONEUWWZKENDUUXDOFUGWHM3KZQC QAC2QJ32ZLAK25KJ7SWT27WOZKD2MMDE7OZPHIRRFP2W2QZW7PBAC J6S23MDGHVSCVVIRB6XRNSY3EGTDNWFJHV7RYLIEHBUK5KU63CFQC ZIG57EE6RB22JGB3CT33EN2HVYCHCXBT5GROBTBMBLEMDTGQOOBQC 5R2Z7FSXJD7Z53QSU2NSTEBONTYK43FIJOSOMUST5XMYIWRXY2HQC BFZN4SUAGYNFFYVAP36BAX32DMO622PK4EPEVQQEAGC2IHTEAAPQC ARX7SHY5UXL5ZZDY4BJ6LVQSC2XCI5M6FFXQ35MBWDRUHNJNICHQC HO2PFRABW6BBTE4MUKUTEGXCMJS46WGVBCNWOHO4OL52DVAB4YDAC Left err -> log ("Login failed: " <> printError err)Right r -> log ("Login status: " <> show r.status)pure $ case result ofLeft err -> LoginError { status: Nothing, message: printError err }Right r -> case r.status ofStatusCode 403 -> LoginForbiddenStatusCode 200 -> LoginOKother -> LoginError { status: Just other, message: r.statusText }
Left err -> log ("Login failed: " <> printError err)Right r -> log ("Login status: " <> show r.status)pure$ case result ofLeft err -> LoginError { status: Nothing, message: printError err }Right r -> case r.status ofStatusCode 403 -> LoginForbiddenStatusCode 200 -> LoginOKother -> LoginError { status: Just other, message: r.statusText }
let signupJSON = encodeJson ${ username: req.username, password: req.password, recoveryType: case req.recoverBy ofRecoverByEmail _ -> "email"RecoverByZAddr _ -> "zaddr", recoveryEmail: case req.recoverBy ofRecoverByEmail email -> Just emailRecoverByZAddr _ -> Nothing, recoveryZAddr: case req.recoverBy ofRecoverByEmail _ -> NothingRecoverByZAddr zaddr -> Just zaddr, captchaToken: req.captchaToken}
letsignupJSON =encodeJson$ { username: req.username, password: req.password, recoveryType:case req.recoverBy ofRecoverByEmail _ -> "email"RecoverByZAddr _ -> "zaddr", recoveryEmail:case req.recoverBy ofRecoverByEmail email -> Just emailRecoverByZAddr _ -> Nothing, recoveryZAddr:case req.recoverBy ofRecoverByEmail _ -> NothingRecoverByZAddr zaddr -> Just zaddr, captchaToken: req.captchaToken}
Right r | r.status == StatusCode 200 -> dolog "Registration succeeded!"pure SignupOKRight r | r.status == StatusCode 403 -> dolog ("Registration failed: Capcha Invalid")pure CaptchaInvalidRight r | r.status == StatusCode 400 -> dolog ("Registration failed: Z-Address Invalid")pure ZAddrInvalid
Right r| r.status == StatusCode 200 -> dolog "Registration succeeded!"pure SignupOKRight r| r.status == StatusCode 403 -> dolog ("Registration failed: Capcha Invalid")pure CaptchaInvalidRight r| r.status == StatusCode 400 -> dolog ("Registration failed: Z-Address Invalid")pure ZAddrInvalid
stop' <- traverse (_ .: "eventTime") =<< ev .:? "stop"note "Only 'stop' and 'start' events are supported." $(StartEvent <$> start') <|>(StopEvent <$> stop')
stop' <- traverse (_ .: "eventTime") =<< ev .:? "stop"note "Only 'stop' and 'start' events are supported."$ (StartEvent <$> start')<|> (StopEvent <$> stop')
liftEffect <<< runExceptT $ dokev <- withExceptT LogFailure $ parseDatedResponse responsecase event kev ofStartEvent _ -> pure kevStopEvent _ -> throwError <<< Unexpected $ "Expected start event, got stop."
liftEffect <<< runExceptT$ dokev <- withExceptT LogFailure $ parseDatedResponse responsecase event kev ofStartEvent _ -> pure kevStopEvent _ -> throwError <<< Unexpected $ "Expected start event, got stop."
liftEffect <<< runExceptT $ dokev <- withExceptT LogFailure $ parseDatedResponse responsecase event kev ofStartEvent _ -> throwError <<< Unexpected $ "Expected stop event, got start."StopEvent _ -> pure kev
liftEffect <<< runExceptT$ dokev <- withExceptT LogFailure $ parseDatedResponse responsecase event kev ofStartEvent _ -> throwError <<< Unexpected $ "Expected stop event, got start."StopEvent _ -> pure kev
let traverseCreditRow r' = ({ intervals: _ }) <$> traverse f r'.intervalsin (ListIntervalsResponse <<< ({ workIndex: _ })) <$> traverse traverseCreditRow r.workIndex
lettraverseCreditRow r' = ({ intervals: _ }) <$> traverse f r'.intervalsin(ListIntervalsResponse <<< ({ workIndex: _ })) <$> traverse traverseCreditRow r.workIndex
let queryElements = case ts' ofBefore t -> ["before=" <> t, "limit=100"]During (Interval x) -> ["after=" <> x.start, "before=" <> x.end, "limit=100"]After t -> ["after=" <> t, "limit=100"]
letqueryElements = case ts' ofBefore t -> [ "before=" <> t, "limit=100" ]During (Interval x) -> [ "after=" <> x.start, "before=" <> x.end, "limit=100" ]After t -> [ "after=" <> t, "limit=100" ]
type LoginState ={ username :: String, password :: String, loginError :: Maybe LoginError}
type LoginState= { username :: String, password :: String, loginError :: Maybe LoginError}
type Capability m ={ login :: String -> String -> m LoginResponse, checkLogin :: m LoginResponse, logout :: m Unit}
type Capability m= { login :: String -> String -> m LoginResponse, checkLogin :: m LoginResponse, logout :: m Unit}
component:: forall query input m. Monad m=> System m-> Capability m-> H.Component HH.HTML query input LoginResult mcomponent system caps = H.mkComponent{ initialState, render, eval: H.mkEval $ H.defaultEval { handleAction = eval }} whereinitialState :: input -> LoginStateinitialState _ = { username: "", password: "", loginError: Nothing }
component ::forall query input m.Monad m =>System m ->Capability m ->H.Component HH.HTML query input LoginResult mcomponent system caps =H.mkComponent{ initialState, render, eval: H.mkEval $ H.defaultEval { handleAction = eval }}whereinitialState :: input -> LoginStateinitialState _ = { username: "", password: "", loginError: Nothing }
render :: forall slots. LoginState -> H.ComponentHTML LoginAction slots mrender st =Card.component $HH.div[ P.classes (ClassName <$> ["row", "no-gutters", "container"]) ]
render :: forall slots. LoginState -> H.ComponentHTML LoginAction slots mrender st =Card.component$ HH.div[ P.classes (ClassName <$> [ "row", "no-gutters", "container" ]) ]
[ P.classes (ClassName <$> ["col-12", "col-md-6", "bg-cover", "card-img-left"]), CSS.style $ backgroundImage (url "/assets/img/photos/latch.jpg")][HH.div[ P.classes (ClassName <$> ["shape", "shape-right", "shape-fluid-y", "svg-shim", "text-white", "d-none", "d-md-block"])][ HH.img [ P.src "/assets/img/shapes/curves/curve-4.svg" ]]]
[ P.classes (ClassName <$> [ "col-12", "col-md-6", "bg-cover", "card-img-left" ]), CSS.style $ backgroundImage (url "/assets/img/photos/latch.jpg")][ HH.div[ P.classes (ClassName <$> [ "shape", "shape-right", "shape-fluid-y", "svg-shim", "text-white", "d-none", "d-md-block" ]) ][ HH.img [ P.src "/assets/img/shapes/curves/curve-4.svg" ] ]]
[ P.classes (ClassName <$> ["col-12", "col-md-6"]) ][ HH.div[ P.classes (ClassName <$> ["card-body"]) ][ HH.h2[ P.classes (ClassName <$> ["mb-0", "font-weight-bold", "text-center"])][ HH.text "Sign In"], HH.form[ P.classes (ClassName <$> ["mb-6"]), E.onSubmit (Just <<< Login)][ HH.div[ P.classes (ClassName <$> ["form-group"])][ HH.label[ P.classes (ClassName <$> ["sr-only"]), P.for "modalSigninHorizontalUsername"][ HH.text "Username" ], HH.input[ P.type_ P.InputText, P.classes (ClassName <$> ["form-control"]), P.id_ "modalSigninHorizontalUsername", P.placeholder "Username", P.required true, P.autofocus true, P.value st.username, E.onValueInput (Just <<< SetUsername)]
[ P.classes (ClassName <$> [ "col-12", "col-md-6" ]) ][ HH.div[ P.classes (ClassName <$> [ "card-body" ]) ][ HH.h2[ P.classes (ClassName <$> [ "mb-0", "font-weight-bold", "text-center" ]) ][ HH.text "Sign In" ], HH.form[ P.classes (ClassName <$> [ "mb-6" ]), E.onSubmit (Just <<< Login)][ HH.div[ P.classes (ClassName <$> [ "form-group" ]) ][ HH.label[ P.classes (ClassName <$> [ "sr-only" ]), P.for "modalSigninHorizontalUsername"][ HH.text "Username" ], HH.input[ P.type_ P.InputText, P.classes (ClassName <$> [ "form-control" ]), P.id_ "modalSigninHorizontalUsername", P.placeholder "Username", P.required true, P.autofocus true, P.value st.username, E.onValueInput (Just <<< SetUsername)]], HH.div[ P.classes (ClassName <$> [ "form-group" ]) ][ HH.label[ P.classes (ClassName <$> [ "sr-only" ]), P.for "modalSigninHorizontalPassword"][ HH.text "Password" ], HH.input[ P.type_ P.InputPassword, P.classes (ClassName <$> [ "form-control" ]), P.id_ "modalSigninHorizontalPassword", P.placeholder "Password", P.required true, P.value st.password, E.onValueInput (Just <<< SetPassword)]], case st.loginError ofNothing -> HH.div_ []Just err ->letmessage = case err ofForbidden -> "Login failed. Check your username and password."ServerError -> "Login failed due to an internal error. Please contact support."inHH.div[ P.classes (ClassName <$> [ "alert alert-danger" ]) ][ HH.text message ], HH.button[ P.classes (ClassName <$> [ "btn", "btn-block", "btn-primary" ]) ][ HH.text "Sign in" ]]
, HH.div[ P.classes (ClassName <$> ["form-group"])][ HH.label[ P.classes (ClassName <$> ["sr-only"]), P.for "modalSigninHorizontalPassword"][ HH.text "Password" ], HH.input[ P.type_ P.InputPassword, P.classes (ClassName <$> ["form-control"]), P.id_ "modalSigninHorizontalPassword", P.placeholder "Password", P.required true, P.value st.password, E.onValueInput (Just <<< SetPassword)]
, HH.p[ P.classes (ClassName <$> [ "mb-0", "font-size-sm", "text-center", "text-muted" ]) ][ HH.text "Need an account? ", HH.a[ P.href "#signup" ][ HH.text "Sign up" ]
, case st.loginError ofNothing ->HH.div_ []Just err ->let message = case err ofForbidden -> "Login failed. Check your username and password."ServerError -> "Login failed due to an internal error. Please contact support."in HH.div[ P.classes (ClassName <$> ["alert alert-danger"]) ][ HH.text message ], HH.button[ P.classes (ClassName <$> ["btn", "btn-block", "btn-primary"]) ][ HH.text "Sign in" ]]
, HH.p[ P.classes (ClassName <$> ["mb-0", "font-size-sm", "text-center", "text-muted"]) ][ HH.text "Need an account? ", HH.a[ P.href "#signup" ][ HH.text "Sign up" ]]]
eval :: LoginAction -> H.HalogenM LoginState LoginAction () LoginResult m Uniteval = case _ ofSetUsername user -> H.modify_ (_ { username = user })SetPassword pass -> H.modify_ (_ { password = pass })Login ev -> dolift $ system.preventDefault evuser <- H.gets (_.username)pass <- H.gets (_.password)response <- lift (caps.login user pass)case response ofLoginOK -> H.raise (LoginComplete { username: user })LoginForbidden -> H.modify_ (_ { loginError = Just Forbidden })LoginError _ -> H.modify_ (_ { loginError = Just ServerError })
eval :: LoginAction -> H.HalogenM LoginState LoginAction () LoginResult m Uniteval = case _ ofSetUsername user -> H.modify_ (_ { username = user })SetPassword pass -> H.modify_ (_ { password = pass })Login ev -> dolift $ system.preventDefault evuser <- H.gets (_.username)pass <- H.gets (_.password)response <- lift (caps.login user pass)case response ofLoginOK -> H.raise (LoginComplete { username: user })LoginForbidden -> H.modify_ (_ { loginError = Just Forbidden })LoginError _ -> H.modify_ (_ { loginError = Just ServerError })
component:: forall query m. Monad m=> System m-> Capability m-> Project.Capability m-> H.Component HH.HTML query OverviewInput ProjectEvent mcomponent system caps pcaps = H.mkComponent{ initialState, render, eval: H.mkEval $ H.defaultEval{ handleAction = eval, initialize = Just Initialize}} whereinitialState :: OverviewInput -> OverviewStateinitialState input ={ selectedProject: input}
component ::forall query m.Monad m =>System m ->Capability m ->Project.Capability m ->H.Component HH.HTML query OverviewInput ProjectEvent mcomponent system caps pcaps =H.mkComponent{ initialState, render, eval:H.mkEval$ H.defaultEval{ handleAction = eval, initialize = Just Initialize}}whereinitialState :: OverviewInput -> OverviewStateinitialState input ={ selectedProject: input}
render :: OverviewState -> H.ComponentHTML OverviewAction Slots mrender st =HH.section[P.classes (ClassName <$> ["section-border", "border-primary"])][HH.div[P.classes (ClassName <$> ["container", "pt-6"])][HH.h1[P.classes (ClassName <$> ["mb-0", "font-weight-bold", "text-center"])][HH.text "Project Overview"],HH.p[P.classes (ClassName <$> ["col-md-5", "text-muted", "text-center", "mx-auto"])][HH.text "Your project timeline"],HH.div_[HH.slot _projectList unit (Project.projectListComponent system pcaps) st.selectedProject (Just <<< ProjectSelected)],HH.div[P.classes (ClassName <$> if isNothing st.selectedProject then ["collapse"] else [])][]
render :: OverviewState -> H.ComponentHTML OverviewAction Slots mrender st =HH.section[ P.classes (ClassName <$> [ "section-border", "border-primary" ]) ][ HH.div[ P.classes (ClassName <$> [ "container", "pt-6" ]) ][ HH.h1[ P.classes (ClassName <$> [ "mb-0", "font-weight-bold", "text-center" ]) ][ HH.text "Project Overview" ], HH.p[ P.classes (ClassName <$> [ "col-md-5", "text-muted", "text-center", "mx-auto" ]) ][ HH.text "Your project timeline" ], HH.div_[ HH.slot _projectList unit (Project.projectListComponent system pcaps) st.selectedProject (Just <<< ProjectSelected) ], HH.div[ P.classes (ClassName <$> if isNothing st.selectedProject then [ "collapse" ] else []) ][]
eval :: OverviewAction -> H.HalogenM OverviewState OverviewAction Slots ProjectEvent m Uniteval action = docase action ofInitialize -> dopure unit
eval :: OverviewAction -> H.HalogenM OverviewState OverviewAction Slots ProjectEvent m Uniteval action = docase action ofInitialize -> dopure unitInvite _ -> dopure unitProjectSelected p -> docurrentProject <- H.gets (_.selectedProject)when (all (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject)$ doH.raise (ProjectChange p)H.modify_ (_ { selectedProject = Just p })
Invite _ -> dopure unitProjectSelected p -> docurrentProject <- H.gets (_.selectedProject)when (all (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject) $ doH.raise (ProjectChange p)H.modify_ (_ { selectedProject = Just p })
type ProjectCState ={ selectedProject :: Maybe Project, projects :: Array Project}
type ProjectCState= { selectedProject :: Maybe Project, projects :: Array Project}
projectListComponent:: forall query input m. Monad m=> System m-> Capability m-> H.Component HH.HTML query ProjectInput Project mprojectListComponent console caps = H.mkComponent{ initialState, render, eval: H.mkEval $ H.defaultEval{ handleAction = eval, initialize = Just Initialize}} whereinitialState :: ProjectInput -> ProjectCStateinitialState input = { selectedProject: input, projects: [] }
projectListComponent ::forall query input m.Monad m =>System m ->Capability m ->H.Component HH.HTML query ProjectInput Project mprojectListComponent console caps =H.mkComponent{ initialState, render, eval:H.mkEval$ H.defaultEval{ handleAction = eval, initialize = Just Initialize}}whereinitialState :: ProjectInput -> ProjectCStateinitialState input = { selectedProject: input, projects: [] }
render :: forall slots. ProjectCState -> H.ComponentHTML ProjectAction slots mrender st =HH.div[P.classes (ClassName <$> ["form-group"])][ HH.label[ P.classes (ClassName <$> ["sr-only"])
render :: forall slots. ProjectCState -> H.ComponentHTML ProjectAction slots mrender st =HH.div[ P.classes (ClassName <$> [ "form-group" ]) ][ HH.label[ P.classes (ClassName <$> [ "sr-only" ])
( [HH.option [P.selected (isNothing st.selectedProject), P.disabled true] [HH.text "Select a project"]]<> map renderOption st.projects
( [ HH.option [ P.selected (isNothing st.selectedProject), P.disabled true ] [ HH.text "Select a project" ] ]<> map renderOption st.projects
eval :: ProjectAction -> H.HalogenM ProjectCState ProjectAction () Project m Uniteval = case _ ofInitialize -> dores <- lift caps.listProjectscase res ofLeft _ -> lift <<< console.error $ "Could not retrieve project list."Right projects -> H.modify_ (_ { projects = projects })
eval :: ProjectAction -> H.HalogenM ProjectCState ProjectAction () Project m Uniteval = case _ ofInitialize -> dores <- lift caps.listProjectscase res ofLeft _ -> lift <<< console.error $ "Could not retrieve project list."Right projects -> H.modify_ (_ { projects = projects })Select i -> doprojects <- H.gets (_.projects)lift <<< console.log $ "Selected project index " <> show itraverse_ H.raise (index projects (i - 1))
Select i -> doprojects <- H.gets (_.projects)lift <<< console.log $ "Selected project index " <> show itraverse_ H.raise (index projects (i - 1))
result <- get RF.json "/api/projects"EC.liftEffect <<< runExceptT $ case result ofLeft err -> throwError $ Error { status: Nothing, message: printError err }Right r -> case r.status ofStatusCode 403 ->throwError ForbiddenStatusCode 200 -> dorecords <- except $ lmap (ParseFailure r.body) (decodeJson r.body)traverse parseProject recordsother ->throwError $ Error { status: Just other, message: r.statusText }
result <- get RF.json "/api/projects"EC.liftEffect <<< runExceptT$ case result ofLeft err -> throwError $ Error { status: Nothing, message: printError err }Right r -> case r.status ofStatusCode 403 -> throwError ForbiddenStatusCode 200 -> dorecords <- except $ lmap (ParseFailure r.body) (decodeJson r.body)traverse parseProject recordsother -> throwError $ Error { status: Just other, message: r.statusText }
type SignupState ={ username :: Maybe String, password :: Maybe String, passwordConfirm :: Maybe String, recoveryType :: RecoveryType, recoveryEmail :: Maybe String, recoveryZAddr :: Maybe String, signupErrors :: Array SignupError}
type SignupState= { username :: Maybe String, password :: Maybe String, passwordConfirm :: Maybe String, recoveryType :: RecoveryType, recoveryEmail :: Maybe String, recoveryZAddr :: Maybe String, signupErrors :: Array SignupError}
type Capability m ={ checkUsername :: String -> m Acc.UsernameCheckResponse, checkZAddr :: String -> m Acc.ZAddrCheckResponse, signup :: SignupRequest -> m SignupResponse, getRecaptchaResponse :: Maybe String -> m (Maybe String)}
type Capability m= { checkUsername :: String -> m Acc.UsernameCheckResponse, checkZAddr :: String -> m Acc.ZAddrCheckResponse, signup :: SignupRequest -> m SignupResponse, getRecaptchaResponse :: Maybe String -> m (Maybe String)}
component:: forall query input m. Monad m=> System m-> Capability m-> Config-> H.Component HH.HTML query input SignupResult mcomponent system caps conf = H.mkComponent{ initialState, render, eval: H.mkEval $ H.defaultEval { handleAction = eval }} whereinitialState :: input -> SignupStateinitialState _ ={ username: Nothing, password: Nothing, passwordConfirm: Nothing, recoveryType: RecoveryEmail, recoveryEmail: Nothing, recoveryZAddr: Nothing, signupErrors: []}
component ::forall query input m.Monad m =>System m ->Capability m ->Config ->H.Component HH.HTML query input SignupResult mcomponent system caps conf =H.mkComponent{ initialState, render, eval: H.mkEval $ H.defaultEval { handleAction = eval }}whereinitialState :: input -> SignupStateinitialState _ ={ username: Nothing, password: Nothing, passwordConfirm: Nothing, recoveryType: RecoveryEmail, recoveryEmail: Nothing, recoveryZAddr: Nothing, signupErrors: []}
render :: forall slots. SignupState -> H.ComponentHTML SignupAction slots mrender st =HH.section[ P.classes (ClassName <$> ["section-border", "border-primary"]) ][ HH.div[ P.classes (ClassName <$> ["container", "d-flex", "flex-column"]) ]
render :: forall slots. SignupState -> H.ComponentHTML SignupAction slots mrender st =HH.section[ P.classes (ClassName <$> [ "section-border", "border-primary" ]) ][ HH.div[ P.classes (ClassName <$> [ "container", "d-flex", "flex-column" ]) ]
[ P.classes (ClassName <$> ["align-items-center", "pt-6"]) ][ HH.h1[ P.classes (ClassName <$> ["mb-0", "font-weight-bold", "text-center"]) ][ HH.text "Sign up" ], HH.p[ P.classes (ClassName <$> ["text-center", "text-muted", "col-md-5", "mx-auto"]) ][ HH.text "You can use either an email address or zcash payment address for account recovery." ]]
[ P.classes (ClassName <$> [ "align-items-center", "pt-6" ]) ][ HH.h1[ P.classes (ClassName <$> [ "mb-0", "font-weight-bold", "text-center" ]) ][ HH.text "Sign up" ], HH.p[ P.classes (ClassName <$> [ "text-center", "text-muted", "col-md-5", "mx-auto" ]) ][ HH.text "You can use either an email address or zcash payment address for account recovery." ]]
[ P.classes (ClassName <$> ["row", "align-items-center", "justify-content-center", "no-gutters"]) ][ HH.div[ P.classes (ClassName <$> ["col-12", "col-lg-4", "py-8", "py-md-0"]) ][ HH.form[ P.classes (ClassName <$> ["mb-6"]), E.onSubmit (Just <<< Signup)][ HH.div[ P.classes (ClassName <$> ["form-group"]) ][ HH.label [ P.for "username" ] [ HH.text "Username" ], HH.input[ P.type_ P.InputText, P.classes (ClassName <$> ["form-control"]), P.id_ "username", P.placeholder "Choose a handle (username)", P.required true, P.autofocus true, P.value (fromMaybe "" st.username), E.onValueInput (Just <<< SetUsername)]], HH.div[ P.classes (ClassName <$> ["form-group"]) ][ HH.label [ P.for "password" ] [ HH.text "Password" ], HH.input[ P.type_ P.InputPassword, P.classes (ClassName <$> ["form-control"]), P.id_ "password", P.placeholder "Enter a unique password", P.required true, P.value (fromMaybe "" st.password), E.onValueInput (Just <<< SetPassword)], HH.input[ P.type_ P.InputPassword, P.classes (ClassName <$> ["form-control"]), P.id_ "passwordConfirm", P.placeholder "Enter a unique password", P.required true, P.value (fromMaybe "" st.passwordConfirm), E.onValueInput (Just <<< ConfirmPassword)]], recoverySwitch st.recoveryType, recoveryField st, HH.div[ P.classes (ClassName <$> ["form-group", "mb-3"]) ][ HH.div[ P.classes (ClassName <$> ["g-recaptcha", "mx-auto"]), P.attr (AttrName "data-sitekey") conf.recaptchaKey] []
[ P.classes (ClassName <$> [ "row", "align-items-center", "justify-content-center", "no-gutters" ]) ][ HH.div[ P.classes (ClassName <$> [ "col-12", "col-lg-4", "py-8", "py-md-0" ]) ][ HH.form[ P.classes (ClassName <$> [ "mb-6" ]), E.onSubmit (Just <<< Signup)][ HH.div[ P.classes (ClassName <$> [ "form-group" ]) ][ HH.label [ P.for "username" ] [ HH.text "Username" ], HH.input[ P.type_ P.InputText, P.classes (ClassName <$> [ "form-control" ]), P.id_ "username", P.placeholder "Choose a handle (username)", P.required true, P.autofocus true, P.value (fromMaybe "" st.username), E.onValueInput (Just <<< SetUsername)]], HH.div[ P.classes (ClassName <$> [ "form-group" ]) ][ HH.label [ P.for "password" ] [ HH.text "Password" ], HH.input[ P.type_ P.InputPassword, P.classes (ClassName <$> [ "form-control" ]), P.id_ "password", P.placeholder "Enter a unique password", P.required true, P.value (fromMaybe "" st.password), E.onValueInput (Just <<< SetPassword)], HH.input[ P.type_ P.InputPassword, P.classes (ClassName <$> [ "form-control" ]), P.id_ "passwordConfirm", P.placeholder "Enter a unique password", P.required true, P.value (fromMaybe "" st.passwordConfirm), E.onValueInput (Just <<< ConfirmPassword)]], recoverySwitch st.recoveryType, recoveryField st, HH.div[ P.classes (ClassName <$> [ "form-group", "mb-3" ]) ][ HH.div[ P.classes (ClassName <$> [ "g-recaptcha", "mx-auto" ]), P.attr (AttrName "data-sitekey") conf.recaptchaKey][]], HH.button[ P.classes (ClassName <$> [ "btn", "btn-block", "btn-primary" ]) ][ HH.text "Sign up" ]], HH.p[ P.classes (ClassName <$> [ "mb-0", "font-size-sm", "text-center", "text-muted" ]) ][ HH.text "Already have an account? ", HH.a[ P.href "#login" ][ HH.text "Sign in" ]]
, HH.button[ P.classes (ClassName <$> ["btn", "btn-block", "btn-primary"]) ][ HH.text "Sign up" ]], HH.p[ P.classes (ClassName <$> ["mb-0", "font-size-sm", "text-center", "text-muted"]) ][ HH.text "Already have an account? ", HH.a[ P.href "#login" ][ HH.text "Sign in" ]]
eval :: SignupAction -> H.HalogenM SignupState SignupAction () SignupResult m Uniteval = case _ ofSetUsername user -> doures <- lift $ caps.checkUsername userH.modify_ (_ { username = Just user })case ures ofAcc.UsernameCheckOK -> pure unitAcc.UsernameCheckTaken -> H.modify_ (_ { signupErrors = [UsernameTaken] })SetPassword pass -> doH.modify_ (_ { password = Just pass })confirm <- H.gets (_.passwordConfirm)when (any (notEq pass) confirm) (H.modify_ (_ { signupErrors = [PasswordMismatch] }))ConfirmPassword confirm -> doH.modify_ (_ { passwordConfirm = Just confirm })password <- H.gets (_.password)when (any (notEq confirm) password) (H.modify_ (_ { signupErrors = [PasswordMismatch] }))SetRecoveryType t -> H.modify_ (_ { recoveryType = t })SetRecoveryEmail email -> H.modify_ (_ { recoveryEmail = Just email })SetRecoveryZAddr addr -> dolift $ system.log "Switching to signin..."zres <- lift $ caps.checkZAddr addrH.modify_ (_ { recoveryZAddr = Just addr })case zres ofAcc.ZAddrCheckValid -> pure unitAcc.ZAddrCheckInvalid -> H.modify_ (_ { signupErrors = [ZAddrInvalid] })Signin ev -> dolift $ system.log "Switching to signin..."lift $ system.preventDefault (ME.toEvent ev)H.raise SigninNav
eval :: SignupAction -> H.HalogenM SignupState SignupAction () SignupResult m Uniteval = case _ ofSetUsername user -> doures <- lift $ caps.checkUsername userH.modify_ (_ { username = Just user })case ures ofAcc.UsernameCheckOK -> pure unitAcc.UsernameCheckTaken -> H.modify_ (_ { signupErrors = [ UsernameTaken ] })SetPassword pass -> doH.modify_ (_ { password = Just pass })confirm <- H.gets (_.passwordConfirm)when (any (notEq pass) confirm) (H.modify_ (_ { signupErrors = [ PasswordMismatch ] }))ConfirmPassword confirm -> doH.modify_ (_ { passwordConfirm = Just confirm })password <- H.gets (_.password)when (any (notEq confirm) password) (H.modify_ (_ { signupErrors = [ PasswordMismatch ] }))SetRecoveryType t -> H.modify_ (_ { recoveryType = t })SetRecoveryEmail email -> H.modify_ (_ { recoveryEmail = Just email })SetRecoveryZAddr addr -> dolift $ system.log "Switching to signin..."zres <- lift $ caps.checkZAddr addrH.modify_ (_ { recoveryZAddr = Just addr })case zres ofAcc.ZAddrCheckValid -> pure unitAcc.ZAddrCheckInvalid -> H.modify_ (_ { signupErrors = [ ZAddrInvalid ] })Signin ev -> dolift $ system.log "Switching to signin..."lift $ system.preventDefault (ME.toEvent ev)H.raise SigninNavSignup ev -> dolift $ system.preventDefault evrecType <- H.gets (_.recoveryType)usernameV <- V <<< note [ UsernameRequired ] <$> H.gets (_.username)pwdFormV <- V <<< note [ PasswordRequired ] <$> H.gets (_.password)pwdConfV <- V <<< note [ ConfirmRequired ] <$> H.gets (_.passwordConfirm)recoveryType <- H.gets (_.recoveryType)recoveryV <- case recoveryType ofRecoveryEmail -> V <<< note [ EmailRequired ] <<< map Acc.RecoverByEmail <$> H.gets (_.recoveryEmail)RecoveryZAddr -> V <<< note [ ZAddrRequired ] <<< map Acc.RecoverByZAddr <$> H.gets (_.recoveryZAddr)recapV <- lift $ V <<< note [ CaptchaError ] <$> caps.getRecaptchaResponse Nothinglift $ system.log "Sending signup request..."letreqV :: V (Array SignupError) Acc.SignupRequestreqV =signupRequest <$> usernameV<*> ( (eq <$> pwdFormV <*> pwdConfV)`andThen`(if _ then pwdFormV else invalid [ PasswordMismatch ]))<*> recoveryV<*> recapVcase toEither reqV ofLeft errors -> dolift $ system.log "Got signup HTTP error."H.modify_ (_ { signupErrors = errors })Right req -> doresponse <- lift (caps.signup req)lift <<< system.log $ "Got signup response " <> show responsecase response ofAcc.SignupOK -> H.raise (SignupComplete $ req.username)Acc.CaptchaInvalid -> H.modify_ (_ { signupErrors = [ CaptchaError ] })Acc.ZAddrInvalid -> H.modify_ (_ { signupErrors = [ ZAddrInvalid ] })Acc.UsernameTaken -> H.modify_ (_ { signupErrors = [ UsernameTaken ] })Acc.ServiceError c m -> H.modify_ (_ { signupErrors = [ APIError { status: c, message: m } ] })
Signup ev -> dolift $ system.preventDefault evrecType <- H.gets (_.recoveryType)usernameV <- V <<< note [UsernameRequired] <$> H.gets (_.username)pwdFormV <- V <<< note [PasswordRequired] <$> H.gets (_.password)pwdConfV <- V <<< note [ConfirmRequired ] <$> H.gets (_.passwordConfirm)recoveryType <- H.gets (_.recoveryType)recoveryV <- case recoveryType ofRecoveryEmail ->V <<< note [EmailRequired] <<< map Acc.RecoverByEmail <$> H.gets (_.recoveryEmail)RecoveryZAddr ->V <<< note [ZAddrRequired] <<< map Acc.RecoverByZAddr <$> H.gets (_.recoveryZAddr)recapV <- lift $ V <<< note [CaptchaError] <$> caps.getRecaptchaResponse Nothinglift $ system.log "Sending signup request..."let reqV :: V (Array SignupError) Acc.SignupRequestreqV = signupRequest <$> usernameV<*> ((eq <$> pwdFormV <*> pwdConfV) `andThen`(if _ then pwdFormV else invalid [PasswordMismatch]))<*> recoveryV<*> recapVcase toEither reqV ofLeft errors -> dolift $ system.log "Got signup HTTP error."H.modify_ (_ { signupErrors = errors })Right req -> doresponse <- lift (caps.signup req)lift <<< system.log $ "Got signup response " <> show responsecase response ofAcc.SignupOK -> H.raise (SignupComplete $ req.username)Acc.CaptchaInvalid -> H.modify_ (_ { signupErrors = [CaptchaError] })Acc.ZAddrInvalid -> H.modify_ (_ { signupErrors = [ZAddrInvalid] })Acc.UsernameTaken -> H.modify_ (_ { signupErrors = [UsernameTaken] })Acc.ServiceError c m -> H.modify_ (_ { signupErrors = [APIError { status: c, message: m }]})
[ P.classes (ClassName <$> ["form-group", "mb-3"]) ][ HH.label[ P.for "recoverySwitch" ][ HH.text "Choose a recovery method" ]
[ P.classes (ClassName <$> [ "form-group", "mb-3" ]) ][ HH.label[ P.for "recoverySwitch" ][ HH.text "Choose a recovery method" ]
[ P.classes (ClassName <$> ["form-group", "mb-3"]), CSS.style dodisplay flexflexFlow row nowrap][ HH.span[ P.classes (ClassName <$> [ if rt == RecoveryEmail then "text-success" else "text-muted"]) ][ HH.text "Email" ], HH.div[ P.classes (ClassName <$> ["custom-control", "custom-switch", "custom-switch-light", "mx-3"]) ][ HH.input[ P.type_ P.InputCheckbox, P.classes (ClassName <$> ["custom-control-input"]), P.id_ "recoverySwitch", E.onChecked (\b -> Just <<< SetRecoveryType $ if b then RecoveryZAddr else RecoveryEmail)], HH.label [ P.classes (ClassName <$> [ "custom-control-label" ]), P.for "recoverySwitch" ] []
[ P.classes (ClassName <$> [ "form-group", "mb-3" ]), CSS.style dodisplay flexflexFlow row nowrap][ HH.span[ P.classes (ClassName <$> [ if rt == RecoveryEmail then "text-success" else "text-muted" ]) ][ HH.text "Email" ], HH.div[ P.classes (ClassName <$> [ "custom-control", "custom-switch", "custom-switch-light", "mx-3" ]) ][ HH.input[ P.type_ P.InputCheckbox, P.classes (ClassName <$> [ "custom-control-input" ]), P.id_ "recoverySwitch", E.onChecked (\b -> Just <<< SetRecoveryType $ if b then RecoveryZAddr else RecoveryEmail)], HH.label [ P.classes (ClassName <$> [ "custom-control-label" ]), P.for "recoverySwitch" ] []], HH.span[ P.classes (ClassName <$> [ if rt == RecoveryZAddr then "text-success" else "text-muted" ]) ][ HH.text "Z-Address" ]
, HH.input[ P.type_ P.InputEmail, P.classes (ClassName <$> ["form-control"]), P.id_ "email", P.placeholder "name@address.com", P.value (fromMaybe "" st.recoveryEmail), E.onValueInput (Just <<< SetRecoveryEmail)]
, HH.input[ P.type_ P.InputEmail, P.classes (ClassName <$> [ "form-control" ]), P.id_ "email", P.placeholder "name@address.com", P.value (fromMaybe "" st.recoveryEmail), E.onValueInput (Just <<< SetRecoveryEmail)]
[ HH.label[ P.for "zaddr" ][ HH.text "Zcash Shielded Address", HH.a[ P.attr (AttrName "data-toggle") "modal", P.href "#modalAboutZAddr"
[ HH.label[ P.for "zaddr" ][ HH.text "Zcash Shielded Address", HH.a[ P.attr (AttrName "data-toggle") "modal", P.href "#modalAboutZAddr"][ HH.img [ P.src "/assets/img/icons/duotone-icons/Code/Info-circle.svg" ]]
[ HH.img [ P.src "/assets/img/icons/duotone-icons/Code/Info-circle.svg" ]
, HH.input[ P.type_ P.InputText, P.classes (ClassName <$> [ "form-control" ]), P.id_ "email", P.placeholder "Enter a Zcash shielded address", P.value (fromMaybe "" st.recoveryZAddr), E.onValueInput (Just <<< SetRecoveryZAddr)
import Aftok.Api.Timeline( TimelineError,Event(..),Interval(..),TimeInterval,KeyedEvent,TimeSpan,start, end, interval,event, eventTime, keyedEvent)
import Aftok.Api.Timeline( TimelineError, Event(..), Interval(..), TimeInterval, KeyedEvent, TimeSpan, start, end, interval, event, eventTime, keyedEvent)
type TimelineLimits ={ bounds :: TimeInterval, current :: Instant}
type TimelineLimits= { bounds :: TimeInterval, current :: Instant}
type DayIntervals ={ dayBounds :: TimeInterval, loggedIntervals :: Array (Interval TimelineEvent)}
type DayIntervals= { dayBounds :: TimeInterval, loggedIntervals :: Array (Interval TimelineEvent)}
type TimelineState ={ selectedProject :: Maybe Project, history :: M.Map Date DayIntervals, active :: Maybe (Interval TimelineEvent), activeHistory :: M.Map Date DayIntervals}
type TimelineState= { selectedProject :: Maybe Project, history :: M.Map Date DayIntervals, active :: Maybe (Interval TimelineEvent), activeHistory :: M.Map Date DayIntervals}
type Capability m ={ timer :: EventSource m TimelineAction, logStart :: ProjectId -> m (Either TimelineError (KeyedEvent Instant)), logEnd :: ProjectId -> m (Either TimelineError (KeyedEvent Instant)), listIntervals :: ProjectId -> TimeSpan -> m (Either TimelineError (Array (Interval (KeyedEvent Instant)))), getLatestEvent :: ProjectId -> m (Either TimelineError (Maybe (KeyedEvent Instant)))}
type Capability m= { timer :: EventSource m TimelineAction, logStart :: ProjectId -> m (Either TimelineError (KeyedEvent Instant)), logEnd :: ProjectId -> m (Either TimelineError (KeyedEvent Instant)), listIntervals :: ProjectId -> TimeSpan -> m (Either TimelineError (Array (Interval (KeyedEvent Instant)))), getLatestEvent :: ProjectId -> m (Either TimelineError (Maybe (KeyedEvent Instant)))}
component:: forall query m. Monad m=> System m-> Capability m-> Project.Capability m-> H.Component HH.HTML query TimelineInput ProjectEvent mcomponent system caps pcaps = H.mkComponent{ initialState, render, eval: H.mkEval $ H.defaultEval{ handleAction = eval, initialize = Just Initialize}} whereinitialState :: TimelineInput -> TimelineStateinitialState input ={ selectedProject: input, history: M.empty, active: Nothing, activeHistory: M.empty}
component ::forall query m.Monad m =>System m ->Capability m ->Project.Capability m ->H.Component HH.HTML query TimelineInput ProjectEvent mcomponent system caps pcaps =H.mkComponent{ initialState, render, eval:H.mkEval$ H.defaultEval{ handleAction = eval, initialize = Just Initialize}}whereinitialState :: TimelineInput -> TimelineStateinitialState input ={ selectedProject: input, history: M.empty, active: Nothing, activeHistory: M.empty}
render :: TimelineState -> H.ComponentHTML TimelineAction Slots mrender st =HH.section[P.classes (ClassName <$> ["section-border", "border-primary"])][HH.div[P.classes (ClassName <$> ["container", "pt-6"])][HH.h1[P.classes (ClassName <$> ["mb-0", "font-weight-bold", "text-center"])][HH.text "Time Tracker"],HH.p[P.classes (ClassName <$> ["col-md-5", "text-muted", "text-center", "mx-auto"])][HH.text "Your project timeline"],HH.div_[HH.slot _projectList unit (Project.projectListComponent system pcaps) st.selectedProject (Just <<< ProjectSelected)],HH.div[P.classes (ClassName <$> if isNothing st.selectedProject then ["collapse"] else [])]([HH.div_[HH.button[P.classes (ClassName <$> ["btn", "btn-primary", "float-left", "my-2"]),E.onClick \_ -> Just Start,P.disabled (isJust st.active)
render :: TimelineState -> H.ComponentHTML TimelineAction Slots mrender st =HH.section[ P.classes (ClassName <$> [ "section-border", "border-primary" ]) ][ HH.div[ P.classes (ClassName <$> [ "container", "pt-6" ]) ][ HH.h1[ P.classes (ClassName <$> [ "mb-0", "font-weight-bold", "text-center" ]) ][ HH.text "Time Tracker" ], HH.p[ P.classes (ClassName <$> [ "col-md-5", "text-muted", "text-center", "mx-auto" ]) ][ HH.text "Your project timeline" ], HH.div_[ HH.slot _projectList unit (Project.projectListComponent system pcaps) st.selectedProject (Just <<< ProjectSelected) ], HH.div[ P.classes (ClassName <$> if isNothing st.selectedProject then [ "collapse" ] else []) ]( [ HH.div_[ HH.button[ P.classes (ClassName <$> [ "btn", "btn-primary", "float-left", "my-2" ]), E.onClick \_ -> Just Start, P.disabled (isJust st.active)][ HH.text "Start Work" ], HH.button[ P.classes (ClassName <$> [ "btn", "btn-primary", "float-right", "my-2" ]), E.onClick \_ -> Just Stop, P.disabled (isNothing st.active)][ HH.text "Stop Work" ]]
[HH.text "Start Work"],HH.button[P.classes (ClassName <$> ["btn", "btn-primary", "float-right", "my-2"]),E.onClick \_ -> Just Stop,P.disabled (isNothing st.active)][HH.text "Stop Work"]]] <> (historyLine <$> reverse (M.toUnfoldable $ unionHistories st.history st.activeHistory)))
<> (historyLine <$> reverse (M.toUnfoldable $ unionHistories st.history st.activeHistory)))
eval :: TimelineAction -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m Uniteval action = docase action ofInitialize -> dovoid $ H.subscribe caps.timercurrentProject <- H.gets (_.selectedProject)traverse_ setStateForProject currentProjectProjectSelected p -> dooldActive <- isJust <$> H.gets (_.active)currentProject <- H.gets (_.selectedProject)-- End any active intervals when switching projects.when (oldActive && any (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject) $ do(traverse_ logEnd currentProject)H.raise (ProjectChange p)setStateForProject pStart -> doproject <- H.gets (_.selectedProject)traverse_ logStart projectStop -> docurrentProject <- H.gets (_.selectedProject)traverse_ logEnd currentProjectRefresh -> dot <- lift $ system.nowH.modify_ (refresh t)-- common updates, irrespective of actionactive <- H.gets (_.active)activeHistory <- lift <<< map (fromMaybe M.empty) <<< runMaybeT $ toHistory system (U.fromMaybe active)H.modify_ (_ { activeHistory = activeHistory })logStart :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m UnitlogStart (Project' p) = dologged <- lift $ caps.logStart p.projectIdcase logged ofLeft err -> lift <<< system.log $ "Failed to start timer: " <> show errRight t -> H.modify_ (updateStart t)logEnd :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m UnitlogEnd (Project' p) = dologged <- lift $ caps.logEnd p.projectIdcase logged ofLeft err -> lift <<< system.log $ "Failed to stop timer: " <> show errRight t -> docurrentState <- H.getupdatedState <- lift $ updateStop system t currentStateH.put updatedState
eval :: TimelineAction -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m Uniteval action = docase action ofInitialize -> dovoid $ H.subscribe caps.timercurrentProject <- H.gets (_.selectedProject)traverse_ setStateForProject currentProjectProjectSelected p -> dooldActive <- isJust <$> H.gets (_.active)currentProject <- H.gets (_.selectedProject)-- End any active intervals when switching projects.when (oldActive && any (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject)$ do(traverse_ logEnd currentProject)H.raise (ProjectChange p)setStateForProject pStart -> doproject <- H.gets (_.selectedProject)traverse_ logStart projectStop -> docurrentProject <- H.gets (_.selectedProject)traverse_ logEnd currentProjectRefresh -> dot <- lift $ system.nowH.modify_ (refresh t)-- common updates, irrespective of actionactive <- H.gets (_.active)activeHistory <- lift <<< map (fromMaybe M.empty) <<< runMaybeT $ toHistory system (U.fromMaybe active)H.modify_ (_ { activeHistory = activeHistory })
setStateForProject :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m UnitsetStateForProject p = dotimeSpan <- TL.Before <$> lift system.nowDateTime -- FIXME, should come from a form controlintervals' <- lift $ caps.listIntervals (unwrap p).projectId timeSpanintervals <- lift $ case intervals' ofLeft err ->(system.log $ "Error occurred listing intervals" <> show err ) *>pure []Right ivals ->pure $ map (map LoggedEvent) ivals
logStart :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m UnitlogStart (Project' p) = dologged <- lift $ caps.logStart p.projectIdcase logged ofLeft err -> lift <<< system.log $ "Failed to start timer: " <> show errRight t -> H.modify_ (updateStart t)
history' <- lift <<< runMaybeT $ toHistory system intervalshist <- case history' ofNothing -> lift $ system.log "Project history was empty." *> pure M.emptyJust h -> pure h
logEnd :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m UnitlogEnd (Project' p) = dologged <- lift $ caps.logEnd p.projectIdcase logged ofLeft err -> lift <<< system.log $ "Failed to stop timer: " <> show errRight t -> docurrentState <- H.getupdatedState <- lift $ updateStop system t currentStateH.put updatedState
latestEventResponse <- lift $ caps.getLatestEvent (unwrap p).projectIdnow <- lift $ system.nowactive <- lift $ case latestEventResponse ofLeft err ->(system.log $ "Error occurred retrieving the latest event: " <> show err) *>pure NothingRight latestEvent -> dolet activeInterval :: TL.KeyedEvent Instant -> m (Maybe (Interval TimelineEvent))
setStateForProject :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m UnitsetStateForProject p = dotimeSpan <- TL.Before <$> lift system.nowDateTime -- FIXME, should come from a form controlintervals' <- lift $ caps.listIntervals (unwrap p).projectId timeSpanintervals <-lift$ case intervals' ofLeft err ->(system.log $ "Error occurred listing intervals" <> show err)*> pure []Right ivals -> pure $ map (map LoggedEvent) ivalshistory' <- lift <<< runMaybeT $ toHistory system intervalshist <- case history' ofNothing -> lift $ system.log "Project history was empty." *> pure M.emptyJust h -> pure hlatestEventResponse <- lift $ caps.getLatestEvent (unwrap p).projectIdnow <- lift $ system.nowactive <-lift$ case latestEventResponse ofLeft err ->(system.log $ "Error occurred retrieving the latest event: " <> show err)*> pure NothingRight latestEvent -> doletactiveInterval :: TL.KeyedEvent Instant -> m (Maybe (Interval TimelineEvent))
TL.StartEvent i ->(system.log $ "Project has an open active interval starting " <> show i) *>(Just <<< interval (LoggedEvent ev) <<< PhantomEvent <$> system.now)TL.StopEvent _ ->pure Nothingjoin <$> traverse activeInterval latestEvent
TL.StartEvent i ->(system.log $ "Project has an open active interval starting " <> show i)*> (Just <<< interval (LoggedEvent ev) <<< PhantomEvent <$> system.now)TL.StopEvent _ -> pure Nothingjoin <$> traverse activeInterval latestEventH.modify_ (_ { selectedProject = Just p, history = hist, active = active })
H.modify_ (_ { selectedProject = Just p, history = hist, active = active })historyLine:: forall w i. Tuple Date DayIntervals-> HH.HTML w ihistoryLine (Tuple d xs) =datedLine d xs.dayBounds xs.loggedIntervals
historyLine ::forall w i.Tuple Date DayIntervals ->HH.HTML w ihistoryLine (Tuple d xs) = datedLine d xs.dayBounds xs.loggedIntervals
datedLine:: forall w i. Date-> TimeInterval-> Array (Interval TimelineEvent)-> HH.HTML w i
datedLine ::forall w i.Date ->TimeInterval ->Array (Interval TimelineEvent) ->HH.HTML w i
[ CSS.style doborder solid (px 3.0) (rgb 0x00 0xFF 0x00)borderRadius px5 px5 px5 px5height (px $ 44.0)display flex, P.classes (ClassName <$> ["my-2"])](evalState (traverse (intervalHtml dateBounds) xs) 0.0)
[ CSS.style doborder solid (px 3.0) (rgb 0x00 0xFF 0x00)borderRadius px5 px5 px5 px5height (px $ 44.0)display flex, P.classes (ClassName <$> [ "my-2" ])](evalState (traverse (intervalHtml dateBounds) xs) 0.0)
intervalHtml:: forall w i. TimeInterval-> Interval TimelineEvent-> State Number (HH.HTML w i)
intervalHtml ::forall w i.TimeInterval ->Interval TimelineEvent ->State Number (HH.HTML w i)
let maxWidth = ilen limits.start limits.endileft = ilen limits.start (tlEventTime i.start)iwidth = ilen (tlEventTime i.start) (tlEventTime i.end)px5 = px (5.0)toPct n = 100.0 * n / maxWidth
letmaxWidth = ilen limits.start limits.endileft = ilen limits.start (tlEventTime i.start)iwidth = ilen (tlEventTime i.start) (tlEventTime i.end)px5 = px (5.0)toPct n = 100.0 * n / maxWidth
pure $ HH.div[ CSS.style dobackgroundColor (rgb 0xf0 0x98 0x18)marginLeft (pct $ toPct ileft - offset)width (pct $ max (toPct iwidth) 0.5)borderRadius px5 px5 px5 px5][]
pure$ HH.div[ CSS.style dobackgroundColor (rgb 0xf0 0x98 0x18)marginLeft (pct $ toPct ileft - offset)width (pct $ max (toPct iwidth) 0.5)borderRadius px5 px5 px5 px5][]
timer = EventSource.affEventSource \emitter -> dofiber <- Aff.forkAff $ forever doAff.delay $ Aff.Milliseconds 10000.0EventSource.emit emitter Refreshpure $ EventSource.Finalizer doAff.killFiber (error "Event source finalized") fiber
timer =EventSource.affEventSource \emitter -> dofiber <-Aff.forkAff$ forever doAff.delay $ Aff.Milliseconds 10000.0EventSource.emit emitter Refreshpure$ EventSource.Finalizer doAff.killFiber (error "Event source finalized") fiber
updateStop:: forall m. Monad m=> System m-> KeyedEvent Instant-> TimelineState-> m TimelineState
updateStop ::forall m.Monad m =>System m ->KeyedEvent Instant ->TimelineState ->m TimelineState
pure { selectedProject: st.selectedProject, history: maybe st.history (unionHistories st.history) newHistory, active: Nothing, activeHistory: M.empty}
pure{ selectedProject: st.selectedProject, history: maybe st.history (unionHistories st.history) newHistory, active: Nothing, activeHistory: M.empty}
let startOfDay = DateTime (date $ toDateTime i) bottomendOfDay = DT.adjust (Days 1.0) startOfDaystartInstant = fromDateTime startOfDayin TL.interval startInstant (maybe startInstant fromDateTime endOfDay)
letstartOfDay = DateTime (date $ toDateTime i) bottomendOfDay = DT.adjust (Days 1.0) startOfDay
localDayBounds:: forall m. Monad m=> System m-> Instant-> MaybeT m (Tuple Date TimeInterval)
startInstant = fromDateTime startOfDayinTL.interval startInstant (maybe startInstant fromDateTime endOfDay)localDayBounds ::forall m.Monad m =>System m ->Instant ->MaybeT m (Tuple Date TimeInterval)
splitInterval:: forall m. Monad m=> System m-> Interval TimelineEvent-> MaybeT m (Array (Tuple Date DayIntervals))
splitInterval ::forall m.Monad m =>System m ->Interval TimelineEvent ->MaybeT m (Array (Tuple Date DayIntervals))
let splitEvent = PhantomEvent (end bounds)currInterval = Tuple date { dayBounds: bounds, loggedIntervals: [interval (start i) splitEvent] }nextInterval = interval splitEvent (end i)
letsplitEvent = PhantomEvent (end bounds)currInterval = Tuple date { dayBounds: bounds, loggedIntervals: [ interval (start i) splitEvent ] }nextInterval = interval splitEvent (end i)
toHistory:: forall m. Monad m=> System m-> Array (Interval TimelineEvent)-> MaybeT m (M.Map Date DayIntervals)
toHistory ::forall m.Monad m =>System m ->Array (Interval TimelineEvent) ->MaybeT m (M.Map Date DayIntervals)
type System m ={ log :: String -> m Unit, error :: String -> m Unit, now :: m Instant, getHash :: m String, setHash :: String -> m Unit, nowDateTime :: m DateTime, preventDefault :: WE.Event -> m Unit, dateFFI :: DateFFI m}
type System m= { log :: String -> m Unit, error :: String -> m Unit, now :: m Instant, getHash :: m String, setHash :: String -> m Unit, nowDateTime :: m DateTime, preventDefault :: WE.Event -> m Unit, dateFFI :: DateFFI m}
midnightLocalJS year month day = JD.jsdateLocal{ year, month, day, hour: 0.0, minute: 0.0, second: 0.0, millisecond: 0.0}
midnightLocalJS year month day =JD.jsdateLocal{ year, month, day, hour: 0.0, minute: 0.0, second: 0.0, millisecond: 0.0}
projectId <- ProjectId <$> (note "Failed to decode project UUID" $ parseUUID projectIdStr)projectName <- project .: "projectName"
projectId <- ProjectId <$> (note "Failed to decode project UUID" $ parseUUID projectIdStr)projectName <- project .: "projectName"
except $ note ("Unable to convert date " <> show jsDate <> " to a valid DateTime value.")(JD.toDateTime jsDate)
except$ note ("Unable to convert date " <> show jsDate <> " to a valid DateTime value.")(JD.toDateTime jsDate)
parseDatedResponse:: forall t. Traversable t=> DecodeJson (t String)=> Either AJAX.Error (Response Json)-> ExceptT APIError Effect (t Instant)
parseDatedResponse ::forall t.Traversable t =>DecodeJson (t String) =>Either AJAX.Error (Response Json) ->ExceptT APIError Effect (t Instant)
StatusCode 403 ->throwError $ ForbiddenStatusCode 200 ->withExceptT (ParseFailure r.body) $ map fromDateTime <$> decodeDatedJson r.bodyother ->throwError $ Error { status: Just other, message: r.statusText }
StatusCode 403 -> throwError $ ForbiddenStatusCode 200 -> withExceptT (ParseFailure r.body) $ map fromDateTime <$> decodeDatedJson r.bodyother -> throwError $ Error { status: Just other, message: r.statusText }
[ P.classes (ClassName <$> ["modal-dialog", "modal-lg", "modal-dialog-centered"])-- , P.role "document"][ HH.div[ P.classes (ClassName <$> ["modal-content"])]children]
[ P.classes (ClassName <$> [ "modal-dialog", "modal-lg", "modal-dialog-centered" ])-- , P.role "document"][ HH.div[ P.classes (ClassName <$> [ "modal-content" ]) ]children]
void $ liftEffect $ matchesWith (match mainRoute) \oldMay new ->when (oldMay /= Just new) dolaunchAff_ <<< halogenIO.query <<< H.tell $ Navigate new
mainComponent = component liveSystem login signup timeline project overviewhalogenIO <- runUI mainComponent unit bodyvoid $ liftEffect$ matchesWith (match mainRoute) \oldMay new ->when (oldMay /= Just new) dolaunchAff_ <<< halogenIO.query <<< H.tell $ Navigate new
mainRoute = oneOf[ VSignup <$ lit "signup", VLogin <$ lit "login", VOverview <$ lit "overview", VTimeline <$ lit "timeline"]
mainRoute =oneOf[ VSignup <$ lit "signup", VLogin <$ lit "login", VOverview <$ lit "overview", VTimeline <$ lit "timeline"]
type MainState ={ view :: View, config :: Signup.Config, selectedProject :: Maybe Project}
type MainState= { view :: View, config :: Signup.Config, selectedProject :: Maybe Project}
type Slots =( login :: Login.Slot Unit, signup :: Signup.Slot Unit, overview :: Overview.Slot Unit, timeline :: Timeline.Slot Unit)
type Slots= ( login :: Login.Slot Unit, signup :: Signup.Slot Unit, overview :: Overview.Slot Unit, timeline :: Timeline.Slot Unit)
component:: forall input output m. Monad m=> System m-> Login.Capability m-> Signup.Capability m-> Timeline.Capability m-> Project.Capability m-> Overview.Capability m-> H.Component HH.HTML MainQuery input output mcomponent system loginCap signupCap tlCap pCap ovCap = H.mkComponent{ initialState, render, eval: H.mkEval $ H.defaultEval{ handleAction = handleAction, handleQuery = handleQuery, initialize = Just Initialize}}
component ::forall input output m.Monad m =>System m ->Login.Capability m ->Signup.Capability m ->Timeline.Capability m ->Project.Capability m ->Overview.Capability m ->H.Component HH.HTML MainQuery input output mcomponent system loginCap signupCap tlCap pCap ovCap =H.mkComponent{ initialState, render, eval:H.mkEval$ H.defaultEval{ handleAction = handleAction, handleQuery = handleQuery, initialize = Just Initialize}}
initialState :: input -> MainStateinitialState _ ={ view: VLoading, config: { recaptchaKey: "6LdiA78ZAAAAAGGvDId_JmDbhalduIDZSqbuikfq" }, selectedProject: Nothing}
initialState :: input -> MainStateinitialState _ ={ view: VLoading, config: { recaptchaKey: "6LdiA78ZAAAAAGGvDId_JmDbhalduIDZSqbuikfq" }, selectedProject: Nothing}
render :: MainState -> H.ComponentHTML MainAction Slots mrender st = case st.view ofVLoading ->HH.div [P.classes [ClassName "loader"]] [HH.text "Loading..."]
render :: MainState -> H.ComponentHTML MainAction Slots mrender st = case st.view ofVLoading -> HH.div [ P.classes [ ClassName "loader" ] ] [ HH.text "Loading..." ]VSignup ->HH.div_[ HH.slot _signup unit (Signup.component system signupCap st.config) unit (Just <<< SignupAction) ]VLogin ->HH.div_[ HH.slot _login unit (Login.component system loginCap) unit (Just <<< LoginAction) ]VOverview ->withNavBar$ HH.div_[ HH.slot _overview unit (Overview.component system ovCap pCap) st.selectedProject (Just <<< ProjectAction) ]VTimeline ->withNavBar$ HH.div_[ HH.slot _timeline unit (Timeline.component system tlCap pCap) st.selectedProject (Just <<< ProjectAction) ]
VSignup ->HH.div_[ HH.slot _signup unit (Signup.component system signupCap st.config) unit (Just <<< SignupAction) ]VLogin ->HH.div_[ HH.slot _login unit (Login.component system loginCap) unit (Just <<< LoginAction) ]
handleAction :: MainAction -> H.HalogenM MainState MainAction Slots output m UnithandleAction = case _ ofInitialize -> doroute <- lift system.getHashnextView <- case route of"login" -> pure VLogin"signup" -> pure VSignupother -> doresult <- lift loginCap.checkLogincase result ofAcc.LoginForbidden -> pure VLoginAcc.LoginError _ -> pure VLogin_ -> pure VTimelinenavigate nextViewSignupAction (Signup.SignupComplete _) -> navigate VTimelineSignupAction (Signup.SigninNav) -> navigate VLoginLoginAction (Login.LoginComplete _) -> navigate VTimelineLogoutAction -> dolift loginCap.logoutnavigate VLoginProjectAction (ProjectChange p) -> H.modify_ (_ { selectedProject = Just p })
VOverview ->withNavBar $ HH.div_[ HH.slot _overview unit (Overview.component system ovCap pCap) st.selectedProject (Just <<< ProjectAction) ]
handleQuery :: forall a. MainQuery a -> H.HalogenM MainState MainAction Slots output m (Maybe a)handleQuery = case _ ofNavigate view a -> docurrentView <- H.gets _.viewwhen (currentView /= view) $ navigate viewpure (Just a)
VTimeline ->withNavBar $ HH.div_[ HH.slot _timeline unit (Timeline.component system tlCap pCap) st.selectedProject (Just <<< ProjectAction) ]
navigate :: View -> H.HalogenM MainState MainAction Slots output m Unitnavigate view = dolift $ system.setHash (routeHash view)H.modify_ (_ { view = view })
handleAction :: MainAction -> H.HalogenM MainState MainAction Slots output m UnithandleAction = case _ ofInitialize -> doroute <- lift system.getHashnextView <- case route of"login" -> pure VLogin"signup" -> pure VSignupother -> doresult <- lift loginCap.checkLogincase result ofAcc.LoginForbidden -> pure VLoginAcc.LoginError _ -> pure VLogin_ -> pure VTimelinenavigate nextViewSignupAction (Signup.SignupComplete _) ->navigate VTimelineSignupAction (Signup.SigninNav) ->navigate VLoginLoginAction (Login.LoginComplete _) ->navigate VTimelineLogoutAction -> dolift loginCap.logoutnavigate VLoginProjectAction (ProjectChange p) ->H.modify_ (_ { selectedProject = Just p })handleQuery :: forall a. MainQuery a -> H.HalogenM MainState MainAction Slots output m (Maybe a)handleQuery = case _ ofNavigate view a -> docurrentView <- H.gets _.viewwhen (currentView /= view) $ navigate viewpure (Just a)navigate :: View -> H.HalogenM MainState MainAction Slots output m Unitnavigate view = dolift $ system.setHash (routeHash view)H.modify_ (_ { view = view })
HH.div_[HH.nav[P.classes (ClassName <$> ["navbar", "navbar-expand-lg", "navbar-light", "bg-white"])][HH.div[P.classes (ClassName <$> ["container-fluid"])][ brand, HH.ul [P.classes (ClassName <$> ["navbar-nav", "ml-auto"])] (map navItem nav), logout
HH.div_[ HH.nav[ P.classes (ClassName <$> [ "navbar", "navbar-expand-lg", "navbar-light", "bg-white" ]) ][ HH.div[ P.classes (ClassName <$> [ "container-fluid" ]) ][ brand, HH.ul [ P.classes (ClassName <$> [ "navbar-nav", "ml-auto" ]) ] (map navItem nav), logout]
brand = HH.div[P.classes (ClassName <$> ["navbar-brand"])][HH.h4[P.classes (ClassName <$> ["font-weight-bold"])][HH.text "Aftok"]]
brand =HH.div[ P.classes (ClassName <$> [ "navbar-brand" ]) ][ HH.h4[ P.classes (ClassName <$> [ "font-weight-bold" ]) ][ HH.text "Aftok" ]]
logout = HH.button[P.classes (ClassName <$> ["btn", "navbar-btn", "btn-sm", "btn-primary", "lift", "ml-auto"]),E.onClick \_ -> Just LogoutAction][HH.text "Logout"]
logout =HH.button[ P.classes (ClassName <$> [ "btn", "navbar-btn", "btn-sm", "btn-primary", "lift", "ml-auto" ]), E.onClick \_ -> Just LogoutAction][ HH.text "Logout" ]