Merge branch 'master' into 'production'

Signup modifications

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!9
This commit is contained in:
Aarni Halinen
2020-07-24 19:35:09 +00:00
174 changed files with 5573 additions and 2578 deletions
+17 -1
View File
@@ -4,13 +4,29 @@
"react"
],
"plugins": [
"react-hot-loader/babel"
"react-hot-loader/babel",
[
"babel-plugin-styled-components",
{ "ssr": true, "displayName": true, "preprocess": false }
]
],
"env": {
"production": {
"plugins": [
[
"babel-plugin-styled-components",
{ "ssr": true, "displayName": true, "preprocess": false }
]
],
"presets": ["minify"]
},
"test": {
"plugins": [
[
"babel-plugin-styled-components",
{ "ssr": true, "displayName": true, "preprocess": false }
]
],
"presets": ["env", "react"]
}
}
+15 -15
View File
@@ -88,25 +88,25 @@ publish:prod:
deploy:dev:
stage: deploy
image: alpine:latest
environment:
name: dev
url: http://web.sik.party:3000
image: docker:stable
only:
- master
environment:
name: dev
url: dev.sik.party
variables:
DOCKER_HOST: $DEV_CI_DOCKER_HOST
DOCKER_TLS_VERIFY: 1
before_script:
- pwd
- apk add --update openssh
- ssh -V
- mkdir -p ~/.ssh
- echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- '[[ -f /.dockerenv ]] && echo -e "Host *\n\tStrictHostKeyChecking no\n\n" > ~/.ssh/config'
- mkdir -p ~/.docker
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
script:
- scp docker-compose.yml $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment-frontend/docker-compose.yml
- scp .deploy_dev.sh $DEV_SSH_USER@$DEV_SSH_HOST:~/deployment-frontend/deploy_dev.sh
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY"
- ssh $DEV_SSH_USER@$DEV_SSH_HOST "bash ~/deployment-frontend/deploy_dev.sh \"$IMAGE_NAME:latest\""
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
deploy:prod:
stage: deploy
+7 -3
View File
@@ -1,11 +1,15 @@
FROM node:12-alpine
FROM node:12-alpine as builder
RUN apk add --no-cache libpng-dev gcc make g++ zlib-dev bash lcms2-dev autoconf automake libtool nasm
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
RUN npm ci --only-prod
COPY . ./
ENV API_URL https://api.dev.sik.party/api
RUN npm run build
FROM fnichol/uhttpd AS server
EXPOSE 3000
CMD ["npm", "start"]
COPY --from=builder /app/dist /www
ENTRYPOINT ["/usr/sbin/run_uhttpd", "-f", "-p", "3000", "-h", "/www", "-E", "/"]
+1 -1
View File
@@ -11,7 +11,7 @@ Minimal starter kit with hot module replacement (HMR) for rapid development.
* [Jest](https://facebook.github.io/jest/) - Testing framework for React applications
* Production build script
* Image loading/minification using [Image Webpack Loader](https://github.com/tcoopman/image-webpack-loader)
* Typescript compiling using [Awesome Typescript Loader](https://github.com/s-panferov/awesome-typescript-loader) (5.x)
* Typescript compiling using [TS-Loader](https://webpack.js.org/guides/typescript/)
* Code quality (linting) for Typescript and SASS/CSS.
## Installation
+4 -4
View File
@@ -1,6 +1,6 @@
// Shared config (dev and prod)
const {resolve} = require("path");
const {CheckerPlugin} = require("awesome-typescript-loader");
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
const StyleLintPlugin = require("stylelint-webpack-plugin");
const webpack = require('webpack');
const Dotenv = require("dotenv-webpack");
@@ -12,7 +12,8 @@ module.exports = function (env, argv) {
};
config.resolve = {
extensions: [".ts", ".tsx", ".js", ".jsx"]
extensions: [".ts", ".tsx", ".js", ".jsx"],
plugins: [new TsconfigPathsPlugin()]
};
config.context = resolve(__dirname, "../../src");
config.module = {
@@ -25,7 +26,7 @@ module.exports = function (env, argv) {
});
config.module.rules.push({
test: /\.tsx?$/,
use: ["babel-loader", "awesome-typescript-loader"]
use: ["babel-loader", "ts-loader"]
});
config.module.rules.push({
@@ -43,7 +44,6 @@ module.exports = function (env, argv) {
config.plugins = [
new webpack.DefinePlugin(envVars),
new CheckerPlugin(),
new StyleLintPlugin(),
new Dotenv({
path: "./.env"
+2504 -1023
View File
File diff suppressed because it is too large Load Diff
+22 -12
View File
@@ -24,9 +24,9 @@
"build:server": "webpack -p --config=configs/webpack/prod.js --env.platform=server",
"build:client": "webpack -p --config=configs/webpack/prod.js --env.platform=client",
"lint": "npm run lint:es && npm run lint:sass",
"lint:es": "eslint './src/**/*.ts*'",
"lint:es:fix": "eslint --fix './src/**/*.ts*'",
"lint:sass": "stylelint ./src/**/**/*.scss ./src/**/*.scss ./src/*.scss",
"lint:es": "eslint \"./src/**/*.{ts,tsx}\"",
"lint:es:fix": "eslint --fix \"./src/**/*.{ts,tsx}\"",
"lint:sass": "stylelint \"./src/**/*.{scss,css}\"",
"start": "npm run start-dev",
"start-dev": "webpack-dev-server --config=configs/webpack/dev.js",
"serve": "node dist/js/server.js",
@@ -45,17 +45,22 @@
}
},
"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-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",
"awesome-typescript-loader": "5.2.1",
"babel-cli": "6.26.0",
"babel-core": "6.26.3",
"babel-loader": "7.1.5",
"babel-plugin-styled-components": "1.10.7",
"babel-preset-env": "1.7.0",
"babel-preset-minify": "0.4.3",
"babel-preset-react": "6.24.1",
@@ -79,7 +84,7 @@
"html-webpack-plugin": "3.2.0",
"husky": "1.3.1",
"image-webpack-loader": "6.0.0",
"json-server": "0.14.2",
"json-server": "0.16.1",
"mini-css-extract-plugin": "0.4.5",
"module-to-cdn": "3.1.2",
"morgan": "1.9.1",
@@ -90,9 +95,9 @@
"react-addons-test-utils": "15.6.2",
"react-dom": "16.8.6",
"react-hot-loader": "4.8.8",
"sass": "^1.25.0",
"sass": "^1.26.8",
"sass-loader": "7.1.0",
"serve": "11.2.0",
"serve": "11.3.2",
"style-loader": "0.21.0",
"stylelint": "11.1.1",
"stylelint-config-recommended-scss": "4.0.0",
@@ -101,31 +106,36 @@
"stylelint-webpack-plugin": "1.0.3",
"testcafe": "1.6.1",
"testcafe-react-selectors": "2.1.0",
"typescript": "3.7.2",
"ts-loader": "7.0.5",
"tsconfig-paths-webpack-plugin": "3.2.0",
"typescript": "3.9.5",
"typescript-plugin-styled-components": "1.4.4",
"uglifyjs-webpack-plugin": "1.3.0",
"webpack": "4.41.2",
"webpack-cdn-plugin": "3.2.0",
"webpack-cli": "3.3.10",
"webpack-dev-middleware": "3.7.2",
"webpack-dev-server": "3.9.0",
"webpack-dev-server": "3.11.0",
"webpack-merge": "4.2.2",
"webpack-node-externals": "1.7.2"
},
"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",
"normalize.css": "8.0.1",
"query-string": "6.5.0",
"react-beautiful-dnd": "10.1.1",
"react-helmet": "5.2.1",
"react-jsonschema-form": "^1.8.0",
"react-router": "4.3.1",
"react-jsonschema-form": "^1.8.1",
"react-router-dom": "4.3.1",
"react-router-hash-link": "1.2.1",
"shortid": "2.2.14"
"shortid": "2.2.14",
"styled-components": "5.1.1"
},
"postcss": {}
}
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
{{#if observer}}
import { observer } from "mobx-react";
import {{ camelCase store_name }} from "../../stores/{{ properCase store_name }}";
+2 -2
View File
@@ -1,5 +1,5 @@
import * as React from "react";
import Helmet from "react-helmet";
import React from "react";
import { Helmet } from "react-helmet";
import "./{{ properCase name}}.scss";
export interface {{ properCase name }}Props {}
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import { BrowserRouter } from "react-router-dom";
import Routes from "../routes";
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import { hydrate } from "react-dom";
import App from "./App";
+4 -4
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./Accordion.scss";
import AccordionIcon from "../AccordionIcon";
@@ -26,9 +26,9 @@ class Accordion extends React.Component<AccordionProps, AccordionState> {
}
handleClick() {
this.setState({
isOpen: !this.state.isOpen,
});
this.setState((prevState) => ({
isOpen: !prevState.isOpen,
}));
}
render() {
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./AccordionIcon.scss";
export interface AccordionIconProps {
+2 -2
View File
@@ -1,6 +1,6 @@
import * as React from "react";
import React from "react";
import { Link } from "react-router-dom";
import * as TitleImage from "../../assets/img/SIK_RGB_W_side.png";
import TitleImage from "@assets/img/SIK_RGB_W_side.png";
import "./AdminHeader.scss";
export interface AdminHeaderProps { }
+2 -2
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./AdminSidebar.scss";
import AdminSidebarLink from "../AdminSidebarLink";
@@ -16,7 +16,7 @@ class AdminSidebar extends React.Component<AdminSidebarProps, AdminSidebarState>
<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 to="https:https://static.sika.sik.party/admin" path={path}>Files</AdminSidebarLink>
<AdminSidebarLink id="admin-sidebar-logout" to="/admin/logout" path={path}>Logout</AdminSidebarLink>
</div>
);
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import Anchor from "../Anchor";
import "./AdminSidebarLink.scss";
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import { Link } from "react-router-dom";
import { HashLink } from "react-router-hash-link";
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./AsideSection.scss";
import ColorDiv, { ColorDivProps } from "../ColorDiv/ColorDiv";
+19
View File
@@ -31,6 +31,25 @@
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;
}
+12 -16
View File
@@ -1,27 +1,23 @@
import * as React from "react";
import React from "react";
import classNames from "classnames";
import "./Button.scss";
export enum ButtonType {
Hero,
Filled,
}
const buttonClassNames = new Map<ButtonType, string>([
[ButtonType.Hero, "hero"],
[ButtonType.Filled, "filled"],
]);
export interface ButtonProps {
interface ButtonProps {
onClick: () => void;
type: ButtonType;
type: "hero" | "filled" | "filter" | "bordered";
selected?: boolean;
}
export default class Button extends React.Component<ButtonProps, undefined> {
render() {
const { type } = this.props;
const className = `button ${buttonClassNames.get(type)}`;
const { type, selected } = this.props;
const classes = classNames(
"button",
type,
{ "selected": selected }
)
return (
<button type="button" onClick={this.props.onClick} className={className}>
<button type="button" onClick={this.props.onClick} className={classes}>
{this.props.children}
</button>
);
+2 -3
View File
@@ -1,3 +1,2 @@
import Button, { ButtonType } from "./Button";
export default Button;
export { ButtonType };
import Button from "./Button";
export default Button;
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./Card.scss";
import Anchor from "../Anchor";
+81
View File
@@ -0,0 +1,81 @@
import React from "react";
import styled from "styled-components";
const Container = styled.label`
display: block;
position: relative;
padding-left: 2rem;
cursor: pointer;
font-size: 1.5rem; /* 24px */
user-select: none;
/* On mouse-over, add a grey background color */
&:hover input ~ .custom-cbox {
background-color: #d4d0c7; /* grey1 */
}
`;
/* Hide the browser's default checkbox */
const HiddenDefaultElement = styled.input`
position: absolute;
opacity: 0;
height: 0;
width: 0;
`;
/* Create a custom checkbox */
const CustomCBoxElement = styled.span<{checked?: boolean}>`
border-radius: 4px;
position: absolute;
top: 0;
left: 0;
height: 1em;
width: 1em;
background-color: ${(props) => props.checked ? "#57b2df" : "#efece4"}; /* blue1 or grey2 */
&:focus &:before {
transition: box-shadow 150ms ease;
content: '';
display: block;
position: absolute;
top: -4px;
bottom: -4px;
left: -4px;
right: -4px;
border-radius: 6px;
border: 2px solid color(blue);
}
`;
const Checkmark = styled.div`
position: absolute;
left: 0.375em;
top: calc(0.25em - 1px);
width: 0.25em;
height: 0.5em;
border: solid #fff;
border-width: 0 0.125em 0.125em 0;
transform: rotate(45deg);
`;
type CheckboxProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
"type"
>;
const Checkbox: React.FC<CheckboxProps> = ({children, checked, ...props}) => (
<Container>
{children}
<HiddenDefaultElement
type="checkbox"
checked={checked}
{...props}
tabIndex={-1}
/>
<CustomCBoxElement tabIndex={0} checked={checked} className="custom-cbox">
{checked && (<Checkmark />)}
</CustomCBoxElement>
</Container>
)
export default Checkbox;
+78
View File
@@ -0,0 +1,78 @@
import React from "react";
import styled from "styled-components";
import { WidgetProps } from "react-jsonschema-form";
import Checkbox from "./Checkbox";
// See https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/core/src/components/widgets/CheckboxesWidget.js
function selectValue(value, selected, all) {
const at = all.indexOf(value);
const updated = selected.slice(0, at).concat(value, selected.slice(at));
// As inserting values at predefined index positions doesn't work with empty
// arrays, we need to reorder the updated selection to match the initial order
return updated.sort((a, b) => all.indexOf(a) > all.indexOf(b));
}
function deselectValue(value, selected) {
return selected.filter(v => v !== value);
}
type CheckboxesProps = Omit<WidgetProps, "options"> & {
options: any;
};
const CheckboxContainer = styled.div`
margin-bottom: 0.5rem;
`;
const Checkboxes: React.FC<CheckboxesProps> = ({id, disabled, options, value, autofocus, readonly, onChange}) => {
const { enumOptions, enumDisabled, inline } = options;
return (
<div className="checkboxes" id={id}>
{enumOptions.map((option, index) => {
const checked = value.indexOf(option.value) !== -1;
const itemDisabled =
enumDisabled && enumDisabled.indexOf(option.value) != -1;
const disabledCls =
disabled || itemDisabled || readonly ? "disabled" : "";
const checkbox = (
<Checkbox
id={`${id}_${index}`}
checked={checked}
disabled={disabled || itemDisabled || readonly}
autoFocus={autofocus && index === 0}
onChange={event => {
const all = enumOptions.map(({ value }) => value);
if (event.target.checked) {
onChange(selectValue(option.value, value, all));
} else {
onChange(deselectValue(option.value, value));
}
}}
>
{option.label}
</Checkbox>
);
return inline ? (
<label key={index} className={`checkbox-inline ${disabledCls}`}>
{checkbox}
</label>
) : (
<CheckboxContainer key={index} className={disabledCls}>
{checkbox}
</CheckboxContainer>
);
})}
</div>
)
}
Checkboxes.defaultProps = {
autofocus: false,
options: {
inline: false,
}
}
export default Checkboxes;
+16 -52
View File
@@ -1,51 +1,13 @@
import * as React from "react";
import React from "react";
import "./ColorDiv.scss";
export enum ColorEnum {
DarkBlue,
LightBlue,
White,
Black,
Grey1,
Grey2,
Orange1,
Orange2,
Blue,
LightTurquoise,
Green,
Sand,
Transparent,
Inherit,
}
const colors = new Map<ColorEnum, string>([
[ColorEnum.DarkBlue, "dark-blue"],
[ColorEnum.LightBlue, "light-blue"],
[ColorEnum.White, "white1"],
[ColorEnum.Black, "black1"],
[ColorEnum.Grey1, "grey1"],
[ColorEnum.Grey2, "grey2"],
[ColorEnum.Orange1, "orange1"],
[ColorEnum.Orange2, "orange2"],
[ColorEnum.Blue, "blue1"],
[ColorEnum.LightTurquoise, "light-turquoise"],
[ColorEnum.Green, "green1"],
[ColorEnum.Sand, "sand"],
[ColorEnum.Transparent, "transparent"],
[ColorEnum.Inherit, "inherit"]
]);
export const getColor = (color: ColorEnum): string => `color-div__${colors.get(color)}`;
export const getBgColor = (color: ColorEnum): string => `color-div__background_${colors.get(color)}`;
export const getHoverColor = (color: ColorEnum): string => `color-div__${colors.get(color)}Hoverable`;
export const getBgHoverColor = (color: ColorEnum): string => `color-div__background_${colors.get(color)}Hoverable`;
import { Colors, colorToClass, bgColorToClass, hoverColorToClass, bgHoverColorToClass } from "@theme/colors";
import classNames from "classnames";
export interface ColorDivProps extends React.HTMLAttributes<HTMLDivElement> {
textColor?: ColorEnum;
backgroundColor?: ColorEnum;
hoverColor?: ColorEnum;
backgroundHoverColor?: ColorEnum;
textColor?: Colors;
backgroundColor?: Colors;
hoverColor?: Colors;
backgroundHoverColor?: Colors;
}
export interface ColorDivState { }
@@ -53,14 +15,16 @@ export interface ColorDivState { }
class ColorDiv extends React.Component<ColorDivProps, ColorDivState> {
render() {
const { children, className, textColor, backgroundColor, hoverColor, backgroundHoverColor, ...props } = this.props;
const classNames = [];
if (className) classNames.push(className);
if (textColor !== undefined) classNames.push(getColor(textColor));
if (backgroundColor !== undefined) classNames.push(getBgColor(backgroundColor));
if (hoverColor !== undefined) classNames.push(getHoverColor(hoverColor));
if (backgroundHoverColor !== undefined) classNames.push(getBgHoverColor(backgroundHoverColor));
const classes = classNames(
className,
colorToClass(textColor),
bgColorToClass(backgroundColor),
hoverColorToClass(hoverColor),
bgHoverColorToClass(backgroundHoverColor)
);
return (
<div {...props} className={classNames.join(" ")}>
<div {...props} className={classes}>
{children}
</div>
);
-2
View File
@@ -1,2 +0,0 @@
import ColorDiv from "./ColorDiv";
export default ColorDiv;
@@ -1,6 +1,6 @@
import * as React from "react";
import React from "react";
import "./CommitteeContainer.scss";
import { Occupation } from "../../models/Contacts";
import { Occupation } from "@models/Contacts";
import ContactCard from "../ContactCard/ContactCard";
export interface CommitteeContainerProps {
+3 -3
View File
@@ -1,7 +1,7 @@
import * as React from "react";
import React from "react";
import "./ContactCard.scss";
import * as blank_profile from "../../assets/img/blank_profile.png";
import { Role } from "../../models/Contacts";
import blank_profile from "@assets/img/blank_profile.png";
import { Role } from "@models/Contacts";
export interface ContactCardProps {
first_name: string;
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./DatetimeWidget.scss";
export interface DatetimeWidgetProps {
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./DropDownBox.scss";
export interface DropDownBoxProps {
+3 -3
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import FooterMap from "../FooterMap";
import Anchor from "../Anchor";
import "./Footer.scss";
@@ -28,8 +28,8 @@ class Footer extends React.Component<FooterProps, FooterState> {
<div className="footer__links">
<Anchor to="/jaseneksi">Jäseneksi</Anchor>
<Anchor to="/palaute">Palaute</Anchor>
<Anchor to="/arkisto">Arkisto</Anchor>
<Anchor to="/materiaalipankki">Materiaalipankki</Anchor>
<Anchor to="https://static.sika.sik.party">Arkisto</Anchor>
<Anchor to="https://static.sika.sik.party">Materiaalipankki</Anchor>
</div>
</div>
</div>
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./FooterMap.scss";
const AnyReactComponent = ({ text }) => <div>{text}</div>;
+16 -7
View File
@@ -2,17 +2,26 @@
.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;
background-color: color(dark-blue);
@media screen and (min-width: 600px) {
position: sticky;
top: 0;
z-index: 10;
}
@media screen and (max-width: 600px - 1px) {
flex-flow: column nowrap;
+86 -69
View File
@@ -1,88 +1,105 @@
import * as React from "react";
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 * as TitleImage from "../../assets/img/SIK_RGB_W_side.png";
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;
}
class Header extends React.Component<HeaderProps, HeaderState> {
constructor(props) {
super(props);
this.state = {
mobileMenuOpen: false,
};
}
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>
</>
);
}
renderNavigationDesktopItems = () => {
return (
<React.Fragment>
<NavbarDropdownLink to="/kilta" text="Kilta ">
<NavbarChildLink to="/kilta/toiminta">Toiminta</NavbarChildLink>
<NavbarChildLink to="/kilta/fuksi">Fuksi</NavbarChildLink>
<NavbarChildLink to="/kilta/arkisto">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>
</React.Fragment>
);
}
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 />
</>
);
}
renderNavigationMobileItems = () => {
return (
<React.Fragment>
<NavbarDropdownLink to="/kilta" text="Kilta " exploded>
<NavbarChildLink to="/kilta/toiminta">Toiminta</NavbarChildLink>
<NavbarChildLink to="/kilta/fuksi">Fuksi</NavbarChildLink>
<NavbarChildLink to="/kilta/arkisto">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 />
</React.Fragment>
);
}
const PREVENT_IS_HIDDEN_Y = 150;
handleMobileMenuClick = (): void => {
this.setState(prevState => ({
mobileMenuOpen: !prevState.mobileMenuOpen,
}));
}
const Header: FC<HeaderProps> = () => {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [isHidden, setHidden] = useState(false);
const yCoord = useRef(0);
render() {
const desktopItems = this.renderNavigationDesktopItems();
const mobileItems = this.renderNavigationMobileItems();
const { mobileMenuOpen } = this.state;
const handleScroll = () => {
const newCoord = window.pageYOffset;
if (!mobileMenuOpen && newCoord > yCoord.current && newCoord > PREVENT_IS_HIDDEN_Y) {
setHidden(true);
} else {
setHidden(false);
}
yCoord.current = newCoord;
};
return (
<React.Fragment>
<div className="header">
<Link to="/">
<img className="logo" src={TitleImage} />
</Link>
<Navigation
onMobileMenuOpen={this.handleMobileMenuClick}
items={desktopItems}
/>
</div>
<div className="navigation-mobile-menu" hidden={!mobileMenuOpen}>
{mobileItems}
</div>
</React.Fragment>
);
}
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,4 +1,4 @@
@import "../../assets/scss/globals";
@import "../../../assets/scss/globals";
.hero-aside-item {
@@ -0,0 +1,21 @@
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,4 +1,4 @@
@import "../../assets/scss/globals";
@import "../../../assets/scss/globals";
.hero-aside-section {
@@ -1,6 +1,6 @@
import * as React from "react";
import React from "react";
import "./HeroAsideSection.scss";
import ColorDiv, { ColorDivProps } from "../ColorDiv/ColorDiv";
import ColorDiv, { ColorDivProps } from "../../ColorDiv/ColorDiv";
export interface HeroAsideSectionProps { }
@@ -0,0 +1,51 @@
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,66 @@
import React from "react";
import styled from "styled-components";
import ColorDiv from "../../ColorDiv/ColorDiv";
interface HeroSecondarySectionItemProps {
note?: string;
}
const Note = styled.span`
color: white;
transform-origin: right;
transform: rotate(-90deg);
height: fit-content;
text-transform: uppercase;
font-size: 2.5rem;
font-weight: bold;
margin-right: 2rem;
margin-top: -0.5rem;
`;
const Item = styled.div`
display: flex;
text-align: left;
flex: 1 0;
margin: 1rem 2rem 1rem;
`;
export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> = ({note, children}) => (
<Item>
<Note>
{note}
</Note>
{children}
</Item>
)
const Container = styled(ColorDiv)`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
h1 {
padding: 1em 0;
}
`;
const Items = styled.div`
display: flex;
flex-direction: row;
`;
interface HeroSecondarySectionProps {
title: string;
}
const HeroSecondarySection: React.FC<HeroSecondarySectionProps> = ({title, children}) => (
<Container textColor="dark-blue" backgroundColor="green1">
<h1>{title}</h1>
<Items>
{children}
</Items>
</Container>
)
export default HeroSecondarySection;
@@ -1,30 +0,0 @@
import * as React from "react";
import "./HeroAsideItem.scss";
import Anchor from "../Anchor";
export interface HeroAsideItemProps {
title: string;
linkText: string;
linkHref: string;
}
export interface HeroAsideItemState { }
class HeroAsideItem extends React.Component<
HeroAsideItemProps,
HeroAsideItemState
> {
render() {
const { title, linkText, linkHref, children } = this.props;
return (
<div className="hero-aside-item">
<h2>{title}</h2>
<p>{children}</p>
<Anchor to={linkHref}>
<h6>{linkText} </h6>
</Anchor>
</div>
);
}
}
export default HeroAsideItem;
-2
View File
@@ -1,2 +0,0 @@
import HeroAsideItem from "./HeroAsideItem";
export default HeroAsideItem;
-2
View File
@@ -1,2 +0,0 @@
import HeroAsideSection from "./HeroAsideSection";
export default HeroAsideSection;
@@ -1,33 +0,0 @@
@import "../../assets/scss/globals";
.hero-main-section {
display: flex;
flex-flow: column nowrap;
align-items: center;
margin-top: 15vh;
flex: 6;
text-align: center;
font-weight: 100;
padding: 2rem 1rem 2rem;
line-height: 24px;
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%;
}
@@ -1,17 +0,0 @@
import * as React from "react";
import "./HeroMainSection.scss";
export interface HeroMainSectionProps {}
export interface HeroMainSectionState {}
class HeroMainSection extends React.Component<HeroMainSectionProps, HeroMainSectionState> {
render() {
return (
<div className="hero-main-section">
{this.props.children}
</div>
);
}
}
export default HeroMainSection;
-2
View File
@@ -1,2 +0,0 @@
import HeroMainSection from "./HeroMainSection";
export default HeroMainSection;
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./Icon.scss";
export enum IconType {
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./InfoBox.scss";
export interface InfoBoxProps {}
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
export interface JsonLDProps {
data: object;
+3 -3
View File
@@ -6,15 +6,15 @@
flex-flow: column;
padding: 0 3rem;
h3 {
& > h3 {
margin: auto;
}
h6 {
& > h6 {
color: color(orange1);
}
p {
& > p {
margin-block-start: 0.5rem;
margin-block-end: 1rem;
}
+7 -9
View File
@@ -1,23 +1,21 @@
import * as React from "react";
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> {
constructor(props: MainSectionProps) {
super(props);
}
render() {
const { children, className, ...props } = this.props;
const classNames = [
const classes = classNames(
"main-section",
];
if (className) classNames.push(className);
className
);
return (
<ColorDiv className={classNames.join(" ")} {...props}>
<ColorDiv className={classes} {...props}>
{children}
</ColorDiv>
);
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./NavbarChildLink.scss";
import Anchor from "../Anchor";
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./NavbarDropdownLink.scss";
import DropDownBox from "../DropDownBox/DropDownBox";
import Anchor from "../Anchor";
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./Navigation.scss";
import NavbarDropdownLink from "../NavbarDropdownLink/NavbarDropdownLink";
import NavbarChildLink from "../NavbarChildLink/NavbarChildLink";
+1 -2
View File
@@ -1,7 +1,6 @@
import * as React from "react";
import React from "react";
import "./PageLink.scss";
import TextAnchor from "../TextAnchor";
import { TextSize } from "../TextAnchor/TextAnchor";
export interface PageLinkProps {
to: string;
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./PageSection.scss";
import ColorDiv, { ColorDivProps } from "../ColorDiv/ColorDiv";
@@ -0,0 +1,69 @@
import React from "react";
import styled from "styled-components";
const Container = styled.label`
display: block;
position: relative;
padding-left: 2rem;
cursor: pointer;
font-size: 1.5rem; /* 24px */
user-select: none;
/* On mouse-over, add a grey background color */
&:hover input ~ .custom-radio {
background-color: #d4d0c7; /* grey1 */
}
`;
const HiddenDefaultElement = styled.input`
position: absolute;
opacity: 0;
height: 0;
width: 0;
`;
const CustomRadioElement = styled.span<{checked?: boolean}>`
position: absolute;
top: 0;
left: 0;
height: 1em;
width: 1em;
background-color: ${(props) => props.checked ? "#57b2df" : "#efece4"}; /* blue1 or grey2 */
border-radius: 50%;
`;
const Indicator = styled.div`
position: absolute;
top: 0.25em;
left: 0.25em;
width: 0.5em;
height: 0.5em;
border-radius: 50%;
background: white;
`;
type RadioButtonProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
"type"
>;
const RadioButton: React.FC<RadioButtonProps> = ({
checked,
children,
...props
}) => (
<Container>
{children}
<HiddenDefaultElement
type="radio"
checked={checked}
{...props}
tabIndex={-1}
/>
<CustomRadioElement tabIndex={0} checked={checked} className="custom-radio">
{checked && (<Indicator />)}
</CustomRadioElement>
</Container>
);
export default RadioButton;
@@ -0,0 +1,74 @@
import React from "react";
import styled from "styled-components";
import { WidgetProps } from "react-jsonschema-form";
import RadioButton from "./RadioButton";
type RadioButtonWidgetProps = Omit<WidgetProps, "options"> & {
options: any;
};
const RadioButtonContainer = styled.div`
margin-bottom: 0.5rem;
`;
const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
const {
options,
value,
required,
disabled,
readonly,
autofocus,
onBlur,
onFocus,
onChange,
id,
} = props;
// Generating a unique field name to identify this set of radio buttons
const name = Math.random().toString();
const { enumOptions, enumDisabled, inline } = options;
// checked={checked} has been moved above name={name}, As mentioned in #349;
// this is a temporary fix for radio button rendering bug in React, facebook/react#7630.
return (
<div className="field-radio-group" id={id}>
{enumOptions.map((option, i) => {
const checked = option.value === value;
const itemDisabled =
enumDisabled && enumDisabled.indexOf(option.value) != -1;
const disabledCls =
disabled || itemDisabled || readonly ? "disabled" : "";
const radio = (
<RadioButton
checked={checked}
name={name}
required={required}
value={option.value}
disabled={disabled || itemDisabled || readonly}
autoFocus={autofocus && i === 0}
onChange={() => onChange(option.value)}
onBlur={onBlur && (event => onBlur(id, event.target.value))}
onFocus={onFocus && (event => onFocus(id, event.target.value))}
>
{option.label}
</RadioButton>
);
return inline ? (
<label key={i} className={`radio-inline ${disabledCls}`}>
{radio}
</label>
) : (
<RadioButtonContainer key={i} className={disabledCls}>
{radio}
</RadioButtonContainer>
);
})}
</div>
);
}
RadioButtonWidget.defaultProps = {
autofocus: false,
};
export default RadioButtonWidget;
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./Ribbon.scss";
export interface RibbonProps {}
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import "./SectionDividerWidget.scss";
import Icon from "../Icon";
import { IconType } from "../Icon/Icon";
@@ -1,6 +1,6 @@
import * as React from "react";
import * as shortid from "shortid";
import React from "react";
import { Question, InputProps, optionTypes, SignupQuestionError } from "./index";
import Checkbox from "@components/Checkbox/Checkbox";
export interface OptionsWidgetProps {
inputProps: InputProps;
@@ -9,7 +9,7 @@ export interface OptionsWidgetProps {
export interface OptionsWidgetState { }
class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWidgetState> {
handleRadiobuttonOptionsChange = (questions: Question[], index: number) => (event) => {
handleListOptionsChange = (questions: Question[], index: number) => (event) => {
const { onChange } = this.props;
const val = event.target.value;
const lst = val.split(",").map(p => p.trimLeft());
@@ -17,14 +17,44 @@ class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWidgetSta
onChange(questions);
}
handleCheckboxOptionsChange = (questions: Question[], index: number) => (event) => {
handleTextOptionsChange = (questions: Question[], index: number) => (event) => {
const { onChange } = this.props;
const val = event.target.value;
const lst = val.split(",").map(p => p.trimLeft());
questions[index].options = lst;
questions[index].options = val;
onChange(questions);
}
handleIntegerOptionsChange = (questions: Question[], index: number) => (event) => {
const { onChange } = this.props;
const val = event.target.value;
if (val !== "") {
const lst = val.split(",").map(p => p.trimLeft());
// Ignore everything else but the two first values
questions[index].options = lst.splice(0, 2);
} else {
questions[index].options = []
}
onChange(questions);
}
handleRequiredChange = (questions: Question[], index: number) => (event) => {
const { onChange } = this.props;
const val: boolean = event.target.checked;
console.log(val);
questions[index].required = val;
onChange(questions);
}
requiredField() {
const { inputProps } = this.props;
const { questions, index } = inputProps;
return <Checkbox
checked={questions[index].required}
onChange={this.handleRequiredChange(questions, index)}
>Required?</Checkbox>;
}
render() {
const { inputProps } = this.props;
const { type, value, questions, index } = inputProps;
@@ -32,28 +62,63 @@ class OptionsWidget extends React.Component<OptionsWidgetProps, OptionsWidgetSta
throw new SignupQuestionError(`Question widget type "${type}" not in types array.`);
}
if (type === "text") {
return null;
if (type === "text" || type === "email" || type === "name") {
return this.requiredField();
}
if (type === "info") {
return (
<>
<input
type="text"
placeholder="Write something informative"
value={questions[index].options}
onChange={this.handleTextOptionsChange(questions, index)} />
{this.requiredField()}
</>
);
}
if (type === "integer") {
const lst = value as string[];
const joinedValue = lst.join(",");
return (
<>
<input
type="text"
placeholder="Minimum,Maximum"
value={joinedValue}
onChange={this.handleIntegerOptionsChange(questions, index)} />
{this.requiredField()}
</>);
}
if (type === "radiobutton") {
const lst = value as string[];
const joinedValue = lst.join(",");
return <input
type="text"
placeholder="Yes,no,maybe"
value={joinedValue}
onChange={this.handleRadiobuttonOptionsChange(questions, index)} />;
return (
<>
<input
type="text"
placeholder="Yes,no,maybe"
value={joinedValue}
onChange={this.handleListOptionsChange(questions, index)} />
{this.requiredField()}
</>);
}
if (type === "checkbox") {
const lst = value as string[];
const joinedValue = lst.join(",");
return <input
type="text"
placeholder="A,B,C"
value={joinedValue}
onChange={this.handleCheckboxOptionsChange(questions, index)} />;
return (
<>
<input
type="text"
placeholder="A,B,C"
value={joinedValue}
onChange={this.handleListOptionsChange(questions, index)} />
{this.requiredField()}
</>);
}
throw new SignupQuestionError(`Unrecognized question widget type "${type}"`);
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
export interface QuestionProps {
children: any;
@@ -1,7 +1,6 @@
import * as React from "react";
import * as shortid from "shortid";
import React from "react";
import { Draggable } from "react-beautiful-dnd";
import { Question, InputProps, optionTypes, SignupQuestionError } from "./index";
import { Question, InputProps } from "./index";
import OptionsWidget from "./OptionsWidget";
import TypeWidget from "./TypeWidget";
import QuestionElement from "./Question";
@@ -1,11 +1,9 @@
import * as React from "react";
import * as shortid from "shortid";
import OptionsWidget from "./OptionsWidget";
import React from "react";
import shortid from "shortid";
import { DragDropContext, Droppable } from "react-beautiful-dnd";
import { Question, InputProps, optionTypes } from "."
import * as AddIcon from "../../assets/img/add-icon.png";
import { Question } from "."
import AddIcon from "@assets/img/add-icon.png";
import "./SignupQuestionsWidget.scss";
import TypeWidget from "./TypeWidget";
import QuestionList from "./QuestionList";
export interface SignupQuestionsWidgetProps {
@@ -1,6 +1,5 @@
import * as React from "react";
import * as shortid from "shortid";
import { Question, InputProps, optionTypes, SignupQuestionError } from "./index";
import React from "react";
import { Question, InputProps, optionTypes } from "./index";
export interface TypeWidgetProps {
inputProps: InputProps;
+15 -1
View File
@@ -3,8 +3,9 @@ import SignupQuestionsWidget from "./SignupQuestionsWidget";
export interface Question {
id: string;
name: string;
type: string;
type: OptionTypes;
options: string[];
required?: boolean;
}
export interface InputProps {
@@ -14,10 +15,23 @@ export interface InputProps {
type: string;
}
type OptionTypes =
"text" |
"info" |
"integer" |
"radiobutton" |
"checkbox" |
"email" |
"name";
export const optionTypes = [
"text",
"info",
"integer",
"radiobutton",
"checkbox",
"email",
"name"
];
export class SignupQuestionError extends Error { }
+2 -3
View File
@@ -1,8 +1,7 @@
import * as React from "react";
import React from "react";
import "./SponsorReel.scss";
import TextAnchor from "../TextAnchor";
import Anchor from "../Anchor";
import { ColorEnum } from "../ColorDiv/ColorDiv";
export interface SponsorReelProps { }
export interface SponsorReelState { }
@@ -19,7 +18,7 @@ class SponsorReel extends React.Component<SponsorReelProps, SponsorReelState> {
<Anchor to="#"><img src="https://placehold.it/200x200" /></Anchor>
<Anchor to="#"><img src="https://placehold.it/200x200" /></Anchor>
</div>
<TextAnchor textColor={ColorEnum.Blue} hoverColor={ColorEnum.LightTurquoise} to="/yritysyhteistyo">Haluatko kuulla lisää yhteistyöstä kanssamme?</TextAnchor>
<TextAnchor textColor="blue1" hoverColor="light-turquoise" to="/yritysyhteistyo">Haluatko kuulla lisää yhteistyöstä kanssamme?</TextAnchor>
</div>
);
}
+25 -22
View File
@@ -1,42 +1,45 @@
import * as React from "react";
import React from "react";
import "./TextAnchor.scss";
import { ColorEnum, getColor, getHoverColor } from "../ColorDiv/ColorDiv";
import { Colors, colorToClass, hoverColorToClass } from "@theme/colors";
import Anchor from "../Anchor";
import classNames from "classnames";
export enum TextSize {
Normal,
Small,
Large,
Ribbon,
SmallRibbon,
}
export type TextSize =
"normal" |
"small" |
"large" |
"ribbon" |
"small-ribbon";
const sizes = new Map<TextSize, string>([
[TextSize.Normal, ""],
[TextSize.Small, "text-anchor__small"],
[TextSize.Large, "text-anchor__large"],
[TextSize.SmallRibbon, "text-anchor__no-weight text-anchor__small"],
[TextSize.Ribbon, "text-anchor__no-weight"],
const textSizeToClassName = new Map<TextSize, string>([
["normal", ""],
["small", "text-anchor__small"],
["large", "text-anchor__large"],
["small-ribbon", "text-anchor__no-weight text-anchor__small"],
["ribbon", "text-anchor__no-weight"],
]);
export interface TextAnchorProps {
size?: TextSize;
to: string;
textColor?: ColorEnum;
hoverColor?: ColorEnum;
textColor?: Colors;
hoverColor?: Colors;
}
export interface TextAnchorState { }
class TextAnchor extends React.Component<TextAnchorProps, TextAnchorState> {
render() {
const { children, size, to, textColor, hoverColor } = this.props;
const classColor = textColor !== undefined ? getColor(textColor) : getColor(ColorEnum.DarkBlue);
const classHoverColor = hoverColor !== undefined ? getHoverColor(hoverColor) : getHoverColor(ColorEnum.Blue);
const classSize = size !== undefined ? sizes.get(size) : sizes.get(TextSize.Normal);
const className = `text-anchor ${classSize} ${classColor} ${classHoverColor}`;
const classes = classNames(
"text-anchor",
colorToClass(textColor),
hoverColorToClass(hoverColor),
textSizeToClassName.get(size)
)
return (
<Anchor to={to} className={className}>
<Anchor to={to} className={classes}>
{children}
</Anchor>
);
-2
View File
@@ -12,7 +12,6 @@ body {
height: 100%;
font-family: $font;
color: color(white1);
overflow: auto;
}
#root {
@@ -25,7 +24,6 @@ body {
body {
padding: 0;
margin: auto !important;
overflow-x: hidden;
}
h1 {
+1 -1
View File
@@ -1,4 +1,4 @@
import * as React from "react";
import React from "react";
import { render } from "react-dom";
import { BrowserRouter } from "react-router-dom";
import { AppContainer } from "react-hot-loader";
+13 -6
View File
@@ -1,7 +1,7 @@
import axios from "axios";
import { getAuthHeader } from "../auth";
import { getAuthHeader } from "@utils/auth";
import { Tag } from "./Tag";
import * as qs from "query-string";
import qs from "query-string";
import { SignupForm } from "./SignupForm";
const url = `${process.env.API_URL}/events/`;
@@ -15,6 +15,7 @@ export interface Event {
content_en: string;
start_time: string;
end_time: string;
image: string;
tags: Tag[];
tag_id?: number[];
visible: boolean;
@@ -23,14 +24,17 @@ export interface Event {
}
export async function getEvents(options: any = {}): Promise<Event[]> {
const { onlyNonPast, limit } = options;
const { onlyNonPast, limit, auth } = options;
try {
const params = {
since: onlyNonPast ? (new Date()).toISOString() : undefined,
limit,
};
const search = qs.stringify(params);
const resp = await axios.get(`${url}?${search}`);
const headers = auth ? { "Authorization": getAuthHeader() } : null;
const resp = await axios.get(`${url}?${search}`, {
headers
});
return resp.data["results"];
} catch (err) {
console.error(err);
@@ -38,9 +42,12 @@ export async function getEvents(options: any = {}): Promise<Event[]> {
}
}
export async function getEvent(id: number): Promise<Event> {
export async function getEvent(id: number, auth = false): Promise<Event> {
try {
const resp = await axios.get(`${url}${id}/`);
const headers = auth ? { "Authorization": getAuthHeader() } : null;
const resp = await axios.get(`${url}${id}/`, {
headers
});
return resp.data;
} catch (err) {
console.error(err);
+1 -1
View File
@@ -1,5 +1,5 @@
import axios from "axios";
import { getAuthHeader } from "../auth";
import { getAuthHeader } from "@utils/auth";
const url = `${process.env.API_URL}/feed/`;
+42 -6
View File
@@ -1,8 +1,6 @@
import axios from "axios";
import { getAuthHeader } from "../auth";
import * as qs from "query-string";
import { getAuthHeader } from "@utils/auth";
const url = `${process.env.API_URL}/signup/`;
import { Question } from "../components/SignupQuestionsWidget";
export interface Signup {
id?: number;
@@ -10,11 +8,11 @@ export interface Signup {
answer: string;
}
export async function createSignup(data: Signup): Promise<Signup> {
export async function getSignup(id: number): Promise<Signup> {
try {
const resp = await axios.post(url, data, {
const resp = await axios.get(`${url}${id}`, {
headers: {
"Authorization": getAuthHeader(),
"Authorization": getAuthHeader()
},
});
return resp.data;
@@ -23,3 +21,41 @@ export async function createSignup(data: Signup): Promise<Signup> {
throw err;
}
}
export async function createSignup(data: Signup): Promise<Signup> {
try {
const resp = await axios.post(url, data);
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function updateSignup(data: Signup, uuid: string): Promise<Signup> {
try {
const { id } = data;
if (!id) throw new Error("SignupId required!");
const resp = await axios.put(`${url}${id}/edit/`, data, {
params: { uuid }
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function getSignupUUID(id: number, uuid: string): Promise<Signup> {
try {
const resp = await axios.get(`${url}${id}/edit/`, {
params: {
uuid
}
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
+13 -5
View File
@@ -1,15 +1,23 @@
import axios from "axios";
import { getAuthHeader } from "../auth";
import * as qs from "query-string";
import { getAuthHeader } from "@utils/auth";
const url = `${process.env.API_URL}/signupForm/`;
import { Question } from "../components/SignupQuestionsWidget";
import { Question } from "@components/SignupQuestionsWidget";
export interface SignupForm {
id: number;
id?: number;
title: string;
start_time: string;
end_time: string;
questions: string;
questions: Question[];
signups: string[];
quota: number;
schema: {
title?: string;
type: string;
required: string[];
properties: any;
minProperties?: number;
};
}
export async function getForms(): Promise<SignupForm[]> {
+89
View File
@@ -0,0 +1,89 @@
import React from "react";
import { Helmet } from "react-helmet";
import { StaticContext } from "@server/StaticContext";
import ActualPageView from "@views/ActualPage/ActualPageView";
import { Event, getEvents } from "@models/Event";
import { Post, getFeed } from "@models/Feed";
interface ActualPageProps {
staticContext: StaticContext;
}
interface ActualPageState {
events: Event[];
feed: Post[];
}
class ActualPage extends React.Component<ActualPageProps, ActualPageState> {
constructor(props: ActualPageProps) {
super(props);
const { staticContext } = props;
if (staticContext) {
/* The static context is an object that manages promises when
rendering on the server. If staticContext exists, that means
we have to store all promises in it. Otherwise, operate
normally. See server/index.ts. */
if (staticContext.resolutions.getEvents) {
const events = staticContext.resolutions.getEvents as Event[];
const feed = staticContext.resolutions.getFeed as Post[];
this.state = {
events,
feed,
};
} else {
this.state = {
events: [],
feed: [],
};
const promiseEvents = this.fetchEvents();
const promiseFeed = this.fetchFeed();
staticContext.promises.getEvents = promiseEvents;
staticContext.promises.getFeed = promiseFeed;
}
} else {
this.state = {
events: [],
feed: [],
};
this.fetchEvents();
this.fetchFeed();
}
}
fetchEvents = () => {
const getEventsPromise = getEvents({
onlyNonPast: true,
});
getEventsPromise.then(events => {
this.setState({
events,
});
});
return getEventsPromise;
}
fetchFeed = () => {
const getFeedPromise = getFeed();
getFeedPromise.then(feed => {
this.setState({
feed,
});
});
return getFeedPromise;
}
render() {
return (
<>
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/kilta/toiminta" />
</Helmet>
<ActualPageView events={this.state.events} feed={this.state.feed} />
</>
)
}
}
export default ActualPage;
-5
View File
@@ -1,5 +0,0 @@
.actual-page {
display: flex;
flex-flow: column wrap;
justify-content: flex-start;
}
-21
View File
@@ -1,21 +0,0 @@
import * as React from "react";
import Helmet from "react-helmet";
import "./ActualPage.scss";
export interface ActualPageProps {}
export interface ActualPageState {}
class ActualPage extends React.Component<ActualPageProps, ActualPageState> {
render() {
return (
<div className="actual-page">
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/kilta/toiminta" />
</Helmet>
Actual Page
</div>
);
}
}
export default ActualPage;
-2
View File
@@ -1,2 +0,0 @@
import ActualPage from "./ActualPage";
export default ActualPage;
-2
View File
@@ -1,2 +0,0 @@
import AdminCommonPage from "./AdminCommonPage";
export default AdminCommonPage;
-2
View File
@@ -1,2 +0,0 @@
import AdminEventPage from "./AdminEventPage";
export default AdminEventPage;
-2
View File
@@ -1,2 +0,0 @@
import AdminFeedPage from "./AdminFeedPage";
export default AdminFeedPage;
-2
View File
@@ -1,2 +0,0 @@
import AdminFrontPage from "./AdminFrontPage";
export default AdminFrontPage;
-2
View File
@@ -1,2 +0,0 @@
import AdminLoginPage from "./AdminLoginPage";
export default AdminLoginPage;
-2
View File
@@ -1,2 +0,0 @@
import AdminLogoutPage from "./AdminLogoutPage";
export default AdminLogoutPage;
-2
View File
@@ -1,2 +0,0 @@
import AdminSignupPage from "./AdminSignupPage";
export default AdminSignupPage;
@@ -1,6 +1,6 @@
import * as React from "react";
import Header from "../../components/Header";
import Footer from "../../components/Footer";
import React from "react";
import Header from "@components/Header";
import Footer from "@components/Footer";
export interface CommonPageProps {
page: any;
-2
View File
@@ -1,2 +0,0 @@
import CommonPage from "./CommonPage";
export default CommonPage;
@@ -1,12 +1,8 @@
import * as React from "react";
import Helmet from "react-helmet";
import "./ContactsPage.scss";
import { ColorEnum, getColor, getHoverColor } from "../../components/ColorDiv/ColorDiv";
import { StaticContext } from "../../server/StaticContext";
import PageSection from "../../components/PageSection";
import { getContacts, Occupation, Committee, getCommittees } from "../../models/Contacts";
import CommitteeContainer from "../../components/CommitteeContainer";
import Anchor from "../../components/Anchor/index";
import React from "react";
import { Helmet } from "react-helmet";
import { StaticContext } from "@server/StaticContext";
import { getContacts, Occupation, Committee, getCommittees } from "@models/Contacts";
import ContactsPageView from "@views/ContactsPage/ContactsPageView";
interface ContactsPageProps {
staticContext: StaticContext;
@@ -76,39 +72,13 @@ class ContactsPage extends React.Component<ContactsPageProps, ContactsPageState>
render() {
const { contacts, committees } = this.state;
const board = contacts.filter(x => x.role.is_board);
return (
<div className="contacts-page">
<>
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/INSERT_PATH_HERE!" />
</Helmet>
<PageSection backgroundColor={ColorEnum.White} textColor={ColorEnum.DarkBlue} center>
<p>
Asiaa olisi, mutta kehen ottaa yhteyttä?<br />
Tämä sivu yrittää valottaa sen oikean ihmisen puhelinnumeroa ja sähköpostiosoitetta.
</p>
</PageSection>
<PageSection backgroundColor={ColorEnum.White} textColor={ColorEnum.DarkBlue} bottomBorder center>
<div>
<CommitteeContainer name_fi="Hallitus" name_en="Board" contacts={board} />
<p>
Hallitukseen saa yhteyden lähettämällä sähköpostia <Anchor
className={`${getColor(ColorEnum.Blue)} ${getHoverColor(ColorEnum.LightBlue)}`}
to="mailto:sik-hallitus@list.ayy.fi">
sik-hallitus@list.ayy.fi
</Anchor>
</p>
</div>
</PageSection>
{committees.map((committee, index) => {
const order = committee.name_fi === "Toimikunnattomat" ? 1 : 0;
return (
<PageSection key={index} style={{order}} backgroundColor={ColorEnum.White} center>
<CommitteeContainer name_fi={committee.name_fi} name_en={committee.name_en} contacts={contacts.filter(x => x.role.committee.name_fi === committee.name_fi)} />
</PageSection>
)
})}
</div>
<ContactsPageView contacts={contacts} committees={committees} />
</>
);
}
}
-2
View File
@@ -1,2 +0,0 @@
import ContactsPage from "./ContactsPage";
export default ContactsPage;
@@ -1,6 +1,6 @@
import * as React from "react";
import Helmet from "react-helmet";
import "./CorporatePage.scss";
import React from "react";
import { Helmet } from "react-helmet";
import CorporatePageView from "@views/CorporatePage/CorporatePageView";
export interface CorporatePageProps {}
export interface CorporatePageState {}
@@ -8,12 +8,12 @@ export interface CorporatePageState {}
class CorporatePage extends React.Component<CorporatePageProps, CorporatePageState> {
render() {
return (
<div className="corporate-page">
<>
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/yritysyhteistyo" />
</Helmet>
Corporate Page
</div>
<CorporatePageView />
</>
);
}
}
-2
View File
@@ -1,2 +0,0 @@
import CorporatePage from "./CorporatePage";
export default CorporatePage;
-2
View File
@@ -1,2 +0,0 @@
import EventCreatePage from "./EventCreatePage";
export default EventCreatePage;
+53
View File
@@ -0,0 +1,53 @@
import React from "react";
import { Helmet } from "react-helmet";
import { RouteComponentProps } from "react-router-dom";
import { Event, getEvent } from "@models/Event";
import EventPageView from "@views/EventPage/EventPageView";
interface MatchParams {
id: string;
}
interface EventPageState {
event?: Event;
}
type EventPageProps = RouteComponentProps<MatchParams>
class EventPage extends React.Component<EventPageProps, EventPageState> {
constructor(props: EventPageProps) {
super(props);
const { id } = this.props.match.params;
this.state = {
event: null
}
this.fetchEvent(Number(id));
}
fetchEvent(id: number) {
const eventPromise = getEvent(id);
eventPromise.then(event => {
this.setState({
event,
});
});
return eventPromise;
}
render() {
const { event } = this.state;
if (!event) return <div>Loading</div>
return (
<>
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/INSERT_PATH_HERE!" />
</Helmet>
<EventPageView event={event} />
</>
);
}
}
export default EventPage;
-18
View File
@@ -1,18 +0,0 @@
.event-page {
display: block;
}
.event-banner {
width: 300px;
height: auto;
}
.event-title {
text-align: center;
}
.event-signup-buttons {
display: flex;
flex-flow: row wrap;
justify-content: center;
}
-85
View File
@@ -1,85 +0,0 @@
import * as React from "react";
import Helmet from "react-helmet";
import "./EventPage.scss";
import { Event, getEvent } from "../../models/Event";
import { RouteComponentProps } from "react-router-dom";
import Button, { ButtonType } from "../../components/Button";
import Anchor from "../../components/Anchor";
import PageSection from "../../components/PageSection";
import { ColorEnum } from "../../components/ColorDiv/ColorDiv";
import MainSection from "../../components/MainSection";
import AsideSection from "../../components/AsideSection/AsideSection";
interface MatchParams {
id: string;
}
export interface EventPageOwnProps {}
export interface EventPageState {
event?: Event;
}
type EventPageProps = EventPageOwnProps & RouteComponentProps<MatchParams>
class EventPage extends React.Component<EventPageProps, EventPageState> {
constructor(props: EventPageProps) {
super(props);
const { id } = this.props.match.params;
this.state = {
event: null
}
this.fetchEvent(Number(id));
}
fetchEvent(id: number) {
const eventPromise = getEvent(id);
eventPromise.then(event => {
this.setState({
event,
});
});
return eventPromise;
}
render() {
const { event } = this.state;
if (!event) return <div>Loading</div>
return (
<div className="event-page">
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/INSERT_PATH_HERE!" />
</Helmet>
<PageSection backgroundColor={ColorEnum.White}>
<AsideSection textColor={ColorEnum.Black} />
<MainSection textColor={ColorEnum.Black}>
<img className="event-banner" src={event.tags[0].icon} alt={event.title_fi} ></img>
<h1 className="event-title">{event.title_fi}</h1>
<p>
{event.description_fi}
</p>
<p>
{event.content_fi}
</p>
{/* We may have multiple signup forms. Generate own Button for each one */}
<div className="event-signup-buttons">
{event.signupForm.map(sf => (
<Anchor key={sf.id} to={`/signup/${sf.id}`}>
<Button type={ButtonType.Filled} onClick={() => {}}>
{sf.title}
</Button>
</Anchor>
)
)}
</div>
</MainSection>
<AsideSection backgroundColor={ColorEnum.White} textColor={ColorEnum.Black} />
</PageSection>
</div>
);
}
}
export default EventPage;
-2
View File
@@ -1,2 +0,0 @@
import EventPage from "./EventPage";
export default EventPage;
-2
View File
@@ -1,2 +0,0 @@
import FeedCreatePage from "./FeedCreatePage";
export default FeedCreatePage;
+17
View File
@@ -0,0 +1,17 @@
import React from "react";
import { Helmet } from "react-helmet";
import FreshmenPageView from "@views/FreshmenPage/FreshmenPageView";
export interface FreshmenPageProps {}
export interface FreshmenPageState {}
const FreshmenPage: React.FC = () => (
<>
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/toiminta/fuksit" />
</Helmet>
<FreshmenPageView />
</>
)
export default FreshmenPage;
-21
View File
@@ -1,21 +0,0 @@
import * as React from "react";
import Helmet from "react-helmet";
import "./FreshmenPage.scss";
export interface FreshmenPageProps {}
export interface FreshmenPageState {}
class FreshmenPage extends React.Component<FreshmenPageProps, FreshmenPageState> {
render() {
return (
<div className="freshmen-page">
<Helmet>
<link rel="canonical" href="https://sik.ayy.fi/toiminta/fuksit" />
</Helmet>
Freshmen Page
</div>
);
}
}
export default FreshmenPage;
-2
View File
@@ -1,2 +0,0 @@
import FreshmenPage from "./FreshmenPage";
export default FreshmenPage;

Some files were not shown because too many files have changed in this diff Show More