Merge branch 'master' into 'production'

Huge production merge

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!18
This commit is contained in:
Aarni Halinen
2020-12-12 14:15:45 +00:00
231 changed files with 7039 additions and 7800 deletions
-16
View File
@@ -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
View File
@@ -1 +1 @@
API_URL=http://web.sik.party:8000/api
API_URL=https://api.dev.sik.party/api
+1 -1
View File
@@ -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:
-102
View File
@@ -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"
}
]
}
+2252 -2682
View File
File diff suppressed because it is too large Load Diff
+17 -21
View File
@@ -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",
-5
View File
@@ -1,5 +0,0 @@
@import "../../assets/scss/globals";
.{{ dashCase name }} {
}
-25
View File
@@ -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}}
-2
View File
@@ -1,2 +0,0 @@
import {{ properCase name }} from "./{{ properCase name }}";
export default {{ properCase name }};
-17
View File
@@ -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;
}
}
-3
View File
@@ -1,3 +0,0 @@
.{{ dashCase name }} {
}
-21
View File
@@ -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 }};
-13
View File
@@ -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
View File
@@ -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
}
]
});
};
-59
View File
@@ -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;
}
}
+68 -40
View File
@@ -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;
-2
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import AccordionIcon from "./AccordionIcon";
export default AccordionIcon;
+35
View File
@@ -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;
+27
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import AdminHeader from "./AdminHeader";
export default AdminHeader;
+57
View File
@@ -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&nbsp;</StyledLink>
<StyledLink to="/admin/events" path={path}>Events&nbsp;</StyledLink>
<StyledLink to="/admin/feed" path={path}>Feed&nbsp;</StyledLink>
<StyledLink to="/admin/signups" path={path}>Signup forms&nbsp;</StyledLink>
<StyledLink to="/admin/jobads" path={path}>Job advertisements&nbsp;</StyledLink>
<StyledLink to="https://static.sika.sik.party/admin" path={path}>Files&nbsp;</StyledLink>
<StyledLink id="admin-sidebar-logout" to="/admin/logout" path={path}>Logout&nbsp;</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;
-2
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import AdminSidebarLink from "./AdminSidebarLink";
export default AdminSidebarLink;
+25
View File
@@ -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;
-29
View File
@@ -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;
-2
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import AsideSection from "./AsideSection";
export default AsideSection;
+73
View File
@@ -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;
-61
View File
@@ -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;
}
}
-25
View File
@@ -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>
);
}
}
-2
View File
@@ -1,2 +0,0 @@
import Button from "./Button";
export default Button;
+119
View File
@@ -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ää&nbsp;
</button>
</Anchor>
);
return (
<Card
title={title}
start_time={datetime}
text={text}
img={img}
button={button}
/>
);
}
export default WrappedCard;
-93
View File
@@ -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;
}
}
}
-53
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import Card from "./Card";
export default Card;
-66
View File
@@ -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;
}
}
-34
View File
@@ -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;
+61
View File
@@ -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;
+70
View File
@@ -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;
-2
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import DatetimeWidget from "./DatetimeWidget";
export default DatetimeWidget;
+11
View File
@@ -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;
+36
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import DropDownBox from "./DropDownBox";
export default DropDownBox;
-90
View File
@@ -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;
}
}
}
+48 -42
View File
@@ -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>&copy; 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>&copy; 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;
+104
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import Footer from "./Footer";
export default Footer;
-13
View File
@@ -1,13 +0,0 @@
@import "../../assets/scss/globals";
.footer-map {
display: flex;
@media screen and (max-width: 1200px - 1px) {
display: none;
}
iframe {
border: none;
}
}
-26
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import FooterMap from "./FooterMap";
export default FooterMap;
+72
View File
@@ -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;
-39
View File
@@ -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;
}
}
-105
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import Header from "./Header";
export default Header;
+25
View File
@@ -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;
+51
View File
@@ -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;
+101
View File
@@ -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;
@@ -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;
+7
View File
@@ -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;
-13
View File
@@ -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;
}
}
-2
View File
@@ -1,2 +0,0 @@
import Icon from "./Icon";
export default Icon;
+15
View File
@@ -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;
-6
View File
@@ -1,6 +0,0 @@
@import "../../assets/scss/globals";
.info-box {
justify-content: flex-end;
text-align: center;
}
-22
View File
@@ -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;
-2
View File
@@ -1,2 +0,0 @@
import InfoBox from "./InfoBox";
export default InfoBox;
+16
View File
@@ -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;
-20
View File
@@ -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;
-2
View File
@@ -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