Merge branch 'master' of github.com:Keksike/koodiklinikka.fi

This commit is contained in:
Riku Rouvila
2017-07-06 20:43:47 +01:00
13 changed files with 558 additions and 34 deletions

View File

@@ -8,6 +8,8 @@
"scripts": {
"start": "rm -rf public && gulp",
"build": "rm -rf public && gulp build",
"dev": "SERVER=http://localhost:9000/ ENV=development npm start",
"prod": "ENV=production npm start",
"lint": "eslint src",
"test": "mocha src/**/__tests__/*.js --compilers js:babel-core/register --require test/test-helper"
},

Binary file not shown.

After

Width:  |  Height:  |  Size: 286 KiB

16
src/config.js Normal file
View File

@@ -0,0 +1,16 @@
var development = {
stripe: {
publicKey: "pk_test_OmNve9H1OuORlmD4rblpjgzh"
}
}
var production = {
stripe: {
publicKey: "pk_live_xrnwdLNXbt20LMxpIDffJnnC"
}
}
module.exports = function() {
return process.env.ENV == 'development' ? development : production;
}

View File

@@ -33,6 +33,7 @@ html
meta(name='msapplication-TileColor', content='#10558c')
meta(name='msapplication-TileImage', content='icons/mstile-144x144.png')
meta(property='og:image', content='images/logo.png')
script(src='https://js.stripe.com/v3/')
script(src='//use.typekit.net/scb5xny.js')
script.
try{Typekit.load();}catch(e){};
@@ -44,15 +45,18 @@ html
video(autoplay, loop, poster='images/poster.jpg', class='header__video-bg')
source(src='videos/jumbo.mp4', type='video/mp4')
.header__container
.header__nav
a(href='/') etusivu
a(href='/yhdistys.html') yhdistys
.header__headline
.header__logo
h1.header__title <a target="_blank" href="https://slack.com/">Slack</a>-yhteisö kaikille ohjelmoinnista ja ohjelmistoalasta kiinnostuneille harrastajille ja&nbsp;ammattilaisille.
.content
.content.with-feed
section
.row
h3 Tule mukaan
#invite-form
#invite-form.form
section
.row
@@ -144,7 +148,6 @@ html
i.fa.fa-facebook
div#email
#fader
// inject:js
// endinject

View File

@@ -48,6 +48,7 @@ module.exports = React.createClass({
},
render() {
var formClasses = classSet({
'form': true,
'invite-form': true,
'has-success': this.state.submitted,
'has-error': this.state.error,
@@ -76,7 +77,7 @@ module.exports = React.createClass({
}
feedbackMessage = (
<div className='invite-form--message'>
<div className='form--message'>
{messageText}
</div>
);

View File

@@ -7,7 +7,7 @@ var _ = require('lodash');
var api = require('../api');
module.exports = React.createClass({
getInitialState() {
getInitialState() {
return {
members: []
};

View File

@@ -0,0 +1,50 @@
'use strict';
var classSet = require('classnames');
var React = require('react');
var request = require('axios');
var MembershipInfoForm = require('./membershipInfoForm.js');
var StripeCheckout = require('./stripeCheckout.js');
module.exports = React.createClass({
getInitialState() {
return {
infoFormSuccess: false,
paymentSuccess: false,
userInfo: null
};
},
handlePaymentSuccess() {
this.setState({ paymentSuccess: true });
},
handleInfoFormSuccess(userInfo) {
this.setState({
infoFormSuccess: true,
userInfo: userInfo
});
},
render() {
if(!this.state.infoFormSuccess) {
return <MembershipInfoForm onSuccess={ this.handleInfoFormSuccess }></MembershipInfoForm>
} else if (!this.state.paymentSuccess) {
return (
<StripeCheckout
userInfo = { this.state.userInfo }
onPaymentSuccess = { this.handlePaymentSuccess }>
</StripeCheckout>)
} else {
return (
<div>
<p> Maksu ja rekisteröityminen onnistui.</p>
<p> Tervetuloa Koodiklinikka ry:n jäseneksi!</p>
</div>
)
}
}
});

View File

@@ -0,0 +1,166 @@
'use strict';
var _ = require('lodash');
var request = require('axios');
var React = require('react');
var classSet = require('classnames');
var api = require('../api');
var StripeCheckout = require('./stripeCheckout.js');
var fieldNameTranslations = {
email: { fi: "Sähköpostiosoite" },
name: { fi: "Koko nimi "},
handle: { fi: "Slack-käyttäjätunnus "},
residence: { fi: "Paikkakunta" }
}
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,}))$/;
return re.test(email);
}
module.exports = React.createClass({
getInitialState() {
return {
email: '',
name: '',
handle: '',
residence: '',
sending: false,
errors: []
};
},
onSubmit(e) {
e.preventDefault();
this.setState({
sending: true,
errors: []
});
var userInfo = {
email: this.state.email,
name: this.state.name,
handle: this.state.handle,
residence: this.state.residence,
}
var userInfoErrors = this.getDataErrors();
if(userInfoErrors.length){
this.setState({
sending: false,
errors: userInfoErrors
});
} else {
this.props.onSuccess(userInfo);
}
},
handleError(err) {
this.setState({ error: err, sending: false });
},
onChange(e) {
if(e.target.value === this.state[e.target.name]) {
return;
}
this.setState({
[e.target.name]: e.target.value,
errors: []
});
},
getDataErrors() {
var foundErrors = [];
if (!this.state.name)
foundErrors.push({ field: 'name', type: 'missing' });
if (!this.state.email)
foundErrors.push({ field: 'email', type: 'missing' });
else if(!validateEmail(this.state.email))
foundErrors.push({ field: 'email', type: 'invalid' });
if (!this.state.handle)
foundErrors.push({ field: 'handle', type: 'missing' });
if (!this.state.residence)
foundErrors.push({ field: 'residence', type: 'missing' });
return foundErrors;
},
render() {
var formClasses = classSet({
'form': true,
'membership-form': true,
'has-error': this.state.errors.length,
'sending': this.state.sending
});
/* generate error messages */
var feedbackMessages = [];
var fieldsWithErrors = [];
this.state.errors.forEach((err, i) => {
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.`
}
feedbackMessages.push((<div key={i} className='form--message'>{ feedbackText }</div>))
});
/* generate input fields */
var fieldNames = ['name', 'email', 'handle', 'residence'];
var inputFields = [];
fieldNames.forEach((fieldName) => {
var inputClasses = classSet({
'input': true,
'has-error': _.includes(fieldsWithErrors, fieldName)
});
inputFields.push((
<input
key = { fieldName }
className = { inputClasses }
type = { fieldName == 'email' ? 'email' : 'text' }
name = { fieldName }
placeholder = { fieldNameTranslations[fieldName].fi }
value = { this.state[fieldName] }
onChange = { this.onChange } />
))
})
return (
<div>
<form className={ formClasses } onSubmit={ this.onSubmit }>
{ feedbackMessages }
{ inputFields }
<button
className = 'btn btn__submit'
type = 'submit'
title = 'Lähetä'
disabled = { this.state.errors.length || this.state.submitted }>
Siirry maksamaan
</button>
<span
className='loader'>
</span>
</form>
</div>
)
}
});

View File

@@ -0,0 +1,66 @@
'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'), {
email: this.props.payerEmail,
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 <p>Virhe maksaessa! Ota yhteyttä info@koodiklinikka.fi</p>
} else if (this.state.sending) {
return <img src="../images/ajax-loader.gif" alt="Odota hetki..." height="42" width="42"></img>
} else {
return (<StripeCheckout
amount = { 1000 }
currency = 'EUR'
description = 'Jäsenmaksu'
email = { this.props.userInfo.email }
image = 'https://avatars3.githubusercontent.com/u/10520119?v=3&s=200'
locale = "en"
name = 'Koodiklinikka ry'
stripeKey = { config.stripe.publicKey }
token = { this.onSubmit }
>
<button className="btn btn-primary">
Maksa kortilla
</button>
</StripeCheckout>)
}
}
});

View File

@@ -3,31 +3,49 @@ require('./ga');
let ReactDOM = require('react-dom');
var React = require('react');
var inviteForm = React.createFactory(require('./components/inviteForm'));
var fader = React.createFactory(require('./components/fader'));
var members = React.createFactory(require('./components/members'));
var feed = React.createFactory(require('./components/feed'));
var email = React.createFactory(require('./components/email'));
var membershipForm = React.createFactory(require('./components/membershipForm'));
ReactDOM.render(
inviteForm(),
document.getElementById('invite-form'));
const pathName = window.location.pathname;
ReactDOM.render(
fader(),
document.getElementById('fader'));
if(pathName == '/') {
ReactDOM.render(
inviteForm(),
document.getElementById('invite-form'));
ReactDOM.render(
fader(),
document.getElementById('fader'));
ReactDOM.render(
members(),
document.getElementById('members'));
ReactDOM.render(
members(),
document.getElementById('members'));
ReactDOM.render(
feed(),
document.getElementById('feed'));
ReactDOM.render(
feed(),
document.getElementById('feed'));
ReactDOM.render(
email(),
document.getElementById('email'));
} else if (pathName == '/yhdistys.html') {
ReactDOM.render(
membershipForm(),
document.getElementById('membership-form'));
ReactDOM.render(
email(),
document.getElementById('email'));
ReactDOM.render(
fader(),
document.getElementById('fader'));
ReactDOM.render(
email(),
document.getElementById('email'));
ReactDOM.render(
members(),
document.getElementById('members'));
}

View File

@@ -23,6 +23,23 @@
vertical-align middle
width 100%
.header__nav
position absolute
right 60px
top 32px
@media screen and (max-width: 1030px)
right 40px
top 20px
a
color white
font-size 18px
margin-left 16px
text-shadow 0 2px 0 rgba(0,0,0,0.1)
@media screen and (max-width: 1030px)
display block
margin-top 5px
.header__headline
display table-cell
padding 0 1em
@@ -85,3 +102,21 @@
@media (min-aspect-ratio: 1/2)
width 100%
height auto
.header__members
z-index -1
position absolute
top 0
left 0
height 100%
.member
margin 0
border-radius 0
width (100/18)%
@media screen and (max-width: 1200px)
width (100/15)%
@media screen and (max-width: 810px)
width 10%
@media screen and (min-width: 2000px)
width 5%

View File

@@ -25,6 +25,9 @@ h1, h2, h3
h2
margin-bottom 1em
h3
font-size 1.25em
p
margin-top 1em
line-height 1.5em
@@ -51,13 +54,16 @@ section
.content
z-index 2
position relative
padding-right feedWidth
min-height 50vh
box-sizing border-box
@media screen and (max-width: 700px)
h3
margin-top 0
&.with-feed
@media screen and (min-width: 1100px)
padding-right feedWidth
section:first-child
box-shadow -1px -1px 1px rgba(0, 0, 0, 0.05)
border-bottom 1px solid #EEEEEE
@@ -104,18 +110,38 @@ section:first-child
padding 0
&: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
display block
margin-left auto
margin-right auto
width 28px
height 28px
.invite-form
position relative
.btn
width 40px
height 32px
background linkColor
padding 0
position absolute
right 8px
top 7px
border-bottom 2px solid #117280
color rgba(255, 255, 255, 0.5)
&:active
border-bottom 0
@@ -123,17 +149,26 @@ section:first-child
position absolute
right 9px
top 9px
width 0px
height 0px
background transparent url('../images/ajax-loader.gif') no-repeat center center
display block
&.sending
.btn
display none
.loader
display block
width 28px
height 28px
.membership-form
.input
margin 8px 0px
.btn
margin-top 12px
.stripe-form
margin 20px 0px
.name
margin-top 20px
text-align left
display block
color rgba(0, 0, 0, 0.4)
@keyframes drop
0%
transform rotateX(90deg)
@@ -146,7 +181,7 @@ section:first-child
100%
transform rotateX(0deg)
.invite-form--message
.form--message
background linkColor
color #fff
line-height 40px
@@ -156,8 +191,8 @@ section:first-child
transform-origin 100% 0
animation drop 0.6s linear
.invite-form.has-error
.invite-form--message
.form.has-error
.form--message
background rgb(226, 33, 112)
.members
@@ -314,3 +349,15 @@ footer
display block
.column
display block
.bread-img
background url('../images/hp3_bw.jpg')
background-size 120%
background-position-y 80%
border-radius 0px
opacity 0.85
height 320px
@media screen and (max-width: 700px)
.bread-img
display none

120
src/yhdistys.jade Normal file
View File

@@ -0,0 +1,120 @@
doctype html
html
head
title Yhdistys
// inject:css
// endinject
meta(name='description', content='Koodiklinikka on suomalainen Slack-yhteisö ohjelmistoalan harrastajille ja ammattilaisille.')
meta(name='keywords', content='ohjelmointi,frontend,open source,devaus,suomi,javascript,clojure,go,java,node.js,io.js,angular.js,web')
meta(charset='utf-8')
meta(name='viewport', content='width=device-width, initial-scale=1')
meta(name='apple-mobile-web-app-capable', content='yes')
script.
if(location.hostname === 'koodiklinikka.fi' && location.protocol !== 'https:') {
location.protocol = 'https';
}
link(rel='apple-touch-icon', sizes='57x57', href='icons/apple-touch-icon-57x57.png')
link(rel='apple-touch-icon', sizes='114x114', href='icons/apple-touch-icon-114x114.png')
link(rel='apple-touch-icon', sizes='72x72', href='icons/apple-touch-icon-72x72.png')
link(rel='apple-touch-icon', sizes='144x144', href='icons/apple-touch-icon-144x144.png')
link(rel='apple-touch-icon', sizes='60x60', href='icons/apple-touch-icon-60x60.png')
link(rel='apple-touch-icon', sizes='120x120', href='icons/apple-touch-icon-120x120.png')
link(rel='apple-touch-icon', sizes='76x76', href='icons/apple-touch-icon-76x76.png')
link(rel='apple-touch-icon', sizes='152x152', href='icons/apple-touch-icon-152x152.png')
link(rel='apple-touch-icon', sizes='180x180', href='icons/apple-touch-icon-180x180.png')
link(rel='icon', type='image/png', href='icons/favicon-192x192.png', sizes='192x192')
link(rel='icon', type='image/png', href='icons/favicon-160x160.png', sizes='160x160')
link(rel='icon', type='image/png', href='icons/favicon-96x96.png', sizes='96x96')
link(rel='icon', type='image/png', href='icons/favicon-16x16.png', sizes='16x16')
link(rel='icon', type='image/png', href='icons/favicon-32x32.png', sizes='32x32')
link(rel='shortcut icon', href='icons/favicon.ico')
link(rel='icon', href='icons/favicon.ico')
meta(name='msapplication-TileColor', content='#10558c')
meta(name='msapplication-TileImage', content='icons/mstile-144x144.png')
meta(property='og:image', content='images/logo.png')
script(src='https://js.stripe.com/v3/')
script(src='//use.typekit.net/scb5xny.js')
script.
try{Typekit.load();}catch(e){};
body
.site
.container
.header
.header__container
.header__nav
a(href='/') etusivu
a(href='/yhdistys.html') yhdistys
.header__headline
.header__logo
h1.header__title Koodiklinikka ry
.header__members
#members
.content
section
.row
.bread
.column.column1-2
h3 Rekisteröity yhdistys
p.
Koodiklinikka on nyt rekisteröity yhdistys!
p.
Lähes kolmivuotisen taipaleensa aikana Koodiklinikka on kasvanut lähes tuhannen rekisteröityneen jäsenen yhteisöiksi ja näin saavuttanut aseman Suomen suurimpana ohjelmointiaiheisena yhteisönä! Liittymällä Koodiklinikka ry:n jäseneksi tuet toimintaamme, ja tulevaisuudessa yhdityksen jäsenyys oikeuttaa etuihin Koodiklinikan tapahtumissa.
p.
Yhdistyksen jäsenyys <b>ei ole</b>, eikä tule olemaan, <b>pakollinen</b> Koodiklinikan käyttäjille tai meetuppeihin osallistujille.
.column.column1-2
.bread-img
.row
.bread
.column.column1-2
h3 Liity jäseneksi
#membership-form.form
.column.column1-2
p.
Kuka tahansa Slack:iin rekisteröitynyt käyttäjä voi liittyä yhdistyksen jäseneksi maksamalla kertaluontoisen <b>10€</b> jäsenmaksun.
p.
Nimellisellä jäsenmaksulla tulemme tekemään tapahtumistamme vieläkin mielenkiintoisempia ja kattamaan kustannuksia jäsenmäärämme kasvaessa.
p.
Jos tarvitset Slack-kutsun, siirry <a target="_blank" href="/">etusivulle</a>.
p.
Jäsenrekisterin rekisteriseloste luettavissa <a target="_blank" href="https://docs.google.com/document/d/1w-NZkf1EFYMC8a4LsoZouhpMVKORmPi8UFWRhurr0oM/edit?usp=sharing">Google Drivessä</a>.
footer
.sponsors
.sponsors__label Yhteistyössä
a(href='http://futurice.com/', target='_blank')
img.sponsor.sponsor__futurice(src='images/futurice.svg')
a(href='http://www.metosin.fi/', target='_blank')
img.sponsor.sponsor__metosin(src='images/metosin.svg')
a(href='https://www.solita.fi/', target='_blank')
img.sponsor(src='images/solita.svg')
a(href='http://leonidasoy.fi/', target='_blank')
img.sponsor.sponsor__leonidas(src='images/leonidas.png')
a(href='https://www.nordea.com/', target='_blank')
img.sponsor.sponsor__nordea(src='images/nordea.png')
.contacts
div
a(href='https://koodiklinikka.slack.com')
i.fa.fa-slack
a(href='https://github.com/koodiklinikka/koodiklinikka.fi')
i.fa.fa-github
a(href='https://twitter.com/koodiklinikka')
i.fa.fa-twitter
a(href='https://www.linkedin.com/groups/12025476')
i.fa.fa-linkedin
a(href='https://www.facebook.com/koodiklinikka')
i.fa.fa-facebook
div#email
#fader
// inject:js
// endinject