Compare commits

...

188 Commits

Author SHA1 Message Date
Aarni Halinen ec713f1617 update axios 2025-04-09 11:54:23 +03:00
Aarni Halinen 539bcef496 re-setup ESLint 2025-04-09 11:54:23 +03:00
Aarni Halinen d308d27727 update Next.JS to latest 2025-04-09 11:02:25 +03:00
Aarni Halinen aea9563a0f update to node v22 2025-04-09 10:39:31 +03:00
Aarni Halinen 86880dbac4 fail audit as a warning 2025-04-09 10:32:30 +03:00
Simeon Pursiainen f7a65fabc0 Merge branch 'revert-33ebf456' into 'master'
Revert "updated sponsors"

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!152
2025-04-07 22:27:43 +03:00
Simeon Pursiainen fbe20594dd Revert "updated sponsors" 2025-04-07 22:27:43 +03:00
Simeon Pursiainen 7280edb99f audit 2025-04-06 12:25:31 +03:00
Simeon Pursiainen 613732aed2 Updated contacts 2025-04-06 11:52:17 +03:00
Simeon Pursiainen 33ebf45627 updated sponsors 2025-03-19 09:37:10 +02:00
SimeonPursiainen 42fed752cc Merge branch 'production'
testing
2025-03-06 23:37:14 +02:00
Simeon Pursiainen e8e9fedf7c Merge branch 'master' into 'production'
merge

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!148
2025-03-06 23:13:18 +02:00
Simeon Pursiainen 98e811e641 merge 2025-03-06 23:13:18 +02:00
SimeonPursiainen dba12edb94 test 2025-03-06 22:50:24 +02:00
Simeon Pursiainen 1360ed2f93 Merge branch 'master' into 'production'
Release kilta-avustus + yhdenvertaisuus pages

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!147
2025-03-06 22:07:10 +02:00
SimeonPursiainen 0a53ede99d edited equitypage 2025-03-06 21:55:06 +02:00
Simeon Pursiainen 82e5b40432 Merge branch 'revert-1434c434' into 'master'
Revert "typos"

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!146
2025-03-04 22:21:52 +02:00
Simeon Pursiainen 9040624ec4 fixes 2025-03-04 22:17:14 +02:00
Simeon Pursiainen 5445d0b419 Revert "typos"
This reverts commit 1434c434bf
2025-03-04 22:14:45 +02:00
Simeon Pursiainen 1434c434bf typos 2025-03-04 21:47:47 +02:00
Simeon Pursiainen e0e8fa6a78 typos 2025-03-04 21:46:00 +02:00
Simeon Pursiainen 5fa35bf681 Merge branch 'master' of https://gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-frontend 2025-03-04 21:24:27 +02:00
Simeon Pursiainen b9ed0181fc Added ProSIK and golden honoraries 2025-03-04 20:35:15 +02:00
Simeon Pursiainen def7c79d82 Added ProSIK and golden honoraries 2025-03-04 20:22:23 +02:00
Simeon Pursiainen fb8340e23e Added funding page 2025-03-04 19:06:52 +02:00
Simeon Pursiainen 6e22c5496a Added equitypage 2025-03-02 16:22:34 +02:00
Simeon Pursiainen 62e2985f39 Merge branch 'master' into 'production'
Updated english page

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!145
2025-02-28 20:20:41 +00:00
SimeonPursiainen 7323600314 test 2025-02-28 22:06:52 +02:00
SimeonPursiainen 2e4e862d87 fixed formatting 2025-02-28 21:36:32 +02:00
SimeonPursiainen 63f0b5e99c Added information about joining the guild in english 2025-02-28 18:55:38 +02:00
SimeonPursiainen 58d9d6cc83 updated membership GDPR link 2025-02-28 16:19:12 +02:00
Simeon Pursiainen 970cceef7f Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!144
2025-02-26 20:39:35 +00:00
SimeonPursiainen 3c791f4b3b fixed spelling 2025-02-26 22:24:31 +02:00
SimeonPursiainen cd52f3b609 visual changes for contactcard 2025-02-26 22:20:27 +02:00
SimeonPursiainen 6227a61eb6 updated board member pictures 2025-02-26 17:28:23 +02:00
Simeon Pursiainen 570c1e0b48 Possibility to display job ads in English 2025-01-20 21:54:08 +02:00
Simeon Pursiainen e6457d7487 Merge branch 'master' into 'production'
updated committees (again)

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!143
2025-01-15 19:35:03 +00:00
Simeon Pursiainen 247c8b793d updated committees (again) 2025-01-15 21:17:14 +02:00
Simeon Pursiainen 282cff19a2 Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!142
2025-01-11 19:15:01 +00:00
Simeon Pursiainen 11fd154c4b Updated committees for year 2025 2025-01-11 20:44:18 +02:00
Simeon Pursiainen 54c23bd530 minor text fix 2025-01-11 18:57:02 +02:00
Simeon Pursiainen 357ac71186 added existing pics to board page 2025-01-11 17:17:36 +02:00
Simeon Pursiainen bcd35e2041 updated board representatives with new names and emails 2025-01-11 15:56:48 +02:00
Johannes Viirimäki c116036748 Merge branch 'master' into 'production'
Master to production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!141
2024-10-31 13:42:11 +00:00
Johannes f0101059dd add silver honorarys, fix links 2024-10-31 15:21:31 +02:00
Johannes eb467bf387 Merge branch 'master' of gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-frontend 2024-10-31 15:00:31 +02:00
Johannes cfce1ef859 add new silver honorarys 2024-10-31 14:59:55 +02:00
Johannes Viirimäki fcce680e80 Merge branch 'master' into 'production'
Master to production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!140
2024-09-24 09:39:59 +00:00
Johannes Viirimäki d649b4fc0c Merge branch 'feature/new-pages' into 'master'
Feature/new pages

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!139
2024-09-23 12:53:32 +00:00
Johannes Viirimäki 0373e07d45 Update yhteystiedot.tsx 2024-09-23 12:46:17 +00:00
Johannes 9745276ffd update corporate logos 2024-09-23 15:41:23 +03:00
Johannes 42835f98f3 delete duplicate info on board page 2024-09-22 17:11:08 +03:00
Johannes 2b150c1d29 Add committee info, fix typos 2024-09-16 15:06:39 +03:00
Johannes 1beb35ee80 create page for membership information 2024-09-11 17:47:02 +03:00
Johannes 4f812dc0c8 Merge remote-tracking branch 'origin/master' into feature/new-pages 2024-09-11 15:02:00 +03:00
Johannes Viirimäki 955664a342 Merge branch 'master' into 'production'
fix typos

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!138
2024-07-02 07:02:12 +00:00
Johannes 53f4f3de4c fix typos 2024-07-02 09:42:23 +03:00
Johannes Viirimäki b77ffff341 Merge branch 'master' into 'production'
update sponsor logos, update fuksi guide link

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!137
2024-06-24 06:31:25 +00:00
Johannes c4e31e3c91 update sponsor logos, update fuksi guide link 2024-06-24 09:11:04 +03:00
Johannes Viirimäki 9f95b3d05f Merge branch 'master' into 'production'
add link to new fopas

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!136
2024-06-20 10:22:59 +00:00
Johannes Viirimäki fd7e41bffb add link to new fopas 2024-06-20 09:58:10 +00:00
Johannes Viirimäki a062841b9c Merge branch 'master' into 'production'
fix typos, update info

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!135
2024-06-03 20:15:51 +00:00
Johannes Viirimäki 643ed1505a fix typos, update info 2024-06-03 19:48:17 +00:00
Johannes Viirimäki 5dde3422e7 Merge branch 'master' into 'production'
Master to production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!134
2024-05-30 12:26:03 +00:00
Johannes 16504230b2 change picture and info on freshmen page 2024-05-30 15:10:13 +03:00
Johannes 0fd26fa246 update info on freshmen page 2024-05-29 16:32:18 +03:00
Jan Tuomi a33dc3e77e Merge branch 'master' into production 2024-05-27 15:32:55 +03:00
Jan Tuomi 2cf804be05 Merge branch 'update-cicd-docker-images' into 'master'
Use docker:25 based dind services and runner images

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!133
2024-05-27 12:20:01 +00:00
Jan Tuomi 0fe6a29ffc Use docker:25 based dind services and runner images 2024-05-27 15:13:08 +03:00
Johannes Viirimäki 8e1b0b9a30 Merge branch 'master' into 'production'
Master to prod

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!132
2024-05-16 22:29:34 +00:00
Johannes Viirimäki ba9d938092 update fuksi tg chat links 2024-05-11 11:34:49 +00:00
Johannes Viirimäki a2e55927ab Merge branch 'master' into 'production'
Master to prod

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!131
2024-04-10 10:05:45 +00:00
Johannes 1bda00ac9d update guild's tg link on freshmen page 2024-04-10 12:50:22 +03:00
Johannes bfdfa28b5b asdf 2024-04-10 12:45:03 +03:00
Justus Ojala 96a3709f0c Merge branch '59-chamge-admin-event-date-format' into 'master'
Update date formats

Closes #59

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!130
2024-02-28 16:21:27 +00:00
Justus Ojala a7fff40d74 Update date formats 2024-02-28 18:12:37 +02:00
Johannes Viirimäki 5ac532176c Merge branch 'master' into 'production'
Master to production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!129
2024-02-19 19:46:38 +00:00
Johannes 325e51953a add new honorarys, add board pictures 2024-02-19 21:27:29 +02:00
Johannes 48d9437f59 stuff 2024-02-19 16:40:33 +02:00
Johannes 3f2cb7717e start new branch 2024-02-03 14:56:21 +02:00
Johannes 2ea74f90ac fix typos, update some info 2024-01-11 00:05:29 +02:00
Johannes Viirimäki af2190c447 Merge branch 'master' into 'production'
master to prod

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!128
2024-01-10 21:15:20 +00:00
Johannes f413435194 update contacts and committees 2024-01-10 22:21:21 +02:00
Johannes e770722ad0 update committee info 2024-01-10 17:56:22 +02:00
tommi s 4cab856739 Merge branch 'master' into 'production'
update contacts page

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!127
2024-01-03 18:26:20 +00:00
Johannes 0e5f7339e8 update contacts page 2024-01-03 18:22:51 +02:00
tommi s d53cd5f34c Merge branch 'master' into 'production'
Master to prod (leasing page)

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!126
2023-12-19 19:15:59 +00:00
tommi s 6c73fe9675 Merge branch 'feat/rent_page' into 'master'
Feat/rent page to master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!125
2023-12-16 09:58:08 +00:00
Tommi S a02e4891c2 fix lint 2023-12-16 11:43:15 +02:00
Tommi S 57ef1484a9 fix lint 2023-12-16 11:42:38 +02:00
Tommi S 25245939ff fix lint 2023-12-16 11:41:42 +02:00
Tommi S 225626137a edit rent page 2023-12-16 11:27:28 +02:00
Tommi S fffd2588f9 edit rent page 2023-12-16 11:24:16 +02:00
Tommi S 95244d6e47 Create renting page 2023-12-15 23:18:40 +02:00
Tommi S 452f11eefe Create renting page 2023-12-15 23:17:58 +02:00
Tommi S 824ab05843 Create renting page 2023-12-15 23:15:24 +02:00
Tommi S bcbd61c18c Create renting page 2023-12-15 23:08:35 +02:00
Tommi S e4ab992be4 Edit contacts page 2023-12-15 21:38:03 +02:00
Tommi S 10ff54f6b0 add mutate to signup 2023-12-15 21:22:35 +02:00
Tommi S d5f6cb359f audit fix 2023-12-15 20:42:09 +02:00
Ojakoo d54652bcc7 add new silver honorarys 2023-11-23 10:28:08 +02:00
Ojakoo 24aa0839de revert form button disable 2023-11-23 10:25:10 +02:00
Aarni Halinen d62ce26759 npm audit fix 2023-10-03 19:30:18 +03:00
Ojakoo faf5269eba set defult value for formSent to disable form hiding in edit view 2023-09-26 14:32:40 +03:00
Ojakoo 9a20cc009d quick fix #42 2023-09-26 13:53:40 +03:00
tommi s 057823c221 Merge branch 'master' into 'production'
Master to prod

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!124
2023-08-08 17:07:35 +00:00
Tommi S 6891f87447 add new words 2023-08-08 19:44:03 +03:00
Tommi S 17633f3345 Add english page international telegram group link 2023-08-02 22:55:10 +03:00
Tommi S 59e7194cf7 Add english page international telegram group link 2023-08-02 22:52:41 +03:00
Ojakoo 5a097080ee fix typo 2023-07-11 11:11:59 +03:00
tommi s f57bf98f31 Merge branch 'master' into 'production'
Master to Prod

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!123
2023-06-27 20:00:01 +00:00
Tommi S 433d9c67d7 Add 2023 silver and proSIK honors 2023-06-27 22:17:48 +03:00
Tommi S d538e6c92e Change freshmen page titles 2023-06-27 17:05:00 +03:00
Tommi S 1be914f37f Update freshmen page contacts and links 2023-06-26 17:55:15 +03:00
Ilari Ojakorpi 437adf1fc2 Merge branch 'master' into 'production'
Merge master to prod

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!122
2023-05-30 07:19:19 +00:00
Tommi S 521df27aa1 Merge branch 'master' of gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-frontend 2023-05-29 00:39:44 +03:00
Tommi S 8bf38f512c Add google calendar link to front page 2023-05-29 00:29:01 +03:00
Tommi S 3ffe8a1e17 Front page google calendar link added and corporate page text updated 2023-05-29 00:23:53 +03:00
Aarni Halinen 32e541533f Small CI/CD cleanup 2023-05-28 23:35:48 +03:00
Aarni Halinen 9f33c667d3 Copy working login from build step 2023-05-28 23:34:34 +03:00
Aarni Halinen 0e4e02e1b3 Revert "Test deploy token"
This reverts commit cfc7dd11f5.
2023-05-28 23:30:29 +03:00
Aarni Halinen cfc7dd11f5 Test deploy token 2023-05-28 23:14:43 +03:00
Tommi S 63df5e6f5f Add google calendar link to front page 2023-05-24 16:03:44 +03:00
Tommi S bdcf4840f5 Add google calendar link to front page 2023-05-24 15:57:17 +03:00
Tommi S 0dc349161e Remove sik100 info 2023-05-20 12:05:48 +03:00
Tommi S d101931020 Remove sik100 info 2023-05-20 11:54:48 +03:00
Tommi S b4d41cd6a7 Removed sik100 info 2023-05-19 17:53:54 +03:00
Tommi S ea82b493d5 Updated sikpaja info and links 2023-03-23 11:49:17 +02:00
Ilari Ojakorpi e767b395a9 Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!121
2023-03-05 17:23:56 +00:00
tommi s fe8f9328fa Updated board pictures 2023-03-03 14:04:36 +00:00
Ojakoo 71d19d44cf add utility to wait for logger 2023-02-12 13:34:57 +02:00
Ilari Ojakorpi c3bbb3eda8 Merge branch 'master' into 'production'
Merge 'Master' into 'Production'

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!118
2023-01-27 08:59:51 +00:00
Ilari Ojakorpi 8a6b2e0846 Merge branch 'master' into 'production'
Updated board info

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!117
2023-01-02 01:16:53 +00:00
Ilari Ojakorpi ea333b7c69 Merge branch 'master' into 'production'
Merge Master to Production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!116
2022-12-25 13:41:15 +00:00
Ilari Ojakorpi 9c77cab47e Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!115
2022-11-06 13:38:14 +00:00
Ilari Ojakorpi 0301f3a996 Merge branch 'master' into 'production'
Master to production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!112
2022-09-22 12:23:42 +00:00
Ilari Ojakorpi ee1be687bb Merge branch 'master' into 'production'
Master to prod

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!111
2022-09-19 08:46:42 +00:00
Ilari Ojakorpi adb505d8ce Merge branch 'master' into 'production'
Merge master to production.

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!110
2022-09-15 17:03:45 +00:00
Ilari Ojakorpi 56669d5031 Merge branch 'master' into 'production'
Merge 'master' into production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!107
2022-07-06 20:01:54 +00:00
Ilari Ojakorpi 1e2ba706bf Merge branch 'master' into 'production'
Merge 'master' into 'prod'

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!106
2022-06-04 10:21:17 +00:00
Ilari Ojakorpi c9b885df9e Merge branch 'master' into 'production'
Merge dev into master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!105
2022-05-20 19:54:01 +00:00
Ilari Ojakorpi 492d28381f Merge branch 'master' into 'production'
Merge master to production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!103
2022-05-04 08:58:45 +00:00
Toni Lyttinen 22f306ff3c Merge branch 'master' into 'production'
master -> prod contacts page update

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!100
2022-02-16 14:16:58 +00:00
Toni Lyttinen c1ff6bbeae Merge branch 'master' into 'production'
Master->Production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!99
2022-02-16 00:12:55 +00:00
Ilari Ojakorpi bb3b9cb27f Merge branch 'master' into 'production'
Merge branch 'master' into 'production'

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!96
2022-01-31 21:38:25 +00:00
Ilari Ojakorpi 4449003cc8 Merge branch 'master' into 'production'
Prod deploy: Package updates

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!92
2022-01-17 14:10:26 +00:00
koskelj8 b4aa3c4871 Merge branch 'master' into production 2022-01-04 19:31:45 +02:00
Ilari Ojakorpi f91bb57932 Merge branch 'master' into 'production'
Prod deploy: Siemens logo and new board

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!91
2022-01-03 14:12:32 +00:00
Aarni Halinen 045d48c988 Merge branch 'master' into 'production'
Prod deploy: Sentry

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!88
2021-12-03 00:31:42 +00:00
Oskari Ponkala b4b29d6c9b Merge branch 'master' into 'production'
Add ramboll to logos

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!86
2021-10-25 13:11:53 +00:00
Aarni Halinen e5f6d5f659 Merge branch 'master' into 'production'
Prod deploy: Translated signup questions & preparation for PoTa100 signup

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!84
2021-09-06 10:37:48 +00:00
Oskari Ponkala 6b05fcab4a Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!82
2021-08-27 11:22:24 +00:00
Aarni Halinen 3f660efa5a Merge branch 'master' into 'production'
Prod deploy: Improve image loading

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!80
2021-08-22 17:34:30 +00:00
Toni Lyttinen dd3adae35f Merge branch 'master' into 'production'
update weboodi to sisu for prod

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!78
2021-08-04 18:00:19 +00:00
Aarni Halinen e9fdeaeb5b Merge branch 'master' into 'production'
Prod deploy: Remove NRC logo

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!77
2021-07-22 14:36:11 +00:00
Aarni Halinen 77122aeea6 Revert "Update FrontPageView.tsx"
This reverts commit c9d6c815d0.
2021-07-22 17:34:00 +03:00
Oskari Ponkala c9d6c815d0 Update FrontPageView.tsx 2021-07-21 17:41:53 +00:00
Aarni Halinen be3ce96b4a Merge branch 'master' into 'production'
Prod deploy: Signup fixes

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!75
2021-06-29 17:26:34 +00:00
Toni Lyttinen 1a8764f725 Merge branch 'master' into 'production'
dev to prod: styling, added fopas etc

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!74
2021-06-29 16:04:22 +00:00
Aarni Halinen 7547b10d70 Merge branch 'master' into 'production'
Prod deploy: Contacts page and minor fixes

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!71
2021-06-16 20:05:45 +00:00
Toni Lyttinen 0561c7ea50 Merge branch 'master' into 'production'
fix actual page read more button style

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!68
2021-06-01 09:57:40 +00:00
Toni Lyttinen 084f7b7a81 Merge branch 'master' into 'production'
Merge 'added max 4 limit to fetch feed on in_english page'

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!67
2021-05-28 18:32:44 +00:00
Toni Lyttinen 01f663756b Merge branch 'master' into 'production'
Dev->Production. Updated Freshman page to 2021

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!66
2021-05-28 15:38:07 +00:00
Aarni Halinen 0979e84567 Merge branch 'master' into 'production'
Prod deploy: Quickfix SWR events API infinite loop, add translations & end time on signup page

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!65
2021-05-15 23:13:18 +00:00
Aarni Halinen 2b16776ee3 Merge branch 'master' into 'production'
Production deploy: Update packages, improved Docker images & style fixes

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!64
2021-05-15 15:41:48 +00:00
Aarni Halinen fc4b327e4b Merge branch 'master' into 'production'
Prod Deploy: i18n support & minor SEO fixes

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!59
2021-04-08 20:27:58 +00:00
Aarni Halinen a525fe81c6 Merge branch 'master' into 'production'
Prod deploy: Update dependencies, fix next/image cache

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!56
2021-04-01 12:20:15 +00:00
Toni Lyttinen 3c0e320bf3 Merge branch 'master' into 'production'
added Tuukka's career story to studies page

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!54
2021-04-01 07:23:22 +00:00
Toni Lyttinen 648cec04ef Merge branch 'master' into 'production'
fix card size on calendars to production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!53
2021-03-31 22:25:56 +00:00
Toni Lyttinen 65430c9017 Merge branch 'master' into 'production'
Master to Production merge: fixed news feed image

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!52
2021-03-31 22:02:13 +00:00
Aarni Halinen f70ff3eedf Merge branch 'master' into 'production'
1st Live deploy

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!48
2021-03-31 21:17:15 +00:00
Toni Lyttinen 6596aa2ec8 Merge branch 'master' into 'production'
Prod deploy

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!45
2021-03-29 16:47:43 +00:00
Toni Lyttinen 6ce9c791b0 Merge branch 'master' into 'production'
master to prod

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!33
2021-02-22 19:13:40 +00:00
Toni Lyttinen d19613f08f Merge branch 'master' into 'production'
Merge dev environment text context and structure changes to prod environment.

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!32
2021-02-20 19:13:24 +00:00
Toni Lyttinen ff7143a5fa Merge branch 'master' into 'production'
master to production merge

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!31
2021-02-11 16:37:44 +00:00
Toni Lyttinen 830538d56e Merge branch 'master' into 'production'
Production deploy

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!30
2021-02-10 19:06:20 +00:00
Toni Lyttinen 2b1e9c6a0b Merge branch 'master' into 'production'
merge master to production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!27
2021-01-28 13:06:41 +00:00
Aarni Halinen e997cdab8c Merge branch 'master' into 'production'
Prod deploy

* NextJS

* Contact Page

* CSS fixes

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!26
2021-01-19 15:55:41 +00:00
Aarni Halinen d98e44e17f Merge branch 'master' into 'production'
Huge production merge

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!18
2020-12-12 14:15:45 +00:00
Aarni Halinen 067843d2b1 Merge branch 'master' into 'production'
API modifications

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!11
2020-08-29 12:23:50 +00:00
Aarni Halinen c25e93ae2c Merge branch 'master' into 'production'
Few form fixes

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!10
2020-07-24 21:48:53 +00:00
Aarni Halinen 8a05a4c459 Merge branch 'master' into 'production'
Signup modifications

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!9
2020-07-24 19:35:09 +00:00
Aarni Halinen 48e4f2f6f8 Merge branch 'master' into production 2020-03-05 19:20:25 +02:00
Aarni Halinen ae1c2b0d24 Merge branch 'master' into 'production'
Production testing cookies for the static.sika.sik.party login

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!8
2020-03-02 21:02:18 +00:00
Aarni Halinen e32070eb7b Merge branch 'master' into production 2019-12-18 17:18:41 +02:00
Aarni Halinen f848259bbf Merge branch 'master' into 'production'
Path fix

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!7
2019-12-18 15:01:30 +00:00
Aarni Halinen 6132aec379 Merge branch 'master' into 'production'
Env testing

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!6
2019-12-18 11:28:03 +00:00
Aarni Halinen 0f344ad70d Merge branch 'master' into 'production'
Prod API_URL for Filebrowser testing

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!5
2019-12-17 23:18:54 +00:00
138 changed files with 13121 additions and 6187 deletions
-7
View File
@@ -1,7 +0,0 @@
# 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
-51
View File
@@ -1,51 +0,0 @@
module.exports = {
extends: [
"eslint:recommended",
"airbnb",
"airbnb-typescript",
// "airbnb/hooks",
"plugin:import/recommended",
"plugin:@typescript-eslint/recommended",
// "plugin:@typescript-eslint/recommended-requiring-type-checking",
"next/core-web-vitals",
],
parser: "@typescript-eslint/parser",
parserOptions: {
tsconfigRootDir: __dirname,
project: "./tsconfig.json",
},
plugins: ["@typescript-eslint"],
overrides: [
{
files: ["*.js"],
rules: {
"@typescript-eslint/no-var-requires": "off",
},
},
],
rules: {
"max-len": [
"warn",
240,
],
"@typescript-eslint/quotes": [
"error",
"double",
],
"react/jsx-props-no-spreading": "off",
"react/jsx-one-expression-per-line": "off",
"react/require-default-props": "off",
"react/default-props-match-prop-types": "off",
"react/function-component-definition": ["error", {
namedComponents: "arrow-function",
unnamedComponents: "arrow-function",
}],
// Temp
"react/no-array-index-key": "warn",
"jsx-a11y/label-has-associated-control": "off",
"jsx-a11y/click-events-have-key-events": "off",
"jsx-a11y/no-noninteractive-element-interactions": "off",
"jsx-a11y/no-static-element-interactions": "off",
"@typescript-eslint/default-param-last": "warn",
},
};
+18 -23
View File
@@ -8,7 +8,7 @@ stages:
- deploy
install:
image: node:16
image: node:22
stage: setup
script:
- npm ci
@@ -21,34 +21,35 @@ install:
expire_in: 1 week
audit:
image: node:16
image: node:22
needs: ["install"]
allow_failure: true
stage: audit
script:
- npm audit --audit-level=critical
es:lint:
image: node:16
image: node:22
needs: ["install"]
stage: lint
script:
- npm run lint:es
css:lint:
image: node:16
image: node:22
needs: ["install"]
stage: lint
script:
- npm run lint:css
# test:unit:
# image: node:16
# image: node:22
# stage: test
# script:
# - npm run test:unit
build:
image: node:16
image: node:22
needs: ["install"]
stage: build
script:
@@ -66,7 +67,7 @@ build:
- .next/cache/
test:e2e:
image: circleci/node:16-browsers
image: circleci/node:22-browsers
needs: ["install", "build"]
stage: test
script:
@@ -79,34 +80,32 @@ test:e2e:
publish:dev:
stage: publish
image: docker:stable
image: docker:25-cli
needs: ["build", "test:e2e", "es:lint", "css:lint"]
services:
- docker:stable-dind
- docker:25-dind
only:
- master
script:
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build . -t "$IMAGE_NAME":latest --build-arg SENTRY_AUTH_TOKEN="$SENTRY_AUTH_TOKEN" --build-arg NEXT_PUBLIC_DEPLOY_ENV=development --build-arg NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api --build-arg NEXT_PUBLIC_SITE_URL=https://dev.sahkoinsinoorikilta.fi
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker push "$IMAGE_NAME":latest
publish:prod:
stage: publish
image: docker:stable
image: docker:25-cli
services:
- docker:stable-dind
- docker:25-dind
only:
- production
script:
- docker info
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker build . -t "$IMAGE_NAME":prod --build-arg SENTRY_AUTH_TOKEN="$SENTRY_AUTH_TOKEN"
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
- docker push "$IMAGE_NAME":prod
deploy:dev:
stage: deploy
image: docker:stable
image: docker:25-cli
only:
- master
environment:
@@ -120,15 +119,13 @@ deploy:dev:
- echo "$DEV_TLSCACERT" > ~/.docker/ca.pem
- echo "$DEV_TLSCERT" > ~/.docker/cert.pem
- echo "$DEV_TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker stack deploy --with-registry-auth -c stack-compose-dev.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
deploy:prod:
stage: deploy
image: docker:stable
image: docker:25-cli
only:
- production
environment:
@@ -142,8 +139,6 @@ deploy:prod:
- echo "$TLSCACERT" > ~/.docker/ca.pem
- echo "$TLSCERT" > ~/.docker/cert.pem
- echo "$TLSKEY" > ~/.docker/key.pem
- docker login -u gitlab-ci-token -p "$CI_BUILD_TOKEN" "$CI_REGISTRY"
- docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
script:
- docker stack deploy --with-registry-auth -c stack-compose.yml "$SERVICE_NAME"
after_script:
- docker logout "$CI_REGISTRY"
+1 -1
View File
@@ -1 +1 @@
16
22
+3 -3
View File
@@ -1,5 +1,5 @@
# Install dependencies only when needed
FROM node:16-alpine AS deps
FROM node:22-alpine AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat
WORKDIR /app
@@ -7,7 +7,7 @@ COPY package.json package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM node:16-alpine AS builder
FROM node:22-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
@@ -21,7 +21,7 @@ ARG SENTRY_AUTH_TOKEN
RUN npm run build
# Production image, copy all the files and run next
FROM node:16-alpine AS runner
FROM node:22-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
+1 -1
View File
@@ -10,7 +10,7 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
## Installation
Install node v16 with **[Node Version Manager](https://github.com/nvm-sh/nvm#installing-and-updating)**.
Install node v22 with **[Node Version Manager](https://github.com/nvm-sh/nvm#installing-and-updating)**.
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the master branch:
```bash
+88
View File
@@ -0,0 +1,88 @@
// @ts-check
import pluginJs from "@eslint/js";
import next from "@next/eslint-plugin-next";
import jsxA11y from "eslint-plugin-jsx-a11y";
import markdown from "eslint-plugin-markdown";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import globals from "globals";
import tseslint from "typescript-eslint";
const reactConfigs = tseslint.config(
{
files: ["**/*.{jsx,tsx}"],
settings: {
react: {
version: "detect",
},
},
},
{
files: ["**/*.{jsx,tsx}"],
languageOptions: {
parserOptions: react.configs["jsx-runtime"].parserOptions,
},
plugins: {
react: /** @type {import('eslint').ESLint.Plugin} */ (react),
},
rules: {
...react.configs.flat.recommended.rules,
...react.configs.flat["jsx-runtime"].rules,
"react/display-name": "off",
"react/no-unstable-nested-components": "warn",
"react/prop-types": "off",
},
},
reactHooks.configs["recommended-latest"],
);
export default tseslint.config(
{
ignores: [".next/", "coverage/"],
},
{
languageOptions: {
globals: {
...globals.serviceworker,
...globals.browser,
},
parserOptions: {
ecmaVersion: "latest",
project: true,
sourceType: "module",
},
},
},
pluginJs.configs.recommended,
...markdown.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylistic,
...reactConfigs,
jsxA11y.flatConfigs.strict,
{
plugins: {
'@next/next': next,
},
rules: {
...next.configs.recommended.rules,
...next.configs['core-web-vitals'].rules,
},
},
{
rules: {
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
}
}
);
+1 -1
View File
@@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
+7 -17
View File
@@ -3,19 +3,7 @@ const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
const sentryWebpackPluginOptions = {
// Additional config options for the Sentry Webpack plugin. Keep in mind that
// the following options are set automatically, and overriding them is not
// recommended:
// release, url, org, project, authToken, configFile, stripPrefix,
// urlPrefix, include, ignore
silent: true, // Suppresses all logs
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options.
};
module.exports = withBundleAnalyzer(withSentryConfig({
const nextConfig = {
images: {
domains: [
"api.sahkoinsinoorikilta.fi",
@@ -23,7 +11,9 @@ module.exports = withBundleAnalyzer(withSentryConfig({
"api.dev.sahkoinsinoorikilta.fi",
],
},
sentry: {
hideSourceMaps: true, // Hide source maps, see: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#configure-source-maps
}
}, sentryWebpackPluginOptions));
};
module.exports = withBundleAnalyzer(withSentryConfig(nextConfig, {
silent: !process.env.CI,
hideSourceMaps: true, // Hide source maps, see: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#configure-source-maps
}));
+9711 -4086
View File
File diff suppressed because it is too large Load Diff
+15 -14
View File
@@ -34,25 +34,25 @@
"prepare": "husky install"
},
"devDependencies": {
"@next/eslint-plugin-next": "^15.2.5",
"@types/jest": "^27.4.1",
"@types/js-cookie": "^3.0.1",
"@types/node": "^16.11.36",
"@types/node": "^22.14.0",
"@types/react": "^18.0.15",
"@types/react-csv": "^1.1.3",
"@types/react-dom": "^18.0.6",
"@types/shortid": "^0.0.29",
"@types/styled-components": "^5.1.25",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0",
"babel-plugin-styled-components": "^2.0.7",
"eslint": "^8.13.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-next": "^13.1.6",
"eslint-plugin-import": "^2.26.0",
"eslint": "^9.24.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-jsx-a11y": "^6.10.2",
"eslint-plugin-markdown": "^5.1.0",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"husky": "^7.0.4",
"jest": "^27.5.1",
"next-sitemap": "^3.1.11",
"next-sitemap": "^4.2.3",
"npm-run-all": "^4.1.5",
"postcss-jsx": "^0.36.4",
"postcss-syntax": "^0.36.2",
@@ -61,18 +61,19 @@
"stylelint-config-styled-components": "^0.1.1",
"testcafe": "^1.18.5",
"ts-jest": "^27.1.4",
"typescript": "^4.6.3"
"typescript": "^4.6.3",
"typescript-eslint": "^8.29.1"
},
"dependencies": {
"@next/bundle-analyzer": "^12.2.3",
"@next/bundle-analyzer": "^15.2.5",
"@rjsf/core": "^4.2.0",
"@sentry/nextjs": "^7.34.0",
"axios": "^0.26.1",
"@sentry/nextjs": "^9.12.0",
"axios": "^1.8.4",
"date-fns": "^2.28.0",
"fast-deep-equal": "^3.1.3",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"next": "^13.1.6",
"next": "^15.2.5",
"normalize.css": "^8.0.1",
"react": "^18.2.0",
"react-csv": "^2.2.2",
+16
View File
@@ -0,0 +1,16 @@
// This file configures the initialization of Sentry on the browser.
// The config you add here will be used whenever a page is visited.
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
import * as Sentry from "@sentry/nextjs";
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
const ENV = process.env.NEXT_PUBLIC_DEPLOY_ENV;
Sentry.init({
dsn: SENTRY_DSN,
environment: ENV,
// Note: if you want to override the automatic release value, do not set a
// `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps
});
+32 -16
View File
@@ -1,28 +1,38 @@
import {
deleteTokenCookies, getAccessTokenCookie, getRefreshTokenCookie, setAccessTokenCookie, setRefreshTokenCookie,
deleteTokenCookies,
getAccessTokenCookie,
getRefreshTokenCookie,
setAccessTokenCookie,
setRefreshTokenCookie,
} from "@utils/auth";
import { APIPath, postBackendAPI } from "./backend";
export type AuthTokenRequest = {
export interface AuthTokenRequest {
username: string;
password: string;
};
}
export type AuthToken = {
export interface AuthToken {
access: string;
refresh: string;
};
}
export type AuthRefreshRequest = {
refresh: AuthToken["refresh"]
};
export interface AuthRefreshRequest {
refresh: AuthToken["refresh"];
}
export type RefreshedAuthToken = {
export interface RefreshedAuthToken {
access: string;
};
}
async function generateToken(username: string, password: string): Promise<AuthToken> {
const resp = await postBackendAPI<AuthTokenRequest, AuthToken>({ path: APIPath.AUTH_TOKEN_GENERATE }, { username, password });
async function generateToken(
username: string,
password: string
): Promise<AuthToken> {
const resp = await postBackendAPI<AuthTokenRequest, AuthToken>(
{ path: APIPath.AUTH_TOKEN_GENERATE },
{ username, password }
);
return {
access: resp.access,
refresh: resp.refresh,
@@ -39,16 +49,22 @@ async function refreshToken(): Promise<boolean> {
try {
// Renew access token
const { access } = await postBackendAPI<AuthRefreshRequest, RefreshedAuthToken>({ path: APIPath.AUTH_TOKEN_REFRESH }, { refresh });
const { access } = await postBackendAPI<
AuthRefreshRequest,
RefreshedAuthToken
>({ path: APIPath.AUTH_TOKEN_REFRESH }, { refresh });
setAccessTokenCookie(access);
} catch (err) {
} catch (_err) {
// If we get HTTP500 or something form backend, do not clear cookies
return false;
}
return true;
}
export const login = async (username: string, password: string): Promise<void> => {
export const login = async (
username: string,
password: string
): Promise<void> => {
const { access, refresh } = await generateToken(username, password);
setAccessTokenCookie(access);
setRefreshTokenCookie(refresh);
@@ -66,7 +82,7 @@ export const authenticate = async (): Promise<boolean> => {
try {
await postBackendAPI({ path: APIPath.AUTH_TOKEN_VERIFY }, { token });
return true;
} catch (err) {
} catch (_err) {
// Handle refresh automatically
return refreshToken();
}
+69 -23
View File
@@ -20,7 +20,7 @@ export enum APIPath {
AUTH_TOKEN_REFRESH = "/token/refresh",
}
export type API = {
export interface API {
path: APIPath;
urlParams?: {
id?: string | number;
@@ -32,11 +32,11 @@ export type API = {
uuid?: string;
};
authenticated?: boolean;
};
}
type Headers = {
interface Headers {
Authorization?: string;
};
}
const getAuthHeader = (): string => {
const jwt = getAccessTokenCookie();
@@ -52,7 +52,10 @@ const getHeaders = (auth?: boolean): Headers => {
return {};
};
const fillUrlParams = (apiPath: APIPath, params: API["urlParams"] = {}): string => {
const fillUrlParams = (
apiPath: APIPath,
params: API["urlParams"] = {}
): string => {
const path = apiPath
.split("/")
.map((urlComponent) => {
@@ -76,20 +79,20 @@ const callBackendAPI = async <RequestType, ResponseType>(
queryParams: API["queryParams"],
method: AxiosRequestConfig["method"],
headers: Headers,
requestBody: RequestType,
requestBody: RequestType
): Promise<ResponseType> => {
const url = fillUrlParams(path, urlParams);
const request: AxiosRequestConfig = {
url,
method,
headers,
headers: { ...headers },
params: queryParams,
data: requestBody,
responseType: "json",
};
const response = await axiosInstance.request<ResponseType>(request);
const arrayResp = (response.data as { results?: ResponseType });
const arrayResp = response.data as { results?: ResponseType };
if (Array.isArray(arrayResp.results)) {
return arrayResp.results;
}
@@ -97,35 +100,78 @@ const callBackendAPI = async <RequestType, ResponseType>(
};
export const getBackendAPI = async <ResponseType>({
path, urlParams, queryParams, authenticated,
path,
urlParams,
queryParams,
authenticated,
}: API): Promise<ResponseType> => {
const headers = getHeaders(authenticated);
return callBackendAPI<undefined, ResponseType>(path, urlParams, queryParams, "GET", headers, undefined);
return callBackendAPI<undefined, ResponseType>(
path,
urlParams,
queryParams,
"GET",
headers,
undefined
);
};
export const postBackendAPI = async <RequestType, ResponseType>({
path, urlParams, queryParams, authenticated,
}: API, body: RequestType): Promise<ResponseType> => {
export const postBackendAPI = async <RequestType, ResponseType>(
{ path, urlParams, queryParams, authenticated }: API,
body: RequestType
): Promise<ResponseType> => {
const headers = getHeaders(authenticated);
return callBackendAPI<RequestType, ResponseType>(path, urlParams, queryParams, "POST", headers, body);
return callBackendAPI<RequestType, ResponseType>(
path,
urlParams,
queryParams,
"POST",
headers,
body
);
};
export const putBackendAPI = async <RequestType, ResponseType>({
path, urlParams, queryParams, authenticated,
}: API, body: RequestType): Promise<ResponseType> => {
export const putBackendAPI = async <RequestType, ResponseType>(
{ path, urlParams, queryParams, authenticated }: API,
body: RequestType
): Promise<ResponseType> => {
const headers = getHeaders(authenticated);
return callBackendAPI<RequestType, ResponseType>(path, urlParams, queryParams, "PUT", headers, body);
return callBackendAPI<RequestType, ResponseType>(
path,
urlParams,
queryParams,
"PUT",
headers,
body
);
};
export const deleteBackendAPI = async <ResponseType>({
path, urlParams, queryParams, authenticated,
path,
urlParams,
queryParams,
authenticated,
}: API): Promise<ResponseType> => {
const headers = getHeaders(authenticated);
return callBackendAPI<undefined, ResponseType>(path, urlParams, queryParams, "DELETE", headers, undefined);
return callBackendAPI<undefined, ResponseType>(
path,
urlParams,
queryParams,
"DELETE",
headers,
undefined
);
};
export const fetcher = <ResponseType>({
path, urlParams, queryParams, authenticated,
}: API) => getBackendAPI<ResponseType>({
path, urlParams, queryParams, authenticated,
path,
urlParams,
queryParams,
authenticated,
}: API) =>
getBackendAPI<ResponseType>({
path,
urlParams,
queryParams,
authenticated,
});
+32 -11
View File
@@ -1,7 +1,10 @@
/* eslint-disable no-console */
import Event from "@models/Event";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
APIPath,
deleteBackendAPI,
getBackendAPI,
postBackendAPI,
putBackendAPI,
} from "./backend";
interface Options {
@@ -15,7 +18,9 @@ class EventApi {
static getEvent = async (id: number, auth = false): Promise<Event> => {
try {
return await getBackendAPI<Event>({
path: APIPath.EVENTS, urlParams: { id }, authenticated: auth,
path: APIPath.EVENTS,
urlParams: { id },
authenticated: auth,
});
} catch (err) {
console.error(err);
@@ -24,7 +29,10 @@ class EventApi {
};
static getEvents = async ({
since, limit, offset, auth,
since,
limit,
offset,
auth,
}: Options = {}): Promise<Event[]> => {
try {
return await getBackendAPI<Event[]>({
@@ -44,9 +52,13 @@ class EventApi {
static createEvent = async (data: Event): Promise<Event> => {
try {
return await postBackendAPI<Event, Event>({
path: APIPath.EVENTS, authenticated: true,
}, data);
return await postBackendAPI<Event, Event>(
{
path: APIPath.EVENTS,
authenticated: true,
},
data
);
} catch (err) {
console.error(err);
throw err;
@@ -55,9 +67,14 @@ class EventApi {
static updateEvent = async (data: Event): Promise<Event> => {
try {
return await putBackendAPI<Event, Event>({
path: APIPath.EVENTS, urlParams: { id: data.id }, authenticated: true,
}, data);
return await putBackendAPI<Event, Event>(
{
path: APIPath.EVENTS,
urlParams: { id: data.id },
authenticated: true,
},
data
);
} catch (err) {
console.error(err);
throw err;
@@ -66,7 +83,11 @@ class EventApi {
static deleteEvent = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
await deleteBackendAPI<{ message: "OK" }>({
path: APIPath.EVENTS,
urlParams: { id },
authenticated: true,
});
} catch (err) {
console.error(err);
throw err;
+28 -9
View File
@@ -1,7 +1,10 @@
/* eslint-disable no-console */
import Post from "@models/Feed";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
APIPath,
deleteBackendAPI,
getBackendAPI,
postBackendAPI,
putBackendAPI,
} from "./backend";
interface Options {
@@ -14,7 +17,9 @@ class FeedApi {
static getPost = async (id: number, auth?: boolean): Promise<Post> => {
try {
return await getBackendAPI<Post>({
path: APIPath.FEED, urlParams: { id }, authenticated: auth,
path: APIPath.FEED,
urlParams: { id },
authenticated: auth,
});
} catch (err) {
console.error(err);
@@ -22,7 +27,9 @@ class FeedApi {
}
};
static getFeed = async ({ limit, offset, auth }: Options = {}): Promise<Post[]> => {
static getFeed = async ({ limit, offset, auth }: Options = {}): Promise<
Post[]
> => {
try {
return await getBackendAPI<Post[]>({
path: APIPath.FEED,
@@ -40,7 +47,10 @@ class FeedApi {
static createPost = async (data: Post): Promise<Post> => {
try {
return await postBackendAPI<Post, Post>({ path: APIPath.FEED, authenticated: true }, data);
return await postBackendAPI<Post, Post>(
{ path: APIPath.FEED, authenticated: true },
data
);
} catch (err) {
console.error(err);
throw err;
@@ -49,9 +59,14 @@ class FeedApi {
static updatePost = async (data: Post): Promise<Post> => {
try {
return await putBackendAPI<Post, Post>({
path: APIPath.FEED, urlParams: { id: data.id }, authenticated: true,
}, data);
return await putBackendAPI<Post, Post>(
{
path: APIPath.FEED,
urlParams: { id: data.id },
authenticated: true,
},
data
);
} catch (err) {
console.error(err);
throw err;
@@ -60,7 +75,11 @@ class FeedApi {
static deletePost = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
await deleteBackendAPI<{ message: "OK" }>({
path: APIPath.EVENTS,
urlParams: { id },
authenticated: true,
});
} catch (err) {
console.error(err);
throw err;
+32 -11
View File
@@ -1,7 +1,10 @@
/* eslint-disable no-console */
import JobAd from "@models/JobAd";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
APIPath,
deleteBackendAPI,
getBackendAPI,
postBackendAPI,
putBackendAPI,
} from "./backend";
interface Options {
@@ -15,7 +18,9 @@ class JobAdApi {
static getJobAd = async (id: number, auth = false): Promise<JobAd> => {
try {
return await getBackendAPI({
path: APIPath.JOBADS, urlParams: { id }, authenticated: auth,
path: APIPath.JOBADS,
urlParams: { id },
authenticated: auth,
});
} catch (err) {
console.error(err);
@@ -24,7 +29,10 @@ class JobAdApi {
};
static getJobAds = async ({
since, limit, offset, auth,
since,
limit,
offset,
auth,
}: Options = {}): Promise<JobAd[]> => {
try {
return await getBackendAPI<JobAd[]>({
@@ -44,9 +52,13 @@ class JobAdApi {
static createJobAd = async (data: JobAd): Promise<JobAd> => {
try {
return await postBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS, authenticated: true,
}, data);
return await postBackendAPI<JobAd, JobAd>(
{
path: APIPath.JOBADS,
authenticated: true,
},
data
);
} catch (err) {
console.error(err);
throw err;
@@ -55,9 +67,14 @@ class JobAdApi {
static updateJobAd = async (data: JobAd): Promise<JobAd> => {
try {
return await putBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS, urlParams: { id: data.id }, authenticated: true,
}, data);
return await putBackendAPI<JobAd, JobAd>(
{
path: APIPath.JOBADS,
urlParams: { id: data.id },
authenticated: true,
},
data
);
} catch (err) {
console.error(err);
throw err;
@@ -66,7 +83,11 @@ class JobAdApi {
static deleteJobAd = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.JOBADS, urlParams: { id }, authenticated: true });
await deleteBackendAPI<{ message: "OK" }>({
path: APIPath.JOBADS,
urlParams: { id },
authenticated: true,
});
} catch (err) {
console.error(err);
throw err;
+74 -29
View File
@@ -1,20 +1,25 @@
/* eslint-disable no-console */
import { Signup, SignupForm } from "@models/Signup";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
APIPath,
deleteBackendAPI,
getBackendAPI,
postBackendAPI,
putBackendAPI,
} from "./backend";
export type EmailRequest = {
export interface EmailRequest {
mode: "all" | "actual" | "reserve";
subject: string;
content: string;
};
}
class SignupApi {
static getSignup = async (id: number): Promise<Signup> => {
try {
return await getBackendAPI<Signup>({
path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true,
path: APIPath.SIGNUPS,
urlParams: { id },
authenticated: true,
});
} catch (err) {
console.error(err);
@@ -24,9 +29,12 @@ class SignupApi {
static createSignup = async (data: Signup): Promise<Signup> => {
try {
return await postBackendAPI<Signup, Signup>({
path: APIPath.SIGNUPS,
}, data);
return await postBackendAPI<Signup, Signup>(
{
path: APIPath.SIGNUPS,
},
data
);
} catch (err) {
console.error(err);
throw err;
@@ -37,15 +45,18 @@ class SignupApi {
try {
const { id } = data;
if (!id) throw new Error("SignupId required!");
return await putBackendAPI<Signup, Signup>({
path: APIPath.SIGNUPS_EDIT,
urlParams: {
id,
return await putBackendAPI<Signup, Signup>(
{
path: APIPath.SIGNUPS_EDIT,
urlParams: {
id,
},
queryParams: {
uuid,
},
},
queryParams: {
uuid,
},
}, data);
data
);
} catch (err) {
console.error(err);
throw err;
@@ -71,7 +82,11 @@ class SignupApi {
static deleteSignup = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true });
await deleteBackendAPI<{ message: "OK" }>({
path: APIPath.SIGNUPS,
urlParams: { id },
authenticated: true,
});
} catch (err) {
console.error(err);
throw err;
@@ -81,7 +96,9 @@ class SignupApi {
static getForm = async (id: number, auth = false): Promise<SignupForm> => {
try {
return await getBackendAPI<SignupForm>({
path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: auth,
path: APIPath.SIGNUP_FORMS,
urlParams: { id },
authenticated: auth,
});
} catch (err) {
console.error(err);
@@ -92,7 +109,8 @@ class SignupApi {
static getForms = async (auth = false): Promise<SignupForm[]> => {
try {
return await getBackendAPI<SignupForm[]>({
path: APIPath.SIGNUP_FORMS, authenticated: auth,
path: APIPath.SIGNUP_FORMS,
authenticated: auth,
});
} catch (err) {
console.error(err);
@@ -102,9 +120,13 @@ class SignupApi {
static createForm = async (data: SignupForm): Promise<SignupForm> => {
try {
return await postBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS, authenticated: true,
}, data);
return await postBackendAPI<SignupForm, SignupForm>(
{
path: APIPath.SIGNUP_FORMS,
authenticated: true,
},
data
);
} catch (err) {
console.error(err);
throw err;
@@ -113,9 +135,14 @@ class SignupApi {
static updateForm = async (data: SignupForm): Promise<SignupForm> => {
try {
return await putBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS, urlParams: { id: data.id }, authenticated: true,
}, data);
return await putBackendAPI<SignupForm, SignupForm>(
{
path: APIPath.SIGNUP_FORMS,
urlParams: { id: data.id },
authenticated: true,
},
data
);
} catch (err) {
console.error(err);
throw err;
@@ -124,16 +151,30 @@ class SignupApi {
static deleteForm = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: true });
await deleteBackendAPI<{ message: "OK" }>({
path: APIPath.SIGNUP_FORMS,
urlParams: { id },
authenticated: true,
});
} catch (err) {
console.error(err);
throw err;
}
};
static signupFormSendEmail = async (data: EmailRequest, id: number): Promise<void> => {
static signupFormSendEmail = async (
data: EmailRequest,
id: number
): Promise<void> => {
try {
await postBackendAPI<EmailRequest, { message: "Email sent" }>({ path: APIPath.SIGNUP_FORMS_EMAIL, urlParams: { id }, authenticated: true }, data);
await postBackendAPI<EmailRequest, { message: "Email sent" }>(
{
path: APIPath.SIGNUP_FORMS_EMAIL,
urlParams: { id },
authenticated: true,
},
data
);
} catch (err) {
console.error(err);
throw err;
@@ -142,7 +183,11 @@ class SignupApi {
static getSignups = async (id: number): Promise<Signup[]> => {
try {
return await getBackendAPI<Signup[]>({ path: APIPath.SIGNUP_FORMS_SIGNUPS, urlParams: { id }, authenticated: true });
return await getBackendAPI<Signup[]>({
path: APIPath.SIGNUP_FORMS_SIGNUPS,
urlParams: { id },
authenticated: true,
});
} catch (err) {
console.error(err);
throw err;
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable no-console */
import Tag from "@models/Tag";
import { APIPath, getBackendAPI } from "./backend";
+1 -1
View File
@@ -74,7 +74,7 @@ const ContactCard: React.FC<ContactCardProps> = ({
src={image}
alt={name}
layout="fill"
objectFit="scale-down"
objectFit="cover"
/>
</ImageContainer>
) : null}
+9 -9
View File
@@ -14,7 +14,7 @@ const AnimatedImage = styled(Image)<{ layout: string; $delay: number }>`
animation-delay: ${(p) => p.$delay}s;
`;
const Container = styled.div<{ $animation: Keyframes; $duration: number; }>`
const Container = styled.div<{ $animation: Keyframes; $duration: number }>`
display: flex;
flex-flow: row nowrap;
@@ -37,7 +37,11 @@ const Container = styled.div<{ $animation: Keyframes; $duration: number; }>`
`;
const CrossFadeImages: React.FC<CrossFadeImagesProps> = ({
width, height, images, presentationTime, fadeTime,
width,
height,
images,
presentationTime,
fadeTime,
}) => {
const len = images.length;
const SINGLE_IMAGE_TIME = presentationTime + fadeTime;
@@ -53,7 +57,7 @@ const CrossFadeImages: React.FC<CrossFadeImagesProps> = ({
${(1 / len) * 100}% {
opacity: 0;
}
${100 - ((fadeTime / TOTAL_TIME) * 100)}% {
${100 - (fadeTime / TOTAL_TIME) * 100}% {
opacity: 0;
}
@@ -65,12 +69,8 @@ const CrossFadeImages: React.FC<CrossFadeImagesProps> = ({
const delays = images.map((_, idx) => idx * SINGLE_IMAGE_TIME).reverse();
return (
<Container
$animation={animation}
$duration={len * SINGLE_IMAGE_TIME}
>
{ images.map((image, idx) => (
// eslint-disable-next-line react/no-array-index-key
<Container $animation={animation} $duration={len * SINGLE_IMAGE_TIME}>
{images.map((image, idx) => (
<div key={idx} className={idx > 0 ? "not-first" : undefined}>
<AnimatedImage
src={image}
+7 -10
View File
@@ -1,11 +1,9 @@
import React, { useRef } from "react";
import { useRef } from "react";
import { useDrag, useDrop } from "react-dnd";
const type = "Draggable";
const Draggable = ({
id, index, handleDrag, children,
}) => {
const Draggable = ({ id, index, handleDrag, children }) => {
const ref = useRef(null); // Initialize the reference
// useDrop hook is responsible for handling whether any item gets hovered or dropped on the element
@@ -13,7 +11,8 @@ const Draggable = ({
// accept receives a definition of what must be the type of the dragged item to be droppable
accept: type,
// This method is called when we hover over an element while dragging
drop(item: { index: number }) { // item is the dragged element
drop(item: { index: number }) {
// item is the dragged element
if (!ref.current) {
return;
}
@@ -30,13 +29,13 @@ const Draggable = ({
Update the index for dragged item directly to avoid flickering
when the image was half dragged into the next
*/
// eslint-disable-next-line no-param-reassign
item.index = hoverIndex;
},
});
// useDrag will be responsible for making an element draggable. It also expose, isDragging method to add any styles while dragging
const [{ isDragging }, drag] = useDrag(() => ({
const [{ isDragging: _isDragging }, drag] = useDrag(() => ({
// what type of item this to determine if a drop target accepts it
type,
// data of the item to be available to the drop methods
@@ -53,9 +52,7 @@ const Draggable = ({
*/
drag(drop(ref));
return (
<div ref={ref}>{children}</div>
);
return <div ref={ref}>{children}</div>;
};
export default Draggable;
+201 -43
View File
@@ -1,57 +1,215 @@
/* eslint-disable react/no-invalid-html-attribute */
import React from "react";
const Icons = (): JSX.Element => (
<>
<link rel="icon" href="/favicons/favicon.ico" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicons/favicon-16x16.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicons/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="48x48" href="/favicons/favicon-48x48.png" />
<link
rel="icon"
type="image/png"
sizes="16x16"
href="/favicons/favicon-16x16.png"
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href="/favicons/favicon-32x32.png"
/>
<link
rel="icon"
type="image/png"
sizes="48x48"
href="/favicons/favicon-48x48.png"
/>
<link rel="manifest" href="/favicons/manifest.json" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#fff" />
<meta name="application-name" content="web2.0-frontend" />
<link rel="apple-touch-icon" sizes="57x57" href="/favicons/apple-touch-icon-57x57.png" />
<link rel="apple-touch-icon" sizes="60x60" href="/favicons/apple-touch-icon-60x60.png" />
<link rel="apple-touch-icon" sizes="72x72" href="/favicons/apple-touch-icon-72x72.png" />
<link rel="apple-touch-icon" sizes="76x76" href="/favicons/apple-touch-icon-76x76.png" />
<link rel="apple-touch-icon" sizes="114x114" href="/favicons/apple-touch-icon-114x114.png" />
<link rel="apple-touch-icon" sizes="120x120" href="/favicons/apple-touch-icon-120x120.png" />
<link rel="apple-touch-icon" sizes="144x144" href="/favicons/apple-touch-icon-144x144.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/favicons/apple-touch-icon-152x152.png" />
<link rel="apple-touch-icon" sizes="167x167" href="/favicons/apple-touch-icon-167x167.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/favicons/apple-touch-icon-180x180.png" />
<link rel="apple-touch-icon" sizes="1024x1024" href="/favicons/apple-touch-icon-1024x1024.png" />
<link
rel="apple-touch-icon"
sizes="57x57"
href="/favicons/apple-touch-icon-57x57.png"
/>
<link
rel="apple-touch-icon"
sizes="60x60"
href="/favicons/apple-touch-icon-60x60.png"
/>
<link
rel="apple-touch-icon"
sizes="72x72"
href="/favicons/apple-touch-icon-72x72.png"
/>
<link
rel="apple-touch-icon"
sizes="76x76"
href="/favicons/apple-touch-icon-76x76.png"
/>
<link
rel="apple-touch-icon"
sizes="114x114"
href="/favicons/apple-touch-icon-114x114.png"
/>
<link
rel="apple-touch-icon"
sizes="120x120"
href="/favicons/apple-touch-icon-120x120.png"
/>
<link
rel="apple-touch-icon"
sizes="144x144"
href="/favicons/apple-touch-icon-144x144.png"
/>
<link
rel="apple-touch-icon"
sizes="152x152"
href="/favicons/apple-touch-icon-152x152.png"
/>
<link
rel="apple-touch-icon"
sizes="167x167"
href="/favicons/apple-touch-icon-167x167.png"
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/favicons/apple-touch-icon-180x180.png"
/>
<link
rel="apple-touch-icon"
sizes="1024x1024"
href="/favicons/apple-touch-icon-1024x1024.png"
/>
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta
name="apple-mobile-web-app-status-bar-style"
content="black-translucent"
/>
<meta name="apple-mobile-web-app-title" content="web2.0-frontend" />
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-640x1136.png" />
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-750x1334.png" />
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-828x1792.png" />
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-1125x2436.png" />
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-1242x2208.png" />
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-1242x2688.png" />
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-1536x2048.png" />
<link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-1668x2224.png" />
<link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-1668x2388.png" />
<link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-2048x2732.png" />
<link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)" href="/favicons/apple-touch-startup-image-1620x2160.png" />
<link rel="apple-touch-startup-image" media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-1136x640.png" />
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-1334x750.png" />
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-1792x828.png" />
<link rel="apple-touch-startup-image" media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-2436x1125.png" />
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-2208x1242.png" />
<link rel="apple-touch-startup-image" media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-2688x1242.png" />
<link rel="apple-touch-startup-image" media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-2048x1536.png" />
<link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-2224x1668.png" />
<link rel="apple-touch-startup-image" media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-2388x1668.png" />
<link rel="apple-touch-startup-image" media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-2732x2048.png" />
<link rel="apple-touch-startup-image" media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)" href="/favicons/apple-touch-startup-image-2160x1620.png" />
<link rel="icon" type="image/png" sizes="228x228" href="/favicons/coast-228x228.png" />
<link
rel="apple-touch-startup-image"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-640x1136.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-750x1334.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-828x1792.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-1125x2436.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-1242x2208.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-1242x2688.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-1536x2048.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-1668x2224.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-1668x2388.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-2048x2732.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: portrait)"
href="/favicons/apple-touch-startup-image-1620x2160.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-1136x640.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 375px) and (device-height: 667px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-1334x750.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-1792x828.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 375px) and (device-height: 812px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-2436x1125.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 414px) and (device-height: 736px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-2208x1242.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 414px) and (device-height: 896px) and (-webkit-device-pixel-ratio: 3) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-2688x1242.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 768px) and (device-height: 1024px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-2048x1536.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 834px) and (device-height: 1112px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-2224x1668.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 834px) and (device-height: 1194px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-2388x1668.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 1024px) and (device-height: 1366px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-2732x2048.png"
/>
<link
rel="apple-touch-startup-image"
media="(device-width: 810px) and (device-height: 1080px) and (-webkit-device-pixel-ratio: 2) and (orientation: landscape)"
href="/favicons/apple-touch-startup-image-2160x1620.png"
/>
<link
rel="icon"
type="image/png"
sizes="228x228"
href="/favicons/coast-228x228.png"
/>
<meta name="msapplication-TileColor" content="#fff" />
<meta name="msapplication-TileImage" content="/favicons/mstile-144x144.png" />
<meta
name="msapplication-TileImage"
content="/favicons/mstile-144x144.png"
/>
<meta name="msapplication-config" content="/favicons/browserconfig.xml" />
<link rel="yandex-tableau-widget" href="/favicons/yandex-browser-manifest.json" />
<link
rel="yandex-tableau-widget"
href="/favicons/yandex-browser-manifest.json"
/>
</>
);
+17 -10
View File
@@ -1,8 +1,4 @@
import {
Card,
PageLink,
CardSection,
} from "@components/index";
import { Card, PageLink, CardSection } from "@components/index";
import Event from "@models/Event";
import noop from "@utils/noop";
import { Lang, getTranslateFunc } from "../../i18n";
@@ -15,10 +11,10 @@ const cardTimeOpts: Intl.DateTimeFormatOptions = {
minute: "2-digit",
};
type EventsProps = {
interface EventsProps {
events: Event[];
lang: Lang
};
lang: Lang;
}
const Events: React.FC<EventsProps> = ({ events, lang }) => {
const isFi = lang === "fi";
@@ -28,6 +24,9 @@ const Events: React.FC<EventsProps> = ({ events, lang }) => {
const pageLinkText = t("Kaikki tapahtumat");
const pageLinkDesc = `${t("löydät tapahtumakalenterista")}\xa0`;
const googleCalendarText = t("Lisää killan");
const googleCalendarDesc = `${t("Google-kalenteri")}\xa0`;
const locale = isFi ? "fi-FI" : "en-GB";
const filteredEvents = events.map((e) => ({
@@ -46,7 +45,10 @@ const Events: React.FC<EventsProps> = ({ events, lang }) => {
<Card
key={event.id}
title={event.title}
startTime={new Date(event.start_time).toLocaleString(locale, cardTimeOpts)}
startTime={new Date(event.start_time).toLocaleString(
locale,
cardTimeOpts
)}
text={event.description}
link={`/events/${event.id}`}
image={{
@@ -62,8 +64,13 @@ const Events: React.FC<EventsProps> = ({ events, lang }) => {
<PageLink to="/kilta/toiminta#tapahtumat" desc={pageLinkDesc}>
{pageLinkText}
</PageLink>
<PageLink
to="https://calendar.google.com/calendar/u/0?cid=Y19mYjhhNWUwMjVjMjhkMTg5YTkzMWYyN2U5N2M4ODBmMGFhNTdmN2M1NDFlYzVhNjdlZDM4NzliYTVhNDEwNWI1QGdyb3VwLmNhbGVuZGFyLmdvb2dsZS5jb20"
desc={googleCalendarDesc}
>
{googleCalendarText}
</PageLink>
</aside>
</CardSection>
);
};
+12 -10
View File
@@ -1,8 +1,4 @@
import {
Card,
PageLink,
CardSection,
} from "@components/index";
import { Card, PageLink, CardSection } from "@components/index";
import Post from "@models/Feed";
import noop from "@utils/noop";
import { Lang, getTranslateFunc } from "../../i18n";
@@ -15,10 +11,10 @@ const cardTimeOpts: Intl.DateTimeFormatOptions = {
minute: "2-digit",
};
type PostsProps = {
interface PostsProps {
feed: Post[];
lang: Lang
};
lang: Lang;
}
const Posts: React.FC<PostsProps> = ({ feed: posts, lang }) => {
const isFi = lang === "fi";
@@ -39,7 +35,10 @@ const Posts: React.FC<PostsProps> = ({ feed: posts, lang }) => {
title: isFi ? post.title_fi : post.title_en,
description: isFi ? post.description_fi : post.description_en,
content: isFi ? post.content_fi : post.content_en,
publish_time: new Date(post.publish_time).toLocaleString(locale, cardTimeOpts),
publish_time: new Date(post.publish_time).toLocaleString(
locale,
cardTimeOpts
),
}));
return (
@@ -59,7 +58,10 @@ const Posts: React.FC<PostsProps> = ({ feed: posts, lang }) => {
<PageLink to="/kilta/toiminta#uutiset" desc={allNewsDesc}>
{allNewsText}
</PageLink>
<PageLink to="https://static.sahkoinsinoorikilta.fi/Poytakirjat/" desc={meetingNotesDesc}>
<PageLink
to="https://static.sahkoinsinoorikilta.fi/Poytakirjat/"
desc={meetingNotesDesc}
>
{meetingNotesText}
</PageLink>
<PageLink to="https://sik.kuvat.fi" desc={galleryDesc}>
+9 -6
View File
@@ -77,20 +77,24 @@ const FooterContent: React.FC = () => (
<div>
<p>TUAS-Talo</p>
<p>Maarintie 8</p>
<p>PL 15500, 00076 Aalto</p>
</div>
<div>
<p>Y-tunnus: 1627010-1</p>
<p>hallitus@sahkoinsinoorikilta.fi</p>
<Link to="/yhteystiedot">Yhteystiedot</Link>
</div>
</div>
<div>
<Link to="https://api.sahkoinsinoorikilta.fi/members/application/">Jäseneksi</Link>
<Link to="https://api.sahkoinsinoorikilta.fi/members/application/">
Jäseneksi
</Link>
<Link to="mailto:hallitus@sahkoinsinoorikilta.fi">Palaute</Link>
<Link to="https://static.sahkoinsinoorikilta.fi">Dokumenttiarkisto</Link>
<Link to="https://static.sahkoinsinoorikilta.fi">
Dokumenttiarkisto
</Link>
<Link to="https://sik.kuvat.fi">Kuvagalleria</Link>
<Link to="https://static.sahkoinsinoorikilta.fi/logot-ja-grafiikka/">Logot ja grafiikka</Link>
<Link to="https://static.sahkoinsinoorikilta.fi/logot-ja-grafiikka/">
Logot ja grafiikka
</Link>
</div>
</Columns>
</MarginSpace>
@@ -99,7 +103,6 @@ const FooterContent: React.FC = () => (
<Map>
<iframe
title="Maarintalo 8 on Google Maps"
// 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%"
+3 -5
View File
@@ -23,14 +23,12 @@ const Container = styled.div`
}
`;
type HeroProps = {
interface HeroProps {
children: React.ReactNode;
};
}
const Hero: React.FC<HeroProps> = ({ children }) => (
<Container>
{children}
</Container>
<Container>{children}</Container>
);
export default Hero;
+18 -30
View File
@@ -18,52 +18,36 @@ interface IconProps {
const nameToIcon = (name: IconType): JSX.Element | null => {
if (name === IconType.Facebook) {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<svg role="img" viewBox="0 0 24 24" 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>
);
}
if (name === IconType.Instagram) {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<svg role="img" viewBox="0 0 24 24" 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>
);
}
if (name === IconType.LinkedIn) {
return (
<svg
role="img"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<svg role="img" viewBox="0 0 24 24" 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>
);
}
if (name === IconType.HamburgerMenu) {
return (
<svg
role="img"
viewBox="0 0 32 32"
xmlns="http://www.w3.org/2000/svg"
>
<svg role="img" viewBox="0 0 32 32" 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>
);
@@ -93,8 +77,14 @@ const nameToIcon = (name: IconType): JSX.Element | null => {
>
<title>GB flag</title>
<path fill="#012169" d="M0 0h640v480H0z" />
<path fill="#FFF" d="m75 0 244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0h75z" />
<path fill="#C8102E" d="m424 281 216 159v40L369 281h55zm-184 20 6 35L54 480H0l240-179zM640 0v3L391 191l2-44L590 0h50zM0 0l239 176h-60L0 42V0z" />
<path
fill="#FFF"
d="m75 0 244 181L562 0h78v62L400 241l240 178v61h-80L320 301 81 480H0v-60l239-178L0 64V0h75z"
/>
<path
fill="#C8102E"
d="m424 281 216 159v40L369 281h55zm-184 20 6 35L54 480H0l240-179zM640 0v3L391 191l2-44L590 0h50zM0 0l239 176h-60L0 42V0z"
/>
<path fill="#FFF" d="M241 0v480h160V0H241zM0 160v160h640V160H0z" />
<path fill="#C8102E" d="M0 193v96h640v-96H0zM273 0v480h96V0h-96z" />
</svg>
@@ -107,16 +97,14 @@ const Icon: React.FC<IconProps> = ({ link, name, onClick }) => {
const elem = nameToIcon(name);
if (link) {
return (
<a
href={link}
onClick={onClick}
>
<a href={link} onClick={onClick}>
{elem}
</a>
);
}
return (
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions
<span role="img" onClick={onClick}>
{elem}
</span>
+4 -8
View File
@@ -6,14 +6,10 @@ const Box = styled.div`
text-align: center;
`;
type InfoBoxProps = {
children?: React.ReactNode
};
interface InfoBoxProps {
children?: React.ReactNode;
}
const InfoBox: React.FC<InfoBoxProps> = ({ children }) => (
<Box>
{children}
</Box>
);
const InfoBox: React.FC<InfoBoxProps> = ({ children }) => <Box>{children}</Box>;
export default InfoBox;
+9 -4
View File
@@ -1,4 +1,3 @@
import React from "react";
import styled from "styled-components";
import colors from "@theme/colors";
@@ -15,9 +14,15 @@ const Loader = styled((props) => (
height: 1em;
@keyframes rotation {
0% { transform: rotate(0deg); }
50% { transform: rotate(180deg); }
100% { transform: rotate(360deg); }
0% {
transform: rotate(0deg);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}
& > div {
+7 -1
View File
@@ -10,16 +10,22 @@ export const renderNavigationItems = (mobile = false): JSX.Element => (
<>
<NavbarDropdownLink to="/kilta" text="Kilta " exploded={mobile}>
<NavbarChildLink to="/kilta/toiminta">Toiminta</NavbarChildLink>
<NavbarChildLink to="/kilta/fuksi">Fuksi</NavbarChildLink>
<NavbarChildLink to="/kilta/jasenyys">Jäsenyys</NavbarChildLink>
<NavbarChildLink to="/kilta/fuksi">Fukseille</NavbarChildLink>
<NavbarChildLink to="/kilta/hallitus">Hallitus</NavbarChildLink>
<NavbarChildLink to="/kilta/toimihenkilot">Toimihenkilöt</NavbarChildLink>
<NavbarChildLink to="/kilta/vuokraa">Vuokraa kalustoa</NavbarChildLink>
<NavbarChildLink to="/kilta/kunnianosoitukset">Kunnianosoitukset</NavbarChildLink>
<NavbarChildLink to="https://static.sahkoinsinoorikilta.fi">Dokumenttiarkisto</NavbarChildLink>
<NavbarChildLink to="https://sik.kuvat.fi">Kuvagalleria</NavbarChildLink>
<NavbarChildLink to="/kilta/kilta-avustus">Kilta-avustus</NavbarChildLink>
</NavbarDropdownLink>
<NavbarDropdownLink to="/opinnot_ja_ura" text="Opinnot ja ura" exploded={mobile} />
<NavbarDropdownLink to="/yritysyhteistyo" text="Yritysyhteistyö" exploded={mobile} />
<NavbarDropdownLink to="/yhteystiedot" text="Yhteystiedot" exploded={mobile}>
{/* <NavbarChildLink to="https://en.wikipedia.org/wiki/Gay">Simo Höglund</NavbarChildLink> */}
</NavbarDropdownLink>
<NavbarDropdownLink to="/yhdenvertaisuus" text="Yhdenvertaisuus" exploded={mobile} />
<NavbarDropdownLink to="/in_english" text="In English" exploded={mobile} />
</>
);
+13 -3
View File
@@ -16,6 +16,7 @@ const selectValue = (value, selected, all) => {
const deselectValue = (value, selected) => selected.filter((v) => v !== value);
type CheckboxesProps = Omit<WidgetProps, "options"> & {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
options: Record<string, any>;
};
@@ -24,7 +25,13 @@ const CheckboxContainer = styled.div`
`;
const Checkboxes: React.FC<CheckboxesProps> = ({
id, disabled, options, value, autofocus, readonly, onChange,
id,
disabled,
options,
value,
autofocus,
readonly,
onChange,
}) => {
const { enumOptions, enumDisabled, inline } = options;
return (
@@ -32,13 +39,16 @@ const Checkboxes: React.FC<CheckboxesProps> = ({
{enumOptions.map((option, index) => {
const key = `${id}_${index}`;
const checked = value.indexOf(option.value) !== -1;
const itemDisabled = enumDisabled && enumDisabled.indexOf(option.value) !== -1;
const disabledCls = disabled || itemDisabled || readonly ? "disabled" : "";
const itemDisabled =
enumDisabled && enumDisabled.indexOf(option.value) !== -1;
const disabledCls =
disabled || itemDisabled || readonly ? "disabled" : "";
const checkbox = (
<Checkbox
id={key}
checked={checked}
disabled={disabled || itemDisabled || readonly}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autofocus && index === 0}
onChange={(event) => {
const all = enumOptions.map(({ val }) => val);
@@ -41,8 +41,10 @@ const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
{enumOptions.map((option, index) => {
const key = `${id}_${index}`;
const checked = option.value === value;
const itemDisabled = enumDisabled && enumDisabled.indexOf(option.value) !== -1;
const disabledCls = disabled || itemDisabled || readonly ? "disabled" : "";
const itemDisabled =
enumDisabled && enumDisabled.indexOf(option.value) !== -1;
const disabledCls =
disabled || itemDisabled || readonly ? "disabled" : "";
const radio = (
<RadioButton
checked={checked}
@@ -50,6 +52,7 @@ const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
required={required}
value={option.value}
disabled={disabled || itemDisabled || readonly}
// eslint-disable-next-line jsx-a11y/no-autofocus
autoFocus={autofocus && index === 0}
onChange={() => onChange(option.value)}
onBlur={onBlur && ((event) => onBlur(id, event.target.value))}
@@ -2,9 +2,7 @@ import React from "react";
import Checkbox from "@components/Widgets/Checkbox/Checkbox";
import { SignupFormQuestion } from "@models/Signup";
import { Lang } from "../../../i18n";
import {
InputProps, optionTypes, SignupQuestionError,
} from "./common";
import { InputProps, optionTypes, SignupQuestionError } from "./common";
interface OptionsWidgetProps {
inputProps: InputProps;
@@ -12,67 +10,87 @@ interface OptionsWidgetProps {
}
class OptionsWidget extends React.Component<OptionsWidgetProps> {
handleListOptionsChange = (questions: SignupFormQuestion[], index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value;
const lst = val.split(";").map((p) => p.trimLeft());
handleListOptionsChange =
(
questions: SignupFormQuestion[],
index: number,
lang: Lang
): React.ChangeEventHandler<HTMLInputElement> =>
(event) => {
const { onChange } = this.props;
const val = event.target.value;
const lst = val.split(";").map((p) => p.trimLeft());
if (lang === "fi") {
// eslint-disable-next-line no-param-reassign
questions[index].options = {
...questions[index].options,
enumNames_fi: lst,
enum: lst,
};
}
if (lang === "en") {
// eslint-disable-next-line no-param-reassign
questions[index].options = {
...questions[index].options,
enumNames_en: lst,
};
}
onChange(questions);
};
if (lang === "fi") {
questions[index].options = {
...questions[index].options,
enumNames_fi: lst,
enum: lst,
};
}
if (lang === "en") {
questions[index].options = {
...questions[index].options,
enumNames_en: lst,
};
}
onChange(questions);
};
handleInfoTextOptionsChange = (questions: SignupFormQuestion[], index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value;
handleInfoTextOptionsChange =
(
questions: SignupFormQuestion[],
index: number,
lang: Lang
): React.ChangeEventHandler<HTMLInputElement> =>
(event) => {
const { onChange } = this.props;
const val = event.target.value;
if (lang === "fi") {
// eslint-disable-next-line no-param-reassign
questions[index].description_fi = val;
}
if (lang === "en") {
// eslint-disable-next-line no-param-reassign
questions[index].description_en = val;
}
onChange(questions);
};
if (lang === "fi") {
questions[index].description_fi = val;
}
if (lang === "en") {
questions[index].description_en = val;
}
onChange(questions);
};
handleIntegerOptionsChange = (questions: SignupFormQuestion[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value;
if (val !== "") {
const lst: number[] = val.split(";").map((p) => Number(p.trimStart()));
// Ignore everything else but the two first values
// eslint-disable-next-line no-param-reassign
questions[index].options.enum = lst.splice(0, 2) as unknown[] as string[];
} else {
// eslint-disable-next-line no-param-reassign
questions[index].options.enum = [];
}
handleIntegerOptionsChange =
(
questions: SignupFormQuestion[],
index: number
): React.ChangeEventHandler<HTMLInputElement> =>
(event) => {
const { onChange } = this.props;
const val = event.target.value;
if (val !== "") {
const lst: number[] = val.split(";").map((p) => Number(p.trimStart()));
// Ignore everything else but the two first values
onChange(questions);
};
questions[index].options.enum = lst.splice(
0,
2
) as unknown[] as string[];
} else {
questions[index].options.enum = [];
}
handleRequiredChange = (questions: SignupFormQuestion[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val: boolean = event.target.checked;
// eslint-disable-next-line no-param-reassign
questions[index].required = val;
onChange(questions);
};
onChange(questions);
};
handleRequiredChange =
(
questions: SignupFormQuestion[],
index: number
): React.ChangeEventHandler<HTMLInputElement> =>
(event) => {
const { onChange } = this.props;
const val: boolean = event.target.checked;
questions[index].required = val;
onChange(questions);
};
requiredField(): JSX.Element {
const { inputProps } = this.props;
@@ -89,11 +107,11 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
render(): JSX.Element {
const { inputProps } = this.props;
const {
value, type, questions, index,
} = inputProps;
const { value, type, questions, index } = inputProps;
if (!optionTypes.includes(type)) {
throw new SignupQuestionError(`Question widget type "${type}" not in types array.`);
throw new SignupQuestionError(
`Question widget type "${type}" not in types array.`
);
}
if (type === "text" || type === "email" || type === "name") {
@@ -178,7 +196,9 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
);
}
throw new SignupQuestionError(`Unrecognized question widget type "${type}"`);
throw new SignupQuestionError(
`Unrecognized question widget type "${type}"`
);
}
}
@@ -21,7 +21,10 @@ interface QuestionListProps {
onChange: (value: SignupFormQuestion[]) => void;
}
const QuestionList: React.FC<QuestionListProps> = ({ questions, onChange }): JSX.Element => {
const QuestionList: React.FC<QuestionListProps> = ({
questions,
onChange,
}): JSX.Element => {
const handleDrag = (srcIndex, dstIndex) => {
const srcCopy = { ...questions[srcIndex] };
questions.splice(srcIndex, 1);
@@ -35,18 +38,18 @@ const QuestionList: React.FC<QuestionListProps> = ({ questions, onChange }): JSX
onChange(newQuestions);
};
const handleNameInputChange = (index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const val = event.target.value;
if (lang === "fi") {
// eslint-disable-next-line no-param-reassign
questions[index].title_fi = val;
}
if (lang === "en") {
// eslint-disable-next-line no-param-reassign
questions[index].title_en = val;
}
onChange(questions);
};
const handleNameInputChange =
(index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> =>
(event) => {
const val = event.target.value;
if (lang === "fi") {
questions[index].title_fi = val;
}
if (lang === "en") {
questions[index].title_en = val;
}
onChange(questions);
};
return (
<div data-e2e="admin-signup-question">
@@ -57,21 +60,26 @@ const QuestionList: React.FC<QuestionListProps> = ({ questions, onChange }): JSX
questions,
index,
};
const optionsWidget = <OptionsWidget inputProps={inputProps} onChange={onChange} />;
const typeSelectWidget = <TypeWidget inputProps={inputProps} onChange={onChange} />;
const optionsWidget = (
<OptionsWidget inputProps={inputProps} onChange={onChange} />
);
const typeSelectWidget = (
<TypeWidget inputProps={inputProps} onChange={onChange} />
);
return (
<Draggable
key={q.id}
id={q.id}
index={index}
handleDrag={handleDrag}
>
<Draggable key={q.id} id={q.id} index={index} handleDrag={handleDrag}>
<WidgetRow>
<QuestionElement
onClick={handleElementRemove(index)}
>
<input type="text" value={q.title_fi} onChange={handleNameInputChange(index, "fi")} />
<input type="text" value={q.title_en} onChange={handleNameInputChange(index, "en")} />
<QuestionElement onClick={handleElementRemove(index)}>
<input
type="text"
value={q.title_fi}
onChange={handleNameInputChange(index, "fi")}
/>
<input
type="text"
value={q.title_en}
onChange={handleNameInputChange(index, "en")}
/>
{typeSelectWidget}
{optionsWidget}
</QuestionElement>
@@ -8,19 +8,30 @@ interface TypeWidgetProps {
}
const TypeWidget = ({ onChange, inputProps }: TypeWidgetProps): JSX.Element => {
const handleTypeChange = (questions: SignupFormQuestion[], index: number): React.ChangeEventHandler<HTMLSelectElement> => (event) => {
const val = event.target.value as SignupFormQuestion["type"];
// eslint-disable-next-line no-param-reassign
questions[index].type = val;
onChange(questions);
};
const handleTypeChange =
(
questions: SignupFormQuestion[],
index: number
): React.ChangeEventHandler<HTMLSelectElement> =>
(event) => {
const val = event.target.value as SignupFormQuestion["type"];
questions[index].type = val;
onChange(questions);
};
const { questions, type, index } = inputProps;
const options = optionTypes.map((t) => (
<option key={t} value={t}>{t}</option>
<option key={t} value={t}>
{t}
</option>
));
return (
<select onChange={handleTypeChange(questions, index)} value={type} name="type">
<select
onChange={handleTypeChange(questions, index)}
value={type}
name="type"
>
{options}
</select>
);
+7 -7
View File
@@ -1,6 +1,4 @@
import React, {
createContext, useContext, useMemo, useReducer,
} from "react";
import React, { createContext, useContext, useMemo, useReducer } from "react";
import fi from "./locales/fi/common.json";
import en from "./locales/en/common.json";
@@ -33,14 +31,14 @@ export const getTranslateFunc = (language: Lang): TranslateFunc => {
interface Store {
language: Lang;
changeLanguage: React.Dispatch<Lang>,
changeLanguage: React.Dispatch<Lang>;
}
let initialLanguage: Lang = "fi";
try {
const storedLang = localStorage.getItem(LOCAL_STORAGE_KEY) as Lang;
initialLanguage = storedLang;
} catch (err) {
} catch (_err: unknown) {
// Just ignore if fails to get value from browser (server etc.)
}
@@ -67,13 +65,15 @@ const Reducer = (state: Store, action: Lang) => {
};
const LocaleContext = createContext(initialState);
const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({
children,
}) => {
const [state, dispatch] = useReducer(Reducer, initialState);
const changeLanguage = (action: Lang) => {
dispatch(action);
try {
localStorage.setItem(LOCAL_STORAGE_KEY, action);
} catch (err) {
} catch (_err) {
// Just ignore if fails to store value in user's browser
}
};
+2
View File
@@ -15,6 +15,8 @@
"ja hallitukset kuulumiset": "and what the board has been up to",
"Kuvia tapahtumista": "Photos from events",
"kuvagalleriassa": "in the photo gallery",
"Lisää killan": "Add guild's",
"Google-kalenteri": "Google-calendar",
"Hakemaasi sivua":
"Page",
+12
View File
@@ -0,0 +1,12 @@
import * as Sentry from "@sentry/nextjs";
export const onRequestError = Sentry.captureRequestError;
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("../sentry.server.config");
}
if (process.env.NEXT_RUNTIME === "edge") {
await import("../sentry.edge.config");
}
}
+1 -4
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import styled from "styled-components";
@@ -26,9 +25,7 @@ const NotFoundPage: NextPage = () => (
<Header />
<NotFound id="not-found">
<p>
<strong>404</strong>
{" "}
| Ei&nbsp;vaan&nbsp;löydy
<strong>404</strong> | Ei&nbsp;vaan&nbsp;löydy
</p>
</NotFound>
</>
-1
View File
@@ -1,4 +1,3 @@
import React from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
+14 -6
View File
@@ -1,6 +1,9 @@
import React from "react";
import Document, {
Html, Head, Main, NextScript, DocumentContext,
Html,
Head,
Main,
NextScript,
DocumentContext,
} from "next/document";
import { ServerStyleSheet } from "styled-components";
import Favicons from "@components/Favicons";
@@ -10,9 +13,11 @@ export default class MyDocument extends Document {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
ctx.renderPage = () => originalRenderPage({
enhanceApp: (App) => (props) => sheet.collectStyles(<App {...props} />),
});
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
});
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
@@ -28,7 +33,10 @@ export default class MyDocument extends Document {
return (
<Html lang="fi">
<Head>
<link href="https://fonts.googleapis.com/css?family=Montserrat:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,800,900&display=swap" rel="stylesheet" />
<link
href="https://fonts.googleapis.com/css?family=Montserrat:100,100i,200,200i,300,300i,400,400i,500,500i,600,600i,700,800,900&display=swap"
rel="stylesheet"
/>
<Favicons />
</Head>
<body>
+34 -11
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import { toast } from "react-toastify";
@@ -19,9 +19,13 @@ const widgets = {
markdownEditor: MarkdownEditorWidget,
};
const buildSchema = (formData: Event | undefined, signupForms: SignupForm[], tags: Tag[]) => {
const date = new Date(); const
tomorrowDate = new Date();
const buildSchema = (
formData: Event | undefined,
signupForms: SignupForm[],
tags: Tag[]
) => {
const date = new Date();
const tomorrowDate = new Date();
const currentDatetime = date.toISOString();
tomorrowDate.setDate(tomorrowDate.getDate() + 1);
const tomorrowDatetime = tomorrowDate.toISOString();
@@ -29,7 +33,19 @@ const buildSchema = (formData: Event | undefined, signupForms: SignupForm[], tag
const schema = {
title: formData?.title_fi ?? "New Event",
type: "object",
required: ["title_fi", "title_en", "tags", "location_fi", "location_en", "start_time", "end_time", "description_fi", "description_en", "content_fi", "content_en"],
required: [
"title_fi",
"title_en",
"tags",
"location_fi",
"location_en",
"start_time",
"end_time",
"description_fi",
"description_en",
"content_fi",
"content_en",
],
properties: {
tags: {
type: "array",
@@ -189,21 +205,27 @@ const EventCreatePage: NextPage = () => {
const eventId = id && Number(id);
if (eventId !== undefined) {
EventApi.getEvent(eventId, true)
.then((res) => setFormData({
...res,
tags: (res.tags).map((inst) => inst.id) as any,
signupForm: (res.signupForm).map((inst) => inst.id) as any,
}))
.then((res) =>
setFormData({
...res,
tags: res.tags.map((inst) => inst.id) as unknown as Tag[],
signupForm: res.signupForm.map(
(inst) => inst.id
) as unknown as SignupForm[],
})
)
.catch((err) => setError(err.message));
}
}, [id]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onSubmit = async (data: any) => {
try {
const payload = data.formData;
payload.signup_id = payload.signupForm;
payload.tag_id = payload.tags;
if (typeof payload.image === "string" && payload.image.startsWith("http")) payload.image = undefined;
if (typeof payload.image === "string" && payload.image.startsWith("http"))
payload.image = undefined;
if (payload.id === undefined) {
const resp = await EventApi.createEvent(payload);
@@ -234,6 +256,7 @@ const EventCreatePage: NextPage = () => {
}
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onChange = (data: any) => setFormData(data.formData);
const title = formData?.id
? `Edit Event "${formData.title_fi}"`
+40 -23
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import useSWR from "swr";
import { formatRelative } from "date-fns";
import { formatISO } from "date-fns";
import { toast } from "react-toastify";
import styled from "styled-components";
import AdminListCommon from "@views/admin/AdminListCommon";
@@ -14,7 +14,7 @@ import { StyledSelect, SelectWrapper } from "@components/Select";
const URL = "/admin/events";
const StyledButton = styled(Button) <{ $colorOverride: "red" }>`
const StyledButton = styled(Button)<{ $colorOverride: "red" }>`
background-color: ${(p) => p.$colorOverride};
border-radius: 8px;
color: white;
@@ -23,12 +23,16 @@ const StyledButton = styled(Button) <{ $colorOverride: "red" }>`
`;
const confirmDelete = async (event: Event) => {
if (window.confirm(`Delete: ${event.id}: ${event.title_fi}/${event.title_en}; Are you sure?`) === true) {
if (
window.confirm(
`Delete: ${event.id}: ${event.title_fi}/${event.title_en}; Are you sure?`
) === true
) {
try {
await EventApi.deleteEvent(event.id);
toast.success("Event removed successfully 😎");
window.location.reload(); // TODO: Fetch/update event list, so user sees the signup in the list
} catch (err) {
} catch (_err: unknown) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
}
}
@@ -71,16 +75,12 @@ const Renderer: React.FC = () => {
return result;
};
useEffect(() => {
}, [sort, order, filter, events]);
// eslint-disable-next-line @typescript-eslint/no-empty-function
useEffect(() => {}, [sort, order, filter, events]);
if (error) {
console.error(error);
return (
<div>
Failed loading events.
</div>
);
return <div>Failed loading events.</div>;
}
if (!events?.length) {
@@ -117,18 +117,35 @@ const Renderer: React.FC = () => {
</tr>
</thead>
<tbody>
{events.sort(eventSort).filter(dateFilter).map((event) => (
<tr key={event.id}>
<td><Link to={`${URL}/${event.id}`}>{event.title_fi}</Link></td>
<td>{formatRelative(new Date(event.start_time), new Date())}</td>
<td>{formatRelative(new Date(event.end_time), new Date())}</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(event)}>
Delete
</StyledButton>
</td>
</tr>
))}
{events
.sort(eventSort)
.filter(dateFilter)
.map((event) => (
<tr key={event.id}>
<td>
<Link to={`${URL}/${event.id}`}>{event.title_fi}</Link>
</td>
<td>
{formatISO(new Date(event.start_time), {
representation: "date",
})}
</td>
<td>
{formatISO(new Date(event.end_time), {
representation: "date",
})}
</td>
<td>
<StyledButton
$colorOverride="red"
buttonStyle="filled"
onClick={() => confirmDelete(event)}
>
Delete
</StyledButton>
</td>
</tr>
))}
</tbody>
</table>
</div>
+18 -7
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
@@ -24,7 +24,15 @@ const buildSchema = (formData: Post, tags: Tag[]) => {
const schema = {
title: formData?.title_fi ?? "New Post",
type: "object",
required: ["title_fi", "title_en", "description_fi", "description_en", "content_fi", "content_en", "publish_time"],
required: [
"title_fi",
"title_en",
"description_fi",
"description_en",
"content_fi",
"content_en",
"publish_time",
],
properties: {
tags: {
type: "array",
@@ -112,7 +120,8 @@ const buildUISchema = (formData: Post) => {
"ui:widget": "datetime",
},
autohide: {
"ui:widget": formData && formData.autohide_enabled ? "datetime" : "hidden",
"ui:widget":
formData && formData.autohide_enabled ? "datetime" : "hidden",
},
finnish_section_divider: {
"ui:widget": "section_divider",
@@ -151,10 +160,12 @@ const FeedCreatePage: NextPage = () => {
const feedId = id && Number(id);
if (feedId !== undefined) {
FeedApi.getPost(feedId, true)
.then((res) => setFormData({
...res,
tags: (res.tags).map((inst) => inst.id) as any,
}))
.then((res) =>
setFormData({
...res,
tags: res.tags.map((inst) => inst.id) as unknown as Tag[],
})
)
.catch((err) => setError(err.message));
}
}, [id]);
+30 -20
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import useSWR from "swr";
import { formatRelative } from "date-fns";
import { formatISO } from "date-fns";
import { toast } from "react-toastify";
import styled from "styled-components";
import AdminListCommon from "@views/admin/AdminListCommon";
@@ -14,7 +14,7 @@ import { SelectWrapper, StyledSelect } from "@components/Select";
const URL = "/admin/feed";
const StyledButton = styled(Button) <{ $colorOverride: "red" }>`
const StyledButton = styled(Button)<{ $colorOverride: "red" }>`
background-color: ${(p) => p.$colorOverride};
border-radius: 8px;
color: white;
@@ -23,12 +23,16 @@ const StyledButton = styled(Button) <{ $colorOverride: "red" }>`
`;
const confirmDelete = async (post: Post) => {
if (window.confirm(`Delete: ${post.id}: ${post.title_fi}/${post.title_en}; Are you sure?`) === true) {
if (
window.confirm(
`Delete: ${post.id}: ${post.title_fi}/${post.title_en}; Are you sure?`
) === true
) {
try {
await PostApi.deletePost(post.id);
toast.success("Post removed successfully 😎");
window.location.reload(); // TODO: Fetch/update post list, so user sees the signup in the list
} catch (err) {
} catch (_err: unknown) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
}
}
@@ -43,28 +47,24 @@ const Renderer: React.FC = () => {
const feedSort = (a, b) => {
let result = 0;
if (order === "descending") {
result = new Date(b.publish_time).getTime() - new Date(a.publish_time).getTime();
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();
result =
new Date(a.publish_time).getTime() - new Date(b.publish_time).getTime();
}
return result;
};
useEffect(() => {
}, [order, feed]);
// eslint-disable-next-line @typescript-eslint/no-empty-function
useEffect(() => {}, [order, feed]);
if (error) {
console.error(error);
return (
<div>
Failed loading feed
</div>
);
return <div>Failed loading feed</div>;
}
if (!feed?.length) {
return (
<div>No posts.</div>
);
return <div>No posts.</div>;
}
return (
@@ -87,11 +87,21 @@ const Renderer: React.FC = () => {
<tbody>
{feed.sort(feedSort).map((post) => (
<tr key={post.id}>
<td><Link to={`${URL}/${post.id}`}>{post.title_fi}</Link></td>
<td>{post.description_fi}</td>
<td>{formatRelative(new Date(post.publish_time), new Date())}</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(post)}>
<Link to={`${URL}/${post.id}`}>{post.title_fi}</Link>
</td>
<td>{post.description_fi}</td>
<td>
{formatISO(new Date(post.publish_time), {
representation: "date",
})}
</td>
<td>
<StyledButton
$colorOverride="red"
buttonStyle="filled"
onClick={() => confirmDelete(post)}
>
Delete
</StyledButton>
</td>
+4 -2
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import AdminPageWrapper from "@views/common/AdminPageWrapper";
@@ -6,7 +5,10 @@ import AdminPageWrapper from "@views/common/AdminPageWrapper";
const AdminFrontPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/admin`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/admin`}
/>
</Head>
<AdminPageWrapper requiresAuthentication>
<div data-e2e="admin-front-page">
+13 -5
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
@@ -22,7 +22,17 @@ const buildSchema = (formData: JobAd) => {
const schema = {
title: formData?.title_fi ?? "New Job Ad",
type: "object",
required: ["title_fi", "title_en", "description_fi", "description_en", "content_fi", "content_en", "autohide_at", "autohide_enabled", "visible"],
required: [
"title_fi",
"title_en",
"description_fi",
"description_en",
"content_fi",
"content_en",
"autohide_at",
"autohide_enabled",
"visible",
],
properties: {
visible: {
type: "boolean",
@@ -149,9 +159,7 @@ const JobAdCreatePage: NextPage = () => {
const onChange = (data) => setFormData(data.formData);
const title = formData?.id
? `Edit Ad "${formData.title_fi}"`
: "Create Ad";
const title = formData?.id ? `Edit Ad "${formData.title_fi}"` : "Create Ad";
return (
<AdminCreateCommon
+20 -12
View File
@@ -1,7 +1,7 @@
import React from "react";
import { NextPage } from "next";
import useSWR from "swr";
import { formatRelative } from "date-fns";
import { formatISO } from "date-fns";
import { toast } from "react-toastify";
import styled from "styled-components";
import AdminListCommon from "@views/admin/AdminListCommon";
@@ -13,7 +13,7 @@ import { fetcher, APIPath, API } from "@api/backend";
const URL = "/admin/jobads";
const StyledButton = styled(Button) <{ $colorOverride: "red" }>`
const StyledButton = styled(Button)<{ $colorOverride: "red" }>`
background-color: ${(p) => p.$colorOverride};
border-radius: 8px;
color: white;
@@ -22,12 +22,16 @@ const StyledButton = styled(Button) <{ $colorOverride: "red" }>`
`;
const confirmDelete = async (jobad: JobAd) => {
if (window.confirm(`Delete: ${jobad.id}: ${jobad.title_fi}/${jobad.title_en}; Are you sure?`) === true) {
if (
window.confirm(
`Delete: ${jobad.id}: ${jobad.title_fi}/${jobad.title_en}; Are you sure?`
) === true
) {
try {
await JobAdApi.deleteJobAd(jobad.id);
toast.success("Job ad removed successfully 😎");
window.location.reload(); // TODO: Fetch/update event list, so user sees the signup in the list
} catch (err) {
} catch (_err: unknown) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
}
}
@@ -38,11 +42,7 @@ const Renderer: React.FC = () => {
const { data: jobAds, error } = useSWR<JobAd[]>(api, fetcher);
if (error) {
console.error(error);
return (
<div>
Failed loading jobads
</div>
);
return <div>Failed loading jobads</div>;
}
if (!jobAds?.length) {
return <div>No advertisements.</div>;
@@ -60,15 +60,23 @@ const Renderer: React.FC = () => {
<tbody>
{jobAds.map((ad) => (
<tr key={ad.id}>
<td><Link to={`${URL}/${ad.id}`}>{ad.title_fi}</Link></td>
<td>
<Link to={`${URL}/${ad.id}`}>{ad.title_fi}</Link>
</td>
<td>{ad.description_fi}</td>
<td>
{ad.autohide_enabled
? formatRelative(new Date(ad.autohide_at), new Date())
? formatISO(new Date(ad.autohide_at), {
representation: "date",
})
: "Disabled"}
</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(ad)}>
<StyledButton
$colorOverride="red"
buttonStyle="filled"
onClick={() => confirmDelete(ad)}
>
Delete
</StyledButton>
</td>
+5 -12
View File
@@ -1,7 +1,4 @@
import React, {
useState,
useEffect,
} from "react";
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import styled from "styled-components";
@@ -20,7 +17,7 @@ const AdminLoginPage: NextPage = () => {
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const router = useRouter();
const next = router.query.next as string || DEFAULT_REDIRECT;
const next = (router.query.next as string) || DEFAULT_REDIRECT;
useEffect(() => {
authenticate().then((authResult) => {
@@ -35,7 +32,7 @@ const AdminLoginPage: NextPage = () => {
try {
await login(username, password);
router.push(next);
} catch (err) {
} catch (_err: unknown) {
setError("Failed to log in!");
}
};
@@ -45,7 +42,7 @@ const AdminLoginPage: NextPage = () => {
<Main>
<h1>Log in to SIK Admin</h1>
{next && next !== DEFAULT_REDIRECT && (
<div className="error">You have to log in first.</div>
<div className="error">You have to log in first.</div>
)}
<form className="admin-login-form" onSubmit={handleSubmit}>
<label>
@@ -74,11 +71,7 @@ const AdminLoginPage: NextPage = () => {
</label>
<input id="login-submit" type="submit" value="Log in" />
</form>
{error && (
<div className="error">
{error}
</div>
)}
{error && <div className="error">{error}</div>}
</Main>
</AdminPageWrapper>
);
+8 -2
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
@@ -115,6 +115,7 @@ const SignupCreatePage: NextPage = () => {
.then((res) => {
setFormData({
...res,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
questions: JSON.stringify(res.questions) as any,
});
})
@@ -122,9 +123,12 @@ const SignupCreatePage: NextPage = () => {
}
}, [id]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const onSubmit = async (data: any) => {
try {
const questions: SignupFormQuestion[] = JSON.parse(data.formData.questions);
const questions: SignupFormQuestion[] = JSON.parse(
data.formData.questions
);
const payload: SignupForm = {
...data.formData,
questions,
@@ -137,6 +141,7 @@ const SignupCreatePage: NextPage = () => {
router.push("/admin/signups");
setFormData({
...resp,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
questions: JSON.stringify(resp.questions) as any,
});
} else {
@@ -145,6 +150,7 @@ const SignupCreatePage: NextPage = () => {
router.push("/admin/signups");
setFormData({
...resp,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
questions: JSON.stringify(resp.questions) as any,
});
}
+3 -8
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import { toast } from "react-toastify";
@@ -29,11 +29,7 @@ const buildSchema = (title: string) => ({
mode: {
type: "string",
title: "Send to",
enum: [
"all",
"actual",
"reserved",
],
enum: ["all", "actual", "reserved"],
default: "all",
},
},
@@ -50,8 +46,7 @@ const useInitializeData = (id: string) => {
useEffect(() => {
const formId = Number(id);
if (formId !== undefined) {
SignupApi.getForm(formId, true)
.then((res) => setSignupForm(res));
SignupApi.getForm(formId, true).then((res) => setSignupForm(res));
}
}, [id]);
+34 -18
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import { toast } from "react-toastify";
@@ -10,7 +10,7 @@ import SignupApi from "@api/signupApi";
import { Button } from "@components/index";
import noop from "@utils/noop";
const StyledButton = styled(Button) <{ $colorOverride: "red" | "green" }>`
const StyledButton = styled(Button)<{ $colorOverride: "red" | "green" }>`
background-color: ${(p) => p.$colorOverride};
border-radius: 8px;
color: white;
@@ -39,13 +39,18 @@ const SignupEmailPage: NextPage = () => {
const title = signupForm ? signupForm.title_fi : "Loading...";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const confirmDelete = async (signup: Signup, question: any) => {
if (window.confirm(`Delete: ${signup.id}: ${signup.answer[question.id]}; Are you sure?`) === true) {
if (
window.confirm(
`Delete: ${signup.id}: ${signup.answer[question.id]}; Are you sure?`
) === true
) {
try {
await SignupApi.deleteSignup(signup.id);
setSignups(signups.filter((s) => s.id !== signup.id));
toast.success("Signup removed successfully 😎");
} catch (err) {
} catch (_err: unknown) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
}
}
@@ -57,10 +62,14 @@ const SignupEmailPage: NextPage = () => {
}
// TODO: ATM we filter 'info' questions from table here. Maybe remove them from answer JSON altogether?
const questions = signupForm ? signupForm.questions.filter((q) => q.type !== "info").map((q) => ({
title: q.title_fi,
id: q.id,
})) : [];
const questions = signupForm
? signupForm.questions
.filter((q) => q.type !== "info")
.map((q) => ({
title: q.title_fi,
id: q.id,
}))
: [];
// Generate 2-dimensional array where rows are signups and columns are answers to questions.
const CSVData = signups.map((s) => questions.map((q) => s.answer[q.id]));
@@ -77,8 +86,16 @@ const SignupEmailPage: NextPage = () => {
<th key={q.id}>{q.title}</th>
))}
<th>
<CSVLink data={CSVData} headers={questions.map((q) => q.title)} separator=";">
<StyledButton $colorOverride="green" buttonStyle="filled" onClick={noop}>
<CSVLink
data={CSVData}
headers={questions.map((q) => q.title)}
separator=";"
>
<StyledButton
$colorOverride="green"
buttonStyle="filled"
onClick={noop}
>
Download CSV
</StyledButton>
</CSVLink>
@@ -89,12 +106,14 @@ const SignupEmailPage: NextPage = () => {
{signups.map((s) => (
<tr key={s.id}>
{questions.map((q) => (
<td key={`${s.id} - ${q.id}`}>
{s.answer[q.id]}
</td>
<td key={`${s.id} - ${q.id}`}>{s.answer[q.id]}</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>
@@ -107,10 +126,7 @@ const SignupEmailPage: NextPage = () => {
return (
<AdminListCommon>
<h1>
{title}
: Sign-ups
</h1>
<h1>{title}: Sign-ups</h1>
{renderData()}
</AdminListCommon>
);
+53 -26
View File
@@ -1,7 +1,7 @@
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import useSWR from "swr";
import { formatRelative } from "date-fns";
import { formatISO } from "date-fns";
import { toast } from "react-toastify";
import styled from "styled-components";
import AdminListCommon from "@views/admin/AdminListCommon";
@@ -14,7 +14,7 @@ import { SelectWrapper, StyledSelect } from "@components/Select";
const URL = "/admin/signups";
const StyledButton = styled(Button) <{ $colorOverride: "red" }>`
const StyledButton = styled(Button)<{ $colorOverride: "red" }>`
background-color: ${(p) => p.$colorOverride};
border-radius: 8px;
color: white;
@@ -23,12 +23,16 @@ const StyledButton = styled(Button) <{ $colorOverride: "red" }>`
`;
const confirmDelete = async (signup: SignupForm) => {
if (window.confirm(`Delete: ${signup.id}: ${signup.title_fi}/${signup.title_en}; Are you sure?`) === true) {
if (
window.confirm(
`Delete: ${signup.id}: ${signup.title_fi}/${signup.title_en}; Are you sure?`
) === true
) {
try {
await SignupApi.deleteForm(signup.id);
toast.success("Signup removed successfully 😎");
window.location.reload(); // TODO: Fetch/update event list, so user sees the signup in the list
} catch (err) {
} catch (_err: unknown) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
}
}
@@ -71,16 +75,12 @@ const Renderer: React.FC = () => {
return result;
};
useEffect(() => {
}, [sort, order, filter, signupForms]);
// eslint-disable-next-line @typescript-eslint/no-empty-function
useEffect(() => {}, [sort, order, filter, signupForms]);
if (error) {
console.error(error);
return (
<div>
Failed loading events.
</div>
);
return <div>Failed loading events.</div>;
}
if (!signupForms?.length) {
@@ -119,20 +119,43 @@ const Renderer: React.FC = () => {
</tr>
</thead>
<tbody>
{signupForms.sort(signupFormSort).filter(dateFilter).map((signupForm) => (
<tr key={signupForm.id}>
<td><Link to={`${URL}/${signupForm.id}`}>{signupForm.title_fi}</Link></td>
<td>{formatRelative(new Date(signupForm.start_time), new Date())}</td>
<td>{formatRelative(new Date(signupForm.end_time), new Date())}</td>
<td><Link to={`${URL}/${signupForm.id}/list`}>View</Link></td>
<td><Link to={`${URL}/${signupForm.id}/email`}>Send</Link></td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(signupForm)}>
Delete
</StyledButton>
</td>
</tr>
))}
{signupForms
.sort(signupFormSort)
.filter(dateFilter)
.map((signupForm) => (
<tr key={signupForm.id}>
<td>
<Link to={`${URL}/${signupForm.id}`}>
{signupForm.title_fi}
</Link>
</td>
<td>
{formatISO(new Date(signupForm.start_time), {
representation: "date",
})}
</td>
<td>
{formatISO(new Date(signupForm.end_time), {
representation: "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>
@@ -142,7 +165,11 @@ const Renderer: React.FC = () => {
const AdminSignupPage: NextPage = () => (
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
<AddLink
text="Create signup form"
to={`${URL}/create`}
data-e2e="create-signup"
/>
<Renderer />
</AdminListCommon>
);
+9 -6
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage, GetStaticProps, GetStaticPaths } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
@@ -21,7 +20,10 @@ const EventPage: NextPage<InitialProps> = ({ event }) => {
return (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/events/${id}`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/events/${id}`}
/>
</Head>
<PageWrapper>
<EventPageView event={event} />
@@ -36,21 +38,22 @@ export const getStaticPaths: GetStaticPaths = async () => {
params: {
id: String(e.id),
},
}
));
}));
return {
paths,
fallback: true,
};
};
export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => {
export const getStaticProps: GetStaticProps<InitialProps> = async ({
params,
}) => {
const { id } = params;
let notFound = false;
let event: Event;
try {
event = await EventApi.getEvent(Number(id));
} catch (err) {
} catch (_err: unknown) {
notFound = true;
}
return {
+9 -6
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage, GetStaticProps, GetStaticPaths } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
@@ -21,7 +20,10 @@ const FeedPage: NextPage<InitialProps> = ({ post }) => {
return (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/feed/${id}`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/feed/${id}`}
/>
</Head>
<PageWrapper>
<FeedPageView post={post} />
@@ -36,21 +38,22 @@ export const getStaticPaths: GetStaticPaths = async () => {
params: {
id: String(post.id),
},
}
));
}));
return {
paths,
fallback: true,
};
};
export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => {
export const getStaticProps: GetStaticProps<InitialProps> = async ({
params,
}) => {
const { id } = params;
let notFound = false;
let post: Post;
try {
post = await FeedApi.getPost(Number(id));
} catch (err) {
} catch (_err) {
notFound = true;
}
+14 -5
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import useSWR from "swr";
@@ -27,14 +26,24 @@ interface InitialProps {
initialFeed: Post[];
}
const InEnglishPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialFeed });
const InEnglishPage: NextPage<InitialProps> = ({
initialEvents,
initialFeed,
}) => {
const { data: events } = useSWR<Event[]>(eventApi, fetcher, {
fallbackData: initialEvents,
});
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, {
fallbackData: initialFeed,
});
return (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/in_english`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/in_english`}
/>
</Head>
<PageWrapper>
<InEnglishPageView events={events} feed={feed} />
+6 -3
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import useSWR from "swr";
@@ -27,8 +26,12 @@ interface InitialProps {
}
const FrontPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialFeed });
const { data: events } = useSWR<Event[]>(eventApi, fetcher, {
fallbackData: initialEvents,
});
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, {
fallbackData: initialFeed,
});
return (
<>
+4 -2
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import FreshmenPageView from "@views/FreshmenPage/FreshmenPageView";
@@ -7,7 +6,10 @@ import PageWrapper from "@views/common/PageWrapper";
const FreshmenPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/fuksi`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/fuksi`}
/>
</Head>
<PageWrapper>
<FreshmenPageView />
+20
View File
@@ -0,0 +1,20 @@
import { NextPage } from "next";
import Head from "next/head";
import BoardPageView from "@views/BoardPage/BoardPageView";
import PageWrapper from "@views/common/PageWrapper";
const BoardPage: NextPage = () => (
<>
<Head>
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/hallitus`}
/>
</Head>
<PageWrapper>
<BoardPageView />
</PageWrapper>
</>
);
export default BoardPage;
+4 -2
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import GuildPageView from "@views/GuildPage/GuildPageView";
@@ -7,7 +6,10 @@ import PageWrapper from "@views/common/PageWrapper";
const GuildPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta`}
/>
</Head>
<PageWrapper>
<GuildPageView />
+20
View File
@@ -0,0 +1,20 @@
import { NextPage } from "next";
import Head from "next/head";
import MembershipPageView from "@views/MembershipPage/MembershipPageView";
import PageWrapper from "@views/common/PageWrapper";
const MembershipPage: NextPage = () => (
<>
<Head>
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/jasenyys`}
/>
</Head>
<PageWrapper>
<MembershipPageView />
</PageWrapper>
</>
);
export default MembershipPage;
+20
View File
@@ -0,0 +1,20 @@
import { NextPage } from "next";
import Head from "next/head";
import FundPageView from "@views/FundPage/FundPageView";
import PageWrapper from "@views/common/PageWrapper";
const FundPage: NextPage = () => (
<>
<Head>
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/kilta-avustus`}
/>
</Head>
<PageWrapper>
<FundPageView />
</PageWrapper>
</>
);
export default FundPage;
+4 -2
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import HonoraryPageView from "@views/HonoraryPage/HonoraryPageView";
@@ -7,7 +6,10 @@ import PageWrapper from "@views/common/PageWrapper";
const HonoraryPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/kunnianosoitukset`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/kunnianosoitukset`}
/>
</Head>
<PageWrapper>
<HonoraryPageView />
+20
View File
@@ -0,0 +1,20 @@
import { NextPage } from "next";
import Head from "next/head";
import CommitteePageView from "@views/CommitteePage/CommitteePageView";
import PageWrapper from "@views/common/PageWrapper";
const CommitteePage: NextPage = () => (
<>
<Head>
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/yhteystiedot`}
/>
</Head>
<PageWrapper>
<CommitteePageView />
</PageWrapper>
</>
);
export default CommitteePage;
+10 -4
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import useSWR from "swr";
@@ -24,13 +23,20 @@ const feedApi: API = {
};
const ActualPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialFeed });
const { data: events } = useSWR<Event[]>(eventApi, fetcher, {
fallbackData: initialEvents,
});
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, {
fallbackData: initialFeed,
});
return (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/toiminta`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/toiminta`}
/>
</Head>
<PageWrapper>
<ActualPageView events={events} feed={feed} />
+20
View File
@@ -0,0 +1,20 @@
import { NextPage } from "next";
import Head from "next/head";
import RentPageView from "@views/RentPage/RentPageView";
import PageWrapper from "@views/common/PageWrapper";
const RentPage: NextPage = () => (
<>
<Head>
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/vuokraa`}
/>
</Head>
<PageWrapper>
<RentPageView />
</PageWrapper>
</>
);
export default RentPage;
+4 -2
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import StudiesPageView from "@views/StudiesPage/StudiesPageView";
@@ -7,7 +6,10 @@ import PageWrapper from "@views/common/PageWrapper";
const StudiesPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/opinnot_ja_ura`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/opinnot_ja_ura`}
/>
</Head>
<PageWrapper>
<StudiesPageView />
+16 -11
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage, GetStaticProps, GetStaticPaths } from "next";
import Head from "next/head";
import { useRouter } from "next/router";
@@ -14,9 +13,9 @@ import LoadingView from "@views/common/LoadingView";
import noop from "@utils/noop";
import NotFoundPage from "@pages/404";
type InitialProps = {
interface InitialProps {
initialForm: SignupForm;
};
}
const FORM_URL = `${process.env.NEXT_PUBLIC_API_URL}/signupForm/`;
@@ -24,7 +23,11 @@ const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
const router = useRouter();
const id = String(initialForm?.id ?? "");
const URL = `${FORM_URL}${id}/`;
const { data: signupForm, error } = useSWR<SignupForm>(URL, (url) => axios.get(url).then((res) => res.data), { fallbackData: initialForm });
const { data: signupForm, error } = useSWR<SignupForm>(
URL,
(url) => axios.get(url).then((res) => res.data),
{ fallbackData: initialForm }
);
if (error) {
console.error(error);
@@ -36,9 +39,7 @@ const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
}
if (!signupForm) {
return (
<NotFoundPage />
);
return <NotFoundPage />;
}
const onSubmit = async ({ formData }: ISubmitEvent<string>) => {
@@ -60,7 +61,10 @@ const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
return (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/signup/${signupForm.id}`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/signup/${signupForm.id}`}
/>
</Head>
<PageWrapper>
<SignUpPageView
@@ -80,15 +84,16 @@ export const getStaticPaths: GetStaticPaths = async () => {
params: {
id: String(e.id),
},
}
));
}));
return {
paths,
fallback: true,
};
};
export const getStaticProps: GetStaticProps<InitialProps> = async ({ params }) => {
export const getStaticProps: GetStaticProps<InitialProps> = async ({
params,
}) => {
const { id } = params;
let notFound = false;
let initialForm: SignupForm;
+5 -2
View File
@@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import { toast } from "react-toastify";
@@ -32,7 +32,10 @@ const useFetchSignup = (signupId: number, uuid: string) => {
return signupForm;
};
const fetchSignUp = async (id: number, uniqueId: string): Promise<Signup> => {
const fetchSignUp = async (
id: number,
uniqueId: string
): Promise<Signup> => {
const signup = await SignupApi.getSignupUUID(id, uniqueId);
setFormData(signup.answer);
return signup;
+20
View File
@@ -0,0 +1,20 @@
import { NextPage } from "next";
import Head from "next/head";
import ContactsPageView from "@views/EquityPage/EquityPageView";
import PageWrapper from "@views/common/PageWrapper";
const ContactsPage: NextPage = () => (
<>
<Head>
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/yhdenvertaisuus`}
/>
</Head>
<PageWrapper>
<ContactsPageView />
</PageWrapper>
</>
);
export default ContactsPage;
+4 -2
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import ContactsPageView from "@views/ContactsPage/ContactsPageView";
@@ -7,7 +6,10 @@ import PageWrapper from "@views/common/PageWrapper";
const ContactsPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/yhteystiedot`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/yhteystiedot`}
/>
</Head>
<PageWrapper>
<ContactsPageView />
+7 -3
View File
@@ -1,4 +1,3 @@
import React from "react";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import useSWR from "swr";
@@ -16,11 +15,16 @@ const jobAdApi: API = {
};
const CorporatePage: NextPage<InitialProps> = ({ initialJobAds }) => {
const { data: jobAds } = useSWR<JobAd[]>(jobAdApi, fetcher, { fallbackData: initialJobAds });
const { data: jobAds } = useSWR<JobAd[]>(jobAdApi, fetcher, {
fallbackData: initialJobAds,
});
return (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/yritysyhteistyo`} />
<link
rel="canonical"
href={`${process.env.NEXT_PUBLIC_SITE_URL}/yritysyhteistyo`}
/>
</Head>
<PageWrapper>
<CorporatePageView jobAds={jobAds} />
+2 -2
View File
@@ -1,4 +1,4 @@
// HTML 5 email regex
// eslint-disable-next-line import/prefer-default-export
export const EMAIL_REGEX = /^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
export const EMAIL_REGEX =
/^[a-zA-Z0-9.!#$%&*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/;
// export const EMAIL_REGEX = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+8 -4
View File
@@ -1,6 +1,10 @@
import React from "react";
import {
Hero, HeroPrimarySection, HeroSecondarySection, HeroSecondarySectionItem, HeroAside, HeroAsideItem, HeroPrimaryButtons,
Hero,
HeroPrimarySection,
HeroAside,
HeroAsideItem,
HeroPrimaryButtons,
} from "@components/Hero";
import { Link } from "@components/index";
import noop from "@utils/noop";
@@ -26,12 +30,13 @@ const ActualPageHero: React.FC = () => (
</HeroPrimarySection>
<HeroAside bgColor="lightTurquoise">
<p>
Kilta järjestää jäsenilleen jos jonkinlaista projektia ja toimintaa, muun muassa:
Kilta järjestää jäsenilleen jos jonkinlaista projektia ja toimintaa,
muun muassa:
</p>
<HeroAsideItem
header="Keksimistä ja rakentelua"
link="#elepaja"
linkText="Elektroniikkapaja&nbsp;"
linkText="SIK-Paja&nbsp;"
/>
<HeroAsideItem
header="Tiimipelejä ja liikuntaa"
@@ -54,7 +59,6 @@ const ActualPageHero: React.FC = () => (
linkText="Ulkoiset suhteet&nbsp;"
/>
</HeroAside>
</Hero>
);
+30 -15
View File
@@ -111,13 +111,13 @@ const ActualPageView: React.FC<ActualPageViewProps> = ({ events, feed }) => (
<div>
<h6 id="elepaja">Rakenna kaikkea elektroniikkaan liittyvää</h6>
<p>
Elepaja on sähköinsinöörikillan ylläpitämä elektroniikkapaja, jossa opiskelijat pääsevät soveltamaan koulussa oppimiaan taitojaan käytännön projekteissa.
SIK-PAJA on sähköinsinöörikillan ylläpitämä elektroniikkapaja, jossa opiskelijat pääsevät soveltamaan koulussa oppimiaan taitojaan käytännön projekteissa.
Opiskelijat ovat aikojen saatossa rakentaneet pajalla mitä monimuotoisempia projekteja kuten ensimmäisiä ledivilkkujaan, teslakäämejä, robotteja ja radiolähettimiä.
Jos elektroniikan rakentelu kiinnostaa tai tarvitset jonkun projektin kanssa apua niin tule ihmeessä käymään elepajalla.
Pajan varustukseen kuluu perustyökalut, piirilevyn syövytysvälineet, kolvit, komponentit, pylväsporakone sekä laaja valikoima mittauslaitteita.
Ota siis kola ja tule nauttimaan elepajan mukavasta ilmapiiristä Elepajan uusissa tiloissa kanditaattikeskuksessa ruokala alvarin alla.
Pajan varustukseen kuluu perustyökalut, kolvit, komponentit sekä laaja valikoima mittauslaitteita.
Tule tutustumaan toimintaamme Kandidaattikeskuksessa ruokala Alvarin alapuolella sijaitseviin tiloihimme.
{" "}
<Link to="https://elepaja.fi/tg">Tästä</Link> pääset liittymään elepajan Telegram-ryhmään.
<Link to="https://t.me/sikpaja">Tästä</Link> pääset liittymään pajan Telegram-ryhmään.
</p>
<h6 id="urheilu">Urheilua ja lajikokeiluja</h6>
<p>
@@ -125,29 +125,30 @@ const ActualPageView: React.FC<ActualPageViewProps> = ({ events, feed }) => (
Yksi näistä asioista on urheilun ja lajikokeiluiden tarjoaminen kiltalaisille.
Järjestämme säännöllisesti muiden kiltojen kanssa yhteistyössä mahdollisuuksia pelata esimerkiksi säbää ja muita urheilulajeja.
Jopa kesällä monien harrastuksien jäädessä tauolle, voit tulla messiin pelaamaan lentopalloa viikottain.
Lajikokeiluita on tarjolla läpi vuoden ja niitä järjestetään yhteistyössä Otaniemen eri urheilu/harrastelujärjestöjen kanssa.
Lajikokeiluita on tarjolla läpi vuoden ja niitä järjestetään yhteistyössä Otaniemen eri urheilu-/harrastelujärjestöjen kanssa.
Pidä silmät auki killan nettisivujen tapahtumaosiossa ja liity <Link to="https://t.me/joinchat/DJRXxkKd0SMj0e9pBPXF1A">Telegram-ryhmään</Link>.
Jos sinulla on ehdotuksia lajikokeiluihin, nykäise urheiluvastaavia tai hyvivointimestaria hihasta!
Jos sinulla on ehdotuksia lajikokeiluihin, nykäise liikuntavastaavia tai hyvivointimestaria hihasta!
</p>
<h6 id="kulttuuri&juhla">Kulttuuria ja juhlia teatterista sitseihin</h6>
<p>
Hyvinvointitoimikunta järjestää urheilun ja lajikokeilun lisäksi myös kultturelleja tapahtumia ja menoja kiltalaisille.
Hyvinvointitoimikunta järjestää urheilun ja lajikokeilujen lisäksi myös kultturelleja tapahtumia ja menoja kiltalaisille.
Näihin kultturelleihin tapahtumiin kuuluu hauskaa laidasta laitaan, eli keittiöstä teatteriin ja teatterista mitä mielenkiintoimpiin museoihin.
Lisäksi ohjelmatoimikunta viihdyttää kiltalaisia erilaisilla juhlilla rennoista saunailloista juhlavimpiin sitseihin.
Lisäksi hupitoimikunta viihdyttää kiltalaisia erilaisilla juhlilla rennoista saunailloista juhlavimpiin sitseihin.
Killan nettisivujen <Link to="#tapahtumat">Tapahtumat</Link>-osiosta voit tutkia tulevia kulttuuritapahtumia.
</p>
<h6 id="yritysyhteistyo">Yhteistyö yritysten kanssa</h6>
<p>
Killassa toimiva yritystoimikunta vastaa siitä, että killan talous pysyy pystyssä, mutta tämän lisäksi he myös tarjoavat kiltalaisille mahdollisuuksia solmia suhteita alamme huippuyritysten kanssa.
Tällaisia mahdollisuuksia järjestetään excujen muodossa, joissa kiltalaiset usein pääsevät yrityksen omiin tiloihin tutustumaan yrityksen toimintaan ja henkilökuntaan, sekä erilaisten Otaniemessä järjestettävien yrityssuhdetapahtumien muodossa.
Killassa toimiva yrityssuhdetoimikunta vastaa siitä, että killan talous pysyy pystyssä, mutta tämän lisäksi he myös tarjoavat kiltalaisille mahdollisuuksia solmia suhteita alamme huippuyritysten kanssa.
Tällaisia mahdollisuuksia järjestetään excursioiden muodossa, joissa kiltalaiset usein pääsevät yrityksen omiin tiloihin tutustumaan yrityksen toimintaan ja henkilökuntaan,
sekä erilaisten Otaniemessä järjestettävien yrityssuhdetapahtumien muodossa.
Otaniemi-yritystapahtumia ovat esimerkiksi yrityksien kanssa yhteistyössä järjestetyt saunaillat, sekä jokavuotinen yritysbrunssi.
Ilmottautumiset näihin tapahtumiin onnistuvat <Link to="#tapahtumat">Tapahtumat</Link>-osiosta killan nettisivuilta.
</p>
<h6 id="ulkosuhteet">Kansainvälisty ja luo suhteita</h6>
<p>
Ulkotoimikunta järjestää kiltalaisten iloksi tapahtumia monien ystävyysjärjestöjen kanssa niin suomessa kuin ulkomaillakin.
UTMK:n järjestämissä tapahtumissa pääset kasvattamaan ystäväpiiriäsi Otaniemen ulkopuolelle ja jopa kansainvälistymään toden teolla.
UTMK järjestää paljon toimintaa myös vaihto-opiskelijoille ja näihin tapahtumiin kannattaa ehdottomasti osallistua, jos tahtoo luoda ystävyyssuhteita ympäri maailman.
Ulkosuhdevastaavat järjestävät kiltalaisten iloksi tapahtumia monien ystävyysjärjestöjen kanssa niin Suomessa kuin ulkomaillakin.
issä tapahtumissa pääset kasvattamaan ystäväpiiriäsi Otaniemen ulkopuolelle ja jopa kansainvälistymään toden teolla.
Kilta järjestää paljon toimintaa myös vaihto-opiskelijoille ja näihin tapahtumiin kannattaa ehdottomasti osallistua, jos tahtoo luoda ystävyyssuhteita ympäri maailman.
</p>
</div>
</div>
@@ -159,9 +160,23 @@ const ActualPageView: React.FC<ActualPageViewProps> = ({ events, feed }) => (
<p>Kuinka pääset kiltatoimintaan mukaan?</p>
<div>
<h6>Kiltakokous</h6>
<p>Kiltakokous on killan ylintä toimivaltaa käyttävä elin, joka koostuu kaikista killan varsinaisista jäsenistä. Kiltakokouksen tehtävänä on valvoa hallituksen toimintaa ja päättää kaikkia kiltalaisia koskevista asioista. Kevään kiltakokouksessa hyväksytään toimintasuunnitelma ja talousarvio sekä annetaan vanhalle hallitukselle vastuunvapautus, mikäli tilinpäätös ja toimintakertomus hyväksytään. Syksyn kiltakokous on moniosainen, jonka 1. osassa valitaan hallituksen muodostaja. 2. osassa valitaan hallitus ja 3. osassa valitaan toimihenkilöt. Tämän kokouksen jälkeen killalla on kaikki toimijat valittuna seuraavalle vuodelle. Tämän lisäksi voidaan pitää ylimääräisiä kokouksia, jos hallitus, yleinen kokous tai vähintään 20 kiltalaista sitä kannattaa. Killan sääntöihin voit tutustua tarkemmin <Link to="https://static.sahkoinsinoorikilta.fi/saannot/killansaannot.pdf">täältä.</Link></p>
<p>
Kiltakokous on killan ylintä toimivaltaa käyttävä elin, joka koostuu kaikista killan varsinaisista jäsenistä.
Kiltakokouksen tehtävänä on valvoa hallituksen toimintaa ja päättää kaikkia kiltalaisia koskevista asioista.
Kevään kiltakokouksessa hyväksytään toimintasuunnitelma ja talousarvio sekä annetaan vanhalle hallitukselle vastuunvapautus, mikäli tilinpäätös ja toimintakertomus hyväksytään.
Syksyn kiltakokous on moniosainen, jonka 1. osassa valitaan puheenjohtaja, 2. osassa valitaan hallitus ja 3. osassa valitaan toimihenkilöt.
Tämän kokouksen jälkeen killalla on kaikki toimijat valittuna seuraavalle vuodelle.
Tämän lisäksi voidaan pitää ylimääräisiä kokouksia, jos hallitus, yleinen kokous tai vähintään 20 kiltalaista sitä kannattaa.
Killan sääntöihin voit tutustua tarkemmin <Link to="https://static.sahkoinsinoorikilta.fi/saannot/killansaannot.pdf">täältä.</Link>
</p>
<h6>Kähmyt</h6>
<p>Killan kähmykaudella voit osoittaa kiinnostuksesi erilaisiin kiltarooleihin kähmyämällä kähmykoneen kautta. Kähmykausi käynnistyy alkusyksystä ja kestää syksyn 3. kiltakokoukseen asti, jossa kiltalaiset äänestävät ensivuoden toimihenkilöt. Hallitusvirkaan pyrkiessä täytyy kähmyäminen tehdä syksyn 2. kiltakokoukseen mennessä. Kähmyttäessäsi voit vapaasti valita tai keksiä roolin ja pyrkiä hallitukseen tai toimihenkilöksi. Muista kuitenkin, että kähmyäminen ei ole sitova killan tehtäviin vaan enemmänkin mielenkiinnon osoitus.</p>
<p>
Killan kähmykaudella voit osoittaa kiinnostuksesi erilaisiin kiltarooleihin kähmyämällä kähmykoneen kautta.
Kähmykausi käynnistyy alkusyksystä ja kestää syksyn 3. kiltakokoukseen asti, jossa kiltalaiset äänestävät seuraavan vuoden toimihenkilöt.
Hallitusvirkaan pyrkiessä täytyy kähmyäminen tehdä syksyn 2. kiltakokoukseen mennessä.
Kähmyttäessäsi voit vapaasti valita tai keksiä roolin ja pyrkiä hallitukseen tai toimihenkilöksi.
Muista kuitenkin, että kähmyäminen ei ole sitova killan tehtäviin vaan enemmänkin mielenkiinnon osoitus.
</p>
</div>
</div>
</TextSection>
+134
View File
@@ -0,0 +1,134 @@
import React from "react";
import styled from "styled-components";
import { TextSection, Link } from "@components/index";
import colors from "@theme/colors";
import ContactCard from "@components/ContactCard";
import BoardJson from "./board.json";
const orderedCommittees = [BoardJson];
const blankProfile = "/img/blank_profile.png";
const BlueLink = styled(Link)`
color: ${colors.blue1};
&:hover {
color: ${colors.lightBlue};
}
`;
const Container = styled.div`
color: ${colors.darkBlue};
align-items: center;
justify-content: center;
width: 50vw;
& > h2 {
text-transform: uppercase;
font-size: 4rem;
width: 100%;
}
& > div {
display: flex;
flex-flow: row wrap;
}
@media (max-width: 950px) {
width: 100vw;
}
`;
const ContactContainer = styled.div`
overflow-x: hidden;
@media (max-width: 950px) {
margin-top: 0;
}
`;
const CommitteeContainer: React.FC<{
committee: Committee;
children: React.ReactNode;
}> = ({ committee, children }) => (
<Container>
<div>
{committee.roles.map((role) =>
role.representatives.map((representative) => (
<ContactCard
key={representative.name}
name={representative.name}
phone={representative.phone_number}
email={representative.email}
image={representative.image || blankProfile}
role_fi={role.name_fi}
role_en={role.name_en}
/>
))
)}
</div>
{children}
</Container>
);
interface Committee {
name_fi: string;
name_en: string;
roles: Role[];
}
interface Role {
name_fi: string;
name_en: string;
representatives: Representative[];
}
interface Representative {
name: string;
phone_number?: string;
email?: string;
image?: string;
}
const BoardPageView: React.FC = () => (
<>
<TextSection>
<h1>Hallitus</h1>
<div>
<p>Tältä sivulta löydät killan hallituksen jäsenten yhteystiedot.</p>
<p>
{
"Koko hallitukseen saa yhteyden lähettämällä sähköpostia osoitteeseen "
}
<BlueLink to="mailto:hallitus@sahkoinsinoorikilta.fi">
hallitus@sahkoinsinoorikilta.fi
</BlueLink>
.
</p>
<p>
Muut yhteystiedot löydät <Link to="/yhteystiedot">täältä.</Link>
</p>
<p>
{"Hallitukselle voi myös lähettää palautetta täyttämällä "}
<BlueLink to="https://docs.google.com/forms/d/e/1FAIpQLSeD8Hm66uvwr7Xa2WGgOCfI2RS1NrZsmISf2QBKUcJf_stv8g/viewform?usp=sf_link">
palautelomakkeen
</BlueLink>
. Lomakkeen vastauksia käydään läpi hallituksen kokouksissa.
</p>
</div>
</TextSection>
<ContactContainer>
{orderedCommittees.map((json) => (
<React.Fragment key={json.slug}>
<TextSection id={json.slug}>
<CommitteeContainer committee={json}>
{json.slug === "board"}
</CommitteeContainer>
</TextSection>
</React.Fragment>
))}
</ContactContainer>
</>
);
export default BoardPageView;
@@ -1,6 +1,6 @@
{
"slug": "board",
"name_fi": "Hallitus",
"name_fi": "Hallitus 2024",
"name_en": "Board",
"roles": [
{
@@ -8,22 +8,22 @@
"name_en": "Chairman of the Board",
"representatives": [
{
"name": "Otto Julkunen",
"name": "Emma Uusküla",
"phone_number": null,
"email": "otto.julkunen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "emma.uuskula@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Emma.jpg"
}
]
},
{
"name_fi": "Sihteeri",
"name_en": "Secretary",
"name_fi": "Varapuheenjohtaja",
"name_en": "Vice Chair",
"representatives": [
{
"name": "Karoliina Talvikangas",
"name": "Johannes Viirimäki",
"phone_number": null,
"email": "karoliina.talvikangas@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "johannes.viirimaki@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Johannes.jpg"
}
]
},
@@ -32,22 +32,22 @@
"name_en": "Treasurer",
"representatives": [
{
"name": "Ville Lairila",
"name": "Nelli Liljasto",
"phone_number": null,
"email": "ville.lairila@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "nelli.liljasto@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Nelli.jpg"
}
]
},
{
"name_fi": "Fuksitoimikunnan Puheenjohtaja",
"name_fi": "Fuksitoimikunnan puheenjohtaja",
"name_en": "",
"representatives": [
{
"name": "Aaron Löfgren",
"name": "Teemu Heikkinen",
"phone_number": null,
"email": "aaron.lofgren@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "teemu.heikkinen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Teemu.jpg"
}
]
},
@@ -56,10 +56,10 @@
"name_en": "",
"representatives": [
{
"name": "Kasper Skog",
"name": "Henri Aito",
"phone_number": null,
"email": "kasper.skog@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "henri.aito@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Henri.jpg"
}
]
},
@@ -68,10 +68,10 @@
"name_en": "",
"representatives": [
{
"name": "Roni Vallius",
"name": "Tuomas Rantamäki",
"phone_number": null,
"email": "roni.vallius@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "tuomas.rantamaki@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/TuomasR.jpg"
}
]
},
@@ -80,10 +80,10 @@
"name_en": "",
"representatives": [
{
"name": "Elina Huttunen",
"name": "Matilda Ahonen",
"phone_number": null,
"email": "elina.huttunen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "matilda.ahonen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Matilda.jpg"
}
]
},
@@ -92,10 +92,10 @@
"name_en": "",
"representatives": [
{
"name": "Julia Pykälä-aho",
"name": "Niklas Ritalahti",
"phone_number": null,
"email": "julia.pykalaaho@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "niklas.ritalahti@sahkoinsinoorikilta.fi",
"image": ""
}
]
},
@@ -104,34 +104,34 @@
"name_en": "",
"representatives": [
{
"name": "Juulia Härkönen",
"name": "Mikael Vatiainen",
"phone_number": null,
"email": "juulia.harkonen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "mikael.vatiainen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Mikael.jpg"
}
]
},
{
"name_fi": "Pajamestari",
"name_fi": "Teknologiamestari",
"name_en": "",
"representatives": [
{
"name": "Tommi Sytelä",
"name": "Simeon Pursiainen",
"phone_number": null,
"email": "tommi.sytela@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "simeon.pursiainen@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Simeon.jpg"
}
]
},
{
"name_fi": "Ulkomestari",
"name_fi": "KV-fuksikapteeni",
"name_en": "",
"representatives": [
{
"name": "Pyry Vaara",
"name": "Markus Aaltio",
"phone_number": null,
"email": "pyry.vaara@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "markus.aaltio@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Markus.jpg"
}
]
},
@@ -140,22 +140,22 @@
"name_en": "",
"representatives": [
{
"name": "Nette Levijoki",
"name": "Tuomas Hintikka",
"phone_number": null,
"email": "nette.levijoki@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "tuomas.hintikka@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/TuomasH.jpg"
}
]
},
{
"name_fi": "Excursiomestari",
"name_fi": "Viestintämestari",
"name_en": "",
"representatives": [
{
"name": "Visa Kurvi",
"name": "Yassine Ramid",
"phone_number": null,
"email": "visa.kurvi@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/placeholder.jpg"
"email": "yassine.ramid@sahkoinsinoorikilta.fi",
"image": "https://static.sahkoinsinoorikilta.fi/img/board/Yassine.jpg"
}
]
}
@@ -0,0 +1,211 @@
import React from "react";
import styled from "styled-components";
import { Divider, TextSection, Link } from "@components/index";
import colors from "@theme/colors";
import ContactCard from "@components/ContactCard";
import FtmkJson from "./ftmk.json";
import HtmkJson from "./htmk.json";
import HvtmkJson from "./hvtmk.json";
import MtmkJson from "./mtmk.json";
import OptmkJson from "./optmk.json";
import NtmkJson from "./ntmk.json";
import PtmkJson from "./ptmk.json";
import TtmkJson from "./ttmk.json";
import YtmkJson from "./ytmk.json";
import SwtmkJson from "./swtmk.json";
import VtmkJson from "./vtmk.json";
import LtmkJson from "./ltmk.json";
import Others from "./others.json";
const orderedCommittees = [
FtmkJson,
HtmkJson,
LtmkJson,
HvtmkJson,
MtmkJson,
OptmkJson,
YtmkJson,
TtmkJson,
PtmkJson,
VtmkJson,
SwtmkJson,
NtmkJson,
Others,
];
const BlueLink = styled(Link)`
color: ${colors.blue1};
&:hover {
color: ${colors.lightBlue};
}
`;
const IndexUL = styled.ul`
padding: 0;
list-style: none;
li::before {
content: attr(data-icon);
margin-right: 4px;
}
`;
const Index: React.FC<{ committees: typeof orderedCommittees }> = ({
committees,
}) => (
<IndexUL>
{committees.map(({ slug, name_fi }) => (
<BlueLink to={`#${slug}`} key={slug}>
<li data-icon="»">{name_fi}</li>
</BlueLink>
))}
</IndexUL>
);
const Container = styled.div`
color: ${colors.darkBlue};
align-items: center;
justify-content: center;
width: 50vw;
& > h2 {
text-transform: uppercase;
font-size: 4rem;
width: 100%;
}
& > div {
display: flex;
flex-flow: row wrap;
}
@media (max-width: 950px) {
width: 100vw;
}
`;
const ContactContainer = styled.div`
overflow-x: hidden;
@media (max-width: 950px) {
margin-top: 0;
}
`;
const TitleContainer = styled.div`
display: flex;
width: 100%;
align-items: center;
justify-content: center;
padding: 10px 10px;
flex-direction: column;
margin: auto;
`;
const CommitteeContainer: React.FC<{
committee: Committee;
children: React.ReactNode;
}> = ({ committee, children }) => (
<Container>
<TitleContainer>
<h2>{committee.name_fi || committee.name_en}</h2>
</TitleContainer>
<p>{committee.info}</p>
<div>
{committee.roles.map((role) =>
role.representatives.map((representative) => (
<ContactCard
key={representative.name}
name={representative.name}
phone={representative.phone_number}
email={representative.email}
image={null}
role_fi={role.name_fi}
role_en={role.name_en}
/>
))
)}
</div>
{children}
</Container>
);
interface Committee {
name_fi: string;
name_en: string;
info: string;
roles: Role[];
}
interface Role {
name_fi: string;
name_en: string;
representatives: Representative[];
}
interface Representative {
name: string;
phone_number?: string;
email?: string;
image?: string;
}
const CommitteePageView: React.FC = () => (
<>
<TextSection>
<h1>Toimihenkilöt</h1>
<p>
Tältä sivulta löytyvät killan toimihenkilöt sekä lyhyet kuvaukset
toimikunnista.
<br />
<br />
Toimihenkilöiden sähköpostiosoitteet ovat muotoa
etunimi.sukunimi@sahkoinsinoorikilta.fi.
</p>
<aside>
<div>
<h6>Toimikuntaluettelo</h6>
<Index committees={orderedCommittees} />
</div>
</aside>
</TextSection>
<ContactContainer>
{orderedCommittees.map((json) => (
<React.Fragment key={json.slug}>
{json.slug !== "board" && <Divider />}
<TextSection id={json.slug}>
<CommitteeContainer committee={json}>
{json.slug === "board" && (
<div>
<p>
{
"Koko hallitukseen saa yhteyden lähettämällä sähköpostia osoitteeseen "
}
<BlueLink to="mailto:hallitus@sahkoinsinoorikilta.fi">
hallitus@sahkoinsinoorikilta.fi
</BlueLink>
.
</p>
<p>
{"Hallitukselle voi myös lähettää palautetta täyttämällä "}
<BlueLink to="https://docs.google.com/forms/d/e/1FAIpQLSeD8Hm66uvwr7Xa2WGgOCfI2RS1NrZsmISf2QBKUcJf_stv8g/viewform?usp=sf_link">
palautelomakkeen
</BlueLink>
. Lomakkeen vastauksia käydään läpi hallituksen kokouksissa.
</p>
<p>
Toimihenkilöiden sähköpostiosoitteet ovat muotoa
etunimi.sukunimi@sahkoinsinoorikilta.fi.
</p>
</div>
)}
</CommitteeContainer>
</TextSection>
</React.Fragment>
))}
</ContactContainer>
</>
);
export default CommitteePageView;
+56
View File
@@ -0,0 +1,56 @@
{
"slug": "ftmk",
"name_fi": "Fuksitoimikunta",
"name_en": "",
"info": "Fuksitoimikuntaan kuuluu fuksikapteenit, KV-kapteenit, ISOvastaava sekä KV-ISOvastaava. Fuksitoimikunta huolehtii fukseista ja fukseille annettavasta fuksikasvatuksesta. ISOvastaava ja KV-ISOvastaava ovat taas vastuussa killan ISOhenkilöistä ja heidän ohjaamisestaan.",
"roles": [
{
"name_fi": "Fuksitoimikunnan puheenjohtaja",
"name_en": "",
"representatives": [
{
"name": "Teemu Heikkinen"
}
]
},
{
"name_fi": "Fuksitoimikunnan puheenjohtajan adjutantti",
"name_en": "",
"representatives": [
{
"name": "Henri Aito"
}
]
},
{
"name_fi": "KV-fuksikapteeni",
"name_en": "International Fuksi Captain",
"representatives": [
{
"name": "Markus Aaltio"
},
{
"name": "Apollo Ailus"
}
]
},
{
"name_fi": "ISOvastaava",
"name_en": "Tutor Coordinator",
"representatives": [
{
"name": "Axel Aurola"
}
]
},
{
"name_fi": "KV-ISOvastaava",
"name_en": "International Tutor Coordinator",
"representatives": [
{
"name": "Igor Oinonen"
}
]
}
]
}
+56
View File
@@ -0,0 +1,56 @@
{
"slug": "htmk",
"name_fi": "Hupitoimikunta",
"name_en": "Entertainment Committee",
"info": "Hupitoimikunta järjestää päätoimenaan kaikenkirjavia tapahtumia, kuten sitsejä, saunailtoja sekä muita juhlia. Hupitoimikuntaa johtaa Hovimestari ja Hovineuvos. Toimikunnassa toimii Hovin lisäksi emäntiä ja isäntiä, jotka hoitavat juhlien käytännön järjestelyjä, esimerkiksi ruoanlaiton, kattauksen ja tarjoilun Hovin johdolla.",
"roles": [
{
"name_fi": "Hovimestari",
"name_en": "Master of Ceremonies",
"representatives": [
{
"name": "Tuomas Rantamäki"
}
]
},
{
"name_fi": "Hovineuvos",
"name_en": "Court Counsellor",
"representatives": [
{
"name": "Matilda Ahonen"
}
]
},
{
"name_fi": "Emäntä",
"name_en": "Hostess",
"representatives": [
{
"name": "Veera Lindroos"
},
{
"name": "Aino Saarela"
},
{
"name": "Nea Kanerva"
},
{
"name": "Rosanna Reims"
}
]
},
{
"name_fi": "Isäntä",
"name_en": "Host",
"representatives": [
{
"name": "Eemeli Hintsanen"
},
{
"name": "André Palosaari"
}
]
}
]
}
+99
View File
@@ -0,0 +1,99 @@
{
"slug": "hvtmk",
"name_fi": "Hyvinvointitoimikunta",
"name_en": "Committee of Wellbeing",
"info": "Hyvinvointitoimikunta järjestää monipuolisesti kiltalaisten hyvinvointia edistävää hyvän mielen toimintaa. Toimikunta koostuu liikunta-, retkeily-, kulttuuri- ja kiltahuonevastaavista, ja toimikuntaa johtaa hyvinvointimestari.",
"roles": [
{
"name_fi": "Hyvinvointimestari",
"name_en": "Master of Wellbeing",
"representatives": [
{
"name": "Niklas Ritalahti"
}
]
},
{
"name_fi": "Kulttuurivastaava",
"name_en": "Culture Representative",
"representatives": [
{
"name": "Peter Lindahl"
},
{
"name": "Kuura Janhunen"
},
{
"name": "Valentin Juhela"
},
{
"name": "Leevi Leinonen"
},
{
"name": "Milla Heino"
},
{
"name": "Hocine Montenez"
}
]
},
{
"name_fi": "Liikuntavastaava",
"name_en": "Sports Representative",
"representatives": [
{
"name": "Matias Hendolin"
},
{
"name": "Sauli Hakala"
}
]
},
{
"name_fi": "Kiltahuonevastaava",
"name_en": "Guild Room Representative",
"representatives": [
{
"name": "Justus Ojala"
},
{
"name": "Aaro Rasilainen"
}
]
},
{
"name_fi": "Retkeilyvastaava",
"name_en": "",
"representatives": [
{
"name": "Tommi Sytelä"
},
{
"name": "Konsta Hakala"
},
{
"name": "Ville Lairila"
}
]
},
{
"name_fi": "Yhdenvertaisuusvastaava",
"name_en": "",
"representatives": [
{
"name": "Saara Rossi"
},
{
"name": "Aaron Löfgren"
},
{
"name": "Milla Heino"
},
{
"name": "Sauli Hakala"
}
]
}
]
}
+62
View File
@@ -0,0 +1,62 @@
{
"slug": "ltmk",
"name_fi": "Lukkaritoimikunta",
"name_en": "",
"info": "Lukkaritoimikunta on vastuussa killan laulukulttuurin kehittämisestä sekä ylläpitämisestä. Toimikunnan muodostaa lukkarimestari, lukkarit sekä lukkarikisällit. Meidät tapaat sitseillä sekä muissa tapahtumissa muistuttamassa, että teekkari laulaa mieluummin kuin hyvin.",
"roles": [
{
"name_fi": "Lukkarimestari",
"name_en": "",
"representatives": [
{
"name": "Leevi Oikarinen"
}
]
},
{
"name_fi": "Lukkari",
"name_en": "",
"representatives": [
{
"name": "Aino Salmi"
},
{
"name": "Ilmari Reponen"
},
{
"name": "Jenni Marttinen"
},
{
"name": "Peter Lindahl"
},
{
"name": "Patrik Varteva"
},
{
"name": "Tapio Immonen"
}
]
},
{
"name_fi": "Lukkarikisälli",
"name_en": "",
"representatives": [
{
"name": "Alex Hyytinen"
},
{
"name": "Antti Salpakari"
},
{
"name": "Iiris Kuulusa"
},
{
"name": "Roman Shalamov"
},
{
"name": "Samuel Södervall"
}
]
}
]
}
+104
View File
@@ -0,0 +1,104 @@
{
"slug": "mtmk",
"name_fi": "Sössö-toimikunta",
"name_en": "Media Committee",
"info": "Sössö-toimikunta toimittaa Sössöä, Sähköinsinöörikillan ikiomaa lehteä, joka on ikänsä ja laatunsa puolesta Otaniemen eliittiä. Toimikunta julkaisee vuodessa kaksi painettua lehteä sekä lukuisia nettiartikkeleita ynnä muuta. Toimikunta hoitaa lisäksi myös valokuvat ja live-striimit.",
"roles": [
{
"name_fi": "Päätoimittaja",
"name_en": "Editor in Chief",
"representatives": [
{
"name": "Topi Manskinen",
"phone_number": null,
"email": null,
"image": null
}
]
},
{
"name_fi": "Tyhjäntoimittaja",
"name_en": "",
"representatives": [
{
"name": "Visa Kurvi",
"phone_number": null,
"email": null,
"image": null
}
]
},
{
"name_fi": "Toimittaja",
"name_en": "Journalist",
"representatives": [
{
"name": "Joona Komonen"
},
{
"name": "Olli Vaismaa"
},
{
"name": "Jenni Marttinen"
},
{
"name": "Ilmari Reponen"
},
{
"name": "Igor Oinonen"
},
{
"name": "Otto Kievimaa"
}
]
},
{
"name_fi": "Toimittaja, Taittaja",
"name_en": "",
"representatives": [
{
"name": "Atte Vitie"
}
]
},
{
"name_fi": "Taittaja",
"name_en": "",
"representatives": [
{
"name": "Otto Kievimaa"
}
]
},
{
"name_fi": "Graafikko",
"name_en": "Photographer & Graphic Artist",
"representatives": [
{
"name": "Elian Salmimaa"
}
]
},
{
"name_fi": "Valokuvaaja",
"name_en": "Photographer",
"representatives": [
{
"name": "Veikko Räty"
},
{
"name": "Into Saarinen"
},
{
"name": "Aaro Rasilainen"
},
{
"name": "Anton Niemi"
},
{
"name": "Veera Melvasalo"
}
]
}
]
}
+97
View File
@@ -0,0 +1,97 @@
{
"slug": "ntmk",
"name_fi": "N-Toimikunta",
"name_en": "",
"info": "N-toimikunta järjestää erinäisiä tapahtumia vanhemmille ja vanhemmanmielisille kiltalaisille, kuten sitsejä, aftereita, ulkoilutapahtumia ja mitä ikinä keksitäänkään. N-toimikunta toimii myös matalan kynnyksen välinä Sklubiin, eli alumniyhdistykseemme. N-toimikuntaan kuuluu myös killan kiltapatruunat, jotka pitävät huolta killan jatkuvuudesta.",
"roles": [
{
"name_fi": "N-toimikunnan nestori",
"name_en": "",
"representatives": [
{
"name": "Karoliina Talvikangas"
}
]
},
{
"name_fi": "N-toimikunnan varanestori, Kiltapatruuna",
"name_en": "",
"representatives": [
{
"name": "Aaron Löfgren"
}
]
},
{
"name_fi": "Sklubi-yhdyshenkilö",
"name_en": "",
"representatives": [
{
"name": "Melisa Dönmez"
},
{
"name": "Eveliina Ahonen"
}
]
},
{
"name_fi": "Kiltapatruuna",
"name_en": "",
"representatives": [
{
"name": "Ville Lairila"
},
{
"name": "Visa Kurvi"
}
]
},
{
"name_fi":
"Kiltapatruuna, Nipsu",
"name_en": "",
"representatives": [
{
"name": "Mikko Sandström"
},
{
"name": "Liisa Haltia"
},
{
"name": "Elina Huttunen"
}
]
},
{
"name_fi": "Nipsu",
"name_en": "",
"representatives": [
{
"name": "Mikael Siikonen"
},
{
"name": "Axel Aurola"
},
{
"name": "Elian Salmimaa"
},
{
"name": "Elias Damski"
},
{
"name": "Elias Lindberg"
},
{
"name": "Eero Ketonen"
},
{
"name": "Verneri Turkki"
},
{
"name": "Akseli Heikkinen"
}
]
}
]
}
+41
View File
@@ -0,0 +1,41 @@
{
"slug": "optmk",
"name_fi": "Opintotoimikunta",
"name_en": "Study Committee",
"info": "Opintotoimikunta vastaa edunvalvonnasta, killan tekemästä abimarkkinoinnista, sekä pitää yhteyttä korkeakoulun henkilökuntaan. Toimikunta järjestää opintoihin liittyviä tapahtumia, kuten opintosaunoja. Tomikunta koostuu opintomestarista ja opintovastaavista.",
"roles": [
{
"name_fi": "Opintomestari",
"name_en": "Master of Studies",
"representatives": [
{
"name": "Mikael Vatiainen"
}
]
},
{
"name_fi": "Opintovastaava",
"name_en": "Study Coordinator",
"representatives": [
{
"name": "Atu Vahla"
},
{
"name": "Antti Lehtonen"
},
{
"name": "Aleksi Liukkonen"
},
{
"name": "Ilmari Reponen"
},
{
"name": "Milla Heino"
},
{
"name": "Samuel Södervall"
}
]
}
]
}
+32
View File
@@ -0,0 +1,32 @@
{
"slug": "others",
"name_fi": "Muut",
"name_en": "Other officials",
"info": "",
"roles": [
{
"name_fi": "Merikapteeni",
"name_en": "Sea captain",
"representatives": [
{
"name": "Ville Lairila",
"phone_number": null,
"email": null
}
]
},
{
"name_fi": "Meripojankloppi",
"name_en": "ship's boy",
"representatives": [
{
"name": "Peter Lindahl",
"phone_number": null,
"email": null
}
]
}
]
}
+53
View File
@@ -0,0 +1,53 @@
{
"slug": "ptmk",
"name_fi": "Pajatoimikunta",
"name_en": "",
"info": "Pajatoimikunta vastaa killan oman elektroniikkapajan eli SIK-pajan ylläpidosta ja kehityksestä. Toimikuntaa johtaa pajamestari ja toimikunta koostuu pajavastaavista ja pajakisälleistä.",
"roles": [
{
"name_fi": "Pajamestari",
"name_en": "",
"representatives": [
{
"name": "Jere Oinonen"
}
]
},
{
"name_fi": "Pajakisälli",
"name_en": "",
"representatives": [
{
"name": "Otto Kievimaa"
},
{
"name": "Đình Minh Trần"
},
{
"name": "Valentin Juhela"
},
{
"name": "Axel Söderberg"
},
{
"name": "Auli Purolinna"
},
{
"name": "Karl Lipping"
},
{
"name": "Petrus Asikainen"
},
{
"name": "Elmo Kankkunen"
},
{
"name": "Samu Nyman"
},
{
"name": "Hilkka Gröhn"
}
]
}
]
}
+38
View File
@@ -0,0 +1,38 @@
{
"slug": "swtmk",
"name_fi": "SIKin Wapaa-aika -toimikunta",
"name_en": "",
"info": "Sikin Wapaa-aika -toimikunta eli tuttavallisemmin SiWa on killan uusin toimikunta. Toimikunnan tavoitteena on järjestää monipuolisesti erilaisia hassunhauskoja matalan kynnyksen tapahtumia kiltalaisille laidasta laitaan. Esimerkkejä SiWan tapahtumista ovat mm. wappulautta, pitsapäivä ja pokeriturnaus.",
"roles": [
{
"name_fi": "Myymäläpäällikkö",
"name_en": "Head of sales",
"representatives": [
{
"name": "Tiitus Koski"
}
]
},
{
"name_fi": "Myyjä",
"name_en": "Clerk",
"representatives": [
{
"name": "Arvi Virkkunen"
},
{
"name": "Valentin Juhela"
},
{
"name": "Otto Rinne"
},
{
"name": "Auli Purolinna"
},
{
"name": "Patrik Varteva"
}
]
}
]
}
+38
View File
@@ -0,0 +1,38 @@
{
"slug": "ttmk",
"name_fi": "Teknologiatoimikunta",
"name_en": "Technology Committee",
"info": "Teknologiatoimikunta huolehtii killan tekniikan toiminnasta. Toimikunnan vastuulle kuuluu killan tietojärjestelmien ylläpito ja kehitys sekä viestintäkanavien toimivuudesta huolehtiminen. Toimikunta koostuu teknologiamestarista ja teknologiavastaavista.",
"roles": [
{
"name_fi": "Teknologiamestari",
"name_en": "Master of technology",
"representatives": [
{
"name": "Simeon Pursiainen"
}
]
},
{
"name_fi": "Teknologiavastaava",
"name_en": "",
"representatives": [
{
"name": "Joona Maaranen"
},
{
"name": "Aleksi Liukkonen"
},
{
"name": "Elmo Kankkunen"
},
{
"name": "Justus Ojala"
},
{
"name": "Tommi Sytelä"
}
]
}
]
}
+80
View File
@@ -0,0 +1,80 @@
{
"slug": "vtmk",
"name_fi": "Viestintätoimikunta",
"name_en": "Communications Committee",
"info": "Viestintätoimikunta huolehtii kiltalaisten tiedottamisesta, tuottaa sisältöä killan sosiaalisen median kanaviin ja suunnittelee killan myyntituotteita. Toimikuntaa johtaa killan viestintämestari ja toimikunta koostuu somevastaavista, brändivastaavista sekä videokuvaajista.",
"roles": [
{
"name_fi": "Viestintämestari",
"name_en": "Head of communcations",
"representatives": [
{
"name": "Yassine Ramid"
}
]
},
{
"name_fi": "Somevastaava",
"name_en": "",
"representatives": [
{
"name": "Aaron Löfgren"
},
{
"name": "Elina Huttunen"
},
{
"name": "Aura Friman"
}
]
},
{
"name_fi": "Somevastaava, Brändivastaava",
"name_en": "",
"representatives": [
{
"name": "Aapo Saranpää"
},
{
"name": "Aino Svahn"
}
]
},
{
"name_fi": "Brändivastaava",
"name_en": "",
"representatives": [
{
"name": "Aleksandr Lemin"
},
{
"name": "Roope Jaskari"
},
{
"name": "Sauli Hakala"
},
{
"name": "Ville Lairila"
},
{
"name": "Aapo Nyyssönen"
},
{
"name": "Mikko Sandström"
}
]
},
{
"name_fi": "Videokuvaaja",
"name_en": "",
"representatives": [
{
"name": "Veera Melvasalo"
},
{
"name": "Aaro Rasilainen"
}
]
}
]
}
+86
View File
@@ -0,0 +1,86 @@
{
"slug": "ytmk",
"name_fi": "Yrityssuhdetoimikunta",
"name_en": "Corporate Relations Committee",
"info": "Yrityssuhdetoimikunta toimii linkkinä yritysmaailman ja Sähköinsinöörikillan välillä. Toimikunnan tehtäviin kuuluu esimerkiksi excursioiden eli yritysvierailujen järjestäminen, yrityssaunailtojen ja muiden yhteistyösopimuksilla rahoitettujen tapahtumien järjestäminen, sekä sponsoreiden hankinta Sähköinsinöörikillan puhtaanvalkoisiin haalareihin. Lisäksi yrityssuhdetoimikunnan vastuulla on ulkosuhteiden ylläpito ystävyysainejärjestöihin kotimaassa ja ulkomailla.",
"roles": [
{
"name_fi": "Yrityssuhdemestari",
"name_en": "Head of Corporate Relations",
"representatives": [
{
"name": "Tuomas Hintikka"
}
]
},
{
"name_fi": "Excursiomestari",
"name_en": "Head of Excursions",
"representatives": [
{
"name": "Aino Tasapuro"
}
]
},
{
"name_fi": "Yrityssuhdevastaava",
"name_en": "Apprentice of Corporate Relations",
"representatives": [
{
"name": "Mikael Sundell"
},
{
"name": "Henrik Ervasti"
},
{
"name": "Samuel Södervall"
},
{
"name": "Markus Määttänen"
},
{
"name": "Aura Friman"
},
{
"name": "Anton Niemi"
},
{
"name": "Iida Toivanen"
},
{
"name": "Joona Kivioja"
},
{
"name": "Jussi Seppälä"
},
{
"name": "Roope Palo"
},
{
"name": "Väinö Saarinen"
},
{
"name": "Junias Vasama"
},
{
"name": "Anton Saari"
},
{
"name": "Väinö Silvenius"
}
]
},
{
"name_fi": "Excursiovastaava",
"name_en": "",
"representatives": [
{
"name": "Into Saarinen"
},
{
"name": "Otto Rinne"
}
]
}
]
}
+27 -200
View File
@@ -1,208 +1,35 @@
import React from "react";
import styled from "styled-components";
import { Divider, TextSection, Link } from "@components/index";
import colors from "@theme/colors";
import ContactCard from "@components/ContactCard";
import BoardJson from "./board.json";
// import HvtmkJson from "./hvtmk.json";
// import MtmkJson from "./mtmk.json";
// import NtmkJson from "./ntmk.json";
// import OptmkJson from "./optmk.json";
// import OtmkJson from "./otmk.json";
// import EPtmkJson from "./eptmk.json";
// import SstmkJson from "./sstmk.json";
// import ShntmkJson from "./shntmk.json";
// import ShtmkJson from "./shtmk.json";
// import TtmkJson from "./ttmk.json";
// import UtmkJson from "./utmk.json";
// import YtmkJson from "./ytmk.json";
// import Others from "./others.json";
const orderedCommittees = [
BoardJson,
// HvtmkJson,
// MtmkJson,
// NtmkJson,
// OptmkJson,
// OtmkJson,
// EPtmkJson,
// SstmkJson,
// ShntmkJson,
// ShtmkJson,
// TtmkJson,
// UtmkJson,
// YtmkJson,
// Others,
];
const blankProfile = "/img/blank_profile.png";
const BlueLink = styled(Link)`
color: ${colors.blue1};
&:hover {
color: ${colors.lightBlue};
}
`;
const IndexUL = styled.ul`
padding: 0;
list-style: none;
li::before {
content: attr(data-icon);
margin-right: 4px;
}
`;
const Index: React.FC<{ committees: typeof orderedCommittees }> = ({ committees }) => (
<IndexUL>
{committees.map(({ slug, name_fi }) => (
<BlueLink to={`#${slug}`} key={slug}>
<li data-icon="»">
{name_fi}
</li>
</BlueLink>
))}
</IndexUL>
);
const Container = styled.div`
color: ${colors.darkBlue};
align-items: center;
justify-content: center;
width: 50vw;
& > h2 {
text-transform: uppercase;
font-size: 4rem;
width: 100%;
}
& > div {
display: flex;
flex-flow: row wrap;
}
@media (max-width: 950px) {
width: 100vw;
}
`;
const ContactContainer = styled.div`
overflow-x: hidden;
@media (max-width: 950px) {
margin-top: 0;
}
`;
const TitleContainer = styled.div`
display: flex;
width: 100%;
align-items: center;
justify-content: center;
padding: 10px 10px;
flex-direction: column;
margin: auto;
`;
const CommitteeContainer: React.FC<{
committee: Committee;
children: React.ReactNode;
}> = ({ committee, children }) => (
<Container>
<TitleContainer>
<h2>
{committee.name_fi || committee.name_en}
</h2>
</TitleContainer>
<div>
{committee.roles.map((role) => (
role.representatives.map((representative) => (
<ContactCard
key={representative.name}
name={representative.name}
phone={representative.phone_number}
email={representative.email}
image={(committee.name_en === "Board") ? (representative.image || blankProfile) : null}
role_fi={role.name_fi}
role_en={role.name_en}
/>
))
))}
</div>
{children}
</Container>
);
interface Committee {
name_fi: string;
name_en: string;
roles: Array<Role>;
}
interface Role {
name_fi: string;
name_en: string;
representatives: Array<Representative>
}
interface Representative {
name: string;
phone_number?: string;
email?: string;
image?: string;
}
import { TextSection, Link } from "@components/index";
const ContactsPageView: React.FC = () => (
<>
<TextSection>
<h1>Yhteystiedot</h1>
<TextSection>
<h1>Yhteystiedot</h1>
<div>
<h6>Hallitus</h6>
<p>
Asiaa olisi, mutta kehen ottaa yhteyttä?
<br />
Tämä sivu yrittää valottaa sen oikean ihmisen puhelinnumeroa ja sähköpostiosoitetta.
Koko hallitukseen saat yhteyden osoitteesta hallitus@sahkoinsinoorikilta.fi.
Yksittäisten hallituksen jäsenten yhteystiedot löydät <Link to="/kilta/hallitus">täältä.</Link>
</p>
<aside>
<div>
<h6>Toimikuntaluettelo</h6>
<Index committees={orderedCommittees} />
</div>
</aside>
</TextSection>
<ContactContainer>
{orderedCommittees.map((json) => (
<React.Fragment key={json.slug}>
{(json.slug !== "board") && (
<Divider />
)}
<TextSection id={json.slug}>
<CommitteeContainer committee={json}>
{(json.slug === "board") && (
<div>
<p>
{"Hallitukseen saa yhteyden lähettämällä sähköpostia "}
<BlueLink to="mailto:hallitus@sahkoinsinoorikilta.fi">
hallitus@sahkoinsinoorikilta.fi
</BlueLink>
. Hallituksen yksittäisiin jäseniin saat yhteyden etunimi.sukunimi@sahkoinsinoorikilta.fi osoitteista.
</p>
<p>
{"Hallitukselle voi myös lähettää palautetta täyttämällä "}
<BlueLink to="https://docs.google.com/forms/d/e/1FAIpQLSeD8Hm66uvwr7Xa2WGgOCfI2RS1NrZsmISf2QBKUcJf_stv8g/viewform?usp=sf_link">
palautelomakkeen
</BlueLink>
, lomakkeen vastauksia käydään läpi hallituksen kokouksissa.
</p>
</div>
)}
</CommitteeContainer>
</TextSection>
</React.Fragment>
))}
</ContactContainer>
</>
<h6>Postiosoite</h6>
<p>
Aalto-yliopisto <br />
Aalto-yliopiston Sähköinsinöörikilta ry <br />
PL 15500 <br />
00076 Aalto
</p>
<h6>Laskutus</h6>
<p>
Yhdistys : Aalto-yliopiston Sähköinsinöörikilta ry <br />
Y-tunnus: 1627010-1 <br />
Sähköpostilaskut: <a href="mailto:rahastonhoitaja@sahkoinsinoorikilta.fi">rahastonhoitaja@sahkoinsinoorikilta.fi</a>
</p>
<h6>Kiltahuone</h6>
<p>
Maarintie 8 <br />
Huoneet 1130-1134
</p>
</div>
</TextSection>
);
export default ContactsPageView;
-52
View File
@@ -1,52 +0,0 @@
{
"slug": "eptmk",
"name_fi": "Elepajatoimikunta",
"name_en": "",
"roles": [
{
"name_fi": "Pajapäävastaava",
"name_en": "",
"representatives": [
{
"name": "Oskari Ponkala"
}
]
},
{
"name_fi": "Pajavastaava",
"name_en": "",
"representatives": [
{
"name": "Karl Lipping"
}
]
},
{
"name_fi": "Pajakisälli",
"name_en": "",
"representatives": [
{
"name": "Samu Nyman"
},
{
"name": "Veikko Räty"
},
{
"name": "Ville Lairila"
},
{
"name": "Justus Ojala"
},
{
"name": "Tommi Sytelä"
},
{
"name": "Visa Kurvi"
},
{
"name": "Petrus Asikainen"
}
]
}
]
}
-82
View File
@@ -1,82 +0,0 @@
{
"slug": "hvtmk",
"name_fi": "Hyvinvointitoimikunta",
"name_en": "Committee of Wellbeing",
"roles": [
{
"name_fi": "Hyvinvointimestari",
"name_en": "Master of Wellbeing",
"representatives": [
{
"name": "Sofia Öhman"
}
]
},
{
"name_fi": "Kulttuurivastaava",
"name_en": "Culture Representative",
"representatives": [
{
"name": "Juha Anttila"
},
{
"name": "Aleksi Helin"
},
{
"name": "Julia Pykälä-aho"
}
]
},
{
"name_fi": "Liikuntavastaava",
"name_en": "Sports Representative",
"representatives": [
{
"name": "Aaro Niskanen"
},
{
"name": "Sauli Norja"
},
{
"name": "Viola Palolahti"
},
{
"name": "Eero Tihtonen"
}
]
},
{
"name_fi": "Kiltahuonevastaava",
"name_en": "Guild Room Representative",
"representatives": [
{
"name": "Patrick Linnanen"
}
]
},
{
"name_fi": "Kiltapäiväkerhovastaava",
"name_en": "",
"representatives": [
{
"name": "Samu Nyman"
},
{
"name": "Aleksanteri Vesala"
}
]
},
{
"name_fi": "Retkeilyvastaava",
"name_en": "",
"representatives": [
{
"name": "Vilhelmiina Honkanen"
},
{
"name": "Pinja Leppänen"
}
]
}
]
}
-120
View File
@@ -1,120 +0,0 @@
{
"slug": "mtmk",
"name_fi": "Sössö-toimikunta",
"name_en": "Media Committee",
"roles": [
{
"name_fi": "Puheenjohtaja, Päätoimittaja",
"name_en": "Chair, Editor in Chief",
"representatives": [
{
"name": "Aino Suomi",
"phone_number": null,
"email": null,
"image": null
}
]
},
{
"name_fi": "Toimittaja",
"name_en": "Journalist",
"representatives": [
{
"name": "Emmaleena Ahonen"
},
{
"name": "Elias Hirvonen"
},
{
"name": "Ville Lairila"
},
{
"name": "Olli Komulainen"
},
{
"name": "Pinja Salo"
},
{
"name": "Tuukka Syrjänen"
},
{
"name": "Aleksanteri Vesala"
}
]
},
{
"name_fi": "Toimittaja & Valokuvaaja",
"name_en": "Journalist & Photographer",
"representatives": [
{
"name": "Jarno Mustonen"
}
]
},
{
"name_fi": "Taittaja & Valokuvaaja",
"name_en": "Layout Artist & Photographer",
"representatives": [
{
"name": "Jonna Tammikivi"
},
{
"name": "Sasu Saalasti"
}
]
},
{
"name_fi": "Taittaja & Toimittaja",
"name_en": "Layout Artist & Journalist",
"representatives": [
{
"name": "Juuli Leppänen"
}
]
},
{
"name_fi": "Valokuvaaja",
"name_en": "Photographer",
"representatives": [
{
"name": "Toni Lyttinen"
},
{
"name": "Sauli Norja"
},
{
"name": "Rasmus Räsänen"
}
]
},
{
"name_fi": "Valokuvaaja & Graafikko",
"name_en": "Photographer & Graphic Artist",
"representatives": [
{
"name": "Kalle Petäjäaho"
}
]
},
{
"name_fi": "Graafikko",
"name_en": "Photographer & Graphic Artist",
"representatives": [
{
"name": "Otto Julkunen"
}
]
},
{
"name_fi": "Videokuvaaja",
"name_en": "Videographer",
"representatives": [
{
"name": "Aaro Rasilainen"
}
]
}
]
}
-82
View File
@@ -1,82 +0,0 @@
{
"slug": "ntmk",
"name_fi": "N-Toimikunta",
"name_en": "",
"roles": [
{
"name_fi": "N-toimikunnan puheenjohtaja",
"name_en": "",
"representatives": [
{
"name": "Ville Kaakinen"
}
]
},
{
"name_fi": "N-toimikunnan varapuheenjohtaja",
"name_en": "",
"representatives": [
{
"name": "Jami Hyytiäinen"
}
]
},
{
"name_fi": "Sklubi-yhdyshenkilö",
"name_en": "",
"representatives": [
{
"name": "Ville-Pekka Laakkonen"
}
]
},
{
"name_fi": "Alumivastaava",
"name_en": "",
"representatives": [
{
"name": "Ella Eilola"
}
]
},
{
"name_fi": "N-Toimihenkilö",
"name_en": "",
"representatives": [
{
"name": "Timi Tiira"
},
{
"name": "Erna Virtanen"
},
{
"name": "Emmaleena Ahonen"
},
{
"name": "Jarno Mustonen"
},
{
"name": "Pekka Aho"
},
{
"name": "Mikko Haapamäki"
},
{
"name": "Jonna Tammikivi"
},
{
"name": "Juuli Leppänen"
},
{
"name": "Simo Hakanummi"
},
{
"name": "Tuomo Leino"
},
{
"name": "Sasu Saalasti"
}
]
}
]
}
-62
View File
@@ -1,62 +0,0 @@
{
"slug": "optmk",
"name_fi": "Opintotoimikunta",
"name_en": "Study Committee",
"roles": [
{
"name_fi": "Opintomestari",
"name_en": "Master of Studies",
"representatives": [
{
"name": "Iikka Huttu"
}
]
},
{
"name_fi": "Opintovastaava",
"name_en": "Study Coordinator",
"representatives": [
{
"name": "Juulia Härkönen"
},
{
"name": "Patrick Linnanen"
},
{
"name": "Veeti Lahtinen"
},
{
"name": "Pinja Leppänen"
},
{
"name": "Mikko Sandström"
}
]
},
{
"name_fi": "Abimarkkinointipäävastaava",
"name_en": "",
"representatives": [
{
"name": "Vilhelmiina Honkanen"
}
]
},
{
"name_fi": "Abimarkkinointivastaava",
"name_en": "",
"representatives": [
{
"name": "Liisa Haltia"
},
{
"name": "Jenni Marttinen"
},
{
"name": "Venla Vastamäki"
}
]
}
]
}

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