Minor module reorg.
[?]
Jan 26, 2021, 6:22 AM
NAFJ6RB3KYDBSTSNB3WQSVUQEPUGG2RZCBWRF4XNT2UKSOXDNMDQCDependencies
- [2]
QH4UB73NFormat with purty. - [3]
J6S23MDGUse server timestamps for interval start and end. - [4]
EA5BFM5GSplit Login component into its own module. - [5]
ENNZIQJGUse live signup API for client. - [6]
PT4276XCAdd logout functionality. - [7]
ZIG57EE6Fix project selection, end log end on project switch. - [8]
RSF6UAJKBreak out api module for timeline. - [9]
ARX7SHY5Begin work on login UI. - [10]
OUR4PAOTUse local dates for display of intervals. - [11]
5R2Z7FSXInitial rendering for signup controls. - [12]
QMEYU4MWAdd display for prior intervals. - [13]
QAC2QJ32Add project overview page to client. - [14]
WRPIYG3EUse project listing functionality to check for whether we have a cookie. - [15]
QU5FW67RAdd project selection to time tracker. - [16]
B4MTB6UOPersist project across pages. - [17]
BFZN4SUAMake timeline component work. - [18]
TKGBRIQTLogin component now raises LoginComplete message. - [19]
2J37EVJMCheck for an open interval on project switch. - [20]
HO2PFRABClient login now handles response correctly. - [21]
JXG3FCXYUpgrade ps + halogen versions. - [22]
NJNMO72SAdd zcash.com submodule and update client to modern halogen. - [23]
SAESJLLYInitial experiments in hash routing. - [24]
AAALU5A2Fix client routing - [25]
IR75ZMX3Return actual events for interval ends, not just timestamps. - [26]
3LMXT7Z6preventDefault on login form submission. - [*]
RB2ETNIFAdd skeletal PureScript client project. - [*]
O2BZOX7MAdd signup form, captcha check.
Change contents
- edit in client/spago.dhall at line 10
, "rationals" - file addition: Json.purs[3.1]
module Aftok.Api.Json whereimport Preludeimport Control.Monad.Error.Class (throwError)import Control.Monad.Except.Trans (ExceptT, except, withExceptT)import Control.Monad.Trans.Class (lift)import Data.Argonaut.Core (Json)import Data.Argonaut.Decode (class DecodeJson, decodeJson)import Data.DateTime (DateTime)import Data.DateTime.Instant (Instant, fromDateTime)import Data.Functor.Compose (Compose(..))import Data.Either (Either(..), note)import Data.Foldable (class Foldable, foldr, foldl, foldMap)import Data.JSDate as JDimport Data.Maybe (Maybe(..))import Data.Newtype (class Newtype, unwrap, over)import Data.Traversable (class Traversable, traverse)import Effect (Effect)import Affjax as AJAXimport Affjax (Response, printError)import Affjax.StatusCode (StatusCode(..))import Aftok.Api.Types (APIError(..))newtype JsonCompose f g a= JsonCompose (Compose f g a)derive instance jsonComposeNewtype :: Newtype (JsonCompose f g a) _instance jsonComposeFunctor :: (Functor f, Functor g) => Functor (JsonCompose f g) wheremap f = over JsonCompose (map f)instance jsonComposeFoldable :: (Foldable f, Foldable g) => Foldable (JsonCompose f g) wherefoldr f b = foldr f b <<< unwrapfoldl f b = foldl f b <<< unwrapfoldMap f = foldMap f <<< unwrapinstance jsonComposeTraversable :: (Traversable f, Traversable g) => Traversable (JsonCompose f g) wheretraverse f = map JsonCompose <<< traverse f <<< unwrapsequence = traverse identityinstance jsonComposeDecodeJson :: (DecodeJson (f (g a))) => DecodeJson (JsonCompose f g a) wheredecodeJson json = JsonCompose <<< Compose <$> decodeJson jsondecompose :: forall f g a. JsonCompose f g a -> f (g a)decompose (JsonCompose (Compose fga)) = fgaparseJsonDate :: Json -> ExceptT String Effect DateTimeparseJsonDate json = dostr <- except $ decodeJson jsonparseDate strparseDate :: String -> ExceptT String Effect DateTimeparseDate str = dojsDate <- lift $ JD.parse strexcept$ note ("Unable to convert date " <> show jsDate <> " to a valid DateTime value.")(JD.toDateTime jsDate)decodeDatedJson :: forall t. Traversable t => DecodeJson (t String) => Json -> ExceptT String Effect (t DateTime)decodeDatedJson json = dodecoded <- except $ decodeJson jsontraverse parseDate decodedparseDatedResponse ::forall t.Traversable t =>DecodeJson (t String) =>Either AJAX.Error (Response Json) ->ExceptT APIError Effect (t Instant)parseDatedResponse = case _ ofLeft err -> throwError $ Error { status: Nothing, message: printError err }Right r -> case r.status ofStatusCode 403 -> throwError $ ForbiddenStatusCode 200 -> withExceptT (ParseFailure r.body) $ map fromDateTime <$> decodeDatedJson r.bodyother -> throwError $ Error { status: Just other, message: r.statusText } - file addition: Project.purs[3.1]
module Aftok.Api.Project whereimport Preludeimport Control.Monad.Except.Trans (ExceptT, runExceptT, except, withExceptT)import Control.Monad.Error.Class (throwError)import Data.Argonaut.Core (Json)import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:))import Data.Bifunctor (lmap)import Data.DateTime (DateTime)import Data.Either (Either(..), note)import Data.Maybe (Maybe(..))import Data.Newtype (class Newtype)import Data.Rational (Rational)import Data.Time.Duration (Hours)import Data.Traversable (traverse)import Data.UUID (UUID, parseUUID)import Effect (Effect)import Effect.Aff (Aff)import Effect.Class as ECimport Affjax (get, printError)import Affjax.StatusCode (StatusCode(..))import Affjax.ResponseFormat as RFimport Aftok.Types( UserId, ProjectId(..))import Aftok.Api.Types( APIError(..) )import Aftok.Api.Json (parseDate)newtype Project' date= Project'{ projectId :: ProjectId, projectName :: String, inceptionDate :: date, initiator :: UUID}derive instance newtypeProject :: Newtype (Project' a) _type Project= Project' DateTimedata ProjectEvent= ProjectChange Projectinstance decodeJsonProject :: DecodeJson (Project' String) wheredecodeJson json = dox <- decodeJson jsonproject <- x .: "project"projectIdStr <- x .: "projectId"projectId <- ProjectId <$> (note "Failed to decode project UUID" $ parseUUID projectIdStr)projectName <- project .: "projectName"inceptionDate <- project .: "inceptionDate"initiatorStr <- project .: "initiator"initiator <- note "Failed to decode initiator UUID" $ parseUUID initiatorStrpure $ Project' { projectId, projectName, inceptionDate, initiator }newtype Member' date= Member'{ userId :: UserId, handle :: String, joinedOn :: date, timeDevoted :: Hours, revShareFrac :: Rational}listProjects :: Aff (Either APIError (Array Project))listProjects = doresult <- 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 }parseProject :: Json -> ExceptT APIError Effect ProjectparseProject json = doProject' p <- except <<< lmap (ParseFailure json) $ decodeJson jsonpdate <- withExceptT (ParseFailure json) $ parseDate p.inceptionDatepure $ Project' (p { inceptionDate = pdate }) - replacement in client/src/Aftok/Api/Timeline.purs at line 28
import Aftok.Types (APIError, decompose, parseDatedResponse, ProjectId(..), pidStr)import Aftok.Types (ProjectId(..), pidStr)import Aftok.Api.Types (APIError)import Aftok.Api.Json (decompose, parseDatedResponse) - file addition: Types.purs[3.1]
module Aftok.Api.Types whereimport Preludeimport Affjax.StatusCode (StatusCode)import Data.Argonaut.Core (Json, stringify)import Data.Maybe (Maybe)data APIError= Forbidden| ParseFailure Json String| Error { status :: Maybe StatusCode, message :: String }instance showAPIError :: Show APIError whereshow = case _ ofForbidden -> "Forbidden"ParseFailure js e -> "ParseFailure (" <> show (stringify js) <> ") " <> show eError r -> "Error { status: " <> show r.status <> ", message: " <> r.message <> "}" - replacement in client/src/Aftok/Overview.purs at line 14
-- import Data.DateTime.Instant (Instant, unInstant, fromDateTime, toDateTime)import Data.DateTime.Instant (Instant) - replacement in client/src/Aftok/Overview.purs at line 55
import Aftok.Project as Projectimport Aftok.ProjectList as ProjectList - replacement in client/src/Aftok/Overview.purs at line 57
import Aftok.Types (System, Project, ProjectEvent(..))import Aftok.Types (System, ProjectId)import Aftok.Api.Project (Project, ProjectEvent(..), Member') - replacement in client/src/Aftok/Overview.purs at line 80
= ( projectList :: Project.ProjectListSlot Unit= ( projectList :: ProjectList.Slot Unit - replacement in client/src/Aftok/Overview.purs at line 86
= {= { getProjectMembers :: ProjectId -> m (Array (Member' Instant)) - replacement in client/src/Aftok/Overview.purs at line 94
Project.Capability m ->ProjectList.Capability m -> - replacement in client/src/Aftok/Overview.purs at line 124
[ HH.text "Your project timeline" ][ HH.text "Your project details" ] - replacement in client/src/Aftok/Overview.purs at line 126
[ HH.slot _projectList unit (Project.projectListComponent system pcaps) st.selectedProject (Just <<< ProjectSelected) ][ HH.slot _projectList unit (ProjectList.component system pcaps) st.selectedProject (Just <<< ProjectSelected) ] - replacement in client/src/Aftok/Overview.purs at line 148
apiCapability = {}apiCapability ={ getProjectMembers: \_ -> pure []} - replacement in client/src/Aftok/Overview.purs at line 153
mockCapability = {}[3.4524]mockCapability ={ getProjectMembers: \_ -> pure []} - file move: Project.purs → ProjectList.purs
- replacement in client/src/Aftok/ProjectList.purs at line 1
module Aftok.Project wheremodule Aftok.ProjectList where - edit in client/src/Aftok/ProjectList.purs at line 5
import Control.Monad.Except.Trans (ExceptT, runExceptT, except, withExceptT)import Control.Monad.Error.Class (throwError) - replacement in client/src/Aftok/ProjectList.purs at line 6[3.107]→[3.728:761](∅→∅),[3.728]→[3.728:761](∅→∅),[3.761]→[3.4585:4626](∅→∅),[3.4626]→[3.826:855](∅→∅),[3.826]→[3.826:855](∅→∅)
import Data.Argonaut.Core (Json)import Data.Argonaut.Decode (decodeJson)import Data.Bifunctor (lmap)-- import Data.Bifunctor (lmap) - replacement in client/src/Aftok/ProjectList.purs at line 10[3.70]→[3.108:154](∅→∅),[3.329]→[3.108:154](∅→∅),[3.1026]→[3.108:154](∅→∅),[3.1097]→[3.1097:1120](∅→∅)
import Data.Traversable (traverse, traverse_)import Effect (Effect)import Data.Traversable (traverse_) - edit in client/src/Aftok/ProjectList.purs at line 12
import Effect.Class as ECimport Affjax (get, printError)import Affjax.StatusCode (StatusCode(..))import Affjax.ResponseFormat as RF - replacement in client/src/Aftok/ProjectList.purs at line 13
( APIError(..), System, parseDate( System - replacement in client/src/Aftok/ProjectList.purs at line 15
, Project'(..))import Aftok.Api.Types( APIError)import Aftok.Api.Project( Project'(..) - edit in client/src/Aftok/ProjectList.purs at line 22
, listProjects - replacement in client/src/Aftok/ProjectList.purs at line 30
type ProjectInputtype Input - replacement in client/src/Aftok/ProjectList.purs at line 33
type ProjectCStatetype Output= Projecttype Slot id= forall query. H.Slot query Project idtype CState - replacement in client/src/Aftok/ProjectList.purs at line 44
data ProjectActiondata Action - edit in client/src/Aftok/ProjectList.purs at line 48[3.634]→[2.12044:12110](∅→∅),[3.698]→[3.1669:1670](∅→∅),[2.12110]→[3.1669:1670](∅→∅),[3.1669]→[3.1669:1670](∅→∅)
type ProjectListSlot id= forall query. H.Slot query Project id - replacement in client/src/Aftok/ProjectList.purs at line 52
projectListComponent ::forall query input m.component ::forall query m. - replacement in client/src/Aftok/ProjectList.purs at line 57
H.Component HH.HTML query ProjectInput Project mprojectListComponent console caps =H.Component HH.HTML query Input Output mcomponent console caps = - replacement in client/src/Aftok/ProjectList.purs at line 70
initialState :: ProjectInput -> ProjectCStateinitialState :: Input -> CState - replacement in client/src/Aftok/ProjectList.purs at line 73
render :: forall slots. ProjectCState -> H.ComponentHTML ProjectAction slots mrender :: forall slots. CState -> H.ComponentHTML Action slots m - replacement in client/src/Aftok/ProjectList.purs at line 99
eval :: ProjectAction -> H.HalogenM ProjectCState ProjectAction () Project m Uniteval :: Action -> H.HalogenM CState Action () Project m Unit - edit in client/src/Aftok/ProjectList.purs at line 111[3.2248]→[3.2248:2320](∅→∅),[3.2320]→[2.13988:14485](∅→∅),[2.14485]→[3.2801:2951](∅→∅),[3.2801]→[3.2801:2951](∅→∅),[3.2951]→[3.128:199](∅→∅),[3.199]→[3.3130:3179](∅→∅),[3.3130]→[3.3130:3179](∅→∅)
listProjects :: Aff (Either APIError (Array Project))listProjects = doresult <- 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 }parseProject :: Json -> ExceptT APIError Effect ProjectparseProject json = doProject' p <- except <<< lmap (ParseFailure json) $ decodeJson jsonpdate <- withExceptT (ParseFailure json) $ parseDate p.inceptionDatepure $ Project' (p { inceptionDate = pdate }) - replacement in client/src/Aftok/Timeline.purs at line 58[3.924]→[3.2875:2907](∅→∅),[3.5960]→[3.2875:2907](∅→∅),[3.6787]→[3.2875:2907](∅→∅),[2.25738]→[3.2875:2907](∅→∅),[3.301880]→[3.2875:2907](∅→∅)
import Aftok.Project as Projectimport Aftok.ProjectList as ProjectList - edit in client/src/Aftok/Timeline.purs at line 61
, ProjectId)import Aftok.Api.Project( Project, Project'(..) - edit in client/src/Aftok/Timeline.purs at line 67
, Project, Project'(..), ProjectId - replacement in client/src/Aftok/Timeline.purs at line 96
type TimelineInputtype Input - replacement in client/src/Aftok/Timeline.purs at line 117
= ( projectList :: Project.ProjectListSlot Unit= ( projectList :: ProjectList.Slot Unit - replacement in client/src/Aftok/Timeline.purs at line 135
Project.Capability m ->H.Component HH.HTML query TimelineInput ProjectEvent mProjectList.Capability m ->H.Component HH.HTML query Input ProjectEvent m - replacement in client/src/Aftok/Timeline.purs at line 149
initialState :: TimelineInput -> TimelineStateinitialState :: Input -> TimelineState - replacement in client/src/Aftok/Timeline.purs at line 170
[ HH.slot _projectList unit (Project.projectListComponent system pcaps) st.selectedProject (Just <<< ProjectSelected) ][ HH.slot _projectList unit (ProjectList.component system pcaps) st.selectedProject (Just <<< ProjectSelected) ] - edit in client/src/Aftok/Types.purs at line 4[3.3259]→[3.10750:10861](∅→∅),[3.10861]→[3.3259:3299](∅→∅),[3.3259]→[3.3259:3299](∅→∅),[3.7427]→[3.3352:3396](∅→∅),[3.3396]→[3.5525:5590](∅→∅)
import Control.Monad.Error.Class (throwError)import Control.Monad.Except.Trans (ExceptT, except, withExceptT)import Control.Monad.Trans.Class (lift)import Data.Argonaut.Core (Json, stringify)import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:)) - replacement in client/src/Aftok/Types.purs at line 6
import Data.DateTime.Instant (Instant, fromDateTime)import Data.Functor.Compose (Compose(..))import Data.Either (Either(..), note)import Data.Foldable (class Foldable, foldr, foldl, foldMap)import Data.DateTime.Instant (Instant) - replacement in client/src/Aftok/Types.purs at line 8
import Data.Maybe (Maybe(..))import Data.Newtype (class Newtype, unwrap, over)import Data.Traversable (class Traversable, traverse)import Data.Maybe (Maybe)import Data.Newtype (class Newtype) - replacement in client/src/Aftok/Types.purs at line 11
import Data.UUID (UUID, toString, parseUUID)import Data.UUID (UUID, toString) - edit in client/src/Aftok/Types.purs at line 16
import Affjax as AJAXimport Affjax (Response, printError)import Affjax.StatusCode (StatusCode(..)) - replacement in client/src/Aftok/Types.purs at line 81
data APIError= Forbidden| ParseFailure Json String| Error { status :: Maybe StatusCode, message :: String }newtype UserId= UserId UUIDderive instance userIdEq :: Eq UserId - replacement in client/src/Aftok/Types.purs at line 86[3.7644]→[3.3550:3643](∅→∅),[3.3643]→[2.37116:37199](∅→∅),[2.37199]→[3.3727:3815](∅→∅),[3.3727]→[3.3727:3815](∅→∅)
instance showAPIError :: Show APIError whereshow = case _ ofForbidden -> "Forbidden"ParseFailure js e -> "ParseFailure (" <> show (stringify js) <> ") " <> show eError r -> "Error { status: " <> show r.status <> ", message: " <> r.message <> "}"derive instance userIdNewtype :: Newtype UserId _ - edit in client/src/Aftok/Types.purs at line 97[3.5844]→[3.5844:5845](∅→∅),[3.5845]→[2.37241:37276](∅→∅),[2.37276]→[3.5878:5983](∅→∅),[3.5878]→[3.5878:5983](∅→∅),[3.5983]→[2.37277:37278](∅→∅),[2.37278]→[3.5983:6041](∅→∅),[3.5983]→[3.5983:6041](∅→∅),[3.6041]→[2.37279:37314](∅→∅),[2.37314]→[3.6074:6263](∅→∅),[3.6074]→[3.6074:6263](∅→∅),[3.6264]→[3.6264:6301](∅→∅),[3.6301]→[2.37315:37454](∅→∅),[2.37454]→[3.6446:6494](∅→∅),[3.6446]→[3.6446:6494](∅→∅),[3.6494]→[2.37455:37579](∅→∅),[2.37579]→[3.6623:6697](∅→∅),[3.6623]→[3.6623:6697](∅→∅),[3.6697]→[2.37580:37638](∅→∅)
newtype Project' date= Project'{ projectId :: ProjectId, projectName :: String, inceptionDate :: date, initiator :: UUID}derive instance newtypeProject :: Newtype (Project' a) _type Project= Project' DateTimedata ProjectEvent= ProjectChange Projectinstance decodeJsonProject :: DecodeJson (Project' String) wheredecodeJson json = dox <- decodeJson jsonproject <- x .: "project"projectIdStr <- x .: "projectId"projectId <- ProjectId <$> (note "Failed to decode project UUID" $ parseUUID projectIdStr)projectName <- project .: "projectName"inceptionDate <- project .: "inceptionDate"initiatorStr <- project .: "initiator"initiator <- note "Failed to decode initiator UUID" $ parseUUID initiatorStrpure $ Project' { projectId, projectName, inceptionDate, initiator }newtype JsonCompose f g a= JsonCompose (Compose f g a) - edit in client/src/Aftok/Types.purs at line 98[2.37639]→[3.11965:12158](∅→∅),[3.11965]→[3.11965:12158](∅→∅),[3.12158]→[3.3815:3816](∅→∅),[3.3815]→[3.3815:3816](∅→∅),[3.3816]→[3.12159:12815](∅→∅),[3.12815]→[3.3816:3896](∅→∅),[3.3816]→[3.3816:3896](∅→∅),[3.3896]→[2.37640:37674](∅→∅),[2.37674]→[3.3933:3949](∅→∅),[3.3933]→[3.3933:3949](∅→∅),[3.3949]→[3.7644:7645](∅→∅),[3.7644]→[3.7644:7645](∅→∅),[3.7645]→[3.3950:4023](∅→∅),[3.4023]→[3.8202:8234](∅→∅),[3.8234]→[2.37675:37802](∅→∅),[3.8274]→[3.12816:13024](∅→∅),[2.37802]→[3.12816:13024](∅→∅),[3.4195]→[3.12816:13024](∅→∅),[3.13024]→[3.4195:4196](∅→∅),[3.4195]→[3.4195:4196](∅→∅),[3.4196]→[2.37803:37960](∅→∅),[2.37960]→[3.13187:13218](∅→∅),[3.13187]→[3.13187:13218](∅→∅),[3.13218]→[2.37961:38039](∅→∅),[2.38039]→[3.13301:13331](∅→∅),[3.13301]→[3.13301:13331](∅→∅),[3.13331]→[2.38040:38265](∅→∅)
derive instance jsonComposeNewtype :: Newtype (JsonCompose f g a) _instance jsonComposeFunctor :: (Functor f, Functor g) => Functor (JsonCompose f g) wheremap f = over JsonCompose (map f)instance jsonComposeFoldable :: (Foldable f, Foldable g) => Foldable (JsonCompose f g) wherefoldr f b = foldr f b <<< unwrapfoldl f b = foldl f b <<< unwrapfoldMap f = foldMap f <<< unwrapinstance jsonComposeTraversable :: (Traversable f, Traversable g) => Traversable (JsonCompose f g) wheretraverse f = map JsonCompose <<< traverse f <<< unwrapsequence = traverse identityinstance jsonComposeDecodeJson :: (DecodeJson (f (g a))) => DecodeJson (JsonCompose f g a) wheredecodeJson json = JsonCompose <<< Compose <$> decodeJson jsondecompose :: forall f g a. JsonCompose f g a -> f (g a)decompose (JsonCompose (Compose fga)) = fgaparseJsonDate :: Json -> ExceptT String Effect DateTimeparseJsonDate json = dostr <- except $ decodeJson jsonparseDate strparseDate :: String -> ExceptT String Effect DateTimeparseDate str = dojsDate <- lift $ JD.parse strexcept$ note ("Unable to convert date " <> show jsDate <> " to a valid DateTime value.")(JD.toDateTime jsDate)decodeDatedJson :: forall t. Traversable t => DecodeJson (t String) => Json -> ExceptT String Effect (t DateTime)decodeDatedJson json = dodecoded <- except $ decodeJson jsontraverse parseDate decodedparseDatedResponse ::forall t.Traversable t =>DecodeJson (t String) =>Either AJAX.Error (Response Json) ->ExceptT APIError Effect (t Instant)parseDatedResponse = case _ ofLeft err -> throwError $ Error { status: Nothing, message: printError err }Right r -> case r.status ofStatusCode 403 -> throwError $ ForbiddenStatusCode 200 -> withExceptT (ParseFailure r.body) $ map fromDateTime <$> decodeDatedJson r.bodyother -> throwError $ Error { status: Just other, message: r.statusText } - replacement in client/src/Main.purs at line 22
import Aftok.Types (System, Project, ProjectEvent(..), liveSystem)import Aftok.Types (System, liveSystem) - edit in client/src/Main.purs at line 25[29.9265][3.1541]
import Aftok.Api.Project (Project, ProjectEvent(ProjectChange)) - replacement in client/src/Main.purs at line 29
import Aftok.Project as Projectimport Aftok.ProjectList as ProjectList - replacement in client/src/Main.purs at line 42
project = Project.apiCapabilityproject = ProjectList.apiCapability - replacement in client/src/Main.purs at line 120
Project.Capability m ->ProjectList.Capability m ->