mirror of
https://github.com/rjNemo/React-SaaS-sample
synced 2026-06-06 05:06:38 +00:00
First
This commit is contained in:
parent
17d6218804
commit
fee97cf1fd
129 changed files with 10080 additions and 682 deletions
10
README.md
10
README.md
|
|
@ -6,23 +6,23 @@ In the project directory, you can run:
|
|||
|
||||
### `npm start`
|
||||
|
||||
Runs the app in the development mode.<br />
|
||||
Runs the app in the development mode.<br>
|
||||
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
|
||||
|
||||
The page will reload if you make edits.<br />
|
||||
The page will reload if you make edits.<br>
|
||||
You will also see any lint errors in the console.
|
||||
|
||||
### `npm test`
|
||||
|
||||
Launches the test runner in the interactive watch mode.<br />
|
||||
Launches the test runner in the interactive watch mode.<br>
|
||||
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
||||
|
||||
### `npm run build`
|
||||
|
||||
Builds the app for production to the `build` folder.<br />
|
||||
Builds the app for production to the `build` folder.<br>
|
||||
It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||
|
||||
The build is minified and the filenames include the hashes.<br />
|
||||
The build is minified and the filenames include the hashes.<br>
|
||||
Your app is ready to be deployed!
|
||||
|
||||
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
|
||||
|
|
|
|||
4551
divjoy-project.json
Normal file
4551
divjoy-project.json
Normal file
File diff suppressed because it is too large
Load diff
3329
package-lock.json
generated
3329
package-lock.json
generated
File diff suppressed because it is too large
Load diff
27
package.json
27
package.json
|
|
@ -1,11 +1,27 @@
|
|||
{
|
||||
"name": "sandbox",
|
||||
"name": "my-divjoy-project",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"keywords": [
|
||||
"divjoy"
|
||||
],
|
||||
"dependencies": {
|
||||
"react": "^16.10.2",
|
||||
"react-dom": "^16.10.2",
|
||||
"react-scripts": "3.2.0"
|
||||
"@analytics/google-analytics": "0.2.0",
|
||||
"analytics": "0.2.2",
|
||||
"aws-sdk": "2.533.0",
|
||||
"body-parser": "1.19.0",
|
||||
"bulma": "0.7.5",
|
||||
"css-loader": "^3.2.0",
|
||||
"express": "4.17.1",
|
||||
"firebase": "6.6.2",
|
||||
"mailchimp-api-v3": "1.13.1",
|
||||
"node-sass": "^4.12.0",
|
||||
"query-string": "6.8.3",
|
||||
"react": "16.9.0",
|
||||
"react-dom": "16.9.0",
|
||||
"react-router-dom": "5.1.0",
|
||||
"react-scripts": "3.1.2",
|
||||
"resolve-url-loader": "^3.1.0"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
|
|
@ -27,5 +43,6 @@
|
|||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
},
|
||||
"proxy": "http://localhost:8080"
|
||||
}
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 3.8 KiB |
|
|
@ -2,19 +2,19 @@
|
|||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
content="Web site created using create-react-app"
|
||||
/>
|
||||
<link rel="apple-touch-icon" href="logo192.png" />
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is installed on a
|
||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
||||
<!-- Font Awesome icons -->
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.10.1/css/all.css"
|
||||
/>
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 8.4 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 22 KiB |
|
|
@ -6,20 +6,10 @@
|
|||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
22
src/App.css
22
src/App.css
|
|
@ -1,22 +0,0 @@
|
|||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.App-logo {
|
||||
height: 40vmin;
|
||||
}
|
||||
|
||||
.App-header {
|
||||
background-color: #282c34;
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: calc(10px + 2vmin);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.App-link {
|
||||
color: #09d3ac;
|
||||
}
|
||||
26
src/App.js
26
src/App.js
|
|
@ -1,26 +0,0 @@
|
|||
import React from 'react';
|
||||
import logo from './logo.svg';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<div className="App">
|
||||
<header className="App-header">
|
||||
<img src={logo} className="App-logo" alt="logo" />
|
||||
<p>
|
||||
Edit <code>src/App.js</code> and save to reload.
|
||||
</p>
|
||||
<a
|
||||
className="App-link"
|
||||
href="https://reactjs.org"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Learn React
|
||||
</a>
|
||||
</header>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
|
||||
it('renders without crashing', () => {
|
||||
const div = document.createElement('div');
|
||||
ReactDOM.render(<App />, div);
|
||||
ReactDOM.unmountComponentAtNode(div);
|
||||
});
|
||||
165
src/components/Auth/index.js
Normal file
165
src/components/Auth/index.js
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
import React, { useState } from "react";
|
||||
import FormStatus from "./../FormStatus";
|
||||
import FormField from "./../FormField";
|
||||
import SectionButton from "./../SectionButton";
|
||||
import { Link } from "./../../util/router.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function Auth(props) {
|
||||
// State for all inputs
|
||||
const [email, setEmail] = useState("");
|
||||
const [pass, setPass] = useState("");
|
||||
const [confirmPass, setConfirmPass] = useState("");
|
||||
|
||||
// Whether to show errors
|
||||
// We set to true if they submit and there are errors.
|
||||
// We only show errors after they submit because
|
||||
// it's annoying to see errors while typing.
|
||||
const [showErrors, setShowErrors] = useState(false);
|
||||
|
||||
// Error array we'll populate
|
||||
let errors = [];
|
||||
|
||||
// Function for fetching error for a field
|
||||
const getError = field => {
|
||||
return errors.find(e => e.field === field);
|
||||
};
|
||||
|
||||
// Function to see if field is empty
|
||||
const isEmpty = val => val.trim() === "";
|
||||
|
||||
// Add error if email empty
|
||||
if (["signin", "signup", "forgotpass"].includes(props.mode)) {
|
||||
if (isEmpty(email)) {
|
||||
errors.push({
|
||||
field: "email",
|
||||
message: "Please enter an email"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add error if password empty
|
||||
if (["signin", "signup", "changepass"].includes(props.mode)) {
|
||||
if (isEmpty(pass)) {
|
||||
errors.push({
|
||||
field: "pass",
|
||||
message: "Please enter a password"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Add error if confirmPass empty or
|
||||
// if it doesn't match pass.
|
||||
// Only for signup and changepass views.
|
||||
if (["signup", "changepass"].includes(props.mode)) {
|
||||
if (isEmpty(confirmPass)) {
|
||||
errors.push({
|
||||
field: "confirmPass",
|
||||
message: "Please confirm password"
|
||||
});
|
||||
} else if (pass !== confirmPass) {
|
||||
errors.push({
|
||||
field: "confirmPass",
|
||||
message: `This doesn't match your password`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
const handleSubmit = () => {
|
||||
// If field errors then show them
|
||||
if (errors.length) {
|
||||
setShowErrors(true);
|
||||
} else {
|
||||
// Otherwise call onSubmit with email/pass
|
||||
if (props.onSubmit) {
|
||||
props.onSubmit({
|
||||
email,
|
||||
pass
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="Auth">
|
||||
{props.status && props.status.message && (
|
||||
<FormStatus type={props.status.type} message={props.status.message} />
|
||||
)}
|
||||
|
||||
<form
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
{["signup", "signin", "forgotpass"].includes(props.mode) && (
|
||||
<FormField
|
||||
value={email}
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
error={showErrors && getError("email")}
|
||||
onChange={value => setEmail(value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{["signup", "signin", "changepass"].includes(props.mode) && (
|
||||
<FormField
|
||||
value={pass}
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
error={showErrors && getError("pass")}
|
||||
onChange={value => setPass(value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{["signup", "changepass"].includes(props.mode) && (
|
||||
<FormField
|
||||
value={confirmPass}
|
||||
type="password"
|
||||
placeholder="Confirm Password"
|
||||
error={showErrors && getError("confirmPass")}
|
||||
onChange={value => setConfirmPass(value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div className="field">
|
||||
<p className="control ">
|
||||
<SectionButton
|
||||
parentColor={props.parentColor}
|
||||
size="medium"
|
||||
fullWidth={true}
|
||||
state={
|
||||
props.status && props.status.type === "pending"
|
||||
? "loading"
|
||||
: "normal"
|
||||
}
|
||||
>
|
||||
{props.buttonText}
|
||||
</SectionButton>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{["signup", "signin"].includes(props.mode) && (
|
||||
<div className="Auth__bottom-link has-text-centered">
|
||||
{props.mode === "signup" && (
|
||||
<>
|
||||
Have an account already?
|
||||
<Link to="/signin">Sign in</Link>
|
||||
</>
|
||||
)}
|
||||
|
||||
{props.mode === "signin" && (
|
||||
<>
|
||||
<Link to="/signup">Create an account</Link>
|
||||
<Link to="/forgotpass">Forgot password</Link>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Auth;
|
||||
13
src/components/Auth/styles.scss
Normal file
13
src/components/Auth/styles.scss
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.Auth {
|
||||
width: 100%;
|
||||
max-width: 450px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.Auth__bottom-link {
|
||||
margin-top: 1rem;
|
||||
font-size: 0.9rem;
|
||||
a {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
14
src/components/Avatar/index.js
Normal file
14
src/components/Avatar/index.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function Avatar(props) {
|
||||
const { image, size, alt, ...otherProps } = props;
|
||||
|
||||
return (
|
||||
<figure className={"image" + (size ? ` is-${size}x${size}` : "")}>
|
||||
<img className="is-rounded" src={image} alt={alt} {...otherProps} />
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
|
||||
export default Avatar;
|
||||
0
src/components/Avatar/styles.scss
Normal file
0
src/components/Avatar/styles.scss
Normal file
16
src/components/BackgroundImage/index.js
Normal file
16
src/components/BackgroundImage/index.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function BackgroundImage(props) {
|
||||
return (
|
||||
<div
|
||||
className="BackgroundImage"
|
||||
style={{
|
||||
"--image": `url(${props.image})`,
|
||||
"--opacity": props.opacity
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default BackgroundImage;
|
||||
13
src/components/BackgroundImage/styles.scss
Normal file
13
src/components/BackgroundImage/styles.scss
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
.BackgroundImage {
|
||||
content: "";
|
||||
background-image: var(--image);
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
opacity: var(--opacity);
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
z-index: 0;
|
||||
}
|
||||
12
src/components/CenteredColumns/index.js
Normal file
12
src/components/CenteredColumns/index.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function CenteredColumns(props) {
|
||||
return (
|
||||
<div className="columns is-centered is-variable is-4 is-multiline">
|
||||
{props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CenteredColumns;
|
||||
0
src/components/CenteredColumns/styles.scss
Normal file
0
src/components/CenteredColumns/styles.scss
Normal file
39
src/components/ChangePass/index.js
Normal file
39
src/components/ChangePass/index.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import React, { useState } from "react";
|
||||
import Auth from "./../Auth";
|
||||
import { useAuth } from "./../../util/auth.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function ChangePass(props) {
|
||||
const auth = useAuth();
|
||||
const [status, setStatus] = useState();
|
||||
|
||||
const onSubmit = ({ pass }) => {
|
||||
setStatus({ type: "pending" });
|
||||
auth
|
||||
.confirmPasswordReset(pass)
|
||||
.then(() => {
|
||||
setStatus({
|
||||
type: "success",
|
||||
message: "Your password has been changed"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
setStatus({
|
||||
type: "error",
|
||||
message: error.message
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Auth
|
||||
mode="changepass"
|
||||
buttonText={props.buttonText}
|
||||
parentColor={props.parentColor}
|
||||
onSubmit={onSubmit}
|
||||
status={status}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChangePass;
|
||||
0
src/components/ChangePass/styles.scss
Normal file
0
src/components/ChangePass/styles.scss
Normal file
23
src/components/ChangePassSection/index.js
Normal file
23
src/components/ChangePassSection/index.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import ChangePass from "./../ChangePass";
|
||||
import "./styles.scss";
|
||||
|
||||
function ChangePassSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<ChangePass buttonText={props.buttonText} parentColor={props.color} />
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default ChangePassSection;
|
||||
0
src/components/ChangePassSection/styles.scss
Normal file
0
src/components/ChangePassSection/styles.scss
Normal file
18
src/components/Clients/index.js
Normal file
18
src/components/Clients/index.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function Clients(props) {
|
||||
return (
|
||||
<div className="columns is-centered is-multiline">
|
||||
{props.items.map((item, index) => (
|
||||
<div className="column is-narrow has-text-centered" key={index}>
|
||||
<div className="Clients__logo">
|
||||
<img src={item.image} width={item.width} alt={item.name} />
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Clients;
|
||||
7
src/components/Clients/styles.scss
Normal file
7
src/components/Clients/styles.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.Clients__logo {
|
||||
margin: 0 12px;
|
||||
img {
|
||||
// Removes extra space under image
|
||||
vertical-align: bottom;
|
||||
}
|
||||
}
|
||||
46
src/components/ClientsSection/index.js
Normal file
46
src/components/ClientsSection/index.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import Clients from "./../Clients";
|
||||
import "./styles.scss";
|
||||
|
||||
function ClientsSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<Clients
|
||||
items={[
|
||||
{
|
||||
name: "Instagram",
|
||||
image: "https://uploads.divjoy.com/logo-instagram.svg",
|
||||
width: "150px"
|
||||
},
|
||||
{
|
||||
name: "Slack",
|
||||
image: "https://uploads.divjoy.com/logo-slack.svg",
|
||||
width: "135px"
|
||||
},
|
||||
{
|
||||
name: "Tinder",
|
||||
image: "https://uploads.divjoy.com/logo-tinder.svg",
|
||||
width: "90px"
|
||||
},
|
||||
{
|
||||
name: "Spotify",
|
||||
image: "https://uploads.divjoy.com/logo-spotify.svg",
|
||||
width: "135px"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default ClientsSection;
|
||||
0
src/components/ClientsSection/styles.scss
Normal file
0
src/components/ClientsSection/styles.scss
Normal file
30
src/components/Contact/index.js
Normal file
30
src/components/Contact/index.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import React, { useState } from "react";
|
||||
import ContactForm from "./../ContactForm";
|
||||
import contact from "./../../util/contact.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function Contact(props) {
|
||||
const [status, setStatus] = useState();
|
||||
|
||||
const onSubmit = ({ name, email, message }) => {
|
||||
setStatus({ type: "pending" });
|
||||
|
||||
contact.submit({ name, email, message }).then(() => {
|
||||
setStatus({
|
||||
type: "success",
|
||||
message: "Your message has been sent! We'll get back to you soon."
|
||||
});
|
||||
});
|
||||
};
|
||||
return (
|
||||
<ContactForm
|
||||
parentColor={props.parentColor}
|
||||
showNameField={props.showNameField}
|
||||
buttonText={props.buttonText}
|
||||
onSubmit={onSubmit}
|
||||
status={status}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default Contact;
|
||||
0
src/components/Contact/styles.scss
Normal file
0
src/components/Contact/styles.scss
Normal file
141
src/components/ContactForm/index.js
Normal file
141
src/components/ContactForm/index.js
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
import React, { useState } from "react";
|
||||
import FormStatus from "./../FormStatus";
|
||||
import FormField from "./../FormField";
|
||||
import SectionButton from "./../SectionButton";
|
||||
import "./styles.scss";
|
||||
|
||||
function ContactForm(props) {
|
||||
// State for input values
|
||||
const [name, setName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [message, setMessage] = useState("");
|
||||
|
||||
// Whether to show errors
|
||||
// We set to true if they submit and there are errors.
|
||||
// We only show errors after they submit because
|
||||
// it's annoying to see errors while typing.
|
||||
const [showErrors, setShowErrors] = useState(false);
|
||||
|
||||
// Error array we'll populate
|
||||
let errors = [];
|
||||
|
||||
// Function for fetching error for a field
|
||||
const getError = field => {
|
||||
return errors.find(e => e.field === field);
|
||||
};
|
||||
|
||||
// Function to see if field is empty
|
||||
const isEmpty = val => val.trim() === "";
|
||||
|
||||
// Add error if email empty
|
||||
if (isEmpty(email)) {
|
||||
errors.push({
|
||||
field: "email",
|
||||
message: "Please enter an email"
|
||||
});
|
||||
}
|
||||
|
||||
// Add error if message empty
|
||||
if (isEmpty(message)) {
|
||||
errors.push({
|
||||
field: "message",
|
||||
message: "Please enter a message"
|
||||
});
|
||||
}
|
||||
|
||||
// Add error if name shown and empty
|
||||
if (props.showNameField) {
|
||||
if (isEmpty(name)) {
|
||||
errors.push({
|
||||
field: "name",
|
||||
message: "Please enter your name"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle form submission
|
||||
const handleSubmit = e => {
|
||||
// If field errors then show them
|
||||
if (errors.length) {
|
||||
setShowErrors(true);
|
||||
} else {
|
||||
// Otherwise call onSubmit with form data
|
||||
if (props.onSubmit) {
|
||||
props.onSubmit({
|
||||
name,
|
||||
email,
|
||||
message
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.status && props.status.message && (
|
||||
<FormStatus type={props.status.type} message={props.status.message} />
|
||||
)}
|
||||
|
||||
<form
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
<div className="field is-horizontal">
|
||||
<div className="field-body">
|
||||
{props.showNameField && (
|
||||
<FormField
|
||||
value={name}
|
||||
type="text"
|
||||
placeholder="Name"
|
||||
error={showErrors && getError("name")}
|
||||
onChange={value => setName(value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
<FormField
|
||||
value={email}
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
error={showErrors && getError("email")}
|
||||
onChange={value => setEmail(value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field is-horizontal">
|
||||
<div className="field-body">
|
||||
<FormField
|
||||
value={message}
|
||||
type="textarea"
|
||||
placeholder="Message"
|
||||
error={showErrors && getError("message")}
|
||||
onChange={value => setMessage(value)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="field is-horizontal">
|
||||
<div className="field-body">
|
||||
<div className="field">
|
||||
<div className="control">
|
||||
<SectionButton
|
||||
parentColor={props.parentColor}
|
||||
size="medium"
|
||||
state={
|
||||
props.status && props.status.type === "pending"
|
||||
? "loading"
|
||||
: "normal"
|
||||
}
|
||||
>
|
||||
{props.buttonText}
|
||||
</SectionButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContactForm;
|
||||
0
src/components/ContactForm/styles.scss
Normal file
0
src/components/ContactForm/styles.scss
Normal file
27
src/components/ContactSection/index.js
Normal file
27
src/components/ContactSection/index.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import Contact from "./../Contact";
|
||||
import "./styles.scss";
|
||||
|
||||
function ContactSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="ContactSection__container container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<Contact
|
||||
parentColor={props.color}
|
||||
showNameField={props.showNameField}
|
||||
buttonText={props.buttonText}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContactSection;
|
||||
3
src/components/ContactSection/styles.scss
Normal file
3
src/components/ContactSection/styles.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.ContactSection__container {
|
||||
max-width: 850px;
|
||||
}
|
||||
26
src/components/ContentSection/index.js
Normal file
26
src/components/ContentSection/index.js
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import "./styles.scss";
|
||||
|
||||
function ContentSection(props) {
|
||||
return (
|
||||
<Section
|
||||
color={props.color}
|
||||
size={props.size}
|
||||
backgroundImage={props.backgroundImage}
|
||||
backgroundImageOpacity={props.backgroundImageOpacity}
|
||||
>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={2}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default ContentSection;
|
||||
0
src/components/ContentSection/styles.scss
Normal file
0
src/components/ContentSection/styles.scss
Normal file
21
src/components/DashboardSection/index.js
Normal file
21
src/components/DashboardSection/index.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import "./styles.scss";
|
||||
|
||||
function DashboardSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default DashboardSection;
|
||||
0
src/components/DashboardSection/styles.scss
Normal file
0
src/components/DashboardSection/styles.scss
Normal file
15
src/components/Faq/index.js
Normal file
15
src/components/Faq/index.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import React from "react";
|
||||
import FaqItem from "./../FaqItem";
|
||||
import "./styles.scss";
|
||||
|
||||
function Faq(props) {
|
||||
return (
|
||||
<>
|
||||
{props.items.map((item, index) => (
|
||||
<FaqItem question={item.question} answer={item.answer} key={index} />
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Faq;
|
||||
0
src/components/Faq/styles.scss
Normal file
0
src/components/Faq/styles.scss
Normal file
27
src/components/FaqItem/index.js
Normal file
27
src/components/FaqItem/index.js
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import React, { useState } from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function FaqItem(props) {
|
||||
const [expanded, setExpanded] = useState(false);
|
||||
|
||||
return (
|
||||
<article className="FaqItem" onClick={() => setExpanded(!expanded)}>
|
||||
<div className="title is-spaced is-4">
|
||||
<span className="FaqItem__icon icon is-size-5 has-text-primary">
|
||||
<i
|
||||
className={
|
||||
"fas" +
|
||||
(expanded ? " fa-minus" : "") +
|
||||
(!expanded ? " fa-plus" : "")
|
||||
}
|
||||
/>
|
||||
</span>
|
||||
{props.question}
|
||||
</div>
|
||||
|
||||
{expanded && <div className="subtitle">{props.answer}</div>}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
export default FaqItem;
|
||||
12
src/components/FaqItem/styles.scss
Normal file
12
src/components/FaqItem/styles.scss
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
.FaqItem {
|
||||
cursor: pointer;
|
||||
padding: 1.6rem 0;
|
||||
border-bottom: 1px solid #efefef;
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
|
||||
.FaqItem__icon {
|
||||
margin-right: 1rem;
|
||||
}
|
||||
51
src/components/FaqSection/index.js
Normal file
51
src/components/FaqSection/index.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import Faq from "./../Faq";
|
||||
import "./styles.scss";
|
||||
|
||||
function FaqSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<Faq
|
||||
items={[
|
||||
{
|
||||
question: "Integer ornare neque mauris?",
|
||||
answer:
|
||||
"Integer ornare neque mauris, ac vulputate lacus venenatis et. Pellentesque ut ultrices purus. Suspendisse ut tincidunt eros. In velit mi, rhoncus dictum neque a, tincidunt lobortis justo."
|
||||
},
|
||||
{
|
||||
question: "Lorem ipsum dolor sit amet?",
|
||||
answer:
|
||||
"Nunc nulla mauris, laoreet vel cursus lacinia, consectetur sit amet tellus. Suspendisse ut tincidunt eros. In velit mi, rhoncus dictum neque a, tincidunt lobortis justo."
|
||||
},
|
||||
{
|
||||
question: "Suspendisse ut tincidunt?",
|
||||
answer:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. In lobortis, metus et mattis ullamcorper. Suspendisse ut tincidunt eros. In velit mi, rhoncus dictum neque a, tincidunt lobortis justo."
|
||||
},
|
||||
{
|
||||
question: "Ut enim ad minim veniam?",
|
||||
answer:
|
||||
"Suspendisse ut tincidunt eros. In velit mi, rhoncus dictum neque a, tincidunt lobortis justo."
|
||||
},
|
||||
{
|
||||
question: "In velit mi, rhoncus dictum neque?",
|
||||
answer:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default FaqSection;
|
||||
0
src/components/FaqSection/styles.scss
Normal file
0
src/components/FaqSection/styles.scss
Normal file
29
src/components/Features/index.js
Normal file
29
src/components/Features/index.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function Features(props) {
|
||||
return (
|
||||
<div className="Features">
|
||||
{props.items.map((item, index) => (
|
||||
<div
|
||||
className="Features__columns columns is-variable is-8 is-vcentered has-text-centered-mobile"
|
||||
key={index}
|
||||
>
|
||||
<div className="column is-half">
|
||||
<h3 className="Features__title title has-text-weight-bold is-spaced is-3">
|
||||
{item.title}
|
||||
</h3>
|
||||
<p className="subtitle">{item.description}</p>
|
||||
</div>
|
||||
<div className="column">
|
||||
<figure className="Features__image image">
|
||||
<img src={item.image} alt={item.title} />
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Features;
|
||||
27
src/components/Features/styles.scss
Normal file
27
src/components/Features/styles.scss
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
.Features {
|
||||
max-width: 900px;
|
||||
margin: 80px auto 0 auto;
|
||||
}
|
||||
|
||||
.Features__columns {
|
||||
// Reverse every other row
|
||||
&:nth-of-type(even) {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
&:not(:last-of-type) {
|
||||
padding-bottom: 1.5rem;
|
||||
@media screen and (min-width: 769px) {
|
||||
padding-bottom: 2.5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Features__title {
|
||||
margin-bottom: 1.2rem !important;
|
||||
}
|
||||
|
||||
.Features__image {
|
||||
max-width: 300px;
|
||||
margin: 30px auto;
|
||||
}
|
||||
51
src/components/FeaturesSection/index.js
Normal file
51
src/components/FeaturesSection/index.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import Features from "./../Features";
|
||||
import "./styles.scss";
|
||||
|
||||
function FeaturesSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<Features
|
||||
items={[
|
||||
{
|
||||
title: "Automatisez des tâches →",
|
||||
description:
|
||||
"Avis d’échéances, suivi des encaissements, comptabilité locative, quittancement, …",
|
||||
image: "https://uploads.divjoy.com/undraw-mind_map_cwng.svg"
|
||||
},
|
||||
{
|
||||
title: "Protégez vos intérêts →",
|
||||
description:
|
||||
"Gestion des retards de paiement, révision du loyer, régularisation des charges, …",
|
||||
image:
|
||||
"https://uploads.divjoy.com/undraw-personal_settings_kihd.svg"
|
||||
},
|
||||
{
|
||||
title: "Soyez mieux accompagné →",
|
||||
description:
|
||||
"Edition du bail, assistance administrative et comptable, information juridique et fiscale, …",
|
||||
image: "https://uploads.divjoy.com/undraw-having_fun_iais.svg"
|
||||
},
|
||||
{
|
||||
title: "Restez organisé (à venir) →",
|
||||
description:
|
||||
"Assignation des dépenses, archivage des justificatifs, déclaration des revenus fonciers, …",
|
||||
image: "https://uploads.divjoy.com/undraw-balloons_vxx5.svg"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default FeaturesSection;
|
||||
0
src/components/FeaturesSection/styles.scss
Normal file
0
src/components/FeaturesSection/styles.scss
Normal file
73
src/components/Footer/index.js
Normal file
73
src/components/Footer/index.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import { Link } from "./../../util/router.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function Footer(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="FooterComponent__container container">
|
||||
<div className="brand left">
|
||||
<Link to="/">
|
||||
<img src={props.logo} alt="Logo" />
|
||||
</Link>
|
||||
</div>
|
||||
<div className="links right">
|
||||
<Link to="/about">À propos</Link>
|
||||
<Link to="/faq">FAQ</Link>
|
||||
<Link to="/contact">Contact</Link>
|
||||
</div>
|
||||
<div className="social right">
|
||||
<a
|
||||
href="https://linkedin.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="icon">
|
||||
<i className="fab fa-linkedin" />
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://twitter.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="icon">
|
||||
<i className="fab fa-twitter" />
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://facebook.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="icon">
|
||||
<i className="fab fa-facebook-f" />
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://instagram.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="icon">
|
||||
<i className="fab fa-instagram" />
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
href="https://medium.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<span className="icon">
|
||||
<i className="fab fa-medium" />
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
<div className="copyright left">{props.copyright}</div>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default Footer;
|
||||
57
src/components/Footer/styles.scss
Normal file
57
src/components/Footer/styles.scss
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
.FooterComponent__container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
> div {
|
||||
display: flex;
|
||||
flex: none;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
img {
|
||||
display: block;
|
||||
height: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
.social {
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.social,
|
||||
.links {
|
||||
a {
|
||||
color: inherit;
|
||||
&:hover {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
&:not(:first-of-type) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tablet and up
|
||||
@media screen and (min-width: 769px) {
|
||||
> div {
|
||||
flex: 50%;
|
||||
}
|
||||
|
||||
.left {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
// Move links to end (bottom right)
|
||||
// Swaps position with social
|
||||
.links {
|
||||
order: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
39
src/components/ForgotPass/index.js
Normal file
39
src/components/ForgotPass/index.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import React, { useState } from "react";
|
||||
import Auth from "./../Auth";
|
||||
import { useAuth } from "./../../util/auth.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function ForgotPass(props) {
|
||||
const auth = useAuth();
|
||||
const [status, setStatus] = useState();
|
||||
|
||||
const onSubmit = ({ email }) => {
|
||||
setStatus({ type: "pending" });
|
||||
auth
|
||||
.sendPasswordResetEmail(email)
|
||||
.then(() => {
|
||||
setStatus({
|
||||
type: "success",
|
||||
message: "Password reset email sent"
|
||||
});
|
||||
})
|
||||
.catch(error => {
|
||||
setStatus({
|
||||
type: "error",
|
||||
message: error.message
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Auth
|
||||
mode="forgotpass"
|
||||
buttonText={props.buttonText}
|
||||
parentColor={props.parentColor}
|
||||
onSubmit={onSubmit}
|
||||
status={status}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgotPass;
|
||||
0
src/components/ForgotPass/styles.scss
Normal file
0
src/components/ForgotPass/styles.scss
Normal file
23
src/components/ForgotPassSection/index.js
Normal file
23
src/components/ForgotPassSection/index.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import ForgotPass from "./../ForgotPass";
|
||||
import "./styles.scss";
|
||||
|
||||
function ForgotPassSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<ForgotPass buttonText={props.buttonText} parentColor={props.color} />
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default ForgotPassSection;
|
||||
0
src/components/ForgotPassSection/styles.scss
Normal file
0
src/components/ForgotPassSection/styles.scss
Normal file
34
src/components/FormField/index.js
Normal file
34
src/components/FormField/index.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function FormField(props) {
|
||||
return (
|
||||
<div className="field">
|
||||
<div className="control">
|
||||
{props.type === "textarea" && (
|
||||
<textarea
|
||||
className="textarea is-medium"
|
||||
type={props.type}
|
||||
value={props.value}
|
||||
placeholder={props.placeholder}
|
||||
onChange={e => props.onChange(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.type !== "textarea" && (
|
||||
<input
|
||||
className="input is-medium"
|
||||
type={props.type}
|
||||
value={props.value}
|
||||
placeholder={props.placeholder}
|
||||
onChange={e => props.onChange(e.target.value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{props.error && <p className="help is-danger">{props.error.message}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FormField;
|
||||
0
src/components/FormField/styles.scss
Normal file
0
src/components/FormField/styles.scss
Normal file
18
src/components/FormStatus/index.js
Normal file
18
src/components/FormStatus/index.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function FormStatus(props) {
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
"notification" +
|
||||
(props.type === "error" ? " is-danger" : "") +
|
||||
(props.type === "success" ? " is-success" : "")
|
||||
}
|
||||
>
|
||||
{props.message}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default FormStatus;
|
||||
0
src/components/FormStatus/styles.scss
Normal file
0
src/components/FormStatus/styles.scss
Normal file
38
src/components/HeroSection/index.js
Normal file
38
src/components/HeroSection/index.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import SectionButton from "./../SectionButton";
|
||||
import "./styles.scss";
|
||||
|
||||
function HeroSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<div className="columns is-vcentered is-desktop">
|
||||
<div className="column is-5-desktop has-text-centered-touch">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
size={1}
|
||||
/>
|
||||
<SectionButton
|
||||
parentColor={props.color}
|
||||
size="medium"
|
||||
onClick={props.buttonOnClick}
|
||||
>
|
||||
{props.buttonText}
|
||||
</SectionButton>
|
||||
</div>
|
||||
<div className="column is-1" />
|
||||
<div className="column">
|
||||
<figure className="HeroSection__image image">
|
||||
<img src={props.image} alt="Illustration" />
|
||||
</figure>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default HeroSection;
|
||||
4
src/components/HeroSection/styles.scss
Normal file
4
src/components/HeroSection/styles.scss
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
.HeroSection__image {
|
||||
margin: 0 auto;
|
||||
max-width: 570px;
|
||||
}
|
||||
BIN
src/components/Navbar/PMS_logo.png
Normal file
BIN
src/components/Navbar/PMS_logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 60 KiB |
66
src/components/Navbar/index.js
Normal file
66
src/components/Navbar/index.js
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
import React, { useState } from "react";
|
||||
import NavbarContainer from "./../NavbarContainer";
|
||||
import { Link } from "./../../util/router.js";
|
||||
import { useAuth } from "./../../util/auth.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function Navbar(props) {
|
||||
const auth = useAuth();
|
||||
const [menuOpen, setMenuOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<NavbarContainer spaced={props.spaced} color={props.color}>
|
||||
<div className="container">
|
||||
<div className="navbar-brand">
|
||||
<div className="navbar-item">
|
||||
<Link to="/">
|
||||
<img className="image" src={props.logo} alt="Logo" />
|
||||
</Link>
|
||||
</div>
|
||||
<div
|
||||
className={"navbar-burger burger" + (menuOpen ? " is-active" : "")}
|
||||
onClick={() => setMenuOpen(!menuOpen)}
|
||||
>
|
||||
<span />
|
||||
<span />
|
||||
<span />
|
||||
</div>
|
||||
</div>
|
||||
<div className={"navbar-menu" + (menuOpen ? " is-active" : "")}>
|
||||
<div className="navbar-end">
|
||||
{auth.user && (
|
||||
<div className="navbar-item has-dropdown is-hoverable">
|
||||
<Link className="navbar-link" to="/">
|
||||
Account
|
||||
</Link>
|
||||
<div className="navbar-dropdown is-boxed">
|
||||
<Link className="navbar-item" to="/dashboard">
|
||||
Dashboard
|
||||
</Link>
|
||||
<Link
|
||||
className="navbar-item"
|
||||
to="/signout"
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
auth.signout();
|
||||
}}
|
||||
>
|
||||
Sign out
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!auth.user && (
|
||||
<Link className="navbar-item" to="/signin">
|
||||
Sign in
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</NavbarContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Navbar;
|
||||
0
src/components/Navbar/styles.scss
Normal file
0
src/components/Navbar/styles.scss
Normal file
18
src/components/NavbarContainer/index.js
Normal file
18
src/components/NavbarContainer/index.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function NavbarContainer(props) {
|
||||
return (
|
||||
<nav
|
||||
className={
|
||||
"navbar" +
|
||||
(props.color ? ` is-${props.color}` : "") +
|
||||
(props.spaced ? " is-spaced" : "")
|
||||
}
|
||||
>
|
||||
{props.children}
|
||||
</nav>
|
||||
);
|
||||
}
|
||||
|
||||
export default NavbarContainer;
|
||||
0
src/components/NavbarContainer/styles.scss
Normal file
0
src/components/NavbarContainer/styles.scss
Normal file
57
src/components/Newsletter/index.js
Normal file
57
src/components/Newsletter/index.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
import React, { useState } from "react";
|
||||
import SectionButton from "./../SectionButton";
|
||||
import newsletter from "./../../util/newsletter.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function Newsletter(props) {
|
||||
const [email, setEmail] = useState("");
|
||||
const [subscribed, setSubscribed] = useState(false);
|
||||
|
||||
const handleSubmit = () => {
|
||||
if (email) {
|
||||
setSubscribed(true);
|
||||
// Parent component can optionally
|
||||
// find out when subscribed.
|
||||
props.onSubscribed && props.onSubscribed();
|
||||
// Subscribe them
|
||||
newsletter.subscribe({ email });
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{subscribed === false && (
|
||||
<form
|
||||
onSubmit={e => {
|
||||
e.preventDefault();
|
||||
handleSubmit();
|
||||
}}
|
||||
>
|
||||
<div className="field is-grouped">
|
||||
<div className="control is-expanded">
|
||||
<input
|
||||
className={`input is-${props.size}`}
|
||||
type="email"
|
||||
placeholder={props.inputPlaceholder}
|
||||
onChange={event => setEmail(event.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="control">
|
||||
<SectionButton
|
||||
parentColor={props.parentColor}
|
||||
size={props.size}
|
||||
onClick={props.buttonOnClick}
|
||||
>
|
||||
{props.buttonText}
|
||||
</SectionButton>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
|
||||
{subscribed === true && <>{props.subscribedMessage}</>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default Newsletter;
|
||||
0
src/components/Newsletter/styles.scss
Normal file
0
src/components/Newsletter/styles.scss
Normal file
34
src/components/NewsletterSection/index.js
Normal file
34
src/components/NewsletterSection/index.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import Newsletter from "./../Newsletter";
|
||||
import "./styles.scss";
|
||||
|
||||
function NewsletterSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<div className="columns is-centered">
|
||||
<div className="column is-12 is-10-fullhd">
|
||||
<div className="columns is-vcentered">
|
||||
<div className="column is-half">
|
||||
<div className="title">{props.title}</div>
|
||||
<div className="subtitle">{props.subtitle}</div>
|
||||
</div>
|
||||
<div className="column is-half">
|
||||
<Newsletter
|
||||
parentColor={props.color}
|
||||
buttonText={props.buttonText}
|
||||
inputPlaceholder={props.inputPlaceholder}
|
||||
subscribedMessage={props.subscribedMessage}
|
||||
size="medium"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default NewsletterSection;
|
||||
0
src/components/NewsletterSection/styles.scss
Normal file
0
src/components/NewsletterSection/styles.scss
Normal file
39
src/components/Pricing/index.js
Normal file
39
src/components/Pricing/index.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function Pricing(props) {
|
||||
return (
|
||||
<div className="columns is-centered is-variable is-5">
|
||||
{props.items.map((item, index) => (
|
||||
<div className="Pricing__column column" key={index}>
|
||||
<div
|
||||
className={
|
||||
"Pricing__card card" +
|
||||
(item.emphasized === true ? " emphasized" : "")
|
||||
}
|
||||
>
|
||||
<div className="Pricing__card-content card-content">
|
||||
<div className="Pricing__period has-text-weight-bold">
|
||||
{item.timespan}
|
||||
</div>
|
||||
<div className="Pricing__price has-text-weight-bold">
|
||||
<span className="Pricing__price-symbol is-size-3">$</span>
|
||||
<span className="is-size-1">{item.price}</span>
|
||||
<span className="Pricing__price-month is-size-4">/m</span>
|
||||
</div>
|
||||
<p className="Pricing__description">{item.description}</p>
|
||||
<button
|
||||
className="Pricing__button button is-medium is-primary"
|
||||
onClick={() => props.onChoosePlan(item.id)}
|
||||
>
|
||||
{props.buttonText}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Pricing;
|
||||
46
src/components/Pricing/styles.scss
Normal file
46
src/components/Pricing/styles.scss
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
.Pricing__column {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.Pricing__card {
|
||||
display: flex;
|
||||
// Stretch to fit column width
|
||||
width: 100%;
|
||||
// Ensure .card-content stretches to fit width
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.Pricing__card-content {
|
||||
// Flex so that button can position self at
|
||||
// bottom of card using margin-top auto.
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
// Stretch to fit column width
|
||||
width: 100%;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.Pricing__period {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.Pricing__price {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.Pricing__price-symbol {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.Pricing__price-month {
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.Pricing__description {
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.Pricing__button {
|
||||
margin-top: auto;
|
||||
}
|
||||
45
src/components/PricingSection/index.js
Normal file
45
src/components/PricingSection/index.js
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import Pricing from "./../Pricing";
|
||||
import "./styles.scss";
|
||||
|
||||
function PricingSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size} id="pricing">
|
||||
<div className="PricingSection__container container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<Pricing
|
||||
buttonText="Choose"
|
||||
onChoosePlan={planId => {
|
||||
// Add your own payments logic here
|
||||
alert(`You chose the plan "${planId}"`);
|
||||
}}
|
||||
items={[
|
||||
{
|
||||
id: "monthly",
|
||||
timespan: "Monthly",
|
||||
price: "29",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum consequatur numquam aliquam."
|
||||
},
|
||||
{
|
||||
id: "yearly",
|
||||
timespan: "Yearly",
|
||||
price: "19",
|
||||
description:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum consequatur numquam aliquam tenetur ad amet inventore hic beatae."
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default PricingSection;
|
||||
3
src/components/PricingSection/styles.scss
Normal file
3
src/components/PricingSection/styles.scss
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
.PricingSection__container {
|
||||
max-width: 800px;
|
||||
}
|
||||
37
src/components/Section/index.js
Normal file
37
src/components/Section/index.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import React from "react";
|
||||
import BackgroundImage from "./../BackgroundImage";
|
||||
import "./styles.scss";
|
||||
|
||||
function Section(props) {
|
||||
const {
|
||||
color,
|
||||
size,
|
||||
backgroundImage,
|
||||
backgroundImageOpacity,
|
||||
children,
|
||||
// Passed to section element
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<section
|
||||
className={
|
||||
"SectionComponent hero section is-block is-relative" +
|
||||
(color ? ` is-${color}` : "") +
|
||||
(size ? ` is-${size}` : "")
|
||||
}
|
||||
{...otherProps}
|
||||
>
|
||||
{backgroundImage && (
|
||||
<BackgroundImage
|
||||
image={backgroundImage}
|
||||
opacity={backgroundImageOpacity}
|
||||
/>
|
||||
)}
|
||||
|
||||
{props.children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default Section;
|
||||
7
src/components/Section/styles.scss
Normal file
7
src/components/Section/styles.scss
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
.SectionComponent {
|
||||
// Add light border if two white
|
||||
// sections next to each other.
|
||||
.is-white + &.is-white {
|
||||
border-top: 1px solid #F0F0F0;
|
||||
}
|
||||
}
|
||||
43
src/components/SectionButton/index.js
Normal file
43
src/components/SectionButton/index.js
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function SectionButton(props) {
|
||||
const {
|
||||
parentColor,
|
||||
size,
|
||||
state,
|
||||
fullWidth,
|
||||
// Passed to button element
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<button
|
||||
className={
|
||||
"button" +
|
||||
([
|
||||
"primary",
|
||||
"info",
|
||||
"success",
|
||||
"warning",
|
||||
"danger",
|
||||
"black",
|
||||
"dark"
|
||||
].includes(parentColor)
|
||||
? ` is-${parentColor} is-inverted`
|
||||
: "") +
|
||||
(["white", "light"].includes(parentColor) || !parentColor
|
||||
? " is-primary"
|
||||
: "") +
|
||||
(size ? ` is-${size}` : "") +
|
||||
(state ? ` is-${state}` : "") +
|
||||
(fullWidth ? " is-fullwidth" : "")
|
||||
}
|
||||
{...otherProps}
|
||||
>
|
||||
{props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
export default SectionButton;
|
||||
0
src/components/SectionButton/styles.scss
Normal file
0
src/components/SectionButton/styles.scss
Normal file
36
src/components/SectionHeader/index.js
Normal file
36
src/components/SectionHeader/index.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import React from "react";
|
||||
import "./styles.scss";
|
||||
|
||||
function SectionHeader(props) {
|
||||
return (
|
||||
<>
|
||||
{(props.title || props.subtitle) && (
|
||||
<header
|
||||
className={
|
||||
"SectionHeader__header" + (props.centered ? " is-centered" : "")
|
||||
}
|
||||
>
|
||||
{props.title && (
|
||||
<h1
|
||||
className={
|
||||
"title is-spaced has-text-weight-bold" +
|
||||
(props.size ? ` is-${props.size}` : "") +
|
||||
(props.size === 1 ? " is-size-2-mobile" : "")
|
||||
}
|
||||
>
|
||||
{props.title}
|
||||
</h1>
|
||||
)}
|
||||
|
||||
{props.subtitle && (
|
||||
<p className={"subtitle" + (props.size > 4 ? " is-6" : "")}>
|
||||
{props.subtitle}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SectionHeader;
|
||||
20
src/components/SectionHeader/styles.scss
Normal file
20
src/components/SectionHeader/styles.scss
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
.SectionHeader__header {
|
||||
margin-bottom: 3rem;
|
||||
|
||||
// Remove margin if nothing after header
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
// Added if props.centered is true
|
||||
&.is-centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
max-width: 700px;
|
||||
// So we can have max-width but still
|
||||
// have alignment controlled by text-align.
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
36
src/components/SignIn/index.js
Normal file
36
src/components/SignIn/index.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import React, { useState } from "react";
|
||||
import Auth from "./../Auth";
|
||||
import { useAuth } from "./../../util/auth.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function SignIn(props) {
|
||||
const auth = useAuth();
|
||||
const [status, setStatus] = useState();
|
||||
|
||||
const onSubmit = ({ email, pass }) => {
|
||||
setStatus({ type: "pending" });
|
||||
auth
|
||||
.signin(email, pass)
|
||||
.then(user => {
|
||||
props.onSignin && props.onSignin();
|
||||
})
|
||||
.catch(error => {
|
||||
setStatus({
|
||||
type: "error",
|
||||
message: error.message
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Auth
|
||||
mode="signin"
|
||||
buttonText={props.buttonText}
|
||||
parentColor={props.parentColor}
|
||||
onSubmit={onSubmit}
|
||||
status={status}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignIn;
|
||||
0
src/components/SignIn/styles.scss
Normal file
0
src/components/SignIn/styles.scss
Normal file
35
src/components/SignInSection/index.js
Normal file
35
src/components/SignInSection/index.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import SignIn from "./../SignIn";
|
||||
import { useRouter } from "./../../util/router.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function SignInSection(props) {
|
||||
const router = useRouter();
|
||||
|
||||
// Go to page after signin
|
||||
const onSignin = () => {
|
||||
router.push("/dashboard");
|
||||
};
|
||||
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<SignIn
|
||||
buttonText={props.buttonText}
|
||||
parentColor={props.color}
|
||||
onSignin={onSignin}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignInSection;
|
||||
0
src/components/SignInSection/styles.scss
Normal file
0
src/components/SignInSection/styles.scss
Normal file
36
src/components/SignUp/index.js
Normal file
36
src/components/SignUp/index.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import React, { useState } from "react";
|
||||
import Auth from "./../Auth";
|
||||
import { useAuth } from "./../../util/auth.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function SignUp(props) {
|
||||
const auth = useAuth();
|
||||
const [status, setStatus] = useState();
|
||||
|
||||
const onSubmit = ({ email, pass }) => {
|
||||
setStatus({ type: "pending" });
|
||||
auth
|
||||
.signup(email, pass)
|
||||
.then(user => {
|
||||
props.onSignup && props.onSignup();
|
||||
})
|
||||
.catch(error => {
|
||||
setStatus({
|
||||
type: "error",
|
||||
message: error.message
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Auth
|
||||
mode="signup"
|
||||
buttonText={props.buttonText}
|
||||
parentColor={props.parentColor}
|
||||
onSubmit={onSubmit}
|
||||
status={status}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignUp;
|
||||
0
src/components/SignUp/styles.scss
Normal file
0
src/components/SignUp/styles.scss
Normal file
35
src/components/SignUpSection/index.js
Normal file
35
src/components/SignUpSection/index.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import SignUp from "./../SignUp";
|
||||
import { useRouter } from "./../../util/router.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function SignUpSection(props) {
|
||||
const router = useRouter();
|
||||
|
||||
// Go to page after signup
|
||||
const onSignup = () => {
|
||||
router.push("/dashboard");
|
||||
};
|
||||
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<SignUp
|
||||
buttonText={props.buttonText}
|
||||
parentColor={props.color}
|
||||
onSignup={onSignup}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default SignUpSection;
|
||||
0
src/components/SignUpSection/styles.scss
Normal file
0
src/components/SignUpSection/styles.scss
Normal file
34
src/components/TeamBios/index.js
Normal file
34
src/components/TeamBios/index.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import React from "react";
|
||||
import CenteredColumns from "./../CenteredColumns";
|
||||
import Avatar from "./../Avatar";
|
||||
import "./styles.scss";
|
||||
|
||||
function TeamBios(props) {
|
||||
return (
|
||||
<CenteredColumns>
|
||||
{props.people.map((person, index) => (
|
||||
<div
|
||||
className="column is-half-tablet is-one-third-desktop is-flex"
|
||||
key={index}
|
||||
>
|
||||
<div className="TeamBios__card card is-flex">
|
||||
<div className="TeamBios__card-content card-content is-flex has-text-centered">
|
||||
<div className="TeamBios__avatar-wrapper">
|
||||
<Avatar image={person.avatar} size={128} alt={person.name} />
|
||||
</div>
|
||||
<div className="TeamBios__details">
|
||||
<p className="is-size-5">{person.name}</p>
|
||||
<p className="is-size-7 is-uppercase has-text-weight-semibold">
|
||||
{person.role}
|
||||
</p>
|
||||
<p className="TeamBios__bio">{person.bio}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CenteredColumns>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamBios;
|
||||
21
src/components/TeamBios/styles.scss
Normal file
21
src/components/TeamBios/styles.scss
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
.TeamBios__card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.TeamBios__card-content {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 1.8rem;
|
||||
}
|
||||
|
||||
.TeamBios__avatar-wrapper {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.TeamBios__details {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.TeamBios__bio {
|
||||
margin-top: 20px;
|
||||
}
|
||||
56
src/components/TeamBiosSection/index.js
Normal file
56
src/components/TeamBiosSection/index.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import TeamBios from "./../TeamBios";
|
||||
import "./styles.scss";
|
||||
|
||||
function TeamBiosSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<TeamBios
|
||||
people={[
|
||||
{
|
||||
avatar: "https://uploads.divjoy.com/pravatar-150x-68.jpeg",
|
||||
name: "John Smith",
|
||||
role: "Software Engineer",
|
||||
bio:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum consequatur numquam aliquam tenetur ad amet inventore hic beatae, quas accusantium perferendis sapiente explicabo.",
|
||||
twitterUrl: "https://twitter.com",
|
||||
facebookUrl: "https://facebook.com",
|
||||
linkedinUrl: "https://linkedin.com"
|
||||
},
|
||||
{
|
||||
avatar: "https://uploads.divjoy.com/pravatar-150x-35.jpeg",
|
||||
name: "Lisa Zinn",
|
||||
role: "Software Engineer",
|
||||
bio:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum consequatur numquam aliquam tenetur ad amet inventore hic beatae, quas accusantium perferendis sapiente explicabo, corporis totam! Labore reprehenderit beatae magnam animi!",
|
||||
twitterUrl: "https://twitter.com",
|
||||
facebookUrl: "https://facebook.com",
|
||||
linkedinUrl: "https://linkedin.com"
|
||||
},
|
||||
{
|
||||
avatar: "https://uploads.divjoy.com/pravatar-150x-16.jpeg",
|
||||
name: "Diana Low",
|
||||
role: "Designer",
|
||||
bio:
|
||||
"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolorum consequatur numquam aliquam tenetur ad amet inventore hic beatae, quas accusantium perferendis sapiente explicabo, corporis totam! Labore reprehenderit beatae magnam animi!",
|
||||
twitterUrl: "https://twitter.com",
|
||||
facebookUrl: "https://facebook.com",
|
||||
linkedinUrl: "https://linkedin.com"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default TeamBiosSection;
|
||||
0
src/components/TeamBiosSection/styles.scss
Normal file
0
src/components/TeamBiosSection/styles.scss
Normal file
31
src/components/Testimonials/index.js
Normal file
31
src/components/Testimonials/index.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
import React from "react";
|
||||
import CenteredColumns from "./../CenteredColumns";
|
||||
import Avatar from "./../Avatar";
|
||||
import "./styles.scss";
|
||||
|
||||
function Testimonials(props) {
|
||||
return (
|
||||
<CenteredColumns>
|
||||
{props.items.map((item, index) => (
|
||||
<div className="column is-flex" key={index}>
|
||||
<div className="Testimonials__card card is-flex">
|
||||
<div className="Testimonials__card-content card-content has-text-centered is-flex">
|
||||
<div className="Testimonials__avatar-wrapper">
|
||||
<Avatar image={item.avatar} size={96} alt={item.name} />
|
||||
</div>
|
||||
<p className="Testimonials__quote">"{item.bio}"</p>
|
||||
<div className="Testimonials__info">
|
||||
<div className="has-text-weight-bold">{item.name}</div>
|
||||
<div className="Testimonials__company link is-size-7">
|
||||
{item.company}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</CenteredColumns>
|
||||
);
|
||||
}
|
||||
|
||||
export default Testimonials;
|
||||
26
src/components/Testimonials/styles.scss
Normal file
26
src/components/Testimonials/styles.scss
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
.Testimonials__card {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.Testimonials__card-content {
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
padding: 1.8rem;
|
||||
}
|
||||
|
||||
.Testimonials__avatar-wrapper {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.Testimonials__quote {
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.Testimonials__info {
|
||||
margin-top: auto;
|
||||
padding-top: 40px;
|
||||
}
|
||||
|
||||
.Testimonials__company {
|
||||
margin-top: 3px;
|
||||
}
|
||||
49
src/components/TestimonialsSection/index.js
Normal file
49
src/components/TestimonialsSection/index.js
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import React from "react";
|
||||
import Section from "./../Section";
|
||||
import SectionHeader from "./../SectionHeader";
|
||||
import Testimonials from "./../Testimonials";
|
||||
import "./styles.scss";
|
||||
|
||||
function TestimonialsSection(props) {
|
||||
return (
|
||||
<Section color={props.color} size={props.size}>
|
||||
<div className="container">
|
||||
<SectionHeader
|
||||
title={props.title}
|
||||
subtitle={props.subtitle}
|
||||
centered={true}
|
||||
size={3}
|
||||
/>
|
||||
<Testimonials
|
||||
items={[
|
||||
{
|
||||
avatar: "https://uploads.divjoy.com/pravatar-150x-5.jpeg",
|
||||
name: "Sarah Kline",
|
||||
bio:
|
||||
"“Ce que j’aime dans cet outil, c’est surtout sa simplicité d’utilisation ! Enfin quelque chose de bien pensé, rassurant et moderne [...]” ",
|
||||
company: "Company"
|
||||
},
|
||||
{
|
||||
avatar: "https://uploads.divjoy.com/pravatar-150x-48.jpeg",
|
||||
name: "Shawna Murray",
|
||||
role: "Software Engineer",
|
||||
bio:
|
||||
"“J’ai 4 locations à gérer : j’étais vite débordé avant, mais maintenant tout est centralisé sur mon compte. Si je n'ai pas le temps de me connecter, je reçois quand même toujours les notifications par mail !” ",
|
||||
company: "Company"
|
||||
},
|
||||
{
|
||||
avatar: "https://uploads.divjoy.com/pravatar-150x-12.jpeg",
|
||||
name: "Blake Elder",
|
||||
role: "Designer",
|
||||
bio:
|
||||
"“Avant, j’envoyais un chèque par la poste... mais c’est bien plus pratique maintenant de payer mon loyer : réglé en un clic, trace du paiement, quittance envoyée par email...” ",
|
||||
company: "Company"
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
export default TestimonialsSection;
|
||||
0
src/components/TestimonialsSection/styles.scss
Normal file
0
src/components/TestimonialsSection/styles.scss
Normal file
|
|
@ -1,13 +0,0 @@
|
|||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
|
||||
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
|
||||
monospace;
|
||||
}
|
||||
12
src/index.js
12
src/index.js
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import './index.css';
|
||||
import App from './App';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import React from "react";
|
||||
import ReactDom from "react-dom";
|
||||
import "./styles.scss";
|
||||
import App from "./pages/_app";
|
||||
import * as serviceWorker from "./serviceWorker";
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
ReactDom.render(<App />, document.getElementById("root"));
|
||||
|
||||
// If you want your app to work offline and load faster, you can change
|
||||
// unregister() to register() below. Note this comes with some pitfalls.
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 8 KiB |
81
src/pages/_app/index.js
Normal file
81
src/pages/_app/index.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
import React from "react";
|
||||
import Navbar from "./../../components/Navbar";
|
||||
import HomePage from "./../home";
|
||||
import AboutPage from "./../about";
|
||||
import FaqPage from "./../faq";
|
||||
import PricingPage from "./../pricing";
|
||||
import ContactPage from "./../contact";
|
||||
import DashboardPage from "./../dashboard";
|
||||
import SigninPage from "./../signin";
|
||||
import SignupPage from "./../signup";
|
||||
import ForgotpassPage from "./../forgotpass";
|
||||
import ChangepassPage from "./../changepass";
|
||||
import { Switch, Route, Router } from "./../../util/router.js";
|
||||
import Footer from "./../../components/Footer";
|
||||
import analytics from "./../../util/analytics.js";
|
||||
import { ProvideAuth } from "./../../util/auth.js";
|
||||
import "./styles.scss";
|
||||
|
||||
function App(props) {
|
||||
return (
|
||||
<ProvideAuth>
|
||||
<Router>
|
||||
<>
|
||||
<Navbar
|
||||
color="white"
|
||||
spaced={true}
|
||||
logo="https://uploads.divjoy.com/logo.svg"
|
||||
/>
|
||||
|
||||
<Switch>
|
||||
<Route exact path="/" component={HomePage} />
|
||||
|
||||
<Route exact path="/about" component={AboutPage} />
|
||||
|
||||
<Route exact path="/faq" component={FaqPage} />
|
||||
|
||||
<Route exact path="/pricing" component={PricingPage} />
|
||||
|
||||
<Route exact path="/contact" component={ContactPage} />
|
||||
|
||||
<Route exact path="/dashboard" component={DashboardPage} />
|
||||
|
||||
<Route exact path="/signin" component={SigninPage} />
|
||||
|
||||
<Route exact path="/signup" component={SignupPage} />
|
||||
|
||||
<Route exact path="/forgotpass" component={ForgotpassPage} />
|
||||
|
||||
<Route exact path="/changepass" component={ChangepassPage} />
|
||||
|
||||
<Route
|
||||
component={({ location }) => {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
padding: "50px",
|
||||
width: "100%",
|
||||
textAlign: "center"
|
||||
}}
|
||||
>
|
||||
The page <code>{location.pathname}</code> could not be
|
||||
found.
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</Switch>
|
||||
|
||||
<Footer
|
||||
color="light"
|
||||
size="normal"
|
||||
logo="https://uploads.divjoy.com/logo.svg"
|
||||
copyright="© 2019 nemo.immo"
|
||||
/>
|
||||
</>
|
||||
</Router>
|
||||
</ProvideAuth>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue