Merge branch 'master' into 'production'
Huge production merge See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!18
This commit is contained in:
@@ -1,16 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
echo "Deploying to development."
|
||||
set -e
|
||||
set -x
|
||||
|
||||
pushd deployment-frontend
|
||||
|
||||
docker-compose down
|
||||
docker pull "$1"
|
||||
API_URL=http://web.sik.party:8000/api docker-compose up -d frontend
|
||||
|
||||
popd
|
||||
|
||||
set +x
|
||||
set +e
|
||||
+1
-1
@@ -1 +1 @@
|
||||
API_URL=http://web.sik.party:8000/api
|
||||
API_URL=https://api.dev.sik.party/api
|
||||
+1
-1
@@ -42,7 +42,7 @@ build:
|
||||
needs: ["install"]
|
||||
stage: build
|
||||
script:
|
||||
- API_URL=http://web.sik.party:8000/api npm run build
|
||||
- API_URL=https://api.dev.sik.party/api npm run build
|
||||
dependencies:
|
||||
- install
|
||||
artifacts:
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
{
|
||||
"events": [
|
||||
{
|
||||
"id": 1,
|
||||
"tags": [],
|
||||
"visible": false,
|
||||
"title": "BaseFeedBaseFeed",
|
||||
"description": "wdnvsflgbkeancQN",
|
||||
"content": "dsfjoisDHNDAH",
|
||||
"start_time": "2018-07-10T18:46:55.924259Z",
|
||||
"end_time": "2018-07-10T18:46:55.924296Z",
|
||||
"signup_id": [],
|
||||
"signupForm": []
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"tags": [],
|
||||
"visible": false,
|
||||
"title": "test",
|
||||
"description": "wdnvsflgbkeancQN",
|
||||
"content": "kciofdconcosnc",
|
||||
"start_time": "2018-07-10T18:57:34.329387Z",
|
||||
"end_time": "2018-07-10T18:57:34.329439Z",
|
||||
"signup_id": [
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"signupForm": [
|
||||
{
|
||||
"id": 1,
|
||||
"start": "2018-07-10T18:16:21.285307Z",
|
||||
"end": "2018-07-10T18:16:21.285340Z",
|
||||
"questions": "json"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"start": "2018-07-10T18:16:35.804521Z",
|
||||
"end": "2018-07-10T18:16:35.804550Z",
|
||||
"questions": "JSON"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"start": "2019-02-11T22:12:00Z",
|
||||
"end": "2018-12-11T22:12:00Z",
|
||||
"questions": "JSON"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"tags": [],
|
||||
"visible": false,
|
||||
"title": "Illanvika",
|
||||
"description": "ahiuhqiuw",
|
||||
"content": "nbiufhiusaiuwad",
|
||||
"start_time": "2018-07-10T18:58:00.516316Z",
|
||||
"end_time": "2018-07-10T18:58:00.516346Z",
|
||||
"signup_id": [
|
||||
],
|
||||
"signupForm": [
|
||||
]
|
||||
}
|
||||
],
|
||||
"SignupForm": [
|
||||
{
|
||||
"id": 1,
|
||||
"start": "2018-07-10T18:16:21.285307Z",
|
||||
"end": "2018-07-10T18:16:21.285340Z",
|
||||
"questions": "json"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"start": "2018-07-10T18:16:35.804521Z",
|
||||
"end": "2018-07-10T18:16:35.804550Z",
|
||||
"questions": "JSON"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"start": "2019-02-11T22:12:00Z",
|
||||
"end": "2018-12-11T22:12:00Z",
|
||||
"questions": "JSON"
|
||||
}
|
||||
],
|
||||
"SignUp": [
|
||||
{
|
||||
"id": 1,
|
||||
"signupForm": 1,
|
||||
"answer": "JSON1"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"signupForm": 2,
|
||||
"answer": "JSON3"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"signupForm": 2,
|
||||
"answer": "JSON4234"
|
||||
}
|
||||
]
|
||||
}
|
||||
Generated
+2252
-2682
File diff suppressed because it is too large
Load Diff
+17
-21
@@ -31,13 +31,11 @@
|
||||
"start-dev": "webpack-dev-server --config=configs/webpack/dev.js",
|
||||
"serve": "node dist/js/server.js",
|
||||
"start-prod": "npm run build && npm run serve",
|
||||
"mock-backend": "json-server --watch db.json -H 0.0.0.0 -p 1234",
|
||||
"test": "npm run test:e2e:verbose",
|
||||
"test:e2e": "npm-run-all -p -r serve test:e2e:testcafe",
|
||||
"test:e2e:verbose": "npm-run-all -p -r serve test:e2e:testcafe:verbose",
|
||||
"test:e2e:testcafe": "testcafe -S -s 'tests/testcafe/screenshots' --app-init-delay 2000 chrome:headless tests/testcafe",
|
||||
"test:e2e:testcafe:verbose": "testcafe -S -s 'tests/testcafe/screenshots' --app-init-delay 2000 chrome tests/testcafe",
|
||||
"plop": "plop"
|
||||
"test:e2e:testcafe:verbose": "testcafe -S -s 'tests/testcafe/screenshots' --app-init-delay 2000 chrome tests/testcafe"
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
@@ -45,18 +43,18 @@
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/classnames": "2.2.10",
|
||||
"@types/jest": "24.0.22",
|
||||
"@types/js-cookie": "2.2.4",
|
||||
"@types/node": "10.14.7",
|
||||
"@types/react": "16.8.18",
|
||||
"@types/react-csv": "1.1.1",
|
||||
"@types/react-dom": "16.8.4",
|
||||
"@types/react-helmet": "6.0.0",
|
||||
"@types/react-jsonschema-form": "1.7.3",
|
||||
"@types/react-router-dom": "5.1.5",
|
||||
"@types/styled-components": "5.1.1",
|
||||
"@typescript-eslint/eslint-plugin": "2.6.1",
|
||||
"@typescript-eslint/parser": "2.6.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.8.2",
|
||||
"@typescript-eslint/parser": "^4.8.2",
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-core": "6.26.3",
|
||||
"babel-loader": "7.1.5",
|
||||
@@ -68,14 +66,14 @@
|
||||
"css-loader": "2.1.1",
|
||||
"dotenv": "6.2.0",
|
||||
"dotenv-webpack": "1.7.0",
|
||||
"eslint": "6.6.0",
|
||||
"eslint-config-standard": "14.1.0",
|
||||
"eslint-plugin-import": "2.18.2",
|
||||
"eslint-plugin-jsx-a11y": "6.2.3",
|
||||
"eslint-plugin-node": "10.0.0",
|
||||
"eslint-plugin-promise": "4.2.1",
|
||||
"eslint-plugin-react": "7.16.0",
|
||||
"eslint-plugin-standard": "4.0.1",
|
||||
"eslint": "^7.14.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.4.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.2.1",
|
||||
"eslint-plugin-react": "^7.21.5",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"express": "4.17.0",
|
||||
"favicons-webpack-plugin": "1.0.2",
|
||||
"file-loader": "4.2.0",
|
||||
@@ -84,18 +82,16 @@
|
||||
"html-webpack-plugin": "3.2.0",
|
||||
"husky": "1.3.1",
|
||||
"image-webpack-loader": "6.0.0",
|
||||
"json-server": "0.16.1",
|
||||
"mini-css-extract-plugin": "0.4.5",
|
||||
"module-to-cdn": "3.1.2",
|
||||
"morgan": "1.9.1",
|
||||
"npm-run-all": "4.1.5",
|
||||
"plop": "2.3.0",
|
||||
"postcss-loader": "2.1.6",
|
||||
"react": "16.8.6",
|
||||
"react-addons-test-utils": "15.6.2",
|
||||
"react-dom": "16.8.6",
|
||||
"react-hot-loader": "4.8.8",
|
||||
"sass": "^1.26.8",
|
||||
"sass": "1.29.0",
|
||||
"sass-loader": "7.1.0",
|
||||
"serve": "11.3.2",
|
||||
"style-loader": "0.21.0",
|
||||
@@ -121,17 +117,17 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "0.19.0",
|
||||
"classnames": "2.2.6",
|
||||
"date-fns": "2.0.0-alpha.27",
|
||||
"js-cookie": "2.2.0",
|
||||
"lodash": "4.17.15",
|
||||
"mobx": "5.9.4",
|
||||
"mobx-react": "5.4.4",
|
||||
"lodash": "4.17.20",
|
||||
"normalize.css": "8.0.1",
|
||||
"query-string": "6.5.0",
|
||||
"react-beautiful-dnd": "10.1.1",
|
||||
"react-csv": "2.0.3",
|
||||
"react-helmet": "5.2.1",
|
||||
"react-jsonschema-form": "^1.8.1",
|
||||
"react-markdown": "4.3.1",
|
||||
"react-mde": "11.0.0",
|
||||
"react-router-dom": "4.3.1",
|
||||
"react-router-hash-link": "1.2.1",
|
||||
"shortid": "2.2.14",
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.{{ dashCase name }} {
|
||||
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
{{#if observer}}
|
||||
import { observer } from "mobx-react";
|
||||
import {{ camelCase store_name }} from "../../stores/{{ properCase store_name }}";
|
||||
{{/if}}
|
||||
import "./{{ properCase name}}.scss";
|
||||
|
||||
export interface {{ properCase name }}Props {}
|
||||
export interface {{ properCase name }}State {}
|
||||
|
||||
{{#if observer}}@observer {{/if}}class {{ properCase name }} extends React.Component<{{ properCase name }}Props, {{ properCase name }}State> {
|
||||
render() {
|
||||
return (
|
||||
<div className="{{ dashCase name }}">
|
||||
{{ titleCase name }}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
{{#if observer}}
|
||||
export default (props) => <{{ properCase name }} {{ camelCase store_name }}={ {{ camelCase store_name }} } { ...props } />;
|
||||
{{else}}
|
||||
export default {{ properCase name }};
|
||||
{{/if}}
|
||||
@@ -1,2 +0,0 @@
|
||||
import {{ properCase name }} from "./{{ properCase name }}";
|
||||
export default {{ properCase name }};
|
||||
@@ -1,17 +0,0 @@
|
||||
import axios from "axios";
|
||||
|
||||
const url = `${process.env.API_URL}/{{ camelCase name }}s`;
|
||||
|
||||
export interface {{ properCase name }} {
|
||||
id: number;
|
||||
}
|
||||
|
||||
export async function get{{ properCase name }}s(): Promise<{{ properCase name }}[]> {
|
||||
try {
|
||||
const resp = await axios.get(url);
|
||||
return resp.data;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
.{{ dashCase name }} {
|
||||
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from "react";
|
||||
import { Helmet } from "react-helmet";
|
||||
import "./{{ properCase name}}.scss";
|
||||
|
||||
export interface {{ properCase name }}Props {}
|
||||
export interface {{ properCase name }}State {}
|
||||
|
||||
class {{ properCase name }} extends React.Component<{{ properCase name }}Props, {{ properCase name }}State> {
|
||||
render() {
|
||||
return (
|
||||
<div className="{{ dashCase name }}">
|
||||
<Helmet>
|
||||
<link rel="canonical" href="https://sik.ayy.fi/INSERT_PATH_HERE!" />
|
||||
</Helmet>
|
||||
{{ titleCase name }}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default {{ properCase name }};
|
||||
@@ -1,13 +0,0 @@
|
||||
import { observable, action } from "mobx";
|
||||
import { observer } from "mobx-react";
|
||||
|
||||
class {{ properCase name }} {
|
||||
@observable counter = 0;
|
||||
|
||||
@action.bound
|
||||
increment() {
|
||||
this.counter += 1;
|
||||
}
|
||||
}
|
||||
|
||||
export default new {{ properCase name }}();
|
||||
-110
@@ -1,110 +0,0 @@
|
||||
module.exports = function(plop) {
|
||||
plop.setGenerator("New component", {
|
||||
description: "Create a new TSX + SCSS component for React.",
|
||||
prompts: [
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message: "Component name:"
|
||||
},
|
||||
{
|
||||
type: "list",
|
||||
choices: [{ name: "No, it does not observe a store", value: false }, { name: "Yes, it observes a store", value: true }],
|
||||
name: "observer",
|
||||
message: "Is the component a MobX observer?"
|
||||
},
|
||||
{
|
||||
type: "input",
|
||||
name: "store_name",
|
||||
message: "MobX store name:",
|
||||
when: answers => answers.observer
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: "add",
|
||||
path: "src/components/{{ properCase name }}/{{ properCase name }}.tsx",
|
||||
templateFile: "plop-templates/component.tsx",
|
||||
abortOnFail: true
|
||||
},
|
||||
{
|
||||
type: "add",
|
||||
path: "src/components/{{ properCase name }}/{{ properCase name }}.scss",
|
||||
templateFile: "plop-templates/component.scss",
|
||||
abortOnFail: true
|
||||
},
|
||||
{
|
||||
type: "add",
|
||||
path: "src/components/{{ properCase name }}/index.ts",
|
||||
templateFile: "plop-templates/index.ts",
|
||||
abortOnFail: true
|
||||
}
|
||||
]
|
||||
});
|
||||
plop.setGenerator("New page", {
|
||||
description: "Create a new TSX + SCSS page for React.",
|
||||
prompts: [
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message: "Page name (has to end in \"page\"):"
|
||||
},
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: "add",
|
||||
path: "src/pages/{{ properCase name }}/{{ properCase name }}.tsx",
|
||||
templateFile: "plop-templates/page.tsx",
|
||||
abortOnFail: true
|
||||
},
|
||||
{
|
||||
type: "add",
|
||||
path: "src/pages/{{ properCase name }}/{{ properCase name }}.scss",
|
||||
templateFile: "plop-templates/page.scss",
|
||||
abortOnFail: true
|
||||
},
|
||||
{
|
||||
type: "add",
|
||||
path: "src/pages/{{ properCase name }}/index.ts",
|
||||
templateFile: "plop-templates/index.ts",
|
||||
abortOnFail: true
|
||||
}
|
||||
]
|
||||
});
|
||||
plop.setGenerator("New MobX state store", {
|
||||
description: "Create a new store for MobX.",
|
||||
prompts: [
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message: "Store name:"
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: "add",
|
||||
path: "src/stores/{{ properCase name }}.ts",
|
||||
templateFile: "plop-templates/store.ts",
|
||||
abortOnFail: true
|
||||
}
|
||||
]
|
||||
});
|
||||
plop.setGenerator("New API model", {
|
||||
description: "Create a new API model for backend communication.",
|
||||
prompts: [
|
||||
{
|
||||
type: "input",
|
||||
name: "name",
|
||||
message: "Model name:"
|
||||
}
|
||||
],
|
||||
actions: [
|
||||
{
|
||||
type: "add",
|
||||
path: "src/models/{{ properCase name }}.ts",
|
||||
templateFile: "plop-templates/model.ts",
|
||||
abortOnFail: true
|
||||
}
|
||||
]
|
||||
});
|
||||
};
|
||||
@@ -1,59 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.accordion {
|
||||
margin: 0.2em;
|
||||
padding: 0.2em;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
border-style: solid;
|
||||
border-color: color(light-turquoise);
|
||||
border-width: 1px;
|
||||
|
||||
&__desc {
|
||||
display: flex;
|
||||
margin-top: -100%;
|
||||
max-height: 15rem;
|
||||
-webkit-transition: margin-top 400ms ease-in-out;
|
||||
-webkit-transform: margin-top 400ms ease-in-out;
|
||||
-moz-transform: margin-top 400ms ease-in-out;
|
||||
-o-transform: margin-top 400ms ease-in-out;
|
||||
transition: margin-top 400ms ease-in-out;
|
||||
|
||||
&.open {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
div {
|
||||
min-width: 40px;
|
||||
max-width: 40px;
|
||||
margin: 0.6em;
|
||||
}
|
||||
|
||||
p {
|
||||
padding-left: 1em;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
button {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
background-color: color(white1);
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
h5 {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
text-align: center;
|
||||
padding-left: 1em;
|
||||
color: color(blue1);
|
||||
font-size: 1.125rem;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,78 @@
|
||||
import React from "react";
|
||||
import "./Accordion.scss";
|
||||
import AccordionIcon from "../AccordionIcon";
|
||||
import React, { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import AccordionIcon from "./AccordionIcon";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
export interface AccordionProps {
|
||||
const Container = styled.div`
|
||||
margin: 0.2em;
|
||||
padding: 0.2em;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
border-style: solid;
|
||||
border-color: ${colors.lightTurquoise};
|
||||
border-width: 1px;
|
||||
|
||||
& > button {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
background-color: ${colors.white};
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: none;
|
||||
|
||||
h5 {
|
||||
flex: 1;
|
||||
text-align: left;
|
||||
padding-left: 1em;
|
||||
color: ${colors.blue1};
|
||||
font-size: 1.125rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
& > div {
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const Panel = styled.div<{visible?: boolean}>`
|
||||
margin-top: ${(p) => p.visible ? "0" : "-100%"};
|
||||
|
||||
display: flex;
|
||||
max-height: 15rem;
|
||||
transition: margin-top 400ms ease-in-out;
|
||||
|
||||
& > * {
|
||||
padding-left: 1em;
|
||||
}
|
||||
`;
|
||||
|
||||
interface AccordionProps {
|
||||
title: string;
|
||||
}
|
||||
export interface AccordionState {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
interface DescriptionProps {
|
||||
visible: boolean;
|
||||
}
|
||||
const Accordion: React.FC<AccordionProps> = ({ title, children }) => {
|
||||
const [isOpen, setOpen] = useState(false);
|
||||
|
||||
const Description: React.SFC<DescriptionProps> = (props) => (
|
||||
<div className={`accordion__desc ${props.visible ? "open" : ""}`}>{props.children}</div>
|
||||
);
|
||||
|
||||
class Accordion extends React.Component<AccordionProps, AccordionState> {
|
||||
constructor(props: AccordionProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
isOpen: false,
|
||||
};
|
||||
const handleClick = () => {
|
||||
setOpen(!isOpen);
|
||||
}
|
||||
|
||||
handleClick() {
|
||||
this.setState((prevState) => ({
|
||||
isOpen: !prevState.isOpen,
|
||||
}));
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isOpen } = this.state;
|
||||
return (
|
||||
<div className="accordion">
|
||||
<button type="button" onClick={() => this.handleClick()}>
|
||||
<AccordionIcon open={isOpen} />
|
||||
<h5>{this.props.title}</h5>
|
||||
</button>
|
||||
<div style={{ overflow: "hidden", }}>
|
||||
<Description visible={isOpen}>{this.props.children}</Description>
|
||||
</div>
|
||||
return (
|
||||
<Container>
|
||||
<button type="button" onClick={handleClick}>
|
||||
<AccordionIcon open={isOpen} />
|
||||
<h5>{title}</h5>
|
||||
</button>
|
||||
<div>
|
||||
<Panel visible={isOpen}>
|
||||
{children}
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default Accordion;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { colors } from "../../theme/colors";
|
||||
|
||||
interface AccordionIconProps {
|
||||
open: boolean;
|
||||
}
|
||||
|
||||
const Icon = styled.div<AccordionIconProps>`
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: ${(p) => p.open ? colors.orange1 : colors.blue1};
|
||||
color: ${colors.white};
|
||||
min-width: 40px;
|
||||
max-width: 40px;
|
||||
min-height: 40px;
|
||||
max-height: 40px;
|
||||
margin: 0.2em;
|
||||
font-size: 40px;
|
||||
|
||||
${(p) => p.open && (`
|
||||
span {
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
`)}
|
||||
|
||||
`;
|
||||
|
||||
const AccordionIcon: React.FC<AccordionIconProps> = ({ open } ) => (
|
||||
<Icon open={open}>
|
||||
<span>+</span>
|
||||
</Icon>
|
||||
);
|
||||
|
||||
export default AccordionIcon;
|
||||
@@ -1,2 +0,0 @@
|
||||
import Accordion from "./Accordion";
|
||||
export default Accordion;
|
||||
@@ -1,29 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.accordion-icon {
|
||||
display: flex;
|
||||
background-color: color(blue1);
|
||||
color: color(white1);
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 40px;
|
||||
max-width: 40px;
|
||||
min-height: 40px;
|
||||
max-height: 40px;
|
||||
margin: 0.2em;
|
||||
font-size: 40px;
|
||||
|
||||
&.open {
|
||||
background-color: color(orange1);
|
||||
}
|
||||
}
|
||||
|
||||
.accordion-text {
|
||||
&.open {
|
||||
-webkit-transform: rotate(45deg);
|
||||
-moz-transform: rotate(45deg);
|
||||
-o-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from "react";
|
||||
import "./AccordionIcon.scss";
|
||||
|
||||
export interface AccordionIconProps {
|
||||
open: boolean;
|
||||
}
|
||||
export interface AccordionIconState {}
|
||||
|
||||
class AccordionIcon extends React.Component<AccordionIconProps, AccordionIconState> {
|
||||
render() {
|
||||
const { open } = this.props;
|
||||
return (
|
||||
<div className={`accordion-icon ${open ? "open" : ""}`}>
|
||||
<div className={`accordion-text ${open ? "open" : ""}`}>+</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AccordionIcon;
|
||||
@@ -1,2 +0,0 @@
|
||||
import AccordionIcon from "./AccordionIcon";
|
||||
export default AccordionIcon;
|
||||
@@ -0,0 +1,35 @@
|
||||
import React, { ComponentProps } from "react";
|
||||
import styled from "styled-components";
|
||||
import { colors }from "@theme/colors";
|
||||
import Anchor from "@components/Anchor";
|
||||
import AddIcon from "@assets/img/add-icon.png";
|
||||
|
||||
const Link = styled(Anchor)`
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
&:hover {
|
||||
color: ${colors.orange2};
|
||||
}
|
||||
|
||||
img {
|
||||
margin-right: 8px;
|
||||
margin-top: -2px;
|
||||
width: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
type AddLinkProps = ComponentProps<typeof Anchor> & {
|
||||
text: string;
|
||||
}
|
||||
|
||||
const AddLink: React.FC<AddLinkProps> = ({ text, ...props }) => (
|
||||
<Link {...props}>
|
||||
<img src={AddIcon} />
|
||||
{text}
|
||||
</Link>
|
||||
)
|
||||
|
||||
export default AddLink;
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import HeaderLogo from "./HeaderLogo";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
const Header = styled.header`
|
||||
background-color: ${colors.darkBlue};
|
||||
|
||||
a {
|
||||
img {
|
||||
display: block;
|
||||
margin: 0.5rem auto;
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
fill: ${colors.white};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const AdminHeader: React.FC = () => (
|
||||
<Header>
|
||||
<HeaderLogo />
|
||||
</Header>
|
||||
);
|
||||
|
||||
export default AdminHeader;
|
||||
@@ -1,32 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.admin-header {
|
||||
margin-bottom: 0.5rem;
|
||||
|
||||
.heading {
|
||||
margin: 0 2rem;
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
|
||||
@media screen and (max-width: 600px - 1px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
max-width: 100%;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
margin: 1rem 0.5rem;
|
||||
|
||||
@media screen and (max-width: 600px - 1px) {
|
||||
max-width: 300px !important;
|
||||
width: 100%;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import TitleImage from "@assets/img/SIK_RGB_W_side.png";
|
||||
import "./AdminHeader.scss";
|
||||
|
||||
export interface AdminHeaderProps { }
|
||||
export interface AdminHeaderState { }
|
||||
|
||||
class AdminHeader extends React.Component<AdminHeaderProps, AdminHeaderState> {
|
||||
render() {
|
||||
return (
|
||||
<React.Fragment>
|
||||
<header className="header admin-header">
|
||||
<Link to="/">
|
||||
<img src={TitleImage} />
|
||||
</Link>
|
||||
<div className="heading">Admin panel</div>
|
||||
</header>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminHeader;
|
||||
@@ -1,2 +0,0 @@
|
||||
import AdminHeader from "./AdminHeader";
|
||||
export default AdminHeader;
|
||||
@@ -0,0 +1,57 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Anchor from "@components/Anchor";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
interface AdminSidebarProps {
|
||||
path: string;
|
||||
}
|
||||
|
||||
const SideBar = styled.nav`
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
margin-right: 1rem;
|
||||
background-color: ${colors.blue1};
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
margin-right: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledLink = styled(Anchor)<{path: string}>`
|
||||
padding: 1rem 3rem 1rem 1rem;
|
||||
letter-spacing: 3px;
|
||||
text-transform: uppercase;
|
||||
line-height: 20px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
color: ${colors.white};
|
||||
border-left: 4px solid transparent;
|
||||
|
||||
${p => p.path === p.to && `
|
||||
border-left: 4px solid ${colors.white};
|
||||
`}
|
||||
|
||||
&:hover {
|
||||
border-left: 4px solid ${colors.white};
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
`;
|
||||
|
||||
const AdminSidebar: React.FC<AdminSidebarProps> = ({ path }) => (
|
||||
<SideBar>
|
||||
<StyledLink to="/admin" path={path}>Home ›</StyledLink>
|
||||
<StyledLink to="/admin/events" path={path}>Events ›</StyledLink>
|
||||
<StyledLink to="/admin/feed" path={path}>Feed ›</StyledLink>
|
||||
<StyledLink to="/admin/signups" path={path}>Signup forms ›</StyledLink>
|
||||
<StyledLink to="/admin/jobads" path={path}>Job advertisements ›</StyledLink>
|
||||
<StyledLink to="https://static.sika.sik.party/admin" path={path}>Files ›</StyledLink>
|
||||
<StyledLink id="admin-sidebar-logout" to="/admin/logout" path={path}>Logout ›</StyledLink>
|
||||
</SideBar>
|
||||
);
|
||||
|
||||
export default AdminSidebar;
|
||||
@@ -1,13 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.admin-sidebar {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-self: stretch;
|
||||
margin-right: 1rem;
|
||||
|
||||
@media screen and (max-width: 800px - 1px) {
|
||||
margin-right: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from "react";
|
||||
import "./AdminSidebar.scss";
|
||||
import AdminSidebarLink from "../AdminSidebarLink";
|
||||
|
||||
export interface AdminSidebarProps {
|
||||
path: string;
|
||||
}
|
||||
export interface AdminSidebarState {}
|
||||
|
||||
class AdminSidebar extends React.Component<AdminSidebarProps, AdminSidebarState> {
|
||||
render() {
|
||||
const { path } = this.props;
|
||||
return (
|
||||
<div className="admin-sidebar">
|
||||
<AdminSidebarLink to="/admin" path={path}>Home</AdminSidebarLink>
|
||||
<AdminSidebarLink to="/admin/events" path={path}>Events</AdminSidebarLink>
|
||||
<AdminSidebarLink to="/admin/feed" path={path}>Feed</AdminSidebarLink>
|
||||
<AdminSidebarLink to="/admin/signups" path={path}>Signup forms</AdminSidebarLink>
|
||||
<AdminSidebarLink to="https://static.sika.sik.party/admin" path={path}>Files</AdminSidebarLink>
|
||||
<AdminSidebarLink id="admin-sidebar-logout" to="/admin/logout" path={path}>Logout</AdminSidebarLink>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminSidebar;
|
||||
@@ -1,2 +0,0 @@
|
||||
import AdminSidebar from "./AdminSidebar";
|
||||
export default AdminSidebar;
|
||||
@@ -1,26 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.admin-sidebar-link {
|
||||
padding: 1rem 3rem 1rem 1rem;
|
||||
background-color: color(blue1);
|
||||
letter-spacing: 3px;
|
||||
text-transform: uppercase;
|
||||
line-height: 20px;
|
||||
font-weight: bold;
|
||||
border-left: 4px solid color(blue1);
|
||||
white-space: nowrap;
|
||||
|
||||
@media screen and (max-width: 800px - 1px) {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
border-left: 4px solid color(white1);
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: " ›";
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import Anchor from "../Anchor";
|
||||
import "./AdminSidebarLink.scss";
|
||||
|
||||
export interface AdminSidebarLinkProps {
|
||||
to: string;
|
||||
path: string;
|
||||
id?: string;
|
||||
}
|
||||
export interface AdminSidebarLinkState { }
|
||||
|
||||
class AdminSidebarLink extends React.Component<AdminSidebarLinkProps, AdminSidebarLinkState> {
|
||||
render() {
|
||||
const { to, path, children, id } = this.props;
|
||||
const activeClass = to === path ? "active" : "";
|
||||
|
||||
return (
|
||||
<Anchor id={id} to={to} className={`admin-sidebar-link ${activeClass}`}>
|
||||
{children}
|
||||
</Anchor>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AdminSidebarLink;
|
||||
@@ -1,2 +0,0 @@
|
||||
import AdminSidebarLink from "./AdminSidebarLink";
|
||||
export default AdminSidebarLink;
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { HashLink } from "react-router-hash-link";
|
||||
|
||||
interface AnchorProps extends React.HTMLAttributes<HTMLAnchorElement> {
|
||||
to: string;
|
||||
}
|
||||
|
||||
const Anchor: React.FC<AnchorProps> = ({ to, ...props }) => {
|
||||
if (to.startsWith("/")) {
|
||||
return (
|
||||
<Link to={to} {...props} />
|
||||
);
|
||||
} else if (to.startsWith("#")) {
|
||||
return (
|
||||
<HashLink to={to} {...props} />
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<a href={to} {...props} />
|
||||
);
|
||||
}
|
||||
}
|
||||
export default Anchor;
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { HashLink } from "react-router-hash-link";
|
||||
|
||||
export interface AnchorProps extends React.HTMLAttributes<HTMLAnchorElement> {
|
||||
to: string;
|
||||
}
|
||||
|
||||
class Anchor extends React.Component<AnchorProps> {
|
||||
render() {
|
||||
const { children, to, ...props } = this.props;
|
||||
|
||||
if (to.startsWith("/")) {
|
||||
return (
|
||||
<Link to={to} {...props}>{children}</Link>
|
||||
);
|
||||
} else if (to.startsWith("#")) {
|
||||
return (
|
||||
<HashLink to={to} {...props}>{children}</HashLink>
|
||||
);
|
||||
}
|
||||
else {
|
||||
return (
|
||||
<a href={to} {...props}>{children}</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
export default Anchor;
|
||||
@@ -1,2 +0,0 @@
|
||||
import Anchor from "./Anchor";
|
||||
export default Anchor;
|
||||
@@ -1,10 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.aside-section {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-flow: column;
|
||||
justify-content: space-between;
|
||||
padding: 3rem 4rem;
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import "./AsideSection.scss";
|
||||
import ColorDiv, { ColorDivProps } from "../ColorDiv/ColorDiv";
|
||||
|
||||
export interface AsideSectionProps {
|
||||
className?: string;
|
||||
}
|
||||
export interface AsideSectionState { }
|
||||
|
||||
class AsideSection extends React.Component<AsideSectionProps & ColorDivProps, AsideSectionState> {
|
||||
render() {
|
||||
const { children, className, ...props } = this.props;
|
||||
const classNames = [
|
||||
"aside-section",
|
||||
];
|
||||
if (className) classNames.push(className);
|
||||
return (
|
||||
<ColorDiv className={classNames.join(" ")} {...props}>
|
||||
{children}
|
||||
</ColorDiv>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default AsideSection;
|
||||
@@ -1,2 +0,0 @@
|
||||
import AsideSection from "./AsideSection";
|
||||
export default AsideSection;
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
interface ButtonProps {
|
||||
onClick: () => void;
|
||||
type: "hero" | "filled" | "filter" | "bordered";
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
const StyledButton = styled.button<ButtonProps>`
|
||||
border-radius: none;
|
||||
padding: 0.8rem 2rem;
|
||||
margin: 0.5rem;
|
||||
font-size: 13px;
|
||||
background: none;
|
||||
text-transform: uppercase;
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&.hero {
|
||||
background-color: ${colors.darkBlue};
|
||||
color: ${colors.blue1};
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.5px;
|
||||
border: 1px solid ${colors.lightTurquoise};
|
||||
}
|
||||
|
||||
&.filled {
|
||||
justify-content: center;
|
||||
background-color: ${colors.blue1};
|
||||
color: ${colors.white};
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.5px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.bordered {
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
color: ${colors.blue1};
|
||||
border: 1px solid ${colors.blue1};
|
||||
}
|
||||
|
||||
&.filter {
|
||||
text-transform: none;
|
||||
color: ${colors.grey1};
|
||||
font-weight: 300;
|
||||
border: 2px solid ${colors.grey1};
|
||||
|
||||
${(p) => p.selected && (`
|
||||
background-color: ${colors.grey1};
|
||||
color: ${colors.white};
|
||||
`)}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({ type, selected, onClick, ...props }) => (
|
||||
<StyledButton type="button" onClick={onClick} className={type} selected={selected} {...props} />
|
||||
)
|
||||
|
||||
export default Button;
|
||||
@@ -1,61 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.button {
|
||||
-webkit-appearance: none;
|
||||
border-radius: none;
|
||||
padding: 0.8rem 2rem;
|
||||
margin: 0.5rem;
|
||||
font-size: 13px;
|
||||
background: none;
|
||||
text-transform: uppercase;
|
||||
|
||||
@media screen and (max-width: 800px - 1px) {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
&.hero {
|
||||
background-color: color(dark-blue);
|
||||
color: color(blue1);
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.5px;
|
||||
border: 1px solid color(light-turquoise);
|
||||
}
|
||||
|
||||
&.filled {
|
||||
justify-content: center;
|
||||
background-color: color(blue1);
|
||||
color: color(white1);
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.5px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
&.bordered {
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
color: color(blue1);
|
||||
border: 1px solid color(blue1);
|
||||
}
|
||||
|
||||
&.filter {
|
||||
text-transform: none;
|
||||
color: color(grey1);
|
||||
font-weight: 300;
|
||||
border: 2px solid color(grey1);
|
||||
|
||||
&.selected {
|
||||
background-color: color(grey1);
|
||||
color: color(white1);
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import classNames from "classnames";
|
||||
import "./Button.scss";
|
||||
|
||||
interface ButtonProps {
|
||||
onClick: () => void;
|
||||
type: "hero" | "filled" | "filter" | "bordered";
|
||||
selected?: boolean;
|
||||
}
|
||||
|
||||
export default class Button extends React.Component<ButtonProps, undefined> {
|
||||
render() {
|
||||
const { type, selected } = this.props;
|
||||
const classes = classNames(
|
||||
"button",
|
||||
type,
|
||||
{ "selected": selected }
|
||||
)
|
||||
return (
|
||||
<button type="button" onClick={this.props.onClick} className={classes}>
|
||||
{this.props.children}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
import Button from "./Button";
|
||||
export default Button;
|
||||
@@ -0,0 +1,119 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
import Anchor from "@components/Anchor";
|
||||
|
||||
interface WrappedCardProps {
|
||||
title: string;
|
||||
start_time: string;
|
||||
text: string;
|
||||
link: string;
|
||||
image?: string;
|
||||
imageAlt?: string;
|
||||
buttonOnClick?: () => void;
|
||||
}
|
||||
|
||||
interface CardProps {
|
||||
title: string;
|
||||
start_time: string;
|
||||
text: string;
|
||||
img: JSX.Element;
|
||||
button: JSX.Element;
|
||||
}
|
||||
|
||||
const StyledCard = styled.article`
|
||||
color: ${colors.black};
|
||||
margin: 1rem;
|
||||
text-align: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
max-height: 300px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
text-overflow: ellipsis;
|
||||
margin: 0 0 0.5rem;
|
||||
font-weight: 200;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
p:first-of-type {
|
||||
color: ${colors.orange1};
|
||||
font-size: 0.9rem !important;
|
||||
font-weight: 600 !important;
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
h3 {
|
||||
padding: 0.5rem;
|
||||
font-size: 1.5rem;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
|
||||
padding: 0.8rem 2rem;
|
||||
margin: 0.5rem;
|
||||
font-size: 13px;
|
||||
background: none;
|
||||
text-transform: uppercase;
|
||||
background-color: ${colors.blue1};
|
||||
color: ${colors.white};
|
||||
font-weight: 800;
|
||||
letter-spacing: 1.5px;
|
||||
border: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const Card: React.FC<CardProps> = ({ title, start_time, text, img, button }) => (
|
||||
<StyledCard>
|
||||
{img}
|
||||
<p>{start_time}</p>
|
||||
<h3>{title}</h3>
|
||||
<p>{text}</p>
|
||||
{button}
|
||||
</StyledCard>
|
||||
);
|
||||
|
||||
const WrappedCard: React.FC<WrappedCardProps> = ({ title, text, link, image, imageAlt, start_time, buttonOnClick }) => {
|
||||
const options = {
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
year: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit"
|
||||
};
|
||||
const datetime = new Date(start_time).toLocaleString("fi-FI", options);
|
||||
const img = image ? (
|
||||
<img src={image} alt={imageAlt} />
|
||||
) : null;
|
||||
|
||||
const button = (
|
||||
<Anchor to={link}>
|
||||
<button onClick={buttonOnClick}>
|
||||
Lue lisää ›
|
||||
</button>
|
||||
</Anchor>
|
||||
);
|
||||
|
||||
return (
|
||||
<Card
|
||||
title={title}
|
||||
start_time={datetime}
|
||||
text={text}
|
||||
img={img}
|
||||
button={button}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default WrappedCard;
|
||||
@@ -1,93 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.card {
|
||||
background-color: color(white1);
|
||||
color: color(dark-blue);
|
||||
white-space: wrap;
|
||||
margin: 1rem 1rem;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
justify-content: flex-start;
|
||||
width: calc(25% - 2rem);
|
||||
|
||||
@media screen and (min-width: 1000px) and (max-width: 1200px - 1px) {
|
||||
width: calc(50% - 2rem);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1000px - 1px) {
|
||||
width: 100%;
|
||||
margin-bottom: 3rem;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&__title {
|
||||
padding: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-size: 1.5rem;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
color: color(black1);
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
background-position: center center;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media screen and (min-width: 1200px) {
|
||||
height: 15vw;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px - 1px) {
|
||||
height: 35vh;
|
||||
}
|
||||
}
|
||||
|
||||
&__button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
button {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
text-overflow: ellipsis;
|
||||
font-size: 16px;
|
||||
margin: 0 0 0.5rem;
|
||||
font-weight: 200;
|
||||
line-height: 22px;
|
||||
color: color(black1);
|
||||
|
||||
@media screen and (max-width: 1200px - 1px) {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&__datetime {
|
||||
color: color(orange1);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
|
||||
@media screen and (max-width: 1200px - 1px) {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import React from "react";
|
||||
import "./Card.scss";
|
||||
import Anchor from "../Anchor";
|
||||
|
||||
export interface CardProps {
|
||||
title: string;
|
||||
start_time: string;
|
||||
text: string;
|
||||
link?: string;
|
||||
image?: string;
|
||||
button?: JSX.Element;
|
||||
}
|
||||
export interface CardState { }
|
||||
|
||||
class Card extends React.Component<CardProps, CardState> {
|
||||
render() {
|
||||
const { title, text, link, image, button } = this.props;
|
||||
const options = {
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
year: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit"
|
||||
};
|
||||
const datetime = new Date(this.props.start_time).toLocaleString("fi-FI", options);
|
||||
|
||||
const imageElem = image ? (
|
||||
<div style={{ backgroundImage: `url(${image})`, }} className="card__image" />
|
||||
) : null;
|
||||
if (link) {
|
||||
return (
|
||||
<Anchor to={link} className="card">
|
||||
{imageElem}
|
||||
<div className="card__datetime">{datetime}</div>
|
||||
<div className="card__title">{title}</div>
|
||||
<div className="card__text">{text}</div>
|
||||
<div className="card__button">{button}</div>
|
||||
</Anchor>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className="card">
|
||||
{imageElem}
|
||||
<div className="card__datetime">{datetime}</div>
|
||||
<div className="card__title">{title}</div>
|
||||
<div className="card__text">{text}</div>
|
||||
{button}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Card;
|
||||
@@ -1,2 +0,0 @@
|
||||
import Card from "./Card";
|
||||
export default Card;
|
||||
@@ -1,66 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.color-div {
|
||||
@mixin hoverableColor($colorName) {
|
||||
&__#{$colorName} {
|
||||
color: color($colorName);
|
||||
}
|
||||
|
||||
&__#{$colorName}Hoverable {
|
||||
&:hover,
|
||||
&:active {
|
||||
color: hover($colorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin backgroundColor($colorName) {
|
||||
&__background_#{$colorName} {
|
||||
background-color: color($colorName);
|
||||
}
|
||||
|
||||
&__background_#{$colorName}Hoverable {
|
||||
&:hover,
|
||||
&:active {
|
||||
background-color: hover($colorName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@mixin backgroundAndHoverableColor($colorName) {
|
||||
@include hoverableColor($colorName);
|
||||
@include backgroundColor($colorName);
|
||||
}
|
||||
|
||||
@include backgroundAndHoverableColor(dark-blue);
|
||||
@include backgroundAndHoverableColor(light-blue);
|
||||
@include backgroundAndHoverableColor(white1);
|
||||
@include backgroundAndHoverableColor(black1);
|
||||
@include backgroundAndHoverableColor(grey1);
|
||||
@include backgroundAndHoverableColor(grey2);
|
||||
@include backgroundAndHoverableColor(orange1);
|
||||
@include backgroundAndHoverableColor(orange2);
|
||||
@include backgroundAndHoverableColor(blue1);
|
||||
@include backgroundAndHoverableColor(light-turquoise);
|
||||
@include backgroundAndHoverableColor(green1);
|
||||
@include backgroundAndHoverableColor(sand);
|
||||
|
||||
&__inherit {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
&__transparent {
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
&__inheritHoverable {
|
||||
&:hover,
|
||||
&:active {
|
||||
color: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
&__background_transparent {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from "react";
|
||||
import "./ColorDiv.scss";
|
||||
import { Colors, colorToClass, bgColorToClass, hoverColorToClass, bgHoverColorToClass } from "@theme/colors";
|
||||
import classNames from "classnames";
|
||||
|
||||
export interface ColorDivProps extends React.HTMLAttributes<HTMLDivElement> {
|
||||
textColor?: Colors;
|
||||
backgroundColor?: Colors;
|
||||
hoverColor?: Colors;
|
||||
backgroundHoverColor?: Colors;
|
||||
}
|
||||
export interface ColorDivState { }
|
||||
|
||||
|
||||
class ColorDiv extends React.Component<ColorDivProps, ColorDivState> {
|
||||
render() {
|
||||
const { children, className, textColor, backgroundColor, hoverColor, backgroundHoverColor, ...props } = this.props;
|
||||
const classes = classNames(
|
||||
className,
|
||||
colorToClass(textColor),
|
||||
bgColorToClass(backgroundColor),
|
||||
hoverColorToClass(hoverColor),
|
||||
bgHoverColorToClass(backgroundHoverColor)
|
||||
);
|
||||
|
||||
return (
|
||||
<div {...props} className={classes}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ColorDiv;
|
||||
@@ -0,0 +1,61 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { Occupation } from "@models/Contacts";
|
||||
import ContactCard from "./ContactCard";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
interface CommitteeContainerProps {
|
||||
name_fi: string;
|
||||
name_en: string;
|
||||
contacts: Occupation[];
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
color: ${colors.darkBlue};
|
||||
|
||||
& > p {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 3px;
|
||||
line-height: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
& > div {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
`;
|
||||
|
||||
const CommitteeContainer: React.FC<CommitteeContainerProps> = ({ name_fi, name_en, contacts }) => (
|
||||
<Container>
|
||||
<p>
|
||||
{name_fi}
|
||||
</p>
|
||||
<p>
|
||||
{name_en}
|
||||
</p>
|
||||
<div>
|
||||
{contacts.map(occupation => (
|
||||
occupation.officials.map(official => (
|
||||
<ContactCard
|
||||
key={official.first_name}
|
||||
first_name={official.first_name}
|
||||
last_name={official.last_name}
|
||||
phone={official.phone_number}
|
||||
email={official.email}
|
||||
image={official.image}
|
||||
role={occupation.role}
|
||||
/>
|
||||
))
|
||||
))}
|
||||
</div>
|
||||
</Container>
|
||||
);
|
||||
|
||||
export default CommitteeContainer;
|
||||
@@ -1,23 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.committee-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
color: color('dark-blue');
|
||||
|
||||
.committee-name {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 3px;
|
||||
line-height: 14px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&__contacts {
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
import React from "react";
|
||||
import "./CommitteeContainer.scss";
|
||||
import { Occupation } from "@models/Contacts";
|
||||
import ContactCard from "../ContactCard/ContactCard";
|
||||
|
||||
export interface CommitteeContainerProps {
|
||||
name_fi: string;
|
||||
name_en: string;
|
||||
contacts: Occupation[];
|
||||
}
|
||||
export interface CommitteeContainerState { }
|
||||
|
||||
class CommitteeContainer extends React.Component<CommitteeContainerProps, CommitteeContainerState> {
|
||||
render() {
|
||||
const { name_fi, name_en, contacts } = this.props;
|
||||
return (
|
||||
<div className="committee-container">
|
||||
<div className="committee-name">
|
||||
{name_fi}
|
||||
</div>
|
||||
<div className="committee-name">
|
||||
{name_en}
|
||||
</div>
|
||||
<div className="committee-container__contacts">
|
||||
{contacts.map(occupation => (
|
||||
occupation.officials.map(official => (
|
||||
<ContactCard
|
||||
key={official.first_name}
|
||||
first_name={official.first_name}
|
||||
last_name={official.last_name}
|
||||
phone={official.phone_number}
|
||||
email={official.email}
|
||||
image={official.image}
|
||||
role={occupation.role}
|
||||
/>
|
||||
))
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default CommitteeContainer;
|
||||
@@ -1,2 +0,0 @@
|
||||
import CommitteeContainer from "./CommitteeContainer";
|
||||
export default CommitteeContainer;
|
||||
@@ -0,0 +1,70 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import blank_profile from "@assets/img/blank_profile.png";
|
||||
import { Role } from "@models/Contacts";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
const Card = styled.article`
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
color: ${colors.darkBlue};
|
||||
margin: 1rem;
|
||||
`;
|
||||
|
||||
const ImageContainer = styled.div`
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-height: 100px;
|
||||
max-width: 100px;
|
||||
|
||||
& > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
}
|
||||
`;
|
||||
|
||||
const Info = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem;
|
||||
justify-content: flex-start;
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
color: ${colors.darkBlue};
|
||||
`;
|
||||
|
||||
interface ContactCardProps {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
phone: number;
|
||||
email: string;
|
||||
image: string;
|
||||
role: Role;
|
||||
}
|
||||
|
||||
const ContactCard: React.FC<ContactCardProps> = ({ first_name, last_name, phone, email, image, role }) => {
|
||||
const fullName = `${first_name} ${last_name}`;
|
||||
return(
|
||||
<Card>
|
||||
<ImageContainer>
|
||||
<img
|
||||
src={image || blank_profile}
|
||||
alt={fullName}
|
||||
/>
|
||||
</ImageContainer>
|
||||
<Info>
|
||||
<p>{fullName}</p>
|
||||
<p>{phone}</p>
|
||||
<p>{email}</p>
|
||||
<p>{role.name_fi}</p>
|
||||
<p>{role.name_en}</p>
|
||||
</Info>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
export default ContactCard;
|
||||
@@ -1,43 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.contact-card {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
color: color(dark-blue);
|
||||
margin: 1rem 1rem;
|
||||
|
||||
&__notBoard {
|
||||
order: 1;
|
||||
}
|
||||
|
||||
&__chair {
|
||||
order: -1;
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.5rem;
|
||||
justify-content: flex-start;
|
||||
font-size: 1rem;
|
||||
font-weight: 300;
|
||||
color: color('dark-blue');
|
||||
}
|
||||
|
||||
&__image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
|
||||
&__container {
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
max-height: 100px;
|
||||
max-width: 100px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import React from "react";
|
||||
import "./ContactCard.scss";
|
||||
import blank_profile from "@assets/img/blank_profile.png";
|
||||
import { Role } from "@models/Contacts";
|
||||
|
||||
export interface ContactCardProps {
|
||||
first_name: string;
|
||||
last_name: string;
|
||||
phone: number;
|
||||
email: string;
|
||||
image: string;
|
||||
role: Role;
|
||||
}
|
||||
export interface ContactCardState { }
|
||||
|
||||
class ContactCard extends React.Component<ContactCardProps, ContactCardState> {
|
||||
render() {
|
||||
const { first_name, last_name, phone, email, image, role } = this.props;
|
||||
let className = "contact-card"
|
||||
if (!role.is_board) {
|
||||
className += " contact-card__notBoard"
|
||||
} else if (role.name_fi === "Puheenjohtaja") {
|
||||
className += " contact-card__chair"
|
||||
}
|
||||
return (
|
||||
<div className={className}>
|
||||
<div className="contact-card__image__container">
|
||||
<img className="contact-card__image" src={image ? image : blank_profile} alt="profile_image" />
|
||||
</div>
|
||||
<div className="contact-card__info">
|
||||
<div className="contact-card__name">{`${first_name} ${last_name}`}</div>
|
||||
<div className="contact-card__phone">{phone}</div>
|
||||
<div className="contact-card__email">{email}</div>
|
||||
<div className="contact-card__role">{role.name_fi}</div>
|
||||
<div className="contact-card__role">{role.name_en}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ContactCard;
|
||||
@@ -1,2 +0,0 @@
|
||||
import ContactCard from "./ContactCard";
|
||||
export default ContactCard;
|
||||
@@ -1,11 +0,0 @@
|
||||
.datetime-widget {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
margin: 0 -0.5rem 0.5rem;
|
||||
|
||||
> input {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import React from "react";
|
||||
import "./DatetimeWidget.scss";
|
||||
|
||||
export interface DatetimeWidgetProps {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
onFocus: () => void;
|
||||
onBlur: () => void;
|
||||
required: boolean;
|
||||
disabled: boolean;
|
||||
}
|
||||
export interface DatetimeWidgetState { }
|
||||
|
||||
class DatetimeWidget extends React.Component<DatetimeWidgetProps, DatetimeWidgetState> {
|
||||
render() {
|
||||
const { value, onChange, onFocus, onBlur, required, disabled } = this.props;
|
||||
|
||||
let date;
|
||||
let time;
|
||||
if (value && value.length !== 0) {
|
||||
let rest;
|
||||
[date, rest] = value.split("T");
|
||||
time = rest.slice(0, 5);
|
||||
}
|
||||
|
||||
const commonProps = {
|
||||
onFocus,
|
||||
onBlur,
|
||||
required,
|
||||
disabled,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="datetime-widget">
|
||||
<input
|
||||
type="date"
|
||||
onChange={(event) => onChange(`${event.target.value}T${time}`)}
|
||||
value={date}
|
||||
{...commonProps} />
|
||||
<input
|
||||
type="time"
|
||||
onChange={(event) => onChange(`${date}T${event.target.value}:00`)}
|
||||
value={time}
|
||||
{...commonProps} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DatetimeWidget;
|
||||
@@ -1,2 +0,0 @@
|
||||
import DatetimeWidget from "./DatetimeWidget";
|
||||
export default DatetimeWidget;
|
||||
@@ -0,0 +1,11 @@
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
const Divider = styled.hr`
|
||||
width: 98%;
|
||||
border: 0;
|
||||
border-bottom: 2px solid ${colors.blue1};
|
||||
margin: 0 auto;
|
||||
`;
|
||||
|
||||
export default Divider;
|
||||
@@ -0,0 +1,36 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
interface DropDownBoxProps {
|
||||
onMouseEnter: () => void;
|
||||
onMouseLeave: () => void;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
const Box = styled.div`
|
||||
background-color: ${colors.white};
|
||||
margin-top: 0.8rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 30px;
|
||||
z-index: 20;
|
||||
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: ${colors.darkBlue} !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
`;
|
||||
|
||||
const DropDownBox: React.FC<DropDownBoxProps> = ({ children, onMouseEnter, onMouseLeave, visible }) => (
|
||||
<Box
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
hidden={!visible}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
|
||||
export default DropDownBox;
|
||||
@@ -1,17 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.drop-down-box {
|
||||
background-color: color(white1);
|
||||
margin-top: 0.8rem;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 30px;
|
||||
z-index: 20;
|
||||
|
||||
&,
|
||||
& a {
|
||||
color: color(dark-blue) !important;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from "react";
|
||||
import "./DropDownBox.scss";
|
||||
|
||||
export interface DropDownBoxProps {
|
||||
onMouseEnter: () => void;
|
||||
onMouseLeave: () => void;
|
||||
visible: boolean;
|
||||
}
|
||||
export interface DropDownBoxState {}
|
||||
|
||||
class DropDownBox extends React.Component<DropDownBoxProps, DropDownBoxState> {
|
||||
render() {
|
||||
const { children, onMouseEnter, onMouseLeave, visible } = this.props;
|
||||
return (
|
||||
<div
|
||||
className="drop-down-box"
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
hidden={!visible}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DropDownBox;
|
||||
@@ -1,2 +0,0 @@
|
||||
import DropDownBox from "./DropDownBox";
|
||||
export default DropDownBox;
|
||||
@@ -1,90 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&__content {
|
||||
display: flex;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: color(light-blue);
|
||||
padding: 1.5rem 0;
|
||||
}
|
||||
|
||||
& a {
|
||||
color: color(white1);
|
||||
text-decoration: underline;
|
||||
padding: 0.4rem 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
& p {
|
||||
color: color(white1);
|
||||
margin: 0;
|
||||
padding: 0.4rem 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-basis: 100%;
|
||||
align-items: center;
|
||||
|
||||
&__block {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__contacts {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-flow: row nowrap;
|
||||
font-weight: 200;
|
||||
|
||||
@media screen and (max-width: 600px - 1px) {
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
&__text {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
min-width: 180px;
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
padding-right: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__links {
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
|
||||
@media screen and (max-width: 600px - 1px) {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
&__copyright {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
background-color: color(black1);
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
padding: 1rem 0;
|
||||
|
||||
p {
|
||||
padding: 0.5rem 1rem;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px - 1px) {
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,48 +1,54 @@
|
||||
import React from "react";
|
||||
import FooterMap from "../FooterMap";
|
||||
import Anchor from "../Anchor";
|
||||
import "./Footer.scss";
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
import FooterContent from "./FooterContent";
|
||||
|
||||
export interface FooterProps { }
|
||||
export interface FooterState { }
|
||||
const StyledFooter = styled.footer`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: ${colors.darkBlue};
|
||||
`;
|
||||
|
||||
class Footer extends React.Component<FooterProps, FooterState> {
|
||||
render() {
|
||||
return <React.Fragment>
|
||||
<div className="footer">
|
||||
<div className="footer__content">
|
||||
<div className="footer__info">
|
||||
<div className="footer__info__block">
|
||||
<h4>Aalto-yliopiston Sähköinsinöörikilta ry</h4>
|
||||
<div className="footer__contacts">
|
||||
<div className="footer__text">
|
||||
<p>TUAS-Talo</p>
|
||||
<p>Maarintie 8</p>
|
||||
<p>PL 15500, 00076 Aalto</p>
|
||||
<br></br>
|
||||
<br></br>
|
||||
<p>Y-tunnus: 1627010-1</p>
|
||||
<p>sik-hallitus@list.ayy.fi</p>
|
||||
<Anchor to="/yhteystiedot">Yhteystiedot</Anchor>
|
||||
</div>
|
||||
<div className="footer__links">
|
||||
<Anchor to="/jaseneksi">Jäseneksi</Anchor>
|
||||
<Anchor to="/palaute">Palaute</Anchor>
|
||||
<Anchor to="https://static.sika.sik.party">Arkisto</Anchor>
|
||||
<Anchor to="https://static.sika.sik.party">Materiaalipankki</Anchor>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<FooterMap />
|
||||
</div>
|
||||
<div className="footer__copyright">
|
||||
<p>© Aalto-yliopiston Sähköinsinöörikilta ry</p>
|
||||
<p>webmaster: sik-vtmk@list.ayy.fi</p>
|
||||
</div>
|
||||
</div>
|
||||
</React.Fragment>;
|
||||
const CopyRight = styled.div`
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
color: ${colors.white};
|
||||
background-color: ${colors.black};
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
font-size: 12px;
|
||||
padding: 1rem 0;
|
||||
|
||||
p {
|
||||
padding: 0.5rem 1rem;
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
display: block;
|
||||
text-decoration: underline;
|
||||
padding: 0.4rem 0;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
const Footer: React.FC = () => (
|
||||
<StyledFooter>
|
||||
<FooterContent />
|
||||
<CopyRight>
|
||||
<p>© Aalto-yliopiston Sähköinsinöörikilta ry</p>
|
||||
<a href="mailto:sik-vtmk@list.ayy.fi">webmaster: sik-vtmk@list.ayy.fi</a>
|
||||
</CopyRight>
|
||||
</StyledFooter>
|
||||
)
|
||||
|
||||
export default Footer;
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Anchor from "../Anchor";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
const Content = styled.div`
|
||||
display: flex;
|
||||
|
||||
& > div:first-of-type {
|
||||
padding: 48px 0;
|
||||
flex: 2 1;
|
||||
|
||||
& > * {
|
||||
padding: 0 24px;
|
||||
}
|
||||
}
|
||||
|
||||
h4 {
|
||||
color: ${colors.lightBlue};
|
||||
padding: 24px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: ${colors.white};
|
||||
display: block;
|
||||
text-decoration: underline;
|
||||
padding: 0.4rem 0;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
color: ${colors.white};
|
||||
margin: 0;
|
||||
padding: 0.4rem 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const MarginSpace = styled.div`
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
`;
|
||||
|
||||
const Columns = styled.div`
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
& > div > div {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const Map = styled.div`
|
||||
flex: 1;
|
||||
@media screen and (max-width: 800px) {
|
||||
display: none;
|
||||
}
|
||||
iframe {
|
||||
border: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const FooterContent: React.FC = () => (
|
||||
<Content>
|
||||
<div>
|
||||
<MarginSpace>
|
||||
<h4>Aalto-yliopiston Sähköinsinöörikilta ry</h4>
|
||||
<Columns>
|
||||
<div>
|
||||
<div>
|
||||
<p>TUAS-Talo</p>
|
||||
<p>Maarintie 8</p>
|
||||
<p>PL 15500, 00076 Aalto</p>
|
||||
</div>
|
||||
<div>
|
||||
<p>Y-tunnus: 1627010-1</p>
|
||||
<p>sik-hallitus@list.ayy.fi</p>
|
||||
<Anchor to="/yhteystiedot">Yhteystiedot</Anchor>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<Anchor to="/jaseneksi">Jäseneksi</Anchor>
|
||||
<Anchor to="/palaute">Palaute</Anchor>
|
||||
<Anchor to="https://static.sika.sik.party">Arkisto</Anchor>
|
||||
<Anchor to="https://static.sika.sik.party">Materiaalipankki</Anchor>
|
||||
</div>
|
||||
</Columns>
|
||||
</MarginSpace>
|
||||
</div>
|
||||
|
||||
<Map>
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1983.6122518000927!2d24.81667815176689!3d60.187150048900186!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x468df5eb3cb4ecf1%3A0x3480cbfeedcc07b6!2sMaarintie+8%2C+02150+Espoo!5e0!3m2!1sfi!2sfi!4v1542413548247"
|
||||
width="100%"
|
||||
height="100%"
|
||||
/>
|
||||
</Map>
|
||||
</Content>
|
||||
)
|
||||
|
||||
export default FooterContent;
|
||||
@@ -1,2 +0,0 @@
|
||||
import Footer from "./Footer";
|
||||
export default Footer;
|
||||
@@ -1,13 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.footer-map {
|
||||
display: flex;
|
||||
|
||||
@media screen and (max-width: 1200px - 1px) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
iframe {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from "react";
|
||||
import "./FooterMap.scss";
|
||||
|
||||
const AnyReactComponent = ({ text }) => <div>{text}</div>;
|
||||
|
||||
export interface FooterMapProps {}
|
||||
|
||||
export interface FooterMapState {}
|
||||
|
||||
class FooterMap extends React.Component<FooterMapProps, FooterMapState> {
|
||||
render() {
|
||||
return (
|
||||
<div className="footer-map">
|
||||
<iframe
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1983.6122518000927!2d24.81667815176689!3d60.187150048900186!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x468df5eb3cb4ecf1%3A0x3480cbfeedcc07b6!2sMaarintie+8%2C+02150+Espoo!5e0!3m2!1sfi!2sfi!4v1542413548247"
|
||||
width="600"
|
||||
height="350"
|
||||
>
|
||||
</iframe>
|
||||
</div>
|
||||
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default FooterMap;
|
||||
@@ -1,2 +0,0 @@
|
||||
import FooterMap from "./FooterMap";
|
||||
export default FooterMap;
|
||||
@@ -0,0 +1,72 @@
|
||||
import React, { useState, useEffect, useRef } from "react";
|
||||
import Navigation from "./Navigation";
|
||||
import throttle from "lodash/throttle";
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
import NavigationMobile from "./NavigationMobile";
|
||||
import HeaderLogo from "./HeaderLogo";
|
||||
|
||||
const StyledHeader = styled.header<{isHidden?: boolean}>`
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
@media screen and (max-width: 600px) {
|
||||
flex-flow: column nowrap;
|
||||
}
|
||||
`;
|
||||
|
||||
const Sticky = styled.div`
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
|
||||
padding: 0 1rem;
|
||||
background-color: ${colors.darkBlue};
|
||||
transition: all 200ms ease-out;
|
||||
|
||||
${(p) => p.isHidden ? (`
|
||||
transition: all 200ms ease-in;
|
||||
transform: translateY(-100%);
|
||||
`) : null}
|
||||
`;
|
||||
|
||||
|
||||
const PREVENT_IS_HIDDEN_Y = 150;
|
||||
|
||||
const Header: React.FC = () => {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [isHidden, setHidden] = useState(false);
|
||||
const yCoord = useRef(0);
|
||||
|
||||
const handleScroll = () => {
|
||||
const newCoord = window.pageYOffset;
|
||||
if (!mobileMenuOpen && newCoord > yCoord.current && newCoord > PREVENT_IS_HIDDEN_Y) {
|
||||
setHidden(true);
|
||||
} else {
|
||||
setHidden(false);
|
||||
}
|
||||
yCoord.current = newCoord;
|
||||
};
|
||||
|
||||
const handleMobileMenuClick = () => setMobileMenuOpen(!mobileMenuOpen);
|
||||
|
||||
useEffect(() => {
|
||||
const func = throttle(handleScroll, 200);
|
||||
// Prevents hide when clicking mobileMenuOpen
|
||||
handleScroll();
|
||||
window.addEventListener("scroll", func);
|
||||
return () => window.removeEventListener("scroll", func);
|
||||
}, [isHidden, mobileMenuOpen]);
|
||||
|
||||
|
||||
return (
|
||||
<Sticky>
|
||||
<StyledHeader isHidden={isHidden}>
|
||||
<HeaderLogo />
|
||||
<Navigation onMobileMenuOpen={handleMobileMenuClick} />
|
||||
</StyledHeader>
|
||||
<NavigationMobile mobileMenuOpen={mobileMenuOpen} />
|
||||
</Sticky>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -1,39 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
background-color: color(dark-blue);
|
||||
transition: all 200ms ease-out;
|
||||
|
||||
&.hidden {
|
||||
transition: all 200ms ease-in;
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-container {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 0 1rem;
|
||||
|
||||
@media screen and (max-width: 600px - 1px) {
|
||||
flex-flow: column nowrap;
|
||||
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
margin: 1rem 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 300px;
|
||||
margin: 1rem 1rem;
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
import React, { FC, useState, useEffect, useRef } from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import "./Header.scss";
|
||||
import NavbarDropdownLink from "../NavbarDropdownLink/NavbarDropdownLink";
|
||||
import NavbarChildLink from "../NavbarChildLink/NavbarChildLink";
|
||||
import Navigation from "../Navigation";
|
||||
import TitleImage from "@assets/img/SIK_RGB_W_side.png";
|
||||
import classNames from "classnames";
|
||||
import throttle from "lodash/throttle";
|
||||
|
||||
export interface HeaderProps { }
|
||||
export interface HeaderState {
|
||||
mobileMenuOpen: boolean;
|
||||
}
|
||||
|
||||
const renderNavigationDesktopItems = () => {
|
||||
return (
|
||||
<>
|
||||
<NavbarDropdownLink to="/kilta" text="Kilta ›">
|
||||
<NavbarChildLink to="/kilta/toiminta">Toiminta</NavbarChildLink>
|
||||
<NavbarChildLink to="/kilta/fuksi">Fuksi</NavbarChildLink>
|
||||
<NavbarChildLink to="https://static.sika.sik.party">Arkisto</NavbarChildLink>
|
||||
</NavbarDropdownLink>
|
||||
<NavbarDropdownLink to="/opinnot_ja_ura" text="Opinnot ja ura"></NavbarDropdownLink>
|
||||
<NavbarDropdownLink to="/yritysyhteistyo" text="Yritysyhteistyö"></NavbarDropdownLink>
|
||||
<NavbarDropdownLink to="/yhteystiedot" text="Yhteystiedot">
|
||||
{/* <NavbarChildLink to="https://en.wikipedia.org/wiki/Gay">Simo Höglund</NavbarChildLink> */}
|
||||
</NavbarDropdownLink>
|
||||
<NavbarDropdownLink to="/in_english" text="In English"></NavbarDropdownLink>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const renderNavigationMobileItems = () => {
|
||||
return (
|
||||
<>
|
||||
<NavbarDropdownLink to="/kilta" text="Kilta ›" exploded>
|
||||
<NavbarChildLink to="/kilta/toiminta">Toiminta</NavbarChildLink>
|
||||
<NavbarChildLink to="/kilta/fuksi">Fuksi</NavbarChildLink>
|
||||
<NavbarChildLink to="https://static.sika.sik.party">Arkisto</NavbarChildLink>
|
||||
</NavbarDropdownLink>
|
||||
<NavbarDropdownLink to="/opinnot_ja_ura" text="Opinnot ja ura" exploded />
|
||||
<NavbarDropdownLink to="/yritysyhteistyo" text="Yritysyhteistyö" exploded />
|
||||
<NavbarDropdownLink to="/yhteystiedot" text="Yhteystiedot" exploded>
|
||||
{/* <NavbarChildLink to="https://en.wikipedia.org/wiki/Gay">Simo Höglund</NavbarChildLink> */}
|
||||
</NavbarDropdownLink>
|
||||
<NavbarDropdownLink to="/in_english" text="In English" exploded />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
const PREVENT_IS_HIDDEN_Y = 150;
|
||||
|
||||
const Header: FC<HeaderProps> = () => {
|
||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||
const [isHidden, setHidden] = useState(false);
|
||||
const yCoord = useRef(0);
|
||||
|
||||
const handleScroll = () => {
|
||||
const newCoord = window.pageYOffset;
|
||||
if (!mobileMenuOpen && newCoord > yCoord.current && newCoord > PREVENT_IS_HIDDEN_Y) {
|
||||
setHidden(true);
|
||||
} else {
|
||||
setHidden(false);
|
||||
}
|
||||
yCoord.current = newCoord;
|
||||
};
|
||||
|
||||
const handleMobileMenuClick = () => setMobileMenuOpen(!mobileMenuOpen);
|
||||
|
||||
useEffect(() => {
|
||||
const func = throttle(handleScroll, 200);
|
||||
// Prevents hide when clicking mobileMenuOpen
|
||||
handleScroll();
|
||||
window.addEventListener("scroll", func);
|
||||
return () => window.removeEventListener("scroll", func);
|
||||
}, [isHidden, mobileMenuOpen]);
|
||||
|
||||
const desktopItems = renderNavigationDesktopItems();
|
||||
const mobileItems = renderNavigationMobileItems();
|
||||
|
||||
const classes = classNames(
|
||||
"header",
|
||||
{ "hidden": isHidden }
|
||||
)
|
||||
|
||||
return (
|
||||
<header className={classes}>
|
||||
<div className="nav-container">
|
||||
<Link to="/">
|
||||
<img className="logo" src={TitleImage} />
|
||||
</Link>
|
||||
<Navigation
|
||||
onMobileMenuOpen={handleMobileMenuClick}
|
||||
items={desktopItems}
|
||||
/>
|
||||
</div>
|
||||
<div className="navigation-mobile-menu" hidden={!mobileMenuOpen}>
|
||||
{mobileItems}
|
||||
</div>
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
||||
export default Header;
|
||||
@@ -1,2 +0,0 @@
|
||||
import Header from "./Header";
|
||||
export default Header;
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { Link } from "react-router-dom";
|
||||
import TitleImage from "@assets/img/SIK_RGB_W_side.png";
|
||||
|
||||
const Logo = styled.img`
|
||||
max-width: 300px;
|
||||
margin: 1rem 1rem;
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
max-width: 100% !important;
|
||||
margin: 1rem 0 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const HeaderLogo: React.FC = () => (
|
||||
<Link to="/">
|
||||
<Logo
|
||||
src={TitleImage}
|
||||
alt="logo"
|
||||
/>
|
||||
</Link>
|
||||
)
|
||||
|
||||
export default HeaderLogo;
|
||||
@@ -0,0 +1,51 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
padding: 0;
|
||||
|
||||
min-height: 75vh;
|
||||
|
||||
section {
|
||||
padding: 2rem 6rem;
|
||||
@media screen and (max-width: 800px) {
|
||||
padding: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
aside {
|
||||
padding: 3rem 6rem;
|
||||
align-items: center;
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
padding: 2rem 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
& > div {
|
||||
flex: 8;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
color: ${colors.white};
|
||||
background-color: ${colors.darkBlue};
|
||||
a:hover {
|
||||
color: ${colors.white};
|
||||
}
|
||||
`;
|
||||
|
||||
const Hero: React.FC = ({ children }) => (
|
||||
<Container>
|
||||
{children}
|
||||
</Container>
|
||||
)
|
||||
|
||||
export default Hero;
|
||||
@@ -0,0 +1,101 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
import Anchor from "@components/Anchor";
|
||||
|
||||
interface HeroAsideItemProps {
|
||||
header: string;
|
||||
text?: string;
|
||||
link: string;
|
||||
linkText: string;
|
||||
}
|
||||
|
||||
const Article = styled.article`
|
||||
margin-bottom: 1rem;
|
||||
`;
|
||||
|
||||
export const HeroAsideItem: React.FC<HeroAsideItemProps> = ({ header, text, link, linkText }) => (
|
||||
<Article>
|
||||
<h2>{header}</h2>
|
||||
{text && (
|
||||
<p>{text}</p>
|
||||
)}
|
||||
<Anchor to={link}>
|
||||
<h6>
|
||||
{linkText}
|
||||
</h6>
|
||||
</Anchor>
|
||||
</Article>
|
||||
)
|
||||
|
||||
type Colors = "darkBlue" | "lightTurquoise";
|
||||
|
||||
interface HeroAsideProps {
|
||||
bgColor: Colors;
|
||||
}
|
||||
|
||||
// TODO: Color combos
|
||||
const Aside = styled.aside<{ colors: string }>`
|
||||
${(p) => p.colors}
|
||||
flex: 4;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
& > div {
|
||||
max-width: 350px;
|
||||
|
||||
h2 {
|
||||
word-break: break-word;
|
||||
hyphens: auto;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 3px;
|
||||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 0;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
|
||||
& > p {
|
||||
font-weight: 600;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
a {
|
||||
line-height: 2rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
text-decoration: none;
|
||||
letter-spacing: 0.1rem;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const textColors = (bgColor: Colors) => {
|
||||
switch(bgColor) {
|
||||
case "darkBlue":
|
||||
return `
|
||||
background-color: ${colors[bgColor]};
|
||||
color: ${colors.lightBlue};
|
||||
`;
|
||||
case "lightTurquoise":
|
||||
return `
|
||||
background-color: ${colors[bgColor]};
|
||||
color: ${colors.darkBlue};
|
||||
`;
|
||||
default: return ""
|
||||
}
|
||||
}
|
||||
|
||||
const HeroAside: React.FC<HeroAsideProps> = ({ bgColor, children}) => (
|
||||
<Aside colors={textColors(bgColor)}>
|
||||
<div>
|
||||
{children}
|
||||
</div>
|
||||
</Aside>
|
||||
)
|
||||
|
||||
export default HeroAside;
|
||||
@@ -1,36 +0,0 @@
|
||||
@import "../../../assets/scss/globals";
|
||||
|
||||
|
||||
.hero-aside-item {
|
||||
max-width: 300px;
|
||||
line-height: 16px;
|
||||
margin-bottom: 1rem;
|
||||
|
||||
@media screen and (max-width: 600px - 1px) {
|
||||
p,
|
||||
a {
|
||||
font-size: 16px !important;
|
||||
line-height: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
h2 {
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 3px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
h6 {
|
||||
color: color(blue1);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
h6:hover {
|
||||
color: color(white1);
|
||||
}
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from "react";
|
||||
import "./HeroAsideItem.scss";
|
||||
import Anchor from "../../Anchor";
|
||||
|
||||
interface HeroAsideItemProps {
|
||||
title: string;
|
||||
linkText: string;
|
||||
linkHref: string;
|
||||
}
|
||||
|
||||
const HeroAsideItem: React.FC<HeroAsideItemProps> = ({ title, linkText, linkHref, children }) => (
|
||||
<div className="hero-aside-item">
|
||||
<h2>{title}</h2>
|
||||
<p>{children}</p>
|
||||
<Anchor to={linkHref}>
|
||||
<h6>{linkText} ›</h6>
|
||||
</Anchor>
|
||||
</div>
|
||||
)
|
||||
|
||||
export default HeroAsideItem;
|
||||
@@ -1,43 +0,0 @@
|
||||
@import "../../../assets/scss/globals";
|
||||
|
||||
|
||||
.hero-aside-section {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
flex: 4;
|
||||
|
||||
@media screen and (max-width: 800px - 1px) {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
text-align: left;
|
||||
padding: 3rem 1rem 2rem;
|
||||
|
||||
&.dark-blue {
|
||||
background-color: color(dark-blue);
|
||||
|
||||
h2 {
|
||||
color: color(light-blue);
|
||||
}
|
||||
|
||||
p {
|
||||
font-weight: 100;
|
||||
color: color(white1);
|
||||
}
|
||||
}
|
||||
|
||||
&.light-turquoise {
|
||||
background-color: color(light-turquoise);
|
||||
|
||||
h2,
|
||||
p {
|
||||
color: color(dark-blue);
|
||||
}
|
||||
}
|
||||
|
||||
&.light-blue {
|
||||
background-color: color(light-blue);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from "react";
|
||||
import "./HeroAsideSection.scss";
|
||||
import ColorDiv, { ColorDivProps } from "../../ColorDiv/ColorDiv";
|
||||
|
||||
|
||||
export interface HeroAsideSectionProps { }
|
||||
export interface HeroAsideSectionState { }
|
||||
|
||||
class HeroAsideSection extends React.Component<HeroAsideSectionProps & ColorDivProps, HeroAsideSectionState> {
|
||||
render() {
|
||||
const className = "hero-aside-section";
|
||||
return (
|
||||
<ColorDiv className={className} {...this.props}>
|
||||
<div className="hero-aside-section-block">
|
||||
{this.props.children}
|
||||
</div>
|
||||
</ColorDiv>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default HeroAsideSection;
|
||||
@@ -1,51 +0,0 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
align-items: center;
|
||||
margin-top: 15vh;
|
||||
flex: 6;
|
||||
text-align: center;
|
||||
font-weight: 100;
|
||||
line-height: 24px;
|
||||
|
||||
& > div {
|
||||
padding: 2rem 1rem 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
max-width: 600px;
|
||||
line-height: 40px;
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
font-size: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 400px;
|
||||
font-weight: 100;
|
||||
}
|
||||
|
||||
.hero-button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 20%;
|
||||
|
||||
&-row {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const HeroMainSection: React.FC = ({children}) => {
|
||||
return (
|
||||
<Container>
|
||||
{children}
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default HeroMainSection;
|
||||
@@ -0,0 +1,39 @@
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
const Buttons = styled.div<{row?: boolean}>`
|
||||
min-width: 20%;
|
||||
max-width: fit-content;
|
||||
margin: auto;
|
||||
display: flex;
|
||||
flex-flow: ${(p) => p.row ? "row" : "column"} wrap;
|
||||
|
||||
a {
|
||||
display: contents;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
button {
|
||||
color: ${colors.blue1};
|
||||
background-color: transparent;
|
||||
padding: 0.8rem 2rem;
|
||||
margin: 0.5rem;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: .1em;
|
||||
text-transform: uppercase;
|
||||
border: 1px solid ${colors.lightTurquoise};
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
color: ${colors.white};
|
||||
}
|
||||
|
||||
&:active,
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default Buttons;
|
||||
@@ -0,0 +1,48 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
interface HeroPrimarySectionProps {
|
||||
header: string;
|
||||
text?: string;
|
||||
}
|
||||
|
||||
const Section = styled.section`
|
||||
margin: 10vh auto 0;
|
||||
max-width: 800px;
|
||||
text-align: center;
|
||||
line-height: 1.5rem;
|
||||
|
||||
h1 {
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
p {
|
||||
max-width: 400px;
|
||||
margin: 1em auto;
|
||||
font-weight: 200;
|
||||
}
|
||||
|
||||
a {
|
||||
color: ${colors.blue1};
|
||||
font-weight: 600;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
color: ${colors.white};
|
||||
text-decoration: none
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const HeroPrimarySection: React.FC<HeroPrimarySectionProps> = ({ header, text, children }) => (
|
||||
<Section>
|
||||
<h1>{header}</h1>
|
||||
{text && (
|
||||
<p>{text}</p>
|
||||
)}
|
||||
{children}
|
||||
</Section>
|
||||
)
|
||||
|
||||
export default HeroPrimarySection;
|
||||
+21
-18
@@ -1,10 +1,6 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import ColorDiv from "../../ColorDiv/ColorDiv";
|
||||
|
||||
interface HeroSecondarySectionItemProps {
|
||||
note?: string;
|
||||
}
|
||||
import { colors } from "@theme/colors";
|
||||
|
||||
const Note = styled.span`
|
||||
color: white;
|
||||
@@ -16,6 +12,10 @@ const Note = styled.span`
|
||||
font-weight: bold;
|
||||
margin-right: 2rem;
|
||||
margin-top: -0.5rem;
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
`;
|
||||
|
||||
const Item = styled.div`
|
||||
@@ -25,6 +25,10 @@ const Item = styled.div`
|
||||
margin: 1rem 2rem 1rem;
|
||||
`;
|
||||
|
||||
interface HeroSecondarySectionItemProps {
|
||||
note?: string;
|
||||
}
|
||||
|
||||
export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> = ({note, children}) => (
|
||||
<Item>
|
||||
<Note>
|
||||
@@ -34,33 +38,32 @@ export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> =
|
||||
</Item>
|
||||
)
|
||||
|
||||
const Container = styled(ColorDiv)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
const Section = styled.section`
|
||||
background-color: ${colors.green1};
|
||||
color: ${colors.darkBlue};
|
||||
|
||||
h1 {
|
||||
padding: 1em 0;
|
||||
padding: 1rem 0;
|
||||
text-align: center;
|
||||
}
|
||||
`;
|
||||
|
||||
const Items = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-flow: row wrap;
|
||||
`;
|
||||
|
||||
interface HeroSecondarySectionProps {
|
||||
title: string;
|
||||
heading: string;
|
||||
}
|
||||
|
||||
const HeroSecondarySection: React.FC<HeroSecondarySectionProps> = ({title, children}) => (
|
||||
<Container textColor="dark-blue" backgroundColor="green1">
|
||||
<h1>{title}</h1>
|
||||
const HeroSecondarySection: React.FC<HeroSecondarySectionProps> = ({ heading, children }) => (
|
||||
<Section>
|
||||
<h1>{heading}</h1>
|
||||
<Items>
|
||||
{children}
|
||||
</Items>
|
||||
</Container>
|
||||
</Section>
|
||||
)
|
||||
|
||||
export default HeroSecondarySection;
|
||||
export default HeroSecondarySection;
|
||||
@@ -0,0 +1,7 @@
|
||||
export { default as Hero } from "./Hero";
|
||||
export { default as HeroPrimarySection } from "./HeroPrimarySection";
|
||||
export { default as HeroSecondarySection } from "./HeroSecondarySection";
|
||||
export { HeroSecondarySectionItem } from "./HeroSecondarySection";
|
||||
export { default as HeroAside } from "./HeroAside";
|
||||
export { HeroAsideItem as HeroAsideItem } from "./HeroAside";
|
||||
export { default as HeroPrimaryButtons } from "./HeroPrimaryButtons";
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import "./Icon.scss";
|
||||
import styled from "styled-components";
|
||||
|
||||
export enum IconType {
|
||||
Facebook,
|
||||
@@ -10,12 +10,11 @@ export enum IconType {
|
||||
GBFlag,
|
||||
}
|
||||
|
||||
export interface IconProps {
|
||||
interface IconProps {
|
||||
name: IconType;
|
||||
link?: string;
|
||||
onClick?: (event?: any) => void;
|
||||
}
|
||||
export interface IconState { }
|
||||
|
||||
const nameToIcon = (name: IconType): JSX.Element | string => {
|
||||
if (name === IconType.Facebook) {
|
||||
@@ -68,27 +67,40 @@ const nameToIcon = (name: IconType): JSX.Element | string => {
|
||||
return null;
|
||||
};
|
||||
|
||||
class Icon extends React.Component<IconProps, IconState> {
|
||||
render() {
|
||||
const { link, name, onClick } = this.props;
|
||||
const elem = nameToIcon(name);
|
||||
if (link) {
|
||||
return (
|
||||
<a
|
||||
href={link}
|
||||
className="so-me-icon"
|
||||
onClick={onClick}
|
||||
>
|
||||
{elem}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
const SomeIcon = styled.a`
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: center;
|
||||
margin: 1em;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
const NormalIcon = styled.span`
|
||||
/* stylelint-disable-next-line no-empty-source */
|
||||
`;
|
||||
|
||||
const Icon: React.FC<IconProps> = ({ link, name, onClick }) => {
|
||||
const elem = nameToIcon(name);
|
||||
if (link) {
|
||||
return (
|
||||
<span className="icon" onClick={onClick}>
|
||||
<SomeIcon
|
||||
href={link}
|
||||
onClick={onClick}
|
||||
>
|
||||
{elem}
|
||||
</span>
|
||||
</SomeIcon>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<NormalIcon role="img" onClick={onClick}>
|
||||
{elem}
|
||||
</NormalIcon>
|
||||
);
|
||||
}
|
||||
|
||||
export default Icon;
|
||||
@@ -1,13 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.so-me-icon {
|
||||
display: flex;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: center;
|
||||
margin: 1em;
|
||||
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
import Icon from "./Icon";
|
||||
export default Icon;
|
||||
@@ -0,0 +1,15 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Box = styled.div`
|
||||
justify-content: flex-end;
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const InfoBox: React.FC = ({ children }) => (
|
||||
<Box>
|
||||
{children}
|
||||
</Box>
|
||||
)
|
||||
|
||||
export default InfoBox;
|
||||
@@ -1,6 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.info-box {
|
||||
justify-content: flex-end;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
import React from "react";
|
||||
import "./InfoBox.scss";
|
||||
|
||||
export interface InfoBoxProps {}
|
||||
export interface InfoBoxState {}
|
||||
|
||||
class InfoBox extends React.Component<InfoBoxProps, InfoBoxState> {
|
||||
constructor(props: InfoBoxProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { children } = this.props;
|
||||
return (
|
||||
<div className="info-box">
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default InfoBox;
|
||||
@@ -1,2 +0,0 @@
|
||||
import InfoBox from "./InfoBox";
|
||||
export default InfoBox;
|
||||
@@ -0,0 +1,16 @@
|
||||
import React from "react";
|
||||
|
||||
interface JsonLDProps {
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
const JsonLD: React.FC<JsonLDProps> = ({ data }) => (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(data),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
|
||||
export default JsonLD;
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from "react";
|
||||
|
||||
export interface JsonLDProps {
|
||||
data: object;
|
||||
}
|
||||
|
||||
class JsonLD extends React.Component<JsonLDProps, undefined> {
|
||||
render() {
|
||||
return (
|
||||
<script
|
||||
type="application/ld+json"
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: JSON.stringify(this.props.data),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default JsonLD;
|
||||
@@ -1,2 +0,0 @@
|
||||
import JsonLD from "./JsonLD";
|
||||
export default JsonLD;
|
||||
@@ -1,21 +0,0 @@
|
||||
@import "../../assets/scss/globals";
|
||||
|
||||
.main-section {
|
||||
display: flex;
|
||||
flex: 2;
|
||||
flex-flow: column;
|
||||
padding: 0 3rem;
|
||||
|
||||
& > h3 {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
& > h6 {
|
||||
color: color(orange1);
|
||||
}
|
||||
|
||||
& > p {
|
||||
margin-block-start: 0.5rem;
|
||||
margin-block-end: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from "react";
|
||||
import "./MainSection.scss";
|
||||
import ColorDiv, { ColorDivProps } from "../ColorDiv/ColorDiv";
|
||||
import classNames from "classnames";
|
||||
|
||||
export interface MainSectionProps { }
|
||||
export interface MainSectionState { }
|
||||
|
||||
class MainSection extends React.Component<MainSectionProps & ColorDivProps, MainSectionState> {
|
||||
render() {
|
||||
const { children, className, ...props } = this.props;
|
||||
const classes = classNames(
|
||||
"main-section",
|
||||
className
|
||||
);
|
||||
|
||||
return (
|
||||
<ColorDiv className={classes} {...props}>
|
||||
{children}
|
||||
</ColorDiv>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default MainSection;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user