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) * **[React](https://facebook.github.io/react/)** (17.x)
* **[Typescript](https://www.typescriptlang.org/)** (4.x) * **[Typescript](https://www.typescriptlang.org/)** (4.x)
* **[Next.js](https://nextjs.org/)** (12.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 ## Installation
1. Clone/download repo
2. Install node v16 ([`nvm`](https://github.com/nvm-sh/nvm)) Install node v16 with **[Node Version Manager](https://github.com/nvm-sh/nvm#installing-and-updating)**.
3. `cp .env.local.example .env.local`
4. `npm install` 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 ## Getting Started
+3 -1
View File
@@ -16,7 +16,6 @@ const sentryWebpackPluginOptions = {
}; };
module.exports = withBundleAnalyzer(withSentryConfig({ module.exports = withBundleAnalyzer(withSentryConfig({
target: "server",
images: { images: {
domains: [ domains: [
"api.sahkoinsinoorikilta.fi", "api.sahkoinsinoorikilta.fi",
@@ -24,4 +23,7 @@ module.exports = withBundleAnalyzer(withSentryConfig({
"api.dev.sahkoinsinoorikilta.fi", "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)); }, 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/jest": "^27.4.1",
"@types/js-cookie": "^3.0.1", "@types/js-cookie": "^3.0.1",
"@types/node": "^16.11.36", "@types/node": "^16.11.36",
"@types/react": "^17.0.19", "@types/react": "^18.0.15",
"@types/react-csv": "^1.1.2", "@types/react-csv": "^1.1.3",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^18.0.6",
"@types/shortid": "^0.0.29", "@types/shortid": "^0.0.29",
"@types/styled-components": "^5.1.25", "@types/styled-components": "^5.1.25",
"@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/eslint-plugin": "^5.18.0",
@@ -48,11 +48,11 @@
"eslint": "^8.13.0", "eslint": "^8.13.0",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0", "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", "eslint-plugin-import": "^2.26.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"jest": "^27.5.1", "jest": "^27.5.1",
"next-sitemap": "^2.5.19", "next-sitemap": "^3.1.11",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss-jsx": "^0.36.4", "postcss-jsx": "^0.36.4",
"postcss-syntax": "^0.36.2", "postcss-syntax": "^0.36.2",
@@ -64,31 +64,37 @@
"typescript": "^4.6.3" "typescript": "^4.6.3"
}, },
"dependencies": { "dependencies": {
"@next/bundle-analyzer": "^12.1.4", "@next/bundle-analyzer": "^12.2.3",
"@rjsf/core": "^4.1.1", "@rjsf/core": "^4.2.0",
"@sentry/nextjs": "^6.19.6", "@sentry/nextjs": "^7.34.0",
"axios": "^0.26.1", "axios": "^0.26.1",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"next": "^12.1.4", "next": "^13.1.6",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"react": "^17.0.2", "react": "^18.2.0",
"react-csv": "^2.2.2", "react-csv": "^2.2.2",
"react-dnd": "15.0.2", "react-dnd": "15.0.2",
"react-dnd-html5-backend": "15.0.2", "react-dnd-html5-backend": "15.0.2",
"react-dnd-touch-backend": "15.0.2", "react-dnd-touch-backend": "15.0.2",
"react-dom": "^17.0.2", "react-dom": "^18.2.0",
"react-is": "^17.0.2", "react-is": "^18.2.0",
"react-markdown": "^8.0.2", "react-markdown": "^8.0.3",
"react-mde": "^11.5.0", "react-mde": "^11.5.0",
"react-toastify": "^8.2.0", "react-toastify": "^9.0.7",
"rehype-raw": "^6.1.1", "rehype-raw": "^6.1.1",
"rehype-sanitize": "^5.0.1", "rehype-sanitize": "^5.0.1",
"sharp": "^0.30.3", "sharp": "^0.30.3",
"shortid": "^2.2.16", "shortid": "^2.2.16",
"styled-components": "^5.3.5", "styled-components": "^5.3.5",
"swr": "^1.2.2" "swr": "^1.2.2"
},
"overrides": {
"react-mde": {
"react": "$react",
"react-dom": "$react-dom"
}
} }
} }
+40 -1
View File
@@ -1,7 +1,10 @@
import { import {
deleteTokenCookies, getAccessTokenCookie, getRefreshTokenCookie, setAccessTokenCookie, setRefreshTokenCookie, deleteTokenCookies, getAccessTokenCookie, getRefreshTokenCookie, setAccessTokenCookie, setRefreshTokenCookie,
} from "@utils/auth"; } from "@utils/auth";
import { APIPath, postBackendAPI } from "./backend"; import {
APIPath, postBackendAPI, getBackendAPI, putBackendAPI, deleteBackendAPI,
API,
} from "./backend";
export type AuthTokenRequest = { export type AuthTokenRequest = {
username: string; username: string;
@@ -71,3 +74,39 @@ export const authenticate = async (): Promise<boolean> => {
return refreshToken(); 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", FEED = "/feed/:id",
JOBADS = "/jobads/:id", JOBADS = "/jobads/:id",
SIGNUPS = "/signup/:id", SIGNUPS = "/signup/:id",
SIGNUPS_DELETE = "/signup/:id/delete",
SIGNUPS_EDIT = "/signup/:id/edit", SIGNUPS_EDIT = "/signup/:id/edit",
SIGNUP_FORMS = "/signupForm/:id", SIGNUP_FORMS = "/signupForm/:id",
SIGNUP_FORMS_EMAIL = "/signupForm/:id/sendemail", SIGNUP_FORMS_EMAIL = "/signupForm/:id/sendemail",
+12 -9
View File
@@ -1,8 +1,11 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import Event from "@models/Event"; import Event from "@models/Event";
import { import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI, APIPath,
} from "./backend"; } from "./backend";
import {
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
} from "./auth";
interface Options { interface Options {
limit?: number; limit?: number;
@@ -14,7 +17,7 @@ interface Options {
class EventApi { class EventApi {
static getEvent = async (id: number, auth = false): Promise<Event> => { static getEvent = async (id: number, auth = false): Promise<Event> => {
try { try {
return await getBackendAPI<Event>({ return await authedGetBackendAPI<Event>({
path: APIPath.EVENTS, urlParams: { id }, authenticated: auth, path: APIPath.EVENTS, urlParams: { id }, authenticated: auth,
}); });
} catch (err) { } catch (err) {
@@ -24,10 +27,10 @@ class EventApi {
}; };
static getEvents = async ({ static getEvents = async ({
since, limit, offset, auth, since, limit, offset, auth = false,
}: Options = {}): Promise<Event[]> => { }: Options = {}): Promise<Event[]> => {
try { try {
return await getBackendAPI<Event[]>({ return await authedGetBackendAPI<Event[]>({
path: APIPath.EVENTS, path: APIPath.EVENTS,
queryParams: { queryParams: {
since, since,
@@ -44,8 +47,8 @@ class EventApi {
static createEvent = async (data: Event): Promise<Event> => { static createEvent = async (data: Event): Promise<Event> => {
try { try {
return await postBackendAPI<Event, Event>({ return await authedPostBackendAPI<Event, Event>({
path: APIPath.EVENTS, authenticated: true, path: APIPath.EVENTS,
}, data); }, data);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -55,8 +58,8 @@ class EventApi {
static updateEvent = async (data: Event): Promise<Event> => { static updateEvent = async (data: Event): Promise<Event> => {
try { try {
return await putBackendAPI<Event, Event>({ return await authedPutBackendAPI<Event, Event>({
path: APIPath.EVENTS, urlParams: { id: data.id }, authenticated: true, path: APIPath.EVENTS, urlParams: { id: data.id },
}, data); }, data);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -66,7 +69,7 @@ class EventApi {
static deleteEvent = async (id: number): Promise<void> => { static deleteEvent = async (id: number): Promise<void> => {
try { try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true }); await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id } });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err; throw err;
+12 -9
View File
@@ -1,8 +1,11 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import Post from "@models/Feed"; import Post from "@models/Feed";
import { import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI, APIPath,
} from "./backend"; } from "./backend";
import {
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
} from "./auth";
interface Options { interface Options {
limit?: number; limit?: number;
@@ -11,9 +14,9 @@ interface Options {
} }
class FeedApi { class FeedApi {
static getPost = async (id: number, auth?: boolean): Promise<Post> => { static getPost = async (id: number, auth = false): Promise<Post> => {
try { try {
return await getBackendAPI<Post>({ return await authedGetBackendAPI<Post>({
path: APIPath.FEED, urlParams: { id }, authenticated: auth, path: APIPath.FEED, urlParams: { id }, authenticated: auth,
}); });
} catch (err) { } 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 { try {
return await getBackendAPI<Post[]>({ return await authedGetBackendAPI<Post[]>({
path: APIPath.FEED, path: APIPath.FEED,
queryParams: { queryParams: {
limit, limit,
@@ -40,7 +43,7 @@ class FeedApi {
static createPost = async (data: Post): Promise<Post> => { static createPost = async (data: Post): Promise<Post> => {
try { try {
return await postBackendAPI<Post, Post>({ path: APIPath.FEED, authenticated: true }, data); return await authedPostBackendAPI<Post, Post>({ path: APIPath.FEED }, data);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err; throw err;
@@ -49,8 +52,8 @@ class FeedApi {
static updatePost = async (data: Post): Promise<Post> => { static updatePost = async (data: Post): Promise<Post> => {
try { try {
return await putBackendAPI<Post, Post>({ return await authedPutBackendAPI<Post, Post>({
path: APIPath.FEED, urlParams: { id: data.id }, authenticated: true, path: APIPath.FEED, urlParams: { id: data.id },
}, data); }, data);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -60,7 +63,7 @@ class FeedApi {
static deletePost = async (id: number): Promise<void> => { static deletePost = async (id: number): Promise<void> => {
try { try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true }); await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id } });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err; throw err;
+12 -9
View File
@@ -1,8 +1,11 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import JobAd from "@models/JobAd"; import JobAd from "@models/JobAd";
import { import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI, APIPath,
} from "./backend"; } from "./backend";
import {
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
} from "./auth";
interface Options { interface Options {
since?: Date; since?: Date;
@@ -14,7 +17,7 @@ interface Options {
class JobAdApi { class JobAdApi {
static getJobAd = async (id: number, auth = false): Promise<JobAd> => { static getJobAd = async (id: number, auth = false): Promise<JobAd> => {
try { try {
return await getBackendAPI({ return await authedGetBackendAPI({
path: APIPath.JOBADS, urlParams: { id }, authenticated: auth, path: APIPath.JOBADS, urlParams: { id }, authenticated: auth,
}); });
} catch (err) { } catch (err) {
@@ -24,10 +27,10 @@ class JobAdApi {
}; };
static getJobAds = async ({ static getJobAds = async ({
since, limit, offset, auth, since, limit, offset, auth = false,
}: Options = {}): Promise<JobAd[]> => { }: Options = {}): Promise<JobAd[]> => {
try { try {
return await getBackendAPI<JobAd[]>({ return await authedGetBackendAPI<JobAd[]>({
path: APIPath.JOBADS, path: APIPath.JOBADS,
queryParams: { queryParams: {
since, since,
@@ -44,8 +47,8 @@ class JobAdApi {
static createJobAd = async (data: JobAd): Promise<JobAd> => { static createJobAd = async (data: JobAd): Promise<JobAd> => {
try { try {
return await postBackendAPI<JobAd, JobAd>({ return await authedPostBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS, authenticated: true, path: APIPath.JOBADS,
}, data); }, data);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -55,8 +58,8 @@ class JobAdApi {
static updateJobAd = async (data: JobAd): Promise<JobAd> => { static updateJobAd = async (data: JobAd): Promise<JobAd> => {
try { try {
return await putBackendAPI<JobAd, JobAd>({ return await authedPutBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS, urlParams: { id: data.id }, authenticated: true, path: APIPath.JOBADS, urlParams: { id: data.id },
}, data); }, data);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -66,7 +69,7 @@ class JobAdApi {
static deleteJobAd = async (id: number): Promise<void> => { static deleteJobAd = async (id: number): Promise<void> => {
try { try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.JOBADS, urlParams: { id }, authenticated: true }); await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.JOBADS, urlParams: { id } });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err; throw err;
+18 -24
View File
@@ -1,8 +1,11 @@
/* eslint-disable no-console */ /* eslint-disable no-console */
import { Signup, SignupForm } from "@models/Signup"; import { Signup, SignupForm } from "@models/Signup";
import { import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI, APIPath, postBackendAPI,
} from "./backend"; } from "./backend";
import {
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
} from "./auth";
export type EmailRequest = { export type EmailRequest = {
mode: "all" | "actual" | "reserve"; mode: "all" | "actual" | "reserve";
@@ -13,8 +16,8 @@ export type EmailRequest = {
class SignupApi { class SignupApi {
static getSignup = async (id: number): Promise<Signup> => { static getSignup = async (id: number): Promise<Signup> => {
try { try {
return await getBackendAPI<Signup>({ return await authedGetBackendAPI<Signup>({
path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true, path: APIPath.SIGNUPS, urlParams: { id },
}); });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -37,7 +40,7 @@ class SignupApi {
try { try {
const { id } = data; const { id } = data;
if (!id) throw new Error("SignupId required!"); if (!id) throw new Error("SignupId required!");
return await putBackendAPI<Signup, Signup>({ return await authedPutBackendAPI<Signup, Signup>({
path: APIPath.SIGNUPS_EDIT, path: APIPath.SIGNUPS_EDIT,
urlParams: { urlParams: {
id, id,
@@ -54,7 +57,7 @@ class SignupApi {
static getSignupUUID = async (id: number, uuid: string): Promise<Signup> => { static getSignupUUID = async (id: number, uuid: string): Promise<Signup> => {
try { try {
return await getBackendAPI<Signup>({ return await authedGetBackendAPI<Signup>({
path: APIPath.SIGNUPS_EDIT, path: APIPath.SIGNUPS_EDIT,
urlParams: { urlParams: {
id, id,
@@ -71,16 +74,7 @@ class SignupApi {
static deleteSignup = async (id: number): Promise<void> => { static deleteSignup = async (id: number): Promise<void> => {
try { 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;
}
};
static userDeleteSignup = async (id: number, uuid: string): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS_DELETE, urlParams: { id }, queryParams: { uuid } });
} catch (err) { } catch (err) {
console.error(err); console.error(err);
throw err; throw err;
@@ -89,7 +83,7 @@ class SignupApi {
static getForm = async (id: number, auth = false): Promise<SignupForm> => { static getForm = async (id: number, auth = false): Promise<SignupForm> => {
try { try {
return await getBackendAPI<SignupForm>({ return await authedGetBackendAPI<SignupForm>({
path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: auth, path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: auth,
}); });
} catch (err) { } catch (err) {
@@ -100,7 +94,7 @@ class SignupApi {
static getForms = async (auth = false): Promise<SignupForm[]> => { static getForms = async (auth = false): Promise<SignupForm[]> => {
try { try {
return await getBackendAPI<SignupForm[]>({ return await authedGetBackendAPI<SignupForm[]>({
path: APIPath.SIGNUP_FORMS, authenticated: auth, path: APIPath.SIGNUP_FORMS, authenticated: auth,
}); });
} catch (err) { } catch (err) {
@@ -111,8 +105,8 @@ class SignupApi {
static createForm = async (data: SignupForm): Promise<SignupForm> => { static createForm = async (data: SignupForm): Promise<SignupForm> => {
try { try {
return await postBackendAPI<SignupForm, SignupForm>({ return await authedPostBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS, authenticated: true, path: APIPath.SIGNUP_FORMS,
}, data); }, data);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -122,8 +116,8 @@ class SignupApi {
static updateForm = async (data: SignupForm): Promise<SignupForm> => { static updateForm = async (data: SignupForm): Promise<SignupForm> => {
try { try {
return await putBackendAPI<SignupForm, SignupForm>({ return await authedPutBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS, urlParams: { id: data.id }, authenticated: true, path: APIPath.SIGNUP_FORMS, urlParams: { id: data.id },
}, data); }, data);
} catch (err) { } catch (err) {
console.error(err); console.error(err);
@@ -133,7 +127,7 @@ class SignupApi {
static deleteForm = async (id: number): Promise<void> => { static deleteForm = async (id: number): Promise<void> => {
try { 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) { } catch (err) {
console.error(err); console.error(err);
throw err; throw err;
@@ -142,7 +136,7 @@ class SignupApi {
static signupFormSendEmail = async (data: EmailRequest, id: number): Promise<void> => { static signupFormSendEmail = async (data: EmailRequest, id: number): Promise<void> => {
try { 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) { } catch (err) {
console.error(err); console.error(err);
throw err; throw err;
@@ -151,7 +145,7 @@ class SignupApi {
static getSignups = async (id: number): Promise<Signup[]> => { static getSignups = async (id: number): Promise<Signup[]> => {
try { 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) { } catch (err) {
console.error(err); console.error(err);
throw err; throw err;
+1
View File
@@ -49,6 +49,7 @@ const Panel = styled.div<{ $visible?: boolean }>`
interface AccordionProps { interface AccordionProps {
title: string; title: string;
children: React.ReactNode;
} }
const Accordion: React.FC<AccordionProps> = ({ title, children }) => { const Accordion: React.FC<AccordionProps> = ({ title, children }) => {
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/legacy/image";
const Icon = "/img/add-icon.png"; const Icon = "/img/add-icon.png";
+2 -1
View File
@@ -3,9 +3,10 @@ import styled from "styled-components";
import colors from "@theme/colors"; import colors from "@theme/colors";
interface ButtonProps { interface ButtonProps {
onClick: (event?: React.MouseEvent<HTMLButtonElement>) => void; onClick: () => void;
buttonStyle: "hero" | "filled" | "filter" | "bordered"; buttonStyle: "hero" | "filled" | "filter" | "bordered";
selected?: boolean; selected?: boolean;
children: React.ReactNode;
} }
const StyledButton = styled.button<{ $selected?: boolean }>` const StyledButton = styled.button<{ $selected?: boolean }>`
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/legacy/image";
import styled from "styled-components"; import styled from "styled-components";
import colors from "@theme/colors"; import colors from "@theme/colors";
import Link from "@components/Link"; import Link from "@components/Link";
+11 -5
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/legacy/image";
import styled from "styled-components"; import styled from "styled-components";
import colors from "@theme/colors"; import colors from "@theme/colors";
@@ -18,8 +18,8 @@ const Row = styled.div`
const ImageContainer = styled.div` const ImageContainer = styled.div`
position: relative; position: relative;
height: 125px; height: 8rem;
width: 125px; width: 8rem;
flex-shrink: 0; flex-shrink: 0;
img { img {
@@ -35,13 +35,19 @@ const Info = styled.div`
margin-left: -20px; margin-left: -20px;
min-width: 150px; min-width: 150px;
padding: 2rem; padding: 2rem;
padding-top: 10px;
color: ${colors.darkBlue}; color: ${colors.darkBlue};
& > p { & > p {
font-size: 1.0rem; font-size: 1rem;
margin: 0; margin: 0;
} }
& > a {
font-weight: 400;
font-size: 0.9rem;
}
& > h3 { & > h3 {
font-size: 1.2rem; font-size: 1.2rem;
font-weight: 500; font-weight: 500;
@@ -76,7 +82,7 @@ const ContactCard: React.FC<ContactCardProps> = ({
<h3>{name}</h3> <h3>{name}</h3>
<p>{role_fi || role_en}</p> <p>{role_fi || role_en}</p>
{phone ? <p>{phone}</p> : null} {phone ? <p>{phone}</p> : null}
{email ? <p>{email}</p> : null} {email ? <a href={`mailto:${email}`}>{email}</a> : null}
</Info> </Info>
</Row> </Row>
</Card> </Card>
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image, { ImageProps } from "next/image"; import Image, { ImageProps } from "next/legacy/image";
import styled, { keyframes, Keyframes } from "styled-components"; import styled, { keyframes, Keyframes } from "styled-components";
interface CrossFadeImagesProps { interface CrossFadeImagesProps {
+1
View File
@@ -6,6 +6,7 @@ interface DropDownBoxProps {
onMouseEnter: () => void; onMouseEnter: () => void;
onMouseLeave: () => void; onMouseLeave: () => void;
visible: boolean; visible: boolean;
children: React.ReactNode;
} }
const Box = styled.div` const Box = styled.div`
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/legacy/image";
import styled from "styled-components"; import styled from "styled-components";
import { Link } from "@components/index"; 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> <Container>
{children} {children}
</Container> </Container>
+1
View File
@@ -35,6 +35,7 @@ type Colors = "darkBlue" | "lightTurquoise";
interface HeroAsideProps { interface HeroAsideProps {
bgColor: Colors; bgColor: Colors;
children: React.ReactNode;
} }
// TODO: Color combos // TODO: Color combos
@@ -6,6 +6,7 @@ import breakpoints from "@theme/breakpoints";
interface HeroPrimarySectionProps { interface HeroPrimarySectionProps {
header: string; header: string;
text?: string; text?: string;
children?: React.ReactNode;
} }
const Section = styled.section` const Section = styled.section`
@@ -22,6 +22,7 @@ const Item = styled.div`
interface HeroSecondarySectionItemProps { interface HeroSecondarySectionItemProps {
note?: string; note?: string;
children: React.ReactNode;
} }
export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> = ({ note, children }) => ( export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> = ({ note, children }) => (
@@ -52,6 +53,7 @@ const Items = styled.div`
interface HeroSecondarySectionProps { interface HeroSecondarySectionProps {
heading: string; heading: string;
children: React.ReactNode;
} }
const HeroSecondarySection: React.FC<HeroSecondarySectionProps> = ({ heading, children }) => ( const HeroSecondarySection: React.FC<HeroSecondarySectionProps> = ({ heading, children }) => (
+5 -1
View File
@@ -6,7 +6,11 @@ const Box = styled.div`
text-align: center; text-align: center;
`; `;
const InfoBox: React.FC = ({ children }) => ( type InfoBoxProps = {
children?: React.ReactNode
};
const InfoBox: React.FC<InfoBoxProps> = ({ children }) => (
<Box> <Box>
{children} {children}
</Box> </Box>
+18 -8
View File
@@ -2,6 +2,7 @@ import React from "react";
import NextJSLink, { LinkProps } from "next/link"; import NextJSLink, { LinkProps } from "next/link";
interface Props extends Omit<LinkProps, "href" | "as"> { interface Props extends Omit<LinkProps, "href" | "as"> {
children?: React.ReactNode;
to: string; to: string;
template?: string; template?: string;
target?: string; target?: string;
@@ -15,18 +16,27 @@ const Link: React.FC<Props> = ({
}) => { }) => {
if (template) { if (template) {
return ( return (
<NextJSLink href={template} passHref={passHref} as={to} {...props}> <NextJSLink
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */} href={template}
<a onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} /> passHref={passHref}
</NextJSLink> as={to}
{...props}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
); );
} }
if (to.startsWith("/") || to.startsWith("#")) { if (to.startsWith("/") || to.startsWith("#")) {
return ( return (
<NextJSLink href={to} passHref={passHref} {...props}> <NextJSLink
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */} href={to}
<a onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} /> passHref={passHref}
</NextJSLink> {...props}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
); );
} }
+1
View File
@@ -6,6 +6,7 @@ import { Link } from "@components/index";
interface NavbarChildLinkProps { interface NavbarChildLinkProps {
to: string; to: string;
children: React.ReactNode;
} }
const StyledLink = styled(Link)` const StyledLink = styled(Link)`
+1
View File
@@ -38,6 +38,7 @@ interface NavbarDropdownLinkProps {
to: string; to: string;
text: string; text: string;
exploded?: boolean; // if exploded, show items directly underneath without a dropdown menu exploded?: boolean; // if exploded, show items directly underneath without a dropdown menu
children?: React.ReactNode;
} }
const NavbarDropdownLink: React.FC<NavbarDropdownLinkProps> = ({ const NavbarDropdownLink: React.FC<NavbarDropdownLinkProps> = ({
+1
View File
@@ -6,6 +6,7 @@ import Link from "@components/Link";
interface PageLinkProps { interface PageLinkProps {
to: string; to: string;
desc: string; desc: string;
children: React.ReactNode;
} }
const StyledPageLink = styled.div` 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, { import React, {
createContext, useContext, useReducer, createContext, useContext, useMemo, useReducer,
} from "react"; } from "react";
import fi from "./locales/fi/common.json"; import fi from "./locales/fi/common.json";
import en from "./locales/en/common.json"; import en from "./locales/en/common.json";
@@ -67,8 +67,7 @@ const Reducer = (state: Store, action: Lang) => {
}; };
const LocaleContext = createContext(initialState); const LocaleContext = createContext(initialState);
const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const LocaleStore: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState); const [state, dispatch] = useReducer(Reducer, initialState);
const changeLanguage = (action: Lang) => { const changeLanguage = (action: Lang) => {
dispatch(action); dispatch(action);
@@ -78,8 +77,11 @@ const LocaleStore: React.FC = ({ children }) => {
// Just ignore if fails to store value in user's browser // Just ignore if fails to store value in user's browser
} }
}; };
const localeValue = useMemo(() => ({ ...state, changeLanguage }), [state]);
return ( return (
<LocaleContext.Provider value={{ ...state, changeLanguage }}> <LocaleContext.Provider value={localeValue}>
{children} {children}
</LocaleContext.Provider> </LocaleContext.Provider>
); );
+1 -1
View File
@@ -48,7 +48,7 @@
"Se aukeaa": "Se aukeaa":
"Signup opens at", "Signup opens at",
"Ilmoittauminen sulkeutuu": "Ilmoittautuminen sulkeutuu":
"Signup closes at", "Signup closes at",
"Ilmoittauminen on umpeutunut!": "Ilmoittauminen on umpeutunut!":
+7 -12
View File
@@ -1,12 +1,12 @@
import React from "react"; import React from "react";
import Document, { import Document, {
Html, Head, Main, NextScript, DocumentContext, DocumentInitialProps, Html, Head, Main, NextScript, DocumentContext,
} from "next/document"; } from "next/document";
import { ServerStyleSheet } from "styled-components"; import { ServerStyleSheet } from "styled-components";
import Favicons from "@components/Favicons"; import Favicons from "@components/Favicons";
export default class MyDocument extends Document<{ styleTags: unknown }> { export default class MyDocument extends Document {
static getInitialProps = async (ctx: DocumentContext): Promise<DocumentInitialProps> => { static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet(); const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage; const originalRenderPage = ctx.renderPage;
try { try {
@@ -16,20 +16,15 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
const initialProps = await Document.getInitialProps(ctx); const initialProps = await Document.getInitialProps(ctx);
return { return {
...initialProps, ...initialProps,
styles: ( styles: [initialProps.styles, sheet.getStyleElement()],
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}; };
} finally { } finally {
sheet.seal(); sheet.seal();
} }
}; }
render(): JSX.Element { render(): JSX.Element {
const { styleTags } = this.props; const { styles } = this.props;
return ( return (
<Html lang="fi"> <Html lang="fi">
<Head> <Head>
@@ -37,7 +32,7 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
<Favicons /> <Favicons />
</Head> </Head>
<body> <body>
{styleTags} {styles}
<Main /> <Main />
<NextScript /> <NextScript />
</body> </body>
+83 -24
View File
@@ -1,4 +1,4 @@
import React from "react"; import React, { useState, useEffect } from "react";
import { NextPage } from "next"; import { NextPage } from "next";
import useSWR from "swr"; import useSWR from "swr";
import { formatRelative } from "date-fns"; import { formatRelative } from "date-fns";
@@ -10,6 +10,7 @@ import AddLink from "@components/AddLink";
import Event from "@models/Event"; import Event from "@models/Event";
import EventApi from "@api/eventApi"; import EventApi from "@api/eventApi";
import { fetcher, APIPath, API } from "@api/backend"; import { fetcher, APIPath, API } from "@api/backend";
import { StyledSelect, SelectWrapper } from "@components/Select";
const URL = "/admin/events"; const URL = "/admin/events";
@@ -37,42 +38,100 @@ const Renderer: React.FC = () => {
const api: API = { path: APIPath.EVENTS, authenticated: true }; const api: API = { path: APIPath.EVENTS, authenticated: true };
const { data: events, error } = useSWR<Event[]>(api, fetcher); 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) { if (error) {
console.error(error); console.error(error);
return ( return (
<div> <div>
Failed loading events Failed loading events.
</div> </div>
); );
} }
if (!events?.length) { if (!events?.length) {
return <div>No events.</div>; return <div>No events.</div>;
} }
return ( return (
<table> <div>
<thead> <SelectWrapper>
<tr> Sort by:
<th>Title</th> <StyledSelect name="" onChange={(e) => setSort(e.target.value)}>
<th>Start time</th> <option value="start_time">Start time</option>
<th>End time</th> <option value="end_time">End time</option>
</tr> <option value="id">Creation order</option>
</thead> </StyledSelect>
<tbody> Order:
{events.map((event) => ( <StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
<tr key={event.id}> <option value="descending">Descending</option>
<td><Link to={`${URL}/${event.id}`}>{event.title_fi}</Link></td> <option value="ascending">Ascending</option>
<td>{formatRelative(new Date(event.start_time), new Date())}</td> </StyledSelect>
<td>{formatRelative(new Date(event.end_time), new Date())}</td> Filter:
<td> <StyledSelect name="" onChange={(e) => setFilter(e.target.value)}>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(event)}> <option value="all">All</option>
Delete <option value="upcoming">Upcoming</option>
</StyledButton> <option value="past">Past</option>
</td> </StyledSelect>
</SelectWrapper>
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {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 { NextPage } from "next";
import useSWR from "swr"; import useSWR from "swr";
import { formatRelative } from "date-fns"; import { formatRelative } from "date-fns";
@@ -10,6 +10,7 @@ import AddLink from "@components/AddLink";
import Post from "@models/Feed"; import Post from "@models/Feed";
import PostApi from "@api/feedApi"; import PostApi from "@api/feedApi";
import { fetcher, APIPath, API } from "@api/backend"; import { fetcher, APIPath, API } from "@api/backend";
import { SelectWrapper, StyledSelect } from "@components/Select";
const URL = "/admin/feed"; const URL = "/admin/feed";
@@ -37,6 +38,21 @@ const Renderer: React.FC = () => {
const api: API = { path: APIPath.FEED, authenticated: true }; const api: API = { path: APIPath.FEED, authenticated: true };
const { data: feed, error } = useSWR<Post[]>(api, fetcher); 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) { if (error) {
console.error(error); console.error(error);
return ( return (
@@ -52,29 +68,38 @@ const Renderer: React.FC = () => {
} }
return ( return (
<table> <div>
<thead> <SelectWrapper>
<tr> Order:
<th>Title</th> <StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
<th>Description</th> <option value="descending">Descending</option>
<th>Publish time</th> <option value="ascending">Ascending</option>
</tr> </StyledSelect>
</thead> </SelectWrapper>
<tbody> <table>
{feed.map((post) => ( <thead>
<tr key={post.id}> <tr>
<td><Link to={`${URL}/${post.id}`}>{post.title_fi}</Link></td> <th>Title</th>
<td>{post.description_fi}</td> <th>Description</th>
<td>{formatRelative(new Date(post.publish_time), new Date())}</td> <th>Publish time</th>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(post)}>
Delete
</StyledButton>
</td>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {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 { NextPage } from "next";
import useSWR from "swr";
import { formatRelative } from "date-fns"; import { formatRelative } from "date-fns";
import { toast } from "react-toastify"; import { toast } from "react-toastify";
import styled from "styled-components"; import styled from "styled-components";
@@ -8,6 +9,8 @@ import { Button, Link } from "@components/index";
import AddLink from "@components/AddLink"; import AddLink from "@components/AddLink";
import { SignupForm } from "@models/Signup"; import { SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi"; import SignupApi from "@api/signupApi";
import { fetcher, APIPath, API } from "@api/backend";
import { SelectWrapper, StyledSelect } from "@components/Select";
const URL = "/admin/signups"; const URL = "/admin/signups";
@@ -31,57 +34,117 @@ const confirmDelete = async (signup: SignupForm) => {
} }
}; };
const renderData = (signupForms: SignupForm[]) => { const Renderer: React.FC = () => {
if (!signupForms || signupForms.length === 0) { 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>No signup forms.</div>;
} }
return ( return (
<table> <div>
<thead> <SelectWrapper>
<tr> Sort by:
<th>Title</th> <StyledSelect name="" onChange={(e) => setSort(e.target.value)}>
<th>Start time</th> <option value="start_time">Start time</option>
<th>End time</th> <option value="end_time">End time</option>
<th>Sign-ups</th> <option value="id">Creation order</option>
<th>Send email</th> </StyledSelect>
</tr> Order:
</thead> <StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
<tbody> <option value="descending">Descending</option>
{signupForms.map((signupForm) => ( <option value="ascending">Ascending</option>
<tr key={signupForm.id}> </StyledSelect>
<td><Link to={`${URL}/${signupForm.id}`}>{signupForm.title_fi}</Link></td> Filter:
<td>{formatRelative(new Date(signupForm.start_time), new Date())}</td> <StyledSelect name="" onChange={(e) => setFilter(e.target.value)}>
<td>{formatRelative(new Date(signupForm.end_time), new Date())}</td> <option value="all">All</option>
<td><Link to={`${URL}/${signupForm.id}/list`}>View</Link></td> <option value="upcoming">Upcoming</option>
<td><Link to={`${URL}/${signupForm.id}/email`}>Send</Link></td> <option value="past">Past</option>
<td> </StyledSelect>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(signupForm)}> </SelectWrapper>
Delete <table>
</StyledButton> <thead>
</td> <tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
<th>Sign-ups</th>
<th>Send email</th>
</tr> </tr>
))} </thead>
</tbody> <tbody>
</table> {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 AdminSignupPage: NextPage = () => (
const [forms, setForms] = useState<SignupForm[]>(null); <AdminListCommon>
<h1>Sign-up forms</h1>
useEffect(() => { <AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
SignupApi.getForms(true) <Renderer />
.then((res) => setForms(res)); </AdminListCommon>
}, []); );
return (
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
{renderData(forms)}
</AdminListCommon>
);
};
export default AdminSignupPage; 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 ( return (
<PageWrapper> <PageWrapper>
<SignUpPageView <SignUpPageView
@@ -84,7 +74,6 @@ const EditSignUpPage: NextPage = () => {
formData={formData} formData={formData}
onChange={noop} onChange={noop}
onSubmit={onSubmit} onSubmit={onSubmit}
onDelete={onDelete}
/> />
</PageWrapper> </PageWrapper>
); );
+29 -32
View File
@@ -5,35 +5,35 @@ import colors from "@theme/colors";
import ContactCard from "@components/ContactCard"; import ContactCard from "@components/ContactCard";
import BoardJson from "./board.json"; import BoardJson from "./board.json";
import HvtmkJson from "./hvtmk.json"; // import HvtmkJson from "./hvtmk.json";
import MtmkJson from "./mtmk.json"; // import MtmkJson from "./mtmk.json";
import NtmkJson from "./ntmk.json"; // import NtmkJson from "./ntmk.json";
import OptmkJson from "./optmk.json"; // import OptmkJson from "./optmk.json";
import OtmkJson from "./otmk.json"; // import OtmkJson from "./otmk.json";
import EPtmkJson from "./eptmk.json"; // import EPtmkJson from "./eptmk.json";
import SstmkJson from "./sstmk.json"; // import SstmkJson from "./sstmk.json";
import ShntmkJson from "./shntmk.json"; // import ShntmkJson from "./shntmk.json";
import ShtmkJson from "./shtmk.json"; // import ShtmkJson from "./shtmk.json";
import TtmkJson from "./ttmk.json"; // import TtmkJson from "./ttmk.json";
import UtmkJson from "./utmk.json"; // import UtmkJson from "./utmk.json";
import YtmkJson from "./ytmk.json"; // import YtmkJson from "./ytmk.json";
import Others from "./others.json"; // import Others from "./others.json";
const orderedCommittees = [ const orderedCommittees = [
BoardJson, BoardJson,
HvtmkJson, // HvtmkJson,
MtmkJson, // MtmkJson,
NtmkJson, // NtmkJson,
OptmkJson, // OptmkJson,
OtmkJson, // OtmkJson,
EPtmkJson, // EPtmkJson,
SstmkJson, // SstmkJson,
ShntmkJson, // ShntmkJson,
ShtmkJson, // ShtmkJson,
TtmkJson, // TtmkJson,
UtmkJson, // UtmkJson,
YtmkJson, // YtmkJson,
Others, // Others,
]; ];
const blankProfile = "/img/blank_profile.png"; const blankProfile = "/img/blank_profile.png";
@@ -91,7 +91,6 @@ const Container = styled.div`
`; `;
const ContactContainer = styled.div` const ContactContainer = styled.div`
margin-top: -13rem;
overflow-x: hidden; overflow-x: hidden;
@media (max-width: 950px) { @media (max-width: 950px) {
margin-top: 0; margin-top: 0;
@@ -110,6 +109,7 @@ const TitleContainer = styled.div`
const CommitteeContainer: React.FC<{ const CommitteeContainer: React.FC<{
committee: Committee; committee: Committee;
children: React.ReactNode;
}> = ({ committee, children }) => ( }> = ({ committee, children }) => (
<Container> <Container>
<TitleContainer> <TitleContainer>
@@ -172,7 +172,6 @@ const ContactsPageView: React.FC = () => (
</aside> </aside>
</TextSection> </TextSection>
<ContactContainer> <ContactContainer>
{orderedCommittees.map((json) => ( {orderedCommittees.map((json) => (
<React.Fragment key={json.slug}> <React.Fragment key={json.slug}>
{(json.slug !== "board") && ( {(json.slug !== "board") && (
@@ -187,17 +186,16 @@ const ContactsPageView: React.FC = () => (
<BlueLink to="mailto:hallitus@sahkoinsinoorikilta.fi"> <BlueLink to="mailto:hallitus@sahkoinsinoorikilta.fi">
hallitus@sahkoinsinoorikilta.fi hallitus@sahkoinsinoorikilta.fi
</BlueLink> </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>
<p> <p>
{"Hallitukselle voi myös lähettää palautetta täyttämällä "} {"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"> <BlueLink to="https://docs.google.com/forms/d/e/1FAIpQLSeD8Hm66uvwr7Xa2WGgOCfI2RS1NrZsmISf2QBKUcJf_stv8g/viewform?usp=sf_link">
palautelomakkeen palautelomakkeen
</BlueLink> </BlueLink>
{", lomakkeen vastauksia käydään läpi hallituksen kokouksissa."} , lomakkeen vastauksia käydään läpi hallituksen kokouksissa.
</p> </p>
</div> </div>
)} )}
</CommitteeContainer> </CommitteeContainer>
</TextSection> </TextSection>
@@ -207,5 +205,4 @@ const ContactsPageView: React.FC = () => (
</> </>
); );
export default ContactsPageView; export default ContactsPageView;
+49 -49
View File
@@ -8,10 +8,10 @@
"name_en": "Chairman of the Board", "name_en": "Chairman of the Board",
"representatives": [ "representatives": [
{ {
"name": "Mikko Suhonen", "name": "Otto Julkunen",
"phone_number": null, "phone_number": null,
"email": null, "email": "otto.julkunen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/mikko.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
@@ -20,10 +20,10 @@
"name_en": "Secretary", "name_en": "Secretary",
"representatives": [ "representatives": [
{ {
"name": "Emilia Kortelainen", "name": "Karoliina Talvikangas",
"phone_number": null, "phone_number": null,
"email": null, "email": "karoliina.talvikangas@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/emilia.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
@@ -32,10 +32,10 @@
"name_en": "Treasurer", "name_en": "Treasurer",
"representatives": [ "representatives": [
{ {
"name": "Esko Väänänen", "name": "Ville Lairila",
"phone_number": null, "phone_number": null,
"email": null, "email": "ville.lairila@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/esko.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
@@ -44,10 +44,10 @@
"name_en": "", "name_en": "",
"representatives": [ "representatives": [
{ {
"name": "Melisa Dönmez", "name": "Aaron Löfgren",
"phone_number": null, "phone_number": null,
"email": null, "email": "aaron.lofgren@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/melisa.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
@@ -56,10 +56,10 @@
"name_en": "", "name_en": "",
"representatives": [ "representatives": [
{ {
"name": "Eveliina Ahonen", "name": "Kasper Skog",
"phone_number": null, "phone_number": null,
"email": null, "email": "kasper.skog@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/eveliina.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
@@ -68,10 +68,10 @@
"name_en": "", "name_en": "",
"representatives": [ "representatives": [
{ {
"name": "Sakke Kangas", "name": "Roni Vallius",
"phone_number": null, "phone_number": null,
"email": null, "email": "roni.vallius@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/sakke.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
@@ -80,22 +80,10 @@
"name_en": "", "name_en": "",
"representatives": [ "representatives": [
{ {
"name": "Eero Ketonen", "name": "Elina Huttunen",
"phone_number": null, "phone_number": null,
"email": null, "email": "elina.huttunen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/eero.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.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"
} }
] ]
}, },
@@ -104,10 +92,10 @@
"name_en": "", "name_en": "",
"representatives": [ "representatives": [
{ {
"name": "Sofia Öhman", "name": "Julia Pykälä-aho",
"phone_number": null, "phone_number": null,
"email": null, "email": "julia.pykalaaho@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/sofia.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
@@ -116,22 +104,22 @@
"name_en": "", "name_en": "",
"representatives": [ "representatives": [
{ {
"name": "Iikka Huttu", "name": "Juulia Härkönen",
"phone_number": null, "phone_number": null,
"email": null, "email": "juulia.harkonen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/iikka.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
{ {
"name_fi": "Teknologiamestari", "name_fi": "Pajamestari",
"name_en": "", "name_en": "",
"representatives": [ "representatives": [
{ {
"name": "Ilari Ojakorpi", "name": "Tommi Sytelä",
"phone_number": null, "phone_number": null,
"email": null, "email": "tommi.sytela@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/ilari.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
@@ -140,10 +128,10 @@
"name_en": "", "name_en": "",
"representatives": [ "representatives": [
{ {
"name": "Heidi Mäkitalo", "name": "Pyry Vaara",
"phone_number": null, "phone_number": null,
"email": null, "email": "pyry.vaara@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/heidi.jpg" "image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
} }
] ]
}, },
@@ -152,10 +140,22 @@
"name_en": "", "name_en": "",
"representatives": [ "representatives": [
{ {
"name": "Tommi Oinonen", "name": "Nette Levijoki",
"phone_number": null, "phone_number": null,
"email": null, "email": "nette.levijoki@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/tommmi.jpg" "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 CorporatePageHero from "./CorporatePageHero";
import JobAdList from "./JobAdList"; import JobAdList from "./JobAdList";
import BoardJson from "../ContactsPage/board.json";
const EXCURSION_RULES = "https://static.sahkoinsinoorikilta.fi/saannot/excursiosaannot.pdf"; 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 { interface CorporatePageViewProps {
jobAds: JobAd[]; jobAds: JobAd[];
@@ -92,15 +94,15 @@ const CorporatePageView: React.FC<CorporatePageViewProps> = ({ jobAds }) => (
<TextSection> <TextSection>
<h3>Olethan yhteydessä!</h3> <h3>Olethan yhteydessä!</h3>
<div> <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> <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> </div>
</TextSection> </TextSection>
<CTASection <CTASection
bgColor="orange1" 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;" linkText="Killan lehden mediakortin löydät täältä&nbsp;"
> >
Mainos Sössöön? Mainos Sössöön?
@@ -110,7 +112,7 @@ const CorporatePageView: React.FC<CorporatePageViewProps> = ({ jobAds }) => (
<h3 id="tyopaikat">Työpaikkailmoitukset</h3> <h3 id="tyopaikat">Työpaikkailmoitukset</h3>
<div> <div>
<JobAdList jobAds={jobAds} /> <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> </div>
</TextSection> </TextSection>
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/legacy/image";
import styled from "styled-components"; import styled from "styled-components";
import rehypeRaw from "rehype-raw"; import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize"; import rehypeSanitize from "rehype-sanitize";
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/legacy/image";
import styled from "styled-components"; import styled from "styled-components";
import rehypeRaw from "rehype-raw"; import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize"; import rehypeSanitize from "rehype-sanitize";
+3 -3
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/legacy/image";
import styled from "styled-components"; import styled from "styled-components";
import { import {
CTASection, TextSection, InfoBox, PageLink, Link, CTASection, TextSection, InfoBox, PageLink, Link,
@@ -147,10 +147,10 @@ const FreshmenPageView: React.FC = () => (
<h3 id="isot">Isoryhmät</h3> <h3 id="isot">Isoryhmät</h3>
<div> <div>
<p> <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>
<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>
<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. 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 React from "react";
import Image from "next/image"; import Image from "next/legacy/image";
import styled from "styled-components"; import styled from "styled-components";
import { import {
Divider, 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 Okmetic = "https://static.sahkoinsinoorikilta.fi/img/corporate_logos/okmetic.jpg";
const Nokia = "https://static.sahkoinsinoorikilta.fi/img/corporate_logos/nokia.jpg"; const Nokia = "https://static.sahkoinsinoorikilta.fi/img/corporate_logos/nokia.jpg";
const Granlund = "https://static.sahkoinsinoorikilta.fi/img/corporate_logos/granlund.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 { interface FrontPageViewProps {
events: Event[]; events: Event[];
@@ -87,16 +88,16 @@ const FrontPageView: React.FC<FrontPageViewProps> = ({ events, feed }) => (
<h6>Yhteistyössä:</h6> <h6>Yhteistyössä:</h6>
<SponsorReel> <SponsorReel>
<div> <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" /> <Image src={ABB} alt="ABB" layout="responsive" width={200} height={100} objectFit="contain" />
</Link> </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" /> <Image src={Caruna} alt="Caruna" layout="responsive" width={200} height={100} objectFit="contain" />
</Link> </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" /> <Image src={Nokia} alt="Nokia" layout="responsive" width={200} height={100} objectFit="contain" />
</Link> </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" /> <Image src={Ensto} alt="Ensto" layout="responsive" width={200} height={100} objectFit="contain" />
</Link> </Link>
<Link to="https://www.esett.com/"> <Link to="https://www.esett.com/">
@@ -111,6 +112,9 @@ const FrontPageView: React.FC<FrontPageViewProps> = ({ events, feed }) => (
<Link to="https://www.granlund.fi/"> <Link to="https://www.granlund.fi/">
<Image src={Granlund} alt="Granlund" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Granlund} alt="Granlund" layout="responsive" width={200} height={100} objectFit="contain" />
</Link> </Link>
<Link to="https://www.gehealthcare.fi/">
<Image src={GE} alt="GE" layout="responsive" width={200} height={100} objectFit="contain" />
</Link>
</div> </div>
<Link to="/yritysyhteistyo">Haluatko kuulla lisää yhteistyöstä kanssamme?</Link> <Link to="/yritysyhteistyo">Haluatko kuulla lisää yhteistyöstä kanssamme?</Link>
</SponsorReel> </SponsorReel>
@@ -22,6 +22,7 @@ const HonoraryPageView: React.FC = () => (
<li>Tapani Jokinen 1996</li> <li>Tapani Jokinen 1996</li>
<li>Kaj G. Lindén 1999</li> <li>Kaj G. Lindén 1999</li>
<li>Jorma Kyyrä 2011</li> <li>Jorma Kyyrä 2011</li>
<li>Seppo Saastamoinen 2022-</li>
</ul> </ul>
<h2>Oltermannit</h2> <h2>Oltermannit</h2>
<p>Oltermanni on yhdyshenkilö killan ja opettajakunnan välillä. Valtuusto valitsee oltermannin kolmeksi vuodeksi kerrallaan.</p> <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>2019 Ville Kapanen</li>
<li>2020 Anni Parkkila, Aliisa Pietilä</li> <li>2020 Anni Parkkila, Aliisa Pietilä</li>
<li>2021 Essi Jukkala</li> <li>2021 Essi Jukkala</li>
<li>2022 Erna Virtanen, Tuukka Syrjänen</li>
</ul> </ul>
<h2>Standaari</h2> <h2>Standaari</h2>
<p>Standaari voidaan hallituksen päätöksellä lahjoittaa killan toimintaan myönteisesti vaikuttaneille tahoille. Standaarit on numeroitu lahjoittamisjärjestyksessä.</p> <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 Tuukka Syrjänen</li>
<li>2021 Timi Tiira</li> <li>2021 Timi Tiira</li>
</ul> </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> <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> <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> <ul>
@@ -544,6 +554,22 @@ const HonoraryPageView: React.FC = () => (
<li>2021 Sofia Öhman</li> <li>2021 Sofia Öhman</li>
<li>2021 Suvi Karanta</li> <li>2021 Suvi Karanta</li>
</ul> </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> </div>
</TextSection> </TextSection>
</> </>
+2 -13
View File
@@ -6,7 +6,7 @@ import {
import { SignupForm } from "@models/Signup"; import { SignupForm } from "@models/Signup";
import Checkboxes from "@components/Widgets/Checkbox/Checkboxes"; import Checkboxes from "@components/Widgets/Checkbox/Checkboxes";
import RadioButtonWidget from "@components/Widgets/RadioButton/RadioButtonWidget"; 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 colors from "@theme/colors";
import FormWrapper from "@views/common/FormWrapper"; import FormWrapper from "@views/common/FormWrapper";
import Loader from "@components/Loader"; import Loader from "@components/Loader";
@@ -23,7 +23,6 @@ interface SignUpPageViewProps {
formData: any; formData: any;
onChange: (e: IChangeEvent<unknown>, es?: ErrorSchema) => unknown; onChange: (e: IChangeEvent<unknown>, es?: ErrorSchema) => unknown;
onSubmit: (e: ISubmitEvent<unknown>) => unknown; onSubmit: (e: ISubmitEvent<unknown>) => unknown;
onDelete: () => void;
} }
const StyledSection = styled(TextSection)` const StyledSection = styled(TextSection)`
@@ -60,7 +59,6 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
formData, formData,
onChange, onChange,
onSubmit, onSubmit,
onDelete,
}) => { }) => {
const { i18n, t } = useTranslation(); const { i18n, t } = useTranslation();
const startDate = new Date(signUpForm?.start_time); 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)); const questions = signUpForm.questions.map((q) => signupFormQuestionToQuestion(q, i18n.language));
form = ( form = (
<> <>
<p>{`${t("Ilmoittauminen sulkeutuu")} ${endDateStr}`}.</p> <p>{`${t("Ilmoittautuminen sulkeutuu")} ${endDateStr}`}.</p>
<FormWrapper <FormWrapper
schema={buildFormSchema(questions, formTitle) as unknown} schema={buildFormSchema(questions, formTitle) as unknown}
uiSchema={buildUISchema(questions)} uiSchema={buildUISchema(questions)}
@@ -129,14 +127,6 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
signups = renderList(); signups = renderList();
} }
const deleteButton = (
<Button
buttonStyle="filled"
onClick={onDelete}
>Delete
</Button>
);
return ( return (
<> <>
<LngButton /> <LngButton />
@@ -147,7 +137,6 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
<div> <div>
{form} {form}
{deleteButton}
</div> </div>
{signups} {signups}
</StyledSection> </StyledSection>
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/image"; import Image from "next/legacy/image";
import { import {
CTASection, TextSection, PageLink, Link, CTASection, TextSection, PageLink, Link,
} from "@components/index"; } 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> <AdminPageWrapper requiresAuthentication>
<Main> <Main>
{children} {children}
+1
View File
@@ -58,6 +58,7 @@ const useShouldRedirect = (enabled = true) => {
type PageProps = { type PageProps = {
requiresAuthentication: boolean; requiresAuthentication: boolean;
children: React.ReactNode;
}; };
const AdminPageWrapper: React.FC<PageProps> = ({ requiresAuthentication, children }) => { 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 Header from "@components/Header";
import Footer from "@components/Footer/Footer"; import Footer from "@components/Footer/Footer";
const PageWrapper: React.FC = ({ children }) => ( type PageWrapperProps = {
children: React.ReactNode;
};
const PageWrapper: React.FC<PageWrapperProps> = ({ children }) => (
<> <>
<Header /> <Header />
{children} {children}
+1 -1
View File
@@ -59,7 +59,7 @@
"./src/**/*", "./src/**/*",
"./types/**/*", "./types/**/*",
"./tests/testcafe/**/*", "./tests/testcafe/**/*",
"next-sitemap.js", "next-sitemap.config.js",
"next.config.js", "next.config.js",
"jest.config.js", "jest.config.js",
".eslintrc.js", ".eslintrc.js",