Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a4a3da7b44 | |||
| 1287a232a9 |
@@ -5,25 +5,14 @@ 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
|
||||
|
||||
|
||||
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
|
||||
```
|
||||
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`
|
||||
|
||||
## Getting Started
|
||||
|
||||
|
||||
+1
-3
@@ -16,6 +16,7 @@ const sentryWebpackPluginOptions = {
|
||||
};
|
||||
|
||||
module.exports = withBundleAnalyzer(withSentryConfig({
|
||||
target: "server",
|
||||
images: {
|
||||
domains: [
|
||||
"api.sahkoinsinoorikilta.fi",
|
||||
@@ -23,7 +24,4 @@ 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));
|
||||
|
||||
Generated
+767
-1738
File diff suppressed because it is too large
Load Diff
+14
-20
@@ -37,9 +37,9 @@
|
||||
"@types/jest": "^27.4.1",
|
||||
"@types/js-cookie": "^3.0.1",
|
||||
"@types/node": "^16.11.36",
|
||||
"@types/react": "^18.0.15",
|
||||
"@types/react-csv": "^1.1.3",
|
||||
"@types/react-dom": "^18.0.6",
|
||||
"@types/react": "^17.0.19",
|
||||
"@types/react-csv": "^1.1.2",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@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": "^13.1.6",
|
||||
"eslint-config-next": "^12.1.4",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"husky": "^7.0.4",
|
||||
"jest": "^27.5.1",
|
||||
"next-sitemap": "^3.1.11",
|
||||
"next-sitemap": "^2.5.19",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"postcss-jsx": "^0.36.4",
|
||||
"postcss-syntax": "^0.36.2",
|
||||
@@ -64,37 +64,31 @@
|
||||
"typescript": "^4.6.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@next/bundle-analyzer": "^12.2.3",
|
||||
"@rjsf/core": "^4.2.0",
|
||||
"@sentry/nextjs": "^7.34.0",
|
||||
"@next/bundle-analyzer": "^12.1.4",
|
||||
"@rjsf/core": "^4.1.1",
|
||||
"@sentry/nextjs": "^6.19.6",
|
||||
"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": "^13.1.6",
|
||||
"next": "^12.1.4",
|
||||
"normalize.css": "^8.0.1",
|
||||
"react": "^18.2.0",
|
||||
"react": "^17.0.2",
|
||||
"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": "^18.2.0",
|
||||
"react-is": "^18.2.0",
|
||||
"react-markdown": "^8.0.3",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-is": "^17.0.2",
|
||||
"react-markdown": "^8.0.2",
|
||||
"react-mde": "^11.5.0",
|
||||
"react-toastify": "^9.0.7",
|
||||
"react-toastify": "^8.2.0",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-40
@@ -1,10 +1,7 @@
|
||||
import {
|
||||
deleteTokenCookies, getAccessTokenCookie, getRefreshTokenCookie, setAccessTokenCookie, setRefreshTokenCookie,
|
||||
} from "@utils/auth";
|
||||
import {
|
||||
APIPath, postBackendAPI, getBackendAPI, putBackendAPI, deleteBackendAPI,
|
||||
API,
|
||||
} from "./backend";
|
||||
import { APIPath, postBackendAPI } from "./backend";
|
||||
|
||||
export type AuthTokenRequest = {
|
||||
username: string;
|
||||
@@ -74,39 +71,3 @@ 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,
|
||||
});
|
||||
};
|
||||
|
||||
+9
-12
@@ -1,11 +1,8 @@
|
||||
/* eslint-disable no-console */
|
||||
import Event from "@models/Event";
|
||||
import {
|
||||
APIPath,
|
||||
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
|
||||
} from "./backend";
|
||||
import {
|
||||
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
|
||||
} from "./auth";
|
||||
|
||||
interface Options {
|
||||
limit?: number;
|
||||
@@ -17,7 +14,7 @@ interface Options {
|
||||
class EventApi {
|
||||
static getEvent = async (id: number, auth = false): Promise<Event> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<Event>({
|
||||
return await getBackendAPI<Event>({
|
||||
path: APIPath.EVENTS, urlParams: { id }, authenticated: auth,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -27,10 +24,10 @@ class EventApi {
|
||||
};
|
||||
|
||||
static getEvents = async ({
|
||||
since, limit, offset, auth = false,
|
||||
since, limit, offset, auth,
|
||||
}: Options = {}): Promise<Event[]> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<Event[]>({
|
||||
return await getBackendAPI<Event[]>({
|
||||
path: APIPath.EVENTS,
|
||||
queryParams: {
|
||||
since,
|
||||
@@ -47,8 +44,8 @@ class EventApi {
|
||||
|
||||
static createEvent = async (data: Event): Promise<Event> => {
|
||||
try {
|
||||
return await authedPostBackendAPI<Event, Event>({
|
||||
path: APIPath.EVENTS,
|
||||
return await postBackendAPI<Event, Event>({
|
||||
path: APIPath.EVENTS, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -58,8 +55,8 @@ class EventApi {
|
||||
|
||||
static updateEvent = async (data: Event): Promise<Event> => {
|
||||
try {
|
||||
return await authedPutBackendAPI<Event, Event>({
|
||||
path: APIPath.EVENTS, urlParams: { id: data.id },
|
||||
return await putBackendAPI<Event, Event>({
|
||||
path: APIPath.EVENTS, urlParams: { id: data.id }, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -69,7 +66,7 @@ class EventApi {
|
||||
|
||||
static deleteEvent = async (id: number): Promise<void> => {
|
||||
try {
|
||||
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id } });
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
|
||||
+9
-12
@@ -1,11 +1,8 @@
|
||||
/* eslint-disable no-console */
|
||||
import Post from "@models/Feed";
|
||||
import {
|
||||
APIPath,
|
||||
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
|
||||
} from "./backend";
|
||||
import {
|
||||
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
|
||||
} from "./auth";
|
||||
|
||||
interface Options {
|
||||
limit?: number;
|
||||
@@ -14,9 +11,9 @@ interface Options {
|
||||
}
|
||||
|
||||
class FeedApi {
|
||||
static getPost = async (id: number, auth = false): Promise<Post> => {
|
||||
static getPost = async (id: number, auth?: boolean): Promise<Post> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<Post>({
|
||||
return await getBackendAPI<Post>({
|
||||
path: APIPath.FEED, urlParams: { id }, authenticated: auth,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -25,9 +22,9 @@ class FeedApi {
|
||||
}
|
||||
};
|
||||
|
||||
static getFeed = async ({ limit, offset, auth = false }: Options = {}): Promise<Post[]> => {
|
||||
static getFeed = async ({ limit, offset, auth }: Options = {}): Promise<Post[]> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<Post[]>({
|
||||
return await getBackendAPI<Post[]>({
|
||||
path: APIPath.FEED,
|
||||
queryParams: {
|
||||
limit,
|
||||
@@ -43,7 +40,7 @@ class FeedApi {
|
||||
|
||||
static createPost = async (data: Post): Promise<Post> => {
|
||||
try {
|
||||
return await authedPostBackendAPI<Post, Post>({ path: APIPath.FEED }, data);
|
||||
return await postBackendAPI<Post, Post>({ path: APIPath.FEED, authenticated: true }, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
@@ -52,8 +49,8 @@ class FeedApi {
|
||||
|
||||
static updatePost = async (data: Post): Promise<Post> => {
|
||||
try {
|
||||
return await authedPutBackendAPI<Post, Post>({
|
||||
path: APIPath.FEED, urlParams: { id: data.id },
|
||||
return await putBackendAPI<Post, Post>({
|
||||
path: APIPath.FEED, urlParams: { id: data.id }, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -63,7 +60,7 @@ class FeedApi {
|
||||
|
||||
static deletePost = async (id: number): Promise<void> => {
|
||||
try {
|
||||
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id } });
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
|
||||
+9
-12
@@ -1,11 +1,8 @@
|
||||
/* eslint-disable no-console */
|
||||
import JobAd from "@models/JobAd";
|
||||
import {
|
||||
APIPath,
|
||||
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
|
||||
} from "./backend";
|
||||
import {
|
||||
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
|
||||
} from "./auth";
|
||||
|
||||
interface Options {
|
||||
since?: Date;
|
||||
@@ -17,7 +14,7 @@ interface Options {
|
||||
class JobAdApi {
|
||||
static getJobAd = async (id: number, auth = false): Promise<JobAd> => {
|
||||
try {
|
||||
return await authedGetBackendAPI({
|
||||
return await getBackendAPI({
|
||||
path: APIPath.JOBADS, urlParams: { id }, authenticated: auth,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -27,10 +24,10 @@ class JobAdApi {
|
||||
};
|
||||
|
||||
static getJobAds = async ({
|
||||
since, limit, offset, auth = false,
|
||||
since, limit, offset, auth,
|
||||
}: Options = {}): Promise<JobAd[]> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<JobAd[]>({
|
||||
return await getBackendAPI<JobAd[]>({
|
||||
path: APIPath.JOBADS,
|
||||
queryParams: {
|
||||
since,
|
||||
@@ -47,8 +44,8 @@ class JobAdApi {
|
||||
|
||||
static createJobAd = async (data: JobAd): Promise<JobAd> => {
|
||||
try {
|
||||
return await authedPostBackendAPI<JobAd, JobAd>({
|
||||
path: APIPath.JOBADS,
|
||||
return await postBackendAPI<JobAd, JobAd>({
|
||||
path: APIPath.JOBADS, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -58,8 +55,8 @@ class JobAdApi {
|
||||
|
||||
static updateJobAd = async (data: JobAd): Promise<JobAd> => {
|
||||
try {
|
||||
return await authedPutBackendAPI<JobAd, JobAd>({
|
||||
path: APIPath.JOBADS, urlParams: { id: data.id },
|
||||
return await putBackendAPI<JobAd, JobAd>({
|
||||
path: APIPath.JOBADS, urlParams: { id: data.id }, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -69,7 +66,7 @@ class JobAdApi {
|
||||
|
||||
static deleteJobAd = async (id: number): Promise<void> => {
|
||||
try {
|
||||
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.JOBADS, urlParams: { id } });
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.JOBADS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
|
||||
+15
-18
@@ -1,11 +1,8 @@
|
||||
/* eslint-disable no-console */
|
||||
import { Signup, SignupForm } from "@models/Signup";
|
||||
import {
|
||||
APIPath, postBackendAPI,
|
||||
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
|
||||
} from "./backend";
|
||||
import {
|
||||
authedGetBackendAPI, authedPostBackendAPI, authedDeleteBackendAPI, authedPutBackendAPI,
|
||||
} from "./auth";
|
||||
|
||||
export type EmailRequest = {
|
||||
mode: "all" | "actual" | "reserve";
|
||||
@@ -16,8 +13,8 @@ export type EmailRequest = {
|
||||
class SignupApi {
|
||||
static getSignup = async (id: number): Promise<Signup> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<Signup>({
|
||||
path: APIPath.SIGNUPS, urlParams: { id },
|
||||
return await getBackendAPI<Signup>({
|
||||
path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true,
|
||||
});
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -40,7 +37,7 @@ class SignupApi {
|
||||
try {
|
||||
const { id } = data;
|
||||
if (!id) throw new Error("SignupId required!");
|
||||
return await authedPutBackendAPI<Signup, Signup>({
|
||||
return await putBackendAPI<Signup, Signup>({
|
||||
path: APIPath.SIGNUPS_EDIT,
|
||||
urlParams: {
|
||||
id,
|
||||
@@ -57,7 +54,7 @@ class SignupApi {
|
||||
|
||||
static getSignupUUID = async (id: number, uuid: string): Promise<Signup> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<Signup>({
|
||||
return await getBackendAPI<Signup>({
|
||||
path: APIPath.SIGNUPS_EDIT,
|
||||
urlParams: {
|
||||
id,
|
||||
@@ -74,7 +71,7 @@ class SignupApi {
|
||||
|
||||
static deleteSignup = async (id: number): Promise<void> => {
|
||||
try {
|
||||
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS, urlParams: { id } });
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
@@ -83,7 +80,7 @@ class SignupApi {
|
||||
|
||||
static getForm = async (id: number, auth = false): Promise<SignupForm> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<SignupForm>({
|
||||
return await getBackendAPI<SignupForm>({
|
||||
path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: auth,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -94,7 +91,7 @@ class SignupApi {
|
||||
|
||||
static getForms = async (auth = false): Promise<SignupForm[]> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<SignupForm[]>({
|
||||
return await getBackendAPI<SignupForm[]>({
|
||||
path: APIPath.SIGNUP_FORMS, authenticated: auth,
|
||||
});
|
||||
} catch (err) {
|
||||
@@ -105,8 +102,8 @@ class SignupApi {
|
||||
|
||||
static createForm = async (data: SignupForm): Promise<SignupForm> => {
|
||||
try {
|
||||
return await authedPostBackendAPI<SignupForm, SignupForm>({
|
||||
path: APIPath.SIGNUP_FORMS,
|
||||
return await postBackendAPI<SignupForm, SignupForm>({
|
||||
path: APIPath.SIGNUP_FORMS, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -116,8 +113,8 @@ class SignupApi {
|
||||
|
||||
static updateForm = async (data: SignupForm): Promise<SignupForm> => {
|
||||
try {
|
||||
return await authedPutBackendAPI<SignupForm, SignupForm>({
|
||||
path: APIPath.SIGNUP_FORMS, urlParams: { id: data.id },
|
||||
return await putBackendAPI<SignupForm, SignupForm>({
|
||||
path: APIPath.SIGNUP_FORMS, urlParams: { id: data.id }, authenticated: true,
|
||||
}, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
@@ -127,7 +124,7 @@ class SignupApi {
|
||||
|
||||
static deleteForm = async (id: number): Promise<void> => {
|
||||
try {
|
||||
await authedDeleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUP_FORMS, urlParams: { id } });
|
||||
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
@@ -136,7 +133,7 @@ class SignupApi {
|
||||
|
||||
static signupFormSendEmail = async (data: EmailRequest, id: number): Promise<void> => {
|
||||
try {
|
||||
await authedPostBackendAPI<EmailRequest, { message: "Email sent" }>({ path: APIPath.SIGNUP_FORMS_EMAIL, urlParams: { id } }, data);
|
||||
await postBackendAPI<EmailRequest, { message: "Email sent" }>({ path: APIPath.SIGNUP_FORMS_EMAIL, urlParams: { id }, authenticated: true }, data);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
@@ -145,7 +142,7 @@ class SignupApi {
|
||||
|
||||
static getSignups = async (id: number): Promise<Signup[]> => {
|
||||
try {
|
||||
return await authedGetBackendAPI<Signup[]>({ path: APIPath.SIGNUP_FORMS_SIGNUPS, urlParams: { id } });
|
||||
return await getBackendAPI<Signup[]>({ path: APIPath.SIGNUP_FORMS_SIGNUPS, urlParams: { id }, authenticated: true });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
throw err;
|
||||
|
||||
@@ -49,7 +49,6 @@ const Panel = styled.div<{ $visible?: boolean }>`
|
||||
|
||||
interface AccordionProps {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Accordion: React.FC<AccordionProps> = ({ title, children }) => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import Image from "next/image";
|
||||
|
||||
const Icon = "/img/add-icon.png";
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ interface ButtonProps {
|
||||
onClick: () => void;
|
||||
buttonStyle: "hero" | "filled" | "filter" | "bordered";
|
||||
selected?: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const StyledButton = styled.button<{ $selected?: boolean }>`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import Image from "next/image";
|
||||
import styled from "styled-components";
|
||||
import colors from "@theme/colors";
|
||||
import Link from "@components/Link";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import Image from "next/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: 8rem;
|
||||
width: 8rem;
|
||||
height: 125px;
|
||||
width: 125px;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
@@ -35,19 +35,13 @@ const Info = styled.div`
|
||||
margin-left: -20px;
|
||||
min-width: 150px;
|
||||
padding: 2rem;
|
||||
padding-top: 10px;
|
||||
color: ${colors.darkBlue};
|
||||
|
||||
& > p {
|
||||
font-size: 1rem;
|
||||
font-size: 1.0rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
& > a {
|
||||
font-weight: 400;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
& > h3 {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
@@ -82,7 +76,7 @@ const ContactCard: React.FC<ContactCardProps> = ({
|
||||
<h3>{name}</h3>
|
||||
<p>{role_fi || role_en}</p>
|
||||
{phone ? <p>{phone}</p> : null}
|
||||
{email ? <a href={`mailto:${email}`}>{email}</a> : null}
|
||||
{email ? <p>{email}</p> : null}
|
||||
</Info>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image, { ImageProps } from "next/legacy/image";
|
||||
import Image, { ImageProps } from "next/image";
|
||||
import styled, { keyframes, Keyframes } from "styled-components";
|
||||
|
||||
interface CrossFadeImagesProps {
|
||||
|
||||
@@ -6,7 +6,6 @@ interface DropDownBoxProps {
|
||||
onMouseEnter: () => void;
|
||||
onMouseLeave: () => void;
|
||||
visible: boolean;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const Box = styled.div`
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import Image from "next/image";
|
||||
import styled from "styled-components";
|
||||
import { Link } from "@components/index";
|
||||
|
||||
|
||||
@@ -23,11 +23,7 @@ const Container = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
type HeroProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const Hero: React.FC<HeroProps> = ({ children }) => (
|
||||
const Hero: React.FC = ({ children }) => (
|
||||
<Container>
|
||||
{children}
|
||||
</Container>
|
||||
|
||||
@@ -35,7 +35,6 @@ type Colors = "darkBlue" | "lightTurquoise";
|
||||
|
||||
interface HeroAsideProps {
|
||||
bgColor: Colors;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
// TODO: Color combos
|
||||
|
||||
@@ -6,7 +6,6 @@ import breakpoints from "@theme/breakpoints";
|
||||
interface HeroPrimarySectionProps {
|
||||
header: string;
|
||||
text?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
const Section = styled.section`
|
||||
|
||||
@@ -22,7 +22,6 @@ const Item = styled.div`
|
||||
|
||||
interface HeroSecondarySectionItemProps {
|
||||
note?: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> = ({ note, children }) => (
|
||||
@@ -53,7 +52,6 @@ const Items = styled.div`
|
||||
|
||||
interface HeroSecondarySectionProps {
|
||||
heading: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const HeroSecondarySection: React.FC<HeroSecondarySectionProps> = ({ heading, children }) => (
|
||||
|
||||
@@ -6,11 +6,7 @@ const Box = styled.div`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
type InfoBoxProps = {
|
||||
children?: React.ReactNode
|
||||
};
|
||||
|
||||
const InfoBox: React.FC<InfoBoxProps> = ({ children }) => (
|
||||
const InfoBox: React.FC = ({ children }) => (
|
||||
<Box>
|
||||
{children}
|
||||
</Box>
|
||||
|
||||
+8
-18
@@ -2,7 +2,6 @@ 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;
|
||||
@@ -16,27 +15,18 @@ const Link: React.FC<Props> = ({
|
||||
}) => {
|
||||
if (template) {
|
||||
return (
|
||||
<NextJSLink
|
||||
href={template}
|
||||
passHref={passHref}
|
||||
as={to}
|
||||
{...props}
|
||||
onClick={onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
/>
|
||||
<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>
|
||||
);
|
||||
}
|
||||
if (to.startsWith("/") || to.startsWith("#")) {
|
||||
return (
|
||||
<NextJSLink
|
||||
href={to}
|
||||
passHref={passHref}
|
||||
{...props}
|
||||
onClick={onClick}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
/>
|
||||
<NextJSLink href={to} passHref={passHref} {...props}>
|
||||
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
|
||||
<a onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} />
|
||||
</NextJSLink>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import { Link } from "@components/index";
|
||||
|
||||
interface NavbarChildLinkProps {
|
||||
to: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
|
||||
@@ -38,7 +38,6 @@ 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> = ({
|
||||
|
||||
@@ -6,7 +6,6 @@ import Link from "@components/Link";
|
||||
interface PageLinkProps {
|
||||
to: string;
|
||||
desc: string;
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const StyledPageLink = styled.div`
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
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 };
|
||||
+4
-6
@@ -1,5 +1,5 @@
|
||||
import React, {
|
||||
createContext, useContext, useMemo, useReducer,
|
||||
createContext, useContext, useReducer,
|
||||
} from "react";
|
||||
import fi from "./locales/fi/common.json";
|
||||
import en from "./locales/en/common.json";
|
||||
@@ -67,7 +67,8 @@ const Reducer = (state: Store, action: Lang) => {
|
||||
};
|
||||
|
||||
const LocaleContext = createContext(initialState);
|
||||
const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
|
||||
|
||||
const LocaleStore: React.FC = ({ children }) => {
|
||||
const [state, dispatch] = useReducer(Reducer, initialState);
|
||||
const changeLanguage = (action: Lang) => {
|
||||
dispatch(action);
|
||||
@@ -77,11 +78,8 @@ const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({ children }) =>
|
||||
// Just ignore if fails to store value in user's browser
|
||||
}
|
||||
};
|
||||
|
||||
const localeValue = useMemo(() => ({ ...state, changeLanguage }), [state]);
|
||||
|
||||
return (
|
||||
<LocaleContext.Provider value={localeValue}>
|
||||
<LocaleContext.Provider value={{ ...state, changeLanguage }}>
|
||||
{children}
|
||||
</LocaleContext.Provider>
|
||||
);
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
"Se aukeaa":
|
||||
"Signup opens at",
|
||||
|
||||
"Ilmoittautuminen sulkeutuu":
|
||||
"Ilmoittauminen sulkeutuu":
|
||||
"Signup closes at",
|
||||
|
||||
"Ilmoittauminen on umpeutunut!":
|
||||
|
||||
+12
-7
@@ -1,12 +1,12 @@
|
||||
import React from "react";
|
||||
import Document, {
|
||||
Html, Head, Main, NextScript, DocumentContext,
|
||||
Html, Head, Main, NextScript, DocumentContext, DocumentInitialProps,
|
||||
} from "next/document";
|
||||
import { ServerStyleSheet } from "styled-components";
|
||||
import Favicons from "@components/Favicons";
|
||||
|
||||
export default class MyDocument extends Document {
|
||||
static async getInitialProps(ctx: DocumentContext) {
|
||||
export default class MyDocument extends Document<{ styleTags: unknown }> {
|
||||
static getInitialProps = async (ctx: DocumentContext): Promise<DocumentInitialProps> => {
|
||||
const sheet = new ServerStyleSheet();
|
||||
const originalRenderPage = ctx.renderPage;
|
||||
try {
|
||||
@@ -16,15 +16,20 @@ export default class MyDocument extends Document {
|
||||
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 { styles } = this.props;
|
||||
const { styleTags } = this.props;
|
||||
return (
|
||||
<Html lang="fi">
|
||||
<Head>
|
||||
@@ -32,7 +37,7 @@ export default class MyDocument extends Document {
|
||||
<Favicons />
|
||||
</Head>
|
||||
<body>
|
||||
{styles}
|
||||
{styleTags}
|
||||
<Main />
|
||||
<NextScript />
|
||||
</body>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import useSWR from "swr";
|
||||
import { formatRelative } from "date-fns";
|
||||
@@ -10,7 +10,6 @@ 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";
|
||||
|
||||
@@ -38,100 +37,42 @@ const Renderer: React.FC = () => {
|
||||
const api: API = { path: APIPath.EVENTS, authenticated: true };
|
||||
const { data: events, error } = useSWR<Event[]>(api, fetcher);
|
||||
|
||||
const [sort, setSort] = useState<string>("start_time");
|
||||
const [order, setOrder] = useState<string>("descending");
|
||||
const [filter, setFilter] = useState<string>("all");
|
||||
|
||||
const eventSort = (a, b) => {
|
||||
let result = 0;
|
||||
if (order === "descending") {
|
||||
if (["start_time", "end_time"].includes(sort)) {
|
||||
result = new Date(b[sort]).getTime() - new Date(a[sort]).getTime();
|
||||
} else if (sort === "id") {
|
||||
result = b[sort] - a[sort];
|
||||
}
|
||||
} else if (order === "ascending") {
|
||||
if (["start_time", "end_time"].includes(sort)) {
|
||||
result = new Date(a[sort]).getTime() - new Date(b[sort]).getTime();
|
||||
} else if (sort === "id") {
|
||||
result = a[sort] - b[sort];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const dateFilter = (a) => {
|
||||
let result = true;
|
||||
|
||||
if (filter === "upcoming") {
|
||||
result = new Date(a.end_time).getTime() > Date.now();
|
||||
} else if (filter === "past") {
|
||||
result = new Date(a.end_time).getTime() < Date.now();
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
}, [sort, order, filter, events]);
|
||||
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return (
|
||||
<div>
|
||||
Failed loading events.
|
||||
Failed loading events
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!events?.length) {
|
||||
return <div>No events.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SelectWrapper>
|
||||
Sort by:
|
||||
<StyledSelect name="" onChange={(e) => setSort(e.target.value)}>
|
||||
<option value="start_time">Start time</option>
|
||||
<option value="end_time">End time</option>
|
||||
<option value="id">Creation order</option>
|
||||
</StyledSelect>
|
||||
Order:
|
||||
<StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
|
||||
<option value="descending">Descending</option>
|
||||
<option value="ascending">Ascending</option>
|
||||
</StyledSelect>
|
||||
Filter:
|
||||
<StyledSelect name="" onChange={(e) => setFilter(e.target.value)}>
|
||||
<option value="all">All</option>
|
||||
<option value="upcoming">Upcoming</option>
|
||||
<option value="past">Past</option>
|
||||
</StyledSelect>
|
||||
</SelectWrapper>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Start time</th>
|
||||
<th>End time</th>
|
||||
<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>
|
||||
</tr>
|
||||
</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>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React from "react";
|
||||
import { NextPage } from "next";
|
||||
import useSWR from "swr";
|
||||
import { formatRelative } from "date-fns";
|
||||
@@ -10,7 +10,6 @@ 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";
|
||||
|
||||
@@ -38,21 +37,6 @@ 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 (
|
||||
@@ -68,38 +52,29 @@ const Renderer: React.FC = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SelectWrapper>
|
||||
Order:
|
||||
<StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
|
||||
<option value="descending">Descending</option>
|
||||
<option value="ascending">Ascending</option>
|
||||
</StyledSelect>
|
||||
</SelectWrapper>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Description</th>
|
||||
<th>Publish time</th>
|
||||
<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>
|
||||
</tr>
|
||||
</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>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import React, { useEffect, useState } 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";
|
||||
@@ -9,8 +8,6 @@ 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";
|
||||
|
||||
@@ -34,117 +31,57 @@ const confirmDelete = async (signup: SignupForm) => {
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
const renderData = (signupForms: SignupForm[]) => {
|
||||
if (!signupForms || signupForms.length === 0) {
|
||||
return <div>No signup forms.</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SelectWrapper>
|
||||
Sort by:
|
||||
<StyledSelect name="" onChange={(e) => setSort(e.target.value)}>
|
||||
<option value="start_time">Start time</option>
|
||||
<option value="end_time">End time</option>
|
||||
<option value="id">Creation order</option>
|
||||
</StyledSelect>
|
||||
Order:
|
||||
<StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
|
||||
<option value="descending">Descending</option>
|
||||
<option value="ascending">Ascending</option>
|
||||
</StyledSelect>
|
||||
Filter:
|
||||
<StyledSelect name="" onChange={(e) => setFilter(e.target.value)}>
|
||||
<option value="all">All</option>
|
||||
<option value="upcoming">Upcoming</option>
|
||||
<option value="past">Past</option>
|
||||
</StyledSelect>
|
||||
</SelectWrapper>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Title</th>
|
||||
<th>Start time</th>
|
||||
<th>End time</th>
|
||||
<th>Sign-ups</th>
|
||||
<th>Send email</th>
|
||||
<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>
|
||||
</tr>
|
||||
</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>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
const AdminSignupPage: NextPage = () => (
|
||||
<AdminListCommon>
|
||||
<h1>Sign-up forms</h1>
|
||||
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
|
||||
<Renderer />
|
||||
</AdminListCommon>
|
||||
);
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminSignupPage;
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import { NextPage, GetStaticProps } from "next";
|
||||
import Head from "next/head";
|
||||
import useSWR from "swr";
|
||||
import EventsPageView from "@views/EventsPage/EventsPageView";
|
||||
import PageWrapper from "@views/common/PageWrapper";
|
||||
import { fetcher, API, APIPath } from "@api/backend";
|
||||
import Event from "@models/Event";
|
||||
|
||||
const eventApi: API = {
|
||||
path: APIPath.EVENTS,
|
||||
queryParams: {
|
||||
limit: 10,
|
||||
},
|
||||
};
|
||||
|
||||
interface InitialProps {
|
||||
initialEvents: Event[];
|
||||
}
|
||||
|
||||
const EventsPage: NextPage<InitialProps> = ({ initialEvents }) => {
|
||||
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/toiminta/tapahtumat`} />
|
||||
</Head>
|
||||
<PageWrapper>
|
||||
<EventsPageView events={events} />
|
||||
</PageWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
|
||||
const initialEvents = fetcher<Event[]>(eventApi);
|
||||
return {
|
||||
props: {
|
||||
initialEvents: await initialEvents,
|
||||
},
|
||||
revalidate: 10,
|
||||
};
|
||||
};
|
||||
|
||||
export default EventsPage;
|
||||
@@ -0,0 +1,46 @@
|
||||
import React from "react";
|
||||
import { NextPage, GetStaticProps } from "next";
|
||||
import Head from "next/head";
|
||||
import useSWR from "swr";
|
||||
import NewsFeedPageView from "@views/NewsFeedPage/NewsFeedPageView";
|
||||
import PageWrapper from "@views/common/PageWrapper";
|
||||
import { fetcher, API, APIPath } from "@api/backend";
|
||||
import Post from "@models/Feed";
|
||||
|
||||
const feedApi: API = {
|
||||
path: APIPath.FEED,
|
||||
queryParams: {
|
||||
limit: 10,
|
||||
},
|
||||
};
|
||||
|
||||
interface InitialProps {
|
||||
initialPosts: Post[];
|
||||
}
|
||||
|
||||
const NewsFeedPage: NextPage<InitialProps> = ({ initialPosts }) => {
|
||||
const { data: posts } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialPosts });
|
||||
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/toiminta/uutiset`} />
|
||||
</Head>
|
||||
<PageWrapper>
|
||||
<NewsFeedPageView posts={posts} />
|
||||
</PageWrapper>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
|
||||
const initialPosts = fetcher<Post[]>(feedApi);
|
||||
return {
|
||||
props: {
|
||||
initialPosts: await initialPosts,
|
||||
},
|
||||
revalidate: 10,
|
||||
};
|
||||
};
|
||||
|
||||
export default NewsFeedPage;
|
||||
@@ -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,6 +91,7 @@ const Container = styled.div`
|
||||
`;
|
||||
|
||||
const ContactContainer = styled.div`
|
||||
margin-top: -13rem;
|
||||
overflow-x: hidden;
|
||||
@media (max-width: 950px) {
|
||||
margin-top: 0;
|
||||
@@ -109,7 +110,6 @@ const TitleContainer = styled.div`
|
||||
|
||||
const CommitteeContainer: React.FC<{
|
||||
committee: Committee;
|
||||
children: React.ReactNode;
|
||||
}> = ({ committee, children }) => (
|
||||
<Container>
|
||||
<TitleContainer>
|
||||
@@ -172,6 +172,7 @@ const ContactsPageView: React.FC = () => (
|
||||
</aside>
|
||||
</TextSection>
|
||||
<ContactContainer>
|
||||
|
||||
{orderedCommittees.map((json) => (
|
||||
<React.Fragment key={json.slug}>
|
||||
{(json.slug !== "board") && (
|
||||
@@ -186,16 +187,17 @@ 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>
|
||||
@@ -205,4 +207,5 @@ const ContactsPageView: React.FC = () => (
|
||||
</>
|
||||
);
|
||||
|
||||
|
||||
export default ContactsPageView;
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
"name_en": "Chairman of the Board",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Otto Julkunen",
|
||||
"name": "Mikko Suhonen",
|
||||
"phone_number": null,
|
||||
"email": "otto.julkunen@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/mikko.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -20,10 +20,10 @@
|
||||
"name_en": "Secretary",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Karoliina Talvikangas",
|
||||
"name": "Emilia Kortelainen",
|
||||
"phone_number": null,
|
||||
"email": "karoliina.talvikangas@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/emilia.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -32,10 +32,10 @@
|
||||
"name_en": "Treasurer",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Ville Lairila",
|
||||
"name": "Esko Väänänen",
|
||||
"phone_number": null,
|
||||
"email": "ville.lairila@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/esko.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -44,10 +44,10 @@
|
||||
"name_en": "",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Aaron Löfgren",
|
||||
"name": "Melisa Dönmez",
|
||||
"phone_number": null,
|
||||
"email": "aaron.lofgren@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/melisa.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -56,10 +56,10 @@
|
||||
"name_en": "",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Kasper Skog",
|
||||
"name": "Eveliina Ahonen",
|
||||
"phone_number": null,
|
||||
"email": "kasper.skog@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/eveliina.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -68,10 +68,10 @@
|
||||
"name_en": "",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Roni Vallius",
|
||||
"name": "Sakke Kangas",
|
||||
"phone_number": null,
|
||||
"email": "roni.vallius@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/sakke.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -80,10 +80,22 @@
|
||||
"name_en": "",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Elina Huttunen",
|
||||
"name": "Eero Ketonen",
|
||||
"phone_number": null,
|
||||
"email": "elina.huttunen@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"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"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -92,10 +104,10 @@
|
||||
"name_en": "",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Julia Pykälä-aho",
|
||||
"name": "Sofia Öhman",
|
||||
"phone_number": null,
|
||||
"email": "julia.pykalaaho@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/sofia.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -104,22 +116,22 @@
|
||||
"name_en": "",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Juulia Härkönen",
|
||||
"name": "Iikka Huttu",
|
||||
"phone_number": null,
|
||||
"email": "juulia.harkonen@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/iikka.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name_fi": "Pajamestari",
|
||||
"name_fi": "Teknologiamestari",
|
||||
"name_en": "",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Tommi Sytelä",
|
||||
"name": "Ilari Ojakorpi",
|
||||
"phone_number": null,
|
||||
"email": "tommi.sytela@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/ilari.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -128,10 +140,10 @@
|
||||
"name_en": "",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Pyry Vaara",
|
||||
"name": "Heidi Mäkitalo",
|
||||
"phone_number": null,
|
||||
"email": "pyry.vaara@sahkoinsinoorikilta.fi",
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/heidi.jpg"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -140,22 +152,10 @@
|
||||
"name_en": "",
|
||||
"representatives": [
|
||||
{
|
||||
"name": "Nette Levijoki",
|
||||
"name": "Tommi Oinonen",
|
||||
"phone_number": null,
|
||||
"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"
|
||||
"email": null,
|
||||
"image": "https://static.sahkoinsinoorikilta.fi/img/board/tommmi.jpg"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ 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_INFO = BoardJson.roles.filter((role) => role.name_fi === "Yrityssuhdemestari")[0].representatives[0];
|
||||
const CORPORATE_MASTER_MAIL = "tommi.oinonen@sahkoinsinoorikilta.fi";
|
||||
|
||||
interface CorporatePageViewProps {
|
||||
jobAds: JobAd[];
|
||||
@@ -94,15 +92,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.</p>
|
||||
<p>Yllämainituista mahdollisuuksista, sekä muista ideoista kiinnostuneena, voit olla yhteydessä Yrityssuhdemestariimme Tommiin.</p>
|
||||
<h6>Yrityssuhdemestari</h6>
|
||||
<p>{CORPORATE_MASTER_INFO.name} <br /> <a href={`mailto:${CORPORATE_MASTER_INFO.email}`}>{CORPORATE_MASTER_INFO.email}</a></p>
|
||||
<p>Tommi Oinonen <br />044 299 3439<br /> <a href={`mailto:${CORPORATE_MASTER_MAIL}`}>{CORPORATE_MASTER_MAIL}</a></p>
|
||||
</div>
|
||||
</TextSection>
|
||||
|
||||
<CTASection
|
||||
bgColor="orange1"
|
||||
link="https://sosso.fi/wp-content/uploads/2023/01/sossomediakortti23.pdf"
|
||||
link="https://sosso.fi/wp-content/uploads/2022/01/sossomediakortti22.pdf"
|
||||
linkText="Killan lehden mediakortin löydät täältä ›"
|
||||
>
|
||||
Mainos Sössöön?
|
||||
@@ -112,7 +110,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_INFO.email}`}>{CORPORATE_MASTER_INFO.email}</a></p>
|
||||
<p>Voit saada yrityksesi työpaikkailmoituksen listalle lähettämällä sen osoitteeseen <a href={`mailto:${CORPORATE_MASTER_MAIL}`}>{CORPORATE_MASTER_MAIL}</a></p>
|
||||
</div>
|
||||
|
||||
</TextSection>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import Image from "next/image";
|
||||
import styled from "styled-components";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import rehypeSanitize from "rehype-sanitize";
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardSection,
|
||||
} from "@components/index";
|
||||
|
||||
import Event from "@models/Event";
|
||||
import noop from "@utils/noop";
|
||||
import { getTranslateFunc } from "../../i18n";
|
||||
|
||||
interface EventsPageViewProps {
|
||||
events: Event[];
|
||||
}
|
||||
|
||||
const cardTimeOpts: Intl.DateTimeFormatOptions = {
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
year: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
};
|
||||
|
||||
const EventsPageView: React.FC<EventsPageViewProps> = ({ events }) => {
|
||||
const isFi = true;
|
||||
const t = getTranslateFunc("fi");
|
||||
|
||||
const buttonText = `${t("Lue lisää")}\xa0›`;
|
||||
|
||||
const locale = isFi ? "fi-FI" : "en-GB";
|
||||
|
||||
const filteredEvents = events.map((e) => ({
|
||||
...e,
|
||||
title: isFi ? e.title_fi : e.title_en,
|
||||
description: isFi ? e.description_fi : e.description_en,
|
||||
content: isFi ? e.content_fi : e.content_en,
|
||||
location: isFi ? e.location_fi : e.location_en,
|
||||
startDate: new Date(e.start_time).toLocaleString(locale, cardTimeOpts),
|
||||
endDate: new Date(e.end_time).toLocaleString(locale, cardTimeOpts),
|
||||
}));
|
||||
return (
|
||||
<CardSection id="#events">
|
||||
{filteredEvents.map((event) => (
|
||||
<Card
|
||||
key={event.id}
|
||||
title={event.title}
|
||||
startTime={new Date(event.start_time).toLocaleString(locale, cardTimeOpts)}
|
||||
text={event.description}
|
||||
link={`/events/${event.id}`}
|
||||
image={{
|
||||
src: event.image || event.tags[0].icon,
|
||||
alt: event.title,
|
||||
}}
|
||||
buttonOnClick={noop}
|
||||
buttonText={buttonText}
|
||||
data-e2e="event-card"
|
||||
/>
|
||||
))}
|
||||
</CardSection>
|
||||
);
|
||||
};
|
||||
|
||||
export default EventsPageView;
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import Image from "next/image";
|
||||
import styled from "styled-components";
|
||||
import rehypeRaw from "rehype-raw";
|
||||
import rehypeSanitize from "rehype-sanitize";
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import Image from "next/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.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import Image from "next/image";
|
||||
import styled from "styled-components";
|
||||
import {
|
||||
Divider,
|
||||
@@ -24,7 +24,6 @@ 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[];
|
||||
@@ -88,16 +87,16 @@ const FrontPageView: React.FC<FrontPageViewProps> = ({ events, feed }) => (
|
||||
<h6>Yhteistyössä:</h6>
|
||||
<SponsorReel>
|
||||
<div>
|
||||
<Link to="https://new.abb.com/fi/">
|
||||
<Link to="https://new.abb.com/fi/uralle">
|
||||
<Image src={ABB} alt="ABB" layout="responsive" width={200} height={100} objectFit="contain" />
|
||||
</Link>
|
||||
<Link to="https://caruna.fi/">
|
||||
<Link to="https://www.caruna.fi/tietoa-meista/tyonhakijalle/tyonantajalupaus">
|
||||
<Image src={Caruna} alt="Caruna" layout="responsive" width={200} height={100} objectFit="contain" />
|
||||
</Link>
|
||||
<Link to="https://www.nokia.com/">
|
||||
<Link to="https://www.nokia.com/fi_fi/">
|
||||
<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/">
|
||||
@@ -112,9 +111,6 @@ 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,7 +22,6 @@ 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>
|
||||
@@ -83,7 +82,6 @@ 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>
|
||||
@@ -197,14 +195,6 @@ 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>
|
||||
@@ -554,22 +544,6 @@ 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>
|
||||
</>
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
import React from "react";
|
||||
|
||||
import {
|
||||
Card,
|
||||
CardSection,
|
||||
} from "@components/index";
|
||||
|
||||
import Post from "@models/Feed";
|
||||
import noop from "@utils/noop";
|
||||
import { getTranslateFunc } from "../../i18n";
|
||||
|
||||
interface NewsFeedPageViewProps {
|
||||
posts: Post[];
|
||||
}
|
||||
|
||||
const cardTimeOpts: Intl.DateTimeFormatOptions = {
|
||||
day: "numeric",
|
||||
month: "numeric",
|
||||
year: "numeric",
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
};
|
||||
|
||||
const NewsFeedPageView: React.FC<NewsFeedPageViewProps> = ({ posts }) => {
|
||||
const isFi = true;
|
||||
const t = getTranslateFunc("fi");
|
||||
|
||||
const buttonText = `${t("Lue lisää")}\xa0›`;
|
||||
|
||||
const locale = isFi ? "fi-FI" : "en-GB";
|
||||
|
||||
const filteredFeed = posts.map((post) => ({
|
||||
...post,
|
||||
title: isFi ? post.title_fi : post.title_en,
|
||||
description: isFi ? post.description_fi : post.description_en,
|
||||
content: isFi ? post.content_fi : post.content_en,
|
||||
publish_time: new Date(post.publish_time).toLocaleString(locale, cardTimeOpts),
|
||||
}));
|
||||
return (
|
||||
<CardSection>
|
||||
{filteredFeed.map((post) => (
|
||||
<Card
|
||||
key={post.id}
|
||||
title={post.title}
|
||||
text={post.description}
|
||||
startTime={post.publish_time}
|
||||
link={`/feed/${post.id}`}
|
||||
buttonOnClick={noop}
|
||||
buttonText={buttonText}
|
||||
/>
|
||||
))}
|
||||
</CardSection>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewsFeedPageView;
|
||||
@@ -112,7 +112,7 @@ const SignUpPageView: React.FC<SignUpPageViewProps> = ({
|
||||
const questions = signUpForm.questions.map((q) => signupFormQuestionToQuestion(q, i18n.language));
|
||||
form = (
|
||||
<>
|
||||
<p>{`${t("Ilmoittautuminen sulkeutuu")} ${endDateStr}`}.</p>
|
||||
<p>{`${t("Ilmoittauminen sulkeutuu")} ${endDateStr}`}.</p>
|
||||
<FormWrapper
|
||||
schema={buildFormSchema(questions, formTitle) as unknown}
|
||||
uiSchema={buildUISchema(questions)}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import Image from "next/legacy/image";
|
||||
import Image from "next/image";
|
||||
import {
|
||||
CTASection, TextSection, PageLink, Link,
|
||||
} from "@components/index";
|
||||
|
||||
@@ -21,11 +21,7 @@ const Main = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
type AdminListCommonProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const AdminListCommon: React.FC<AdminListCommonProps> = ({ children }) => (
|
||||
const AdminListCommon: React.FC = ({ children }) => (
|
||||
<AdminPageWrapper requiresAuthentication>
|
||||
<Main>
|
||||
{children}
|
||||
|
||||
@@ -58,7 +58,6 @@ const useShouldRedirect = (enabled = true) => {
|
||||
|
||||
type PageProps = {
|
||||
requiresAuthentication: boolean;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const AdminPageWrapper: React.FC<PageProps> = ({ requiresAuthentication, children }) => {
|
||||
|
||||
@@ -2,11 +2,7 @@ import React from "react";
|
||||
import Header from "@components/Header";
|
||||
import Footer from "@components/Footer/Footer";
|
||||
|
||||
type PageWrapperProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const PageWrapper: React.FC<PageWrapperProps> = ({ children }) => (
|
||||
const PageWrapper: React.FC = ({ children }) => (
|
||||
<>
|
||||
<Header />
|
||||
{children}
|
||||
|
||||
+1
-1
@@ -59,7 +59,7 @@
|
||||
"./src/**/*",
|
||||
"./types/**/*",
|
||||
"./tests/testcafe/**/*",
|
||||
"next-sitemap.config.js",
|
||||
"next-sitemap.js",
|
||||
"next.config.js",
|
||||
"jest.config.js",
|
||||
".eslintrc.js",
|
||||
|
||||
Reference in New Issue
Block a user