Merge branch 'master' into 'production'

Prod deploy

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!45
This commit is contained in:
Toni Lyttinen
2021-03-29 16:47:43 +00:00
152 changed files with 18277 additions and 3177 deletions
+2 -1
View File
@@ -1 +1,2 @@
NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_SITE_URL=https://dev.sahkoinsinoorikilta.fi
+2 -1
View File
@@ -1 +1,2 @@
NEXT_PUBLIC_API_URL=https://api.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_API_URL=https://api.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_SITE_URL=https://sahkoinsinoorikilta.fi
+2 -1
View File
@@ -1 +1,2 @@
NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_SITE_URL=https://dev.sahkoinsinoorikilta.fi
+7
View File
@@ -0,0 +1,7 @@
# don't ever lint node_modules
node_modules
# don't lint build output (make sure it's set to your correct build folder name)
.next
# don't lint nyc coverage output
coverage
next-env.d.ts
+5 -7
View File
@@ -1,6 +1,8 @@
{
"root": true,
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true
},
@@ -12,8 +14,7 @@
"plugin:jsx-a11y/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"airbnb-typescript",
"airbnb/hooks"
"airbnb-typescript"
],
"globals": {
"Atomics": "readonly",
@@ -28,10 +29,7 @@
"sourceType": "module",
"project": "./tsconfig.json"
},
"plugins": [
"react",
"@typescript-eslint"
],
"plugins": [],
"settings": {
"react": {
"version": "detect"
@@ -41,7 +39,7 @@
"@typescript-eslint/naming-convention": "off",
"max-len": [
"warn",
160
240
],
"@typescript-eslint/quotes": [
"error",
+4
View File
@@ -36,3 +36,7 @@ yarn-error.log*
# vercel
.vercel
# SEO
public/robots.txt
public/sitemap.xml
+1
View File
@@ -0,0 +1 @@
_
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint
+1 -1
View File
@@ -1 +1 @@
14
14.15.5
+3 -2
View File
@@ -3,18 +3,19 @@ FROM node:14-alpine as builder
COPY package.json package-lock.json ./
RUN npm install
COPY tsconfig.json next-env.d.ts .babelrc next.config.js ./
COPY tsconfig.json next-env.d.ts .babelrc next.config.js next-sitemap.js ./
COPY src src/
COPY public public/
COPY types types/
ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api
ENV NEXT_PUBLIC_SITE_URL=https://dev.sahkoinsinoorikilta.fi
RUN npm run build
FROM node:14-alpine as server
WORKDIR /www
COPY package.json package-lock.json next.config.js ./
COPY package.json package-lock.json next.config.js next-sitemap.js ./
COPY --from=builder .next .next
COPY --from=builder node_modules node_modules
COPY --from=builder public public
+3 -2
View File
@@ -3,18 +3,19 @@ FROM node:14-alpine as builder
COPY package.json package-lock.json ./
RUN npm install
COPY tsconfig.json next-env.d.ts .babelrc next.config.js ./
COPY tsconfig.json next-env.d.ts .babelrc next.config.js next-sitemap.js ./
COPY src src/
COPY public public/
COPY types types/
ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_PUBLIC_API_URL=https://api.sahkoinsinoorikilta.fi/api
ENV NEXT_PUBLIC_SITE_URL=https://sahkoinsinoorikilta.fi
RUN npm run build
FROM node:14-alpine as server
WORKDIR /www
COPY package.json package-lock.json next.config.js ./
COPY package.json package-lock.json next.config.js next-sitemap.js ./
COPY --from=builder .next .next
COPY --from=builder node_modules node_modules
COPY --from=builder public public
-25
View File
@@ -21,28 +21,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
React Webpack Typescript Starter
MIT License
Copyright (c) 2017 Viktor Persson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-30
View File
@@ -1,30 +0,0 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `pages/index.js`. The page auto-updates as you edit the file.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/import?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
+35 -37
View File
@@ -1,46 +1,41 @@
# Web 2.0 Frontend
Minimal starter kit with hot module replacement (HMR) for rapid development.
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
* **[React](https://facebook.github.io/react/)** (16.x)
* **[Webpack](https://webpack.js.org/)** (4.x)
* **[Typescript](https://www.typescriptlang.org/)** (2.x)
* **[Hot Module Replacement (HMR)](https://webpack.js.org/concepts/hot-module-replacement/)** using [React Hot Loader](https://github.com/gaearon/react-hot-loader) (4.x)
* [Babel](http://babeljs.io/) (6.x)
* [SASS](http://sass-lang.com/)
* [Jest](https://facebook.github.io/jest/) - Testing framework for React applications
* Production build script
* Image loading/minification using [Image Webpack Loader](https://github.com/tcoopman/image-webpack-loader)
* Typescript compiling using [TS-Loader](https://webpack.js.org/guides/typescript/)
* Code quality (linting) for Typescript and SASS/CSS.
* **[React](https://facebook.github.io/react/)** (17.x)
* **[Typescript](https://www.typescriptlang.org/)** (4.x)
* **[Next.js](https://nextjs.org/)** (10.x)
* [Testcafe](https://devexpress.github.io/testcafe/) - E2E Testing framework
## Installation
1. Clone/download repo
2. Install node v14
2. Install node v14 ([`nvm`](https://github.com/nvm-sh/nvm))
3. `npm install`
## Usage
## Getting Started
### Development
`npm start`
Run the dev-server:
* Build app continously (HMR enabled)
* App served @ `http://localhost:3000`
```bash
npm start
```
### Create new component
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
`npm run plop`
* Create a new component interactively using plop templates
You can start editing the website by modifying source files. The page auto-updates as you edit the file.
### Production
`npm run start-prod`
```bash
npm run build
npm run start-prod
```
* Build app once (HMR disabled)
* App served @ `http://localhost:3000`
* App served @ `http://localhost:80`
---
@@ -48,22 +43,25 @@ Minimal starter kit with hot module replacement (HMR) for rapid development.
Command | Description
--- | ---
`npm run start-dev` | Build app continously (HMR enabled) and serve @ `http://localhost:3000`
`npm run start-prod` | Build app once (HMR disabled) and serve @ `http://localhost:3000`
`npm run build` | Build app to `/dist/`
`npm run test` | Run e2e and unit tests
`npm run test:unit` | Run unit tests with Jest
`npm run test:e2e` | Run end-to-end tests with TestCafé
`npm run lint` | Run Typescript and SASS linter
`npm run lint:ts` | Run Typescript linter
`npm run lint:sass` | Run SASS linter
`npm run start` | (alias of `npm run start-dev`)
`npm run dev` | Build app continously (HMR enabled) and serve @ `http://localhost:3000`
`npm run start` | (alias of `npm run dev`)
`npm run build` | Build app to `/.next/`
`npm run start-prod` | Serve built app (HMR disabled) @ `http://localhost:80`
`npm run test` | Run e2e tests verbose with Chrome
`npm run test:e2e` | Run end-to-end tests with Headless Chrome
`npm run lint` | Run Typescript and CSS linter
`npm run lint:es` | Run Typescript linter (ESLint)
`npm run lint:css` | Run Stylelint (CSS linter)
## See also
## Learn More
* [React Webpack Babel Starter](https://github.com/vikpe/react-webpack-babel-starter)
* [Isomorphic Webapp Starter](https://github.com/vikpe/isomorphic-webapp-starter)
To learn more about Next.js, take a look at the following resources:
* [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
* [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Copyright
Aalto-yliopiston Sähköinsinöörikilta ry
Aalto-yliopiston Sähköinsinöörikilta ry
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
siteUrl: process.env.NEXT_PUBLIC_SITE_URL || "https://sahkoinsinoorikilta.fi",
generateRobotsTxt: true,
exclude: ["/events/*", "/feed/*", "/signup/*", "/admin/*"]
}
+15933 -1729
View File
File diff suppressed because it is too large Load Diff
+34 -39
View File
@@ -8,23 +8,22 @@
},
"keywords": [
"react",
"webpack",
"next.js",
"typescript",
"babel",
"sass",
"hmr",
"starter",
"boilerplate"
"styled-components"
],
"author": "Jan Tuomi",
"author": "Aarni Halinen",
"license": "MIT",
"homepage": "https://sik.ayy.fi",
"homepage": "https://sahkoinsinoorikilta.fi",
"scripts": {
"build": "next build",
"postbuild": "next-sitemap",
"export": "next export",
"lint": "npm run lint:es && npm run lint:css",
"lint:es": "eslint \"./src/**/*.{ts,tsx}\"",
"lint:es:fix": "eslint --fix \"./src/**/*.{ts,tsx}\"",
"lint:es": "eslint . --ext .ts,.tsx",
"lint:es:fix": "eslint --fix . --ext .ts,.tsx",
"lint:css": "stylelint \"./src/**/*.{ts,tsx}\"",
"dev": "next dev",
"start": "next dev",
"start-prod": "next start --port ${SERVER_PORT:=80}",
"serve": "next start --port 3000",
@@ -33,59 +32,55 @@
"test:e2e:verbose": "npm-run-all -p -r serve testcafe:verbose",
"testcafe": "testcafe --skip-js-errors -S -s 'e2e-screenshots' --app-init-delay 2000 chrome:headless tests/testcafe",
"testcafe:verbose": "testcafe --skip-js-errors -S -s 'e2e-screenshots' --app-init-delay 2000 chrome tests/testcafe",
"build-analyze": "ANALYZE=true npm run build"
},
"husky": {
"hooks": {
"pre-push": "npm run lint"
}
"build-analyze": "ANALYZE=true npm run build",
"prepare": "husky install"
},
"devDependencies": {
"@types/js-cookie": "2.2.4",
"@types/react": "16.8.18",
"@types/js-cookie": "2.2.6",
"@types/react": "17.0.2",
"@types/react-beautiful-dnd": "13.0.0",
"@types/react-csv": "1.1.1",
"@types/react-dom": "16.8.4",
"@types/react-jsonschema-form": "1.7.3",
"@types/react-dom": "17.0.1",
"@types/react-jsonschema-form": "1.7.4",
"@types/shortid": "0.0.29",
"@types/styled-components": "5.1.1",
"@typescript-eslint/eslint-plugin": "4.15.0",
"@typescript-eslint/parser": "4.15.0",
"babel-plugin-styled-components": "1.10.7",
"eslint": "7.19.0",
"@types/styled-components": "5.1.7",
"@typescript-eslint/eslint-plugin": "4.16.1",
"@typescript-eslint/parser": "4.16.1",
"babel-plugin-styled-components": "1.12.0",
"eslint": "7.21.0",
"eslint-config-airbnb-typescript": "12.3.1",
"eslint-plugin-import": "2.22.1",
"eslint-plugin-jsx-a11y": "6.4.1",
"eslint-plugin-react": "7.22.0",
"eslint-plugin-react-hooks": "4.2.0",
"husky": "1.3.1",
"husky": "5.1.3",
"next-sitemap": "^1.6.9",
"npm-run-all": "4.1.5",
"stylelint": "13.8.0",
"stylelint": "13.11.0",
"stylelint-config-recommended": "3.0.0",
"stylelint-config-styled-components": "0.1.1",
"stylelint-processor-styled-components": "1.10.0",
"testcafe": "1.10.1",
"typescript": "4.1.5",
"typescript-plugin-styled-components": "1.4.4"
"testcafe": "1.12.0",
"typescript": "4.2.2"
},
"dependencies": {
"@next/bundle-analyzer": "10.0.5",
"@next/bundle-analyzer": "10.0.7",
"axios": "0.21.1",
"date-fns": "2.0.0-alpha.27",
"js-cookie": "2.2.0",
"lodash": "4.17.20",
"next": "10.0.7",
"date-fns": "2.18.0",
"fast-deep-equal": "^3.1.3",
"js-cookie": "2.2.1",
"lodash": "4.17.21",
"next": "10.0.8",
"normalize.css": "8.0.1",
"query-string": "6.5.0",
"react": "17.0.1",
"react-beautiful-dnd": "13.0.0",
"react-csv": "2.0.3",
"react-dom": "17.0.1",
"react-jsonschema-form": "1.8.1",
"react-markdown": "5.0.3",
"react-mde": "11.0.0",
"shortid": "2.2.14",
"react-mde": "11.0.6",
"shortid": "2.2.16",
"styled-components": "5.2.1",
"swr": "0.3.11"
"swr": "0.4.2"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 242 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 556 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 914 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 574 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 407 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 865 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 954 KiB

+91
View File
@@ -0,0 +1,91 @@
/* eslint-disable no-console */
import axios from "axios";
import Event from "@models/Event";
import { getAuthHeader } from "@utils/auth";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/events/`;
export interface Options {
onlyNonPast?: boolean;
limit?: number;
auth?: boolean;
}
class EventApi {
static async getEvent(id: number, auth = false): Promise<Event> {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}${id}/`, {
headers,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async getEvents(options: Options = {}): Promise<Event[]> {
const { onlyNonPast, limit, auth } = options;
try {
const params = {
since: onlyNonPast ? (new Date()).toISOString() : undefined,
limit,
};
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}`, {
headers,
params,
});
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
static async createEvent(data: Event): Promise<Event> {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async updateEvent(data: Event): Promise<Event> {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async deleteEvent(id: number) {
try {
const resp = await axios.delete(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
}
export default EventApi;
+67
View File
@@ -0,0 +1,67 @@
/* eslint-disable no-console */
import axios from "axios";
import Post from "@models/Feed";
import { getAuthHeader } from "@utils/auth";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/feed/`;
export interface Options {
auth?: boolean;
}
class FeedApi {
static async getFeed(options: Options = {}): Promise<Post[]> {
const { auth } = options;
const headers = auth ? { Authorization: getAuthHeader() } : null;
try {
const resp = await axios.get(URL, { headers });
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
static async getPost(id: number, options: Options = {}): Promise<Post> {
const { auth } = options;
const headers = auth ? { Authorization: getAuthHeader() } : null;
try {
const resp = await axios.get(`${URL}${id}/`, { headers });
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async createPost(data: Post): Promise<Post> {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async updatePost(data: Post): Promise<Post> {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
}
export default FeedApi;
+77
View File
@@ -0,0 +1,77 @@
/* eslint-disable no-console */
import axios from "axios";
import JobAd from "@models/JobAd";
import { getAuthHeader } from "@utils/auth";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/jobads/`;
export interface Options {
onlyNonPast?: boolean;
limit?: number;
auth?: boolean;
}
class JobAdApi {
static async getJobAds(options: Options = {}): Promise<JobAd[]> {
const { onlyNonPast, limit, auth } = options;
try {
const params = {
since: onlyNonPast ? (new Date()).toISOString() : undefined,
limit,
};
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}`, {
headers,
params,
});
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
static async getJobAd(id: number, auth = false): Promise<JobAd> {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}${id}/`, {
headers,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async createJobAd(data: JobAd): Promise<JobAd> {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async updateJobAd(data: JobAd): Promise<JobAd> {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
}
export default JobAdApi;
+181
View File
@@ -0,0 +1,181 @@
/* eslint-disable no-console */
import axios from "axios";
import { Signup, SignupForm } from "@models/Signup";
import { getAuthHeader } from "@utils/auth";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/signup/`;
export const FORM_URL = `${process.env.NEXT_PUBLIC_API_URL}/signupForm/`;
export interface Options {
onlyNonPast?: boolean;
limit?: number;
auth?: boolean;
}
class SignupApi {
static async getSignup(id: number): Promise<Signup> {
try {
const resp = await axios.get(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async createSignup(data: Signup): Promise<Signup> {
try {
const resp = await axios.post(URL, data);
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async updateSignup(data: Signup, uuid: string): Promise<Signup> {
try {
const { id } = data;
if (!id) throw new Error("SignupId required!");
const resp = await axios.put(`${URL}${id}/edit/`, data, {
params: { uuid },
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async getSignupUUID(id: number, uuid: string): Promise<Signup> {
try {
const resp = await axios.get(`${URL}${id}/edit/`, {
params: {
uuid,
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async deleteSignup(id: number) {
try {
const resp = await axios.delete(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async getForms(auth = false): Promise<SignupForm[]> {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(FORM_URL, {
headers,
});
const { results } = resp.data;
return results;
} catch (err) {
console.error(err);
throw err;
}
}
static async getForm(id: number, auth = false): Promise<SignupForm> {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${FORM_URL}${id}/`, {
headers,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async createForm(data: SignupForm): Promise<SignupForm> {
try {
const resp = await axios.post(FORM_URL, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async updateForm(data: SignupForm): Promise<SignupForm> {
try {
const putUrl = `${FORM_URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async deleteForm(id: number) {
try {
const resp = await axios.delete(`${FORM_URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async signupFormSendEmail(data: any, id: number): Promise<any> {
try {
const resp = await axios.post(`${FORM_URL}${id}/sendemail/`, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async getSignups(id: number): Promise<Signup[]> {
try {
const resp = await axios.get(`${FORM_URL}${id}/signups/`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
}
export default SignupApi;
+25
View File
@@ -0,0 +1,25 @@
/* eslint-disable no-console */
import axios from "axios";
import Tag from "@models/Tag";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/tags/`;
export interface Options {
onlyNonPast?: boolean;
limit?: number;
auth?: boolean;
}
class TagApi {
static async getTags(): Promise<Tag[]> {
try {
const resp = await axios.get(URL);
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
}
export default TagApi;
+5 -7
View File
@@ -38,15 +38,13 @@ const Container = styled.div`
}
`;
const Panel = styled.div<{ visible?: boolean }>`
margin-top: ${(p) => (p.visible ? "0" : "-100%")};
const Panel = styled.div<{ $visible?: boolean }>`
max-height: ${((p) => (p.$visible ? "1000vh" : "0"))};
transition: max-height 400ms ease-in-out;
display: flex;
max-height: 15rem;
transition: margin-top 400ms ease-in-out;
& > * {
padding-left: 1em;
padding: 0 1em;
}
`;
@@ -67,7 +65,7 @@ const Accordion: React.FC<AccordionProps> = ({ title, children }) => {
<h5>{title}</h5>
</button>
<div>
<Panel visible={isOpen}>
<Panel $visible={isOpen}>
{children}
</Panel>
</div>
+14
View File
@@ -0,0 +1,14 @@
import React from "react";
import { createGlobalStyle } from "styled-components";
const GlobalBlock = createGlobalStyle`
body {
overflow: hidden;
}
`;
const BlockScroll: React.FC = () => (
<GlobalBlock />
);
export default BlockScroll;
+1 -1
View File
@@ -69,7 +69,7 @@ const StyledCard = styled.article`
const WrappedCard: React.FC<WrappedCardProps> = ({
title, text, link, image, imageAlt, start_time, buttonOnClick, ...props
}) => {
const options = {
const options: Intl.DateTimeFormatOptions = {
day: "numeric",
month: "numeric",
year: "numeric",
+88
View File
@@ -0,0 +1,88 @@
import React from "react";
import Image, { ImageProps } from "next/image";
import styled, { keyframes, Keyframes } from "styled-components";
interface CrossFadeImagesProps {
width: ImageProps["width"];
height: ImageProps["height"];
images: string[];
presentationTime: number;
fadeTime: number;
}
const AnimatedImage = styled(Image)<{ layout: string; $delay: number }>`
animation-delay: ${(p) => p.$delay}s;
`;
const Container = styled.div<{ $animation: Keyframes; $duration: number; }>`
display: flex;
flex-flow: row nowrap;
.not-first {
margin-left: -100%;
}
& > div {
box-sizing: border-box;
width: 100%;
flex: none;
img {
animation-name: ${(p) => p.$animation};
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
animation-duration: ${(p) => p.$duration}s;
}
}
`;
const CrossFadeImages: React.FC<CrossFadeImagesProps> = ({
width, height, images, presentationTime, fadeTime,
}) => {
const len = images.length;
const SINGLE_IMAGE_TIME = presentationTime + fadeTime;
const TOTAL_TIME = SINGLE_IMAGE_TIME * len;
const animation = keyframes`
0% {
opacity:1;
}
${(presentationTime / TOTAL_TIME) * 100}% {
opacity:1;
}
${(1 / len) * 100}% {
opacity:0;
}
${100 - ((fadeTime / TOTAL_TIME) * 100)}% {
opacity:0;
}
100% {
opacity:1;
}
`;
const delays = images.map((_, idx) => idx * SINGLE_IMAGE_TIME).reverse();
return (
<Container
$animation={animation}
$duration={len * SINGLE_IMAGE_TIME}
>
{ images.map((image, idx) => (
<div className={idx > 0 ? "not-first" : undefined}>
<AnimatedImage
key={image}
src={image}
objectFit="cover"
width={width}
height={height}
layout="responsive"
$delay={delays[idx]}
/>
</div>
))}
</Container>
);
};
export default CrossFadeImages;
+3 -3
View File
@@ -84,9 +84,8 @@ const FooterContent: React.FC = () => (
</div>
<div>
<Link to="https://api.sahkoinsinoorikilta.fi/members/application/">Jäseneksi</Link>
<Link to="/palaute">Palaute</Link>
<Link to="https://static.sahkoinsinoorikilta.fi">Arkisto</Link>
<Link to="https://static.sahkoinsinoorikilta.fi">Materiaalipankki</Link>
<Link to="mailto:hallitus@sahkoinsinoorikilta.fi">Palaute</Link>
<Link to="https://static.sahkoinsinoorikilta.fi">Dokumenttiarkisto</Link>
</div>
</Columns>
</MarginSpace>
@@ -94,6 +93,7 @@ const FooterContent: React.FC = () => (
<Map>
<iframe
// eslint-disable-next-line max-len
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d1983.6122518000927!2d24.81667815176689!3d60.187150048900186!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x468df5eb3cb4ecf1%3A0x3480cbfeedcc07b6!2sMaarintie+8%2C+02150+Espoo!5e0!3m2!1sfi!2sfi!4v1542413548247"
width="100%"
height="100%"
+1
View File
@@ -2,6 +2,7 @@ import React from "react";
// eslint-disable-next-line react/display-name
export default (): JSX.Element => (
// eslint-disable-next-line react/no-danger
<head dangerouslySetInnerHTML={{
__html:
`<!--
+8 -2
View File
@@ -6,6 +6,7 @@ import breakpoints from "@theme/breakpoints";
import Navigation from "./Navigation";
import NavigationMobile from "./NavigationMobile";
import HeaderLogo from "./HeaderLogo";
import BlockScroll from "./BlockScroll";
const StyledHeader = styled.header`
display: flex;
@@ -15,7 +16,7 @@ const StyledHeader = styled.header`
}
`;
const Sticky = styled.div<{ $isHidden?: boolean }>`
const Sticky = styled.div<{ $isHidden?: boolean; $mobileMenuOpen?: boolean }>`
position: sticky;
top: 0;
z-index: 10;
@@ -24,6 +25,8 @@ const Sticky = styled.div<{ $isHidden?: boolean }>`
background-color: ${colors.darkBlue};
transition: all 200ms ease-out;
height: ${(p) => (p.$mobileMenuOpen ? "100vh" : "unset")};
${(p) => (p.$isHidden ? (`
transition: all 200ms ease-in;
transform: translateY(-100%);
@@ -56,7 +59,10 @@ const Header: React.FC = () => {
}, [mobileMenuOpen]);
return (
<Sticky $isHidden={isHidden}>
<Sticky $isHidden={isHidden} $mobileMenuOpen={mobileMenuOpen}>
{ mobileMenuOpen && (
<BlockScroll />
)}
<StyledHeader>
<HeaderLogo />
<Navigation onMobileMenuOpen={handleMobileMenuClick} />
+5 -1
View File
@@ -12,7 +12,7 @@ export enum IconType {
interface IconProps {
name: IconType;
link?: string;
onClick?: (event?: any) => void;
onClick?: React.MouseEventHandler<HTMLSpanElement>;
}
const nameToIcon = (name: IconType): JSX.Element | string => {
@@ -24,6 +24,7 @@ const nameToIcon = (name: IconType): JSX.Element | string => {
xmlns="http://www.w3.org/2000/svg"
>
<title>Facebook icon</title>
{/* eslint-disable-next-line max-len */}
<path d="M22.676 0H1.324C.593 0 0 .593 0 1.324v21.352C0 23.408.593 24 1.324 24h11.494v-9.294H9.689v-3.621h3.129V8.41c0-3.099 1.894-4.785 4.659-4.785 1.325 0 2.464.097 2.796.141v3.24h-1.921c-1.5 0-1.792.721-1.792 1.771v2.311h3.584l-.465 3.63H16.56V24h6.115c.733 0 1.325-.592 1.325-1.324V1.324C24 .593 23.408 0 22.676 0" />
</svg>
);
@@ -36,6 +37,7 @@ const nameToIcon = (name: IconType): JSX.Element | string => {
xmlns="http://www.w3.org/2000/svg"
>
<title>Instagram icon</title>
{/* eslint-disable-next-line max-len */}
<path d="M12 0C8.74 0 8.333.015 7.053.072 5.775.132 4.905.333 4.14.63c-.789.306-1.459.717-2.126 1.384S.935 3.35.63 4.14C.333 4.905.131 5.775.072 7.053.012 8.333 0 8.74 0 12s.015 3.667.072 4.947c.06 1.277.261 2.148.558 2.913.306.788.717 1.459 1.384 2.126.667.666 1.336 1.079 2.126 1.384.766.296 1.636.499 2.913.558C8.333 23.988 8.74 24 12 24s3.667-.015 4.947-.072c1.277-.06 2.148-.262 2.913-.558.788-.306 1.459-.718 2.126-1.384.666-.667 1.079-1.335 1.384-2.126.296-.765.499-1.636.558-2.913.06-1.28.072-1.687.072-4.947s-.015-3.667-.072-4.947c-.06-1.277-.262-2.149-.558-2.913-.306-.789-.718-1.459-1.384-2.126C21.319 1.347 20.651.935 19.86.63c-.765-.297-1.636-.499-2.913-.558C15.667.012 15.26 0 12 0zm0 2.16c3.203 0 3.585.016 4.85.071 1.17.055 1.805.249 2.227.415.562.217.96.477 1.382.896.419.42.679.819.896 1.381.164.422.36 1.057.413 2.227.057 1.266.07 1.646.07 4.85s-.015 3.585-.074 4.85c-.061 1.17-.256 1.805-.421 2.227-.224.562-.479.96-.899 1.382-.419.419-.824.679-1.38.896-.42.164-1.065.36-2.235.413-1.274.057-1.649.07-4.859.07-3.211 0-3.586-.015-4.859-.074-1.171-.061-1.816-.256-2.236-.421-.569-.224-.96-.479-1.379-.899-.421-.419-.69-.824-.9-1.38-.165-.42-.359-1.065-.42-2.235-.045-1.26-.061-1.649-.061-4.844 0-3.196.016-3.586.061-4.861.061-1.17.255-1.814.42-2.234.21-.57.479-.96.9-1.381.419-.419.81-.689 1.379-.898.42-.166 1.051-.361 2.221-.421 1.275-.045 1.65-.06 4.859-.06l.045.03zm0 3.678c-3.405 0-6.162 2.76-6.162 6.162 0 3.405 2.76 6.162 6.162 6.162 3.405 0 6.162-2.76 6.162-6.162 0-3.405-2.76-6.162-6.162-6.162zM12 16c-2.21 0-4-1.79-4-4s1.79-4 4-4 4 1.79 4 4-1.79 4-4 4zm7.846-10.405c0 .795-.646 1.44-1.44 1.44-.795 0-1.44-.646-1.44-1.44 0-.794.646-1.439 1.44-1.439.793-.001 1.44.645 1.44 1.439z" />
</svg>
);
@@ -48,6 +50,7 @@ const nameToIcon = (name: IconType): JSX.Element | string => {
xmlns="http://www.w3.org/2000/svg"
>
<title>LinkedIn icon</title>
{/* eslint-disable-next-line max-len */}
<path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" />
</svg>
);
@@ -60,6 +63,7 @@ const nameToIcon = (name: IconType): JSX.Element | string => {
xmlns="http://www.w3.org/2000/svg"
>
<title>Menu</title>
{/* eslint-disable-next-line max-len */}
<path d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z M28,14H4c-1.104,0-2,0.896-2,2 s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2 S29.104,22,28,22z" />
</svg>
);
-16
View File
@@ -1,16 +0,0 @@
import React from "react";
interface JsonLDProps {
data: Record<string, unknown>;
}
const JsonLD: React.FC<JsonLDProps> = ({ data }) => (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(data),
}}
/>
);
export default JsonLD;
+1 -1
View File
@@ -12,7 +12,7 @@ const renderNavigationDesktopItems = () => (
<NavbarChildLink to="/kilta/toiminta">Toiminta</NavbarChildLink>
<NavbarChildLink to="/kilta/fuksi">Fuksi</NavbarChildLink>
<NavbarChildLink to="/kilta/kunnia">Kunnianosoitukset</NavbarChildLink>
<NavbarChildLink to="https://static.sahkoinsinoorikilta.fi">Arkisto</NavbarChildLink>
<NavbarChildLink to="https://static.sahkoinsinoorikilta.fi">Dokumenttiarkisto</NavbarChildLink>
</NavbarDropdownLink>
<NavbarDropdownLink to="/opinnot_ja_ura" text="Opinnot ja ura" />
<NavbarDropdownLink to="/yritysyhteistyo" text="Yritysyhteistyö" />
+1 -1
View File
@@ -10,7 +10,7 @@ const renderNavigationMobileItems = () => (
<NavbarChildLink to="/kilta/toiminta">Toiminta</NavbarChildLink>
<NavbarChildLink to="/kilta/fuksi">Fuksi</NavbarChildLink>
<NavbarChildLink to="/kilta/kunnia">Kunnianosoitukset</NavbarChildLink>
<NavbarChildLink to="https://static.sahkoinsinoorikilta.fi">Arkisto</NavbarChildLink>
<NavbarChildLink to="https://static.sahkoinsinoorikilta.fi">Dokumenttiarkisto</NavbarChildLink>
</NavbarDropdownLink>
<NavbarDropdownLink to="/opinnot_ja_ura" text="Opinnot ja ura" exploded />
<NavbarDropdownLink to="/yritysyhteistyo" text="Yritysyhteistyö" exploded />
+8 -4
View File
@@ -11,7 +11,7 @@ const Section = styled.section<{ colors: string }>`
text-align: center;
align-items: baseline;
padding: 2rem 0;
border: 0.5rem colors.sik100Gold;
flex-flow: row wrap;
a {
@@ -29,7 +29,8 @@ const Section = styled.section<{ colors: string }>`
& > h1 {
a {
text-decoration: underline;
text-decoration: underline;
}
a:hover {
text-decoration: none;
@@ -37,7 +38,7 @@ const Section = styled.section<{ colors: string }>`
}
`;
type Colors = "orange1" | "lightBlue" | "darkBlue" | "blue1" | "lightTurquoise";
type Colors = "orange1" | "lightBlue" | "darkBlue" | "blue1" | "lightTurquoise" | "sik100Blue";
interface CTASectionProps extends React.HTMLAttributes<HTMLDivElement> {
bgColor?: Colors;
@@ -54,7 +55,10 @@ a:hover {
color: ${colors.darkBlue};
}
`;
case "sik100Blue": return `
background-color: ${colors[bgColor]};
color: ${colors.sik100Gold};
`;
case "darkBlue": return `
background-color: ${colors[bgColor]};
color: ${colors.white};
@@ -5,7 +5,7 @@ import { WidgetProps } from "react-jsonschema-form";
import MarkdownStyles from "@views/common/MarkdownStyles";
type MarkdownEditorWidgetProps = Omit<WidgetProps, "options"> & {
options: any;
options: unknown;
};
const Container = styled.div`
@@ -13,7 +13,6 @@ const getIconByLabel = (label: string) => {
if (label === "English") {
return <Icon name={IconType.GBFlag} />;
}
console.error(`No icon found for label: ${label}`);
return null;
};
@@ -10,7 +10,7 @@ interface OptionsWidgetProps {
}
class OptionsWidget extends React.Component<OptionsWidgetProps> {
handleListOptionsChange = (questions: Question[], index: number) => (event) => {
handleListOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value;
const lst = val.split(",").map((p) => p.trimLeft());
@@ -19,15 +19,15 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
onChange(questions);
};
handleTextOptionsChange = (questions: Question[], index: number) => (event) => {
handleTextOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value;
// eslint-disable-next-line no-param-reassign
questions[index].options = val;
questions[index].options = val as unknown as string[]; // TODO: Check type
onChange(questions);
};
handleIntegerOptionsChange = (questions: Question[], index: number) => (event) => {
handleIntegerOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value;
if (val !== "") {
@@ -43,10 +43,9 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
onChange(questions);
};
handleRequiredChange = (questions: Question[], index: number) => (event) => {
handleRequiredChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val: boolean = event.target.checked;
console.log(val);
// eslint-disable-next-line no-param-reassign
questions[index].required = val;
onChange(questions);
@@ -1,4 +1,4 @@
import React from "react";
import React, { ReactNode } from "react";
import styled from "styled-components";
import { Draggable } from "react-beautiful-dnd";
import { colors } from "@theme/colors";
@@ -17,8 +17,8 @@ const WidgetRow = styled.div`
interface QuestionListProps {
questions: Question[];
innerRef: any;
placeholder: any;
innerRef: React.Ref<HTMLDivElement>;
placeholder: ReactNode;
onChange: (value: Question[]) => void;
}
+1
View File
@@ -9,3 +9,4 @@ export { default as FullWidthSection } from "./Sections/FullWidthSection";
export { default as InfoBox } from "./InfoBox";
export { default as Accordion } from "./Accordion/Accordion";
export { default as Link } from "./Link";
export { default as CrossFadeImages } from "./CrossFadeImages";
+53
View File
@@ -0,0 +1,53 @@
import { useRef } from "react";
import useSWR from "swr";
import isDeepEqual from "fast-deep-equal/react";
import axios, { AxiosRequestConfig } from "axios";
import Event from "@models/Event";
import { getAuthHeader } from "@utils/auth";
import { URL, Options } from "@api/eventApi";
const fetcher = (url: string, config: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
const generateFetchParams = (id = "", options: Options = {}) => {
const url = `${URL}${id}`;
const { auth, onlyNonPast, limit } = options;
return {
url,
config: {
params: {
since: onlyNonPast ? (new Date()).toISOString() : undefined,
limit,
},
headers: auth ? { Authorization: getAuthHeader() } : null,
},
};
};
interface FetchArguments {
initialData?: Event | Event[],
id?: string;
options?: Options
}
const useFetchEvents = ({
initialData,
id = "",
options = {},
}: FetchArguments) => {
const { url, config } = generateFetchParams(id, options);
// Use ref, since config dependency is non-primitive => without this we have infinite fetch loop
const configRef = useRef(config);
if (!isDeepEqual(configRef.current, config)) {
configRef.current = config;
}
const { data, error } = useSWR([url, configRef.current], fetcher, { initialData });
return {
data: data?.results || data,
error,
};
};
export default useFetchEvents;
+49
View File
@@ -0,0 +1,49 @@
import { useRef } from "react";
import useSWR from "swr";
import isDeepEqual from "fast-deep-equal/react";
import axios, { AxiosRequestConfig } from "axios";
import Post from "@models/Feed";
import { getAuthHeader } from "@utils/auth";
import { URL, Options } from "@api/feedApi";
const feedFetcher = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
const generateFetchParams = (id = "", options: Options = {}) => {
const url = `${URL}${id}`;
const { auth } = options;
return {
url,
config: {
headers: auth ? { Authorization: getAuthHeader() } : null,
},
};
};
interface FetchArguments {
initialData?: Post | Post[],
id?: string;
options?: Options
}
const useFetchFeed = ({
initialData,
id = "",
options = {},
}: FetchArguments) => {
const { url, config } = generateFetchParams(id, options);
// Use ref, since config dependency is non-primitive => without this we have infinite fetch loop
const configRef = useRef(config);
if (!isDeepEqual(configRef.current, config)) {
configRef.current = config;
}
const { data, error } = useSWR([url, configRef.current], feedFetcher, { initialData });
return {
data: data?.results || data,
error,
};
};
export default useFetchFeed;
+49
View File
@@ -0,0 +1,49 @@
import { useRef } from "react";
import useSWR from "swr";
import isDeepEqual from "fast-deep-equal/react";
import axios, { AxiosRequestConfig } from "axios";
import JobAd from "@models/JobAd";
import { getAuthHeader } from "@utils/auth";
import { URL, Options } from "@api/jobAdApi";
const jobAdFetcher = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
const generateFetchParams = (id = "", options: Options = {}) => {
const url = `${URL}${id}`;
const { auth } = options;
return {
url,
config: {
headers: auth ? { Authorization: getAuthHeader() } : null,
},
};
};
interface FetchArguments {
initialData?: JobAd | JobAd[],
id?: string;
options?: Options;
}
const useFetchJobAds = ({
initialData,
id = "",
options = {},
}: FetchArguments) => {
const { url, config } = generateFetchParams(id, options);
// Use ref, since config dependency is non-primitive => without this we have infinite fetch loop
const configRef = useRef(config);
if (!isDeepEqual(configRef.current, config)) {
configRef.current = config;
}
const { data, error } = useSWR([url, configRef.current], jobAdFetcher, { initialData });
return {
data: data?.results || data,
error,
};
};
export default useFetchJobAds;
+4 -126
View File
@@ -1,13 +1,7 @@
import axios, { AxiosRequestConfig } from "axios";
import useSWR from "swr";
import qs from "query-string";
import { getAuthHeader } from "@utils/auth";
import { Tag } from "./Tag";
import { SignupForm } from "./SignupForm";
import Tag from "./Tag";
import { SignupForm } from "./Signup";
const URL = `${process.env.NEXT_PUBLIC_API_URL}/events/`;
export interface Event {
interface Event {
id: number;
title_fi: string;
title_en: string;
@@ -25,120 +19,4 @@ export interface Event {
signupForm: SignupForm[];
}
interface Options {
onlyNonPast?: boolean;
limit?: number;
auth?: boolean;
}
export async function getEvents(options: Options = {}): Promise<Event[]> {
const { onlyNonPast, limit, auth } = options;
try {
const params = {
since: onlyNonPast ? (new Date()).toISOString() : undefined,
limit,
};
const search = qs.stringify(params);
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}?${search}`, {
headers,
});
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
export async function getEvent(id: number, auth = false): Promise<Event> {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}${id}/`, {
headers,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function createEvent(data): Promise<Event> {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function updateEvent(data): Promise<Event> {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function deleteEvent(id: number) {
try {
const resp = await axios.delete(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export const eventFetcher = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
export const generateFetchParams = (id = "", options: Options = {}) => {
const url = `${URL}${id}`;
const { auth, onlyNonPast, limit } = options;
return {
url,
config: {
params: {
since: onlyNonPast ? (new Date()).toISOString() : undefined,
limit,
},
headers: auth ? { Authorization: getAuthHeader() } : null,
},
};
};
interface FetchArguments {
initialData?: Event | Event[],
id?: string;
options?: Options
}
export const useFetchEvents = ({
initialData,
id = "",
options = {},
}: FetchArguments) => {
const { url, config } = generateFetchParams(id, options);
const { data, error } = useSWR([url, config], eventFetcher, { initialData });
return {
data: data?.results || data,
error,
};
};
export default Event;
+3 -96
View File
@@ -1,11 +1,6 @@
import axios, { AxiosRequestConfig } from "axios";
import useSWR from "swr";
import { getAuthHeader } from "@utils/auth";
import { Tag } from "./Tag";
import Tag from "./Tag";
const URL = `${process.env.NEXT_PUBLIC_API_URL}/feed/`;
export interface Post {
interface Post {
id: number;
tags: Tag[];
visible: boolean;
@@ -21,92 +16,4 @@ export interface Post {
autohide_enabled: boolean;
}
interface Options {
auth?: boolean;
}
export async function getFeed(options: Options = {}): Promise<Post[]> {
const { auth } = options;
const headers = auth ? { Authorization: getAuthHeader() } : null;
try {
const resp = await axios.get(URL, { headers });
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
export async function getPost(id: number, options: Options = {}): Promise<Post> {
const { auth } = options;
const headers = auth ? { Authorization: getAuthHeader() } : null;
try {
const resp = await axios.get(`${URL}${id}/`, { headers });
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function createPost(data): Promise<Post> {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function updatePost(data): Promise<Post> {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export const feedFetcher = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
export const generateFetchParams = (id = "", options: Options = {}) => {
const url = `${URL}${id}`;
const { auth } = options;
return {
url,
config: {
headers: auth ? { Authorization: getAuthHeader() } : null,
},
};
};
interface FetchArguments {
initialData?: Post | Post[],
id?: string;
options?: Options
}
export const useFetchFeed = ({
initialData,
id = "",
options = {},
}: FetchArguments) => {
const { url, config } = generateFetchParams(id, options);
const { data, error } = useSWR([url, config], feedFetcher, { initialData });
return {
data: data?.results || data,
error,
};
};
export default Post;
+2 -111
View File
@@ -1,11 +1,4 @@
import axios, { AxiosRequestConfig } from "axios";
import useSWR from "swr";
import qs from "query-string";
import { getAuthHeader } from "@utils/auth";
const URL = `${process.env.NEXT_PUBLIC_API_URL}/jobads/`;
export interface JobAd {
interface JobAd {
id: number;
title_fi: string;
title_en: string;
@@ -17,106 +10,4 @@ export interface JobAd {
autohide_enabled: boolean;
}
export interface GetJobAdsOptions {
onlyNonPast?: boolean;
limit?: number;
auth?: boolean;
}
export const getJobAds = async (options: GetJobAdsOptions = {}): Promise<JobAd[]> => {
const { onlyNonPast, limit, auth } = options;
try {
const params = {
since: onlyNonPast ? (new Date()).toISOString() : undefined,
limit,
};
const search = qs.stringify(params);
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}?${search}`, {
headers,
});
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
};
export const getJobAd = async (id: number, auth = false): Promise<JobAd> => {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}${id}/`, {
headers,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
export const createJobAd = async (data: any): Promise<JobAd> => {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
export const updateJobAd = async (data: any): Promise<JobAd> => {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
export const jobAdFetcher = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
export const generateFetchParams = (id = "", options: Options = {}) => {
const url = `${URL}${id}`;
const { auth } = options;
return {
url,
config: {
headers: auth ? { Authorization: getAuthHeader() } : null,
},
};
};
interface Options {
auth?: boolean;
}
interface FetchArguments {
initialData?: JobAd | JobAd[],
id?: string;
options?: Options;
}
export const useFetchJobAds = ({
initialData,
id = "",
options = {},
}: FetchArguments) => {
const { url, config } = generateFetchParams(id, options);
const { data, error } = useSWR([url, config], jobAdFetcher, { initialData });
return {
data: data?.results || data,
error,
};
};
export default JobAd;
+19 -69
View File
@@ -1,7 +1,4 @@
import axios from "axios";
import { getAuthHeader } from "@utils/auth";
const url = `${process.env.NEXT_PUBLIC_API_URL}/signup/`;
import { Question } from "@components/Widgets/SignupQuestionsWidget/common";
export interface Signup {
id?: number;
@@ -9,68 +6,21 @@ export interface Signup {
answer: string;
}
export const getSignup = async (id: number): Promise<Signup> => {
try {
const resp = await axios.get(`${url}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
export const createSignup = async (data: Signup): Promise<Signup> => {
try {
const resp = await axios.post(url, data);
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
export const updateSignup = async (data: Signup, uuid: string): Promise<Signup> => {
try {
const { id } = data;
if (!id) throw new Error("SignupId required!");
const resp = await axios.put(`${url}${id}/edit/`, data, {
params: { uuid },
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
export const getSignupUUID = async (id: number, uuid: string): Promise<Signup> => {
try {
const resp = await axios.get(`${url}${id}/edit/`, {
params: {
uuid,
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
export const deleteSignup = async (id: number) => {
try {
const resp = await axios.delete(`${url}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
export interface SignupForm {
id?: number;
title_fi: string;
title_en: string;
visible: boolean;
start_time: string;
end_time: string;
questions: Question[];
signups: string[];
quota: number;
schema: {
title?: string;
type: string;
required: string[];
properties: unknown;
minProperties?: number;
};
}
-123
View File
@@ -1,123 +0,0 @@
import axios from "axios";
import { getAuthHeader } from "@utils/auth";
import { Question } from "@components/Widgets/SignupQuestionsWidget/common";
import { Signup } from "./Signup";
const URL = `${process.env.NEXT_PUBLIC_API_URL}/signupForm/`;
export interface SignupForm {
id?: number;
title_fi: string;
title_en: string;
visible: boolean;
start_time: string;
end_time: string;
questions: Question[];
signups: string[];
quota: number;
schema: {
title?: string;
type: string;
required: string[];
properties: any;
minProperties?: number;
};
}
export async function getForms(auth = false): Promise<SignupForm[]> {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(URL, {
headers,
});
const { results } = resp.data;
return results;
} catch (err) {
console.error(err);
throw err;
}
}
export async function getForm(id: number, auth = false): Promise<SignupForm> {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}${id}/`, {
headers,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function createForm(data): Promise<SignupForm> {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function updateForm(data): Promise<SignupForm> {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export async function deleteForm(id: number) {
try {
const resp = await axios.delete(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
export const signupFormSendEmail = async (data, id): Promise<any> => {
try {
const resp = await axios.post(`${URL}${id}/sendemail/`, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
export const getSignups = async (id): Promise<Signup[]> => {
try {
const resp = await axios.get(`${URL}${id}/signups/`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
};
+2 -14
View File
@@ -1,8 +1,4 @@
import axios from "axios";
const url = `${process.env.NEXT_PUBLIC_API_URL}/tags/`;
export interface Tag {
interface Tag {
id: number;
name_fi: string;
name_en: string;
@@ -10,12 +6,4 @@ export interface Tag {
icon: string;
}
export async function getTags(): Promise<Tag[]> {
try {
const resp = await axios.get(url);
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
export default Tag;
-1
View File
@@ -16,7 +16,6 @@ const NotFound = styled.main`
const NotFoundPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/404" />
<title>404 | Ei vaan löydy</title>
<meta name="robots" content="noindex" />
</Head>
+4 -1
View File
@@ -131,7 +131,10 @@ const Web20App = ({ Component, pageProps }: AppProps) => (
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aalto-yliopiston Sähköinsinöörikilta ry</title>
<meta name="description" content="Aalto-yliopiston Sähköinsinöörikilta ry on Otaniemessä vaikuttava opiskelijajärjestö, joka on perustettu vuonna 1921. Kilta järjestää kaikenlaista toimintaa liittyen opintoihin ja vapaa-ajan viettoon." />
<meta
name="description"
content="Aalto-yliopiston Sähköinsinöörikilta ry on Otaniemessä vaikuttava opiskelijajärjestö, joka on perustettu vuonna 1921. Kilta järjestää kaikenlaista toimintaa liittyen opintoihin ja vapaa-ajan viettoon."
/>
<meta name="keywords" content="SIK AYY" />
</Head>
<GlobalCommonStyles />
+2 -2
View File
@@ -6,7 +6,7 @@ import { ServerStyleSheet } from "styled-components";
import Favicons from "@components/Favicons";
import HTMLLogo from "@components/HTMLLogo";
export default class MyDocument extends Document {
export default class MyDocument extends Document<{ styleTags: unknown }> {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
@@ -30,7 +30,7 @@ export default class MyDocument extends Document {
}
render() {
const { styleTags } = this.props as any;
const { styleTags } = this.props;
return (
<Html lang="fi">
<Head>
+26 -33
View File
@@ -1,13 +1,13 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import Head from "next/head";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
import { Tag, getTags } from "@models/Tag";
import { SignupForm, getForms } from "@models/SignupForm";
import {
Event, createEvent, getEvent, updateEvent,
} from "@models/Event";
import Tag from "@models/Tag";
import TagApi from "@api/tagApi";
import { SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import DatetimeWidget from "@components/Widgets/DatetimeWidget";
import SectionDividerWidget from "@components/Widgets/SectionDividerWidget";
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
@@ -177,17 +177,17 @@ const EventCreatePage: NextPage = () => {
}
useEffect(() => {
getTags()
TagApi.getTags()
.then((res) => setTags(res))
.catch((err) => setError(err));
getForms(true)
SignupApi.getForms(true)
.then((res) => setSignupForms(res))
.catch((err) => setError(err));
const eventId = id && Number(id);
if (eventId !== undefined) {
getEvent(eventId, true)
EventApi.getEvent(eventId, true)
.then((res) => setFormData({
...res,
tags: (res.tags).map((inst) => inst.id) as any,
@@ -197,7 +197,7 @@ const EventCreatePage: NextPage = () => {
}
}, [id]);
const onSubmit = async (data) => {
const onSubmit = async (data: any) => {
try {
const payload = data.formData;
payload.signup_id = payload.signupForm;
@@ -205,7 +205,7 @@ const EventCreatePage: NextPage = () => {
if (typeof payload.image === "string" && payload.image.startsWith("http")) payload.image = undefined;
if (payload.id === undefined) {
const resp = await createEvent(payload);
const resp = await EventApi.createEvent(payload);
// TODO: Backend return old data because of Prefetch (used for filtering invisble signupForms from GET)
// Copy from old state instead...
// resp.tags = (resp.tags as any).map(inst => inst.id);
@@ -215,7 +215,7 @@ const EventCreatePage: NextPage = () => {
setStatusMessage("Event created successfully");
setFormData(resp);
} else {
const resp = await updateEvent(payload);
const resp = await EventApi.updateEvent(payload);
// TODO: Backend return old data because of Prefetch (used for filtering invisble signupForms from GET)
// Copy from old state instead...
// resp.tags = (resp.tags as any).map(inst => inst.id);
@@ -230,33 +230,26 @@ const EventCreatePage: NextPage = () => {
}
};
const onChange = (data) => setFormData(data.formData);
const onChange = (data: any) => setFormData(data.formData);
const onFocus = () => setStatusMessage(null);
const title = formData?.id
? `Edit Event "${formData.title_fi}"`
: "Create Event";
return (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/admin/events/create" />
</Head>
<AdminCreateCommon
title={title}
formData={formData}
schema={buildSchema(formData, signupForms, tags)}
UISchema={buildUISchema()}
onChange={onChange}
onFocus={onFocus}
onSubmit={onSubmit}
statusMessage={statusMessage}
error={error}
widgets={widgets}
/>
</>
<AdminCreateCommon
title={title}
formData={formData}
schema={buildSchema(formData, signupForms, tags)}
UISchema={buildUISchema()}
onChange={onChange}
onFocus={onFocus}
onSubmit={onSubmit}
statusMessage={statusMessage}
error={error}
widgets={widgets}
/>
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default EventCreatePage;
+10 -23
View File
@@ -1,11 +1,11 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import Head from "next/head";
import React from "react";
import { NextPage } from "next";
import { formatRelative } from "date-fns";
import AdminListCommon from "@views/admin/AdminListCommon";
import { Link } from "@components/index";
import AddLink from "@components/AddLink";
import { Event, getEvents } from "@models/Event";
import Event from "@models/Event";
import useFetchEvents from "@hooks/useFetchEvents";
const URL = "/admin/events";
@@ -36,27 +36,14 @@ const renderData = (events: Event[]) => {
);
};
const AdminEventPage: NextPage = () => {
const [events, setEvents] = useState<Event[]>(null);
useEffect(() => {
getEvents({ auth: true })
.then((res) => setEvents(res));
}, []);
const { data } = useFetchEvents({ options: { auth: true } });
return (
<>
<Head>
<link rel="canonical" href={`https://sik.ayy.fi/${URL}`} />
</Head>
<AdminListCommon>
<h1>Events</h1>
<AddLink text="Create event" to={`${URL}/create`} data-e2e="create-event" />
{renderData(events)}
</AdminListCommon>
</>
<AdminListCommon>
<h1>Events</h1>
<AddLink text="Create event" to={`${URL}/create`} data-e2e="create-event" />
{renderData(data)}
</AdminListCommon>
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default AdminEventPage;
+21 -29
View File
@@ -1,12 +1,11 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import Head from "next/head";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
import { Tag, getTags } from "@models/Tag";
import {
Post, createPost, getPost, updatePost,
} from "@models/Feed";
import Tag from "@models/Tag";
import TagApi from "@api/tagApi";
import Post from "@models/Feed";
import FeedApi from "@api/feedApi";
import DatetimeWidget from "@components/Widgets/DatetimeWidget";
import SectionDividerWidget from "@components/Widgets/SectionDividerWidget";
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
@@ -145,13 +144,13 @@ const FeedCreatePage: NextPage = () => {
}
useEffect(() => {
getTags()
TagApi.getTags()
.then((res) => setTags(res))
.catch((err) => setError(err));
const feedId = id && Number(id);
if (feedId !== undefined) {
getPost(feedId, { auth: true })
FeedApi.getPost(feedId, { auth: true })
.then((res) => setFormData({
...res,
tags: (res.tags).map((inst) => inst.id) as any,
@@ -166,12 +165,12 @@ const FeedCreatePage: NextPage = () => {
payload.tag_id = payload.tags;
payload.autohide = payload.autohide || new Date();
if (payload.id === undefined) {
const resp = await createPost(payload);
const resp = await FeedApi.createPost(payload);
// resp.tags = resp.tags;
setStatusMessage("Post created successfully");
setFormData(resp);
} else {
const resp = await updatePost(payload);
const resp = await FeedApi.updatePost(payload);
// resp.tags = resp.tag_id;
setStatusMessage("Post updated successfully");
setFormData(resp);
@@ -189,26 +188,19 @@ const FeedCreatePage: NextPage = () => {
: "Create Post";
return (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/admin/feed/create" />
</Head>
<AdminCreateCommon
title={title}
formData={formData}
schema={buildSchema(formData, tags)}
UISchema={buildUISchema(formData)}
onChange={onChange}
onFocus={onFocus}
onSubmit={onSubmit}
statusMessage={statusMessage}
error={error}
widgets={widgets}
/>
</>
<AdminCreateCommon
title={title}
formData={formData}
schema={buildSchema(formData, tags)}
UISchema={buildUISchema(formData)}
onChange={onChange}
onFocus={onFocus}
onSubmit={onSubmit}
statusMessage={statusMessage}
error={error}
widgets={widgets}
/>
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default FeedCreatePage;
+10 -23
View File
@@ -1,11 +1,11 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import Head from "next/head";
import React from "react";
import { NextPage } from "next";
import { formatRelative } from "date-fns";
import AdminListCommon from "@views/admin/AdminListCommon";
import { Link } from "@components/index";
import AddLink from "@components/AddLink";
import { Post, getFeed } from "@models/Feed";
import Post from "@models/Feed";
import useFetchFeed from "@hooks/useFetchFeed";
const URL = "/admin/feed";
@@ -37,27 +37,14 @@ const renderData = (feed: Post[]) => {
};
const AdminFeedPage: NextPage = () => {
const [forms, setForms] = useState<Post[]>(null);
useEffect(() => {
getFeed({ auth: true })
.then((res) => setForms(res));
}, []);
const { data } = useFetchFeed({ options: { auth: true } });
return (
<>
<Head>
<link rel="canonical" href={`https://sik.ayy.fi/${URL}`} />
</Head>
<AdminListCommon>
<h1>Feed</h1>
<AddLink text="Create news post" to={`${URL}/create`} />
{renderData(forms)}
</AdminListCommon>
</>
<AdminListCommon>
<h1>Feed</h1>
<AddLink text="Create news post" to={`${URL}/create`} />
{renderData(data)}
</AdminListCommon>
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default AdminFeedPage;
+2 -4
View File
@@ -1,12 +1,12 @@
import React from "react";
import { NextPage, GetServerSideProps } from "next";
import { NextPage } from "next";
import Head from "next/head";
import AdminPageWrapper from "@views/common/AdminPageWrapper";
const AdminFrontPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/admin" />
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/admin`} />
</Head>
<AdminPageWrapper requiresAuthentication>
<div data-e2e="admin-front-page">
@@ -17,5 +17,3 @@ const AdminFrontPage: NextPage = () => (
);
export default AdminFrontPage;
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
+18 -27
View File
@@ -1,11 +1,9 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import Head from "next/head";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
import {
JobAd, getJobAd, createJobAd, updateJobAd,
} from "@models/JobAd";
import JobAd from "@models/JobAd";
import JobAdApi from "@api/jobAdApi";
import DatetimeWidget from "@components/Widgets/DatetimeWidget";
import SectionDividerWidget from "@components/Widgets/SectionDividerWidget";
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
@@ -123,7 +121,7 @@ const JobAdCreatePage: NextPage = () => {
useEffect(() => {
const jobId = id && Number(id);
if (jobId !== undefined) {
getJobAd(jobId, true)
JobAdApi.getJobAd(jobId, true)
.then((res) => setFormData(res))
.catch((err) => setError(err));
}
@@ -133,11 +131,11 @@ const JobAdCreatePage: NextPage = () => {
try {
const payload = data.formData;
if (payload.id === undefined) {
const resp = await createJobAd(payload);
const resp = await JobAdApi.createJobAd(payload);
setStatusMessage("Post created successfully");
setFormData(resp);
} else {
const resp = await updateJobAd(payload);
const resp = await JobAdApi.updateJobAd(payload);
setStatusMessage("Post updated successfully");
setFormData(resp);
}
@@ -154,26 +152,19 @@ const JobAdCreatePage: NextPage = () => {
: "Create Ad";
return (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/admin/jobads/create" />
</Head>
<AdminCreateCommon
title={title}
formData={formData}
schema={buildSchema(formData)}
UISchema={buildUISchema(formData)}
onChange={onChange}
onFocus={onFocus}
onSubmit={onSubmit}
statusMessage={statusMessage}
error={error}
widgets={widgets}
/>
</>
<AdminCreateCommon
title={title}
formData={formData}
schema={buildSchema(formData)}
UISchema={buildUISchema(formData)}
onChange={onChange}
onFocus={onFocus}
onSubmit={onSubmit}
statusMessage={statusMessage}
error={error}
widgets={widgets}
/>
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default JobAdCreatePage;
+10 -23
View File
@@ -1,11 +1,11 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import Head from "next/head";
import React from "react";
import { NextPage } from "next";
import { formatRelative } from "date-fns";
import AdminListCommon from "@views/admin/AdminListCommon";
import { Link } from "@components/index";
import AddLink from "@components/AddLink";
import { JobAd, getJobAds } from "@models/JobAd";
import JobAd from "@models/JobAd";
import useFetchJobAds from "@hooks/useFetchJobAds";
const URL = "/admin/jobads";
@@ -41,27 +41,14 @@ const renderData = (jobAds: JobAd[]) => {
};
const AdminJobAdPage: NextPage = () => {
const [jobAds, setAds] = useState<JobAd[]>(null);
useEffect(() => {
getJobAds({ auth: true })
.then((res) => setAds(res));
}, []);
const { data } = useFetchJobAds({ options: { auth: true } });
return (
<>
<Head>
<link rel="canonical" href={`https://sik.ayy.fi/${URL}`} />
</Head>
<AdminListCommon>
<h1>Job advertisements</h1>
<AddLink text="Create job ad" to={`${URL}/create`} />
{renderData(jobAds)}
</AdminListCommon>
</>
<AdminListCommon>
<h1>Job advertisements</h1>
<AddLink text="Create job ad" to={`${URL}/create`} />
{renderData(data)}
</AdminListCommon>
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default AdminJobAdPage;
+39 -47
View File
@@ -1,6 +1,5 @@
import React, { useState, useEffect } from "react";
import { NextPage, GetServerSideProps } from "next";
import Head from "next/head";
import { NextPage } from "next";
import { useRouter } from "next/router";
import styled from "styled-components";
import { generateToken, setTokenCookie, isAuthenticated } from "@utils/auth";
@@ -40,52 +39,45 @@ const AdminLoginPage: NextPage = () => {
};
return (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/admin/login" />
</Head>
<AdminPageWrapper requiresAuthentication={false}>
<Main>
<h1>Log in to SIK Admin</h1>
{next && next !== DEFAULT_REDIRECT && (
<div className="error">You have to log in first.</div>
)}
<form className="admin-login-form" onSubmit={handleSubmit}>
<label>Username
<input
id="login-username"
type="text"
name="username"
value={username}
onChange={(e) => {
setUsername(e.target.value);
}}
/>
</label>
<label>Password
<input
id="login-password"
type="password"
name="password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
</label>
<input id="login-submit" type="submit" value="Log in" />
</form>
{error && (
<div className="error">
{error}
</div>
)}
</Main>
</AdminPageWrapper>
</>
<AdminPageWrapper requiresAuthentication={false}>
<Main>
<h1>Log in to SIK Admin</h1>
{next && next !== DEFAULT_REDIRECT && (
<div className="error">You have to log in first.</div>
)}
<form className="admin-login-form" onSubmit={handleSubmit}>
<label>Username
<input
id="login-username"
type="text"
name="username"
value={username}
onChange={(e) => {
setUsername(e.target.value);
}}
/>
</label>
<label>Password
<input
id="login-password"
type="password"
name="password"
value={password}
onChange={(e) => {
setPassword(e.target.value);
}}
/>
</label>
<input id="login-submit" type="submit" value="Log in" />
</form>
{error && (
<div className="error">
{error}
</div>
)}
</Main>
</AdminPageWrapper>
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default AdminLoginPage;
+6 -13
View File
@@ -1,11 +1,9 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import Head from "next/head";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
import {
SignupForm, createForm, getForm, updateForm,
} from "@models/SignupForm";
import { SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi";
import DatetimeWidget from "@components/Widgets/DatetimeWidget";
import SignupQuestionsWidget from "@components/Widgets/SignupQuestionsWidget/SignupQuestionsWidget";
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
@@ -113,7 +111,7 @@ const SignupCreatePage: NextPage = () => {
useEffect(() => {
const suId = id && Number(id);
if (suId !== undefined) {
getForm(suId, true)
SignupApi.getForm(suId, true)
.then((res) => {
setFormData({
...res,
@@ -134,14 +132,14 @@ const SignupCreatePage: NextPage = () => {
};
if (payload.id === undefined) {
const resp = await createForm(payload);
const resp = await SignupApi.createForm(payload);
setStatusMessage("Sign-up created successfully");
setFormData({
...resp,
questions: JSON.stringify(resp.questions) as any,
});
} else {
const resp = await updateForm(payload);
const resp = await SignupApi.updateForm(payload);
setStatusMessage("Sign-up updated successfully");
setFormData({
...resp,
@@ -162,9 +160,6 @@ const SignupCreatePage: NextPage = () => {
return (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/admin/signups/create" />
</Head>
<AdminCreateCommon
title={title}
formData={formData}
@@ -184,6 +179,4 @@ const SignupCreatePage: NextPage = () => {
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default SignupCreatePage;
+14 -11
View File
@@ -1,9 +1,10 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
import { SignupForm, getForm, signupFormSendEmail } from "@models/SignupForm";
import { SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi";
const widgets = {
markdownEditor: MarkdownEditorWidget,
@@ -43,27 +44,31 @@ const buildUISchema = () => ({
},
});
const SignupEmailPage: NextPage = () => {
const useInitializeData = (id: string) => {
const [signupForm, setSignupForm] = useState<SignupForm>(null);
const router = useRouter();
const { id } = router.query;
useEffect(() => {
const formId = Number(id);
if (formId !== undefined) {
getForm(formId, true)
SignupApi.getForm(formId, true)
.then((res) => setSignupForm(res));
}
}, [id]);
return signupForm;
};
const SignupEmailPage: NextPage = () => {
const router = useRouter();
const { id } = router.query;
const signupForm = useInitializeData(id as string);
const [error, setError] = useState<string>(null);
const [statusMessage, setStatusMessage] = useState<string>(null);
const onSubmit = async (data) => {
try {
const payload = data.formData;
await signupFormSendEmail(payload, id);
await SignupApi.signupFormSendEmail(payload, Number(id));
setStatusMessage("Email sent successfully");
} catch (err) {
setError(err);
@@ -91,6 +96,4 @@ const SignupEmailPage: NextPage = () => {
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default SignupEmailPage;
+10 -12
View File
@@ -1,16 +1,16 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import { NextPage } from "next";
import { useRouter } from "next/router";
import styled from "styled-components";
import { CSVLink } from "react-csv";
import AdminListCommon from "@views/admin/AdminListCommon";
import { SignupForm, getForm, getSignups } from "@models/SignupForm";
import { Signup, deleteSignup } from "@models/Signup";
import { Signup, SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi";
import { Button } from "@components/index";
import noop from "@utils/noop";
const StyledButton = styled(Button) <{ colorOverride: "red" | "green" }>`
background-color: ${(p: any) => p.colorOverride};
const StyledButton = styled(Button) <{ $colorOverride: "red" | "green" }>`
background-color: ${(p) => p.$colorOverride};
`;
const SignupEmailPage: NextPage = () => {
@@ -22,16 +22,16 @@ const SignupEmailPage: NextPage = () => {
useEffect(() => {
const formId = Number(id);
getForm(formId, true)
SignupApi.getForm(formId, true)
.then((res) => setSignupForm(res));
getSignups(formId).then((res) => setSignups(res));
SignupApi.getSignups(formId).then((res) => setSignups(res));
}, [id]);
const confirmDelete = async (signup: Signup, question: any) => {
if (window.confirm(`Delete: ${signup.id}: ${signup.answer[question.id]}; Are you sure?`) === true) {
try {
await deleteSignup(signup.id);
await SignupApi.deleteSignup(signup.id);
setSignups(signups.filter((s) => s.id !== signup.id));
} catch (err) {
window.alert("Delete failed!");
@@ -64,7 +64,7 @@ const SignupEmailPage: NextPage = () => {
))}
<th>
<CSVLink data={CSVData} headers={questions.map((q) => q.title)} separator=";">
<StyledButton colorOverride="green" buttonStyle="filled" onClick={noop}>
<StyledButton $colorOverride="green" buttonStyle="filled" onClick={noop}>
Download CSV
</StyledButton>
</CSVLink>
@@ -81,7 +81,7 @@ const SignupEmailPage: NextPage = () => {
</td>
))}
<td>
<StyledButton colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(s, questions[0])}>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(s, questions[0])}>
Delete
</StyledButton>
</td>
@@ -93,6 +93,4 @@ const SignupEmailPage: NextPage = () => {
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default SignupEmailPage;
+9 -16
View File
@@ -1,11 +1,11 @@
import React, { useEffect, useState } from "react";
import { NextPage, GetServerSideProps } from "next";
import Head from "next/head";
import { NextPage } from "next";
import { formatRelative } from "date-fns";
import AdminListCommon from "@views/admin/AdminListCommon";
import { Link } from "@components/index";
import AddLink from "@components/AddLink";
import { SignupForm, getForms } from "@models/SignupForm";
import { SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi";
const URL = "/admin/signups";
@@ -44,24 +44,17 @@ const AdminSignupPage: NextPage = () => {
const [forms, setForms] = useState<SignupForm[]>(null);
useEffect(() => {
getForms(true)
SignupApi.getForms(true)
.then((res) => setForms(res));
}, []);
return (
<>
<Head>
<link rel="canonical" href={`https://sik.ayy.fi/${URL}`} />
</Head>
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
{renderData(forms)}
</AdminListCommon>
</>
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
{renderData(forms)}
</AdminListCommon>
);
};
export const getServerSideProps: GetServerSideProps = async () => ({ props: {} });
export default AdminSignupPage;
+26 -10
View File
@@ -1,10 +1,10 @@
import React from "react";
import { NextPage, GetServerSideProps } from "next";
import { NextPage, GetStaticProps, GetStaticPaths } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import {
Event, useFetchEvents, eventFetcher, generateFetchParams,
} from "@models/Event";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import useFetchEvents from "@hooks/useFetchEvents";
import EventPageView from "@views/EventPage/EventPageView";
import PageWrapper from "@views/common/PageWrapper";
import LoadingView from "@views/common/LoadingView";
@@ -16,14 +16,16 @@ interface InitialProps {
const EventPage: NextPage<InitialProps> = ({ initialEvent }) => {
const router = useRouter();
const { id } = router.query;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { data, error } = useFetchEvents({ initialData: initialEvent, id: id as string });
if (!data) return <LoadingView />;
if (!data || router.isFallback) return <LoadingView />;
return (
<>
<Head>
<link rel="canonical" href={`https://sik.ayy.fi/events/${id}`} />
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/events/${id}`} />
</Head>
<PageWrapper>
<EventPageView event={data as Event} />
@@ -32,14 +34,28 @@ const EventPage: NextPage<InitialProps> = ({ initialEvent }) => {
);
};
export const getServerSideProps: GetServerSideProps<InitialProps> = async (context) => {
const { id } = context.query;
const { url, config } = generateFetchParams(id as string);
const initialEvent = await eventFetcher(url, config);
export const getStaticPaths: GetStaticPaths = async () => {
const allEvents = await EventApi.getEvents();
const paths = allEvents.map((e: Event) => ({
params: {
id: String(e.id),
},
}
));
return {
paths,
fallback: true,
};
};
export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => {
const { id } = params;
const initialEvent = await EventApi.getEvent(Number(id));
return {
props: {
initialEvent,
},
revalidate: 10,
};
};
+62
View File
@@ -0,0 +1,62 @@
import React from "react";
import { NextPage, GetStaticProps, GetStaticPaths } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
import Post from "@models/Feed";
import FeedApi from "@api/feedApi";
import useFetchFeed from "@hooks/useFetchFeed";
import FeedPageView from "@views/FeedPage/FeedPageView";
import PageWrapper from "@views/common/PageWrapper";
import LoadingView from "@views/common/LoadingView";
interface InitialProps {
initialPost: Post;
}
const FeedPage: NextPage<InitialProps> = ({ initialPost }) => {
const router = useRouter();
const { id } = router.query;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { data, error } = useFetchFeed({ initialData: initialPost, id: id as string });
if (!data || router.isFallback) return <LoadingView />;
return (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/feed/${id}`} />
</Head>
<PageWrapper>
<FeedPageView post={data} />
</PageWrapper>
</>
);
};
export const getStaticPaths: GetStaticPaths = async () => {
const feed = await FeedApi.getFeed();
const paths = feed.map((post: Post) => ({
params: {
id: String(post.id),
},
}
));
return {
paths,
fallback: true,
};
};
export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => {
const { id } = params;
const initialPost = await FeedApi.getPost(Number(id));
return {
props: {
initialPost,
},
revalidate: 10,
};
};
export default FeedPage;
+44 -11
View File
@@ -1,18 +1,51 @@
import React from "react";
import { NextPage } from "next";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import useFetchEvents from "@hooks/useFetchEvents";
import Post from "@models/Feed";
import FeedApi from "@api/feedApi";
import useFetchFeed from "@hooks/useFetchFeed";
import InEnglishPageView from "@views/InEnglishPage/InEnglishPageView";
import PageWrapper from "@views/common/PageWrapper";
const InEnglishPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/in_english" />
</Head>
<PageWrapper>
<InEnglishPageView />
</PageWrapper>
</>
);
const eventOptions = {
onlyNonPast: true,
limit: 4,
};
interface InitialProps {
initialEvents: Event[];
initialFeed: Post[];
}
const InEnglishPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
const eventResult = useFetchEvents({ initialData: initialEvents, options: eventOptions });
const feedResult = useFetchFeed({ initialData: initialFeed });
return (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/in_english`} />
</Head>
<PageWrapper>
<InEnglishPageView events={eventResult.data as Event[]} feed={feedResult.data} />
</PageWrapper>
</>
);
};
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
const initialEvents = await EventApi.getEvents(eventOptions);
const initialFeed = await FeedApi.getFeed();
return {
props: {
initialEvents,
initialFeed,
},
revalidate: 10,
};
};
export default InEnglishPage;
+12 -15
View File
@@ -1,12 +1,12 @@
import React from "react";
import { NextPage, GetServerSideProps } from "next";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import {
Event, useFetchEvents, eventFetcher, generateFetchParams as eventParams,
} from "@models/Event";
import {
Post, useFetchFeed, feedFetcher, generateFetchParams as feedParams,
} from "@models/Feed";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import useFetchEvents from "@hooks/useFetchEvents";
import Post from "@models/Feed";
import FeedApi from "@api/feedApi";
import useFetchFeed from "@hooks/useFetchFeed";
import FrontPageView from "@views/FrontPage/FrontPageView";
import PageWrapper from "@views/common/PageWrapper";
@@ -27,7 +27,7 @@ const FrontPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
return (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/" />
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/`} />
</Head>
<PageWrapper>
<FrontPageView events={eventResult.data as Event[]} feed={feedResult.data} />
@@ -36,18 +36,15 @@ const FrontPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
);
};
export const getServerSideProps: GetServerSideProps<InitialProps> = async () => {
let url: string;
let config: any;
({ url, config } = eventParams("", eventOptions));
const initialEvents = await eventFetcher(url, config);
({ url, config } = feedParams(""));
const initialFeed = await feedFetcher(url, config);
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
const initialEvents = await EventApi.getEvents(eventOptions);
const initialFeed = await FeedApi.getFeed();
return {
props: {
initialEvents,
initialFeed,
},
revalidate: 10,
};
};
Binary file not shown.
+1 -1
View File
@@ -7,7 +7,7 @@ import PageWrapper from "@views/common/PageWrapper";
const FreshmenPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/kilta/fuksit" />
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/fuksi`} />
</Head>
<PageWrapper>
<FreshmenPageView />
+1 -1
View File
@@ -7,7 +7,7 @@ import PageWrapper from "@views/common/PageWrapper";
const GuildPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/kilta" />
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta`} />
</Head>
<PageWrapper>
<GuildPageView />
+1 -1
View File
@@ -7,7 +7,7 @@ import PageWrapper from "@views/common/PageWrapper";
const HonoraryPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/kilta/kunnia" />
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/kunnia`} />
</Head>
<PageWrapper>
<HonoraryPageView />
+12 -15
View File
@@ -1,12 +1,12 @@
import React from "react";
import { NextPage, GetServerSideProps } from "next";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import {
Event, useFetchEvents, eventFetcher, generateFetchParams as eventParams,
} from "@models/Event";
import {
Post, useFetchFeed, feedFetcher, generateFetchParams as feedParams,
} from "@models/Feed";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import useFetchEvents from "@hooks/useFetchEvents";
import Post from "@models/Feed";
import FeedApi from "@api/feedApi";
import useFetchFeed from "@hooks/useFetchFeed";
import ActualPageView from "@views/ActualPage/ActualPageView";
import PageWrapper from "@views/common/PageWrapper";
@@ -26,7 +26,7 @@ const ActualPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
return (
<>
<Head>
<link rel="canonical" href="https://sik.ayy.fi/kilta/toiminta" />
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/toiminta`} />
</Head>
<PageWrapper>
<ActualPageView events={eventResult.data} feed={feedResult.data} />
@@ -35,18 +35,15 @@ const ActualPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
);
};
export const getServerSideProps: GetServerSideProps<InitialProps> = async () => {
let url: string;
let config: any;
({ url, config } = eventParams("", eventOptions));
const initialEvents = await eventFetcher(url, config);
({ url, config } = feedParams(""));
const initialFeed = await feedFetcher(url, config);
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
const initialEvents = await EventApi.getEvents(eventOptions);
const initialFeed = await FeedApi.getFeed();
return {
props: {
initialEvents,
initialFeed,
},
revalidate: 10,
};
};

Some files were not shown because too many files have changed in this diff Show More