module Main where
import Prelude
import Control.Monad.Trans.Class (lift)
import Data.Foldable (oneOf)
import Data.Maybe (Maybe(..))
import Data.Symbol (SProxy(..))
import Effect (Effect)
import Effect.Class (liftEffect)
import Effect.Aff (launchAff_)
import Halogen as H
import Halogen.Aff (runHalogenAff, awaitBody)
import Halogen.VDom.Driver (runUI)
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 Routing (match)
import Routing.Hash (matchesWith)
import Routing.Match (Match, lit)
import Aftok.Types (System, ProjectId, liveSystem)
import Aftok.Login as Login
import Aftok.Api.Account as Acc
import Aftok.Billing as Billing
import Aftok.Signup as Signup
import Aftok.Timeline as Timeline
import Aftok.Overview as Overview
import Aftok.ProjectList as ProjectList
main :: Effect Unit
main =
runHalogenAff do
body <- awaitBody
let --login = Login.mockCapability
login = Login.apiCapability
signup = Signup.apiCapability
timeline = Timeline.apiCapability
project = ProjectList.apiCapability
overview = Overview.apiCapability
billing = Billing.apiCapability
mainComponent = component liveSystem login signup timeline project overview billing
halogenIO <- runUI mainComponent unit body
void $ liftEffect
$ matchesWith (match mainRoute) \oldMay new ->
when (oldMay /= Just new) do
launchAff_ <<< halogenIO.query <<< H.tell $ Navigate new
data View
= VLoading
| VSignup
| VLogin
| VOverview
| VTimeline
| VBilling
mainRoute :: Match View
mainRoute =
oneOf
[ VSignup <$ lit "signup"
, VLogin <$ lit "login"
, VOverview <$ lit "overview"
, VTimeline <$ lit "timeline"
, VBilling <$ lit "billing"
]
routeHash :: View -> String
routeHash = case _ of
VSignup -> "signup"
VLogin -> "login"
VTimeline -> "timeline"
VOverview -> "overview"
VBilling -> "billing"
VLoading -> ""
-- derive instance genericView :: Generic View _
derive instance eqView :: Eq View
derive instance ordView :: Ord View
data MainQuery a
= Navigate View a
type MainState
= { view :: View
, config :: Signup.Config
, selectedProject :: Maybe ProjectId
}
data MainAction
= Initialize
| LoginAction Login.LoginResult
| SignupAction Signup.SignupResult
| ProjectAction ProjectList.Output
| LogoutAction
type Slots
= ( login :: Login.Slot Unit
, signup :: Signup.Slot Unit
, overview :: Overview.Slot Unit
, timeline :: Timeline.Slot Unit
, billing :: Billing.Slot Unit
)
_login = SProxy :: SProxy "login"
_signup = SProxy :: SProxy "signup"
_overview = SProxy :: SProxy "overview"
_timeline = SProxy :: SProxy "timeline"
_billing = SProxy :: SProxy "billing"
component ::
forall input output m.
Monad m =>
System m ->
Login.Capability m ->
Signup.Capability m ->
Timeline.Capability m ->
ProjectList.Capability m ->
Overview.Capability m ->
Billing.Capability m ->
H.Component HH.HTML MainQuery input output m
component system loginCap signupCap tlCap pCap ovCap bcap =
H.mkComponent
{ initialState
, render
, eval:
H.mkEval
$ H.defaultEval
{ handleAction = handleAction
, handleQuery = handleQuery
, initialize = Just Initialize
}
}
where
initialState :: input -> MainState
initialState _ =
{ view: VLoading
, config: { recaptchaKey: "6LdiA78ZAAAAAGGvDId_JmDbhalduIDZSqbuikfq" }
, selectedProject: Nothing
}
render :: MainState -> H.ComponentHTML MainAction Slots m
render st = case st.view of
VLoading -> 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) ]
VBilling ->
withNavBar
$ HH.div_
[ HH.slot _billing unit (Billing.component system bcap pCap) st.selectedProject (Just <<< ProjectAction) ]
handleAction :: MainAction -> H.HalogenM MainState MainAction Slots output m Unit
handleAction = case _ of
Initialize -> do
route <- lift system.getHash
nextView <- case route of
"login" -> pure VLogin
"signup" -> pure VSignup
other -> do
result <- lift loginCap.checkLogin
pure
$ case result of
Acc.LoginForbidden -> VLogin
Acc.LoginError _ -> VLogin
_ -> case other of
"timeline" -> VTimeline
"billing" -> VBilling
_ -> VOverview
navigate nextView
SignupAction (Signup.SignupComplete _) -> navigate VLogin
SignupAction (Signup.SigninNav) -> navigate VLogin
LoginAction (Login.LoginComplete _) -> navigate VOverview
LogoutAction -> do
lift loginCap.logout
navigate VLogin
ProjectAction (ProjectList.ProjectChange p) ->
H.modify_ (_ { selectedProject = Just p })
handleQuery :: forall a. MainQuery a -> H.HalogenM MainState MainAction Slots output m (Maybe a)
handleQuery = case _ of
Navigate view a -> do
currentView <- H.gets _.view
when (currentView /= view) $ navigate view
pure (Just a)
navigate :: View -> H.HalogenM MainState MainAction Slots output m Unit
navigate view = do
lift $ system.setHash (routeHash view)
H.modify_ (_ { view = view })
withNavBar :: forall s m. H.ComponentHTML MainAction s m -> H.ComponentHTML MainAction s m
withNavBar body =
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
]
]
, body
]
nav :: Array NavItem
nav =
[ { label: "Overview", path: "overview" }
, { label: "Timeline", path: "timeline" }
, { label: "Billing", path: "billing" }
]
brand :: forall a s m. H.ComponentHTML a s m
brand =
HH.div
[ P.classes (ClassName <$> [ "navbar-brand" ]) ]
[ HH.h4
[ P.classes (ClassName <$> [ "font-weight-bold" ]) ]
[ HH.a [ P.href "/" ] [ HH.text "Aftok" ] ]
]
logout :: forall s m. H.ComponentHTML MainAction s m
logout =
HH.button
[ P.classes (ClassName <$> [ "btn", "navbar-btn", "btn-sm", "btn-primary", "lift", "ml-auto" ])
, E.onClick \_ -> Just LogoutAction
]
[ HH.text "Logout" ]
type NavTop
= { label :: String
, items :: Array NavItem
}
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 ]
]