Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e5b511148a | |||
| 4146af7207 | |||
| c243e76324 | |||
| 659d0e63a0 | |||
| 2c6c1d1e67 | |||
| eeb2f949c6 | |||
| 894e630664 | |||
| 56c13dbf64 | |||
| 9c0e1a0e61 | |||
| 3b2d0596c9 | |||
| 2395321825 | |||
| 05b045c2fc | |||
| faf12816bb | |||
| e7ef69d75f | |||
| 03e6131fe8 | |||
| 87f803ca3e | |||
| dd3eded4a1 | |||
| efacbe9c40 | |||
| c7a1502a26 | |||
| 59a4f3567e | |||
| 0ad59bfba6 | |||
| 6aa0b3fe19 | |||
| 88d5e57858 | |||
| 07efb4caed | |||
| ce29f5a311 | |||
| e1d4a300c5 | |||
| 90f33048d7 | |||
| c55c7699c7 | |||
| 2e37072703 | |||
| aa90d97007 |
@@ -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
@@ -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));
|
||||||
|
|||||||
Generated
+1739
-768
File diff suppressed because it is too large
Load Diff
+20
-14
@@ -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
@@ -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,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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;
|
||||||
|
|||||||
@@ -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,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";
|
||||||
|
|
||||||
|
|||||||
@@ -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,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";
|
||||||
|
|||||||
@@ -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,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 {
|
||||||
|
|||||||
@@ -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,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";
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 }) => (
|
||||||
|
|||||||
@@ -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
@@ -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}
|
||||||
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)`
|
||||||
|
|||||||
@@ -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> = ({
|
||||||
|
|||||||
@@ -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`
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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ä ›"
|
linkText="Killan lehden mediakortin löydät täältä ›"
|
||||||
>
|
>
|
||||||
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,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,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,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.
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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,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";
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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 }) => {
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user