This commit is contained in:
Víctor Losada Hernández 2024-12-07 12:25:26 +01:00
parent 0141433131
commit 6ff861afb7
25 changed files with 1548 additions and 1248 deletions

View File

@ -1,129 +1,135 @@
const React = require('react');
const _ = require('lodash');
const _ = require('lodash');
const createClass = require('create-react-class');
const BadgeTemplate = require('./badgeTemplate.js');
const replaceAll = (text, target, str)=>text.replace(new RegExp(target, 'g'), str);
const replaceAll = (text, target, str) => text.replace(new RegExp(target, 'g'), str);
const BadgeRender = createClass({
getDefaultProps: function() {
getDefaultProps: function () {
return {
title: '',
text: '',
rawSVG : '',
color : '#333'
rawSVG: '',
color: '#333',
};
},
componentWillReceiveProps: function(nextProps) {
componentWillReceiveProps: function (nextProps) {
this.drawBadge(nextProps);
},
shouldComponentUpdate: function(nextProps, nextState) {
return false
shouldComponentUpdate: function (nextProps, nextState) {
return false;
},
componentDidMount: function() {
componentDidMount: function () {
this.ctx = this.refs.canvas.getContext('2d');
this.drawBadge(this.props);
},
handleDownload : function(){
handleDownload: function () {
const target = document.createElement('a');
const name = (this.props.title ? _.snakeCase(this.props.title) : 'badge')
const name = this.props.title ? _.snakeCase(this.props.title) : 'badge';
target.download = `${name}.png`;
target.href = this.refs.canvas.toDataURL("image/png").replace(/^data:image\/[^;]/, 'data:application/octet-stream');
target.href = this.refs.canvas
.toDataURL('image/png')
.replace(/^data:image\/[^;]/, 'data:application/octet-stream');
target.click();
},
clearCanvas : function(){
clearCanvas: function () {
this.ctx.clearRect(0, 0, this.refs.canvas.width, this.refs.canvas.height);
},
readyFrame : function(color){
return new Promise((resolve, reject)=>{
readyFrame: function (color) {
return new Promise((resolve, reject) => {
const frame = new Image();
frame.src = `data:image/svg+xml;base64,${btoa(BadgeTemplate(color))}`;
frame.onload = ()=>resolve(frame);
frame.onload = () => resolve(frame);
});
},
readyIconSVG : function(props){
return new Promise((resolve, reject)=>{
if(!props.rawSVG) return resolve();
readyIconSVG: function (props) {
return new Promise((resolve, reject) => {
if (!props.rawSVG) return resolve();
const icon = new Image();
let svg = props.rawSVG || '';
//console.log(svg);
if(svg.indexOf('style=') === -1){
svg = _.reduce(['path', 'rect', 'polygon', 'circle', 'polyline', 'ellipse'], (acc, type)=>{
return replaceAll(acc, `<${type}`, `<${type} style="fill:${props.color}"`);
}, svg);
if (svg.indexOf('style=') === -1) {
svg = _.reduce(
['path', 'rect', 'polygon', 'circle', 'polyline', 'ellipse'],
(acc, type) => {
return replaceAll(acc, `<${type}`, `<${type} style="fill:${props.color}"`);
},
svg
);
}
svg = svg.replace(/<text.*<\/text>/, '');
icon.onload = ()=>resolve(icon);
icon.onload = () => resolve(icon);
icon.src = `data:image/svg+xml;base64,${btoa(svg)}`;
});
},
drawSVG : function(props){
return Promise.all([this.readyFrame(props.color), this.readyIconSVG(props)])
.then(([frame, icon])=>{
this.clearCanvas();
if(frame) this.ctx.drawImage(frame, 0, 0);
if(icon){
const scale = 1.1
const newWidth = icon.width * scale;
const newHeight = icon.height * scale;
this.ctx.drawImage(icon,
150 - newWidth / 2, 120 - newWidth / 2,
newWidth, newHeight);
}
})
drawSVG: function (props) {
return Promise.all([this.readyFrame(props.color), this.readyIconSVG(props)]).then(([frame, icon]) => {
this.clearCanvas();
if (frame) this.ctx.drawImage(frame, 0, 0);
if (icon) {
const scale = 1.1;
const newWidth = icon.width * scale;
const newHeight = icon.height * scale;
this.ctx.drawImage(icon, 150 - newWidth / 2, 120 - newWidth / 2, newWidth, newHeight);
}
});
},
drawTitle : function(title){
this.ctx.textAlign='center';
this.ctx.textBaseline="middle";
this.ctx.fillStyle = "#ffffff";
drawTitle: function (title) {
this.ctx.textAlign = 'center';
this.ctx.textBaseline = 'middle';
this.ctx.fillStyle = '#ffffff';
const trySize = (font)=>{
this.ctx.font=`${font}px Calluna`;
const trySize = (font) => {
this.ctx.font = `${font}px Calluna`;
const length = this.ctx.measureText(title).width;
if(length >= 230) return trySize(font-1);
if (length >= 230) return trySize(font - 1);
return font;
};
const finalSize = trySize(35);
this.ctx.fillText(title,150,220);
this.ctx.fillText(title, 150, 220);
},
drawText : function(text){
this.ctx.textAlign='left';
this.ctx.font='bold 18px Calluna';
this.ctx.fillStyle = "#000";
drawText: function (text) {
this.ctx.textAlign = 'left';
this.ctx.font = 'bold 18px Calluna';
this.ctx.fillStyle = '#000';
const lines = _.reduce(text.split(' '), (acc, word)=>{
const currLine = _.last(acc);
const length = this.ctx.measureText(`${currLine.join(' ')} ${word}`).width;
if(length >= this.refs.canvas.width - 30){
acc.push([word]);
}else{
currLine.push(word);
}
return acc;
}, [[]]);
_.each(lines, (line, index)=>{
this.ctx.fillText(line.join(' '),15,315 + index*20);
const lines = _.reduce(
text.split(' '),
(acc, word) => {
const currLine = _.last(acc);
const length = this.ctx.measureText(`${currLine.join(' ')} ${word}`).width;
if (length >= this.refs.canvas.width - 30) {
acc.push([word]);
} else {
currLine.push(word);
}
return acc;
},
[[]]
);
_.each(lines, (line, index) => {
this.ctx.fillText(line.join(' '), 15, 315 + index * 20);
});
},
drawAttribution : function(svg){
drawAttribution: function (svg) {
const canvas = this.refs.canvas;
this.ctx.textAlign='left';
this.ctx.font='9px Open Sans';
this.ctx.fillStyle = "#bbb";
this.ctx.textAlign = 'left';
this.ctx.font = '9px Open Sans';
this.ctx.fillStyle = '#bbb';
let maxDepth = 95;
const check = svg.match(/<text.*<\/text>/);
if(check && check.length){
if (check && check.length) {
const a = check[0].indexOf('Created by ') + 11;
const b = check[0].indexOf('</text>');
const author = check[0].substr(a, b-a);
const author = check[0].substr(a, b - a);
const width = this.ctx.measureText(`Icon by ${author}`).width;
maxDepth = _.max([maxDepth, width + 3]);
@ -132,28 +138,29 @@ const BadgeRender = createClass({
this.ctx.fillText(`Made with NaturalCrit`, canvas.width - maxDepth, canvas.height - 7);
},
drawBadge : function(props){
let height = (props.text ? 400 : 320);
if(this.refs.canvas.height != height) this.refs.canvas.height = height;
this.drawSVG(props)
.then(()=>{
this.drawTitle(props.title);
this.drawText(props.text);
this.drawAttribution(props.rawSVG);
})
drawBadge: function (props) {
let height = props.text ? 400 : 320;
if (this.refs.canvas.height != height) this.refs.canvas.height = height;
this.drawSVG(props).then(() => {
this.drawTitle(props.title);
this.drawText(props.text);
this.drawAttribution(props.rawSVG);
});
},
render: function(){
return <div className='badgeRender'>
<canvas ref='canvas' width={300} height={320}/>
<div>
<button onClick={this.handleDownload}>
<i className='fa fa-download' />
Download
</button>
render: function () {
return (
<div className="badgeRender">
<canvas ref="canvas" width={300} height={320} />
<div>
<button onClick={this.handleDownload}>
<i className="fa fa-download" />
Download
</button>
</div>
</div>
</div>
}
);
},
});
module.exports = BadgeRender;

View File

@ -1,33 +1,37 @@
@font-face {
font-family : Calluna;
font-style : normal;
font-weight : normal;
src : url('/assets/badges/badgeRender/calluna.woff2') format('woff2'),
url('/assets/badges/badgeRender/calluna.woff') format('woff');
font-family: Calluna;
font-style: normal;
font-weight: normal;
src: url('/assets/badges/badgeRender/calluna.woff2') format('woff2'),
url('/assets/badges/badgeRender/calluna.woff') format('woff');
}
.badgeRender{
text-align : center;
canvas{
border : 1px solid #999;
.badgeRender {
text-align: center;
canvas {
border: 1px solid #999;
}
button{
button {
.animate(background-color);
margin : 10px auto;
padding : 5px 20px;
cursor : pointer;
background-color : @green;
font-size : 0.8em;
color : white;
border : none;
highlight : none;
i{
vertical-align : middle;
margin-right : 8px;
font-size : 2em;
margin: 10px auto;
padding: 5px 20px;
cursor: pointer;
background-color: @green;
font-size: 0.8em;
color: white;
border: none;
highlight: none;
i {
vertical-align: middle;
margin-right: 8px;
font-size: 2em;
}
&:hover{
background-color : darken(@green, 10%);
&:hover {
background-color: darken(@green, 10%);
}
}
}

View File

@ -1,14 +1,26 @@
module.exports = (color = '#666')=>{
module.exports = (color = '#666') => {
function shadeColor2(color, percent) {
var f=parseInt(color.slice(1),16),t=percent<0?0:255,p=percent<0?percent*-1:percent,R=f>>16,G=f>>8&0x00FF,B=f&0x0000FF;
return "#"+(0x1000000+(Math.round((t-R)*p)+R)*0x10000+(Math.round((t-G)*p)+G)*0x100+(Math.round((t-B)*p)+B)).toString(16).slice(1);
var f = parseInt(color.slice(1), 16),
t = percent < 0 ? 0 : 255,
p = percent < 0 ? percent * -1 : percent,
R = f >> 16,
G = (f >> 8) & 0x00ff,
B = f & 0x0000ff;
return (
'#' +
(
0x1000000 +
(Math.round((t - R) * p) + R) * 0x10000 +
(Math.round((t - G) * p) + G) * 0x100 +
(Math.round((t - B) * p) + B)
)
.toString(16)
.slice(1)
);
}
const light = shadeColor2(color, 0.4);
const dark = shadeColor2(color, -0.3);
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 344.83 341.02"><defs><style>.cls-1{fill:${color};}.cls-2{fill:${light};}.cls-3{fill:${dark};}</style></defs><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><polygon class="cls-1" points="344.83 291.26 297.49 291.26 297.49 239.3 344.83 239.3 324.5 264.36 344.83 291.26"/><polygon class="cls-1" points="0 239.3 47.34 239.3 47.34 291.26 0 291.26 20.34 266.2 0 239.3"/><path class="cls-1" d="M172.42,341,93.87,283.33A139.56,139.56,0,0,1,37.13,171.25V32.78L60.05,29.9A429.26,429.26,0,0,0,163.15,3.64L172.43,0l8,3.16A429.34,429.34,0,0,0,283.73,29.73l24,3.06V171.25A139.56,139.56,0,0,1,251,283.33l-6.32-8.61,6.32,8.61ZM58.49,51.62V171.25a118.13,118.13,0,0,0,48,94.87l65.9,48.41,65.9-48.41a118.13,118.13,0,0,0,48-94.87V51.6L281,50.92A450.67,450.67,0,0,1,172.57,23L172.4,23l-1.46.57A450.65,450.65,0,0,1,62.71,51.09Z"/><path class="cls-1" d="M172.42,341,93.87,283.33A139.56,139.56,0,0,1,37.13,171.25V32.78L60.05,29.9A429.26,429.26,0,0,0,163.15,3.64L172.43,0l8,3.16A429.34,429.34,0,0,0,283.73,29.73l24,3.06V171.25A139.56,139.56,0,0,1,251,283.33l-6.32-8.61,6.32,8.61ZM58.49,51.62V171.25a118.13,118.13,0,0,0,48,94.87l65.9,48.41,65.9-48.41a118.13,118.13,0,0,0,48-94.87V51.6L281,50.92A450.67,450.67,0,0,1,172.57,23L172.4,23l-1.46.57A450.65,450.65,0,0,1,62.71,51.09Z"/><path class="cls-1" d="M172.42,302.22l-60-44.09a108.18,108.18,0,0,1-44-86.88V60.37L73,59.7A461.13,461.13,0,0,0,170.5,34.33l1.9-.72,1.9.72a461.12,461.12,0,0,0,97.56,25.35l4.57.67V171.25a108.18,108.18,0,0,1-44,86.88ZM79.08,69.58V171.25a97.46,97.46,0,0,0,39.62,78.27L172.42,289l53.71-39.45a97.46,97.46,0,0,0,39.63-78.27V69.55A471.85,471.85,0,0,1,172.4,45,472,472,0,0,1,79.08,69.58Z"/><rect class="cls-2" x="27.78" y="223.53" width="289.27" height="51.96"/><polygon class="cls-3" points="317.05 275.49 297.49 275.49 297.49 291.26 317.05 275.49"/><polygon class="cls-3" points="27.78 275.49 47.34 275.49 47.34 291.26 27.78 275.49"/></g></g></svg>`;
}
};

File diff suppressed because one or more lines are too long

View File

@ -1,52 +1,58 @@
@import "shared/naturalcrit/styles/core.less";
.badges{
width : 800px;
margin : auto;
h1{
margin-bottom : 10px;
padding-top : 30px;
font-family : 'Calluna';
font-size : 2.5em;
font-weight : 800;
text-align : center;
.badges {
width: 800px;
margin: auto;
h1 {
margin-bottom: 10px;
padding-top: 30px;
font-family: 'Calluna';
font-size: 2.5em;
font-weight: 800;
text-align: center;
}
&>p{
margin-bottom : 20px;
font-size : 0.8em;
text-align : center;
&>p {
margin-bottom: 20px;
font-size: 0.8em;
text-align: center;
//font-family: 'Calluna';
}
.content{
display : flex;
padding-top : 50px;
justify-content : space-around;
.badgeRender, .controls{
padding : 0px 50px;
.content {
display: flex;
padding-top: 50px;
justify-content: space-around;
.badgeRender,
.controls {
padding: 0px 50px;
}
}
.credit{
.credit {
display: block;
margin-top : 50px;
opacity : 0.8;
font-family : 'Calluna';
font-size : 0.8em;
text-align : center;
margin-top: 50px;
opacity: 0.8;
font-family: 'Calluna';
font-size: 0.8em;
text-align: center;
text-decoration: none;
color : inherit;
color: inherit;
cursor: pointer;
line-height: 1.2em;
&:hover{
&:hover {
text-decoration: underline;
}
}
}
.logo {
position:fixed;
top:10px;
left:10px;
position: fixed;
top: 10px;
left: 10px;
font-size: 2em;
color: black;
text-decoration: none;
@ -65,5 +71,4 @@
font-family: 'CodeBold';
}
}
}
}

View File

@ -1,86 +1,95 @@
const React = require('react');
const _ = require('lodash');
const _ = require('lodash');
const createClass = require('create-react-class');
const cx = require('classnames');
const Color = require('react-color');
const Controls = createClass({
getDefaultProps: function() {
getDefaultProps: function () {
return {
data : {
data: {
title: '',
text: '',
color : '#2b4486',
rawSVG : ''
color: '#2b4486',
rawSVG: '',
},
onChange : ()=>{}
onChange: () => {},
};
},
getInitialState: function() {
getInitialState: function () {
return {
hover: false
hover: false,
};
},
handleDrop: function(e){
handleDrop: function (e) {
e.preventDefault();
const files = e.target.files || e.dataTransfer.files;
const reader = new FileReader();
reader.onload = (e)=>{
reader.onload = (e) => {
this.handleChange('rawSVG', e.target.result);
}
};
reader.readAsText(files[0]);
this.setState({hover : false});
this.setState({ hover: false });
},
handleHover : function(e, val){
handleHover: function (e, val) {
e.preventDefault();
this.setState({hover : val});
this.setState({ hover: val });
},
handleChange : function(path, val){
handleChange: function (path, val) {
this.props.onChange(_.set(this.props.data, path, val));
},
render: function(){
return <div className='controls'>
<div className='field'>
<label>Title</label>
<input type='text'
className='value'
value={this.props.data.title}
onChange={(e)=>this.handleChange('title', e.target.value)} />
</div>
<div className='field'>
<label>Text</label>
<textarea type='text'
className='value'
rows={3}
value={this.props.data.text}
onChange={(e)=>this.handleChange('text', e.target.value)} />
</div>
<div className='field'>
<label>Color</label>
<Color.SliderPicker
className='value'
disableAlpha={true}
color={this.props.data.color}
onChange={(colorObj)=>this.handleChange('color', colorObj.hex)}
/>
</div>
<div className='field svg'>
<label>SVG</label>
<div className='value'>
<div className={cx('dropZone', {hover : this.state.hover})}
onDragOver={(e)=>this.handleHover(e, true)}
onDragLeave={(e)=>this.handleHover(e, false)}
onDrop={this.handleDrop}>
<i className='fa fa-arrow-down' />
<p>Drop SVG here</p>
render: function () {
return (
<div className="controls">
<div className="field">
<label>Title</label>
<input
type="text"
className="value"
value={this.props.data.title}
onChange={(e) => this.handleChange('title', e.target.value)}
/>
</div>
<div className="field">
<label>Text</label>
<textarea
type="text"
className="value"
rows={3}
value={this.props.data.text}
onChange={(e) => this.handleChange('text', e.target.value)}
/>
</div>
<div className="field">
<label>Color</label>
<Color.SliderPicker
className="value"
disableAlpha={true}
color={this.props.data.color}
onChange={(colorObj) => this.handleChange('color', colorObj.hex)}
/>
</div>
<div className="field svg">
<label>SVG</label>
<div className="value">
<div
className={cx('dropZone', { hover: this.state.hover })}
onDragOver={(e) => this.handleHover(e, true)}
onDragLeave={(e) => this.handleHover(e, false)}
onDrop={this.handleDrop}>
<i className="fa fa-arrow-down" />
<p>Drop SVG here</p>
</div>
<input type="file" onChange={this.handleDrop} />
<p>
Download an icon from <a href="https://thenounproject.com/">The Noun Project</a>, then drag
and drop it here.
</p>
</div>
<input type='file' onChange={this.handleDrop} />
<p>Download an icon from <a href='https://thenounproject.com/'>The Noun Project</a>, then drag and drop it here.</p>
</div>
</div>
</div>
}
);
},
});
module.exports = Controls;

View File

@ -1,47 +1,58 @@
.controls {
width: 500px;
.controls{
width : 500px;
.field{
display : flex;
margin-bottom : 10px;
label{
vertical-align : top;
width : 70px;
font-size : 0.8em;
font-weight : 800;
.field {
display: flex;
margin-bottom: 10px;
label {
vertical-align: top;
width: 70px;
font-size: 0.8em;
font-weight: 800;
}
.value, .slider-picker{
flex-grow : 1;
.value,
.slider-picker {
flex-grow: 1;
}
textarea.value, input.value{
padding : 4px;
font-family : 'Open Sans';
font-size : 1em;
rows : 3;
textarea.value,
input.value {
padding: 4px;
font-family: 'Open Sans';
font-size: 1em;
rows: 3;
}
&.svg{
&.svg {
margin-left: 20px;
.dropZone{
.dropZone {
margin-left: -1px;
padding : 20px 10px;
font-size : 0.8em;
font-weight : 800;
text-align : center;
border : 1px #999 dashed;
border-width : 8px;
i{
font-size : 3em;
color : #999
padding: 20px 10px;
font-size: 0.8em;
font-weight: 800;
text-align: center;
border: 1px #999 dashed;
border-width: 8px;
i {
font-size: 3em;
color: #999
}
&.hover{
background-color : #ddd;
&.hover {
background-color: #ddd;
}
}
.value>p{
margin-top : 10px;
font-size : 0.7em;
.value>p {
margin-top: 10px;
font-size: 0.7em;
}
input{
input {
margin-top: 10px;
}
}

View File

@ -1,63 +1,65 @@
const request = require('superagent');
const AccountActions = {
login : (user, pass) => {
login: (user, pass) => {
return new Promise((resolve, reject) => {
request.post('/login')
.send({ user , pass })
request
.post('/login')
.send({ user, pass })
.end((err, res) => {
if(err) return reject(res.body);
if (err) return reject(res.body);
AccountActions.createSession(res.body);
return resolve(res.body);
});
});
},
signup : (user, pass) => {
signup: (user, pass) => {
return new Promise((resolve, reject) => {
request.post('/signup')
.send({ user , pass })
request
.post('/signup')
.send({ user, pass })
.end((err, res) => {
if(err) return reject(res.body);
if (err) return reject(res.body);
AccountActions.createSession(res.body);
return resolve(res.body);
});
});
},
linkGoogle : (username, pass, user) => {
linkGoogle: (username, pass, user) => {
return new Promise((resolve, reject) => {
request.post('/link')
.send({ username , pass, user })
request
.post('/link')
.send({ username, pass, user })
.end((err, res) => {
if(err) return reject(res.body);
if (err) return reject(res.body);
AccountActions.createSession(res.body);
return resolve(res.body);
});
});
},
checkUsername : (username) => {
checkUsername: (username) => {
return new Promise((resolve, reject) => {
request.get(`/user_exists/${username}`)
request
.get(`/user_exists/${username}`)
.send()
.end((err, res) => {
if(err) return reject(res.body);
if (err) return reject(res.body);
return resolve(res.body);
});
});
},
createSession: (token) => {
const domain = window.domain === '.local.naturalcrit.com' ? 'localhost' : window.domain;
document.cookie = `nc_session=${token}; max-age=${60*60*24*365}; path=/; samesite=lax; domain=${domain};`;
document.cookie = `nc_session=${token}; max-age=${60 * 60 * 24 * 365}; path=/; samesite=lax; domain=${domain};`;
},
removeSession : () => {
removeSession: () => {
const domain = window.domain === '.local.naturalcrit.com' ? 'localhost' : window.domain;
document.cookie = `nc_session=, expires=Thu, 01 Jan 1970 00:00:01 GMT, samesite=lax, domain=${domain}`;
}
}
},
};
module.exports = AccountActions;
module.exports = AccountActions;

View File

@ -12,7 +12,6 @@ const AccountPage = (props) => {
<p>
<b>Username:</b> {props.user.username}
<br />
</p>
<br />
<button

View File

@ -1,257 +1,283 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const _ = require('lodash');
const cx = require('classnames');
const NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
const AccountActions = require('../account.actions.js');
//TODO: Almost identidal to "loginPage". Should possibly be merged to reduce redundancy
const LoginPage = React.createClass({
getDefaultProps: function() {
getDefaultProps: function () {
return {
redirect : '',
user : null
redirect: '',
user: null,
};
},
getInitialState: function() {
getInitialState: function () {
return {
view : 'login', //or 'signup'
visible : false,
view: 'login', //or 'signup'
visible: false,
username : '',
password : '',
username: '',
password: '',
processing : false,
checkingUsername : false,
redirecting : false,
processing: false,
checkingUsername: false,
redirecting: false,
usernameExists : false,
usernameExists: false,
errors : null,
success : false,
errors: null,
success: false,
};
},
componentDidMount: function() {
window.document.onkeypress = (e)=>{
if(e.code == 'Enter') this.handleClick();
}
componentDidMount: function () {
window.document.onkeypress = (e) => {
if (e.code == 'Enter') this.handleClick();
};
},
handleUserChange : function(e){
handleUserChange: function (e) {
this.setState(
{
usernameExists: true,
checkingUsername: true,
username: e.target.value,
},
() => {
if (this.state.view === 'signup') this.checkUsername();
}
);
},
handlePassChange: function (e) {
this.setState({ password: e.target.value });
},
handleClick: function () {
if (!this.isValid()) return;
if (this.state.view === 'login') this.login();
if (this.state.view === 'signup') this.signup();
},
redirect: function () {
if (!this.props.redirect) return window.location.reload();
this.setState(
{
redirecting: true,
},
() => {
window.location = this.props.redirect;
}
);
},
login: function () {
this.setState({
usernameExists : true,
checkingUsername : true,
username : e.target.value
}, ()=>{ if(this.state.view==='signup') this.checkUsername(); });
},
handlePassChange : function(e){
this.setState({ password : e.target.value });
},
handleClick : function(){
if(!this.isValid()) return;
if(this.state.view === 'login') this.login();
if(this.state.view === 'signup') this.signup();
},
redirect : function(){
if(!this.props.redirect) return window.location.reload();
this.setState({
redirecting : true
}, () => {window.location = this.props.redirect;});
},
login : function(){
this.setState({
processing : true,
errors : null
processing: true,
errors: null,
});
AccountActions.login(this.state.username, this.state.password)
.then(result => {
AccountActions.linkGoogle(this.state.username, this.state.password, this.props.user);
})
.then((token) => {
window.location = '/success';
})
.catch((err) => {
console.log(err);
this.setState({
processing : false,
errors : err
.then((result) => {
AccountActions.linkGoogle(this.state.username, this.state.password, this.props.user);
})
.then((token) => {
window.location = '/success';
})
.catch((err) => {
console.log(err);
this.setState({
processing: false,
errors: err,
});
});
});
},
logout : function(e){
logout: function (e) {
e.preventDefault();
AccountActions.removeSession();
window.location.reload();
return false;
},
signup : function(){
signup: function () {
this.setState({
processing : true,
errors : null
processing: true,
errors: null,
});
AccountActions.signup(this.state.username, this.state.password)
.then(result => {
AccountActions.linkGoogle(this.state.username, this.state.password, this.props.user);
})
.then((token) => {
window.location = '/success';
})
.catch((err) => {
console.log(err);
this.setState({
processing : false,
errors : err
.then((result) => {
AccountActions.linkGoogle(this.state.username, this.state.password, this.props.user);
})
.then((token) => {
window.location = '/success';
})
.catch((err) => {
console.log(err);
this.setState({
processing: false,
errors: err,
});
});
});
},
checkUsername : function(){
if(this.state.username === '') return;
checkUsername: function () {
if (this.state.username === '') return;
this.setState({
checkingUsername : true
checkingUsername: true,
});
this.debounceCheckUsername();
},
debounceCheckUsername : _.debounce(function(){
AccountActions.checkUsername(this.state.username)
.then((doesExist) => {
this.setState({
usernameExists : !!doesExist,
checkingUsername : false
});
})
debounceCheckUsername: _.debounce(function () {
AccountActions.checkUsername(this.state.username).then((doesExist) => {
this.setState({
usernameExists: !!doesExist,
checkingUsername: false,
});
});
}, 1000),
handleChangeView : function(newView){
this.setState({
view : newView,
errors : null
},this.checkUsername);
handleChangeView: function (newView) {
this.setState(
{
view: newView,
errors: null,
},
this.checkUsername
);
},
isValid : function(){
if(this.state.processing) return false;
isValid: function () {
if (this.state.processing) return false;
if(this.state.view === 'login'){
if (this.state.view === 'login') {
return this.state.username && this.state.password;
}else if(this.state.view === 'signup'){
} else if (this.state.view === 'signup') {
return this.state.username && this.state.password && !this.state.usernameExists;
}
},
renderErrors : function(){
if(!this.state.errors) return;
if(this.state.errors.msg) return <div className='errors'>{this.state.errors.msg}</div>;
return <div className='errors'>Something went wrong</div>;
renderErrors: function () {
if (!this.state.errors) return;
if (this.state.errors.msg) return <div className="errors">{this.state.errors.msg}</div>;
return <div className="errors">Something went wrong</div>;
},
renderUsernameValidation : function(){
if(this.state.view === 'login') return;
renderUsernameValidation: function () {
if (this.state.view === 'login') return;
let icon = null;
if(this.state.checkingUsername){
icon = <i className='fa fa-spinner fa-spin' />;
}else if(!this.state.username || this.state.usernameExists){
icon = <i className='fa fa-times red' />;
}else if(!this.state.usernameExists){
icon = <i className='fa fa-check green' />
if (this.state.checkingUsername) {
icon = <i className="fa fa-spinner fa-spin" />;
} else if (!this.state.username || this.state.usernameExists) {
icon = <i className="fa fa-times red" />;
} else if (!this.state.usernameExists) {
icon = <i className="fa fa-check green" />;
}
return <div className='control'>{icon}</div>
return <div className="control">{icon}</div>;
},
renderButton : function(){
renderButton: function () {
let className = '';
let text = '';
let icon = '';
if(this.state.processing){
if (this.state.processing) {
className = 'processing';
text = 'processing';
icon = 'fa-spinner fa-spin';
}else if(this.state.view === 'login'){
} else if (this.state.view === 'login') {
className = 'login';
text = 'login';
icon = 'fa-sign-in';
}else if(this.state.view === 'signup'){
} else if (this.state.view === 'signup') {
className = 'signup';
text = 'signup';
icon = 'fa-user-plus';
}
return <button
className={cx('action', className)}
disabled={!this.isValid()}
onClick={this.handleClick}>
<i className={`fa ${icon}`} />
{text}
</button>
return (
<button className={cx('action', className)} disabled={!this.isValid()} onClick={this.handleClick}>
<i className={`fa ${icon}`} />
{text}
</button>
);
},
render : function(){
return <div className='loginPage'>
<div className='logo'>
<NaturalCritIcon />
<span className='name'>
Natural
<span className='crit'>Crit</span>
</span>
render: function () {
return (
<div className="loginPage">
<div className="logo">
<NaturalCritIcon />
<span className="name">
Natural
<span className="crit">Crit</span>
</span>
</div>
<p>
To finish linking your Google account to the Homebrewery, please create a user ID
<br />
for the Homebrewery below (or sign in to an existing Homebrewery account).
<br />
<br />
You will only need to complete this step once. After your Google account is linked,
<br />
you will be able to access the Homebrewery with your Google account.
</p>
<div className="content">
<div className="switchView">
<div
className={cx('login', { 'selected': this.state.view === 'login' })}
onClick={this.handleChangeView.bind(null, 'login')}>
<i className="fa fa-sign-in" /> Login
</div>
<div
className={cx('signup', { 'selected': this.state.view === 'signup' })}
onClick={this.handleChangeView.bind(null, 'signup')}>
<i className="fa fa-user-plus" /> Signup
</div>
</div>
<div className="field user">
<label>username</label>
<input type="text" onChange={this.handleUserChange} value={this.state.username} />
{this.renderUsernameValidation()}
{this.state.usernameExists && !this.state.checkingUsername && this.state.view === 'signup' ? (
<div className="userExists">User with that name already exists</div>
) : null}
</div>
<div className="field password">
<label>password</label>
<input
type={cx({ text: this.state.visible, password: !this.state.visible })}
onChange={this.handlePassChange}
value={this.state.password}
/>
<div
className="control"
onClick={() => {
this.setState({ visible: !this.state.visible });
}}>
<i
className={cx('fa', {
'fa-eye': !this.state.visible,
'fa-eye-slash': this.state.visible,
})}
/>
</div>
</div>
{this.renderErrors()}
{this.renderButton()}
</div>
</div>
<p>To finish linking your Google account to the Homebrewery, please create a user ID<br />
for the Homebrewery below (or sign in to an existing Homebrewery account).<br />
<br />
You will only need to complete this step once. After your Google account is linked,<br />
you will be able to access the Homebrewery with your Google account.</p>
<div className='content'>
<div className='switchView'>
<div className={cx('login', {'selected' : this.state.view === 'login'})}
onClick={this.handleChangeView.bind(null, 'login')}>
<i className='fa fa-sign-in' /> Login
</div>
<div className={cx('signup', {'selected' : this.state.view === 'signup'})}
onClick={this.handleChangeView.bind(null, 'signup')}>
<i className='fa fa-user-plus' /> Signup
</div>
</div>
<div className='field user'>
<label>username</label>
<input
type='text'
onChange={this.handleUserChange}
value={this.state.username} />
{this.renderUsernameValidation()}
{this.state.usernameExists && !this.state.checkingUsername && this.state.view==='signup' ? <div className='userExists'>User with that name already exists</div> : null}
</div>
<div className='field password'>
<label>password</label>
<input
type={cx({text : this.state.visible, password : !this.state.visible})}
onChange={this.handlePassChange}
value={this.state.password} />
<div className='control' onClick={()=>{this.setState({visible : !this.state.visible})}}>
<i className={cx('fa', {'fa-eye' : !this.state.visible, 'fa-eye-slash' : this.state.visible})} />
</div>
</div>
{this.renderErrors()}
{this.renderButton()}
</div>
</div>
}
);
},
});
module.exports = LoginPage;

View File

@ -1,104 +1,129 @@
.loginPage{
text-align : center;
.loginPage {
text-align: center;
padding-top: 30px;
.content{
width : 400px;
margin : 0 auto;
padding : 20px;
.content {
width: 400px;
margin: 0 auto;
padding: 20px;
padding-top: 30px;
.switchView{
width : 100%;
&>div{
.switchView {
width: 100%;
&>div {
.animate(background-color);
display : inline-block;
width : 50%;
padding : 10px;
cursor : pointer;
background-color : transparent;
font-size : 0.8em;
font-weight : 800;
text-transform : uppercase;
i{
vertical-align : middle;
font-size : 2em;
display: inline-block;
width: 50%;
padding: 10px;
cursor: pointer;
background-color: transparent;
font-size: 0.8em;
font-weight: 800;
text-transform: uppercase;
i {
vertical-align: middle;
font-size: 2em;
}
}
.login:hover, .login.selected{
background-color : fade(@blue, 20%);
.login:hover,
.login.selected {
background-color: fade(@blue, 20%);
}
.signup:hover, .signup.selected{
background-color : fade(@green, 20%);
.signup:hover,
.signup.selected {
background-color: fade(@green, 20%);
}
}
.field{
position : relative;
margin : 20px 0px;
text-align : left;
label{
display : block;
width : 100%;
font-size : 0.6em;
font-weight : 800;
line-height : 1.1em;
text-transform : uppercase;
.field {
position: relative;
margin: 20px 0px;
text-align: left;
label {
display: block;
width: 100%;
font-size: 0.6em;
font-weight: 800;
line-height: 1.1em;
text-transform: uppercase;
}
input{
width : 100%;
padding : 10px;
font-size : 1.2em;
color : #333;
input {
width: 100%;
padding: 10px;
font-size: 1.2em;
color: #333;
}
.control{
position : absolute;
display : block;
right : -40px;
bottom : 0px;
height : 46px;
width : 40px;
text-align : center;
i{
margin-top : 15px;
.control {
position: absolute;
display: block;
right: -40px;
bottom: 0px;
height: 46px;
width: 40px;
text-align: center;
i {
margin-top: 15px;
}
}
&.password .control{
cursor : pointer;
&.password .control {
cursor: pointer;
}
.userExists{
.userExists {
font-size: 0.6em;
position: absolute;
right : 0px;
color : @red;
right: 0px;
color: @red;
}
}
button.action{
button.action {
.animate(opacity);
padding : 10px 20px;
cursor : pointer;
opacity : 0.8;
font-size : 0.8em;
font-weight : 800;
color : black;
text-transform : uppercase;
border : none;
outline : none;
&:hover{
opacity : 1;
padding: 10px 20px;
cursor: pointer;
opacity: 0.8;
font-size: 0.8em;
font-weight: 800;
color: black;
text-transform: uppercase;
border: none;
outline: none;
&:hover {
opacity: 1;
}
&:disabled{
cursor : not-allowed;
opacity : 0.3;
background-color : #ddd;
&:disabled {
cursor: not-allowed;
opacity: 0.3;
background-color: #ddd;
}
&.login{ background-color: @blue; }
&.signup{ background-color: @green; }
i{
margin-right : 10px;
font-size : 1.5em;
&.login {
background-color: @blue;
}
&.signup {
background-color: @green;
}
i {
margin-right: 10px;
font-size: 1.5em;
}
}
button.google{
button.google {
cursor: pointer;
width: 191px;
height: 46px;
@ -106,23 +131,27 @@
outline: none;
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_normal_web.png');
background-size: contain;
&:hover{
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_hover_web.png');
&:hover {
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_hover_web.png');
}
&:focus{
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_pressed_web.png');
&:focus {
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_pressed_web.png');
}
}
.errors{
margin-bottom : 20px;
color : @red;
.errors {
margin-bottom: 20px;
color: @red;
}
}
.divider{
.divider {
font-size: 1em;
font-weight: 800;
color: black;
text-transform: uppercase;
padding: 12px 20px 10px;
}
}
}

View File

@ -1,6 +1,6 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const _ = require('lodash');
const cx = require('classnames');
const NaturalCritIcon = require('naturalcrit/components/naturalcritLogo.jsx');
@ -8,75 +8,75 @@ const HomebrewIcon = require('naturalcrit/svg/homebrew.svg.jsx');
const TPKIcon = require('naturalcrit/svg/tpk.svg.jsx');
const BadgeIcon = require('naturalcrit/svg/badge.svg.jsx');
const HomePage = React.createClass({
getDefaultProps: function() {
return {
tools : [
{
id : 'homebrew',
path : 'https://homebrewery.naturalcrit.com',
name : 'The Homebrewery',
icon : <HomebrewIcon />,
desc : 'Make authentic-looking D&D homebrews using Markdown',
getDefaultProps: function () {
return {
tools: [
{
id: 'homebrew',
path: 'https://homebrewery.naturalcrit.com',
name: 'The Homebrewery',
icon: <HomebrewIcon />,
desc: 'Make authentic-looking D&D homebrews using Markdown',
show : true,
beta : false
},
{
id : 'badges',
path : '/badges',
name : 'Achievement Badges',
icon : <BadgeIcon />,
desc : 'Create simple badges to award your players',
show: true,
beta: false,
},
{
id: 'badges',
path: '/badges',
name: 'Achievement Badges',
icon: <BadgeIcon />,
desc: 'Create simple badges to award your players',
show : true,
beta : false
},
{
id : 'tpk',
path : 'http://tpk.naturalcrit.com',
name : 'Total Player Knoller',
icon : <TPKIcon />,
desc : 'Effortless custom character sheets',
show: true,
beta: false,
},
{
id: 'tpk',
path: 'http://tpk.naturalcrit.com',
name: 'Total Player Knoller',
icon: <TPKIcon />,
desc: 'Effortless custom character sheets',
show : false,
beta : true
},
show: false,
beta: true,
},
],
};
},
]
};
},
renderTool: function (tool) {
if (!tool.show) return null;
renderTool : function(tool){
if(!tool.show) return null;
return <a href={tool.path} className={cx('tool', tool.id, {beta : tool.beta})} key={tool.id}>
<div className='content'>
return (
<a href={tool.path} className={cx('tool', tool.id, { beta: tool.beta })} key={tool.id}>
<div className="content">
{tool.icon}
<h2>{tool.name}</h2>
<p>{tool.desc}</p>
</div>
</a>;
},
</a>
);
},
renderTools : function(){
return _.map(this.props.tools, (tool)=>{
return this.renderTool(tool);
});
},
renderTools: function () {
return _.map(this.props.tools, (tool) => {
return this.renderTool(tool);
});
},
render : function(){
return <div className='homePage'>
<div className='top'>
render: function () {
return (
<div className="homePage">
<div className="top">
<NaturalCritIcon />
<p>Top-tier tools for the discerning DM</p>
</div>
<div className='tools'>
{this.renderTools()}
</div>
<div className="tools">{this.renderTools()}</div>
</div>
}
});
);
},
});
module.exports = HomePage;

View File

@ -1,122 +1,175 @@
.homePage {
height: 100vh;
background-color: white;
.homePage{
height : 100vh;
background-color : white;
.top{
.top {
.fadeInTop(1s);
.delay(0.5);
margin-bottom : 100px;
padding-top : 100px;
text-align : center;
margin-bottom: 100px;
padding-top: 100px;
text-align: center;
p{
margin-top : 10px;
font-size : 1.3em;
font-style : italic;
color : @grey;
p {
margin-top: 10px;
font-size: 1.3em;
font-style: italic;
color: @grey;
}
}
.tools{
width : 100%;
text-align : center;
.tool{
.tools {
width: 100%;
text-align: center;
.tool {
.sequentialDelay(0.5s, 1s);
.fadeInDown(1s);
.keep();
display : inline-block;
vertical-align : top;
cursor : pointer;
opacity : 0;
color : black;
text-align : center;
text-decoration : none;
&+.tool{
border-left : 1px solid #666;
display: inline-block;
vertical-align: top;
cursor: pointer;
opacity: 0;
color: black;
text-align: center;
text-decoration: none;
&+.tool {
border-left: 1px solid #666;
}
.badges svg{
.badges svg {
width: 110px;
transform: translateY(-8px);
}
.content{
.content {
.addSketch(360px);
.animateAll(0.5s);
position : relative;
width : 320px;
padding : 35px;
&:hover{
svg, h2{
position: relative;
width: 320px;
padding: 35px;
&:hover {
svg,
h2 {
.transform(scale(1.3));
}
}
h2{
h2 {
.animateAll(0.5s);
font-family : 'CodeBold';
font-size : 2em;
font-family: 'CodeBold';
font-size: 2em;
}
p{
max-width : 300px;
margin : 20px auto;
line-height : 1.5em;
p {
max-width: 300px;
margin: 20px auto;
line-height: 1.5em;
}
svg{
svg {
.animateAll(0.5s);
height : 9em;
margin-bottom : 0.8em;
height: 9em;
margin-bottom: 0.8em;
}
}
.content:hover{
background-color : fade(@teal, 20%);
.content:hover {
background-color: fade(@teal, 20%);
}
//Beta styles
&.beta{
overflow : hidden;
cursor : initial;
.content{
&:after{
&.beta {
overflow: hidden;
cursor: initial;
.content {
&:after {
.animateAll();
.transform(rotate(45deg));
content : "beta!";
position : absolute;
display : block;
top : -12px;
right : -46px;
width : 36%;
padding-top : 27px;
padding-bottom : 15px;
content: "beta!";
position: absolute;
display: block;
top: -12px;
right: -46px;
width: 36%;
padding-top: 27px;
padding-bottom: 15px;
//opacity : 0;
background-color : fade(@grey, 50%);
font-size : 1em;
font-weight : 800;
text-align : center;
text-transform : uppercase;
background-color: fade(@grey, 50%);
font-size: 1em;
font-weight: 800;
text-align: center;
text-transform: uppercase;
}
}
}
}
}
}
.addSketch(@length, @color : black){
path, line, polyline, circle, rect, polygon {
.addSketch(@length, @color : black) {
path,
line,
polyline,
circle,
rect,
polygon {
.sketch(@length, @color, 4s);
stroke-dasharray : @length;
stroke-dashoffset : 0px;
stroke : @color;
stroke-width : 0.5px;
fill : @color;
stroke-dasharray: @length;
stroke-dashoffset: 0px;
stroke: @color;
stroke-width: 0.5px;
fill: @color;
//.animateAll(3s);
}
}
.sketch(@length, @color : black, @duration : 3s, @easing : @defaultEasing){
.sketch(@length, @color : black, @duration : 3s, @easing : @defaultEasing) {
.createAnimation(sketch, @duration, @easing);
.sketchKeyFrames(){
0% { stroke-dashoffset : @length; fill: transparent;}
50% { stroke-dashoffset : @length; fill: transparent;}
80% { stroke-dashoffset : 0px; fill: transparent;}
100% { stroke-dashoffset : 0px; fill:@color;}
.sketchKeyFrames() {
0% {
stroke-dashoffset: @length;
fill: transparent;
}
50% {
stroke-dashoffset: @length;
fill: transparent;
}
80% {
stroke-dashoffset: 0px;
fill: transparent;
}
100% {
stroke-dashoffset: 0px;
fill: @color;
}
}
@-webkit-keyframes sketch {.sketchKeyFrames();}
@-moz-keyframes sketch {.sketchKeyFrames();}
@-ms-keyframes sketch {.sketchKeyFrames();}
@-o-keyframes sketch {.sketchKeyFrames();}
@keyframes sketch {.sketchKeyFrames();}
}
@-webkit-keyframes sketch {
.sketchKeyFrames();
}
@-moz-keyframes sketch {
.sketchKeyFrames();
}
@-ms-keyframes sketch {
.sketchKeyFrames();
}
@-o-keyframes sketch {
.sketchKeyFrames();
}
@keyframes sketch {
.sketchKeyFrames();
}
}

View File

@ -1,187 +1,208 @@
const React = require('react');
const _ = require('lodash');
const cx = require('classnames');
const _ = require('lodash');
const cx = require('classnames');
const NaturalCritIcon = require('naturalcrit/components/naturalcritLogo.jsx');
const AccountActions = require('../account.actions.js');
const RedirectLocation = 'NC-REDIRECT-URL';
const LoginPage = React.createClass({
getDefaultProps: function() {
getDefaultProps: function () {
return {
redirect : '',
user : null
redirect: '',
user: null,
};
},
getInitialState: function() {
getInitialState: function () {
return {
view : 'login', //or 'signup'
visible : false,
view: 'login', //or 'signup'
visible: false,
username : '',
password : '',
username: '',
password: '',
processing : false,
checkingUsername : false,
redirecting : false,
processing: false,
checkingUsername: false,
redirecting: false,
usernameExists : false,
usernameExists: false,
errors : null,
success : false,
errors: null,
success: false,
};
},
componentDidMount: function() {
window.document.onkeypress = (e)=>{
if(e.code == 'Enter') this.handleClick();
}
componentDidMount: function () {
window.document.onkeypress = (e) => {
if (e.code == 'Enter') this.handleClick();
};
this.handleRedirectURL();
},
handleRedirectURL : function() {
if(!this.props.redirect) {
handleRedirectURL: function () {
if (!this.props.redirect) {
return window.sessionStorage.removeItem(RedirectLocation);
};
}
return window.sessionStorage.setItem(RedirectLocation, this.props.redirect);
},
handleUserChange : function(e){
this.setState({username : e.target.value});
if(this.props.user && this.props.user.username) return;
this.setState({
usernameExists : true,
checkingUsername : true,
}, ()=>{ if(this.state.view==='signup') this.checkUsername(); });
handleUserChange: function (e) {
this.setState({ username: e.target.value });
if (this.props.user && this.props.user.username) return;
this.setState(
{
usernameExists: true,
checkingUsername: true,
},
() => {
if (this.state.view === 'signup') this.checkUsername();
}
);
},
handlePassChange : function(e){
this.setState({ password : e.target.value });
handlePassChange: function (e) {
this.setState({ password: e.target.value });
},
handleClick : function(){
if(!this.isValid()) return;
if(this.state.view === 'login') this.login();
if(this.state.view === 'signup') this.signup();
handleClick: function () {
if (!this.isValid()) return;
if (this.state.view === 'login') this.login();
if (this.state.view === 'signup') this.signup();
},
redirect : function(){
if(!this.props.redirect) return window.location = '/';
this.setState({
redirecting : true
}, () => {window.location = this.props.redirect;});
redirect: function () {
if (!this.props.redirect) return (window.location = '/');
this.setState(
{
redirecting: true,
},
() => {
window.location = this.props.redirect;
}
);
},
login : function(){
login: function () {
this.setState({
processing : true,
errors : null
processing: true,
errors: null,
});
AccountActions.login(this.state.username, this.state.password)
.then((token) => {
this.setState({
processing : false,
errors : null,
success : true
}, this.redirect);
this.setState(
{
processing: false,
errors: null,
success: true,
},
this.redirect
);
})
.catch((err) => {
console.log(err);
this.setState({
processing : false,
errors : err
processing: false,
errors: err,
});
});
},
logout : function(e){
logout: function (e) {
e.preventDefault();
AccountActions.removeSession();
window.location.reload();
return false;
},
signup : function(){
signup: function () {
this.setState({
processing : true,
errors : null
processing: true,
errors: null,
});
const regex = /^(?!.*@).{3,}$/;
if(!regex.test(this.state.username)) {
if (!regex.test(this.state.username)) {
this.setState({
processing : false,
errors : {username : 'Username must be at least 3 characters long.'}
processing: false,
errors: { username: 'Username must be at least 3 characters long.' },
});
return;
}
AccountActions.signup(this.state.username, this.state.password)
.then((token) => {
this.setState({
processing : false,
errors : null,
success : true
}, this.redirect);
this.setState(
{
processing: false,
errors: null,
success: true,
},
this.redirect
);
})
.catch((err) => {
console.log(err);
this.setState({
processing : false,
errors : err
processing: false,
errors: err,
});
});
},
checkUsername : function(){
if(this.state.username === '') return;
checkUsername: function () {
if (this.state.username === '') return;
this.setState({
checkingUsername : true
checkingUsername: true,
});
this.debounceCheckUsername();
},
debounceCheckUsername : _.debounce(function(){
AccountActions.checkUsername(this.state.username)
.then((doesExist) => {
this.setState({
usernameExists : !!doesExist,
checkingUsername : false
});
})
debounceCheckUsername: _.debounce(function () {
AccountActions.checkUsername(this.state.username).then((doesExist) => {
this.setState({
usernameExists: !!doesExist,
checkingUsername: false,
});
});
}, 1000),
handleChangeView : function(newView){
this.setState({
view : newView,
errors : null
},this.checkUsername);
handleChangeView: function (newView) {
this.setState(
{
view: newView,
errors: null,
},
this.checkUsername
);
},
isValid : function(){
if(this.state.processing) return false;
isValid: function () {
if (this.state.processing) return false;
if(this.state.view === 'login'){
if (this.state.view === 'login') {
return this.state.username && this.state.password;
}else if(this.state.view === 'signup'){
} else if (this.state.view === 'signup') {
return this.state.username && this.state.password && !this.state.usernameExists;
}
},
linkGoogle : function(){
if(this.props.user) {
if(!confirm(`You are currently logged in as ${this.props.user.username}. ` +
`Do you want to link this user to a Google account? ` +
`This will allow you to access the Homebrewery with your ` +
`Google account and back up your files to Google Drive.`))
linkGoogle: function () {
if (this.props.user) {
if (
!confirm(
`You are currently logged in as ${this.props.user.username}. ` +
`Do you want to link this user to a Google account? ` +
`This will allow you to access the Homebrewery with your ` +
`Google account and back up your files to Google Drive.`
)
)
return;
}
this.setState({
processing : true,
errors : null
processing: true,
errors: null,
});
window.location.href='/auth/google';
window.location.href = '/auth/google';
},
// loginGoogle : function(){
@ -193,7 +214,6 @@ const LoginPage = React.createClass({
// AccountActions.loginGoogle();
// },
// console.log("about to start login");
// AccountActions.login(this.state.username, this.state.password)
// .then((token) => {
@ -212,124 +232,150 @@ const LoginPage = React.createClass({
// });
// },
renderErrors : function(){
if(!this.state.errors) return;
if(this.state.errors.msg) return <div className='errors'>{this.state.errors.msg}</div>;
return <div className='errors'>Something went wrong</div>;
renderErrors: function () {
if (!this.state.errors) return;
if (this.state.errors.msg) return <div className="errors">{this.state.errors.msg}</div>;
return <div className="errors">Something went wrong</div>;
},
renderUsernameValidation : function(){
if(this.state.view === 'login') return;
renderUsernameValidation: function () {
if (this.state.view === 'login') return;
let icon = null;
if(this.state.checkingUsername){
icon = <i className='fa fa-spinner fa-spin' />;
}else if(!this.state.username || this.state.usernameExists){
icon = <i className='fa fa-times red' />;
}else if(!this.state.usernameExists){
icon = <i className='fa fa-check green' />
if (this.state.checkingUsername) {
icon = <i className="fa fa-spinner fa-spin" />;
} else if (!this.state.username || this.state.usernameExists) {
icon = <i className="fa fa-times red" />;
} else if (!this.state.usernameExists) {
icon = <i className="fa fa-check green" />;
}
return <div className='control'>{icon}</div>
return <div className="control">{icon}</div>;
},
renderButton : function(){
renderButton: function () {
let className = '';
let text = '';
let icon = '';
if(this.state.processing){
if (this.state.processing) {
className = 'processing';
text = 'processing';
icon = 'fa-spinner fa-spin';
}else if(this.state.view === 'login'){
} else if (this.state.view === 'login') {
className = 'login';
text = 'login';
icon = 'fa-sign-in';
}else if(this.state.view === 'signup'){
} else if (this.state.view === 'signup') {
className = 'signup';
text = 'signup';
icon = 'fa-user-plus';
}
return <button
className={cx('action', className)}
disabled={!this.isValid() || this.props.user && this.props.user.username}
onClick={this.handleClick}>
<i className={`fa ${icon}`} />
{text}
</button>
return (
<button
className={cx('action', className)}
disabled={!this.isValid() || (this.props.user && this.props.user.username)}
onClick={this.handleClick}>
<i className={`fa ${icon}`} />
{text}
</button>
);
},
renderLoggedIn : function(){
if(!this.props.user) return;
let loggedInGoogle = "";
if (!this.props.user.googleId) {
return <small>
You are logged in as {this.props.user.username}. <a href='' onClick={this.logout}>logout.</a>
</small>
}
else {
return <small>
You are logged in via Google as {this.props.user.username}. <a href='' onClick={this.logout}>logout.</a>
</small>
renderLoggedIn: function () {
if (!this.props.user) return;
let loggedInGoogle = '';
if (!this.props.user.googleId) {
return (
<small>
You are logged in as {this.props.user.username}.{' '}
<a href="" onClick={this.logout}>
logout.
</a>
</small>
);
} else {
return (
<small>
You are logged in via Google as {this.props.user.username}.{' '}
<a href="" onClick={this.logout}>
logout.
</a>
</small>
);
}
},
render : function(){
return <div className='loginPage'>
<NaturalCritIcon />
<div className='content'>
<div className='switchView'>
<div className={cx('login', {'selected' : this.state.view === 'login'})}
onClick={this.handleChangeView.bind(null, 'login')}>
<i className='fa fa-sign-in' /> Login
render: function () {
return (
<div className="loginPage">
<NaturalCritIcon />
<div className="content">
<div className="switchView">
<div
className={cx('login', { 'selected': this.state.view === 'login' })}
onClick={this.handleChangeView.bind(null, 'login')}>
<i className="fa fa-sign-in" /> Login
</div>
<div
className={cx('signup', { 'selected': this.state.view === 'signup' })}
onClick={this.handleChangeView.bind(null, 'signup')}>
<i className="fa fa-user-plus" /> Signup
</div>
</div>
<div className={cx('signup', {'selected' : this.state.view === 'signup'})}
onClick={this.handleChangeView.bind(null, 'signup')}>
<i className='fa fa-user-plus' /> Signup
<div className="field user">
<label>username</label>
<input
type="text"
title={this.state.view === 'signup' ? 'Min 3 characters, and cannot contain ?!¿@ .' : ''}
onChange={this.handleUserChange}
value={this.state.username}
/>
{this.renderUsernameValidation()}
{this.state.usernameExists && !this.state.checkingUsername && this.state.view === 'signup' ? (
<div className="userExists">User with that name already exists</div>
) : null}
</div>
</div>
<div className="field password">
<label>password</label>
<input
type={cx({ text: this.state.visible, password: !this.state.visible })}
onChange={this.handlePassChange}
value={this.state.password}
/>
<div className='field user'>
<label>username</label>
<input
type='text'
title={this.state.view==='signup' ? 'Min 3 characters, and cannot contain ?!¿@ .': ''}
onChange={this.handleUserChange}
value={this.state.username} />
{this.renderUsernameValidation()}
{this.state.usernameExists && !this.state.checkingUsername && this.state.view==='signup' ? <div className='userExists'>User with that name already exists</div> : null}
</div>
<div className='field password'>
<label>password</label>
<input
type={cx({text : this.state.visible, password : !this.state.visible})}
onChange={this.handlePassChange}
value={this.state.password} />
<div className='control' onClick={()=>{this.setState({visible : !this.state.visible})}}>
<i className={cx('fa', {'fa-eye' : !this.state.visible, 'fa-eye-slash' : this.state.visible})} />
<div
className="control"
onClick={() => {
this.setState({ visible: !this.state.visible });
}}>
<i
className={cx('fa', {
'fa-eye': !this.state.visible,
'fa-eye-slash': this.state.visible,
})}
/>
</div>
</div>
{this.renderErrors()}
{this.renderButton()}
<div className="divider"> OR </div>
<button className="google" onClick={this.linkGoogle}></button>
</div>
{this.renderErrors()}
{this.renderButton()}
<div className='divider'> OR </div>
<button className='google' onClick={this.linkGoogle}></button>
<br />
<br />
<br />
<br />
{this.renderLoggedIn()}
</div>
<br />
<br />
<br />
<br />
{this.renderLoggedIn()}
</div>
}
);
},
});
module.exports = LoginPage;

View File

@ -1,134 +1,162 @@
.loginPage{
text-align : center;
.loginPage {
text-align: center;
padding-top: 30px;
.content{
width : 400px;
margin : 0 auto;
padding : 20px;
.content {
width: 400px;
margin: 0 auto;
padding: 20px;
padding-top: 50px;
.switchView{
width : 100%;
&>div{
.switchView {
width: 100%;
&>div {
.animate(background-color);
display : inline-block;
width : 50%;
padding : 10px;
cursor : pointer;
background-color : transparent;
font-size : 0.8em;
font-weight : 800;
text-transform : uppercase;
i{
vertical-align : middle;
font-size : 2em;
display: inline-block;
width: 50%;
padding: 10px;
cursor: pointer;
background-color: transparent;
font-size: 0.8em;
font-weight: 800;
text-transform: uppercase;
i {
vertical-align: middle;
font-size: 2em;
}
}
.login:hover, .login.selected{
background-color : fade(@blue, 20%);
.login:hover,
.login.selected {
background-color: fade(@blue, 20%);
}
.signup:hover, .signup.selected{
background-color : fade(@green, 20%);
.signup:hover,
.signup.selected {
background-color: fade(@green, 20%);
}
}
.field{
position : relative;
margin : 20px 0px;
text-align : left;
label{
display : block;
width : 100%;
font-size : 0.6em;
font-weight : 800;
line-height : 1.1em;
text-transform : uppercase;
.field {
position: relative;
margin: 20px 0px;
text-align: left;
label {
display: block;
width: 100%;
font-size: 0.6em;
font-weight: 800;
line-height: 1.1em;
text-transform: uppercase;
}
input{
width : 100%;
padding : 10px;
font-size : 1.2em;
color : #333;
input {
width: 100%;
padding: 10px;
font-size: 1.2em;
color: #333;
}
.control{
position : absolute;
display : block;
right : -40px;
bottom : 0px;
height : 46px;
width : 40px;
text-align : center;
i{
margin-top : 15px;
.control {
position: absolute;
display: block;
right: -40px;
bottom: 0px;
height: 46px;
width: 40px;
text-align: center;
i {
margin-top: 15px;
}
}
&.password .control{
cursor : pointer;
&.password .control {
cursor: pointer;
}
.userExists{
.userExists {
font-size: 0.6em;
position: absolute;
right : 0px;
color : @red;
right: 0px;
color: @red;
}
}
button.action{
button.action {
.animate(opacity);
padding : 10px 20px;
cursor : pointer;
opacity : 0.8;
font-size : 0.8em;
font-weight : 800;
color : black;
text-transform : uppercase;
border : none;
outline : none;
&:hover{
opacity : 1;
padding: 10px 20px;
cursor: pointer;
opacity: 0.8;
font-size: 0.8em;
font-weight: 800;
color: black;
text-transform: uppercase;
border: none;
outline: none;
&:hover {
opacity: 1;
}
&:disabled{
cursor : not-allowed;
opacity : 0.3;
background-color : #ddd;
&:disabled {
cursor: not-allowed;
opacity: 0.3;
background-color: #ddd;
}
&.login{ background-color: @blue; }
&.signup{ background-color: @green; }
i{
margin-right : 10px;
font-size : 1.5em;
&.login {
background-color: @blue;
}
&.signup {
background-color: @green;
}
i {
margin-right: 10px;
font-size: 1.5em;
}
}
button.google{
button.google {
cursor: pointer;
width: 191px;
height: 46px;
border: none;
outline: none;
background-color:unset;
background-color: unset;
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_normal_web.png');
background-size: contain;
&:hover{
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_hover_web.png');
&:hover {
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_hover_web.png');
}
&:focus{
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_pressed_web.png');
&:focus {
background-image: url('../assets/naturalcrit/styles/btn_google_signin_light_pressed_web.png');
}
}
.errors{
margin-bottom : 20px;
color : @red;
.errors {
margin-bottom: 20px;
color: @red;
}
}
.divider{
.divider {
font-size: 1em;
font-weight: 800;
color: black;
text-transform: uppercase;
padding: 12px 20px 10px;
}
+.accountButton.login {
display:none;
}
}
+.accountButton.login {
display: none;
}
}

View File

@ -6,69 +6,70 @@ var NaturalCritIcon = require('naturalcrit/svg/naturalcrit.svg.jsx');
var HomebrewIcon = require('naturalcrit/svg/homebrew.svg.jsx');
var Main = React.createClass({
getDefaultProps: function() {
getDefaultProps: function () {
return {
tools : [
tools: [
{
id : 'homebrew',
path : 'https://homebrewery.naturalcrit.com',
name : 'The Homebrewery',
icon : <HomebrewIcon />,
desc : 'Make authentic-looking 5e homebrews using Markdown',
id: 'homebrew',
path: 'https://homebrewery.naturalcrit.com',
name: 'The Homebrewery',
icon: <HomebrewIcon />,
desc: 'Make authentic-looking 5e homebrews using Markdown',
show : true,
beta : false
show: true,
beta: false,
},
{
id : 'homebrew2',
path : '/homebrew',
name : 'The Homebrewery',
icon : <HomebrewIcon />,
desc : 'Make authentic-looking 5e homebrews using Markdown',
id: 'homebrew2',
path: '/homebrew',
name: 'The Homebrewery',
icon: <HomebrewIcon />,
desc: 'Make authentic-looking 5e homebrews using Markdown',
show : false,
beta : true
show: false,
beta: true,
},
]
],
};
},
renderTool : function(tool){
if(!tool.show) return null;
renderTool: function (tool) {
if (!tool.show) return null;
return <a href={tool.path} className={cx('tool', tool.id, {beta : tool.beta})} key={tool.id}>
<div className='content'>
{tool.icon}
<h2>{tool.name}</h2>
<p>{tool.desc}</p>
</div>
</a>;
return (
<a href={tool.path} className={cx('tool', tool.id, { beta: tool.beta })} key={tool.id}>
<div className="content">
{tool.icon}
<h2>{tool.name}</h2>
<p>{tool.desc}</p>
</div>
</a>
);
},
renderTools : function(){
return _.map(this.props.tools, (tool)=>{
renderTools: function () {
return _.map(this.props.tools, (tool) => {
return this.renderTool(tool);
});
},
render : function(){
return <div className='main'>
<div className='top'>
<div className='logo'>
<NaturalCritIcon />
<span className='name'>
Natural
<span className='crit'>Crit</span>
</span>
render: function () {
return (
<div className="main">
<div className="top">
<div className="logo">
<NaturalCritIcon />
<span className="name">
Natural
<span className="crit">Crit</span>
</span>
</div>
<p>Top-tier tools for the discerning DM</p>
</div>
<p>Top-tier tools for the discerning DM</p>
<div className="tools">{this.renderTools()}</div>
</div>
<div className='tools'>
{this.renderTools()}
</div>
</div>
}
);
},
});
module.exports = Main;

View File

@ -1,136 +1,200 @@
@import 'naturalcrit/styles/core.less';
.main{
height : 100vh;
background-color : white;
.top{
.main {
height: 100vh;
background-color: white;
.top {
.fadeInTop(1s);
.delay(0.5);
margin-bottom : 100px;
padding-top : 100px;
text-align : center;
.logo{
font-size : 4em;
color : black;
svg{
height : .9em;
margin-right : .2em;
cursor : pointer;
fill : black;
margin-bottom: 100px;
padding-top: 100px;
text-align: center;
.logo {
font-size: 4em;
color: black;
svg {
height: .9em;
margin-right: .2em;
cursor: pointer;
fill: black;
}
.name{
font-family : 'CodeLight';
.crit{
font-family : 'CodeBold';
.name {
font-family: 'CodeLight';
.crit {
font-family: 'CodeBold';
}
}
}
p{
margin-top : 10px;
font-size : 1.3em;
font-style : italic;
color : @grey;
p {
margin-top: 10px;
font-size: 1.3em;
font-style: italic;
color: @grey;
}
}
.tools{
width : 100%;
text-align : center;
.tool{
.tools {
width: 100%;
text-align: center;
.tool {
.sequentialDelay(0.5s, 1s);
.fadeInDown(1s);
.keep();
display : inline-block;
cursor : pointer;
opacity : 0;
color : black;
text-align : center;
text-decoration : none;
&+.tool{
border-left : 1px solid #666;
display: inline-block;
cursor: pointer;
opacity: 0;
color: black;
text-align: center;
text-decoration: none;
&+.tool {
border-left: 1px solid #666;
}
.content{
.content {
.addSketch(360px);
.animateAll(0.5s);
position : relative;
width : 320px;
padding : 35px;
&:hover{
svg, h2{
position: relative;
width: 320px;
padding: 35px;
&:hover {
svg,
h2 {
.transform(scale(1.3));
}
}
h2{
h2 {
.animateAll(0.5s);
font-family : 'CodeBold';
font-size : 2em;
font-family: 'CodeBold';
font-size: 2em;
}
p{
max-width : 300px;
margin : 20px auto;
line-height : 1.5em;
p {
max-width: 300px;
margin: 20px auto;
line-height: 1.5em;
}
svg{
svg {
.animateAll(0.5s);
height : 10em;
height: 10em;
}
}
.content:hover{
background-color : fade(@teal, 20%);
.content:hover {
background-color: fade(@teal, 20%);
}
//Beta styles
&.beta{
cursor : initial;
.content{
&:hover{
svg, h2{
&.beta {
cursor: initial;
.content {
&:hover {
svg,
h2 {
.transform(scale(1.0));
}
}
svg, h2{
opacity : 0.3;
svg,
h2 {
opacity: 0.3;
}
&:after{
&:after {
.animateAll();
content : "beta!";
position : absolute;
display : block;
top : 120px;
left : 0px;
width : 100%;
padding : 10px 0px;
content: "beta!";
position: absolute;
display: block;
top: 120px;
left: 0px;
width: 100%;
padding: 10px 0px;
//opacity : 0;
background-color : fade(@grey, 50%);
font-size : 2em;
font-weight : 800;
text-align : center;
text-transform : uppercase;
background-color: fade(@grey, 50%);
font-size: 2em;
font-weight: 800;
text-align: center;
text-transform: uppercase;
}
}
}
}
}
}
.addSketch(@length, @color : black){
path, line, polyline, circle, rect, polygon {
.addSketch(@length, @color : black) {
path,
line,
polyline,
circle,
rect,
polygon {
.sketch(@length, @color, 4s);
stroke-dasharray : @length;
stroke-dashoffset : 0px;
stroke : @color;
stroke-width : 0.5px;
fill : @color;
stroke-dasharray: @length;
stroke-dashoffset: 0px;
stroke: @color;
stroke-width: 0.5px;
fill: @color;
//.animateAll(3s);
}
}
.sketch(@length, @color : black, @duration : 3s, @easing : @defaultEasing){
.sketch(@length, @color : black, @duration : 3s, @easing : @defaultEasing) {
.createAnimation(sketch, @duration, @easing);
.sketchKeyFrames(){
0% { stroke-dashoffset : @length; fill: transparent;}
50% { stroke-dashoffset : @length; fill: transparent;}
80% { stroke-dashoffset : 0px; fill: transparent;}
100% { stroke-dashoffset : 0px; fill:@color;}
.sketchKeyFrames() {
0% {
stroke-dashoffset: @length;
fill: transparent;
}
50% {
stroke-dashoffset: @length;
fill: transparent;
}
80% {
stroke-dashoffset: 0px;
fill: transparent;
}
100% {
stroke-dashoffset: 0px;
fill: @color;
}
}
@-webkit-keyframes sketch {
.sketchKeyFrames();
}
@-moz-keyframes sketch {
.sketchKeyFrames();
}
@-ms-keyframes sketch {
.sketchKeyFrames();
}
@-o-keyframes sketch {
.sketchKeyFrames();
}
@keyframes sketch {
.sketchKeyFrames();
}
@-webkit-keyframes sketch {.sketchKeyFrames();}
@-moz-keyframes sketch {.sketchKeyFrames();}
@-ms-keyframes sketch {.sketchKeyFrames();}
@-o-keyframes sketch {.sketchKeyFrames();}
@keyframes sketch {.sketchKeyFrames();}
}

View File

@ -11,62 +11,55 @@ const GoogleRedirect = require('./googleRedirect/googleRedirect.jsx');
let Router;
const Naturalcrit = React.createClass({
getDefaultProps: function() {
getDefaultProps: function () {
return {
user : null,
url : '',
domain : '',
authToken : ''
user: null,
url: '',
domain: '',
authToken: '',
};
},
componentWillMount: function() {
componentWillMount: function () {
global.domain = this.props.domain;
Router = CreateRouter({
'/account' : (args, query) => {
return <AccountPage
user={this.props.user} />
'/account': (args, query) => {
return <AccountPage user={this.props.user} />;
},
'/login' : (args, query) => {
return <LoginPage
redirect={query.redirect}
user={this.props.user} />
'/login': (args, query) => {
return <LoginPage redirect={query.redirect} user={this.props.user} />;
},
'/success' : (args, query) => {
return <SuccessPage
user={this.props.user} />
'/success': (args, query) => {
return <SuccessPage user={this.props.user} />;
},
'/auth/google/redirect' : (args, query) => {
return <GoogleRedirect
user={this.props.user} />
'/auth/google/redirect': (args, query) => {
return <GoogleRedirect user={this.props.user} />;
},
'*': () => {
return <HomePage configTools={this.props.tools} user={this.props.user} />;
},
'*' : () => {
return <HomePage
configTools={this.props.tools}
user={this.props.user} />
}
});
},
renderAccount : function(){
renderAccount: function () {
let accountLink = '';
if(this.props.user && this.props.user.username) {
accountLink=<a href='/account'>{this.props.user.username}</a>
if (this.props.user && this.props.user.username) {
accountLink = <a href="/account">{this.props.user.username}</a>;
} else {
accountLink=<a href='/login'>Log in</a>
};
accountLink = <a href="/login">Log in</a>;
}
return accountLink;
},
render : function(){
return <div className='naturalcrit'>
<Router initialUrl={this.props.url}/>
<div className={`accountButton ${this.props.user ? '': 'login'}`}>
{this.renderAccount()}
render: function () {
return (
<div className="naturalcrit">
<Router initialUrl={this.props.url} />
<div className={`accountButton ${this.props.user ? '' : 'login'}`}>{this.renderAccount()}</div>
</div>
</div>
}
);
},
});
module.exports = Naturalcrit;

View File

@ -6,7 +6,7 @@
}
a {
cursor:pointer;
cursor: pointer;
}
b {
@ -14,8 +14,8 @@ b {
}
small {
font-size : 12px;
font-style : italic;
font-size: 12px;
font-style: italic;
}
.logo {
@ -47,7 +47,7 @@ small {
}
button {
border:none;
border: none;
font-family: 'CodeBold';
letter-spacing: 1px;
padding: 12px 20px;

View File

@ -1,14 +1,9 @@
var React = require('react');
var _ = require('lodash');
var cx = require('classnames');
var SignupPage = React.createClass({
render : function(){
return <div className='signupPage'>
SignupPage Component Ready.
</div>
}
render: function () {
return <div className="signupPage">SignupPage Component Ready.</div>;
},
});
module.exports = SignupPage;

View File

@ -1,3 +0,0 @@
.signupPage{
}

View File

@ -5,48 +5,51 @@ const NaturalCritIcon = require('naturalcrit/components/naturalcritLogo.jsx');
const RedirectLocation = 'NC-REDIRECT-URL';
const SuccessPage = React.createClass({
getDefaultProps: function() {
getDefaultProps: function () {
return {
redirect : '',
user : null
redirect: '',
user: null,
};
},
getInitialState: function() {
getInitialState: function () {
return {
view : 'login', //or 'signup'
visible : false,
view: 'login', //or 'signup'
visible: false,
username : '',
password : '',
username: '',
password: '',
processing : false,
checkingUsername : false,
redirecting : false,
processing: false,
checkingUsername: false,
redirecting: false,
usernameExists : false,
usernameExists: false,
errors : null,
success : false,
errors: null,
success: false,
};
},
componentDidMount: function() {
componentDidMount: function () {
const redirectURL = window.sessionStorage.getItem(RedirectLocation) || '/';
window.sessionStorage.removeItem(RedirectLocation);
setTimeout(function(){window.location=redirectURL;}, 1500);
},
render : function(){
return <div className='loginPage'>
<NaturalCritIcon />
setTimeout(function () {
window.location = redirectURL;
}, 1500);
},
render: function () {
return (
<div className="loginPage">
<NaturalCritIcon />
<div className='content'>
<p>Successfully logged in!</p>
<br />
<br />
<p>Redirecting...</p>
</div>
</div>
}
<div className="content">
<p>Successfully logged in!</p>
<br />
<br />
<p>Redirecting...</p>
</div>
</div>
);
},
});
module.exports = SuccessPage;

View File

@ -1,4 +1,4 @@
module.exports = function(vitreum){
module.exports = function (vitreum) {
return `
<!DOCTYPE html>
<html>
@ -21,7 +21,4 @@ module.exports = function(vitreum){
${vitreum.js}
</html>
`;
}
};

View File

@ -5,16 +5,20 @@
@import 'naturalcrit/styles/tooltip.less';
@font-face {
font-family : CodeLight;
src : url('/assets/naturalcrit/styles/CODE Light.otf');
font-family: CodeLight;
src: url('/assets/naturalcrit/styles/CODE Light.otf');
}
@font-face {
font-family : CodeBold;
src : url('/assets/naturalcrit/styles/CODE Bold.otf');
font-family: CodeBold;
src: url('/assets/naturalcrit/styles/CODE Bold.otf');
}
html,body, #reactContainer{
html,
body,
#reactContainer {
min-height: 100vh;
height: 100vh;
margin: 0;
font-family : 'Open Sans', sans-serif;
font-family: 'Open Sans', sans-serif;
}

View File

@ -1,86 +1,100 @@
@containerWidth : 1000px;
html, body{
position : relative;
height : 100%;
min-height : 100%;
background-color : #eee;
font-family : 'Lato', sans-serif;
color : @copyGrey;
html,
body {
position: relative;
height: 100%;
min-height: 100%;
background-color: #eee;
font-family: 'Lato', sans-serif;
color: @copyGrey;
}
.container{
position : relative;
max-width : @containerWidth;
margin : 0 auto;
padding-right : 20px;
padding-left : 20px;
.container {
position: relative;
max-width: @containerWidth;
margin: 0 auto;
padding-right: 20px;
padding-left: 20px;
}
h1{
margin-top : 10px;
margin-bottom : 15px;
font-size : 2em;
h1 {
margin-top: 10px;
margin-bottom: 15px;
font-size: 2em;
}
h2{
margin-top : 10px;
margin-bottom : 15px;
font-size : 1.5em;
font-weight : 900;
h2 {
margin-top: 10px;
margin-bottom: 15px;
font-size: 1.5em;
font-weight: 900;
}
h3{
margin-top : 5px;
margin-bottom : 7px;
font-size : 1em;
font-weight : 900;
h3 {
margin-top: 5px;
margin-bottom: 7px;
font-size: 1em;
font-weight: 900;
}
p{
margin-bottom : 1em;
font-size : 16px;
color : @copyGrey;
line-height : 1.5em;
p {
margin-bottom: 1em;
font-size: 16px;
color: @copyGrey;
line-height: 1.5em;
}
code{
background-color : #F8F8F8;
font-family : 'Courier', mono;
color : black;
white-space : pre;
code {
background-color: #F8F8F8;
font-family: 'Courier', mono;
color: black;
white-space: pre;
}
a{
color : inherit;
a {
color: inherit;
}
strong{
font-weight : bold;
strong {
font-weight: bold;
}
button{
button {
.button();
}
.button(@backgroundColor : @green){
.button(@backgroundColor : @green) {
.animate(background-color);
display : inline-block;
padding : 0.6em 1.2em;
cursor : pointer;
background-color : @backgroundColor;
font-family : "Lato", Helvetica, Arial, sans-serif;
font-size : 15px;
color : white;
text-decoration : none;
border : none;
outline : none;
&:hover{
background-color : darken(@backgroundColor, 5%);
display: inline-block;
padding: 0.6em 1.2em;
cursor: pointer;
background-color: @backgroundColor;
font-family: "Lato", Helvetica, Arial, sans-serif;
font-size: 15px;
color: white;
text-decoration: none;
border: none;
outline: none;
&:hover {
background-color: darken(@backgroundColor, 5%);
}
&:active{
background-color : darken(@backgroundColor, 10%);
&:active {
background-color: darken(@backgroundColor, 10%);
}
&:disabled{
background-color : @silver !important;
&:disabled {
background-color: @silver !important;
}
}
.iconButton(@backgroundColor : @green){
padding : 0.6em;
cursor : pointer;
background-color : @backgroundColor;
font-size : 14px;
color : white;
text-align : center;
.iconButton(@backgroundColor : @green) {
padding: 0.6em;
cursor: pointer;
background-color: @backgroundColor;
font-size: 14px;
color: white;
text-align: center;
}