Modularise the UI with widgets
This commit is contained in:
parent
3e274d94f8
commit
7ebc6b0eda
|
@ -49,6 +49,7 @@ executable datarekisteri-frontend
|
||||||
Datarekisteri.Frontend.Handlers.Profile,
|
Datarekisteri.Frontend.Handlers.Profile,
|
||||||
Datarekisteri.Frontend.Handlers.VerifyEmail,
|
Datarekisteri.Frontend.Handlers.VerifyEmail,
|
||||||
Datarekisteri.Frontend.Handlers.Members,
|
Datarekisteri.Frontend.Handlers.Members,
|
||||||
Datarekisteri.Frontend.Types
|
Datarekisteri.Frontend.Types,
|
||||||
|
Datarekisteri.Frontend.Widgets
|
||||||
hs-source-dirs: src
|
hs-source-dirs: src
|
||||||
default-language: Haskell2010
|
default-language: Haskell2010
|
||||||
|
|
|
@ -21,6 +21,8 @@ import Yesod.Auth
|
||||||
|
|
||||||
import qualified Yesod.Auth.Message as Msg
|
import qualified Yesod.Auth.Message as Msg
|
||||||
|
|
||||||
|
import Datarekisteri.Frontend.Widgets
|
||||||
|
|
||||||
pluginName = "externalBasic"
|
pluginName = "externalBasic"
|
||||||
|
|
||||||
loginR = PluginR pluginName ["login"]
|
loginR = PluginR pluginName ["login"]
|
||||||
|
@ -53,9 +55,7 @@ loginForm :: YesodAuth site => (Route Auth -> Route site) -> WidgetFor site ()
|
||||||
loginForm toParent = do
|
loginForm toParent = do
|
||||||
emailId <- newIdent
|
emailId <- newIdent
|
||||||
passwordId <- newIdent
|
passwordId <- newIdent
|
||||||
[whamlet|
|
let formContent = [whamlet|
|
||||||
<h1>Kirjaudu
|
|
||||||
<form action=@{toParent loginR} method=post>
|
|
||||||
<label for="#{emailId}">
|
<label for="#{emailId}">
|
||||||
Sähköposti
|
Sähköposti
|
||||||
<input id="#{emailId}" name="email" type="text" autofocus>
|
<input id="#{emailId}" name="email" type="text" autofocus>
|
||||||
|
@ -63,4 +63,8 @@ loginForm toParent = do
|
||||||
Salasana
|
Salasana
|
||||||
<input id="#{passwordId}" name="password" type="password">
|
<input id="#{passwordId}" name="password" type="password">
|
||||||
<input type="submit" value="Kirjaudu">
|
<input type="submit" value="Kirjaudu">
|
||||||
|
|]
|
||||||
|
[whamlet|
|
||||||
|
<h1>Kirjaudu
|
||||||
|
^{form (toParent loginR) UrlEncoded formContent}
|
||||||
|]
|
|]
|
||||||
|
|
|
@ -25,6 +25,7 @@ import Datarekisteri.Core.Types hiding (Applications)
|
||||||
import Datarekisteri.Frontend.ApiRequests
|
import Datarekisteri.Frontend.ApiRequests
|
||||||
import Datarekisteri.Frontend.FormFields
|
import Datarekisteri.Frontend.FormFields
|
||||||
import Datarekisteri.Frontend.Types
|
import Datarekisteri.Frontend.Types
|
||||||
|
import Datarekisteri.Frontend.Widgets
|
||||||
|
|
||||||
declareLocalTypesInline "schema.gql" [raw|
|
declareLocalTypesInline "schema.gql" [raw|
|
||||||
query Applications {
|
query Applications {
|
||||||
|
@ -65,41 +66,54 @@ applicationsW applications = do
|
||||||
acceptRoute <- handlerToWidget $ maybeAuthorized AcceptR True
|
acceptRoute <- handlerToWidget $ maybeAuthorized AcceptR True
|
||||||
forM_ applications $ \ApplicationsApplications {..} -> do
|
forM_ applications $ \ApplicationsApplications {..} -> do
|
||||||
(widget, enctype) <- handlerToWidget $ generateFormPost $ resolveApplicationForm $ Just id
|
(widget, enctype) <- handlerToWidget $ generateFormPost $ resolveApplicationForm $ Just id
|
||||||
|
let keysAndValues =
|
||||||
|
[ ("Nimi" :: Text, name)
|
||||||
|
, ("Kutsumanimi", nickname)
|
||||||
|
, ("Kotipaikka", homeplace)
|
||||||
|
, ("Syntymäaika", renderDate birthdate)
|
||||||
|
, ("Sähköposti", renderEmail $ fromJust email)
|
||||||
|
, ("Puhelinnumero", renderPhoneNumber phoneNumber)
|
||||||
|
]
|
||||||
|
acceptFormContent = [whamlet|
|
||||||
|
^{widget}
|
||||||
|
^{submitButton "Hyväksy"}
|
||||||
|
|]
|
||||||
|
rejectFormContent = [whamlet|
|
||||||
|
^{widget}
|
||||||
|
^{submitButtonBad "Hylkää"}
|
||||||
|
|]
|
||||||
|
detailsClass <- newIdent
|
||||||
[whamlet|
|
[whamlet|
|
||||||
<details>
|
<details .#{detailsClass}>
|
||||||
<summary>
|
<summary>
|
||||||
<h2>#{name}
|
<h2>#{name}
|
||||||
<article>
|
<article>
|
||||||
<h3>Hakemus
|
<h3>Hakemus
|
||||||
<p>#{Textarea application}
|
<p>#{Textarea application}
|
||||||
<table>
|
^{keyValueTable keysAndValues}
|
||||||
<tr>
|
|
||||||
<th scope="row">Nimi
|
|
||||||
<td>#{name}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Kutsumanimi
|
|
||||||
<td>#{nickname}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Kotipaikka
|
|
||||||
<td>#{homeplace}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Syntymäaika
|
|
||||||
<td>#{renderDate $ birthdate}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Sähköposti
|
|
||||||
<td>#{renderEmail $ fromJust $ email}
|
|
||||||
<tr>
|
|
||||||
<th scope="row">Puhelinnumero
|
|
||||||
<td>#{renderPhoneNumber $ phoneNumber}
|
|
||||||
$maybe route <- acceptRoute
|
$maybe route <- acceptRoute
|
||||||
<form action="@{route}" method="post" enctype="#{enctype}">
|
^{form route enctype acceptFormContent}
|
||||||
^{widget}
|
|
||||||
<input type="submit" value="Hyväksy">
|
|
||||||
$maybe route <- rejectRoute
|
$maybe route <- rejectRoute
|
||||||
<form action="@{route}" method="post" enctype="#{enctype}">
|
^{form route enctype rejectFormContent}
|
||||||
^{widget}
|
|]
|
||||||
<input type="submit" value="Hylkää" class="reject-button">
|
toWidget [lucius|
|
||||||
|]
|
.#{detailsClass} {
|
||||||
|
padding: 1.3em;
|
||||||
|
margin: 0.6em;
|
||||||
|
border-color: #00838a;
|
||||||
|
border-width: 0 0 0 0.6em;
|
||||||
|
border-style: solid;
|
||||||
|
border-radius: 0.4em;
|
||||||
|
background-color: #f6f8f8;
|
||||||
|
> summary > h2 {
|
||||||
|
display: inline-block;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
> article { margin-bottom: 1.5em }
|
||||||
|
}
|
||||||
|
.#{detailsClass}[open] { border-color: #339ca1; }
|
||||||
|
|]
|
||||||
|
|
||||||
getApplicationsR :: (YesodAuth DataIdClient, AuthId DataIdClient ~ Text) => Handler Html
|
getApplicationsR :: (YesodAuth DataIdClient, AuthId DataIdClient ~ Text) => Handler Html
|
||||||
getApplicationsR = do
|
getApplicationsR = do
|
||||||
|
|
|
@ -24,6 +24,7 @@ import Datarekisteri.Core.Types
|
||||||
import Datarekisteri.Frontend.ApiRequests
|
import Datarekisteri.Frontend.ApiRequests
|
||||||
import Datarekisteri.Frontend.FormFields
|
import Datarekisteri.Frontend.FormFields
|
||||||
import Datarekisteri.Frontend.Types
|
import Datarekisteri.Frontend.Types
|
||||||
|
import Datarekisteri.Frontend.Widgets
|
||||||
|
|
||||||
declareLocalTypesInline "schema.gql" [raw|
|
declareLocalTypesInline "schema.gql" [raw|
|
||||||
mutation Apply($name: String!, $nickname: String, $homeplace: String!,
|
mutation Apply($name: String!, $nickname: String, $homeplace: String!,
|
||||||
|
@ -60,17 +61,19 @@ applyForm = renderDivs $ ApplyArgs
|
||||||
applyW :: (Widget, Enctype) -> Widget
|
applyW :: (Widget, Enctype) -> Widget
|
||||||
applyW (applyWidget, applyEnctype) = do
|
applyW (applyWidget, applyEnctype) = do
|
||||||
setTitle "Jäsenhakemus"
|
setTitle "Jäsenhakemus"
|
||||||
[whamlet|
|
let formContent = [whamlet|
|
||||||
<h1>
|
|
||||||
Jäsenhakemus
|
|
||||||
<form action="@{ApplyR}" method="post" enctype="#{applyEnctype}">
|
|
||||||
^{applyWidget}
|
^{applyWidget}
|
||||||
<p>
|
<p>
|
||||||
Lähettämällä jäsenhakemuksen vakuutat antamiesi tietojen oikeellisuuden ja puuttettomuuden
|
Lähettämällä jäsenhakemuksen vakuutat antamiesi tietojen oikeellisuuden ja puuttettomuuden
|
||||||
sekä sitoudut pitämään ne ajan tasalla. Rekisteriin kirjattuja tietoja käsitellään
|
sekä sitoudut pitämään ne ajan tasalla. Rekisteriin kirjattuja tietoja käsitellään
|
||||||
<a href="https://datat.fi/rekisteriseloste">rekisteriselosteen</a> mukaisesti.
|
<a href="https://datat.fi/rekisteriseloste">rekisteriselosteen</a> mukaisesti.
|
||||||
<input type="submit" value="Hae jäseneksi">
|
^{submitButton "Hae Jäseneksi"}
|
||||||
|]
|
|]
|
||||||
|
[whamlet|
|
||||||
|
<h1>
|
||||||
|
Jäsenhakemus
|
||||||
|
^{form ApplyR applyEnctype formContent}
|
||||||
|
|]
|
||||||
|
|
||||||
getApplyR :: (YesodAuth DataIdClient, AuthId DataIdClient ~ Text) => Handler Html
|
getApplyR :: (YesodAuth DataIdClient, AuthId DataIdClient ~ Text) => Handler Html
|
||||||
getApplyR = do
|
getApplyR = do
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{-# LANGUAGE DeriveGeneric #-}
|
{-# LANGUAGE DeriveGeneric #-}
|
||||||
{-# LANGUAGE FlexibleContexts #-}
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
{-# LANGUAGE OverloadedStrings #-}
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
{-# LANGUAGE QuasiQuotes #-}
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
{-# LANGUAGE RecordWildCards #-}
|
{-# LANGUAGE RecordWildCards #-}
|
||||||
|
@ -19,6 +20,7 @@ import Yesod.Auth
|
||||||
|
|
||||||
import Datarekisteri.Frontend.Types
|
import Datarekisteri.Frontend.Types
|
||||||
import Datarekisteri.Frontend.ApiRequests
|
import Datarekisteri.Frontend.ApiRequests
|
||||||
|
import Datarekisteri.Frontend.Widgets
|
||||||
import Datarekisteri.Core.Types
|
import Datarekisteri.Core.Types
|
||||||
|
|
||||||
declareLocalTypesInline "schema.gql" [raw|
|
declareLocalTypesInline "schema.gql" [raw|
|
||||||
|
@ -32,6 +34,10 @@ query MembersPage {
|
||||||
}
|
}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
|
instance ToTableRow DataIdClient MembersPageUsers where
|
||||||
|
tableHeader _ = toWidget <$> ["Nimi" :: Text, "Kutsumanimi", "Kotipaikka"]
|
||||||
|
toCells MembersPageUsers {..} = toWidget <$> [name, nickname, homeplace]
|
||||||
|
|
||||||
getMembersR :: (YesodAuth DataIdClient, AuthId DataIdClient ~ Text) => Handler Html
|
getMembersR :: (YesodAuth DataIdClient, AuthId DataIdClient ~ Text) => Handler Html
|
||||||
getMembersR = do
|
getMembersR = do
|
||||||
MembersPage {..} <- apiRequest True ()
|
MembersPage {..} <- apiRequest True ()
|
||||||
|
|
|
@ -23,6 +23,7 @@ import Datarekisteri.Core.Types
|
||||||
import Datarekisteri.Frontend.ApiRequests
|
import Datarekisteri.Frontend.ApiRequests
|
||||||
import Datarekisteri.Frontend.Types
|
import Datarekisteri.Frontend.Types
|
||||||
import Datarekisteri.Frontend.FormFields
|
import Datarekisteri.Frontend.FormFields
|
||||||
|
import Datarekisteri.Frontend.Widgets
|
||||||
|
|
||||||
declareLocalTypesInline "schema.gql" [raw|
|
declareLocalTypesInline "schema.gql" [raw|
|
||||||
query ProfilePage($id: UserID) {
|
query ProfilePage($id: UserID) {
|
||||||
|
@ -100,20 +101,24 @@ profile user (profileWidget, profileEnctype) (passwordWidget, passwordEnctype) =
|
||||||
setTitle "Muokkaa profiilia"
|
setTitle "Muokkaa profiilia"
|
||||||
let userID = let ProfilePageUser {..} = user in id
|
let userID = let ProfilePageUser {..} = user in id
|
||||||
passwordRoute <- handlerToWidget $ maybeAuthorized (UpdatePasswordR userID) True
|
passwordRoute <- handlerToWidget $ maybeAuthorized (UpdatePasswordR userID) True
|
||||||
|
let profileFormContent = [whamlet|
|
||||||
|
^{profileWidget}
|
||||||
|
^{submitButton "Päivitä tiedot"}
|
||||||
|
|]
|
||||||
|
passwordFormContent = [whamlet|
|
||||||
|
^{passwordWidget}
|
||||||
|
^{submitButton "Vaihda salasana"}
|
||||||
|
|]
|
||||||
[whamlet|
|
[whamlet|
|
||||||
<h1>
|
<h1>
|
||||||
$if isMember user
|
$if isMember user
|
||||||
Jäsentiedot
|
Jäsentiedot
|
||||||
$else
|
$else
|
||||||
Jäsenhakemuksen tiedot
|
Jäsenhakemuksen tiedot
|
||||||
<form action="@{ProfileR userID}" method="post" enctype="#{profileEnctype}">
|
^{form (ProfileR userID) profileEnctype profileFormContent}
|
||||||
^{profileWidget}
|
|
||||||
<input type="submit" value="Päivitä tiedot">
|
|
||||||
$maybe route <- passwordRoute
|
$maybe route <- passwordRoute
|
||||||
<form action="@{route}" method="post" enctype="#{passwordEnctype}">
|
^{form route passwordEnctype passwordFormContent}
|
||||||
^{passwordWidget}
|
|]
|
||||||
<input type="submit" value="Vaihda salasana">
|
|
||||||
|]
|
|
||||||
|
|
||||||
getProfile :: (YesodAuth DataIdClient, AuthId DataIdClient ~ Text) => (Maybe UserID) -> Handler Html
|
getProfile :: (YesodAuth DataIdClient, AuthId DataIdClient ~ Text) => (Maybe UserID) -> Handler Html
|
||||||
getProfile userID = do
|
getProfile userID = do
|
||||||
|
|
|
@ -20,6 +20,7 @@ import Yesod.Auth
|
||||||
|
|
||||||
import Datarekisteri.Frontend.ApiRequests
|
import Datarekisteri.Frontend.ApiRequests
|
||||||
import Datarekisteri.Frontend.Types
|
import Datarekisteri.Frontend.Types
|
||||||
|
import Datarekisteri.Frontend.Widgets
|
||||||
import Datarekisteri.Core.Types
|
import Datarekisteri.Core.Types
|
||||||
|
|
||||||
declareLocalTypesInline "schema.gql" [raw|
|
declareLocalTypesInline "schema.gql" [raw|
|
||||||
|
@ -45,12 +46,14 @@ postVerifyEmailR = do
|
||||||
|
|
||||||
verifyEmailW (codeWidget, codeEnctype) = do
|
verifyEmailW (codeWidget, codeEnctype) = do
|
||||||
setTitle "Vahvista sähköpostiosoite"
|
setTitle "Vahvista sähköpostiosoite"
|
||||||
|
let formContent = [whamlet|
|
||||||
|
^{codeWidget}
|
||||||
|
^{submitButton "Vahvista"}
|
||||||
|
|]
|
||||||
[whamlet|
|
[whamlet|
|
||||||
<h1>
|
<h1>
|
||||||
Vahvista sähköpostiosoite
|
Vahvista sähköpostiosoite
|
||||||
<form action="@{VerifyEmailR}" method="post" enctype="#{codeEnctype}">
|
^{form VerifyEmailR codeEnctype formContent}
|
||||||
^{codeWidget}
|
|
||||||
<input type="submit" value="Vahvista">
|
|
||||||
|]
|
|]
|
||||||
|
|
||||||
verifyForm = renderDivs $ VerifyEmailArgs <$> areq textField "Vahvistuskoodi" Nothing
|
verifyForm = renderDivs $ VerifyEmailArgs <$> areq textField "Vahvistuskoodi" Nothing
|
||||||
|
|
|
@ -163,47 +163,105 @@ instance Yesod DataIdClient where
|
||||||
errorHandler x = defaultErrorHandler x
|
errorHandler x = defaultErrorHandler x
|
||||||
addStaticContent = addStaticContentExternal Right base64md5 "/tmp/data-id" (StaticR . flip StaticRoute [])
|
addStaticContent = addStaticContentExternal Right base64md5 "/tmp/data-id" (StaticR . flip StaticRoute [])
|
||||||
defaultLayout widget = do
|
defaultLayout widget = do
|
||||||
|
let messages :: Widget
|
||||||
|
messages = do
|
||||||
msgs <- getMessages
|
msgs <- getMessages
|
||||||
applicationsRoute <- maybeAuthorized ApplicationsR False
|
messagesClass <- newIdent
|
||||||
profileRoute <- maybeAuthorized OwnProfileR False
|
messageClass <- newIdent
|
||||||
membersRoute <- maybeAuthorized MembersR False
|
toWidget [hamlet|
|
||||||
|
$if not $ null msgs
|
||||||
|
<aside class="#{messagesClass}">
|
||||||
|
<ul>
|
||||||
|
$forall (_, msg) <- msgs
|
||||||
|
<li class="#{messageClass}">#{msg}
|
||||||
|
|]
|
||||||
|
toWidget [lucius|
|
||||||
|
aside.#{messagesClass} > ul {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
li.#{messageClass} {
|
||||||
|
display: block;
|
||||||
|
padding: 0.7em;
|
||||||
|
background-color: #3b4553;
|
||||||
|
color: white;
|
||||||
|
border-radius: 0.3em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
navigationBar :: Widget
|
||||||
|
navigationBar = do
|
||||||
|
applicationsRoute <- handlerToWidget $ maybeAuthorized ApplicationsR False
|
||||||
|
profileRoute <- handlerToWidget $ maybeAuthorized OwnProfileR False
|
||||||
|
membersRoute <- handlerToWidget $ maybeAuthorized MembersR False
|
||||||
currentRoute <- getCurrentRoute
|
currentRoute <- getCurrentRoute
|
||||||
loggedIn <- isJust <$> maybeAuthId
|
loggedIn <- isJust <$> maybeAuthId
|
||||||
|
navClass <- newIdent
|
||||||
|
activeClass <- newIdent
|
||||||
|
rightClass <- newIdent
|
||||||
|
let navItem :: Bool -> Maybe (Route DataIdClient) -> Text -> HtmlUrl (Route DataIdClient)
|
||||||
|
navItem alignRight maybeRoute name = [hamlet|
|
||||||
|
$maybe route <- maybeRoute
|
||||||
|
<li :Just route == currentRoute:class="#{activeClass}" :alignRight:class="#{rightClass}">
|
||||||
|
<a href="@{route}">#{name}
|
||||||
|
|]
|
||||||
|
rightNavItem = navItem True
|
||||||
|
leftNavItem = navItem False
|
||||||
|
toWidget [hamlet|
|
||||||
|
<nav .#{navClass}>
|
||||||
|
<ul>
|
||||||
|
^{leftNavItem profileRoute "Profiili"}
|
||||||
|
^{leftNavItem membersRoute "Jäsenet"}
|
||||||
|
^{leftNavItem applicationsRoute "Hakemukset"}
|
||||||
|
$if loggedIn
|
||||||
|
^{rightNavItem (Just (AuthR LogoutR)) "Kirjaudu ulos"}
|
||||||
|
$else
|
||||||
|
^{rightNavItem (Just (AuthR LoginR)) "Kirjaudu sisään"}
|
||||||
|
^{rightNavItem (Just ApplyR) "Hae jäseneksi"}
|
||||||
|
|]
|
||||||
|
toWidget [lucius|
|
||||||
|
.#{navClass} {
|
||||||
|
display: block;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
background-color: #{fgColor};
|
||||||
|
ul {
|
||||||
|
list-style-type: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
li {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
a {
|
||||||
|
color: #{bgColor};
|
||||||
|
text-decoration: none;
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.7em 1em;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
background-color: #00838a;
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.#{activeClass} { font-weight: 500; }
|
||||||
|
.#{rightClass} { float: right; }
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
p <- widgetToPageContent $ do
|
p <- widgetToPageContent $ do
|
||||||
[whamlet|
|
[whamlet|
|
||||||
<header>
|
<header>
|
||||||
<nav>
|
^{navigationBar}
|
||||||
<ul>
|
|
||||||
$maybe route <- profileRoute
|
|
||||||
<li :Just route == currentRoute:class="active-nav">
|
|
||||||
<a href="@{route}">Profiili
|
|
||||||
$maybe route <- membersRoute
|
|
||||||
<li :Just route == currentRoute:class="active-nav">
|
|
||||||
<a href="@{route}">Jäsenet
|
|
||||||
$maybe route <- applicationsRoute
|
|
||||||
<li :Just route == currentRoute:class="active-nav">
|
|
||||||
<a href="@{route}">Hakemukset
|
|
||||||
$if loggedIn
|
|
||||||
<li class="left-nav">
|
|
||||||
<a href="@{AuthR LogoutR}">Kirjaudu ulos
|
|
||||||
$else
|
|
||||||
<li :Just (AuthR LoginR) == currentRoute:class="active-nav" class="left-nav">
|
|
||||||
<a href="@{AuthR LoginR}">Kirjaudu sisään
|
|
||||||
<li :Just ApplyR == currentRoute:class="active-nav" class="left-nav">
|
|
||||||
<a href="@{ApplyR}">Hae jäseneksi
|
|
||||||
<main>
|
<main>
|
||||||
$if not $ null msgs
|
^{messages}
|
||||||
<aside class="messages">
|
|
||||||
<ul>
|
|
||||||
$forall (_, msg) <- msgs
|
|
||||||
<li class="message">#{msg}
|
|
||||||
^{widget}
|
^{widget}
|
||||||
|] :: Widget
|
|] :: Widget
|
||||||
toWidget $ [lucius|
|
toWidget $ [lucius|
|
||||||
:root {
|
|
||||||
--bg-colour: #ffffff;
|
|
||||||
--fg-colour: #181c22;
|
|
||||||
}
|
|
||||||
* {
|
* {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
@ -211,135 +269,17 @@ instance Yesod DataIdClient where
|
||||||
font-family: "Fira Sans", sans-serif;
|
font-family: "Fira Sans", sans-serif;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
th[scope="row"] {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
th[scope="col"] {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
body {
|
body {
|
||||||
background-color: #e8eaef;
|
background-color: #e8eaef;
|
||||||
color: var(--fg-colour);
|
color: #{fgColor};
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
label {
|
|
||||||
display: block;
|
|
||||||
padding-top: 0.3em;
|
|
||||||
padding-bottom: 0.3em;
|
|
||||||
}
|
|
||||||
input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.4em;
|
|
||||||
margin-top: 0.3em;
|
|
||||||
margin-bottom: 0.3em;
|
|
||||||
border-radius: 0.25em;
|
|
||||||
}
|
|
||||||
textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.4em;
|
|
||||||
margin-top: 0.3em;
|
|
||||||
margin-bottom: 0.3em;
|
|
||||||
}
|
|
||||||
form {
|
|
||||||
max-width: min(30em,100%);
|
|
||||||
}
|
|
||||||
summary > h2 {
|
|
||||||
display: inline-block;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
details > table th {
|
|
||||||
text-align: left;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
details {
|
|
||||||
padding: 1.3em;
|
|
||||||
margin: 0.6em;
|
|
||||||
border-color: #00838a;
|
|
||||||
border-width: 0 0 0 0.6em;
|
|
||||||
border-style: solid;
|
|
||||||
border-radius: 0.4em;
|
|
||||||
background-color: #f6f8f8;
|
|
||||||
}
|
|
||||||
details[open] {
|
|
||||||
border-color: #339ca1;
|
|
||||||
}
|
|
||||||
details > article {
|
|
||||||
margin-bottom: 1.5em
|
|
||||||
}
|
|
||||||
input[type="submit"] {
|
|
||||||
background-color: #00838a;
|
|
||||||
color: #ffffff;
|
|
||||||
padding: 0.5em 1em;
|
|
||||||
border: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
input[type="submit"]:hover {
|
|
||||||
background-color: #339ca1;
|
|
||||||
}
|
|
||||||
input[type="submit"].reject-button {
|
|
||||||
background-color: #8a003a;
|
|
||||||
}
|
|
||||||
input[type="submit"].reject-button:hover {
|
|
||||||
background-color: #aa3968;
|
|
||||||
}
|
|
||||||
input:invalid {
|
|
||||||
border-color: #8a003a;
|
|
||||||
}
|
|
||||||
nav {
|
|
||||||
display: block;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
width: 100%;
|
|
||||||
padding: 0;
|
|
||||||
background-color: var(--fg-colour);
|
|
||||||
}
|
|
||||||
nav ul {
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
nav li {
|
|
||||||
display: block;
|
|
||||||
float: left;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
nav li a {
|
|
||||||
color: var(--bg-colour);
|
|
||||||
text-decoration: none;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.7em 1em;
|
|
||||||
}
|
|
||||||
nav li a:hover {
|
|
||||||
background-color: #00838a;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
.active-nav {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
.left-nav {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
aside.messages > ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
aside.messages li.message {
|
|
||||||
display: block;
|
|
||||||
padding: 0.7em;
|
|
||||||
background-color: #3b4553;
|
|
||||||
color: white;
|
|
||||||
border-radius: 0.3em;
|
|
||||||
}
|
|
||||||
main {
|
main {
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 50em;
|
max-width: 50em;
|
||||||
padding: 5em 1em 3em 1em;
|
padding: 5em 1em 3em 1em;
|
||||||
background-color: var(--bg-colour);
|
background-color: #{bgColor};
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
@ -355,6 +295,10 @@ instance Yesod DataIdClient where
|
||||||
^{pageBody p}
|
^{pageBody p}
|
||||||
|]
|
|]
|
||||||
|
|
||||||
|
bgColor, fgColor :: Text
|
||||||
|
bgColor = "#ffffff"
|
||||||
|
fgColor = "#181c22"
|
||||||
|
|
||||||
instance ApiRequest DataIdClient where
|
instance ApiRequest DataIdClient where
|
||||||
getApiUrl = configServerUrl . getConfig <$> getYesod
|
getApiUrl = configServerUrl . getConfig <$> getYesod
|
||||||
authIdToAuthorization = flip const
|
authIdToAuthorization = flip const
|
||||||
|
|
|
@ -0,0 +1,124 @@
|
||||||
|
{-# LANGUAGE ConstraintKinds #-}
|
||||||
|
{-# LANGUAGE FlexibleContexts #-}
|
||||||
|
{-# LANGUAGE MultiParamTypeClasses #-}
|
||||||
|
{-# LANGUAGE QuasiQuotes #-}
|
||||||
|
{-# LANGUAGE ScopedTypeVariables #-}
|
||||||
|
|
||||||
|
module Datarekisteri.Frontend.Widgets where
|
||||||
|
|
||||||
|
import Data.Text (Text)
|
||||||
|
|
||||||
|
import Yesod
|
||||||
|
|
||||||
|
type Widget' a = WidgetFor a ()
|
||||||
|
|
||||||
|
submitButton :: Text -> Widget' a
|
||||||
|
submitButton text = do
|
||||||
|
toWidget [hamlet|<input type="submit" value="#{text}">|]
|
||||||
|
|
||||||
|
submitButtonBad :: Text -> Widget' a
|
||||||
|
submitButtonBad text = do
|
||||||
|
buttonId <- newIdent
|
||||||
|
toWidget [hamlet|<input id="#{buttonId}" type="submit" value="#{text}">|]
|
||||||
|
toWidget [lucius|
|
||||||
|
input[type="submit"]##{buttonId} {
|
||||||
|
background-color: #8a003a;
|
||||||
|
}
|
||||||
|
input[type="submit"]##{buttonId}:hover {
|
||||||
|
background-color: #aa3968;
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
|
keyValueTable :: (ToWidget site a, ToWidget site b) => [(a, b)] -> Widget' site
|
||||||
|
keyValueTable rows = do
|
||||||
|
tableClass <- newIdent
|
||||||
|
[whamlet|
|
||||||
|
<table .#{tableClass}>
|
||||||
|
$forall row <- rows
|
||||||
|
<tr>
|
||||||
|
<th scope="row">^{fst row}
|
||||||
|
<td>^{snd row}
|
||||||
|
|]
|
||||||
|
toWidget [lucius|
|
||||||
|
.#{tableClass} {
|
||||||
|
th { text-align: right; }
|
||||||
|
td { text-align: left; }
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
|
form :: Route a -> Enctype -> Widget' a -> Widget' a
|
||||||
|
form route enctype content = do
|
||||||
|
formClass <- newIdent
|
||||||
|
[whamlet|
|
||||||
|
<form .#{formClass} action="@{route}" method="post" enctype="#{enctype}">
|
||||||
|
^{content}
|
||||||
|
|]
|
||||||
|
toWidget [lucius|
|
||||||
|
form.#{formClass} {
|
||||||
|
max-width: min(30em,100%);
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
padding-top: 0.3em;
|
||||||
|
padding-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.4em;
|
||||||
|
margin-top: 0.3em;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
}
|
||||||
|
input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.4em;
|
||||||
|
margin-top: 0.3em;
|
||||||
|
margin-bottom: 0.3em;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
}
|
||||||
|
input:invalid {
|
||||||
|
border-color: #8a003a;
|
||||||
|
}
|
||||||
|
input[type="submit"] {
|
||||||
|
background-color: #00838a;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 0.5em 1em;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
input[type="submit"]:hover {
|
||||||
|
background-color: #339ca1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
|
table' :: [Widget' a] -> [[Widget' a]] -> Widget' a
|
||||||
|
table' header rows = do
|
||||||
|
tableClass <- newIdent
|
||||||
|
thClass <- newIdent
|
||||||
|
[whamlet|
|
||||||
|
<table .#{tableClass}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
$forall cell <- header
|
||||||
|
<th .#{thClass} scope="col">
|
||||||
|
^{cell}
|
||||||
|
$forall row <- rows
|
||||||
|
<tr>
|
||||||
|
$forall cell <- row
|
||||||
|
<td>
|
||||||
|
^{cell}
|
||||||
|
|]
|
||||||
|
toWidget [lucius|
|
||||||
|
table.#{tableClass} {
|
||||||
|
th[scope="col"].#{thClass} {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|]
|
||||||
|
|
||||||
|
table :: forall a site. ToTableRow site a => [a] -> Widget' site
|
||||||
|
table rows = table' (tableHeader (undefined :: a)) (toCells <$> rows)
|
||||||
|
|
||||||
|
class ToTableRow site a where
|
||||||
|
tableHeader :: a -> [Widget' site]
|
||||||
|
toCells :: a -> [Widget' site]
|
Loading…
Reference in New Issue