style forms and add form validation and form status block

This commit is contained in:
Benedikt Rötsch
2017-10-04 17:01:23 +02:00
committed by Benedikt Rötsch
parent f2b2a5845e
commit 6430a2c849
8 changed files with 291 additions and 24 deletions

View File

@@ -77,12 +77,13 @@ datalist {
label {
display: var(--fh-layout-display);
margin: var(--fh-layout-margin);
margin: var(--fh-layout-margin) 0 0;
text-align: var(--fh-layout-text-align);
font-weight: bold;
font-size: 0.875em;
& + input {
margin-top: calc(var(--grid-gutter) * 0.5 * -1)
margin-top: calc(var(--grid-gutter) * 0.25)
}
}
@@ -233,7 +234,11 @@ button[disabled] {
input:focus,
textarea:focus,
select:focus,
option:focus,
option:focus {
border-color: var(--color-palette-blue);
color: var(--fh-font-color);
}
button:focus,
.cta:focus {
background-color: var(--fh-button-hover-bg-color);

View File

@@ -1,10 +1,65 @@
.form-item {
@block form-item {
& + .form-item {
margin-top: var(--grid-gutter);
}
& .help-text {
font-size: 0.9em;
& input {
margin-bottom: calc(var(--grid-gutter) * 0.25);
}
@element help-text {
font-size: 0.875em;
color: var(--color-text-grey);
margin-top: calc(var(--grid-gutter) * -1);
}
@element error-message {
font-size: 0.875em;
color: var(--color-text-error);
&:before {
content: '';
display: inline-block;
background: url('/images/icon-error.svg') no-repeat left center;
background-size: contain;
width: 12px;
height: 12px;
padding-right: 0.3em;
}
}
}
@block status-block {
display: flex;
margin: var(--grid-gutter) 0;
padding: calc(var(--grid-gutter) * 1);
border-radius: var(--border-radius);
font-size: 0.875em;
@modifier success {
background: var(--color-status-block-bg-success);
}
@modifier error {
background: var(--color-status-block-bg-error);
}
@element icon {
flex: 0 0 auto;
width: 24px;
height: 24px;
margin-right: calc(var(--grid-gutter) * 0.5);
}
@element content {
flex: 1 1 auto;
}
@element title {
font-weight: bold;
}
@element message {
color: var(--color-status-block-message);
}
}

View File

@@ -13,11 +13,13 @@
--color-palette-blue: #5c9fef;
--color-palette-blue-medium: #3c80cf;
--color-palette-blue-dark: #3072be;
--color-palette-red: #cd3f39;
--color-text-default: #2a3039;
--color-text-grey: #8091a5;
--color-text-dark-bg: #fff;
--color-text-dark-bg-grey: #a9b9c0;
--color-text-error: var(--color-palette-red);
--color-link-content: var(--color-palette-blue);
@@ -33,6 +35,10 @@
--color-course-active: #536171;
--color-course-card-description: #536171;
--color-status-block-bg-success: #f4fffb;
--color-status-block-bg-error: #fbe3e2;
--color-status-block-message: #536171;
--layout-sidebar-sidebar-width: 228px;
--layout-sidebar-content-width: 732px;

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<g fill="none" fill-rule="evenodd">
<path d="M-2-2h24v24H-2z"/>
<path fill="#CD3F39" fill-rule="nonzero" d="M10 0C4.48 0 0 4.48 0 10s4.48 10 10 10 10-4.48 10-10S15.52 0 10 0zm1 15H9v-2h2v2zm0-4H9V5h2v6z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 291 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20">
<path fill="none" d="M-1-1h582v402H-1z"/>
<path fill="#0EB87F" fill-rule="evenodd" d="M10 15.5c-2.33 0-4.31-1.46-5.11-3.5h10.22c-.8 2.04-2.78 3.5-5.11 3.5M13.5 9a1.5 1.5 0 1 1 0-3 1.5 1.5 0 1 1 0 3m-7 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 1 1 0 3m3.5 9c4.42 0 8-3.58 8-8s-3.58-8-8-8-8 3.58-8 8 3.58 8 8 8m.01-18C15.53 0 20 4.48 20 10s-4.47 10-9.99 10C4.48 20 0 15.52 0 10S4.48 0 10.01 0"/>
</svg>

After

Width:  |  Height:  |  Size: 457 B

View File

@@ -561,13 +561,14 @@ datalist {
label {
display: block;
margin: 22px 0;
margin: 22px 0 0 0;
text-align: left;
font-weight: bold
font-weight: bold;
font-size: 0.875em
}
label + input {
margin-top: -11px;
margin-top: 5.5px;
}
/* Input & Textarea ------------------ */
@@ -719,7 +720,11 @@ button[disabled] {
input:focus,
textarea:focus,
select:focus,
option:focus,
option:focus {
border-color: #5c9fef;
color: rgb(40, 40, 40);
}
button:focus,
.cta:focus {
background-color: rgb(125, 178, 242);
@@ -749,10 +754,70 @@ input[type="reset"]:focus,
margin-top: 22px;
}
.form-item .help-text {
font-size: 0.9em;
.form-item input {
margin-bottom: 5.5px;
}
.form-item__help-text {
font-size: 0.875em;
color: #8091a5;
margin-top: -22px;
}
.form-item__error-message {
font-size: 0.875em;
color: #cd3f39;
}
.form-item__error-message:before {
content: '';
display: inline-block;
background: url('/images/icon-error.svg') no-repeat left center;
background-size: contain;
width: 12px;
height: 12px;
padding-right: 0.3em;
}
.status-block {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
margin: 22px 0;
padding: 22px;
border-radius: 4px;
font-size: 0.875em;
}
.status-block--success {
background: #f4fffb;
}
.status-block--error {
background: #fbe3e2;
}
.status-block__icon {
-webkit-box-flex: 0;
-ms-flex: 0 0 auto;
flex: 0 0 auto;
width: 24px;
height: 24px;
margin-right: 11px;
}
.status-block__content {
-webkit-box-flex: 1;
-ms-flex: 1 1 auto;
flex: 1 1 auto;
}
.status-block__title {
font-weight: bold;
}
.status-block__message {
color: #536171;
}
.layout-centered {

View File

@@ -1,4 +1,5 @@
const express = require('express')
const { createClient } = require('contentful')
const { catchErrors } = require('../handlers/errorHandlers')
const router = express.Router()
@@ -6,14 +7,117 @@ const router = express.Router()
router.get('/', catchErrors(async function (req, res, next) {
const cookie = req.cookies.theExampleAppSettings
const settings = cookie || { cpa: '', cda: '', space: '' }
res.render('settings', { title: 'Settings', settings })
res.render('settings', {
title: 'Settings',
settings,
errors: {},
hasErrors: false,
success: false
})
}))
/* POST settings page. */
router.post('/', catchErrors(async function (req, res, next) {
const settings = {space: req.body.space, cda: req.body.cda, cpa: req.body.cpa}
const errorList = []
const { space, cda, cpa } = req.body
const settings = {space, cda, cpa}
// Validate required fields.
if (!space) {
errorList.push({
field: 'space',
message: 'This field is required'
})
}
if (!cda) {
errorList.push({
field: 'cda',
message: 'This field is required'
})
}
if (!cpa) {
errorList.push({
field: 'cpa',
message: 'This field is required'
})
}
// Validate space and CDA access token.
if (space && cda) {
try {
await createClient({
space,
accessToken: cda
}).getSpace()
} catch (err) {
if (err.response.status === 401) {
errorList.push({
field: 'cda',
message: 'Your Delivery API key is invalid.'
})
} else if (err.response.status === 404) {
errorList.push({
field: 'space',
message: 'This space does not exist.'
})
} else {
errorList.push({
field: 'cda',
message: `Something went wrong: ${err.response.data.message}`
})
}
}
}
// Validate space and CPA access token.
if (space && cpa) {
try {
await createClient({
space,
accessToken: cpa,
host: 'preview.contentful.com'
}).getSpace()
} catch (err) {
if (err.response.status === 401) {
errorList.push({
field: 'cpa',
message: 'Your Preview API key is invalid.'
})
} else if (err.response.status === 404) {
// Already validated via CDA
} else {
errorList.push({
field: 'cpa',
message: `Something went wrong: ${err.response.data.message}`
})
}
}
}
if (!errorList.length) {
res.cookie('theExampleAppSettings', settings, { maxAge: 900000, httpOnly: true })
res.render('settings', settings)
}
// Generate error dictionary
const errors = errorList.reduce((errors, error) => {
return {
...errors,
[error.field]: [
...(errors[error.field] || []),
error.message
]
}
}, {})
res.render('settings', {
title: 'Settings',
settings,
errors,
hasErrors: errorList.length > 0,
success: errorList.length === 0
})
}))
module.exports = router

View File

@@ -5,21 +5,43 @@ block content
h1= title
p To query and get content using the APIs, client applications need to authenticate with both the Space ID and an access token.
if success
.status-block.status-block--success
img.status-block__icon(src='/images/icon-success.svg')
.status-block__content
.status-block__title Changes saved successfully!
if hasErrors
.status-block.status-block--error
img.status-block__icon(src='/images/icon-error.svg')
.status-block__content
.status-block__title Error occurred
.status-block__message Some errors occurred. Please check the error messages next to the fields.
form(action=`/settings` method="POST" class="form")
.form-item
label(for="space") Space ID
input(type="text" name="space" value=settings.space)
.help-text Some help text we still need to define
if 'space' in errors
each message in errors.space
.form-item__error-message= message
.form-item__help-text Some help text we still need to define
.form-item
label(for="cda") Delivery API key
input(type="text" name="cda" value=settings.cda)
.help-text Some help text we still need to define
if 'cda' in errors
each message in errors.cda
.form-item__error-message= message
.form-item__help-text Some help text we still need to define
.form-item
label(for="cpa") Preview API key
input(type="text" name="cpa" value=settings.cpa)
.help-text Some help text we still need to define
if 'cpa' in errors
each message in errors.cpa
.form-item__error-message= message
.form-item__help-text Some help text we still need to define
.form-item
input.cta(type="submit" value="Load settings")