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
48 changed files with 2329 additions and 1060 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,
});
};
+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 -15
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,7 +74,7 @@ class SignupApi {
static deleteSignup = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true });
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS, urlParams: { id } });
} catch (err) {
console.error(err);
throw err;
@@ -80,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) {
@@ -91,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) {
@@ -102,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);
@@ -113,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);
@@ -124,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;
@@ -133,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;
@@ -142,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";
+1
View File
@@ -6,6 +6,7 @@ interface ButtonProps {
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>
+83 -24
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,42 +38,100 @@ 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 (
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
</tr>
</thead>
<tbody>
{events.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>
<td>{formatRelative(new Date(event.end_time), new Date())}</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(event)}>
Delete
</StyledButton>
</td>
<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>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{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>
<td>{formatRelative(new Date(event.end_time), new Date())}</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(event)}>
Delete
</StyledButton>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
+48 -23
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,29 +68,38 @@ const Renderer: React.FC = () => {
}
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Publish time</th>
</tr>
</thead>
<tbody>
{feed.map((post) => (
<tr key={post.id}>
<td><Link to={`${URL}/${post.id}`}>{post.title_fi}</Link></td>
<td>{post.description_fi}</td>
<td>{formatRelative(new Date(post.publish_time), new Date())}</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(post)}>
Delete
</StyledButton>
</td>
<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>
<th>Title</th>
<th>Description</th>
<th>Publish time</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{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>
<td>{formatRelative(new Date(post.publish_time), new Date())}</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(post)}>
Delete
</StyledButton>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
+108 -45
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,57 +34,117 @@ 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 (
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
<th>Sign-ups</th>
<th>Send email</th>
</tr>
</thead>
<tbody>
{signupForms.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>
<td>{formatRelative(new Date(signupForm.end_time), new Date())}</td>
<td><Link to={`${URL}/${signupForm.id}/list`}>View</Link></td>
<td><Link to={`${URL}/${signupForm.id}/email`}>Send</Link></td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(signupForm)}>
Delete
</StyledButton>
</td>
<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>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
<th>Sign-ups</th>
<th>Send email</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{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>
<td>{formatRelative(new Date(signupForm.end_time), new Date())}</td>
<td><Link to={`${URL}/${signupForm.id}/list`}>View</Link></td>
<td><Link to={`${URL}/${signupForm.id}/email`}>Send</Link></td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(signupForm)}>
Delete
</StyledButton>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
const AdminSignupPage: NextPage = () => {
const [forms, setForms] = useState<SignupForm[]>(null);
useEffect(() => {
SignupApi.getForms(true)
.then((res) => setForms(res));
}, []);
return (
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
{renderData(forms)}
</AdminListCommon>
);
};
const AdminSignupPage: NextPage = () => (
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
<Renderer />
</AdminListCommon>
);
export default AdminSignupPage;
+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";
+3 -3
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,
@@ -147,10 +147,10 @@ const FreshmenPageView: React.FC = () => (
<h3 id="isot">Isoryhmät</h3>
<div>
<p>
SIK:n fuksit nauttivat hurmaavien ISOhenkilöidensä opastuksesta ja hellästä huolenpidosta omissa fuksiryhmissään.
SIK:n fuksit nauttivat hurmaavien ISOhenkilöidensä opastuksesta ja hellästä huolenpidosta omissa fuksiryhmissään.
</p>
<p>
ISOt ovat hiukan vanhempia opiskelijoita ja kiltalaisia, joiden tehtävänä on olla tukenasi fuksivuoden ajan. Ensimmäisenä päivänä teidät jaetaan noin kymmenen hengen fuksiryhmiin ja jokaiseen ryhmään kuuluu kolmesta viiteen ISOa, joista yksi toimii opintoISOna. ISOilta voit kysyä mitä vain opiskeluun ja opiskelijaelämään liittyen. Vaikka he eivät tietäisi vastausta, he luultavimmin osaavat auttaa sinua vastausten löytämisessä.
ISOt ovat hiukan vanhempia opiskelijoita ja kiltalaisia, joiden tehtävänä on olla tukenasi fuksivuoden ajan. Ensimmäisenä päivänä teidät jaetaan noin kymmenen hengen fuksiryhmiin ja jokaiseen ryhmään kuuluu kolmesta viiteen ISOa, joista yksi toimii opintoISOna. ISOilta voit kysyä mitä vain opiskeluun ja opiskelijaelämään liittyen. Vaikka he eivät tietäisi vastausta, he luultavimmin osaavat auttaa sinua vastausten löytämisessä.
</p>
<p>
Kuten sanottu ISOt tukevat sinua koko fuksivuoden ajan, mutta eniten tulet näkemään heitä Orientaatioviikolla, jolloin he kulkevat fuksiryhmäsi kanssa ympäri Otaniemeä ja avaavat ovia teekkariuden saloihin. He auttavat sinua myös löytämään opintojen aloittamiseen tarvittavat asiat ja tukevat esimerkiksi lukujärjestyksen tekemisessä ja kirjastokortin, sekä matkakortin ja opiskelijakortin hankkimisessa.
+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>
</>
+1 -1
View File
@@ -112,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)}
+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",