Add project overview page to client.

[?]
Jan 24, 2021, 6:25 PM
QAC2QJ32ZLAK25KJ7SWT27WOZKD2MMDE7OZPHIRRFP2W2QZW7PBAC

Dependencies

  • [2] AAALU5A2 Fix client routing
  • [3] WRPIYG3E Use project listing functionality to check for whether we have a cookie.
  • [4] NJNMO72S Add zcash.com submodule and update client to modern halogen.
  • [5] JXG3FCXY Upgrade ps + halogen versions.
  • [6] BFZN4SUA Make timeline component work.
  • [7] TUA4HMUD Use real API capability for login.
  • [8] QU5FW67R Add project selection to time tracker.
  • [9] EA5BFM5G Split Login component into its own module.
  • [10] ARX7SHY5 Begin work on login UI.
  • [11] ZIG57EE6 Fix project selection, end log end on project switch.
  • [12] 2J37EVJM Check for an open interval on project switch.
  • [13] J6S23MDG Use server timestamps for interval start and end.
  • [14] OUR4PAOT Use local dates for display of intervals.
  • [15] HO2PFRAB Client login now handles response correctly.
  • [16] QMEYU4MW Add display for prior intervals.
  • [17] RSF6UAJK Break out api module for timeline.
  • [18] TKGBRIQT Login component now raises LoginComplete message.
  • [19] SAESJLLY Initial experiments in hash routing.
  • [20] IR75ZMX3 Return actual events for interval ends, not just timestamps.
  • [21] PT4276XC Add logout functionality.
  • [*] RB2ETNIF Add skeletal PureScript client project.
  • [*] O2BZOX7M Add signup form, captcha check.

Change contents

  • replacement in client/src/Aftok/Api/Timeline.purs at line 34
    [3.912][3.912:957](),[3.957][3.1906:2044]()
    import Aftok.Project (ProjectId(..), pidStr)
    -- import Aftok.Types (APIError, JsonCompose, decompose, parseDatedResponse)
    import Aftok.Types (APIError, decompose, parseDatedResponse)
    [3.912]
    [3.1031]
    import Aftok.Types (APIError, decompose, parseDatedResponse, ProjectId(..), pidStr)
  • file addition: Overview.purs (----------)
    [3.1]
    module Aftok.Overview where
    import Prelude
    -- import Control.Alt ((<|>))
    -- import Control.Monad.Rec.Class (forever)
    -- import Control.Monad.State (State, put, get, evalState)
    -- import Control.Monad.Trans.Class (lift)
    -- import Control.Monad.Maybe.Trans (MaybeT(..), runMaybeT)
    --
    -- import Data.Array (reverse, cons)
    -- import Data.Date (Date, year, month, day)
    -- import Data.DateTime as DT
    -- import Data.DateTime (DateTime(..), date)
    -- import Data.DateTime.Instant (Instant, unInstant, fromDateTime, toDateTime)
    -- import Data.Either (Either(..))
    -- import Data.Enum (fromEnum)
    import Data.Foldable (any)
    -- import Data.Map as M
    import Data.Maybe (Maybe(..), isNothing)
    import Data.Newtype (unwrap)
    import Data.Symbol (SProxy(..))
    -- import Data.Time.Duration (Milliseconds(..), Hours(..), Days(..))
    -- import Data.Traversable (traverse_, traverse)
    -- import Data.Tuple (Tuple(..))
    -- import Data.Unfoldable as U
    -- -- import Text.Format as F -- (format, zeroFill, width)
    -- import Effect.Aff as Aff
    import Effect.Aff (Aff)
    -- import Effect.Class (liftEffect)
    -- import Effect.Exception (error)
    -- import Effect.Now (now)
    import Halogen as H
    -- import Halogen.Query.EventSource (EventSource)
    -- import Halogen.Query.EventSource as EventSource
    import Halogen.HTML.Core (ClassName(..))
    import Halogen.HTML as HH
    -- import Halogen.HTML.CSS as CSS
    -- import Halogen.HTML.Events as E
    import Halogen.HTML.Properties as P
    -- import CSS (backgroundColor, clear, clearBoth, border, rgb, solid, borderRadius, marginLeft)
    -- import CSS.Display (display, flex)
    -- import CSS.Geometry (width, height)
    -- import CSS.Size (px, pct)
    -- import Aftok.Api.Overview as TL
    -- import Aftok.Api.Overview
    -- ( OverviewError,
    -- Event(..),
    -- Interval(..),
    -- TimeInterval,
    -- KeyedEvent,
    -- TimeSpan,
    -- start, end, interval,
    -- event, eventTime, keyedEvent
    -- )
    import Aftok.Project as Project
    -- import Aftok.Project (Project, Project'(..), ProjectId) --, pidStr)
    import Aftok.Types (System, Project, ProjectEvent(..))
    type OverviewInput = Maybe Project
    type OverviewState =
    { selectedProject :: Maybe Project
    }
    data Invitation
    = InviteByEmail String
    | InviteByZaddr String
    data OverviewAction
    = Initialize
    | ProjectSelected Project
    | Invite Invitation
    type Slot id = forall query. H.Slot query ProjectEvent id
    type Slots =
    ( projectList :: Project.ProjectListSlot Unit
    )
    _projectList = SProxy :: SProxy "projectList"
    type Capability (m :: Type -> Type) =
    {
    }
    component
    :: forall query m
    . Monad m
    => System m
    -> Capability m
    -> Project.Capability m
    -> H.Component HH.HTML query OverviewInput ProjectEvent m
    component system caps pcaps = H.mkComponent
    { initialState
    , render
    , eval: H.mkEval $ H.defaultEval
    { handleAction = eval
    , initialize = Just Initialize
    }
    } where
    initialState :: OverviewInput -> OverviewState
    initialState input =
    { selectedProject: input
    }
    render :: OverviewState -> H.ComponentHTML OverviewAction Slots m
    render 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) unit (Just <<< ProjectSelected)]
    ,HH.div
    [P.classes (ClassName <$> if isNothing st.selectedProject then ["collapse"] else [])]
    []
    ]
    ]
    eval :: OverviewAction -> H.HalogenM OverviewState OverviewAction Slots ProjectEvent m Unit
    eval action = do
    case action of
    Initialize -> do
    pure unit
    Invite _ -> do
    pure unit
    ProjectSelected p -> do
    currentProject <- H.gets (_.selectedProject)
    when (any (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject) $ do
    H.raise (ProjectChange p)
    H.modify_ (_ { selectedProject = Just p })
    apiCapability :: Capability Aff
    apiCapability = { }
    mockCapability :: Capability Aff
    mockCapability = { }
  • replacement in client/src/Aftok/Project.purs at line 11
    [3.761][3.761:826]()
    import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:))
    [3.761]
    [3.826]
    import Data.Argonaut.Decode (decodeJson)
  • replacement in client/src/Aftok/Project.purs at line 13
    [3.855][3.855:887](),[3.916][3.916:954]()
    import Data.DateTime (DateTime)
    import Data.Either (Either(..), note)
    [3.855]
    [3.996]
    import Data.Either (Either(..))
  • edit in client/src/Aftok/Project.purs at line 15
    [3.1026][3.34:70]()
    import Data.Newtype (class Newtype)
  • edit in client/src/Aftok/Project.purs at line 16
    [3.154][3.214:259]()
    import Data.UUID (UUID, parseUUID, toString)
  • replacement in client/src/Aftok/Project.purs at line 24
    [3.1287][3.260:313]()
    import Aftok.Types (APIError(..), System, parseDate)
    [3.1287]
    [3.216]
    import Aftok.Types
    ( APIError(..)
    , System
    , parseDate
    , pidStr
    , Project'(..)
    , Project
    )
  • edit in client/src/Aftok/Project.purs at line 38
    [3.331][3.331:332](),[3.369][3.369:404](),[3.404][3.155:199](),[3.199][3.314:370](),[3.255][3.404:435](),[3.370][3.404:435](),[3.404][3.404:435](),[3.435][3.371:411]()
    newtype ProjectId = ProjectId UUID
    derive instance projectIdEq :: Eq ProjectId
    derive instance projectIdNewtype :: Newtype ProjectId _
    pidStr :: ProjectId -> String
    pidStr (ProjectId uuid) = toString uuid
  • edit in client/src/Aftok/Project.purs at line 39
    [3.1524][3.1524:1557](),[3.1557][3.472:525](),[3.525][3.1583:1635](),[3.1583][3.1583:1635](),[3.1635][3.256:313](),[3.313][3.1635:1669](),[3.1635][3.1635:1669](),[3.1669][3.526:527]()
    newtype Project' date = Project'
    { projectId :: ProjectId
    , projectName :: String
    , inceptionDate :: date
    , initiator :: UUID
    }
    derive instance newtypeProject :: Newtype (Project' a) _
    type Project = Project' DateTime
  • edit in client/src/Aftok/Project.purs at line 104
    [3.1870][3.1870:1983](),[3.1983][3.1948:2114](),[3.2114][3.2246:2247](),[3.2246][3.2246:2247](),[3.2247][3.2115:2411](),[3.2411][3.2247:2248](),[3.2247][3.2247:2248]()
    instance decodeJsonProject :: DecodeJson (Project' String) where
    decodeJson json = do
    x <- decodeJson json
    project <- 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 initiatorStr
    pure $ Project' { projectId, projectName, inceptionDate, initiator }
  • replacement in client/src/Aftok/Timeline.purs at line 62
    [3.2907][3.536:604](),[3.604][3.6844:6872](),[3.989][3.6844:6872](),[3.6844][3.6844:6872]()
    import Aftok.Project (Project, Project'(..), ProjectId) --, pidStr)
    import Aftok.Types (System)
    [3.2907]
    [3.3080]
    import Aftok.Types
    ( System,
    ProjectEvent(..),
    Project,
    Project'(..),
    ProjectId
    )
  • replacement in client/src/Aftok/Timeline.purs at line 105
    [3.302073][3.3075:3111]()
    | ProjectSelected Project.Project
    [3.302073]
    [3.302073]
    | ProjectSelected Project
  • replacement in client/src/Aftok/Timeline.purs at line 110
    [3.3158][3.302105:302155](),[3.3626][3.302105:302155]()
    type Slot id = forall query. H.Slot query Void id
    [3.3626]
    [3.3687]
    type Slot id = forall query. H.Slot query ProjectEvent id
  • replacement in client/src/Aftok/Timeline.purs at line 127
    [3.1170][3.4297:4330]()
    :: forall query input output m
    [3.1170]
    [3.4330]
    :: forall query input m
  • replacement in client/src/Aftok/Timeline.purs at line 132
    [3.4401][3.4401:4447]()
    -> H.Component HH.HTML query input output m
    [3.4401]
    [3.4447]
    -> H.Component HH.HTML query input ProjectEvent m
  • replacement in client/src/Aftok/Timeline.purs at line 184
    [3.4813][3.5189:5279]()
    eval :: TimelineAction -> H.HalogenM TimelineState TimelineAction Slots output m Unit
    [3.4813]
    [3.1431]
    eval :: TimelineAction -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m Unit
  • replacement in client/src/Aftok/Timeline.purs at line 195
    [3.810][3.810:914](),[3.914][3.1776:1825](),[3.1776][3.1776:1825]()
    when (oldActive && any (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject)
    (traverse_ logEnd currentProject)
    [3.810]
    [3.915]
    when (oldActive && any (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject) $ do
    H.raise (ProjectChange p)
    (traverse_ logEnd currentProject)
  • replacement in client/src/Aftok/Timeline.purs at line 248
    [3.304478][3.5953:6040]()
    logStart :: Project -> H.HalogenM TimelineState TimelineAction Slots output m Unit
    [3.304478]
    [3.2872]
    logStart :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m Unit
  • replacement in client/src/Aftok/Timeline.purs at line 255
    [3.3060][3.6122:6207]()
    logEnd :: Project -> H.HalogenM TimelineState TimelineAction Slots output m Unit
    [3.3060]
    [3.3147]
    logEnd :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectEvent m Unit
  • replacement in client/src/Aftok/Types.purs at line 10
    [3.3396][3.10862:10921]()
    import Data.Argonaut.Decode (class DecodeJson, decodeJson)
    [3.3396]
    [3.7181]
    import Data.Argonaut.Decode (class DecodeJson, decodeJson, (.:))
  • edit in client/src/Aftok/Types.purs at line 22
    [3.7262]
    [3.3525]
    import Data.UUID (UUID, toString, parseUUID)
  • edit in client/src/Aftok/Types.purs at line 104
    [3.3815]
    [3.11908]
    newtype ProjectId = ProjectId UUID
    derive instance projectIdEq :: Eq ProjectId
    derive instance projectIdNewtype :: Newtype ProjectId _
  • edit in client/src/Aftok/Types.purs at line 109
    [3.11909]
    [3.11909]
    pidStr :: ProjectId -> String
    pidStr (ProjectId uuid) = toString uuid
    newtype Project' date = Project'
    { projectId :: ProjectId
    , projectName :: String
    , inceptionDate :: date
    , initiator :: UUID
    }
    derive instance newtypeProject :: Newtype (Project' a) _
    type Project = Project' DateTime
    data ProjectEvent
    = ProjectChange Project
    instance decodeJsonProject :: DecodeJson (Project' String) where
    decodeJson json = do
    x <- decodeJson json
    project <- 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 initiatorStr
    pure $ Project' { projectId, projectName, inceptionDate, initiator }
  • replacement in client/src/Main.purs at line 16
    [3.307634][3.307634:307659]()
    import Halogen.Aff as HA
    [3.307634]
    [3.1305]
    import Halogen.Aff (runHalogenAff, awaitBody)
  • replacement in client/src/Main.purs at line 28
    [3.1095][3.13580:13620]()
    import Aftok.Types (System, liveSystem)
    [3.1095]
    [3.307686]
    import Aftok.Types (System, Project, ProjectEvent(..), liveSystem)
  • edit in client/src/Main.purs at line 33
    [3.307748]
    [3.3755]
    import Aftok.Overview as Overview
  • replacement in client/src/Main.purs at line 37
    [3.307845][3.307845:307895]()
    main = HA.runHalogenAff do
    body <- HA.awaitBody
    [3.307845]
    [3.1546]
    main = runHalogenAff do
    body <- awaitBody
  • replacement in client/src/Main.purs at line 44
    [3.3826][3.1610:1683]()
    mainComponent = component liveSystem login signup timeline project
    [3.3826]
    [2.401]
    overview = Overview.apiCapability
    mainComponent = component liveSystem login signup timeline project overview
  • edit in client/src/Main.purs at line 56
    [3.1736]
    [3.1736]
    | VOverview
  • edit in client/src/Main.purs at line 63
    [2.721]
    [2.721]
    , VOverview <$ lit "overview"
  • edit in client/src/Main.purs at line 72
    [2.876]
    [2.876]
    VOverview -> "overview"
  • edit in client/src/Main.purs at line 84
    [3.1818]
    [3.1818]
    , selectedProject :: Maybe Project
  • edit in client/src/Main.purs at line 91
    [3.1894]
    [3.1894]
    | ProjectAction ProjectEvent
  • edit in client/src/Main.purs at line 97
    [3.1943]
    [3.1723]
    , overview :: Overview.Slot Unit
  • edit in client/src/Main.purs at line 103
    [3.1980]
    [3.308236]
    _overview = SProxy :: SProxy "overview"
  • edit in client/src/Main.purs at line 114
    [3.13825]
    [2.1097]
    -> Overview.Capability m
  • replacement in client/src/Main.purs at line 116
    [2.1147][3.2007:2071](),[3.13871][3.2007:2071]()
    component system loginCap signupCap tlCap pCap = H.mkComponent
    [2.1147]
    [3.308429]
    component system loginCap signupCap tlCap pCap ovCap = H.mkComponent
  • edit in client/src/Main.purs at line 130
    [24.9389]
    [24.9389]
    , selectedProject: Nothing
  • edit in client/src/Main.purs at line 146
    [3.2455]
    [3.2455]
    VOverview ->
    HH.div_
    [ HH.slot _overview unit (Overview.component system ovCap pCap) st.selectedProject (Just <<< ProjectAction) ]
  • replacement in client/src/Main.purs at line 152
    [3.1844][3.14088:14176]()
    [ HH.slot _timeline unit (Timeline.component system tlCap pCap) unit absurd ]
    [3.1844]
    [3.308924]
    [ HH.slot _timeline unit (Timeline.component system tlCap pCap) unit (Just <<< ProjectAction) ]
  • edit in client/src/Main.purs at line 168
    [2.1375][3.4407:4408](),[3.2080][3.4407:4408](),[3.2872][3.4407:4408](),[3.4407][3.4407:4408](),[3.4408][3.2873:2919](),[3.2919][2.1376:1403]()
    LoginAction (Login.LoginComplete _) ->
    navigate VTimeline
  • edit in client/src/Main.purs at line 174
    [2.1456]
    [24.9578]
    LoginAction (Login.LoginComplete _) ->
    navigate VTimeline
  • edit in client/src/Main.purs at line 182
    [2.1482]
    [2.1482]
    ProjectAction (ProjectChange p) ->
    H.modify_ (_ { selectedProject = Just p })
  • replacement in client/src/Main.purs at line 205
    [3.2450][3.2450:2474]()
    [brand, logout]
    [3.2450]
    [3.2474]
    [ brand
    , HH.ul [P.classes (ClassName <$> ["navbar-nav", "ml-auto"])] (map navItem nav)
    , logout
    ]
  • edit in client/src/Main.purs at line 213
    [3.2506]
    [3.2506]
    nav :: Array NavItem
    nav = [ { label: "Overview", path: "overview" }
    , { label: "Timeline", path: "timeline" }
    ]
  • edit in client/src/Main.purs at line 232
    [3.2926]
    [3.2926]
    type NavTop =
    { label :: String
    , items :: Array NavItem
    }
  • edit in client/src/Main.purs at line 238
    [3.2927]
    type NavItem =
    { label :: String
    , path :: String
    }
    navItem :: forall a s m. NavItem -> H.ComponentHTML a s m
    navItem ni =
    HH.li
    [P.classes (ClassName <$> ["nav-item"]) ]
    [ HH.a
    [ P.classes (ClassName <$> ["nav-link"])
    , P.href ("#" <> ni.path)
    ]
    [ HH.text ni.label ]
    ]