diff --git a/src/assets/images/ajax-loader.gif b/src/assets/images/ajax-loader.gif deleted file mode 100644 index c35c951..0000000 Binary files a/src/assets/images/ajax-loader.gif and /dev/null differ diff --git a/src/config.js b/src/config.js index 43576a3..84d5d3b 100644 --- a/src/config.js +++ b/src/config.js @@ -6,11 +6,11 @@ var development = { } var production = { - stripe: { - publicKey: "pk_live_xrnwdLNXbt20LMxpIDffJnnC" - } + stripe: { + publicKey: "pk_live_xrnwdLNXbt20LMxpIDffJnnC" + } } -module.exports = function() { +module.exports = function () { return process.env.ENV == 'development' ? development : production; } diff --git a/src/js/components/inviteForm.js b/src/js/components/inviteForm.js index 768b235..07317ea 100644 --- a/src/js/components/inviteForm.js +++ b/src/js/components/inviteForm.js @@ -5,7 +5,7 @@ var React = require('react'); var classSet = require('classnames'); var api = require('../api'); - +var Loader = require('./loader'); module.exports = React.createClass({ getInitialState() { return { @@ -99,11 +99,11 @@ module.exports = React.createClass({ disabled={this.state.error || this.state.submitted}> ⏎ - - +
+ +
{feedbackMessage} - ) + ); } }); diff --git a/src/js/components/loader.js b/src/js/components/loader.js new file mode 100644 index 0000000..80f58c2 --- /dev/null +++ b/src/js/components/loader.js @@ -0,0 +1,12 @@ +const React = require('react'); + +module.exports = function Loader() { + return ( +
+
+
+
+
+
+ ); +}; diff --git a/src/js/components/membershipForm.js b/src/js/components/membershipForm.js index d40287c..ceb30be 100644 --- a/src/js/components/membershipForm.js +++ b/src/js/components/membershipForm.js @@ -1,50 +1,34 @@ -'use strict'; - -var classSet = require('classnames'); -var React = require('react'); -var request = require('axios'); - -var MembershipInfoForm = require('./membershipInfoForm.js'); -var StripeCheckout = require('./stripeCheckout.js'); +import React from 'react'; +import MembershipInfoForm from './membershipInfoForm'; module.exports = React.createClass({ getInitialState() { return { - infoFormSuccess: false, - paymentSuccess: false, - userInfo: null + paymentSuccess: false }; }, - handlePaymentSuccess() { this.setState({ paymentSuccess: true }); }, - - handleInfoFormSuccess(userInfo) { - this.setState({ - infoFormSuccess: true, - userInfo: userInfo - }); - }, - render() { - if(!this.state.infoFormSuccess) { - return - - } else if (!this.state.paymentSuccess) { + if(!this.state.paymentSuccess) { return ( - - ) - - } else { - return ( -
-

Maksu ja rekisteröityminen onnistui.

-

Tervetuloa Koodiklinikka ry:n jäseneksi!

-
- ) + + ); } + return ( +
+ + + + +

Maksu ja rekisteröityminen onnistui.

+

Tervetuloa Koodiklinikka ry:n jäseneksi!

+
+ ); } }); diff --git a/src/js/components/membershipInfoForm.js b/src/js/components/membershipInfoForm.js index 2363ec3..f9774a4 100644 --- a/src/js/components/membershipInfoForm.js +++ b/src/js/components/membershipInfoForm.js @@ -1,21 +1,23 @@ 'use strict'; -var _ = require('lodash'); -var request = require('axios'); -var React = require('react'); +var _ = require('lodash'); +var request = require('axios'); +var React = require('react'); var classSet = require('classnames'); +var StripeCheckout = require('react-stripe-checkout').default; -var api = require('../api'); -var StripeCheckout = require('./stripeCheckout.js'); +var api = require('../api'); +var Loader = require('./loader'); +var config = require('../../config.js')(); var fieldNameTranslations = { - address: { fi: "Osoite" }, - city: { fi: "Paikkakunta" }, - email: { fi: "Sähköpostiosoite" }, - handle: { fi: "Slack-käyttäjätunnus "}, - name: { fi: "Koko nimi "}, - postcode: { fi: "Postinumero" } -} + address: { fi: 'Osoite' }, + city: { fi: 'Paikkakunta' }, + email: { fi: 'Sähköpostiosoite' }, + handle: { fi: 'Slack-käyttäjätunnus ' }, + name: { fi: 'Koko nimi ' }, + postcode: { fi: 'Postinumero' } +}; function validateEmail(email) { var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; @@ -24,54 +26,51 @@ function validateEmail(email) { const fieldNames = ['name', 'email', 'handle', 'address', 'city', 'postcode']; +function getUserInfo(state) { + return _.pick(state, fieldNames); +} + module.exports = React.createClass({ getInitialState() { return { - address: '', - city: '', - email: '', - errors: [], - handle: '', - name: '', - postcode: '', - sending: false + address: '', + city: '', + email: '', + handle: '', + name: '', + postcode: '', + sending: false, + pristineFields: fieldNames }; }, - onSubmit(e) { - e.preventDefault(); - + onSubmit(token) { this.setState({ sending: true, - errors: [] + error: null }); - if (this.getDataErrors().length) { - this.setState({ - sending: false, - errors: userInfoErrors + request.post(api('membership'), { + userInfo: getUserInfo(this.state), + stripeToken: token.id + }) + .then(() => { + this.setState({ sending: false }); + this.props.onPaymentSuccess(); + }) + .catch((err) => { + this.setState({ error: err, sending: false }); }); - } else { - this.props.onSuccess({ - email: this.state.email, - name: this.state.name, - handle: this.state.handle, - address: this.state.address, - postcode: this.state.postcode, - city: this.state.city, - }); - } - }, - handleError(err) { - this.setState({ error: err, sending: false }); }, onChange(e) { - if (e.target.value === this.state[e.target.name]) { + var name = e.target.name; + if (e.target.value === this.state[name]) { return; } this.setState({ [e.target.name]: e.target.value, + pristineFields: this.state.pristineFields.filter((fieldName) => fieldName !== name), errors: [] }); }, @@ -80,84 +79,117 @@ module.exports = React.createClass({ var foundErrors = []; fieldNames.forEach((fieldName) => { - if(!this.state[fieldName]) - foundErrors.push({ field: fieldName, type: 'missing' }) - }) + if (!this.state[fieldName]) { + foundErrors.push({ field: fieldName, type: 'missing' }); + } + }); - if(this.state.email && !validateEmail(this.state.email)) + if (this.state.email && !validateEmail(this.state.email)) { foundErrors.push({ field: 'email', type: 'invalid' }); + } return foundErrors; }, render() { + const inputErrors = this.getDataErrors(); + var formClasses = classSet({ - 'form': true, + 'form': true, 'membership-form': true, - 'has-error': this.state.errors.length, - 'sending': this.state.sending + 'has-error': inputErrors.length !== 0 || this.state.error, + 'sending': this.state.sending }); - - /* generate error messages */ - var feedbackMessages = []; - var fieldsWithErrors = []; - - this.state.errors.forEach((err, i) => { + function getErrorMessage(err) { var feedbackText; - fieldsWithErrors.push(err.field); - - if(err.type == 'missing') { - feedbackText = `${ fieldNameTranslations[err.field].fi } on pakollinen.` - } else if (err.type == 'invalid') { - feedbackText = `${ fieldNameTranslations[err.field].fi } on virheellinen.` + if (err.type === 'missing') { + feedbackText = `${fieldNameTranslations[err.field].fi} on pakollinen.`; + } else if (err.type === 'invalid') { + feedbackText = `${fieldNameTranslations[err.field].fi} on virheellinen.`; } - feedbackMessages.push((
{ feedbackText }
)) - }); + return
{feedbackText}
; + } + /* generate error messages */ + var visibleErrors = inputErrors + .filter((error) => this.state.pristineFields.indexOf(error.field) === -1); - /* generate input fields */ - var inputFields = []; + var fieldsWithErrors = visibleErrors.map(({ field }) => field); - fieldNames.forEach((fieldName) => { + var inputFields = fieldNames.map((fieldName) => { var inputClasses = classSet({ 'input': true, 'has-error': _.includes(fieldsWithErrors, fieldName), - 'half': fieldName == 'city' || fieldName == 'postcode', - 'left': fieldName == 'city' + 'half': fieldName === 'city' || fieldName === 'postcode', + 'left': fieldName === 'city' }); - inputFields.push(( - - )) - }) + function showsErrorFor(field) { + if (fieldName === 'city') { + return false; + } + return field === fieldName || fieldName === 'postcode' && field === 'city'; + } + + return ( + + + { + visibleErrors + .filter(({ field }) => showsErrorFor(field)) + .map(getErrorMessage) + + } + + ); + }); + if (this.state.sending) { + return ( +
+ +
+ ); + } return (
-
- { feedbackMessages } - { inputFields } - - - +

Liity jäseneksi

+ + {inputFields} + {this.state.error && ( +
+ Jotain meni pieleen! Ota yhteyttä info@koodiklinikka.fi +
+ )} + + +
- ) + ); } }); diff --git a/src/js/components/stripeCheckout.js b/src/js/components/stripeCheckout.js deleted file mode 100644 index 98da3fb..0000000 --- a/src/js/components/stripeCheckout.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -var request = require('axios'); -var React = require('react'); -var classSet = require('classnames'); -import StripeCheckout from 'react-stripe-checkout'; - -var api = require('../api'); -var config = require('../../config.js')(); - -module.exports = React.createClass({ - getInitialState() { - return { - error: null, - sending: false - }; - }, - - onSubmit(token) { - this.setState({ - error: null, - sending: true - }); - - request.post(api('membership'), { - userInfo: this.props.userInfo, - stripeToken: token.id - }) - .then(() => { - this.setState({ - sending: false - }); - this.props.onPaymentSuccess(); - }) - .catch((e) => { - this.setState({ - error: e, - sending: false - }); - }); - }, - - render() { - if (this.state.error) { - return

Virhe maksaessa! Ota yhteyttä info@koodiklinikka.fi

- } else if (this.state.sending) { - return Odota hetki... - } else { - return ( - - ) - } - } -}); diff --git a/src/styles/_header.styl b/src/styles/_header.styl index baddeb7..db74ade 100644 --- a/src/styles/_header.styl +++ b/src/styles/_header.styl @@ -35,13 +35,33 @@ headerHeight = 400px top 20px a color white - font-size 18px + font-size 16px + font-weight bold margin-left 16px text-shadow 0 2px 0 rgba(0,0,0,0.1) - @media screen and (max-width: 1030px) + position relative + @media screen and (max-width: 420px) display block margin-top 5px + &:last-child + margin-left 1.5em + &:before + content '!' + font-size 11px + line-height 15px + width 14px + height 14px + text-align center + background #ec3d3d + display inline-block + margin-right 5px + top -5px + right -15px + border-radius 100% + position absolute + text-shadow none + .header__headline display table-cell padding 0 1em diff --git a/src/styles/_loader.styl b/src/styles/_loader.styl new file mode 100644 index 0000000..71eb8f5 --- /dev/null +++ b/src/styles/_loader.styl @@ -0,0 +1,55 @@ +.sk-folding-cube + margin auto + width 100% + height 100% + position relative + transform rotateZ(45deg) + +.sk-folding-cube .sk-cube + float left + width 50% + height 50% + position relative + transform scale(1.1) + +.sk-folding-cube .sk-cube:before + content '' + position absolute + top 0 + left 0 + width 100% + height 100% + background-color linkColor + animation sk-foldCubeAngle 2.4s infinite linear both + transform-origin 100% 100% + +.sk-folding-cube .sk-cube2 + transform scale(1.1) rotateZ(90deg) + +.sk-folding-cube .sk-cube3 + transform scale(1.1) rotateZ(180deg) + +.sk-folding-cube .sk-cube4 + transform scale(1.1) rotateZ(270deg) + +.sk-folding-cube .sk-cube2:before + animation-delay 0.3s + +.sk-folding-cube .sk-cube3:before + animation-delay 0.6s + +.sk-folding-cube .sk-cube4:before + animation-delay 0.9s + +@keyframes sk-foldCubeAngle + 0%, 10% + transform perspective(140px) rotateX(-180deg) + opacity 0 + 25%, 75% + transform perspective(140px) rotateX(0deg) + opacity 1 + 90%, 100% + transform perspective(140px) rotateY(180deg) + opacity 0 + + diff --git a/src/styles/style.styl b/src/styles/style.styl index d3a67f0..8538de8 100644 --- a/src/styles/style.styl +++ b/src/styles/style.styl @@ -6,6 +6,7 @@ footerHeight = 50px @require '_input' @require '_button' @require '_header' +@require '_loader' body, html margin 0 @@ -112,28 +113,16 @@ section:first-child &:first-child margin-top 0 -.loader - background transparent url('../images/ajax-loader.gif') no-repeat center center - width 28px - height 28px - .form .btn background linkColor border-bottom 2px solid #117280 color rgba(255, 255, 255, 0.9) - .loader - display none &.sending .btn display none - .loader + .invite-form__loader display block - margin-left auto - margin-right auto - width 28px - height 28px - .invite-form position relative .btn @@ -146,15 +135,13 @@ section:first-child color rgba(255, 255, 255, 0.5) &:active border-bottom 0 - .loader + .invite-form__loader + display none + width 20px + height 20px position absolute - right 9px - top 9px - &.sending - .loader - display block - width 28px - height 28px + right 14px + top 14px .membership-form .input @@ -162,6 +149,11 @@ section:first-child .btn margin-top 12px +.membership-form__loader + width 70px + height 70px + margin auto + .stripe-form margin 20px 0px .name @@ -218,7 +210,7 @@ footer justify-content space-between flex-wrap wrap text-align center - @media screen and (max-width: 760px) + @media screen and (max-width: 940px) display block i margin 0 0.30em @@ -230,7 +222,7 @@ footer display flex flex-direction column justify-content center - @media screen and (max-width: 760px) + @media screen and (max-width: 940px) margin-top 1em .sponsors @@ -251,16 +243,17 @@ footer height 40px margin-right 1em vertical-align middle - @media screen and (max-width: 760px) + @media screen and (max-width: 940px) margin-top 1em - .sponsor__futurice, .sponsor__metosin, .sponsor__leonidas height 30px .sponsor__nordea height 25px - margin-top: -3px + margin-top -3px + @media screen and (max-width: 940px) + margin-top 1em .feed width feedWidth @@ -353,11 +346,12 @@ footer .bread-img background url('../images/hp3_bw.jpg') - background-size 120% - background-position-y 80% - border-radius 0px + background-size cover + border-radius 160px opacity 0.85 + width 320px height 320px + margin auto @media screen and (max-width: 700px) .bread-img diff --git a/src/yhdistys.jade b/src/yhdistys.jade index bc097a9..f7e441a 100644 --- a/src/yhdistys.jade +++ b/src/yhdistys.jade @@ -32,7 +32,6 @@ block content .row .bread .column.column1-2 - h3 Liity jäseneksi #membership-form.form .column.column1-2