Compare commits

..

30 Commits

Author SHA1 Message Date
Ojakoo e5b511148a add authentication wrapper to api requests 2023-02-12 12:52:48 +02:00
Ilari Ojakorpi 4146af7207 Merge branch 'update-react' into 'master'
Update React & Next.js

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!109
2023-02-12 08:01:22 +00:00
Ojakoo c243e76324 fix lint 2023-02-02 22:09:56 +02:00
Ilari Ojakorpi 659d0e63a0 Merge branch 'master' into 'update-react'
# Conflicts:
#   src/views/ContactsPage/ContactsPageView.tsx
2023-02-02 10:47:00 +00:00
Ojakoo 2c6c1d1e67 Update sentry 2023-02-01 13:47:44 +02:00
Ojakoo eeb2f949c6 Update Next Link 2023-02-01 13:04:05 +02:00
Ojakoo 894e630664 Update next image import to legacy version 2023-02-01 12:52:28 +02:00
Ojakoo 56c13dbf64 Next 13 2023-02-01 12:48:33 +02:00
Ojakoo 9c0e1a0e61 Updated sponsor links 2023-01-26 14:20:29 +02:00
Ojakoo 3b2d0596c9 Updated board info 2023-01-26 14:17:39 +02:00
Ojakoo 2395321825 Updated contacts 2023-01-19 13:19:06 +02:00
Ojakoo 05b045c2fc update media card 2023-01-09 16:05:21 +02:00
Ojakoo faf12816bb Updated board info 2023-01-02 00:52:44 +02:00
Ojakoo e7ef69d75f fix spelling mistake 2022-12-25 14:53:36 +02:00
Ojakoo 03e6131fe8 #47 add 2022 honors 2022-12-21 16:58:25 +02:00
Ojakoo 87f803ca3e Update readme. 2022-12-21 16:41:50 +02:00
Ojakoo dd3eded4a1 add filter and sort functionality to admin pages 2022-11-06 14:25:07 +02:00
Ojakoo efacbe9c40 useSWR in admin signups 2022-11-06 14:05:17 +02:00
Ojakoo c7a1502a26 correct asc/desc 2022-11-06 12:46:56 +02:00
Ojakoo 59a4f3567e basic styling, use arrow functions 2022-11-06 12:44:37 +02:00
Ojakoo 0ad59bfba6 dont use python syntax in js 2022-11-06 12:26:40 +02:00
Ojakoo 6aa0b3fe19 Added filter base 2022-11-06 11:56:55 +02:00
Ojakoo 88d5e57858 Added GE healthcare to corporate logos. 2022-10-11 18:19:52 +03:00
Aarni Halinen 07efb4caed override react-mde react peer dependencies 2022-07-25 00:07:27 +03:00
Aarni Halinen ce29f5a311 fix lint issues after next update 2022-07-25 00:07:27 +03:00
Aarni Halinen e1d4a300c5 update next 2022-07-25 00:07:27 +03:00
Aarni Halinen 90f33048d7 update lockfile 2022-07-25 00:07:27 +03:00
Aarni Halinen c55c7699c7 update _document 2022-07-25 00:07:27 +03:00
Aarni Halinen 2e37072703 add children props 2022-07-25 00:07:27 +03:00
Aarni Halinen aa90d97007 update all react packages 2022-07-25 00:07:27 +03:00
50 changed files with 2331 additions and 1094 deletions
+16 -5
View File
@@ -5,14 +5,25 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
* **[React](https://facebook.github.io/react/)** (17.x)
* **[Typescript](https://www.typescriptlang.org/)** (4.x)
* **[Next.js](https://nextjs.org/)** (12.x)
* [Testcafe](https://devexpress.github.io/testcafe/) - E2E Testing framework
* **[Testcafe](https://devexpress.github.io/testcafe/)** - E2E Testing framework
## Installation
1. Clone/download repo
2. Install node v16 ([`nvm`](https://github.com/nvm-sh/nvm))
3. `cp .env.local.example .env.local`
4. `npm install`
Install node v16 with **[Node Version Manager](https://github.com/nvm-sh/nvm#installing-and-updating)**.
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the master branch:
```bash
git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-frontend.git
cd web2.0-frontend
git checkout master
```
Create local env file for development and install dependencies:
```bash
cp .env.local.example .env.local
npm install
```
## Getting Started
+3 -1
View File
@@ -16,7 +16,6 @@ const sentryWebpackPluginOptions = {
};
module.exports = withBundleAnalyzer(withSentryConfig({
target: "server",
images: {
domains: [
"api.sahkoinsinoorikilta.fi",
@@ -24,4 +23,7 @@ module.exports = withBundleAnalyzer(withSentryConfig({
"api.dev.sahkoinsinoorikilta.fi",
],
},
sentry: {
hideSourceMaps: true, // Hide source maps, see: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#configure-source-maps
}
}, sentryWebpackPluginOptions));
+1739 -768
View File
File diff suppressed because it is too large Load Diff
+20 -14
View File
@@ -37,9 +37,9 @@
"@types/jest": "^27.4.1",
"@types/js-cookie": "^3.0.1",
"@types/node": "^16.11.36",
"@types/react": "^17.0.19",
"@types/react-csv": "^1.1.2",
"@types/react-dom": "^17.0.9",
"@types/react": "^18.0.15",
"@types/react-csv": "^1.1.3",
"@types/react-dom": "^18.0.6",
"@types/shortid": "^0.0.29",
"@types/styled-components": "^5.1.25",
"@typescript-eslint/eslint-plugin": "^5.18.0",
@@ -48,11 +48,11 @@
"eslint": "^8.13.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-next": "^12.1.4",
"eslint-config-next": "^13.1.6",
"eslint-plugin-import": "^2.26.0",
"husky": "^7.0.4",
"jest": "^27.5.1",
"next-sitemap": "^2.5.19",
"next-sitemap": "^3.1.11",
"npm-run-all": "^4.1.5",
"postcss-jsx": "^0.36.4",
"postcss-syntax": "^0.36.2",
@@ -64,31 +64,37 @@
"typescript": "^4.6.3"
},
"dependencies": {
"@next/bundle-analyzer": "^12.1.4",
"@rjsf/core": "^4.1.1",
"@sentry/nextjs": "^6.19.6",
"@next/bundle-analyzer": "^12.2.3",
"@rjsf/core": "^4.2.0",
"@sentry/nextjs": "^7.34.0",
"axios": "^0.26.1",
"date-fns": "^2.28.0",
"fast-deep-equal": "^3.1.3",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"next": "^12.1.4",
"next": "^13.1.6",
"normalize.css": "^8.0.1",
"react": "^17.0.2",
"react": "^18.2.0",
"react-csv": "^2.2.2",
"react-dnd": "15.0.2",
"react-dnd-html5-backend": "15.0.2",
"react-dnd-touch-backend": "15.0.2",
"react-dom": "^17.0.2",
"react-is": "^17.0.2",
"react-markdown": "^8.0.2",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"react-markdown": "^8.0.3",
"react-mde": "^11.5.0",
"react-toastify": "^8.2.0",
"react-toastify": "^9.0.7",
"rehype-raw": "^6.1.1",
"rehype-sanitize": "^5.0.1",
"sharp": "^0.30.3",
"shortid": "^2.2.16",
"styled-components": "^5.3.5",
"swr": "^1.2.2"
},
"overrides": {
"react-mde": {
"react": "$react",
"react-dom": "$react-dom"
}
}
}
+40 -1
View File
@@ -1,7 +1,10 @@
import {
deleteTokenCookies, getAccessTokenCookie, getRefreshTokenCookie, setAccessTokenCookie, setRefreshTokenCookie,
} from "@utils/auth";
import { APIPath, postBackendAPI } from "./backend";
import {
APIPath, postBackendAPI, getBackendAPI, putBackendAPI, deleteBackendAPI,
API,
} from "./backend";
export type AuthTokenRequest = {
username: string;
@@ -71,3 +74,39 @@ export const authenticate = async (): Promise<boolean> => {
return refreshToken();
}
};
export const authedGetBackendAPI = async <ResponseType>({
path, urlParams, queryParams, authenticated = true,
}: API): Promise<ResponseType> => {
if (authenticated) await authenticate();
return getBackendAPI<ResponseType>({
path, urlParams, queryParams, authenticated,
});
};
export const authedPostBackendAPI = async <RequestType, ResponseType>({
path, urlParams, queryParams, authenticated = true,
}: API, body: RequestType): Promise<ResponseType> => {
if (authenticated) await authenticate();
return postBackendAPI<RequestType, ResponseType>({
path, urlParams, queryParams, authenticated,
}, body);
};
export const authedPutBackendAPI = async <RequestType, ResponseType>({
path, urlParams, queryParams, authenticated = true,
}: API, body: RequestType): Promise<ResponseType> => {
if (authenticated) await authenticate();
return putBackendAPI<RequestType, ResponseType>({
path, urlParams, queryParams, authenticated,
}, body);
};
export const authedDeleteBackendAPI = async <ResponseType>({
path, urlParams, queryParams, authenticated = true,
}: API): Promise<ResponseType> => {
if (authenticated) await authenticate();
return deleteBackendAPI<ResponseType>({
path, urlParams, queryParams, authenticated,
});
};
-1
View File
@@ -11,7 +11,6 @@ export enum APIPath {
FEED = "/feed/:id",
JOBADS = "/jobads/:id",
SIGNUPS = "/signup/:id",
SIGNUPS_DELETE = "/signup/:id/delete",
SIGNUPS_EDIT = "/signup/:id/edit",
SIGNUP_FORMS = "/signupForm/:id",
SIGNUP_FORMS_EMAIL = "/signupForm/:id/sendemail",
+12 -9
View File
@@ -1,8 +1,11 @@
/* eslint-disable no-console */
import Event from "@models/Event";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
APIPath,
} from "./backend";
import {
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
} from "./auth";
interface Options {
limit?: number;
@@ -14,7 +17,7 @@ interface Options {
class EventApi {
static getEvent = async (id: number, auth = false): Promise<Event> => {
try {
return await getBackendAPI<Event>({
return await authedGetBackendAPI<Event>({
path: APIPath.EVENTS, urlParams: { id }, authenticated: auth,
});
} catch (err) {
@@ -24,10 +27,10 @@ class EventApi {
};
static getEvents = async ({
since, limit, offset, auth,
since, limit, offset, auth = false,
}: Options = {}): Promise<Event[]> => {
try {
return await getBackendAPI<Event[]>({
return await authedGetBackendAPI<Event[]>({
path: APIPath.EVENTS,
queryParams: {
since,
@@ -44,8 +47,8 @@ class EventApi {
static createEvent = async (data: Event): Promise<Event> => {
try {
return await postBackendAPI<Event, Event>({
path: APIPath.EVENTS, authenticated: true,
return await authedPostBackendAPI<Event, Event>({
path: APIPath.EVENTS,
}, data);
} catch (err) {
console.error(err);
@@ -55,8 +58,8 @@ class EventApi {
static updateEvent = async (data: Event): Promise<Event> => {
try {
return await putBackendAPI<Event, Event>({
path: APIPath.EVENTS, urlParams: { id: data.id }, authenticated: true,
return await authedPutBackendAPI<Event, Event>({
path: APIPath.EVENTS, urlParams: { id: data.id },
}, data);
} catch (err) {
console.error(err);
@@ -66,7 +69,7 @@ class EventApi {
static deleteEvent = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id } });
} catch (err) {
console.error(err);
throw err;
+12 -9
View File
@@ -1,8 +1,11 @@
/* eslint-disable no-console */
import Post from "@models/Feed";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
APIPath,
} from "./backend";
import {
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
} from "./auth";
interface Options {
limit?: number;
@@ -11,9 +14,9 @@ interface Options {
}
class FeedApi {
static getPost = async (id: number, auth?: boolean): Promise<Post> => {
static getPost = async (id: number, auth = false): Promise<Post> => {
try {
return await getBackendAPI<Post>({
return await authedGetBackendAPI<Post>({
path: APIPath.FEED, urlParams: { id }, authenticated: auth,
});
} catch (err) {
@@ -22,9 +25,9 @@ class FeedApi {
}
};
static getFeed = async ({ limit, offset, auth }: Options = {}): Promise<Post[]> => {
static getFeed = async ({ limit, offset, auth = false }: Options = {}): Promise<Post[]> => {
try {
return await getBackendAPI<Post[]>({
return await authedGetBackendAPI<Post[]>({
path: APIPath.FEED,
queryParams: {
limit,
@@ -40,7 +43,7 @@ class FeedApi {
static createPost = async (data: Post): Promise<Post> => {
try {
return await postBackendAPI<Post, Post>({ path: APIPath.FEED, authenticated: true }, data);
return await authedPostBackendAPI<Post, Post>({ path: APIPath.FEED }, data);
} catch (err) {
console.error(err);
throw err;
@@ -49,8 +52,8 @@ class FeedApi {
static updatePost = async (data: Post): Promise<Post> => {
try {
return await putBackendAPI<Post, Post>({
path: APIPath.FEED, urlParams: { id: data.id }, authenticated: true,
return await authedPutBackendAPI<Post, Post>({
path: APIPath.FEED, urlParams: { id: data.id },
}, data);
} catch (err) {
console.error(err);
@@ -60,7 +63,7 @@ class FeedApi {
static deletePost = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id } });
} catch (err) {
console.error(err);
throw err;
+12 -9
View File
@@ -1,8 +1,11 @@
/* eslint-disable no-console */
import JobAd from "@models/JobAd";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
APIPath,
} from "./backend";
import {
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
} from "./auth";
interface Options {
since?: Date;
@@ -14,7 +17,7 @@ interface Options {
class JobAdApi {
static getJobAd = async (id: number, auth = false): Promise<JobAd> => {
try {
return await getBackendAPI({
return await authedGetBackendAPI({
path: APIPath.JOBADS, urlParams: { id }, authenticated: auth,
});
} catch (err) {
@@ -24,10 +27,10 @@ class JobAdApi {
};
static getJobAds = async ({
since, limit, offset, auth,
since, limit, offset, auth = false,
}: Options = {}): Promise<JobAd[]> => {
try {
return await getBackendAPI<JobAd[]>({
return await authedGetBackendAPI<JobAd[]>({
path: APIPath.JOBADS,
queryParams: {
since,
@@ -44,8 +47,8 @@ class JobAdApi {
static createJobAd = async (data: JobAd): Promise<JobAd> => {
try {
return await postBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS, authenticated: true,
return await authedPostBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS,
}, data);
} catch (err) {
console.error(err);
@@ -55,8 +58,8 @@ class JobAdApi {
static updateJobAd = async (data: JobAd): Promise<JobAd> => {
try {
return await putBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS, urlParams: { id: data.id }, authenticated: true,
return await authedPutBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS, urlParams: { id: data.id },
}, data);
} catch (err) {
console.error(err);
@@ -66,7 +69,7 @@ class JobAdApi {
static deleteJobAd = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.JOBADS, urlParams: { id }, authenticated: true });
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.JOBADS, urlParams: { id } });
} catch (err) {
console.error(err);
throw err;
+18 -24
View File
@@ -1,8 +1,11 @@
/* eslint-disable no-console */
import { Signup, SignupForm } from "@models/Signup";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
APIPath, postBackendAPI,
} from "./backend";
import {
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
} from "./auth";
export type EmailRequest = {
mode: "all" | "actual" | "reserve";
@@ -13,8 +16,8 @@ export type EmailRequest = {
class SignupApi {
static getSignup = async (id: number): Promise<Signup> => {
try {
return await getBackendAPI<Signup>({
path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true,
return await authedGetBackendAPI<Signup>({
path: APIPath.SIGNUPS, urlParams: { id },
});
} catch (err) {
console.error(err);
@@ -37,7 +40,7 @@ class SignupApi {
try {
const { id } = data;
if (!id) throw new Error("SignupId required!");
return await putBackendAPI<Signup, Signup>({
return await authedPutBackendAPI<Signup, Signup>({
path: APIPath.SIGNUPS_EDIT,
urlParams: {
id,
@@ -54,7 +57,7 @@ class SignupApi {
static getSignupUUID = async (id: number, uuid: string): Promise<Signup> => {
try {
return await getBackendAPI<Signup>({
return await authedGetBackendAPI<Signup>({
path: APIPath.SIGNUPS_EDIT,
urlParams: {
id,
@@ -71,16 +74,7 @@ class SignupApi {
static deleteSignup = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true });
} catch (err) {
console.error(err);
throw err;
}
};
static userDeleteSignup = async (id: number, uuid: string): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS_DELETE, urlParams: { id }, queryParams: { uuid } });
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS, urlParams: { id } });
} catch (err) {
console.error(err);
throw err;
@@ -89,7 +83,7 @@ class SignupApi {
static getForm = async (id: number, auth = false): Promise<SignupForm> => {
try {
return await getBackendAPI<SignupForm>({
return await authedGetBackendAPI<SignupForm>({
path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: auth,
});
} catch (err) {
@@ -100,7 +94,7 @@ class SignupApi {
static getForms = async (auth = false): Promise<SignupForm[]> => {
try {
return await getBackendAPI<SignupForm[]>({
return await authedGetBackendAPI<SignupForm[]>({
path: APIPath.SIGNUP_FORMS, authenticated: auth,
});
} catch (err) {
@@ -111,8 +105,8 @@ class SignupApi {
static createForm = async (data: SignupForm): Promise<SignupForm> => {
try {
return await postBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS, authenticated: true,
return await authedPostBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS,
}, data);
} catch (err) {
console.error(err);
@@ -122,8 +116,8 @@ class SignupApi {
static updateForm = async (data: SignupForm): Promise<SignupForm> => {
try {
return await putBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS, urlParams: { id: data.id }, authenticated: true,
return await authedPutBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS, urlParams: { id: data.id },
}, data);
} catch (err) {
console.error(err);
@@ -133,7 +127,7 @@ class SignupApi {
static deleteForm = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: true });
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUP_FORMS, urlParams: { id } });
} catch (err) {
console.error(err);
throw err;
@@ -142,7 +136,7 @@ class SignupApi {
static signupFormSendEmail = async (data: EmailRequest, id: number): Promise<void> => {
try {
await postBackendAPI<EmailRequest, { message: "Email sent" }>({ path: APIPath.SIGNUP_FORMS_EMAIL, urlParams: { id }, authenticated: true }, data);
await authedPostBackendAPI<EmailRequest, { message: "Email sent" }>({ path: APIPath.SIGNUP_FORMS_EMAIL, urlParams: { id } }, data);
} catch (err) {
console.error(err);
throw err;
@@ -151,7 +145,7 @@ class SignupApi {
static getSignups = async (id: number): Promise<Signup[]> => {
try {
return await getBackendAPI<Signup[]>({ path: APIPath.SIGNUP_FORMS_SIGNUPS, urlParams: { id }, authenticated: true });
return await authedGetBackendAPI<Signup[]>({ path: APIPath.SIGNUP_FORMS_SIGNUPS, urlParams: { id } });
} catch (err) {
console.error(err);
throw err;
+1
View File
@@ -49,6 +49,7 @@ const Panel = styled.div<{ $visible?: boolean }>`
interface AccordionProps {
title: string;
children: React.ReactNode;
}
const Accordion: React.FC<AccordionProps> = ({ title, children }) => {
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
const Icon = "/img/add-icon.png";
+2 -1
View File
@@ -3,9 +3,10 @@ import styled from "styled-components";
import colors from "@theme/colors";
interface ButtonProps {
onClick: (event?: React.MouseEvent<HTMLButtonElement>) => void;
onClick: () => void;
buttonStyle: "hero" | "filled" | "filter" | "bordered";
selected?: boolean;
children: React.ReactNode;
}
const StyledButton = styled.button<{ $selected?: boolean }>`
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import colors from "@theme/colors";
import Link from "@components/Link";
+11 -5
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import colors from "@theme/colors";
@@ -18,8 +18,8 @@ const Row = styled.div`
const ImageContainer = styled.div`
position: relative;
height: 125px;
width: 125px;
height: 8rem;
width: 8rem;
flex-shrink: 0;
img {
@@ -35,13 +35,19 @@ const Info = styled.div`
margin-left: -20px;
min-width: 150px;
padding: 2rem;
padding-top: 10px;
color: ${colors.darkBlue};
& > p {
font-size: 1.0rem;
font-size: 1rem;
margin: 0;
}
& > a {
font-weight: 400;
font-size: 0.9rem;
}
& > h3 {
font-size: 1.2rem;
font-weight: 500;
@@ -76,7 +82,7 @@ const ContactCard: React.FC<ContactCardProps> = ({
<h3>{name}</h3>
<p>{role_fi || role_en}</p>
{phone ? <p>{phone}</p> : null}
{email ? <p>{email}</p> : null}
{email ? <a href={`mailto:${email}`}>{email}</a> : null}
</Info>
</Row>
</Card>
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image, { ImageProps } from "next/image";
import Image, { ImageProps } from "next/legacy/image";
import styled, { keyframes, Keyframes } from "styled-components";
interface CrossFadeImagesProps {
+1
View File
@@ -6,6 +6,7 @@ interface DropDownBoxProps {
onMouseEnter: () => void;
onMouseLeave: () => void;
visible: boolean;
children: React.ReactNode;
}
const Box = styled.div`
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import { Link } from "@components/index";
+5 -1
View File
@@ -23,7 +23,11 @@ const Container = styled.div`
}
`;
const Hero: React.FC = ({ children }) => (
type HeroProps = {
children: React.ReactNode;
};
const Hero: React.FC<HeroProps> = ({ children }) => (
<Container>
{children}
</Container>
+1
View File
@@ -35,6 +35,7 @@ type Colors = "darkBlue" | "lightTurquoise";
interface HeroAsideProps {
bgColor: Colors;
children: React.ReactNode;
}
// TODO: Color combos
@@ -6,6 +6,7 @@ import breakpoints from "@theme/breakpoints";
interface HeroPrimarySectionProps {
header: string;
text?: string;
children?: React.ReactNode;
}
const Section = styled.section`
@@ -22,6 +22,7 @@ const Item = styled.div`
interface HeroSecondarySectionItemProps {
note?: string;
children: React.ReactNode;
}
export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> = ({ note, children }) => (
@@ -52,6 +53,7 @@ const Items = styled.div`
interface HeroSecondarySectionProps {
heading: string;
children: React.ReactNode;
}
const HeroSecondarySection: React.FC<HeroSecondarySectionProps> = ({ heading, children }) => (
+5 -1
View File
@@ -6,7 +6,11 @@ const Box = styled.div`
text-align: center;
`;
const InfoBox: React.FC = ({ children }) => (
type InfoBoxProps = {
children?: React.ReactNode
};
const InfoBox: React.FC<InfoBoxProps> = ({ children }) => (
<Box>
{children}
</Box>
+18 -8
View File
@@ -2,6 +2,7 @@ import React from "react";
import NextJSLink, { LinkProps } from "next/link";
interface Props extends Omit<LinkProps, "href" | "as"> {
children?: React.ReactNode;
to: string;
template?: string;
target?: string;
@@ -15,18 +16,27 @@ const Link: React.FC<Props> = ({
}) => {
if (template) {
return (
<NextJSLink href={template} passHref={passHref} as={to} {...props}>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} />
</NextJSLink>
<NextJSLink
href={template}
passHref={passHref}
as={to}
{...props}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
);
}
if (to.startsWith("/") || to.startsWith("#")) {
return (
<NextJSLink href={to} passHref={passHref} {...props}>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} />
</NextJSLink>
<NextJSLink
href={to}
passHref={passHref}
{...props}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
);
}
+1
View File
@@ -6,6 +6,7 @@ import { Link } from "@components/index";
interface NavbarChildLinkProps {
to: string;
children: React.ReactNode;
}
const StyledLink = styled(Link)`
+1
View File
@@ -38,6 +38,7 @@ interface NavbarDropdownLinkProps {
to: string;
text: string;
exploded?: boolean; // if exploded, show items directly underneath without a dropdown menu
children?: React.ReactNode;
}
const NavbarDropdownLink: React.FC<NavbarDropdownLinkProps> = ({
+1
View File
@@ -6,6 +6,7 @@ import Link from "@components/Link";
interface PageLinkProps {
to: string;
desc: string;
children: React.ReactNode;
}
const StyledPageLink = styled.div`
+12
View File
@@ -0,0 +1,12 @@
import styled from "styled-components";
const StyledSelect = styled.select`
padding: 0.25rem;
margin: 0.5rem;
`;
const SelectWrapper = styled.div`
padding: 0.5rem;
`;
export { StyledSelect, SelectWrapper };
+6 -4
View File
@@ -1,5 +1,5 @@
import React, {
createContext, useContext, useReducer,
createContext, useContext, useMemo, useReducer,
} from "react";
import fi from "./locales/fi/common.json";
import en from "./locales/en/common.json";
@@ -67,8 +67,7 @@ const Reducer = (state: Store, action: Lang) => {
};
const LocaleContext = createContext(initialState);
const LocaleStore: React.FC = ({ children }) => {
const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
const changeLanguage = (action: Lang) => {
dispatch(action);
@@ -78,8 +77,11 @@ const LocaleStore: React.FC = ({ children }) => {
// Just ignore if fails to store value in user's browser
}
};
const localeValue = useMemo(() => ({ ...state, changeLanguage }), [state]);
return (
<LocaleContext.Provider value={{ ...state, changeLanguage }}>
<LocaleContext.Provider value={localeValue}>
{children}
</LocaleContext.Provider>
);
+1 -1
View File
@@ -48,7 +48,7 @@
"Se aukeaa":
"Signup opens at",
"Ilmoittauminen sulkeutuu":
"Ilmoittautuminen sulkeutuu":
"Signup closes at",
"Ilmoittauminen on umpeutunut!":
+7 -12
View File
@@ -1,12 +1,12 @@
import React from "react";
import Document, {
Html, Head, Main, NextScript, DocumentContext, DocumentInitialProps,
Html, Head, Main, NextScript, DocumentContext,
} from "next/document";
import { ServerStyleSheet } from "styled-components";
import Favicons from "@components/Favicons";
export default class MyDocument extends Document<{ styleTags: unknown }> {
static getInitialProps = async (ctx: DocumentContext): Promise<DocumentInitialProps> => {
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
@@ -16,20 +16,15 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
styles: [initialProps.styles, sheet.getStyleElement()],
};
} finally {
sheet.seal();
}
};
}
render(): JSX.Element {
const { styleTags } = this.props;
const { styles } = this.props;
return (
<Html lang="fi">
<Head>
@@ -37,7 +32,7 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
<Favicons />
</Head>
<body>
{styleTags}
{styles}
<Main />
<NextScript />
</body>
+62 -3
View File
@@ -1,4 +1,4 @@
import React from "react";
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import useSWR from "swr";
import { formatRelative } from "date-fns";
@@ -10,6 +10,7 @@ import AddLink from "@components/AddLink";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import { fetcher, APIPath, API } from "@api/backend";
import { StyledSelect, SelectWrapper } from "@components/Select";
const URL = "/admin/events";
@@ -37,19 +38,76 @@ const Renderer: React.FC = () => {
const api: API = { path: APIPath.EVENTS, authenticated: true };
const { data: events, error } = useSWR<Event[]>(api, fetcher);
const [sort, setSort] = useState<string>("start_time");
const [order, setOrder] = useState<string>("descending");
const [filter, setFilter] = useState<string>("all");
const eventSort = (a, b) => {
let result = 0;
if (order === "descending") {
if (["start_time", "end_time"].includes(sort)) {
result = new Date(b[sort]).getTime() - new Date(a[sort]).getTime();
} else if (sort === "id") {
result = b[sort] - a[sort];
}
} else if (order === "ascending") {
if (["start_time", "end_time"].includes(sort)) {
result = new Date(a[sort]).getTime() - new Date(b[sort]).getTime();
} else if (sort === "id") {
result = a[sort] - b[sort];
}
}
return result;
};
const dateFilter = (a) => {
let result = true;
if (filter === "upcoming") {
result = new Date(a.end_time).getTime() > Date.now();
} else if (filter === "past") {
result = new Date(a.end_time).getTime() < Date.now();
}
return result;
};
useEffect(() => {
}, [sort, order, filter, events]);
if (error) {
console.error(error);
return (
<div>
Failed loading events
Failed loading events.
</div>
);
}
if (!events?.length) {
return <div>No events.</div>;
}
return (
<div>
<SelectWrapper>
Sort by:
<StyledSelect name="" onChange={(e) => setSort(e.target.value)}>
<option value="start_time">Start time</option>
<option value="end_time">End time</option>
<option value="id">Creation order</option>
</StyledSelect>
Order:
<StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
<option value="descending">Descending</option>
<option value="ascending">Ascending</option>
</StyledSelect>
Filter:
<StyledSelect name="" onChange={(e) => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="upcoming">Upcoming</option>
<option value="past">Past</option>
</StyledSelect>
</SelectWrapper>
<table>
<thead>
<tr>
@@ -59,7 +117,7 @@ const Renderer: React.FC = () => {
</tr>
</thead>
<tbody>
{events.map((event) => (
{events.sort(eventSort).filter(dateFilter).map((event) => (
<tr key={event.id}>
<td><Link to={`${URL}/${event.id}`}>{event.title_fi}</Link></td>
<td>{formatRelative(new Date(event.start_time), new Date())}</td>
@@ -73,6 +131,7 @@ const Renderer: React.FC = () => {
))}
</tbody>
</table>
</div>
);
};
+27 -2
View File
@@ -1,4 +1,4 @@
import React from "react";
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import useSWR from "swr";
import { formatRelative } from "date-fns";
@@ -10,6 +10,7 @@ import AddLink from "@components/AddLink";
import Post from "@models/Feed";
import PostApi from "@api/feedApi";
import { fetcher, APIPath, API } from "@api/backend";
import { SelectWrapper, StyledSelect } from "@components/Select";
const URL = "/admin/feed";
@@ -37,6 +38,21 @@ const Renderer: React.FC = () => {
const api: API = { path: APIPath.FEED, authenticated: true };
const { data: feed, error } = useSWR<Post[]>(api, fetcher);
const [order, setOrder] = useState<string>("descending");
const feedSort = (a, b) => {
let result = 0;
if (order === "descending") {
result = new Date(b.publish_time).getTime() - new Date(a.publish_time).getTime();
} else if (order === "ascending") {
result = new Date(a.publish_time).getTime() - new Date(b.publish_time).getTime();
}
return result;
};
useEffect(() => {
}, [order, feed]);
if (error) {
console.error(error);
return (
@@ -52,6 +68,14 @@ const Renderer: React.FC = () => {
}
return (
<div>
<SelectWrapper>
Order:
<StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
<option value="descending">Descending</option>
<option value="ascending">Ascending</option>
</StyledSelect>
</SelectWrapper>
<table>
<thead>
<tr>
@@ -61,7 +85,7 @@ const Renderer: React.FC = () => {
</tr>
</thead>
<tbody>
{feed.map((post) => (
{feed.sort(feedSort).map((post) => (
<tr key={post.id}>
<td><Link to={`${URL}/${post.id}`}>{post.title_fi}</Link></td>
<td>{post.description_fi}</td>
@@ -75,6 +99,7 @@ const Renderer: React.FC = () => {
))}
</tbody>
</table>
</div>
);
};
+79 -16
View File
@@ -1,5 +1,6 @@
import React, { useEffect, useState } from "react";
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import useSWR from "swr";
import { formatRelative } from "date-fns";
import { toast } from "react-toastify";
import styled from "styled-components";
@@ -8,6 +9,8 @@ import { Button, Link } from "@components/index";
import AddLink from "@components/AddLink";
import { SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi";
import { fetcher, APIPath, API } from "@api/backend";
import { SelectWrapper, StyledSelect } from "@components/Select";
const URL = "/admin/signups";
@@ -31,12 +34,80 @@ const confirmDelete = async (signup: SignupForm) => {
}
};
const renderData = (signupForms: SignupForm[]) => {
if (!signupForms || signupForms.length === 0) {
const Renderer: React.FC = () => {
const api: API = { path: APIPath.SIGNUP_FORMS, authenticated: true };
const { data: signupForms, error } = useSWR<SignupForm[]>(api, fetcher);
const [sort, setSort] = useState<string>("start_time");
const [order, setOrder] = useState<string>("descending");
const [filter, setFilter] = useState<string>("all");
const signupFormSort = (a, b) => {
let result = 0;
if (order === "descending") {
if (["start_time", "end_time"].includes(sort)) {
result = new Date(b[sort]).getTime() - new Date(a[sort]).getTime();
} else if (sort === "id") {
result = b[sort] - a[sort];
}
} else if (order === "ascending") {
if (["start_time", "end_time"].includes(sort)) {
result = new Date(a[sort]).getTime() - new Date(b[sort]).getTime();
} else if (sort === "id") {
result = a[sort] - b[sort];
}
}
return result;
};
const dateFilter = (a) => {
let result = true;
if (filter === "upcoming") {
result = new Date(a.end_time).getTime() > Date.now();
} else if (filter === "past") {
result = new Date(a.end_time).getTime() < Date.now();
}
return result;
};
useEffect(() => {
}, [sort, order, filter, signupForms]);
if (error) {
console.error(error);
return (
<div>
Failed loading events.
</div>
);
}
if (!signupForms?.length) {
return <div>No signup forms.</div>;
}
return (
<div>
<SelectWrapper>
Sort by:
<StyledSelect name="" onChange={(e) => setSort(e.target.value)}>
<option value="start_time">Start time</option>
<option value="end_time">End time</option>
<option value="id">Creation order</option>
</StyledSelect>
Order:
<StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
<option value="descending">Descending</option>
<option value="ascending">Ascending</option>
</StyledSelect>
Filter:
<StyledSelect name="" onChange={(e) => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="upcoming">Upcoming</option>
<option value="past">Past</option>
</StyledSelect>
</SelectWrapper>
<table>
<thead>
<tr>
@@ -48,7 +119,7 @@ const renderData = (signupForms: SignupForm[]) => {
</tr>
</thead>
<tbody>
{signupForms.map((signupForm) => (
{signupForms.sort(signupFormSort).filter(dateFilter).map((signupForm) => (
<tr key={signupForm.id}>
<td><Link to={`${URL}/${signupForm.id}`}>{signupForm.title_fi}</Link></td>
<td>{formatRelative(new Date(signupForm.start_time), new Date())}</td>
@@ -64,24 +135,16 @@ const renderData = (signupForms: SignupForm[]) => {
))}
</tbody>
</table>
</div>
);
};
const AdminSignupPage: NextPage = () => {
const [forms, setForms] = useState<SignupForm[]>(null);
useEffect(() => {
SignupApi.getForms(true)
.then((res) => setForms(res));
}, []);
return (
const AdminSignupPage: NextPage = () => (
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
{renderData(forms)}
<Renderer />
</AdminListCommon>
);
};
);
export default AdminSignupPage;
-11
View File
@@ -67,16 +67,6 @@ const EditSignUpPage: NextPage = () => {
}
};
const onDelete = async () => {
try {
await SignupApi.userDeleteSignup(Number(signupId), uuid);
toast.success("Sign-up deleted successfully 😎");
} catch (error) {
console.error(error);
toast.error("Uh oh! Deleting sign-up failed! 😟");
}
};
return (
<PageWrapper>
<SignUpPageView
@@ -84,7 +74,6 @@ const EditSignUpPage: NextPage = () => {
formData={formData}
onChange={noop}
onSubmit={onSubmit}
onDelete={onDelete}
/>
</PageWrapper>
);
+29 -32
View File
@@ -5,35 +5,35 @@ import colors from "@theme/colors";
import ContactCard from "@components/ContactCard";
import BoardJson from "./board.json";
import HvtmkJson from "./hvtmk.json";
import MtmkJson from "./mtmk.json";
import NtmkJson from "./ntmk.json";
import OptmkJson from "./optmk.json";
import OtmkJson from "./otmk.json";
import EPtmkJson from "./eptmk.json";
import SstmkJson from "./sstmk.json";
import ShntmkJson from "./shntmk.json";
import ShtmkJson from "./shtmk.json";
import TtmkJson from "./ttmk.json";
import UtmkJson from "./utmk.json";
import YtmkJson from "./ytmk.json";
import Others from "./others.json";
// import HvtmkJson from "./hvtmk.json";
// import MtmkJson from "./mtmk.json";
// import NtmkJson from "./ntmk.json";
// import OptmkJson from "./optmk.json";
// import OtmkJson from "./otmk.json";
// import EPtmkJson from "./eptmk.json";
// import SstmkJson from "./sstmk.json";
// import ShntmkJson from "./shntmk.json";
// import ShtmkJson from "./shtmk.json";
// import TtmkJson from "./ttmk.json";
// import UtmkJson from "./utmk.json";
// import YtmkJson from "./ytmk.json";
// import Others from "./others.json";
const orderedCommittees = [
BoardJson,
HvtmkJson,
MtmkJson,
NtmkJson,
OptmkJson,
OtmkJson,
EPtmkJson,
SstmkJson,
ShntmkJson,
ShtmkJson,
TtmkJson,
UtmkJson,
YtmkJson,
Others,
// HvtmkJson,
// MtmkJson,
// NtmkJson,
// OptmkJson,
// OtmkJson,
// EPtmkJson,
// SstmkJson,
// ShntmkJson,
// ShtmkJson,
// TtmkJson,
// UtmkJson,
// YtmkJson,
// Others,
];
const blankProfile = "/img/blank_profile.png";
@@ -91,7 +91,6 @@ const Container = styled.div`
`;
const ContactContainer = styled.div`
margin-top: -13rem;
overflow-x: hidden;
@media (max-width: 950px) {
margin-top: 0;
@@ -110,6 +109,7 @@ const TitleContainer = styled.div`
const CommitteeContainer: React.FC<{
committee: Committee;
children: React.ReactNode;
}> = ({ committee, children }) => (
<Container>
<TitleContainer>
@@ -172,7 +172,6 @@ const ContactsPageView: React.FC = () => (
</aside>
</TextSection>
<ContactContainer>
{orderedCommittees.map((json) => (
<React.Fragment key={json.slug}>
{(json.slug !== "board") && (
@@ -187,17 +186,16 @@ const ContactsPageView: React.FC = () => (
<BlueLink to="mailto:hallitus@sahkoinsinoorikilta.fi">
hallitus@sahkoinsinoorikilta.fi
</BlueLink>
{". Hallituksen yksittäisiin jäseniin saat yhteyden etunimi.sukunimi@sahkoinsinoorikilta.fi osoitteista."}
. Hallituksen yksittäisiin jäseniin saat yhteyden etunimi.sukunimi@sahkoinsinoorikilta.fi osoitteista.
</p>
<p>
{"Hallitukselle voi myös lähettää palautetta täyttämällä "}
<BlueLink to="https://docs.google.com/forms/d/e/1FAIpQLSeD8Hm66uvwr7Xa2WGgOCfI2RS1NrZsmISf2QBKUcJf_stv8g/viewform?usp=sf_link">
palautelomakkeen
</BlueLink>
{", lomakkeen vastauksia käydään läpi hallituksen kokouksissa."}
, lomakkeen vastauksia käydään läpi hallituksen kokouksissa.
</p>
</div>
)}
</CommitteeContainer>
</TextSection>
@@ -207,5 +205,4 @@ const ContactsPageView: React.FC = () => (
</>
);
export default ContactsPageView;
+49 -49
View File
@@ -8,10 +8,10 @@
"name_en": "Chairman of the Board",
"representatives": [
{
"name": "Mikko Suhonen",
"name": "Otto Julkunen",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/mikko.jpg"
"email": "otto.julkunen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -20,10 +20,10 @@
"name_en": "Secretary",
"representatives": [
{
"name": "Emilia Kortelainen",
"name": "Karoliina Talvikangas",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/emilia.jpg"
"email": "karoliina.talvikangas@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -32,10 +32,10 @@
"name_en": "Treasurer",
"representatives": [
{
"name": "Esko Väänänen",
"name": "Ville Lairila",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/esko.jpg"
"email": "ville.lairila@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -44,10 +44,10 @@
"name_en": "",
"representatives": [
{
"name": "Melisa Dönmez",
"name": "Aaron Löfgren",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/melisa.jpg"
"email": "aaron.lofgren@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -56,10 +56,10 @@
"name_en": "",
"representatives": [
{
"name": "Eveliina Ahonen",
"name": "Kasper Skog",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/eveliina.jpg"
"email": "kasper.skog@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -68,10 +68,10 @@
"name_en": "",
"representatives": [
{
"name": "Sakke Kangas",
"name": "Roni Vallius",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/sakke.jpg"
"email": "roni.vallius@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -80,22 +80,10 @@
"name_en": "",
"representatives": [
{
"name": "Eero Ketonen",
"name": "Elina Huttunen",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/eero.jpg"
}
]
},
{
"name_fi": "ISOvastaava",
"name_en": "",
"representatives": [
{
"name": "Salla Lyytikäinen",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/salla.jpg"
"email": "elina.huttunen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -104,10 +92,10 @@
"name_en": "",
"representatives": [
{
"name": "Sofia Öhman",
"name": "Julia Pykälä-aho",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/sofia.jpg"
"email": "julia.pykalaaho@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -116,22 +104,22 @@
"name_en": "",
"representatives": [
{
"name": "Iikka Huttu",
"name": "Juulia Härkönen",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/iikka.jpg"
"email": "juulia.harkonen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
{
"name_fi": "Teknologiamestari",
"name_fi": "Pajamestari",
"name_en": "",
"representatives": [
{
"name": "Ilari Ojakorpi",
"name": "Tommi Sytelä",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/ilari.jpg"
"email": "tommi.sytela@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -140,10 +128,10 @@
"name_en": "",
"representatives": [
{
"name": "Heidi Mäkitalo",
"name": "Pyry Vaara",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/heidi.jpg"
"email": "pyry.vaara@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
@@ -152,10 +140,22 @@
"name_en": "",
"representatives": [
{
"name": "Tommi Oinonen",
"name": "Nette Levijoki",
"phone_number": null,
"email": null,
"image": "https://static.sahkoinsinoorikilta.fi/img/board/tommmi.jpg"
"email": "nette.levijoki@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
},
{
"name_fi": "Excursiomestari",
"name_en": "",
"representatives": [
{
"name": "Visa Kurvi",
"phone_number": null,
"email": "visa.kurvi@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
}
]
}
@@ -6,8 +6,10 @@ import JobAd from "@models/JobAd";
import CorporatePageHero from "./CorporatePageHero";
import JobAdList from "./JobAdList";
import BoardJson from "../ContactsPage/board.json";
const EXCURSION_RULES = "https://static.sahkoinsinoorikilta.fi/saannot/excursiosaannot.pdf";
const CORPORATE_MASTER_MAIL = "tommi.oinonen@sahkoinsinoorikilta.fi";
const CORPORATE_MASTER_INFO = BoardJson.roles.filter((role) => role.name_fi === "Yrityssuhdemestari")[0].representatives[0];
interface CorporatePageViewProps {
jobAds: JobAd[];
@@ -92,15 +94,15 @@ const CorporatePageView: React.FC<CorporatePageViewProps> = ({ jobAds }) => (
<TextSection>
<h3>Olethan yhteydessä!</h3>
<div>
<p>Yllämainituista mahdollisuuksista, sekä muista ideoista kiinnostuneena, voit olla yhteydessä Yrityssuhdemestariimme Tommiin.</p>
<p>Yllämainituista mahdollisuuksista, sekä muista ideoista kiinnostuneena, voit olla yhteydessä Yrityssuhdemestariimme.</p>
<h6>Yrityssuhdemestari</h6>
<p>Tommi Oinonen <br />044 299 3439<br /> <a href={`mailto:${CORPORATE_MASTER_MAIL}`}>{CORPORATE_MASTER_MAIL}</a></p>
<p>{CORPORATE_MASTER_INFO.name} <br /> <a href={`mailto:${CORPORATE_MASTER_INFO.email}`}>{CORPORATE_MASTER_INFO.email}</a></p>
</div>
</TextSection>
<CTASection
bgColor="orange1"
link="https://sosso.fi/wp-content/uploads/2022/01/sossomediakortti22.pdf"
link="https://sosso.fi/wp-content/uploads/2023/01/sossomediakortti23.pdf"
linkText="Killan lehden mediakortin löydät täältä&nbsp;"
>
Mainos Sössöön?
@@ -110,7 +112,7 @@ const CorporatePageView: React.FC<CorporatePageViewProps> = ({ jobAds }) => (
<h3 id="tyopaikat">Työpaikkailmoitukset</h3>
<div>
<JobAdList jobAds={jobAds} />
<p>Voit saada yrityksesi työpaikkailmoituksen listalle lähettämällä sen osoitteeseen <a href={`mailto:${CORPORATE_MASTER_MAIL}`}>{CORPORATE_MASTER_MAIL}</a></p>
<p>Voit saada yrityksesi työpaikkailmoituksen listalle lähettämällä sen osoitteeseen <a href={`mailto:${CORPORATE_MASTER_INFO.email}`}>{CORPORATE_MASTER_INFO.email}</a></p>
</div>
</TextSection>
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize";
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import {
CTASection, TextSection, InfoBox, PageLink, Link,
+9 -5
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import {
Divider,
@@ -24,6 +24,7 @@ const Fingrid = "https://static.sahkoinsinoorikilta.fi/img/corporate_logos/fingr
const Okmetic = "https://static.sahkoinsinoorikilta.fi/img/corporate_logos/okmetic.jpg";
const Nokia = "https://static.sahkoinsinoorikilta.fi/img/corporate_logos/nokia.jpg";
const Granlund = "https://static.sahkoinsinoorikilta.fi/img/corporate_logos/granlund.jpg";
const GE = "https://static.sahkoinsinoorikilta.fi/img/corporate_logos/GE.png";
interface FrontPageViewProps {
events: Event[];
@@ -87,16 +88,16 @@ const FrontPageView: React.FC<FrontPageViewProps> = ({ events, feed }) => (
<h6>Yhteistyössä:</h6>
<SponsorReel>
<div>
<Link to="https://new.abb.com/fi/uralle">
<Link to="https://new.abb.com/fi/">
<Image src={ABB} alt="ABB" layout="responsive" width={200} height={100} objectFit="contain" />
</Link>
<Link to="https://www.caruna.fi/tietoa-meista/tyonhakijalle/tyonantajalupaus">
<Link to="https://caruna.fi/">
<Image src={Caruna} alt="Caruna" layout="responsive" width={200} height={100} objectFit="contain" />
</Link>
<Link to="https://www.nokia.com/fi_fi/">
<Link to="https://www.nokia.com/">
<Image src={Nokia} alt="Nokia" layout="responsive" width={200} height={100} objectFit="contain" />
</Link>
<Link to="https://www.ensto.com/fi">
<Link to="https://www.ensto.com/fi/">
<Image src={Ensto} alt="Ensto" layout="responsive" width={200} height={100} objectFit="contain" />
</Link>
<Link to="https://www.esett.com/">
@@ -111,6 +112,9 @@ const FrontPageView: React.FC<FrontPageViewProps> = ({ events, feed }) => (
<Link to="https://www.granlund.fi/">
<Image src={Granlund} alt="Granlund" layout="responsive" width={200} height={100} objectFit="contain" />
</Link>
<Link to="https://www.gehealthcare.fi/">
<Image src={GE} alt="GE" layout="responsive" width={200} height={100} objectFit="contain" />
</Link>
</div>
<Link to="/yritysyhteistyo">Haluatko kuulla lisää yhteistyöstä kanssamme?</Link>
</SponsorReel>
@@ -22,6 +22,7 @@ const HonoraryPageView: React.FC = () => (
<li>Tapani Jokinen 1996</li>
<li>Kaj G. Lindén 1999</li>
<li>Jorma Kyyrä 2011</li>
<li>Seppo Saastamoinen 2022-</li>
</ul>
<h2>Oltermannit</h2>
<p>Oltermanni on yhdyshenkilö killan ja opettajakunnan välillä. Valtuusto valitsee oltermannin kolmeksi vuodeksi kerrallaan.</p>
@@ -82,6 +83,7 @@ const HonoraryPageView: React.FC = () => (
<li>2019 Ville Kapanen</li>
<li>2020 Anni Parkkila, Aliisa Pietilä</li>
<li>2021 Essi Jukkala</li>
<li>2022 Erna Virtanen, Tuukka Syrjänen</li>
</ul>
<h2>Standaari</h2>
<p>Standaari voidaan hallituksen päätöksellä lahjoittaa killan toimintaan myönteisesti vaikuttaneille tahoille. Standaarit on numeroitu lahjoittamisjärjestyksessä.</p>
@@ -195,6 +197,14 @@ const HonoraryPageView: React.FC = () => (
<li>2021 Tuukka Syrjänen</li>
<li>2021 Timi Tiira</li>
</ul>
<ul>
<li>2022 Elias Hirvonen</li>
<li>2022 Emmaleena Ahonen</li>
<li>2022 Jonna Tammikivi</li>
<li>2022 Leo Kivikunnas</li>
<li>2022 Sini Huhtinen</li>
<li>2022 Ukko Kasvi</li>
</ul>
<h2>Hopeiset ansiomerkit</h2>
<p>Killan hallitus voi myöntää hopeitosen ansiomerkin killan jäsenelle tai perustellusta syystä myös muulle henkilölle tunnustuksena erityisestä kiinnostuksesta kiltaa kohtaan sekä ansioituneesta toiminnasta killan hyväksi.</p>
<ul>
@@ -544,6 +554,22 @@ const HonoraryPageView: React.FC = () => (
<li>2021 Sofia Öhman</li>
<li>2021 Suvi Karanta</li>
</ul>
<ul>
<li>2022 Aaro Niskanen</li>
<li>2022 Aaro Rasilainen</li>
<li>2022 Aino Suomi</li>
<li>2022 Eino Tyrvänen</li>
<li>2022 Henry Gustafsson</li>
<li>2022 Johannes Ora</li>
<li>2022 Niilo Ojala</li>
<li>2022 Oliver Hiekkamies</li>
<li>2022 Oskari Ponkala</li>
<li>2022 Otto Julkunen</li>
<li>2022 Pyry Vaara</li>
<li>2022 Toni Lyttinen</li>
<li>2022 Tuomas Pajunpää</li>
<li>2022 Ville-Pekka Laakkonen</li>
</ul>
</div>
</TextSection>
</>
+2 -13
View File
@@ -6,7 +6,7 @@ import {
import { SignupForm } from "@models/Signup";
import Checkboxes from "@components/Widgets/Checkbox/Checkboxes";
import RadioButtonWidget from "@components/Widgets/RadioButton/RadioButtonWidget";
import { TextSection, ChangeLanguageButton, Button } from "@components/index";
import { TextSection, ChangeLanguageButton } from "@components/index";
import colors from "@theme/colors";
import FormWrapper from "@views/common/FormWrapper";
import Loader from "@components/Loader";
@@ -23,7 +23,6 @@ interface SignUpPageViewProps {
formData: any;
onChange: (e: IChangeEvent<unknown>, es?: ErrorSchema) => unknown;
onSubmit: (e: ISubmitEvent<unknown>) => unknown;
onDelete: () => void;
}
const StyledSection = styled(TextSection)`
@@ -60,7 +59,6 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
formData,
onChange,
onSubmit,
onDelete,
}) => {
const { i18n, t } = useTranslation();
const startDate = new Date(signUpForm?.start_time);
@@ -114,7 +112,7 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
const questions = signUpForm.questions.map((q) => signupFormQuestionToQuestion(q, i18n.language));
form = (
<>
<p>{`${t("Ilmoittauminen sulkeutuu")} ${endDateStr}`}.</p>
<p>{`${t("Ilmoittautuminen sulkeutuu")} ${endDateStr}`}.</p>
<FormWrapper
schema={buildFormSchema(questions, formTitle) as unknown}
uiSchema={buildUISchema(questions)}
@@ -129,14 +127,6 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
signups = renderList();
}
const deleteButton = (
<Button
buttonStyle="filled"
onClick={onDelete}
>Delete
</Button>
);
return (
<>
<LngButton />
@@ -147,7 +137,6 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
<div>
{form}
{deleteButton}
</div>
{signups}
</StyledSection>
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import {
CTASection, TextSection, PageLink, Link,
} from "@components/index";
+5 -1
View File
@@ -21,7 +21,11 @@ const Main = styled.div`
}
`;
const AdminListCommon: React.FC = ({ children }) => (
type AdminListCommonProps = {
children: React.ReactNode;
};
const AdminListCommon: React.FC<AdminListCommonProps> = ({ children }) => (
<AdminPageWrapper requiresAuthentication>
<Main>
{children}
+1
View File
@@ -58,6 +58,7 @@ const useShouldRedirect = (enabled = true) => {
type PageProps = {
requiresAuthentication: boolean;
children: React.ReactNode;
};
const AdminPageWrapper: React.FC<PageProps> = ({ requiresAuthentication, children }) => {
+5 -1
View File
@@ -2,7 +2,11 @@ import React from "react";
import Header from "@components/Header";
import Footer from "@components/Footer/Footer";
const PageWrapper: React.FC = ({ children }) => (
type PageWrapperProps = {
children: React.ReactNode;
};
const PageWrapper: React.FC<PageWrapperProps> = ({ children }) => (
<>
<Header />
{children}
+1 -1
View File
@@ -59,7 +59,7 @@
"./src/**/*",
"./types/**/*",
"./tests/testcafe/**/*",
"next-sitemap.js",
"next-sitemap.config.js",
"next.config.js",
"jest.config.js",
".eslintrc.js",