Merge branch 'master' into 'production'

dev to prod: styling, added fopas etc

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!74
This commit is contained in:
Toni Lyttinen
2021-06-29 16:04:22 +00:00
31 changed files with 2001 additions and 1632 deletions
+2 -1
View File
@@ -4,4 +4,5 @@ node_modules
.next .next
# don't lint nyc coverage output # don't lint nyc coverage output
coverage coverage
next-env.d.ts next-env.d.ts
.eslintrc.js
+33
View File
@@ -0,0 +1,33 @@
module.exports = {
parserOptions: {
project: "./tsconfig.json"
},
extends: [
"plugin:@typescript-eslint/recommended",
"airbnb-typescript",
"next",
"next/core-web-vitals"
],
rules: {
"max-len": [
"warn",
240
],
"@typescript-eslint/quotes": [
"error",
"double"
],
"import/prefer-default-export": "warn",
"react/jsx-props-no-spreading": "off",
"react/prop-types": "off",
"react/jsx-one-expression-per-line": "off",
"eslintreact/jsx-one-expression-per-line": "off",
// Temp
"import/no-cycle": "warn",
"react/no-array-index-key": "warn",
"jsx-a11y/label-has-associated-control": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"jsx-a11y/no-static-element-interactions": "off",
}
}
-61
View File
@@ -1,61 +0,0 @@
{
"root": true,
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:import/errors",
"plugin:import/warnings",
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"airbnb-typescript"
],
"globals": {
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 2018,
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": [],
"settings": {
"react": {
"version": "detect"
}
},
"rules": {
"@typescript-eslint/naming-convention": "off",
"max-len": [
"warn",
240
],
"@typescript-eslint/quotes": [
"error",
"double"
],
"import/prefer-default-export": "warn",
"react/jsx-props-no-spreading": "off",
"react/prop-types": "off",
// Temp
"react/jsx-one-expression-per-line": "off",
"react/no-array-index-key": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"jsx-a11y/label-has-associated-control": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"jsx-a11y/no-static-element-interactions": "off"
}
}
+3
View File
@@ -12,6 +12,9 @@ install:
stage: setup stage: setup
script: script:
- npm ci - npm ci
after_script:
- node -v
- npm -v
artifacts: artifacts:
paths: paths:
- node_modules - node_modules
+1
View File
@@ -1,2 +1,3 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/types/global" /> /// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
+1 -4
View File
@@ -10,8 +10,5 @@ module.exports = withBundleAnalyzer({
"static.sahkoinsinoorikilta.fi", "static.sahkoinsinoorikilta.fi",
"api.dev.sahkoinsinoorikilta.fi", "api.dev.sahkoinsinoorikilta.fi",
], ],
}, }
future: {
webpack5: true,
},
}); });
+1833 -1457
View File
File diff suppressed because it is too large Load Diff
+9 -13
View File
@@ -20,8 +20,8 @@
"postbuild": "next-sitemap", "postbuild": "next-sitemap",
"export": "next export", "export": "next export",
"lint": "npm run lint:es && npm run lint:css", "lint": "npm run lint:es && npm run lint:css",
"lint:es": "eslint . --ext .ts,.tsx", "lint:es": "next lint",
"lint:es:fix": "eslint --fix . --ext .ts,.tsx", "lint:es:fix": "next lint --fix",
"lint:css": "stylelint \"./src/**/*.{ts,tsx}\"", "lint:css": "stylelint \"./src/**/*.{ts,tsx}\"",
"dev": "next dev", "dev": "next dev",
"start": "next dev", "start": "next dev",
@@ -37,21 +37,17 @@
"@types/react": "^17.0.11", "@types/react": "^17.0.11",
"@types/react-beautiful-dnd": "^13.0.0", "@types/react-beautiful-dnd": "^13.0.0",
"@types/react-csv": "^1.1.1", "@types/react-csv": "^1.1.1",
"@types/react-dom": "^17.0.7", "@types/react-dom": "^17.0.8",
"@types/react-jsonschema-form": "^1.7.5", "@types/react-jsonschema-form": "^1.7.5",
"@types/shortid": "^0.0.29", "@types/shortid": "^0.0.29",
"@types/styled-components": "^5.1.10", "@types/styled-components": "^5.1.10",
"@typescript-eslint/eslint-plugin": "^4.27.0", "@typescript-eslint/eslint-plugin": "^4.28.1",
"@typescript-eslint/parser": "^4.27.0",
"babel-plugin-styled-components": "^1.12.0", "babel-plugin-styled-components": "^1.12.0",
"eslint": "^7.28.0", "eslint": "^7.29.0",
"eslint-config-airbnb-typescript": "^12.3.1", "eslint-config-airbnb-typescript": "^12.3.1",
"eslint-plugin-import": "^2.23.4", "eslint-config-next": "^11.0.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.24.0",
"eslint-plugin-react-hooks": "^4.2.0",
"husky": "^6.0.0", "husky": "^6.0.0",
"next-sitemap": "^1.6.116", "next-sitemap": "^1.6.124",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"stylelint": "^13.13.1", "stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0", "stylelint-config-standard": "^22.0.0",
@@ -60,13 +56,13 @@
"typescript": "^4.3.2" "typescript": "^4.3.2"
}, },
"dependencies": { "dependencies": {
"@next/bundle-analyzer": "^10.2.3", "@next/bundle-analyzer": "^11.0.1",
"axios": "^0.21.1", "axios": "^0.21.1",
"date-fns": "^2.22.1", "date-fns": "^2.22.1",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"js-cookie": "^2.2.1", "js-cookie": "^2.2.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"next": "^10.2.3", "next": "^11.0.1",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"react": "^17.0.2", "react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0", "react-beautiful-dnd": "^13.1.0",
+3 -2
View File
@@ -1,7 +1,6 @@
import React from "react"; import React from "react";
// eslint-disable-next-line react/display-name const Icons = (): JSX.Element => (
export default (): JSX.Element => (
<> <>
<link rel="shortcut icon" href="/favicons/favicon.ico" /> <link rel="shortcut icon" href="/favicons/favicon.ico" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png" /> <link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png" />
@@ -54,3 +53,5 @@ export default (): JSX.Element => (
<link rel="yandex-tableau-widget" href="/favicons/yandex-browser-manifest.json" /> <link rel="yandex-tableau-widget" href="/favicons/yandex-browser-manifest.json" />
</> </>
); );
export default Icons;
+3 -2
View File
@@ -1,7 +1,6 @@
import React from "react"; import React from "react";
// eslint-disable-next-line react/display-name const Logo = (): JSX.Element => (
export default (): JSX.Element => (
// eslint-disable-next-line react/no-danger // eslint-disable-next-line react/no-danger
<head dangerouslySetInnerHTML={{ <head dangerouslySetInnerHTML={{
__html: __html:
@@ -55,3 +54,5 @@ export default (): JSX.Element => (
}} }}
/> />
); );
export default Logo;
@@ -8,6 +8,7 @@ const Container = styled.label`
padding-left: 2rem; padding-left: 2rem;
cursor: pointer; cursor: pointer;
font-size: 1.5rem; /* 24px */ font-size: 1.5rem; /* 24px */
line-height: 1;
user-select: none; user-select: none;
/* On mouse-over, add a grey background color */ /* On mouse-over, add a grey background color */
@@ -22,7 +22,7 @@ type CheckboxesProps = Omit<WidgetProps, "options"> & {
}; };
const CheckboxContainer = styled.div` const CheckboxContainer = styled.div`
margin-bottom: 0.5rem; margin-bottom: 1rem;
`; `;
const Checkboxes: React.FC<CheckboxesProps> = ({ const Checkboxes: React.FC<CheckboxesProps> = ({
@@ -7,6 +7,7 @@ const Container = styled.label`
padding-left: 2rem; padding-left: 2rem;
cursor: pointer; cursor: pointer;
font-size: 1.5rem; /* 24px */ font-size: 1.5rem; /* 24px */
line-height: 1;
user-select: none; user-select: none;
/* On mouse-over, add a grey background color */ /* On mouse-over, add a grey background color */
@@ -8,7 +8,7 @@ type RadioButtonWidgetProps = Omit<WidgetProps, "options"> & {
}; };
const RadioButtonContainer = styled.div` const RadioButtonContainer = styled.div`
margin-bottom: 0.5rem; margin-bottom: 1rem;
`; `;
const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => { const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
@@ -27,7 +27,9 @@ const Heading = styled.h3`
const SectionDividerWidget: React.FC<SectionDividerWidgetProps> = ({ label }) => ( const SectionDividerWidget: React.FC<SectionDividerWidgetProps> = ({ label }) => (
<Heading> <Heading>
{label}&nbsp;{getIconByLabel(label)} {label}
&nbsp;
{getIconByLabel(label)}
</Heading> </Heading>
); );
@@ -13,7 +13,7 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
handleListOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => { handleListOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props; const { onChange } = this.props;
const val = event.target.value; const val = event.target.value;
const lst = val.split(",").map((p) => p.trimLeft()); const lst = val.split(";").map((p) => p.trimLeft());
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
questions[index].options = lst; questions[index].options = lst;
onChange(questions); onChange(questions);
@@ -31,7 +31,7 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
const { onChange } = this.props; const { onChange } = this.props;
const val = event.target.value; const val = event.target.value;
if (val !== "") { if (val !== "") {
const lst = val.split(",").map((p) => p.trimLeft()); const lst = val.split(";").map((p) => p.trimLeft());
// Ignore everything else but the two first values // Ignore everything else but the two first values
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
questions[index].options = lst.splice(0, 2); questions[index].options = lst.splice(0, 2);
@@ -51,19 +51,20 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
onChange(questions); onChange(questions);
}; };
requiredField() { requiredField(): JSX.Element {
const { inputProps } = this.props; const { inputProps } = this.props;
const { questions, index } = inputProps; const { questions, index } = inputProps;
return ( return (
<Checkbox <Checkbox
checked={questions[index].required} checked={questions[index].required}
onChange={this.handleRequiredChange(questions, index)} onChange={this.handleRequiredChange(questions, index)}
>Required? >
Required?
</Checkbox> </Checkbox>
); );
} }
render() { render(): JSX.Element {
const { inputProps } = this.props; const { inputProps } = this.props;
const { const {
type, value, questions, index, type, value, questions, index,
@@ -92,12 +93,12 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
if (type === "integer") { if (type === "integer") {
const lst = value as string[]; const lst = value as string[];
const joinedValue = lst.join(","); const joinedValue = lst.join(";");
return ( return (
<> <>
<input <input
type="text" type="text"
placeholder="Minimum,Maximum" placeholder="Minimum;Maximum"
value={joinedValue} value={joinedValue}
onChange={this.handleIntegerOptionsChange(questions, index)} onChange={this.handleIntegerOptionsChange(questions, index)}
/> />
@@ -108,12 +109,12 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
if (type === "radiobutton") { if (type === "radiobutton") {
const lst = value as string[]; const lst = value as string[];
const joinedValue = lst.join(","); const joinedValue = lst.join(";");
return ( return (
<> <>
<input <input
type="text" type="text"
placeholder="Yes,no,maybe" placeholder="Yes;no;maybe"
value={joinedValue} value={joinedValue}
onChange={this.handleListOptionsChange(questions, index)} onChange={this.handleListOptionsChange(questions, index)}
/> />
@@ -124,12 +125,12 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
if (type === "checkbox") { if (type === "checkbox") {
const lst = value as string[]; const lst = value as string[];
const joinedValue = lst.join(","); const joinedValue = lst.join(";");
return ( return (
<> <>
<input <input
type="text" type="text"
placeholder="A,B,C" placeholder="A;B;C"
value={joinedValue} value={joinedValue}
onChange={this.handleListOptionsChange(questions, index)} onChange={this.handleListOptionsChange(questions, index)}
/> />
+3 -1
View File
@@ -26,7 +26,9 @@ const NotFoundPage: NextPage = () => (
<Header /> <Header />
<NotFound id="not-found"> <NotFound id="not-found">
<p> <p>
<strong>404</strong> | Ei&nbsp;vaan&nbsp;löydy <strong>404</strong>
{" "}
| Ei&nbsp;vaan&nbsp;löydy
</p> </p>
</NotFound> </NotFound>
</> </>
+1 -2
View File
@@ -126,11 +126,10 @@ const AppContainer = styled.div`
background-color: ${colors.white}; background-color: ${colors.white};
`; `;
const Web20App = ({ Component, pageProps }: AppProps) => ( const Web20App = ({ Component, pageProps }: AppProps): JSX.Element => (
<> <>
<Head> <Head>
<meta httpEquiv="Content-Type" content="text/html; charset=utf-8" /> <meta httpEquiv="Content-Type" content="text/html; charset=utf-8" />
<link href="https://fonts.googleapis.com/css?family=Montserrat:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,800,900&display=swap" rel="stylesheet" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" /> <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aalto-yliopiston Sähköinsinöörikilta ry</title> <title>Aalto-yliopiston Sähköinsinöörikilta ry</title>
+2 -1
View File
@@ -29,12 +29,13 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
} }
} }
render() { render(): JSX.Element {
const { styleTags } = this.props; const { styleTags } = this.props;
return ( return (
<Html lang="fi"> <Html lang="fi">
<Head> <Head>
<HTMLLogo /> <HTMLLogo />
<link href="https://fonts.googleapis.com/css?family=Montserrat:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,800,900&display=swap" rel="stylesheet" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css" /> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/8.0.0/normalize.min.css" />
<Favicons /> <Favicons />
</Head> </Head>
+4 -2
View File
@@ -46,7 +46,8 @@ const AdminLoginPage: NextPage = () => {
<div className="error">You have to log in first.</div> <div className="error">You have to log in first.</div>
)} )}
<form className="admin-login-form" onSubmit={handleSubmit}> <form className="admin-login-form" onSubmit={handleSubmit}>
<label>Username <label>
Username
<input <input
id="login-username" id="login-username"
type="text" type="text"
@@ -57,7 +58,8 @@ const AdminLoginPage: NextPage = () => {
}} }}
/> />
</label> </label>
<label>Password <label>
Password
<input <input
id="login-password" id="login-password"
type="password" type="password"
+4 -1
View File
@@ -61,7 +61,10 @@ const SignupEmailPage: NextPage = () => {
return ( return (
<AdminListCommon> <AdminListCommon>
<h1>{title}: Sign-ups</h1> <h1>
{title}
: Sign-ups
</h1>
<table> <table>
<thead> <thead>
<tr> <tr>
+7 -18
View File
@@ -4,38 +4,27 @@ import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Event from "@models/Event"; import Event from "@models/Event";
import EventApi from "@api/eventApi"; import EventApi from "@api/eventApi";
import useFetchEvents from "@hooks/useFetchEvents";
import EventPageView from "@views/EventPage/EventPageView"; import EventPageView from "@views/EventPage/EventPageView";
import PageWrapper from "@views/common/PageWrapper"; import PageWrapper from "@views/common/PageWrapper";
import LoadingView from "@views/common/LoadingView"; import LoadingView from "@views/common/LoadingView";
import NotFoundPage from "@pages/404";
interface InitialProps { interface InitialProps {
initialEvent: Event; event: Event;
} }
const EventPage: NextPage<InitialProps> = ({ initialEvent }) => { const EventPage: NextPage<InitialProps> = ({ event }) => {
const router = useRouter(); const router = useRouter();
const { id } = router.query; const { id } = router.query;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { data, error } = useFetchEvents({ initialData: initialEvent, id: id as string });
if (router.isFallback) return <LoadingView />; if (router.isFallback) return <LoadingView />;
if (!data) {
return (
<NotFoundPage />
);
}
return ( return (
<> <>
<Head> <Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/events/${id}`} /> <link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/events/${id}`} />
</Head> </Head>
<PageWrapper> <PageWrapper>
<EventPageView event={data as Event} /> <EventPageView event={event} />
</PageWrapper> </PageWrapper>
</> </>
); );
@@ -58,17 +47,17 @@ export const getStaticPaths: GetStaticPaths = async () => {
export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => { export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => {
const { id } = params; const { id } = params;
let notFound = false; let notFound = false;
let initialEvent: Event; let event: Event;
try { try {
initialEvent = await EventApi.getEvent(Number(id)); event = await EventApi.getEvent(Number(id));
} catch (err) { } catch (err) {
notFound = true; notFound = true;
} }
return { return {
props: { props: {
initialEvent, event,
}, },
revalidate: 10, revalidate: 10, // Required for deleting hidden pages
notFound, notFound,
}; };
}; };
+7 -18
View File
@@ -4,38 +4,27 @@ import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import Post from "@models/Feed"; import Post from "@models/Feed";
import FeedApi from "@api/feedApi"; import FeedApi from "@api/feedApi";
import useFetchFeed from "@hooks/useFetchFeed";
import FeedPageView from "@views/FeedPage/FeedPageView"; import FeedPageView from "@views/FeedPage/FeedPageView";
import PageWrapper from "@views/common/PageWrapper"; import PageWrapper from "@views/common/PageWrapper";
import LoadingView from "@views/common/LoadingView"; import LoadingView from "@views/common/LoadingView";
import NotFoundPage from "@pages/404";
interface InitialProps { interface InitialProps {
initialPost: Post; post: Post;
} }
const FeedPage: NextPage<InitialProps> = ({ initialPost }) => { const FeedPage: NextPage<InitialProps> = ({ post }) => {
const router = useRouter(); const router = useRouter();
const { id } = router.query; const { id } = router.query;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { data, error } = useFetchFeed({ initialData: initialPost, id: id as string });
if (router.isFallback) return <LoadingView />; if (router.isFallback) return <LoadingView />;
if (!data) {
return (
<NotFoundPage />
);
}
return ( return (
<> <>
<Head> <Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/feed/${id}`} /> <link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/feed/${id}`} />
</Head> </Head>
<PageWrapper> <PageWrapper>
<FeedPageView post={data} /> <FeedPageView post={post} />
</PageWrapper> </PageWrapper>
</> </>
); );
@@ -58,18 +47,18 @@ export const getStaticPaths: GetStaticPaths = async () => {
export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => { export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => {
const { id } = params; const { id } = params;
let notFound = false; let notFound = false;
let initialPost: Post; let post: Post;
try { try {
initialPost = await FeedApi.getPost(Number(id)); post = await FeedApi.getPost(Number(id));
} catch (err) { } catch (err) {
notFound = true; notFound = true;
} }
return { return {
props: { props: {
initialPost, post,
}, },
revalidate: 10, revalidate: 10, // Required for deleting hidden pages
notFound, notFound,
}; };
}; };
+30 -17
View File
@@ -2,7 +2,10 @@ import React from "react";
import { NextPage, GetStaticProps, GetStaticPaths } from "next"; import { NextPage, GetStaticProps, GetStaticPaths } from "next";
import Head from "next/head"; import Head from "next/head";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { ISubmitEvent } from "react-jsonschema-form";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import axios from "axios";
import useSWR, { mutate } from "swr";
import { Signup, SignupForm } from "@models/Signup"; import { Signup, SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi"; import SignupApi from "@api/signupApi";
import SignUpPageView from "@views/SignUpPage/SignUpPageView"; import SignUpPageView from "@views/SignUpPage/SignUpPageView";
@@ -12,34 +15,44 @@ import noop from "@utils/noop";
import NotFoundPage from "@pages/404"; import NotFoundPage from "@pages/404";
type InitialProps = { type InitialProps = {
form: SignupForm; initialForm: SignupForm;
}; };
const SignUpPage: NextPage<InitialProps> = ({ form }) => { const FORM_URL = `${process.env.NEXT_PUBLIC_API_URL}/signupForm/`;
const router = useRouter();
if (router.isFallback) { const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
const router = useRouter();
const id = String(initialForm?.id ?? "");
const URL = `${FORM_URL}${id}/`;
const { data, error } = useSWR<SignupForm>(URL, (url) => axios.get(url).then((res) => res.data), { initialData: initialForm });
if (error) {
console.error(error);
}
// TODO: Shows LoadingView on client-side fetch error. Maybe something else preferred?
if (router.isFallback || error) {
return <LoadingView />; return <LoadingView />;
} }
if (!form) { if (!data) {
return ( return (
<NotFoundPage /> <NotFoundPage />
); );
} }
const onSubmit = async (data) => { const onSubmit = async ({ formData }: ISubmitEvent<string>) => {
const payload: Signup = { const payload: Signup = {
signupForm_id: form.id, signupForm_id: data.id,
answer: data.formData, answer: formData,
}; };
try { try {
await SignupApi.createSignup(payload); await SignupApi.createSignup(payload);
toast.success("Sign-up submitted successfully 😎"); toast.success("Sign-up submitted successfully 😎");
router.push(window.location.href); // TODO: Fetch/update signup list, so user sees the signup in the list mutate(URL);
} catch (error) { } catch (err) {
console.error(error); console.error(err);
toast.error("Uh oh! Sign-up failed! 😟"); toast.error("Uh oh! Sign-up failed! 😟");
} }
}; };
@@ -47,11 +60,11 @@ const SignUpPage: NextPage<InitialProps> = ({ form }) => {
return ( return (
<> <>
<Head> <Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/signup/${form.id}`} /> <link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/signup/${data.id}`} />
</Head> </Head>
<PageWrapper> <PageWrapper>
<SignUpPageView <SignUpPageView
signUpForm={form} signUpForm={data}
formData={{}} formData={{}}
onChange={noop} onChange={noop}
onSubmit={onSubmit} onSubmit={onSubmit}
@@ -78,17 +91,17 @@ export const getStaticPaths: GetStaticPaths = async () => {
export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => { export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => {
const { id } = params; const { id } = params;
let notFound = false; let notFound = false;
let form: SignupForm; let initialForm: SignupForm;
try { try {
form = await SignupApi.getForm(Number(id)); initialForm = await SignupApi.getForm(Number(id));
} catch { } catch {
notFound = true; notFound = true;
} }
return { return {
props: { props: {
form, initialForm,
}, },
revalidate: 10, revalidate: 10, // Required for deleting hidden pages
notFound, notFound,
}; };
}; };
-1
View File
@@ -60,7 +60,6 @@ const EditSignUpPage: NextPage = () => {
try { try {
await SignupApi.updateSignup(payload, uuid); await SignupApi.updateSignup(payload, uuid);
// TODO: Update signup list, so user sees possible changes in the list
toast.success("Sign-up updated successfully 😎"); toast.success("Sign-up updated successfully 😎");
} catch (error) { } catch (error) {
console.error(error); console.error(error);
+4 -3
View File
@@ -30,7 +30,7 @@ const orderedCommittees = [
Others, Others,
]; ];
const blank_profile = "/img/blank_profile.png"; const blankProfile = "/img/blank_profile.png";
const BlueLink = styled(Link)` const BlueLink = styled(Link)`
color: ${colors.blue1}; color: ${colors.blue1};
@@ -91,7 +91,7 @@ const CommitteeContainer: React.FC<{
name={representative.name} name={representative.name}
phone={representative.phone_number} phone={representative.phone_number}
email={representative.email} email={representative.email}
image={(committee.name_en === "Board") ? (representative.image || blank_profile) : null} image={(committee.name_en === "Board") ? (representative.image || blankProfile) : null}
role_fi={role.name_fi} role_fi={role.name_fi}
role_en={role.name_en} role_en={role.name_en}
/> />
@@ -126,7 +126,8 @@ const ContactsPageView: React.FC = () => (
<TextSection> <TextSection>
<h1>Yhteystiedot</h1> <h1>Yhteystiedot</h1>
<p> <p>
Asiaa olisi, mutta kehen ottaa yhteyttä?<br /> Asiaa olisi, mutta kehen ottaa yhteyttä?
<br />
Tämä sivu yrittää valottaa sen oikean ihmisen puhelinnumeroa ja sähköpostiosoitetta. Tämä sivu yrittää valottaa sen oikean ihmisen puhelinnumeroa ja sähköpostiosoitetta.
</p> </p>
<aside> <aside>
@@ -11,6 +11,11 @@ const FreshmenPageHero: React.FC = () => (
/> />
<HeroAside bgColor="lightTurquoise"> <HeroAside bgColor="lightTurquoise">
<HeroAsideItem
header="Lue killan fuksiopas"
link="https://static.sahkoinsinoorikilta.fi/FTMK/Fuksiopas2021.pdf"
linkText="lue fuksiopas täältä!"
/>
<HeroAsideItem <HeroAsideItem
header="Seuraa killan tiedotusta" header="Seuraa killan tiedotusta"
link="https://t.me/joinchat/rKg3rCtAVkkyNTdk" link="https://t.me/joinchat/rKg3rCtAVkkyNTdk"
+23 -12
View File
@@ -15,6 +15,17 @@ const EMAIL_LINK_MAILTO = `mailto:${EMAIL_LINK}`;
const ImageContainer = styled.div` const ImageContainer = styled.div`
width: 100%; width: 100%;
display: block; display: block;
margin: auto;
`;
const QRImages = styled.img`
width: 10em;
height: 10em;
`;
const FopasImage = styled.img`
width: 15em;
margin-bottom: 1em;
`; `;
const FreshmenPageView: React.FC = () => ( const FreshmenPageView: React.FC = () => (
@@ -92,6 +103,16 @@ const FreshmenPageView: React.FC = () => (
</div> </div>
<div> <div>
<InfoBox> <InfoBox>
<Link to="https://static.sahkoinsinoorikilta.fi/FTMK/Fuksiopas2021.pdf">
<FopasImage
src="https://static.sahkoinsinoorikilta.fi/FTMK/Fuksiopas2021-kansi.png"
/>
</Link>
<h6>Killan Fuksiopas</h6>
<p>Ennen opintojen alkua on hyvä tutustua killan fuksioppaaseen. Sitä pääset selailemaan
<Link to="https://static.sahkoinsinoorikilta.fi/FTMK/Fuksiopas2021.pdf"> tästä.</Link>
</p>
<br/>
<h6>Telegram?</h6> <h6>Telegram?</h6>
<p> <p>
Telegram on pikaviestinpalvelu, jota käytetään otaniemessä paljon. Telegram on pikaviestinpalvelu, jota käytetään otaniemessä paljon.
@@ -101,23 +122,13 @@ const FreshmenPageView: React.FC = () => (
<p> <p>
SIK:n fukseilla on oma Telegram-ryhmä, jonne pääset liitymään tästä: SIK:n fukseilla on oma Telegram-ryhmä, jonne pääset liitymään tästä:
</p> </p>
<Image <QRImages
src="https://static.sahkoinsinoorikilta.fi/FTMK/sik-fuksit-2021-tg.png" src="https://static.sahkoinsinoorikilta.fi/FTMK/sik-fuksit-2021-tg.png"
alt="SIK-Fuksit 2021 TG"
layout="responsive"
width={0}
height={0}
objectFit="contain"
/> />
<p>tai <Link to="https://tinyurl.com/sik-fuksit-2021">tästä</Link></p> <p>tai <Link to="https://tinyurl.com/sik-fuksit-2021">tästä</Link></p>
<p>Liity myös samalla SIK-fuksien tiedotuskanavalle tästä:</p> <p>Liity myös samalla SIK-fuksien tiedotuskanavalle tästä:</p>
<Image <QRImages
src="https://static.sahkoinsinoorikilta.fi/FTMK/sik-fuksit-2021-tiedotus-tg.png" src="https://static.sahkoinsinoorikilta.fi/FTMK/sik-fuksit-2021-tiedotus-tg.png"
alt="SIK-Fuksit 2021 Tiedotus TG"
layout="responsive"
width={0}
height={0}
objectFit="contain"
/> />
<p>tai <Link to="https://tinyurl.com/sik-fuksit-2021-tiedotus">tästä</Link></p> <p>tai <Link to="https://tinyurl.com/sik-fuksit-2021-tiedotus">tästä</Link></p>
</InfoBox> </InfoBox>
+1 -1
View File
@@ -76,7 +76,7 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
<h6> <h6>
{t("Ilmoittautuneet")} {signUpForm.quota > 0 && (` (${signUpForm.signups.length}/${signUpForm.quota})`)}: {t("Ilmoittautuneet")} {signUpForm.quota > 0 && (` (${signUpForm.signups.length}/${signUpForm.quota})`)}:
</h6> </h6>
<ol> <ol data-e2e="signup-list">
{signUpForm.signups.map((s, idx) => ( {signUpForm.signups.map((s, idx) => (
<li key={idx} className={signUpForm.quota && idx + 1 > signUpForm.quota ? "reserved" : ""}>{s}</li> <li key={idx} className={signUpForm.quota && idx + 1 > signUpForm.quota ? "reserved" : ""}>{s}</li>
))} ))}
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"src": "tests/testcafe", "src": "tests/testcafe",
"browsers": "all", "browsers": "chrome",
"concurrency": 1, "concurrency": 1,
"screenshots": { "screenshots": {
"path": "e2e-screenshots/", "path": "e2e-screenshots/",
+2
View File
@@ -58,4 +58,6 @@ test("User signups to event from front page", async (t) => {
.expect( .expect(
statusMessage.innerText, statusMessage.innerText,
).eql("Sign-up submitted successfully 😎"); ).eql("Sign-up submitted successfully 😎");
await t.expect(Selector("[data-e2e=\"signup-list\"] > li").nth(0).innerText).eql("Testi Testeri");
}); });