Add project creation modal.

[?]
Feb 14, 2021, 5:04 AM
YHSVVVYW2RVHLFF3I4DUFSHPSQ2AIJUBSIZ343R3ZATUQKX5DR6QC

Dependencies

  • [2] XGMFJUER Fix the broken modals.
  • [3] V54JCKJX Payment request creation.
  • [4] U7YAT2ZK Add error reporting to signup form.
  • [5] ARX7SHY5 Begin work on login UI.
  • [6] SAESJLLY Initial experiments in hash routing.
  • [7] ZIG57EE6 Fix project selection, end log end on project switch.
  • [8] OUR4PAOT Use local dates for display of intervals.
  • [9] 3HTCTHHU Add halogen-portal dependency and update argonaut.
  • [10] PT4276XC Add logout functionality.
  • [11] JXG3FCXY Upgrade ps + halogen versions.
  • [12] O2BZOX7M Add signup form, captcha check.
  • [13] GLQSD33Y Use mock capability for overview init.
  • [14] QU5FW67R Add project selection to time tracker.
  • [15] 7TQPQW3N Begin adding parsing for project detail.
  • [16] RV7ZIULZ Update overview to have access to the real project detail capability.
  • [17] KKJSBWO6 Add createPaymentRequestHandler
  • [18] B4MTB6UO Persist project across pages.
  • [19] AKM2VYBL Fix errors with project ID persistence.
  • [20] PPW6ROC5 Render project data
  • [21] T2DN23M7 Factor out billing create component.
  • [22] Z5KNL332 Add skeleton of project overview HTML.
  • [23] RSF6UAJK Break out api module for timeline.
  • [24] ANDJ6GEY Add billing component skeleton.
  • [25] KET5QGQP Add billable list (in-progress)
  • [26] ZHV75AEN basic cleanup
  • [27] QMEYU4MW Add display for prior intervals.
  • [28] NJNMO72S Add zcash.com submodule and update client to modern halogen.
  • [29] 4GOBY5NQ WIP on modals.
  • [30] WRPIYG3E Use project listing functionality to check for whether we have a cookie.
  • [31] BPIQKEXE gussify
  • [32] J6S23MDG Use server timestamps for interval start and end.
  • [33] 46PUXHTY Implement project invitations.
  • [34] NAFJ6RB3 Minor module reorg.
  • [35] QH4UB73N Format with purty.
  • [36] APOATM4X Add getProjectDetail call to project API
  • [37] YBLHJFCN Implement billing modal.
  • [38] QAC2QJ32 Add project overview page to client.
  • [39] AAALU5A2 Fix client routing
  • [40] VTZT2ILU Wire up billing navigation.
  • [*] RB2ETNIF Add skeletal PureScript client project.

Change contents

  • replacement in client/src/Aftok/Api/Project.purs at line 7
    [3.763][3.1698:1743]()
    import Data.Argonaut.Core (Json, fromString)
    [3.763]
    [3.1743]
    import Data.Argonaut.Core (Json, fromString, jsonEmptyObject)
  • replacement in client/src/Aftok/Api/Project.purs at line 9
    [3.1829][3.5:46]()
    import Data.Argonaut.Encode (encodeJson)
    [3.1829]
    [3.3197]
    import Data.Argonaut.Encode (encodeJson, (:=), (~>))
  • edit in client/src/Aftok/Api/Project.purs at line 224
    [3.1138]
    type ProjectCreateRequest =
    { projectName :: String
    , depf :: DepreciationFn
    }
    encodeProjectCreateRequest :: ProjectCreateRequest -> Json
    encodeProjectCreateRequest pc =
    "projectName" := pc.projectName
    ~> "depf" := (
    case pc.depf of
    LinearDepreciation vs ->
    encodeJson {
    "type": "LinearDepreciation",
    "arguments": { "undep": unwrap vs.undep, dep: unwrap vs.dep }
    }
    )
    ~> jsonEmptyObject
    decodeProjectId :: Json -> Either JsonDecodeError ProjectId
    decodeProjectId json = (_ .: "projectId") =<< decodeJson json
    createProject :: ProjectCreateRequest -> Aff (Either APIError ProjectId)
    createProject pc = do
    let body = RB.json $ encodeProjectCreateRequest pc
    response <- post RF.json "/api/projects/" (Just body)
    parseResponse decodeProjectId response
  • edit in client/src/Aftok/Billing.purs at line 5
    [3.2839][3.2839:2880]()
    -- import Data.DateTime (DateTime, date)
  • edit in client/src/Aftok/Billing.purs at line 9
    [3.2653][3.3011:3040](),[3.3011][3.3011:3040]()
    import Data.Newtype (unwrap)
  • edit in client/src/Aftok/Billing.purs at line 10
    [3.3072][3.2558:2599]()
    -- import Data.Time.Duration (Hours(..))
  • edit in client/src/Aftok/Billing.purs at line 14
    [3.3157][3.3157:3228]()
    -- import Effect.Class (liftEffect)
    -- import Effect.Now (nowDateTime)
  • edit in client/src/Aftok/Billing.purs at line 24
    [3.2042][3.3468:3503](),[2.15070][3.3468:3503](),[3.3468][3.3468:3503]()
    import Aftok.Api.Project (Project)
  • replacement in client/src/Aftok/Billing.purs at line 37
    [3.3693][3.3693:3711]()
    = Maybe Project
    [3.3693]
    [3.3711]
    = Maybe ProjectId
  • replacement in client/src/Aftok/Billing.purs at line 40
    [3.3730][3.3730:3769]()
    = { selectedProject :: Maybe Project
    [3.3730]
    [3.3769]
    = { selectedProject :: Maybe ProjectId
  • replacement in client/src/Aftok/Billing.purs at line 47
    [3.3994][3.3994:4022]()
    | ProjectSelected Project
    [3.3994]
    [2.15071]
    | ProjectSelected ProjectId
  • replacement in client/src/Aftok/Billing.purs at line 53
    [3.4036][3.4036:4088]()
    = forall query. H.Slot query ProjectList.Event id
    [3.4036]
    [3.4088]
    = forall query. H.Slot query ProjectList.Output id
  • replacement in client/src/Aftok/Billing.purs at line 78
    [3.4642][3.4642:4703]()
    H.Component HH.HTML query BillingInput ProjectList.Event m
    [3.4642]
    [3.4703]
    H.Component HH.HTML query BillingInput ProjectList.Output m
  • replacement in client/src/Aftok/Billing.purs at line 121
    [3.2709][3.4798:4825](),[3.4798][3.4798:4825](),[3.4825][2.15184:15257]()
    Just p ->
    [ renderBillableList (unwrap p).projectId st.billables
    [3.2709]
    [3.14739]
    Just pid ->
    [ renderBillableList pid st.billables
  • replacement in client/src/Aftok/Billing.purs at line 128
    [2.15393][2.15393:15481]()
    , E.onClick (\_ -> Just (OpenBillableModal (unwrap p).projectId))
    [2.15393]
    [2.15481]
    , E.onClick (\_ -> Just (OpenBillableModal pid))
  • replacement in client/src/Aftok/Billing.purs at line 195
    [3.3966][3.6300:6396](),[3.6300][3.6300:6396]()
    eval :: BillingAction -> H.HalogenM BillingState BillingAction Slots ProjectList.Event m Unit
    [3.3966]
    [3.6396]
    eval :: BillingAction -> H.HalogenM BillingState BillingAction Slots ProjectList.Output m Unit
  • replacement in client/src/Aftok/Billing.purs at line 199
    [3.6457][3.6457:6510](),[3.6510][3.15415:15465]()
    currentProject <- H.gets (_.selectedProject)
    traverse_ refreshBillables currentProject
    [3.6457]
    [3.6796]
    currentPid <- H.gets (_.selectedProject)
    traverse_ refreshBillables currentPid
  • replacement in client/src/Aftok/Billing.purs at line 202
    [3.6797][3.6797:6880](),[3.6880][3.15466:15493](),[3.4048][3.6880:7093](),[3.15493][3.6880:7093](),[3.6880][3.6880:7093]()
    ProjectSelected p -> do
    currentProject <- H.gets (_.selectedProject)
    refreshBillables p
    when (all (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject)
    $ do
    H.raise (ProjectList.ProjectChange p)
    H.modify_ (_ { selectedProject = Just p })
    [3.6797]
    [2.15967]
    ProjectSelected pid -> do
    currentPid <- H.gets (_.selectedProject)
    refreshBillables pid
    when (all (_ /= pid) currentPid) $ do
    H.raise (ProjectList.ProjectChange pid)
    H.modify_ (_ { selectedProject = Just pid })
  • replacement in client/src/Aftok/Billing.purs at line 213
    [3.4080][3.4080:4133](),[3.4133][3.15494:15544]()
    currentProject <- H.gets (_.selectedProject)
    traverse_ refreshBillables currentProject
    [3.4080]
    [3.9891]
    currentPid <- H.gets (_.selectedProject)
    traverse_ refreshBillables currentPid
  • replacement in client/src/Aftok/Billing.purs at line 220
    [3.4184][3.4184:4227](),[3.4227][3.15545:15633]()
    refreshBillables currentProject = do
    billables <- lift $ caps.listProjectBillables (unwrap currentProject).projectId
    [3.4184]
    [3.4336]
    refreshBillables pid = do
    billables <- lift $ caps.listProjectBillables pid
  • edit in client/src/Aftok/Overview.purs at line 5
    [3.44]
    [3.513]
    import Data.Array (reverse, sortWith)
  • edit in client/src/Aftok/Overview.purs at line 32
    [3.5947]
    [3.255]
    import Aftok.Projects.Create as Create
  • replacement in client/src/Aftok/Overview.purs at line 36
    [3.3567][3.3567:3596]()
    ( Project
    , Project'(..)
    [3.3567]
    [3.3596]
    ( Project'(..)
  • replacement in client/src/Aftok/Overview.purs at line 45
    [3.9684][3.9684:9702]()
    = Maybe Project
    [3.9684]
    [3.2179]
    = Maybe ProjectId
  • replacement in client/src/Aftok/Overview.purs at line 48
    [3.9722][3.9722:9761]()
    = { selectedProject :: Maybe Project
    [3.9722]
    [3.1039]
    = { selectedProject :: Maybe ProjectId
  • edit in client/src/Aftok/Overview.purs at line 52
    [3.2243][3.2243:2310]()
    data Invitation
    = InviteByEmail String
    | InviteByZaddr String
  • replacement in client/src/Aftok/Overview.purs at line 54
    [3.2345][3.2345:2373](),[3.2373][2.19198:19219]()
    | ProjectSelected Project
    | Invite ProjectId
    [3.2345]
    [3.2395]
    | ProjectSelected ProjectId
    | OpenCreateModal
    | OpenInviteModal ProjectId
  • replacement in client/src/Aftok/Overview.purs at line 59
    [3.9781][3.4311:4363]()
    = forall query. H.Slot query ProjectList.Event id
    [3.9781]
    [3.2454]
    = forall query. H.Slot query ProjectList.Output id
  • edit in client/src/Aftok/Overview.purs at line 63
    [3.6592]
    [3.6043]
    , projectCreateModal :: Create.Slot Unit
  • edit in client/src/Aftok/Overview.purs at line 68
    [3.2567]
    [3.6136]
    _projectCreateModal = SProxy :: SProxy "projectCreateModal"
  • edit in client/src/Aftok/Overview.purs at line 74
    [3.6285]
    [3.9939]
    , createCaps :: Create.Capability m
  • replacement in client/src/Aftok/Overview.purs at line 83
    [3.6692][3.4364:4426]()
    H.Component HH.HTML query OverviewInput ProjectList.Event m
    [3.6692]
    [3.10105]
    H.Component HH.HTML query OverviewInput ProjectList.Output m
  • replacement in client/src/Aftok/Overview.purs at line 112
    [3.3872][3.3872:3971]()
    [ P.classes (ClassName <$> [ "col-md-5", "text-muted", "text-center", "mx-auto" ]) ]
    [3.3872]
    [3.3971]
    [ P.classes (ClassName <$> [ "text-muted", "text-center", "mx-auto" ]) ]
  • edit in client/src/Aftok/Overview.purs at line 121
    [3.4293]
    [3.4293]
    , system.portal
    _projectCreateModal
    unit
    (Create.component system caps.createCaps)
    unit
    Nothing
    (Just <<< (\(Create.ProjectCreated p) -> ProjectSelected p))
  • edit in client/src/Aftok/Overview.purs at line 132
    [3.4497]
    [3.4662]
    , HH.div
    [ P.classes (ClassName <$> [ "pt-6", "mx-auto" ]) ]
    [ HH.button
    [ P.classes [ C.btn, C.btnPrimary ]
    , P.type_ ButtonButton
    , E.onClick (\_ -> Just OpenCreateModal)
    ]
    [ HH.text "Create a new project" ]
    ]
  • replacement in client/src/Aftok/Overview.purs at line 181
    [3.5818][3.5818:5905](),[3.5905][3.6286:6303]()
    <> (contributorCols <$> (L.toUnfoldable $ M.values detail.contributors))
    <>
    [3.5818]
    [3.6303]
    <> (contributorCols <$> (
    reverse
    <<< sortWith ((_.revShare) <<< unwrap)
    <<< L.toUnfoldable
    $ M.values detail.contributors
    ))
    <>
  • replacement in client/src/Aftok/Overview.purs at line 195
    [2.19349][2.19349:19421]()
    , E.onClick (\_ -> Just (Invite project.projectId))
    [2.19349]
    [2.19421]
    , E.onClick (\_ -> Just (OpenInviteModal project.projectId))
  • replacement in client/src/Aftok/Overview.purs at line 240
    [3.11215][3.5176:5275](),[3.3921][3.5176:5275]()
    eval :: OverviewAction -> H.HalogenM OverviewState OverviewAction Slots ProjectList.Event m Unit
    [3.651]
    [3.11425]
    eval :: OverviewAction -> H.HalogenM OverviewState OverviewAction Slots ProjectList.Output m Unit
  • replacement in client/src/Aftok/Overview.purs at line 245
    [3.542][3.542:627](),[3.627][2.19599:19622]()
    traverse_ (setProjectDetail <<< (\p -> (unwrap p).projectId)) currentProject
    Invite pid -> do
    [3.542]
    [2.19622]
    traverse_ setProjectDetail currentProject
    OpenCreateModal -> do
    void <<< H.query _projectCreateModal unit $ H.tell (Create.OpenModal)
    OpenInviteModal pid -> do
  • replacement in client/src/Aftok/Overview.purs at line 250
    [2.19701][3.11543:11573](),[3.11543][3.11543:11573]()
    ProjectSelected p -> do
    [2.19701]
    [3.11573]
    ProjectSelected pid -> do
  • replacement in client/src/Aftok/Overview.purs at line 252
    [3.11626][3.11626:11715]()
    when (all (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject)
    [3.11626]
    [3.11715]
    when (all (_ /= pid) currentProject)
  • replacement in client/src/Aftok/Overview.purs at line 254
    [3.11730][3.5276:5328](),[3.5328][3.11770:11827](),[3.11770][3.11770:11827](),[3.11827][3.628:680]()
    H.raise (ProjectList.ProjectChange p)
    H.modify_ (_ { selectedProject = Just p })
    setProjectDetail (unwrap p).projectId
    [3.11730]
    [3.4104]
    H.raise (ProjectList.ProjectChange pid)
    H.modify_ (_ { selectedProject = Just pid })
    setProjectDetail pid
  • replacement in client/src/Aftok/Overview.purs at line 258
    [3.4105][3.5329:5435]()
    setProjectDetail :: ProjectId -> H.HalogenM OverviewState OverviewAction Slots ProjectList.Event m Unit
    [3.4105]
    [3.782]
    setProjectDetail :: ProjectId -> H.HalogenM OverviewState OverviewAction Slots ProjectList.Output m Unit
  • edit in client/src/Aftok/Overview.purs at line 269
    [3.7460]
    [3.6925]
    , createCaps: Create.apiCapability
  • edit in client/src/Aftok/Overview.purs at line 299
    [3.7502]
    [3.6985]
    , createCaps: Create.apiCapability
  • edit in client/src/Aftok/ProjectList.purs at line 6
    [3.107][3.7064:7096]()
    -- import Data.Bifunctor (lmap)
  • edit in client/src/Aftok/ProjectList.purs at line 9
    [3.329]
    [3.7097]
    import Data.Newtype (unwrap)
  • edit in client/src/Aftok/ProjectList.purs at line 14
    [3.7145]
    [3.4722]
    , ProjectId
  • replacement in client/src/Aftok/ProjectList.purs at line 32
    [3.7262][3.11908:11926](),[3.11908][3.11908:11926]()
    = Maybe Project
    [3.7262]
    [3.364]
    = Maybe ProjectId
    data Query a
    = ProjectCreated ProjectId a
  • replacement in client/src/Aftok/ProjectList.purs at line 37
    [3.365][3.5692:5729]()
    data Event
    = ProjectChange Project
    [3.365]
    [3.7288]
    data Output
    = ProjectChange ProjectId
  • replacement in client/src/Aftok/ProjectList.purs at line 41
    [3.7302][3.5730:5770]()
    = forall query. H.Slot query Event id
    [3.7302]
    [3.7344]
    = H.Slot Query Output id
  • replacement in client/src/Aftok/ProjectList.purs at line 44
    [3.7357][3.11946:11985](),[3.11946][3.11946:11985]()
    = { selectedProject :: Maybe Project
    [3.7357]
    [3.11985]
    = { selectedPid :: Maybe ProjectId
  • replacement in client/src/Aftok/ProjectList.purs at line 49
    [3.7370][3.603:618](),[3.12043][3.603:618](),[3.603][3.603:618]()
    = Initialize
    [3.7370]
    [3.618]
    = Initialize (Maybe ProjectId)
  • replacement in client/src/Aftok/ProjectList.purs at line 57
    [3.7384][3.7384:7402]()
    forall query m.
    [3.7384]
    [3.12242]
    forall m.
  • replacement in client/src/Aftok/ProjectList.purs at line 61
    [3.12287][3.5771:5813]()
    H.Component HH.HTML query Input Event m
    [3.12287]
    [3.7446]
    H.Component HH.HTML Query Input Output m
  • replacement in client/src/Aftok/ProjectList.purs at line 69
    [3.12477][3.12477:12558]()
    { handleAction = eval
    , initialize = Just Initialize
    [3.12477]
    [3.12558]
    { handleAction = handleAction
    , handleQuery = handleQuery
    , initialize = Just (Initialize Nothing)
  • replacement in client/src/Aftok/ProjectList.purs at line 76
    [3.7506][3.12636:12700](),[3.12636][3.12636:12700]()
    initialState input = { selectedProject: input, projects: [] }
    [3.7506]
    [3.1106]
    initialState input = { selectedPid: input, projects: [] }
  • replacement in client/src/Aftok/ProjectList.purs at line 92
    [3.989][3.13094:13214]()
    ( [ HH.option [ P.selected (isNothing st.selectedProject), P.disabled true ] [ HH.text "Select a project" ] ]
    [3.989]
    [3.13214]
    ( [ HH.option [ P.selected (isNothing st.selectedPid), P.disabled true ] [ HH.text "Select a project" ] ]
  • replacement in client/src/Aftok/ProjectList.purs at line 99
    [3.13327][3.13327:13421]()
    [ P.selected (any (\(Project' p') -> p'.projectId == p.projectId) st.selectedProject)
    [3.13327]
    [3.13421]
    [ P.selected (any (p.projectId == _) st.selectedPid)
  • replacement in client/src/Aftok/ProjectList.purs at line 104
    [3.1519][3.5814:5875](),[3.5875][3.13580:13620](),[3.7638][3.13580:13620](),[3.13580][3.13580:13620]()
    eval :: Action -> H.HalogenM CState Action () Event m Unit
    eval = case _ of
    Initialize -> do
    [3.1519]
    [3.13620]
    handleQuery :: forall slots a. Query a -> H.HalogenM CState Action slots Output m (Maybe a)
    handleQuery = case _ of
    ProjectCreated pid a -> do
    handleAction (Initialize (Just pid))
    pure (Just a)
    handleAction :: forall slots. Action -> H.HalogenM CState Action slots Output m Unit
    handleAction = case _ of
    Initialize pidMay -> do
  • replacement in client/src/Aftok/ProjectList.purs at line 116
    [3.13752][3.13752:13816]()
    Right projects -> H.modify_ (_ { projects = projects })
    [3.13752]
    [3.13816]
    Right projects -> H.modify_ (_ { projects = projects, selectedPid = pidMay })
  • replacement in client/src/Aftok/ProjectList.purs at line 120
    [3.13938][3.5876:5945]()
    traverse_ (H.raise <<< ProjectChange) (index projects (i - 1))
    [3.13938]
    [3.1840]
    traverse_ (\p -> H.raise $ ProjectChange (unwrap p).projectId) (index projects (i - 1))
  • file addition: Create.purs (----------)
    [3.7504]
    module Aftok.Projects.Create where
    import Prelude
    import Control.Monad.Trans.Class (lift)
    import Data.Either (Either(..), note)
    import Data.Foldable (any)
    import Data.Maybe (Maybe(..))
    import Data.Number (fromString) as Number
    import Data.Time.Duration (Days(..))
    import Data.Validation.Semigroup (V(..), toEither)
    import Effect.Aff (Aff)
    import DOM.HTML.Indexed.ButtonType (ButtonType(..))
    import Halogen as H
    import Halogen.HTML as HH
    import Halogen.HTML.Core (ClassName(..))
    import Halogen.HTML.Events as E
    import Halogen.HTML.Properties as P
    import Halogen.HTML.Properties.ARIA as ARIA
    import Aftok.Api.Project (ProjectCreateRequest, DepreciationFn(..), createProject)
    import Aftok.Api.Types (APIError(..))
    import Aftok.HTML.Classes as C
    import Aftok.Modals.ModalFFI as ModalFFI
    import Aftok.Types (System, ProjectId)
    data Field
    = NameField
    | UndepField
    | DepField
    derive instance fieldEq :: Eq Field
    derive instance fieldOrd :: Ord Field
    type CState =
    { projectName :: Maybe String
    , undep :: Maybe Days
    , dep :: Maybe Days
    , fieldErrors :: Array Field
    }
    data Query a
    = OpenModal a
    data Output
    = ProjectCreated ProjectId
    data Action
    = SetName String
    | SetUndepDays String
    | SetDepDays String
    | Save
    | Close
    type Slot id
    = H.Slot Query Output id
    type Capability (m :: Type -> Type)
    = { createProject :: ProjectCreateRequest -> m (Either APIError ProjectId)
    }
    modalId :: String
    modalId = "createProject"
    component ::
    forall input m.
    Monad m =>
    System m ->
    Capability m ->
    H.Component HH.HTML Query input Output m
    component system caps =
    H.mkComponent
    { initialState: const initialState
    , render
    , eval: H.mkEval
    $ H.defaultEval
    { handleAction = handleAction
    , handleQuery = handleQuery
    }
    }
    where
    initialState :: CState
    initialState =
    { projectName : Nothing
    , undep : Nothing
    , dep : Nothing
    , fieldErrors : []
    }
    render :: forall slots. CState -> H.ComponentHTML Action slots m
    render st =
    HH.div
    [ P.classes [ C.modal ]
    , P.id_ modalId
    , P.tabIndex (negate 1)
    , ARIA.role "dialog"
    , ARIA.labelledBy (modalId <> "Title")
    , ARIA.hidden "true"
    ]
    [ HH.div
    [ P.classes [C.modalDialog], ARIA.role "document" ]
    [ HH.div
    [ P.classes [C.modalContent] ]
    [ HH.div
    [ P.classes [C.modalHeader] ]
    [ HH.h5 [P.classes [C.modalTitle], P.id_ (modalId <>"Title") ] [HH.text "Create a new project"]
    , HH.button
    [ P.classes [ C.close ]
    , ARIA.label "Close"
    , P.type_ ButtonButton
    , E.onClick (\_ -> Just Close)
    ]
    [ HH.span [ARIA.hidden "true"] [HH.text "×"]]
    ]
    , HH.div
    [ P.classes [C.modalBody] ]
    [ HH.form_
    [ formGroup st
    [ NameField ]
    [ HH.label
    [ P.for "projectName"]
    [ HH.text "Project Name" ]
    , HH.input
    [ P.type_ P.InputText
    , P.classes [ C.formControl, C.formControlSm ]
    , P.id_ "projectName"
    , P.placeholder "My awesome new project!!!"
    , E.onValueInput (Just <<< SetName)
    ]
    ]
    , formGroup st
    [ UndepField ]
    [ HH.label
    [ P.for "undepDays"]
    [ HH.text "Number of days before a share begins to depreciate" ]
    , HH.input
    [ P.type_ P.InputNumber
    , P.classes [ C.formControl, C.formControlXs, C.formControlFlush, C.marginX2 ]
    , P.id_ "undepDays"
    , P.placeholder "180"
    , E.onValueInput (Just <<< SetUndepDays)
    ]
    ]
    , formGroup st
    [ DepField ]
    [ HH.label
    [ P.for "undepDays"]
    [ HH.text "Number of days over which a share depreciates" ]
    , HH.input
    [ P.type_ P.InputNumber
    , P.classes [ C.formControl, C.formControlXs, C.formControlFlush, C.marginX2 ]
    , P.id_ "undepDays"
    , P.placeholder "1800"
    , E.onValueInput (Just <<< SetDepDays)
    ]
    ]
    ]
    ]
    , HH.div
    [ P.classes [C.modalFooter] ]
    [ HH.button
    [ P.type_ ButtonButton
    , P.classes [ C.btn, C.btnSecondary]
    , E.onClick (\_ -> Just Close)
    ]
    [ HH.text "Close" ]
    , HH.button
    [ P.type_ ButtonButton
    , P.classes [ C.btn, C.btnPrimary ]
    , E.onClick (\_ -> Just Save)
    ]
    [ HH.text "Create project"]
    ]
    ]
    ]
    ]
    formGroup :: forall i a. CState -> Array Field -> Array (HH.HTML i a) -> HH.HTML i a
    formGroup st fields body =
    HH.div
    [ P.classes [C.formGroup] ]
    (body <> (fieldError st =<< fields))
    fieldError :: forall i a. CState -> Field -> Array (HH.HTML i a)
    fieldError st field =
    if any (_ == field) st.fieldErrors
    then case field of
    NameField -> err "The name field is required"
    UndepField -> err "A number of days before depreciation starts is required"
    DepField -> err "The number of days over which a share depreciates is required"
    else []
    where
    err str =
    [ HH.div_
    [ HH.span
    [ P.classes (ClassName <$> [ "badge", "badge-danger-soft" ]) ] [ HH.text str ] ]
    ]
    -- we use a query to initialize, since this is a modal that doesn't actually get unloaded.
    handleQuery :: forall slots a. Query a -> H.HalogenM CState Action slots Output m (Maybe a)
    handleQuery = case _ of
    OpenModal a -> do
    H.modify_ (const initialState)
    lift $ system.toggleModal modalId ModalFFI.ShowModal
    pure (Just a)
    handleAction :: forall slots. Action -> H.HalogenM CState Action slots Output m Unit
    handleAction = case _ of
    SetName name ->
    H.modify_ (_ { projectName = Just name })
    SetUndepDays days ->
    case Number.fromString days of
    (Just n) -> H.modify_ (_ { undep = Just $ Days n })
    (Nothing) -> pure unit
    SetDepDays days ->
    case Number.fromString days of
    (Just n) -> H.modify_ (_ { dep = Just $ Days n })
    (Nothing) -> pure unit
    Save -> do
    nameV <- V <<< note [NameField] <$> H.gets (_.projectName)
    undepV <- V <<< note [UndepField] <$> H.gets (_.undep)
    depV <- V <<< note [DepField] <$> H.gets (_.dep)
    let req = { projectName: _
    , depf : _
    }
    reqV =
    req <$> nameV
    <*> (LinearDepreciation <$> ({ undep: _, dep: _ } <$> undepV <*> depV))
    case toEither reqV of
    Right req' -> do
    res <- lift $ caps.createProject req'
    case res of
    Right pid -> do
    H.raise (ProjectCreated pid)
    handleAction Close
    Left errs -> do
    lift $ system.error (show errs)
    Left errors -> do
    H.modify_ (_ { fieldErrors = errors })
    Close -> do
    H.modify_ (const initialState) -- wipe the state for safety
    lift $ system.toggleModal modalId ModalFFI.HideModal
    apiCapability :: Capability Aff
    apiCapability =
    { createProject: createProject
    }
    mockCapability :: Capability Aff
    mockCapability =
    { createProject: \_ -> pure $ Left Forbidden }
  • edit in client/src/Aftok/Timeline.purs at line 18
    [3.590][3.6611:6640](),[3.1054][3.6611:6640]()
    import Data.Newtype (unwrap)
  • edit in client/src/Aftok/Timeline.purs at line 62
    [3.7699][3.7699:7753](),[3.25833][3.25833:25837]()
    import Aftok.Api.Project
    ( Project
    , Project'(..)
    )
  • replacement in client/src/Aftok/Timeline.purs at line 91
    [3.7765][3.26093:26111](),[3.26093][3.26093:26111]()
    = Maybe Project
    [3.7765]
    [3.1119]
    = Maybe ProjectId
  • replacement in client/src/Aftok/Timeline.purs at line 94
    [3.26131][3.26131:26170]()
    = { selectedProject :: Maybe Project
    [3.26131]
    [3.26170]
    = { selectedProject :: Maybe ProjectId
  • replacement in client/src/Aftok/Timeline.purs at line 102
    [3.302073][3.4878:4906]()
    | ProjectSelected Project
    [3.302073]
    [3.302073]
    | ProjectSelected ProjectId
  • replacement in client/src/Aftok/Timeline.purs at line 108
    [3.26325][3.5947:5999]()
    = forall query. H.Slot query ProjectList.Event id
    [3.26325]
    [3.3687]
    = forall query. H.Slot query ProjectList.Output id
  • replacement in client/src/Aftok/Timeline.purs at line 130
    [3.7840][3.6000:6054]()
    H.Component HH.HTML query Input ProjectList.Event m
    [3.7840]
    [3.27025]
    H.Component HH.HTML query Input ProjectList.Output m
  • replacement in client/src/Aftok/Timeline.purs at line 169
    [3.19414][3.19414:19497]()
    (Just <<< (\(ProjectList.ProjectChange p) -> ProjectSelected p))
    [3.19414]
    [3.6314]
    (Just <<< (\(ProjectList.ProjectChange pid) -> ProjectSelected pid))
  • replacement in client/src/Aftok/Timeline.purs at line 193
    [3.4813][3.6331:6430]()
    eval :: TimelineAction -> H.HalogenM TimelineState TimelineAction Slots ProjectList.Event m Unit
    [3.4813]
    [3.29204]
    eval :: TimelineAction -> H.HalogenM TimelineState TimelineAction Slots ProjectList.Output m Unit
  • replacement in client/src/Aftok/Timeline.purs at line 200
    [3.29408][3.29408:29438]()
    ProjectSelected p -> do
    [3.29408]
    [3.29438]
    ProjectSelected pid -> do
  • replacement in client/src/Aftok/Timeline.purs at line 204
    [3.29602][3.29602:29767](),[3.29767][3.6431:6477](),[3.6477][3.29801:29830](),[3.29801][3.29801:29830]()
    when (oldActive && any (\p' -> (unwrap p').projectId /= (unwrap p).projectId) currentProject)
    $ do
    (traverse_ logEnd currentProject)
    H.raise (ProjectList.ProjectChange p)
    setStateForProject p
    [3.29602]
    [3.29830]
    when (oldActive && any (_ /= pid) currentProject)
    $ (traverse_ logEnd currentProject)
    H.raise (ProjectList.ProjectChange pid)
    setStateForProject pid
  • replacement in client/src/Aftok/Timeline.purs at line 222
    [3.1970][3.6478:6574](),[3.6574][3.30450:30526](),[3.30450][3.30450:30526]()
    logStart :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectList.Event m Unit
    logStart (Project' p) = do
    logged <- lift $ caps.logStart p.projectId
    [3.1970]
    [3.30526]
    logStart :: ProjectId -> H.HalogenM TimelineState TimelineAction Slots ProjectList.Output m Unit
    logStart pid = do
    logged <- lift $ caps.logStart pid
  • replacement in client/src/Aftok/Timeline.purs at line 229
    [3.2528][3.6575:6669](),[3.6669][3.30756:30828](),[3.30756][3.30756:30828]()
    logEnd :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectList.Event m Unit
    logEnd (Project' p) = do
    logged <- lift $ caps.logEnd p.projectId
    [3.2528]
    [3.30828]
    logEnd :: ProjectId -> H.HalogenM TimelineState TimelineAction Slots ProjectList.Output m Unit
    logEnd pid = do
    logged <- lift $ caps.logEnd pid
  • replacement in client/src/Aftok/Timeline.purs at line 239
    [3.6289][3.6670:6776](),[3.6776][3.31167:31195](),[3.31167][3.31167:31195]()
    setStateForProject :: Project -> H.HalogenM TimelineState TimelineAction Slots ProjectList.Event m Unit
    setStateForProject p = do
    [3.6289]
    [3.31195]
    setStateForProject :: ProjectId -> H.HalogenM TimelineState TimelineAction Slots ProjectList.Output m Unit
    setStateForProject pid = do
  • replacement in client/src/Aftok/Timeline.purs at line 242
    [3.31291][3.31291:31365]()
    intervals' <- lift $ caps.listIntervals (unwrap p).projectId timeSpan
    [3.31291]
    [3.31365]
    intervals' <- lift $ caps.listIntervals pid timeSpan
  • replacement in client/src/Aftok/Timeline.purs at line 254
    [3.31807][3.31807:31882]()
    latestEventResponse <- lift $ caps.getLatestEvent (unwrap p).projectId
    [3.31807]
    [3.31882]
    latestEventResponse <- lift $ caps.getLatestEvent pid
  • replacement in client/src/Aftok/Timeline.purs at line 271
    [3.32599][3.32599:32679]()
    H.modify_ (_ { selectedProject = Just p, history = hist, active = active })
    [3.32599]
    [3.3566]
    H.modify_ (_ { selectedProject = Just pid, history = hist, active = active })
  • replacement in client/src/Main.purs at line 22
    [3.400][3.8323:8363]()
    import Aftok.Types (System, liveSystem)
    [3.400]
    [3.307686]
    import Aftok.Types (System, ProjectId, liveSystem)
  • edit in client/src/Main.purs at line 25
    [3.9265][3.7440:7475]()
    import Aftok.Api.Project (Project)
  • replacement in client/src/Main.purs at line 93
    [3.39317][3.39317:39356]()
    , selectedProject :: Maybe Project
    [3.39317]
    [3.39356]
    , selectedProject :: Maybe ProjectId
  • replacement in client/src/Main.purs at line 100
    [3.1894][3.7476:7512]()
    | ProjectAction ProjectList.Event
    [3.1894]
    [3.1894]
    | ProjectAction ProjectList.Output