mirror of
https://github.com/quepasaevents/qpa-client.git
synced 2023-12-14 05:33:02 +01:00
Style and i18nize signup
This commit is contained in:
parent
370f2c608b
commit
3defe9a5b7
3
@types/index.d.ts
vendored
3
@types/index.d.ts
vendored
|
@ -1 +1,4 @@
|
|||
export type EventStatus = "confirmed" | "canceled"
|
||||
declare module "*.png" {
|
||||
|
||||
}
|
||||
|
|
|
@ -5,6 +5,10 @@ import {ITextFieldProps, TextField as OUITextField} from 'office-ui-fabric-react
|
|||
export interface TextFieldProps extends ITextFieldProps {}
|
||||
|
||||
const TextField = (props: TextFieldProps) => {
|
||||
|
||||
return <OUITextField {...props} />
|
||||
}
|
||||
export default styled(TextField)``
|
||||
export default styled(TextField)`
|
||||
border-radius: 8px;
|
||||
border-color: #5E8036;
|
||||
`
|
||||
|
|
|
@ -1,66 +1,76 @@
|
|||
import {css, Global} from "@emotion/core"
|
||||
import { css, Global } from "@emotion/core"
|
||||
import styled from "@emotion/styled"
|
||||
import {MessageCenterDisplay} from "qpa-message-center"
|
||||
import { MessageCenterDisplay } from "qpa-message-center"
|
||||
import * as React from "react"
|
||||
import Footer from "./Footer"
|
||||
import Header from "./Header/Header"
|
||||
import Routes from "./Routes"
|
||||
import * as intl from 'react-intl-universal'
|
||||
import {Helmet} from 'react-helmet'
|
||||
import AppMessages from './App.msg.json'
|
||||
import * as intl from "react-intl-universal"
|
||||
import { Helmet } from "react-helmet"
|
||||
import AppMessages from "./App.msg.json"
|
||||
|
||||
const App = () => {
|
||||
intl.init({
|
||||
currentLocale: 'es-ES',
|
||||
locales: {
|
||||
'en-GB': AppMessages.en,
|
||||
'es-ES': AppMessages.es
|
||||
}
|
||||
})
|
||||
return (
|
||||
<Root>
|
||||
<Helmet>
|
||||
<title>{ intl.get('APP_TITLE')}</title>
|
||||
</Helmet>
|
||||
<Global
|
||||
styles={css`
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
}
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<StyledHeader/>
|
||||
<Content>
|
||||
<Routes />
|
||||
</Content>
|
||||
<MessageCenterDisplay/>
|
||||
<StyledFooter/>
|
||||
</Root>
|
||||
)
|
||||
|
||||
intl.init({
|
||||
currentLocale: "es-ES",
|
||||
locales: {
|
||||
"en-GB": AppMessages.en,
|
||||
"es-ES": AppMessages.es,
|
||||
},
|
||||
})
|
||||
return (
|
||||
<Root>
|
||||
<Helmet>
|
||||
<title>{intl.get("APP_TITLE")}</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
</Helmet>
|
||||
<Global
|
||||
styles={css`
|
||||
body {
|
||||
margin: 0;
|
||||
height: 100vh;
|
||||
--sansserif: "Segoe UI Web (East European)", Segoe UI, -apple-system, BlinkMacSystemFont, Roboto, Helvetica Neue, sans-serif;
|
||||
font-family: var(--sansserif);
|
||||
}
|
||||
#app {
|
||||
height: 100%;
|
||||
}
|
||||
`}
|
||||
/>
|
||||
<StyledHeader />
|
||||
<Content>
|
||||
<Routes />
|
||||
</Content>
|
||||
<MessageCenterDisplay />
|
||||
<StyledFooter />
|
||||
</Root>
|
||||
)
|
||||
}
|
||||
const Root = styled.div`
|
||||
display: grid;
|
||||
height: 100%;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: [header] 48px [body] 1fr [footer] 32px;
|
||||
grid-template-columns:
|
||||
[left-start full-start] minmax(0, 240px)
|
||||
[left-end content-start] minmax(320px, 1200px)
|
||||
[content-end right-start] minmax(0, 240px)
|
||||
[right-end full-end];
|
||||
grid-template-rows:
|
||||
[header-start] 48px
|
||||
[header-end center-start] 1fr
|
||||
[center-end footer-start] 32px
|
||||
[footer-end];
|
||||
`
|
||||
|
||||
const Content = styled.div`
|
||||
grid-row: body;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-self: center;
|
||||
grid-row: center;
|
||||
grid-column: content;
|
||||
`
|
||||
|
||||
const StyledFooter = styled(Footer)`
|
||||
grid-row: footer;
|
||||
grid-column: full;
|
||||
`
|
||||
const StyledHeader = styled(Header)`
|
||||
grid-column: full;
|
||||
grid-row: header;
|
||||
`
|
||||
export default App
|
||||
|
|
32
packages/qpa/App/Auth/Signup.msg.json
Normal file
32
packages/qpa/App/Auth/Signup.msg.json
Normal file
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"es-ES": {
|
||||
"form-error-no-name": "Por favor, introduze un nombre",
|
||||
"form-error-name-too-short": "El Nombre tiene que ser más largo",
|
||||
"form-error-no-email": "Por favor, introduze un email",
|
||||
"form-error-legal-email": "Por favor, introduze una dirección valida de email",
|
||||
"signup-error": "Error en crear una cuenta, por favor intenta más tarde otra vez",
|
||||
"signup-success": "Cuenta creada correctamente, mira en tu email",
|
||||
"email-taken": "Esta email ya tiene una cuenta. Quieres iniciar una sesión?",
|
||||
"go-to-calendar": "Ir al calendario",
|
||||
"your-email": "Tu email",
|
||||
"your-name": "Tu nombre",
|
||||
"sign-up": "Registrarse",
|
||||
"signup-form-title": "Para poder entrar to propios eventos, tienes que registrarte. Solo tendrías que darnos un nombre y una dirección email, dónde reciber correos electronico. Una vez tengamos esta información, de mandamos una invitación a tú email",
|
||||
"already-have-account-login": "Ya tengo una cuenta. ¡Inicia Sesión!"
|
||||
},
|
||||
"en-GB": {
|
||||
"form-error-no-name": "Please type in a name",
|
||||
"form-error-name-too-short": "Name has to be longer",
|
||||
"form-error-no-email": "Please type in an email",
|
||||
"form-error-legal-email": "Please enter a legal email",
|
||||
"signup-error": "Error signin up. Please try later",
|
||||
"signup-success": "Sign up succeeded. Please check your email",
|
||||
"email-taken": "Email is taken. Maybe try to log in?",
|
||||
"go-to-calendar": "Go to calendar",
|
||||
"your-email": "Your email",
|
||||
"your-name": "Your name",
|
||||
"sign-up": "Sign Up",
|
||||
"signup-form-title": "In order to insert your own event, please sign up. You only have to give us a name, and an email where we can reach you. Once these are set, we will send an invitation to your email.",
|
||||
"already-have-account-login": "I already have an account, go to login!"
|
||||
}
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
import styled from "@emotion/styled"
|
||||
import {Field, Form, Formik} from "formik"
|
||||
import {Button, Label, Spinner, TextField} from "qpa-components"
|
||||
import {useMessageCenter} from "qpa-message-center"
|
||||
import { Field, Form, Formik } from "formik"
|
||||
import { Button, Label, Spinner, TextField } from "qpa-components"
|
||||
import { useMessageCenter } from "qpa-message-center"
|
||||
import * as React from "react"
|
||||
import {Link} from "react-router-dom"
|
||||
import { hot } from "react-hot-loader"
|
||||
import { Link } from "react-router-dom"
|
||||
import Logo from "../LOGO.png"
|
||||
import intl from "react-intl-universal"
|
||||
import messages from "./Signup.msg.json"
|
||||
|
||||
interface SignupFormData {
|
||||
email: string
|
||||
|
@ -11,94 +15,165 @@ interface SignupFormData {
|
|||
}
|
||||
class SignupFormik extends Formik<SignupFormData> {}
|
||||
|
||||
const emailRegex = /^(([^<>()\[\]\\.,;:\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,}))$/
|
||||
const Signup = () => {
|
||||
intl.load(messages)
|
||||
const { addMessage } = useMessageCenter()
|
||||
const [loading, setLoading] = React.useState(false)
|
||||
const [success, setSuccess] = React.useState(null)
|
||||
const [error, setError] = React.useState(null)
|
||||
const [emailTaken, setEmailTaken] = React.useState(false)
|
||||
|
||||
if (success) {
|
||||
return <Label>Sign up was successful. Please check your email or <Link to="/">go to calendar</Link></Label>
|
||||
}
|
||||
return (
|
||||
<SignupFormik
|
||||
initialValues={{name: "", email: ""}}
|
||||
onSubmit={(values, {setFieldError}) => {
|
||||
fetch("/api/signup", {
|
||||
method: "post",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: values.name,
|
||||
email: values.email,
|
||||
}),
|
||||
}).then((res) => {
|
||||
if (res.status === 200) {
|
||||
setSuccess(true)
|
||||
addMessage({
|
||||
type: "success",
|
||||
text: "Sign up succeeded. Please check your email",
|
||||
<Root>
|
||||
<LogoHolder>
|
||||
<img src={Logo} />
|
||||
</LogoHolder>
|
||||
{success ? (
|
||||
<Label>
|
||||
{intl.get("signup-success")}{" "}
|
||||
<Link to="/">{intl.get("go-to-calendar")}</Link>
|
||||
</Label>
|
||||
) : (
|
||||
<SignupFormik
|
||||
initialValues={{ name: "", email: "" }}
|
||||
validate={(values: SignupFormData) => {
|
||||
const errors: any = {}
|
||||
if (!values.name) {
|
||||
errors.name = intl.get("form-error-no-name")
|
||||
}
|
||||
if (values.name.length < 4) {
|
||||
errors.name = intl.get("form-error-name-too-short")
|
||||
}
|
||||
if (!values.email) {
|
||||
errors.email = intl.get("form-error-no-email")
|
||||
}
|
||||
if (!emailRegex.test(values.email)) {
|
||||
errors.email = intl.get("form-error-legal-email")
|
||||
}
|
||||
return errors
|
||||
}}
|
||||
onSubmit={(values, { setFieldError }) => {
|
||||
fetch("/api/signup", {
|
||||
method: "post",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
name: values.name,
|
||||
email: values.email,
|
||||
}),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
addMessage({
|
||||
type: "error",
|
||||
text: "Error signin up. Please try later",
|
||||
})
|
||||
}
|
||||
.then(res => {
|
||||
if (res.status === 200) {
|
||||
setSuccess(true)
|
||||
addMessage({
|
||||
type: "success",
|
||||
text: intl.get("signup-success"),
|
||||
})
|
||||
return
|
||||
} else {
|
||||
addMessage({
|
||||
type: "error",
|
||||
text: intl.get("signup-error"),
|
||||
})
|
||||
}
|
||||
|
||||
if (res.status === 409) {
|
||||
setFieldError("email", "Email is taken. Maybe try to log in?")
|
||||
}
|
||||
}).catch((e) => {
|
||||
addMessage({
|
||||
type: "error",
|
||||
text: `Error signin up. Please try later. ${e.message}`,
|
||||
})
|
||||
})
|
||||
|
||||
}}
|
||||
>
|
||||
{
|
||||
({values, isValid}) => (
|
||||
<SForm>
|
||||
<p css={{gridArea: "1/1/1/4"}}>
|
||||
In order to insert your own event, please sign up.
|
||||
You only have to give us a name, and and email where we can reach you.
|
||||
Once these are set, we will send an invitation to your email.
|
||||
</p>
|
||||
<Field name="name">
|
||||
{
|
||||
({field}) => <STextField placeholder="Your name" {...field} css={{gridArea: "2/2/2/3"}}/>
|
||||
}
|
||||
</Field>
|
||||
<Field name="email" css={{gridRow: 2}}>
|
||||
{
|
||||
({field}) => <STextField placeholder="Your email" {...field} css={{gridArea: "3/2/3/4"}}/>
|
||||
}
|
||||
</Field>
|
||||
|
||||
<Button type="submit" disabled={!isValid || loading} css={{gridArea: "4/2/5/3", marginTop: 12}}>{
|
||||
loading ? <Spinner /> : "Sign up"
|
||||
}</Button>
|
||||
|
||||
</SForm>
|
||||
)
|
||||
}
|
||||
</SignupFormik>
|
||||
if (res.status === 409) {
|
||||
setFieldError("email", intl.get("email-taken"))
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
addMessage({
|
||||
type: "error",
|
||||
text: `${intl.get('signup-error')} ${e.message}`,
|
||||
})
|
||||
})
|
||||
}}
|
||||
>
|
||||
{({ values, isValid, errors, touched }) => (
|
||||
<SForm>
|
||||
<Title>
|
||||
{
|
||||
intl.get('signup-form-title')
|
||||
}
|
||||
</Title>
|
||||
<Fields>
|
||||
<Field name="name">
|
||||
{({ field }) => (
|
||||
<TextField errorMessage={touched.name && errors.name} placeholder={intl.get('your-name')} {...field} />
|
||||
)}
|
||||
</Field>
|
||||
<Field name="email" css={{ gridRow: 2 }}>
|
||||
{({ field }) => (
|
||||
<TextField errorMessage={touched.email && errors.email} placeholder={intl.get("your-email")} {...field} />
|
||||
)}
|
||||
</Field>
|
||||
</Fields>
|
||||
<SButton type="submit" disabled={!isValid || loading}>
|
||||
{loading ? <Spinner /> : intl.get("sign-up")}
|
||||
</SButton>
|
||||
<GoToLogin to="/login">
|
||||
{
|
||||
intl.get('already-have-account-login')
|
||||
}
|
||||
</GoToLogin>
|
||||
</SForm>
|
||||
)}
|
||||
</SignupFormik>
|
||||
)}
|
||||
</Root>
|
||||
)
|
||||
}
|
||||
|
||||
const Root = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
`
|
||||
|
||||
const LogoHolder = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
margin: 20px;
|
||||
`
|
||||
|
||||
const SForm = styled(Form)`
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 240px 1fr;
|
||||
max-width: 640px;
|
||||
grid-template-columns:
|
||||
[full-start] auto
|
||||
[center-start] 200px
|
||||
[center-end] auto
|
||||
[full-end];
|
||||
grid-template-rows:
|
||||
[title-start] 120px
|
||||
[title-end fields-start] 120px
|
||||
[fields-end button-start] 32px
|
||||
[button-end bottom-start] 48px
|
||||
[bottom-end]
|
||||
;
|
||||
`
|
||||
const Title = styled.div`
|
||||
grid-column: full;
|
||||
grid-row: title;
|
||||
`
|
||||
const Fields = styled.div`
|
||||
grid-column: full;
|
||||
grid-row: fields;
|
||||
> *:not(:first-child) {
|
||||
margin-top: 24px;
|
||||
}
|
||||
`
|
||||
const SButton = styled(Button)`
|
||||
grid-row: button;
|
||||
grid-column: center;
|
||||
`
|
||||
|
||||
const STextField = styled(TextField)`
|
||||
width: 240px;
|
||||
const GoToLogin = styled(Link)`
|
||||
grid-row: bottom;
|
||||
grid-column: center;
|
||||
font-size: 12px;
|
||||
margin-top: 4px;
|
||||
`
|
||||
|
||||
export default Signup
|
||||
export default hot(module)(Signup)
|
||||
|
|
|
@ -11,7 +11,7 @@ const Footer = (props: Props) => (
|
|||
)
|
||||
|
||||
const Root = styled.div`
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
background: #FFAD00;
|
||||
`
|
||||
|
||||
export default Footer
|
||||
|
|
|
@ -43,7 +43,7 @@ const Title = styled.div`
|
|||
flex: 1;
|
||||
`
|
||||
const Root = styled.div`
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
background: #5E8036;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-right: 14px;
|
||||
|
|
BIN
packages/qpa/App/LOGO.png
Normal file
BIN
packages/qpa/App/LOGO.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -2,26 +2,39 @@ import styled from "@emotion/styled"
|
|||
import addMonths from "date-fns/add_months"
|
||||
import endOfMonth from "date-fns/end_of_month"
|
||||
import startOfMonth from "date-fns/start_of_month"
|
||||
import {PrimaryButton} from "qpa-components"
|
||||
import { PrimaryButton } from "qpa-components"
|
||||
import * as React from "react"
|
||||
import {hot} from "react-hot-loader"
|
||||
import {RouteComponentProps, withRouter} from "react-router"
|
||||
import { hot } from "react-hot-loader"
|
||||
import { RouteComponentProps, withRouter } from "react-router"
|
||||
import RangedCalendar from "./RangedCalendar"
|
||||
|
||||
interface Props extends RouteComponentProps<{month?: string}> {
|
||||
interface Props extends RouteComponentProps<{ month?: string }> {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const now = new Date()
|
||||
const MONTH_NAMES = ["january", "february", "march", "april", "may", "june",
|
||||
"july", "august", "september", "october", "november", "december"]
|
||||
const MONTH_NAMES = [
|
||||
"january",
|
||||
"february",
|
||||
"march",
|
||||
"april",
|
||||
"may",
|
||||
"june",
|
||||
"july",
|
||||
"august",
|
||||
"september",
|
||||
"october",
|
||||
"november",
|
||||
"december",
|
||||
]
|
||||
|
||||
const Calendar = (props: Props) => {
|
||||
const month = props.match.params.month
|
||||
|
||||
const currentDateOfMonth = (month && MONTH_NAMES.includes(month)) ? (
|
||||
now.setMonth(MONTH_NAMES.indexOf(month))
|
||||
) : now
|
||||
const currentDateOfMonth =
|
||||
month && MONTH_NAMES.includes(month)
|
||||
? now.setMonth(MONTH_NAMES.indexOf(month))
|
||||
: now
|
||||
|
||||
const from = startOfMonth(currentDateOfMonth)
|
||||
const to = endOfMonth(currentDateOfMonth)
|
||||
|
@ -34,19 +47,29 @@ const Calendar = (props: Props) => {
|
|||
return (
|
||||
<Root>
|
||||
<Controls>
|
||||
<PrimaryButton title="Previous"
|
||||
onClick={() => {
|
||||
props.history.push(`/${monthBeforeName}`)
|
||||
}}>{"<"}{monthBeforeName}</PrimaryButton>
|
||||
<ThisMonth>{ MONTH_NAMES[from.getMonth()] }</ThisMonth>
|
||||
<PrimaryButton title="Next" onClick={() => {
|
||||
props.history.push(`/${monthAfterName}`)
|
||||
}}>{monthAfterName}{">"}</PrimaryButton>
|
||||
<PrimaryButton
|
||||
title="Previous"
|
||||
onClick={() => {
|
||||
props.history.push(`/${monthBeforeName}`)
|
||||
}}
|
||||
>
|
||||
{"<"}
|
||||
{monthBeforeName}
|
||||
</PrimaryButton>
|
||||
<ThisMonth>{MONTH_NAMES[from.getMonth()]}</ThisMonth>
|
||||
<PrimaryButton
|
||||
title="Next"
|
||||
onClick={() => {
|
||||
props.history.push(`/${monthAfterName}`)
|
||||
}}
|
||||
>
|
||||
{monthAfterName}
|
||||
{">"}
|
||||
</PrimaryButton>
|
||||
</Controls>
|
||||
<RangedCalendar from={from} to={to} className={props.className}/>
|
||||
<RangedCalendar from={from} to={to} className={props.className} />
|
||||
</Root>
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
const Controls = styled.div`
|
||||
|
@ -56,16 +79,16 @@ const Controls = styled.div`
|
|||
|
||||
const ThisMonth = styled.div`
|
||||
font-weight: 600;
|
||||
|
||||
color: rgba(0,0,0,.6);
|
||||
|
||||
font-size: 24px;
|
||||
color: rgba(0, 0, 0, 0.6);
|
||||
font-size: 24px;
|
||||
`
|
||||
|
||||
const Root = styled.div`
|
||||
min-width: 480px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
${Controls} {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ const config: webpack.Configuration = {
|
|||
},
|
||||
},
|
||||
{
|
||||
test: /\.(woff|woff2)$/i,
|
||||
test: /\.(woff|woff2|png)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: "url-loader",
|
||||
|
|
Loading…
Reference in a new issue