style forms and add form validation and form status block
This commit is contained in:
committed by
Benedikt Rötsch
parent
f2b2a5845e
commit
6430a2c849
@@ -77,12 +77,13 @@ datalist {
|
|||||||
|
|
||||||
label {
|
label {
|
||||||
display: var(--fh-layout-display);
|
display: var(--fh-layout-display);
|
||||||
margin: var(--fh-layout-margin);
|
margin: var(--fh-layout-margin) 0 0;
|
||||||
text-align: var(--fh-layout-text-align);
|
text-align: var(--fh-layout-text-align);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
font-size: 0.875em;
|
||||||
|
|
||||||
& + input {
|
& + input {
|
||||||
margin-top: calc(var(--grid-gutter) * 0.5 * -1)
|
margin-top: calc(var(--grid-gutter) * 0.25)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,9 +234,13 @@ button[disabled] {
|
|||||||
input:focus,
|
input:focus,
|
||||||
textarea:focus,
|
textarea:focus,
|
||||||
select:focus,
|
select:focus,
|
||||||
option:focus,
|
option:focus {
|
||||||
|
border-color: var(--color-palette-blue);
|
||||||
|
color: var(--fh-font-color);
|
||||||
|
}
|
||||||
|
|
||||||
button:focus,
|
button:focus,
|
||||||
.cta:focus {
|
.cta:focus {
|
||||||
background-color: var(--fh-button-hover-bg-color);
|
background-color: var(--fh-button-hover-bg-color);
|
||||||
color: var(--fh-button-hover-font-color);
|
color: var(--fh-button-hover-font-color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,65 @@
|
|||||||
.form-item {
|
@block form-item {
|
||||||
& + .form-item {
|
& + .form-item {
|
||||||
margin-top: var(--grid-gutter);
|
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);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,13 @@
|
|||||||
--color-palette-blue: #5c9fef;
|
--color-palette-blue: #5c9fef;
|
||||||
--color-palette-blue-medium: #3c80cf;
|
--color-palette-blue-medium: #3c80cf;
|
||||||
--color-palette-blue-dark: #3072be;
|
--color-palette-blue-dark: #3072be;
|
||||||
|
--color-palette-red: #cd3f39;
|
||||||
|
|
||||||
--color-text-default: #2a3039;
|
--color-text-default: #2a3039;
|
||||||
--color-text-grey: #8091a5;
|
--color-text-grey: #8091a5;
|
||||||
--color-text-dark-bg: #fff;
|
--color-text-dark-bg: #fff;
|
||||||
--color-text-dark-bg-grey: #a9b9c0;
|
--color-text-dark-bg-grey: #a9b9c0;
|
||||||
|
--color-text-error: var(--color-palette-red);
|
||||||
|
|
||||||
--color-link-content: var(--color-palette-blue);
|
--color-link-content: var(--color-palette-blue);
|
||||||
|
|
||||||
@@ -33,6 +35,10 @@
|
|||||||
--color-course-active: #536171;
|
--color-course-active: #536171;
|
||||||
--color-course-card-description: #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-sidebar-width: 228px;
|
||||||
--layout-sidebar-content-width: 732px;
|
--layout-sidebar-content-width: 732px;
|
||||||
|
|
||||||
|
|||||||
6
public/images/icon-error.svg
Normal file
6
public/images/icon-error.svg
Normal 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 |
4
public/images/icon-success.svg
Normal file
4
public/images/icon-success.svg
Normal 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 |
@@ -561,13 +561,14 @@ datalist {
|
|||||||
|
|
||||||
label {
|
label {
|
||||||
display: block;
|
display: block;
|
||||||
margin: 22px 0;
|
margin: 22px 0 0 0;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-weight: bold
|
font-weight: bold;
|
||||||
|
font-size: 0.875em
|
||||||
}
|
}
|
||||||
|
|
||||||
label + input {
|
label + input {
|
||||||
margin-top: -11px;
|
margin-top: 5.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Input & Textarea ------------------ */
|
/* Input & Textarea ------------------ */
|
||||||
@@ -719,9 +720,13 @@ button[disabled] {
|
|||||||
input:focus,
|
input:focus,
|
||||||
textarea:focus,
|
textarea:focus,
|
||||||
select:focus,
|
select:focus,
|
||||||
option:focus,
|
option:focus {
|
||||||
|
border-color: #5c9fef;
|
||||||
|
color: rgb(40, 40, 40);
|
||||||
|
}
|
||||||
|
|
||||||
button:focus,
|
button:focus,
|
||||||
.cta:focus {
|
.cta:focus {
|
||||||
background-color: rgb(125, 178, 242);
|
background-color: rgb(125, 178, 242);
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
@@ -749,10 +754,70 @@ input[type="reset"]:focus,
|
|||||||
margin-top: 22px;
|
margin-top: 22px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-item .help-text {
|
.form-item input {
|
||||||
font-size: 0.9em;
|
margin-bottom: 5.5px;
|
||||||
color: #8091a5;
|
}
|
||||||
margin-top: -22px;
|
|
||||||
|
.form-item__help-text {
|
||||||
|
font-size: 0.875em;
|
||||||
|
color: #8091a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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 {
|
.layout-centered {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const express = require('express')
|
const express = require('express')
|
||||||
|
const { createClient } = require('contentful')
|
||||||
const { catchErrors } = require('../handlers/errorHandlers')
|
const { catchErrors } = require('../handlers/errorHandlers')
|
||||||
const router = express.Router()
|
const router = express.Router()
|
||||||
|
|
||||||
@@ -6,14 +7,117 @@ const router = express.Router()
|
|||||||
router.get('/', catchErrors(async function (req, res, next) {
|
router.get('/', catchErrors(async function (req, res, next) {
|
||||||
const cookie = req.cookies.theExampleAppSettings
|
const cookie = req.cookies.theExampleAppSettings
|
||||||
const settings = cookie || { cpa: '', cda: '', space: '' }
|
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. */
|
/* POST settings page. */
|
||||||
router.post('/', catchErrors(async function (req, res, next) {
|
router.post('/', catchErrors(async function (req, res, next) {
|
||||||
const settings = {space: req.body.space, cda: req.body.cda, cpa: req.body.cpa}
|
const errorList = []
|
||||||
res.cookie('theExampleAppSettings', settings, { maxAge: 900000, httpOnly: true })
|
const { space, cda, cpa } = req.body
|
||||||
res.render('settings', settings)
|
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 })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
module.exports = router
|
||||||
|
|||||||
@@ -5,21 +5,43 @@ block content
|
|||||||
h1= title
|
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.
|
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(action=`/settings` method="POST" class="form")
|
||||||
.form-item
|
.form-item
|
||||||
label(for="space") Space ID
|
label(for="space") Space ID
|
||||||
input(type="text" name="space" value=settings.space)
|
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
|
.form-item
|
||||||
label(for="cda") Delivery API key
|
label(for="cda") Delivery API key
|
||||||
input(type="text" name="cda" value=settings.cda)
|
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
|
.form-item
|
||||||
label(for="cpa") Preview API key
|
label(for="cpa") Preview API key
|
||||||
input(type="text" name="cpa" value=settings.cpa)
|
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
|
.form-item
|
||||||
input.cta(type="submit" value="Load settings")
|
input.cta(type="submit" value="Load settings")
|
||||||
|
|||||||
Reference in New Issue
Block a user