Compare commits

...

509 Commits

Author SHA1 Message Date
einstein 6af5d7fa1f Fix CI build SSG timeouts
- Add backend request timeout to prevent hanging builds

- Make getStaticProps resilient to API failures

- Avoid build-time crawling for dynamic routes (blocking fallback)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-26 18:47:04 +03:00
Your Name 1df9b22fa3 lint fixes 2026-05-25 18:57:55 +03:00
Your Name 17713d4f9d picutre updates for 2026 fuksis 2026-05-25 18:56:26 +03:00
toimistokone d0a930794e fix poop overflow 2026-03-23 14:13:09 +02:00
toimistokone 170e7b3c31 hallitusbigpick 2026-03-18 14:56:02 +02:00
jadera 20f39b545d allow ... operator 2026-03-11 12:04:30 +02:00
jadera 22454369fd xd 2026-03-11 11:56:44 +02:00
jadera 7a9805ebe9 kappaa 2026-03-11 11:52:18 +02:00
jadera 4c69a4620d to detect source 2026-03-11 11:44:37 +02:00
jadera ae28ec183e 2026 prosik,oltermanni,ansiomerkit 2026-03-10 22:35:16 +02:00
Aarni Halinen b49e9e70b2 add hyphens to cards if text needs breaks 2026-03-10 21:10:43 +02:00
toimistokone 4510bb08d8 eslint xD 2026-03-10 21:10:43 +02:00
toimistokone 0825d87d0f antibot 2026-03-10 17:27:16 +02:00
J4DER4 bcad873b97 Merge branch 'atte-temp-2' into 'production'
Fixed typos in committees (again)

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!201
2026-02-27 20:48:29 +00:00
Atte Elo f6a5080769 Update 5 files
- /src/views/CommitteePage/ltmk.json
- /src/views/CommitteePage/mtmk.json
- /src/views/CommitteePage/ptmk.json
- /src/views/CommitteePage/sicc.json
- /src/views/CommitteePage/vtmk.json
2026-02-27 19:50:24 +00:00
J4DER4 e3d3b736f1 Merge branch 'boardimg26' into 'production'
change board images howth his work

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!199
2026-02-16 18:38:02 +00:00
jadera 56c509b4c1 change board images howth his work 2026-02-16 20:27:12 +02:00
J4DER4 771b9eb391 Merge branch 'comiteeentries' into 'production'
Comitee entries

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!198
2026-02-13 16:52:53 +00:00
J4DER4 75cf2e2ce1 Comitee entries 2026-02-13 16:52:53 +00:00
J4DER4 4fe78fd96d Merge branch 'master' into 'production'
Change board members for 2026

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!196
2026-01-06 18:26:03 +00:00
J4DER4 50fd27d193 Change board members for 2026 2026-01-06 18:26:02 +00:00
J4DER4 6cb18c4a13 Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!194
2025-11-21 19:23:29 +00:00
SimeonPursiainen 2009a65f55 Added silver honoraries for 2025 2025-10-28 14:13:21 +02:00
Justus Ojala c22bad5718 Use secure websockets for mqtt 2025-10-14 17:59:55 +03:00
Justus Ojala 4fbec0b85c Do not try to connect to MQTT if host undefined 2025-10-14 08:41:25 +03:00
Justus Ojala 81be5a1e60 Merge branch 'Coffeescale' into 'master'
Coffeescale

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!193
2025-10-13 22:02:53 +03:00
Justus Ojala 80ccf1bc66 Coffeescale 2025-10-13 22:02:53 +03:00
Justus Ojala d75c6b4756 Rename submitKey to submit_id 2025-10-13 19:38:28 +03:00
SimeonPursiainen 69c06636ab Fix link for freshmen page on the homepage 2025-10-07 11:43:41 +03:00
Simeon Pursiainen 3c72152704 Merge branch 'textFix' into 'production'
Textfix

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!191
2025-09-24 13:00:08 +03:00
Justus Ojala 42ce058dc9 Update guild room custodians 2025-09-23 21:38:58 +03:00
SimeonPursiainen 2ad2907f5f Fixup 2025-09-23 21:27:48 +03:00
SimeonPursiainen f61fc155a8 Updated membership fee payment info 2025-09-23 21:18:20 +03:00
SimeonPursiainen 67627d4d16 Clearer instructions for membership payments 2025-09-23 20:54:32 +03:00
Justus Ojala 4639397d25 Merge branch 'signup_duplicate_prevention' into 'master'
Add submission key to frontend to prevent duplicate signups

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!189
2025-09-16 21:43:15 +03:00
Justus Ojala 630c0bce05 Add submission key to frontend to prevent duplicate signups 2025-09-15 14:00:24 +03:00
Simeon Pursiainen 802b308826 Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!188
2025-09-11 21:21:13 +03:00
Simeon Pursiainen e4784e1932 Master 2025-09-11 21:21:13 +03:00
Simeon Pursiainen b80942ee53 Merge branch 'New_visual' into 'master'
New visual

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!187
2025-09-11 20:45:14 +03:00
Simeon Pursiainen a27c77e16c New visual 2025-09-11 20:45:14 +03:00
Simeon Pursiainen 813479a602 Merge branch 'New_visual' into 'master'
New visual

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!186
2025-09-11 20:10:29 +03:00
Simeon Pursiainen c12d4c1e73 New visual 2025-09-11 20:10:29 +03:00
Simeon Pursiainen c015f0a275 Merge branch 'master' into 'production'
touch d'andre

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!185
2025-09-09 19:18:11 +03:00
Simeon Pursiainen 6a9cb63cff touch d'andre 2025-09-09 19:18:11 +03:00
SimeonPursiainen 453d20d345 Added kyykkäsetti to rentpage 2025-09-09 19:05:15 +03:00
Simeon Pursiainen 5007d443e7 Merge branch 'master' into 'production'
Fixup

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!184
2025-09-09 14:42:39 +03:00
SimeonPursiainen 7abb7dc414 Fixup 2025-09-09 13:36:16 +03:00
Simeon Pursiainen 648c49582e Merge branch 'master' into 'production'
Changed instructions for job ads

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!183
2025-09-05 12:30:03 +03:00
SimeonPursiainen 103f2b163a Changed instructions for job ads 2025-09-05 12:15:15 +03:00
Simeon Pursiainen c50a09c691 Merge branch 'master' into 'production'
added new media card for sosso

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!182
2025-09-02 16:55:36 +03:00
SimeonPursiainen fda39d7372 added new media card for sosso 2025-09-02 16:42:35 +03:00
Simeon Pursiainen 2b75f5a567 Merge branch 'master' into 'production'
Added Metso to sponsors

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!181
2025-09-01 20:04:25 +03:00
SimeonPursiainen 3689dbc60c Added Metso to sponsors 2025-09-01 19:52:02 +03:00
Simeon Pursiainen 4e27d892d5 Merge branch 'master' into 'production'
fix stack compose

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!180
2025-09-01 19:29:30 +03:00
SimeonPursiainen 80a961d1f9 fix stack compose 2025-09-01 19:15:28 +03:00
Simeon Pursiainen 0a36c1c233 Merge branch 'master' into 'production'
removing replicas

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!179
2025-09-01 18:57:43 +03:00
SimeonPursiainen db8c8ea2b9 removing replicas 2025-09-01 18:42:42 +03:00
Simeon Pursiainen 9459930291 Merge branch 'master' into 'production'
Separate eng and international fuksi pages

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!177
2025-08-16 14:51:57 +03:00
Justus Ojala cea106b134 Separate eng and international fuksi pages 2025-08-16 14:51:57 +03:00
Justus Ojala a854de921b Merge branch 'text-fix' into 'master'
Reverse captain name order to match photo

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!178
2025-08-16 14:13:05 +03:00
Justus Ojala 9c8a2eb4ce Reverse captain name order to match photo 2025-08-16 14:13:05 +03:00
Justus Ojala ff558534a0 Merge branch 'revert-eng-freshmen' into 'master'
Revert eng freshmen page

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!176
2025-08-16 13:34:46 +03:00
Justus Ojala 56531b1cfc Revert eng freshmen page 2025-08-16 13:27:06 +03:00
Justus Ojala a7c297354f Merge branch 'update-links' into 'master'
Update links on international freshman page

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!175
2025-08-16 12:56:02 +03:00
Justus Ojala ff5da7a131 Update links on international freshman page 2025-08-16 12:27:35 +03:00
Justus Ojala 7412b652c1 Merge branch 'separate-eng-and-intl-pages' into 'master'
Separate eng and intl pages

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!174
2025-08-16 12:17:13 +03:00
Justus Ojala eb64777252 Separate eng and intl pages 2025-08-16 12:17:13 +03:00
Simeon Pursiainen af9b115205 Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!173
2025-08-13 21:16:40 +03:00
Justus Ojala 73869a4c15 Merge branch 'for-freshmen-update-20250813' into 'master'
Update "for feshmen" page

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!172
2025-08-13 20:35:09 +03:00
Justus Ojala b6e0e5ea36 Update "for feshmen" page 2025-08-13 20:35:09 +03:00
Simeon Pursiainen 7ed7849c43 Merge branch 'master' into 'production'
FIx fopas links

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!171
2025-07-15 21:41:51 +03:00
Simeon Pursiainen 96a591b1c5 Merge branch 'production' into 'master'
# Conflicts:
#   src/views/ForFreshmenPage/ForFreshmenPageHero.tsx
2025-07-15 21:29:15 +03:00
SimeonPursiainen 19ad40b969 fopas links fix 2025-07-15 20:43:11 +03:00
SimeonPursiainen 482be66b43 new links for fopas 2025-07-09 20:58:23 +03:00
SimeonPursiainen 5439ff9a56 fixing lint 2025-07-02 07:36:43 +03:00
SimeonPursiainen b7c06890fe english fuksipoint guide 2025-07-02 07:35:57 +03:00
SimeonPursiainen cdff86c0f3 english fuksiguide 2025-07-02 07:34:19 +03:00
SimeonPursiainen 984966f3af added replica in case of a failure 2025-07-01 20:56:44 +03:00
SimeonPursiainen fe015c3bce testing force push to refresh site 2025-07-01 19:37:07 +03:00
Simeon Pursiainen a570fde9d7 Merge branch 'master' into 'production'
Translate the freshman page hero

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!170
2025-06-26 07:22:24 +03:00
SimeonPursiainen c683b2d61a removed ramboll from sponsors 2025-06-26 06:51:13 +03:00
SimeonPursiainen c23200401f edited ForFreshmenPage 2025-06-26 06:48:37 +03:00
SimeonPursiainen 827eab0531 edited english freshmen page 2025-06-26 06:47:44 +03:00
Justus Ojala 1ee25d3447 Merge branch '60-forfreshmen' into 'master'
Translate the freshman page hero

Closes #60

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!168
2025-06-24 20:47:28 +03:00
Simeon Pursiainen e62017691c Merge branch 'master' into 'production'
added 2025 fuksiopas

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!169
2025-06-24 18:48:02 +03:00
SimeonPursiainen c116ea27cc added 2025 fuksiopas 2025-06-24 18:12:07 +03:00
Justus Ojala eb67fedde4 Merge branch 'master' into '60-forfreshmen'
# Conflicts:
#   src/views/FreshmenPage/FreshmenPageView.tsx
2025-06-23 21:49:35 +03:00
SimeonPursiainen 006a2dd548 commit5 2025-06-17 22:55:52 +03:00
SimeonPursiainen 49bb413424 commit5 2025-06-17 22:54:32 +03:00
SimeonPursiainen be4358b128 commit4 2025-06-17 22:43:13 +03:00
SimeonPursiainen db662959aa commit3 2025-06-17 22:42:05 +03:00
SimeonPursiainen 58b3e9594a commit2 2025-06-17 22:39:00 +03:00
SimeonPursiainen a120d7580d edited for freshmen 2025-06-17 22:32:25 +03:00
Simeon Pursiainen 7b2393142f Merge branch 'master' into 'production'
updated corporate logos

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!167
2025-06-16 20:47:13 +03:00
SimeonPursiainen 110ea83dc5 updated corporate logos 2025-06-12 20:44:16 +03:00
Simeon Pursiainen d4bdeeb9ae Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!166
2025-06-04 20:12:56 +03:00
SimeonPursiainen 2a44d99814 Removed Merus Power logo from frontpage 2025-06-04 19:57:06 +03:00
SimeonPursiainen 4e56f5d832 same 2025-06-04 19:55:09 +03:00
SimeonPursiainen 5ed2bfcbec same as previous 2025-06-04 19:52:14 +03:00
SimeonPursiainen 9195bd2d59 Edited fukseille-page 2025-06-04 19:49:11 +03:00
SimeonPursiainen a50f6d2562 New picture for Kipparit 2025-05-25 21:08:36 +03:00
Simeon Pursiainen ead8465673 Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!165
2025-05-25 18:41:27 +03:00
SimeonPursiainen 749acccb07 Cleaning "fukseille" page 2025-05-25 18:20:30 +03:00
SimeonPursiainen 5ef98ae1f5 New FTMK 2025-05-25 18:18:37 +03:00
SimeonPursiainen c449d2e1d0 same as previous commit 2025-05-25 14:08:33 +03:00
SimeonPursiainen c8b846f518 new tg groupchat links 2025-05-25 13:48:19 +03:00
SimeonPursiainen a80e92dcd4 updated telegram links 2025-05-22 22:21:02 +03:00
Justus Ojala 95f02de0ae Translate the ISO section 2025-05-20 21:42:29 +03:00
Justus Ojala b16ea3d5de Translate the freshman page hero
also removed the link to the bachelor programme info package. It is in finnish only, and is largely not applicable to exchange students and master fuksis
2025-05-20 21:21:50 +03:00
Justus Ojala bad9ace8c8 Copy content of finnish freshmen page to english freshmen page 2025-05-20 21:13:55 +03:00
Simeon Pursiainen 1a2a870f18 Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!164
2025-05-17 13:23:18 +03:00
Simeon Pursiainen 34d8213156 Master 2025-05-17 13:23:17 +03:00
SimeonPursiainen 238508f875 added member application link 2025-05-17 11:36:47 +03:00
SimeonPursiainen 0fdc1aef3a freshmen page 2025-05-16 23:59:28 +03:00
SimeonPursiainen 71f209edde english page update 2025-05-16 23:28:54 +03:00
SimeonPursiainen 3181cede9b Added english page for freshmen 2025-05-16 23:15:04 +03:00
Simeon Pursiainen 2579cd4763 Merge branch 'master' into 'production'
Revert "Merge branch 'master' of gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-frontend"

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!163
2025-05-16 22:41:30 +03:00
Simeon Pursiainen 63209bbf20 Merge branch 'production' into 'master'
# Conflicts:
#   src/views/FreshmenPage/FreshmenPageView.tsx
2025-05-16 22:29:22 +03:00
SimeonPursiainen 047c8656ac updated tg-links 2025-05-12 23:21:33 +03:00
SimeonPursiainen e16a3bb8fc changes 2025-05-12 23:05:43 +03:00
SimeonPursiainen 9b34d77c42 Changed links 2025-05-12 23:03:18 +03:00
SimeonPursiainen a76ba2b1df removed due to abuse 2025-05-12 22:35:59 +03:00
Simeon Pursiainen 5b59d36f76 Merge branch 'fix_next_version' into 'master'
Revert "audit" to fix next update try 2

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!159
2025-05-12 17:46:31 +03:00
Joona M b381400903 Revert "audit" to fix next update try 2
This reverts commit 7280edb99f.
2025-05-12 17:32:34 +03:00
Simeon Pursiainen de93bb2a05 allow audit failure 2025-05-04 09:06:19 +03:00
Simeon Pursiainen 4a6b8093bc Merge branch 'production' into 'master'
Production

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!157
2025-05-03 21:02:45 +03:00
Simeon Pursiainen d381e39f0f Production 2025-05-03 21:02:45 +03:00
Simeon Pursiainen acb335e010 sponsors 2025-05-03 20:48:28 +03:00
Simeon Pursiainen c630ebdb4d Merge branch 'revert-e1e06f18' into 'master'
Revert "Merge branch 'master' of gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-frontend"

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!156
2025-05-03 20:40:58 +03:00
Simeon Pursiainen 504b035b2a Revert "Merge branch 'master' of gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-frontend"
This reverts commit e1e06f185e
2025-05-03 20:33:33 +03:00
SimeonPursiainen 15843d8970 edit 2025-05-03 20:31:36 +03:00
Simeon Pursiainen d34e371d37 Edit FrontPageView.tsx 2025-05-03 20:06:46 +03:00
Simeon Pursiainen e1e06f185e Merge branch 'master' of gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-frontend 2025-05-03 19:42:16 +03:00
SimeonPursiainen a39e6fec4d updated email link 2025-04-19 16:28:15 +03:00
SimeonPursiainen 12cce80502 testing 2025-04-19 15:31:01 +03:00
Simeon Pursiainen 1be5ef9cb4 Update .gitlab-ci.yml file 2025-04-10 14:28:11 +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
SimeonPursiainen 734a91c490 updated contacts-page 2025-04-07 21:41:26 +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 d22d6cb232 Merge branch 'master' into production 2025-03-19 10:35:56 +02:00
Simeon Pursiainen 744aee88c4 Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!151
2025-03-19 09:52:25 +02:00
Simeon Pursiainen 1f2d33ce50 Master 2025-03-19 09:52:25 +02:00
Simeon Pursiainen 33ebf45627 updated sponsors 2025-03-19 09:37:10 +02:00
Simeon Pursiainen b0489b04ca Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!150
2025-03-07 15:15:43 +02:00
Simeon Pursiainen 217ffefb63 Master 2025-03-07 15:15:43 +02:00
Simeon Pursiainen 4df467988a Merge branch 'master' into 'production'
Master

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!149
2025-03-07 08:29:35 +02:00
Simeon Pursiainen 0d272bc58c Master 2025-03-07 08:29:35 +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 4146af7207 Merge branch 'update-react' into 'master'
Update React & Next.js

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!109
2023-02-12 08:01:22 +00:00
Ojakoo c243e76324 fix lint 2023-02-02 22:09:56 +02:00
Ilari Ojakorpi 659d0e63a0 Merge branch 'master' into 'update-react'
# Conflicts:
#   src/views/ContactsPage/ContactsPageView.tsx
2023-02-02 10:47:00 +00:00
Ojakoo 2c6c1d1e67 Update sentry 2023-02-01 13:47:44 +02:00
Ojakoo eeb2f949c6 Update Next Link 2023-02-01 13:04:05 +02:00
Ojakoo 894e630664 Update next image import to legacy version 2023-02-01 12:52:28 +02:00
Ojakoo 56c13dbf64 Next 13 2023-02-01 12:48:33 +02:00
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
Ojakoo 9c0e1a0e61 Updated sponsor links 2023-01-26 14:20:29 +02:00
Ojakoo 3b2d0596c9 Updated board info 2023-01-26 14:17:39 +02:00
Ojakoo 2395321825 Updated contacts 2023-01-19 13:19:06 +02:00
Ojakoo 05b045c2fc update media card 2023-01-09 16:05:21 +02:00
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
Ojakoo faf12816bb Updated board info 2023-01-02 00:52:44 +02: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
Ojakoo e7ef69d75f fix spelling mistake 2022-12-25 14:53:36 +02:00
Ojakoo 03e6131fe8 #47 add 2022 honors 2022-12-21 16:58:25 +02:00
Ojakoo 87f803ca3e Update readme. 2022-12-21 16:41:50 +02:00
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
Ojakoo dd3eded4a1 add filter and sort functionality to admin pages 2022-11-06 14:25:07 +02:00
Ojakoo efacbe9c40 useSWR in admin signups 2022-11-06 14:05:17 +02:00
Ojakoo c7a1502a26 correct asc/desc 2022-11-06 12:46:56 +02:00
Ojakoo 59a4f3567e basic styling, use arrow functions 2022-11-06 12:44:37 +02:00
Ojakoo 0ad59bfba6 dont use python syntax in js 2022-11-06 12:26:40 +02:00
Ojakoo 6aa0b3fe19 Added filter base 2022-11-06 11:56:55 +02:00
Ojakoo 88d5e57858 Added GE healthcare to corporate logos. 2022-10-11 18:19:52 +03:00
Elmo Kankkunen c6c5ff33c3 Fixed language icon width 2022-09-29 01:03:47 +03:00
Elmo Kankkunen 544b36d1e7 Merge branch 'bug/38-change-language-button' into 'master'
Bug/38 change language button

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!113
2022-09-26 13:54:22 +00:00
Elmo Kankkunen 783e5907b4 Merge branch 'master' into bug/38-change-language-button 2022-09-26 16:32:54 +03: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
Ojakoo 60b1b08c1a Update corp logos 2022-09-21 04:22:56 +03:00
Ojakoo c87dc4ece5 Fix signup list error when opening in new tab 2022-09-19 15:45:20 +03: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
Ojakoo 05f972a81a Update guild tg link 2022-09-19 11:26:10 +03:00
Ojakoo 16c59b75ab Quick fix to Fix issues where frontend calls backend with NaN signup id 2022-09-15 20:23:37 +03: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
Ojakoo eb819f7345 Revert 4849be84 2022-09-14 16:38:00 +03:00
Ojakoo 50485c8cbb Update sössö mediacard url. 2022-09-14 16:36:53 +03:00
Elmo Kankkunen 0380ee7d6d Changed language icons to svg images 2022-09-13 17:29:52 +03:00
Elmo Kankkunen 11bd5a90a2 Fixed indentation 2022-09-13 17:21:42 +03:00
Elmo Kankkunen 937c7c9166 Changed language button to an svg image 2022-09-13 16:53:00 +03:00
Aarni Halinen 4849be8414 Fix issues where frontend calls backend with NaN signup id 2022-08-11 17:38:03 +03:00
Ilari Ojakorpi f75e02d8b3 Merge branch 'update-dnd-package' into 'master'
Replace react-beautiful-dnd with react-dnd

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!108
2022-07-31 18:39:36 +00:00
Aarni Halinen 5ca75818b5 Cast integer question limits to number 2022-07-28 22:26:40 +03:00
Aarni Halinen eaab0f4e72 Fix typos in data fetching 2022-07-27 00:03:50 +03:00
Aarni Halinen 07efb4caed override react-mde react peer dependencies 2022-07-25 00:07:27 +03:00
Aarni Halinen ce29f5a311 fix lint issues after next update 2022-07-25 00:07:27 +03:00
Aarni Halinen e1d4a300c5 update next 2022-07-25 00:07:27 +03:00
Aarni Halinen 90f33048d7 update lockfile 2022-07-25 00:07:27 +03:00
Aarni Halinen c55c7699c7 update _document 2022-07-25 00:07:27 +03:00
Aarni Halinen 2e37072703 add children props 2022-07-25 00:07:27 +03:00
Aarni Halinen aa90d97007 update all react packages 2022-07-25 00:07:27 +03:00
Aarni Halinen fb21025231 fix e2e test 2022-07-24 23:57:32 +03:00
Aarni Halinen e4a6e6b4f7 remove old types package 2022-07-24 23:10:17 +03:00
Aarni Halinen 557310e81c re-write QuestionList as function component 2022-07-24 23:06:09 +03:00
Aarni Halinen 8ea71e41a0 fix styles 2022-07-24 23:06:09 +03:00
Aarni Halinen e3b64ab144 handleDrag on drop instead of while dragging 2022-07-24 23:06:09 +03:00
Aarni Halinen 98edf1a8bf rollback react-dnd version 2022-07-24 23:06:02 +03:00
Aarni Halinen a1be41842e Draggable component and move provider to _app 2022-07-24 23:05:38 +03:00
Aarni Halinen 9fe0390f0d Add touch backend for mobile devices 2022-07-24 23:05:38 +03:00
Aarni Halinen 9c6e771b1c Add functional changes for the replacement 2022-07-24 23:05:38 +03:00
Aarni Halinen 653ec8a7a5 Replace react-beautiful-dnd with react-dnd 2022-07-24 23:05:38 +03:00
Aarni Halinen 6f7ef76af4 Merge branch 'refactor/api' into 'master'
Refactor API functions

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!85
2022-07-24 17:54:09 +00:00
Aarni Halinen dae6806a13 remove unnecessary key 2022-07-24 20:42:36 +03:00
Aarni Halinen dd28243557 npm audit fix 2022-07-22 00:33:10 +03:00
Aarni Halinen 6bd36a8bf9 Fix unnecessary auth check after logout 2022-07-21 21:27:20 +03:00
Aarni Halinen 9d2673c1b9 Merge branch 'master' into refactor/api 2022-07-21 21:11:32 +03: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
Ojakoo a1434b84be Fixed access token generation for testcafe 2022-07-05 18:57:29 +03:00
Ojakoo 2ab8185a59 Forgot to add this 2022-07-04 11:14:21 +03:00
Ojakoo 9005c3dd93 Updated authentication. 2022-07-04 11:13:02 +03:00
Ojakoo fab3479ad0 Added fopas 2022-07-04 09:27:26 +03: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
Ojakoo 9c738d3140 Removed guild room banner 2022-06-02 18:30:26 +03:00
Ojakoo b23a52372b removed sik100 banner from frontpage 2022-06-02 18:15:09 +03:00
Ojakoo 56776c5fcc Removed fopas link 2022-06-02 18:12:40 +03: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
Aarni Halinen e3d288a2cf increase calendar and news number of cards 2022-05-20 00:42:24 +03:00
Aarni Halinen 41167efe8c add Posts component 2022-05-20 00:37:39 +03:00
Aarni Halinen f2fbc9e274 add Events component 2022-05-20 00:16:02 +03:00
Aarni Halinen 31637c065b add getTranslateFunc 2022-05-20 00:14:32 +03:00
Aarni Halinen 515b4780eb Fix typo 2022-05-19 23:41:37 +03:00
Aarni Halinen 1f1595a1e8 fetcher with API type 2022-05-19 22:53:30 +03:00
Aarni Halinen 0e285c1ecc Rename variables 2022-05-19 22:37:16 +03:00
Aarni Halinen 02df6bb9eb Remove useFetchBackend 2022-05-19 22:37:16 +03:00
Aarni Halinen b55a04f0f3 Fix lint error 2022-05-19 22:37:16 +03:00
Aarni Halinen 21e74c3422 Add path as key for useSWR 2022-05-19 22:37:16 +03:00
Aarni Halinen ed29d11b89 fix APIPaths 2022-05-19 22:37:15 +03:00
Aarni Halinen cf9db40582 remove unused export 2022-05-19 22:37:15 +03:00
Aarni Halinen 49ed39ee5a fix lint warning 2022-05-19 22:37:15 +03:00
Aarni Halinen 191fedfbc8 fix paths 2022-05-19 22:37:15 +03:00
Aarni Halinen 394b7300af fix lint 2022-05-19 22:37:15 +03:00
Aarni Halinen 44ccdd87de arrow functions 2022-05-19 22:37:15 +03:00
Aarni Halinen 8fb4dd9000 improve few types 2022-05-19 22:37:15 +03:00
Aarni Halinen b7518d9bed refactor all api files 2022-05-19 22:37:15 +03:00
Aarni Halinen efd916a8a2 generic functions for backend queries 2022-05-19 22:37:15 +03:00
Aarni Halinen d48c6a0c3e Merge branch 'update-deps' into 'master'
Update dependencies

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!104
2022-05-17 18:07:20 +00:00
Aarni Halinen 577f14fbe8 Remove HTML logo 2022-05-17 18:26:32 +03:00
Ojakoo 3eddbbe252 Updated freshmen page 2022-05-17 18:14:28 +03:00
Aarni Halinen fe5c570da8 install @types/node 2022-05-17 18:01:44 +03:00
Aarni Halinen 33251dbd18 npm update 2022-05-17 18:00:08 +03:00
Aarni Halinen d60c3e87e3 audit fix 2022-05-17 17:56:21 +03:00
Ojakoo ded7b4b146 Set committee index on stack top and correct shtmk slug 2022-05-17 16:00:27 +03:00
Ojakoo 380cdab7b0 Added feedback form link to contacts page. 2022-05-09 20:21:17 +03: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
Aarni Halinen 88e220bb16 Add width to markdown images 2022-04-26 20:14:13 +03:00
e-k1 edf2c71851 Added EKAK to honors page 2022-04-25 11:16:06 +03:00
Aarni Halinen 601e8f2688 Merge branch 'update-packages' into 'master'
Update dependencies

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!101
2022-04-10 21:30:42 +00:00
Aarni Halinen 7e125a62dd Merge branch 'feature/new_branch_name' into 'master'
Add chairman email

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!97
2022-04-10 21:27:52 +00:00
Lasse Ruokokoski 9d3245e135 changed chairman email 2022-04-11 00:18:15 +03:00
Aarni Halinen baf9159d31 npm update 2022-04-11 00:16:18 +03:00
Aarni Halinen a54ee79bdb update next-sitemap 2022-04-11 00:16:18 +03:00
Aarni Halinen 51afac9b26 update babel-plugin-styled-components 2022-04-11 00:16:18 +03:00
Aarni Halinen 08d6f0b676 update jest 2022-04-11 00:16:18 +03:00
Aarni Halinen cff8c1409e update typescript 2022-04-11 00:16:18 +03:00
Aarni Halinen 88c7a5593c update eslint 2022-04-11 00:16:18 +03:00
Aarni Halinen 3d98ff1b06 install eslint-plugin-import 2022-04-11 00:16:18 +03:00
Aarni Halinen 8534644c72 update types 2022-04-11 00:16:18 +03:00
Aarni Halinen ddc4a34926 update patch versions 2022-04-11 00:16:18 +03:00
Aarni Halinen 79e6f4ae27 update react-toastify 2022-04-11 00:16:18 +03:00
Aarni Halinen a653e01b6e update react-markdown 2022-04-11 00:16:18 +03:00
Aarni Halinen 84a1caf2c1 update react-csv 2022-04-11 00:16:18 +03:00
Aarni Halinen b361046da4 update date-fns 2022-04-11 00:16:18 +03:00
Aarni Halinen fa5e8b76c8 update swr 2022-04-11 00:16:18 +03:00
Aarni Halinen cf77735c39 update rjsf 2022-04-11 00:16:18 +03:00
Aarni Halinen 085277ac84 update sentry 2022-04-11 00:16:18 +03:00
Aarni Halinen 8bbb99aa88 npm audit fix 2022-04-11 00:15:27 +03:00
Aarni Halinen f1d4534355 update axios 2022-02-18 06:23:44 +02:00
Aarni Halinen 39478ee035 update sharp 2022-02-18 06:23:44 +02:00
Aarni Halinen 95b0e3ac82 update testcafe 2022-02-18 06:23:44 +02:00
Aarni Halinen b747d41722 update next.js 2022-02-18 06:05:47 +02:00
Aarni Halinen 0da2fefcc1 update sentry 2022-02-18 05:56:28 +02: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 9fff8dea54 fix: contact page aside handling 2022-02-16 15:27:29 +02:00
toni-lyttinen 97a91f1f6f fix: contact page styling 2022-02-16 14:57:26 +02: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
toni-lyttinen 99dc91db69 fix: contacts styling 2022-02-16 01:43:09 +02:00
toni-lyttinen 724c7711d5 style: change contacts card styling 2022-02-15 22:08:35 +02:00
Ojakoo 4ccbcb27d3 Changed board picture paths 2022-02-15 19:54:04 +02: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
Ojakoo 0b810e04d0 Added missing comittees 2022-01-31 22:50:16 +02:00
Ojakoo f7e97f3020 seems i misspoke 2022-01-31 19:55:19 +02:00
Ojakoo 02e7e8c182 now? 2022-01-31 19:04:34 +02:00
Ojakoo b9b90121dd some more merge fixes 2022-01-31 19:03:07 +02:00
Ojakoo 32df63500f Fix merge conflict 2022-01-31 18:57:23 +02:00
Ojakoo ed6e32dc3f fix merge conflict 2022-01-31 18:50:04 +02:00
Ojakoo 70149535af Honorary and tmks 2022-01-31 18:46:13 +02:00
Justus Ojala d5abc1cb10 Added ttmk contacts 2022-01-31 18:34:32 +02:00
e-k1 fb1368f31e Updatet hvtmk, optmk and mtmk 2022-01-31 18:30:50 +02:00
Justus Ojala 3bac8a925a Added sstmk contacts 2022-01-31 18:29:10 +02:00
Justus Ojala 6899c1c940 added otmk and eptmk contacts 2022-01-31 18:22:28 +02:00
Aarni Halinen ae9c5f1bc5 Merge branch 'env-files' into 'master'
Cleanup ENV files

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!93
2022-01-17 20:18:48 +00:00
Aarni Halinen 736a5e7eb7 update next version in README 2022-01-17 22:06:16 +02:00
Aarni Halinen 9a03a67683 Cleanup env-files 2022-01-17 22:06:10 +02: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
Aarni Halinen 614e7a1103 Merge branch 'next-js-12' into 'master'
Next.js v12

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!89
2022-01-13 00:51:09 +00:00
Aarni Halinen 9d778c61e3 Update stylelint 2022-01-13 02:09:53 +02:00
Aarni Halinen cc7072fc1c npm audit fix 2022-01-13 01:47:43 +02:00
Ojakoo 1f6bd31b37 Updated honorary surename 2022-01-13 00:20:24 +02:00
Ojakoo 206d421809 Changed some contact information. 2022-01-13 00:12:31 +02:00
Ojakoo 69a2887b6b Removed guild room event, fixed HeroSecondarySection width 2022-01-12 23:48:48 +02:00
koskelj8 b4aa3c4871 Merge branch 'master' into production 2022-01-04 19:31:45 +02:00
koskelj8 41b45f3d7d Add afry logo 2022-01-04 17:57:44 +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
Ojakoo 4e72a97f42 Fix roles 2022-01-02 20:08:58 +02:00
Ojakoo dea2830bdb Added new board, hid old committees 2022-01-02 18:19:50 +02:00
Oskari Ponkala 272c0027da Merge branch 'Master' into 'master'
Add Siemens logo

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!90
2021-12-30 08:49:21 +00:00
Aarni Halinen 8b40e336e3 Fix typo 2021-12-21 19:11:59 +00:00
Oskari Ponkala 44286ab1fd Update FrontPageView.tsx 2021-12-18 09:25:14 +00:00
Aarni Halinen 0a3e006c0f bump node to v16 2021-12-10 01:24:33 +02:00
Aarni Halinen f9e855fd23 update @rjsf/core 2021-12-09 21:17:46 +02:00
Aarni Halinen 3858d61c38 fix type error by casting 2021-12-09 21:17:21 +02:00
Aarni Halinen 3a136c0663 update next.js to v12 2021-12-09 21:11:14 +02: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
Aarni Halinen c6b2fa146e remove sentry tracing 2021-12-03 02:14:24 +02:00
koskelj8 7d30eae5fc Add Helmet to logos 2021-12-02 15:12:47 +02:00
Aarni Halinen 795497d00e lower Sentry tracesSampleRate 2021-11-17 19:29:52 +02:00
Aarni Halinen 08c780d948 add SENTRY_AUTH_TOKEN for docker builds 2021-11-11 23:16:49 +02:00
Aarni Halinen 5fa3defc47 Merge branch 'sentry' into 'master'
Sentry error analytics

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!87
2021-11-11 19:58:05 +00:00
Aarni Halinen cedfe2ae11 update Sentry project name 2021-11-11 21:46:34 +02:00
Aarni Halinen 2c59fdf592 setup Sentry envs 2021-11-11 21:04:46 +02:00
Aarni Halinen a5dd2ae3b8 next.js minor update 2021-11-11 21:04:46 +02:00
Aarni Halinen 1189c53f93 Fix linter 2021-11-11 21:04:46 +02:00
Aarni Halinen f299e791c7 Fix MyError page types 2021-11-11 21:04:46 +02:00
Aarni Halinen f87d8b9939 install Sentry with wizard 2021-11-11 21:04:45 +02:00
Aarni Halinen 56c71e8bab npm update 2021-11-11 20:58:42 +02:00
Aarni Halinen cc4fcd965e npm audit fix 2021-11-11 20:58:42 +02: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
Oskari Ponkala 0eaeae2012 Add ramboll to logos 2021-10-25 12:51:18 +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
Aarni Halinen f3d233ae52 Fix admin auth redirect path on dynamic routes 2021-09-04 13:38:52 +03:00
Aarni Halinen 2216c6481b Fix error rendering 2021-09-04 13:29:42 +03:00
Aarni Halinen 7da4c66da4 Merge branch 'feature/translate-signups' into 'master'
Feature: Signup form translations

See merge request sahkoinsinoorikilta/vtmk/web2.0-frontend!83
2021-09-04 09:48:22 +00:00
Aarni Halinen 4f94c3799f Add better default Finnish title for new question 2021-09-04 12:36:23 +03:00
Aarni Halinen 2f06ddf252 Modify Lang imports 2021-09-01 23:22:16 +03:00
Aarni Halinen d898d01f8a Fix E2E tests 2021-09-01 23:01:03 +03:00
Aarni Halinen c2a338417a Bubblegum fix for missing options 2021-09-01 22:29:55 +03:00
Aarni Halinen 6bf05244c8 Add translation functionality on admin signup pages 2021-09-01 21:29:05 +03:00
Aarni Halinen 5a251f736c Set translations on enum labels 2021-09-01 20:43:24 +03:00
Aarni Halinen 14006ccc2d Update signup question types 2021-09-01 20:20:30 +03:00
Aarni Halinen b0b1120015 Add unit tests for form functions 2021-09-01 18:28:28 +03: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
Oskari Ponkala a3e74f5e0d change Ode email 2021-08-27 11:05:51 +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
182 changed files with 19561 additions and 13898 deletions
-16
View File
@@ -1,16 +0,0 @@
{
"presets": [
"next/babel"
],
"plugins": [
[
"babel-plugin-styled-components",
{
"ssr": true,
"displayName": true,
"preprocess": false,
"pure": true
}
]
]
}
+2
View File
@@ -1,2 +1,4 @@
NEXT_PUBLIC_DEPLOY_ENV=local
NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_SITE_URL=https://dev.sahkoinsinoorikilta.fi
NEXT_MQTT_HOST=mqtt.dev.sahkoinsinoorikilta.fi
-2
View File
@@ -1,2 +0,0 @@
NEXT_PUBLIC_API_URL=https://api.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_SITE_URL=https://sahkoinsinoorikilta.fi
+1
View File
@@ -1,2 +1,3 @@
NEXT_PUBLIC_DEPLOY_ENV=test
NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_SITE_URL=https://dev.sahkoinsinoorikilta.fi
+14 -1
View File
@@ -3,7 +3,7 @@ module.exports = {
"eslint:recommended",
"airbnb",
"airbnb-typescript",
"airbnb/hooks",
// "airbnb/hooks",
"plugin:import/recommended",
"plugin:@typescript-eslint/recommended",
// "plugin:@typescript-eslint/recommended-requiring-type-checking",
@@ -34,11 +34,24 @@ module.exports = {
],
"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",
"object-curly-newline": "warn",
"no-mixed-spaces-and-tabs": "warn",
"no-tabs": "warn",
"react/jsx-indent": "warn",
"padded-blocks": "warn",
"spaced-comment": "warn",
},
};
@@ -0,0 +1,78 @@
---
description: "Use this agent when the user asks to set up or fix ESLint in a project, especially legacy or older projects.\n\nTrigger phrases include:\n- 'get ESLint working'\n- 'fix ESLint'\n- 'setup ESLint for this project'\n- 'enable linting locally'\n- 'ESLint not working'\n- 'get linting working on this old project'\n\nExamples:\n- User says 'get this old projects eslint working so i can lint locally' → invoke this agent to diagnose and repair ESLint setup\n- User asks 'why isn't ESLint running?' → invoke this agent to troubleshoot configuration and dependencies\n- User says 'I need to lint locally but ESLint is broken' → invoke this agent to fix the setup end-to-end"
name: eslint-setup-fixer
---
# eslint-setup-fixer instructions
You are an expert build and tooling engineer specializing in getting ESLint working in legacy and older projects. Your mission is to diagnose ESLint issues and establish a working local linting setup that the user can reliably use.
Your core responsibilities:
- Diagnose why ESLint is not working in the project
- Identify and fix configuration issues
- Ensure all dependencies are properly installed and compatible
- Verify Node.js version compatibility
- Establish a working local linting workflow
- Document any fixes applied
Methodology:
1. First, examine the current project state:
- Check if .eslintrc file exists (any format: .js, .json, .yml, .yaml)
- Look for eslintConfig in package.json
- Review package.json to see if eslint is listed as a dependency
- Check the Node.js version being used
2. Diagnose the root cause:
- Run eslint to see what error messages appear
- Check if eslint is installed (node_modules)
- Identify dependency version conflicts
- Look for missing parser or plugin dependencies
- Check for Node version incompatibilities
3. Fix the issues systematically:
- Install or update eslint if needed
- Install any missing parser or plugin dependencies
- Create or repair .eslintrc configuration if missing
- Update package.json scripts with lint commands if needed
- Handle any Node version issues (upgrade, use nvm, etc.)
4. Validate the setup:
- Successfully run eslint on the codebase
- Verify linting rules are being applied
- Test that local linting works reliably
- Confirm users can run lint commands
Common pitfalls to avoid:
- Old ESLint versions (< v6) may not work with modern Node versions
- Missing @babel/eslint-parser for projects using older Babel
- Incompatible parser versions (e.g., wrong TypeScript parser)
- Node version too old or too new for the project's dependencies
- Configuration files with syntax errors preventing parsing
- Circular dependency issues in plugin configurations
Edge cases to handle:
- Project using TypeScript: ensure typescript parser is installed
- Project with React: ensure react plugin is installed
- Project with old Node version requirements: provide upgrade guidance
- Multiple conflicting .eslintrc files: consolidate to single source of truth
- Projects with monorepo structure: handle root and package-level configs
Output format:
- Clear summary of what was broken and why
- Step-by-step list of all fixes applied
- Verification results showing linting now works
- Any warnings about compatibility or recommendations for modernization
- Command to run linting locally (e.g., `npm run lint` or `npm run eslint`)
Quality checks:
- Verify eslint command runs without errors
- Confirm linting actually processes files (not just succeeding with no output)
- Test that rules are being enforced
- Ensure the fix is reproducible for other developers
- Document any version constraints or platform-specific requirements
When to ask for clarification:
- If you're unsure whether the project uses TypeScript, React, or other special configs
- If multiple conflicting approaches exist and you need user preference
- If Node version constraints prevent a standard fix
- If the project has unusual structure that prevents standard ESLint discovery
+4
View File
@@ -40,3 +40,7 @@ yarn-error.log*
# SEO
public/robots.txt
public/sitemap.xml
public/sitemap-0.xml
# Sentry
.sentryclirc
+18 -23
View File
@@ -8,7 +8,7 @@ stages:
- deploy
install:
image: node:14
image: node:16
stage: setup
script:
- npm ci
@@ -21,34 +21,35 @@ install:
expire_in: 1 week
audit:
image: node:14
image: node:16
needs: ["install"]
allow_failure: true
stage: audit
script:
- npm audit --audit-level=critical
es:lint:
image: node:14
image: node:16
needs: ["install"]
stage: lint
script:
- npm run lint:es
css:lint:
image: node:14
image: node:16
needs: ["install"]
stage: lint
script:
- npm run lint:css
# test:unit:
# image: node:14
# image: node:16
# stage: test
# script:
# - npm run test:unit
build:
image: node:14
image: node:16
needs: ["install"]
stage: build
script:
@@ -66,7 +67,7 @@ build:
- .next/cache/
test:e2e:
image: circleci/node:14-browsers
image: circleci/node:16-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 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 build . -t "$IMAGE_NAME":latest --build-arg NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api --build-arg NEXT_PUBLIC_SITE_URL=https://dev.sahkoinsinoorikilta.fi
- 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 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 build . -t "$IMAGE_NAME":prod
- 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,4 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint
npm run lint:es
+1 -1
View File
@@ -1 +1 @@
14
16
+2 -2
View File
@@ -1,7 +1,7 @@
{
"extends": [
"stylelint-config-standard",
"stylelint-config-recommended",
"stylelint-config-styled-components"
],
"syntax": "css"
"customSyntax": "postcss-jsx"
}
+6 -3
View File
@@ -1,5 +1,5 @@
# Install dependencies only when needed
FROM node:14-alpine AS deps
FROM node:16-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,18 +7,21 @@ COPY package.json package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM node:14-alpine AS builder
FROM node:16-alpine AS builder
WORKDIR /app
COPY . .
COPY --from=deps /app/node_modules ./node_modules
ENV NEXT_TELEMETRY_DISABLED=1
ENV NEXT_PUBLIC_SENTRY_DSN=https://3ad96a8fb4ee46dab4a913049e2a8b38@o1039142.ingest.sentry.io/6007885
ARG NEXT_PUBLIC_DEPLOY_ENV=production
ARG NEXT_PUBLIC_API_URL=https://api.sahkoinsinoorikilta.fi/api
ARG NEXT_PUBLIC_SITE_URL=https://sahkoinsinoorikilta.fi
ARG SENTRY_AUTH_TOKEN
RUN npm run build
# Production image, copy all the files and run next
FROM node:14-alpine AS runner
FROM node:16-alpine AS runner
WORKDIR /app
ENV NODE_ENV production
+17 -5
View File
@@ -4,14 +4,26 @@ This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next
* **[React](https://facebook.github.io/react/)** (17.x)
* **[Typescript](https://www.typescriptlang.org/)** (4.x)
* **[Next.js](https://nextjs.org/)** (10.x)
* [Testcafe](https://devexpress.github.io/testcafe/) - E2E Testing framework
* **[Next.js](https://nextjs.org/)** (12.x)
* **[Testcafe](https://devexpress.github.io/testcafe/)** - E2E Testing framework
## Installation
1. Clone/download repo
2. Install node v14 ([`nvm`](https://github.com/nvm-sh/nvm))
3. `npm install`
Install node v16 with **[Node Version Manager](https://github.com/nvm-sh/nvm#installing-and-updating)**.
Set up your SSH key authentication in GitLab Profile Settings. Then clone the repository and checkout the master branch:
```bash
git clone git@gitlab.com:sahkoinsinoorikilta/vtmk/web2.0-frontend.git
cd web2.0-frontend
git checkout master
```
Create local env file for development and install dependencies:
```bash
cp .env.local.example .env.local
npm install
```
## Getting Started
+20
View File
@@ -0,0 +1,20 @@
module.exports = {
roots: ["<rootDir>/src"],
testMatch: ["**/*.test.ts"],
transform: {
"^.+\\.tsx?$": "ts-jest",
},
preset: "ts-jest",
verbose: true,
moduleNameMapper: {
"^@api/(.*)$": "<rootDir>/src/api/$1",
"^@components/(.*)$": "<rootDir>/src/components/$1",
"^@hooks/(.*)$": "<rootDir>/src/hooks/$1",
"^@models/(.*)$": "<rootDir>/src/models/$1",
"^@pages/(.*)$": "<rootDir>/src/pages/$1",
"^@theme/(.*)$": "<rootDir>/src/theme/$1",
"^@views/(.*)$": "<rootDir>/src/views/$1",
"^@utils/(.*)$": "<rootDir>/src/utils/$1",
},
};
-1
View File
@@ -1,5 +1,4 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
+18 -3
View File
@@ -1,9 +1,21 @@
const { withSentryConfig } = require("@sentry/nextjs");
const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true",
});
module.exports = withBundleAnalyzer({
target: "server",
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({
images: {
domains: [
"api.sahkoinsinoorikilta.fi",
@@ -11,4 +23,7 @@ module.exports = withBundleAnalyzer({
"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));
+13085 -11371
View File
File diff suppressed because it is too large Load Diff
+58 -37
View File
@@ -27,58 +27,79 @@
"start": "next dev",
"start-prod": "next start --port ${SERVER_PORT:=80}",
"serve": "next start --port 3000",
"test:unit": "jest --coverage",
"test": "npm run testcafe",
"testcafe": "testcafe --config-file testcafe.json",
"build-analyze": "ANALYZE=true npm run build",
"prepare": "husky install"
},
"devDependencies": {
"@types/js-cookie": "^2.2.7",
"@types/react": "^17.0.19",
"@types/react-beautiful-dnd": "^13.1.1",
"@types/react-csv": "^1.1.2",
"@types/react-dom": "^17.0.9",
"@types/jest": "^27.4.1",
"@types/js-cookie": "^3.0.1",
"@types/node": "^16.11.36",
"@types/react": "^18.0.15",
"@types/react-csv": "^1.1.3",
"@types/react-dom": "^18.0.6",
"@types/shortid": "^0.0.29",
"@types/styled-components": "^5.1.12",
"@typescript-eslint/eslint-plugin": "^4.29.2",
"@typescript-eslint/parser": "^4.29.2",
"babel-plugin-styled-components": "^1.13.2",
"eslint": "^7.32.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-airbnb-typescript": "^13.0.0",
"eslint-config-next": "^11.1.0",
"husky": "^7.0.1",
"next-sitemap": "^1.6.162",
"@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",
"husky": "^7.0.4",
"jest": "^27.5.1",
"next-sitemap": "^3.1.11",
"npm-run-all": "^4.1.5",
"stylelint": "^13.13.1",
"stylelint-config-standard": "^22.0.0",
"postcss-jsx": "^0.36.4",
"postcss-syntax": "^0.36.2",
"stylelint": "^14.2.0",
"stylelint-config-recommended": "^6.0.0",
"stylelint-config-styled-components": "^0.1.1",
"testcafe": "^1.15.3",
"typescript": "^4.3.5"
"testcafe": "^1.18.5",
"ts-jest": "^27.1.4",
"typescript": "^4.6.3"
},
"dependencies": {
"@next/bundle-analyzer": "^11.1.0",
"@rjsf/core": "^3.1.0",
"axios": "^0.21.1",
"date-fns": "^2.23.0",
"@next/bundle-analyzer": "^12.2.3",
"@rjsf/core": "^4.2.0",
"@sentry/nextjs": "^7.34.0",
"axios": "^0.26.1",
"date-fns": "^2.28.0",
"fast-deep-equal": "^3.1.3",
"js-cookie": "^3.0.0",
"js-cookie": "^3.0.1",
"lodash": "^4.17.21",
"next": "^11.1.0",
"mqtt": "^5.14.1",
"next": "^13.1.6",
"normalize.css": "^8.0.1",
"react": "^17.0.2",
"react-beautiful-dnd": "^13.1.0",
"react-csv": "^2.0.3",
"react-dom": "^17.0.2",
"react-is": "^17.0.2",
"react-markdown": "^7.0.0",
"react": "^18.2.0",
"react-csv": "^2.2.2",
"react-dnd": "15.0.2",
"react-dnd-html5-backend": "15.0.2",
"react-dnd-touch-backend": "15.0.2",
"react-dom": "^18.2.0",
"react-is": "^18.2.0",
"react-markdown": "^8.0.3",
"react-mde": "^11.5.0",
"react-toastify": "^7.0.4",
"rehype-raw": "^6.0.0",
"rehype-sanitize": "^5.0.0",
"sharp": "^0.29.0",
"react-toastify": "^9.0.7",
"rehype-raw": "^6.1.1",
"rehype-sanitize": "^5.0.1",
"sharp": "^0.30.3",
"shortid": "^2.2.16",
"styled-components": "^5.3.0",
"swr": "^0.5.6"
"styled-components": "^5.3.5",
"swr": "^1.2.2",
"uuid": "^13.0.0"
},
"engines": {
"node": "16"
},
"overrides": {
"react-mde": {
"react": "$react",
"react-dom": "$react-dom"
}
}
}
+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
});
+4
View File
@@ -0,0 +1,4 @@
defaults.url=https://sentry.io/
defaults.org=sik-kf
defaults.project=sik-web
cli.executable=../../.npm/_npx/a8388072043b4cbc/node_modules/@sentry/cli/bin/sentry-cli
+16
View File
@@ -0,0 +1,16 @@
// This file configures the initialization of Sentry on the server.
// The config you add here will be used whenever the server handles a request.
// 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
});
+73
View File
@@ -0,0 +1,73 @@
import {
deleteTokenCookies, getAccessTokenCookie, getRefreshTokenCookie, setAccessTokenCookie, setRefreshTokenCookie,
} from "@utils/auth";
import { APIPath, postBackendAPI } from "./backend";
export type AuthTokenRequest = {
username: string;
password: string;
};
export type AuthToken = {
access: string;
refresh: string;
};
export type AuthRefreshRequest = {
refresh: AuthToken["refresh"]
};
export type 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 });
return {
access: resp.access,
refresh: resp.refresh,
};
}
async function refreshToken(): Promise<boolean> {
// Get refresh token if exists
const refresh = getRefreshTokenCookie();
if (!refresh) {
deleteTokenCookies();
return false;
}
try {
// Renew access token
const { access } = await postBackendAPI<AuthRefreshRequest, RefreshedAuthToken>({ path: APIPath.AUTH_TOKEN_REFRESH }, { refresh });
setAccessTokenCookie(access);
} 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> => {
const { access, refresh } = await generateToken(username, password);
setAccessTokenCookie(access);
setRefreshTokenCookie(refresh);
};
export const authenticate = async (): Promise<boolean> => {
// Find access token
const token = getAccessTokenCookie();
if (!token) {
// Unnecessary, but might be good idea to clear old refresh tokens etc.
deleteTokenCookies();
return false;
}
try {
await postBackendAPI({ path: APIPath.AUTH_TOKEN_VERIFY }, { token });
return true;
} catch (err) {
// Handle refresh automatically
return refreshToken();
}
};
+134
View File
@@ -0,0 +1,134 @@
import axios, { AxiosInstance, AxiosRequestConfig } from "axios";
import { getAccessTokenCookie } from "@utils/auth";
const API_TIMEOUT_MS = 10000;
const axiosInstance: AxiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL,
timeout: API_TIMEOUT_MS,
});
export enum APIPath {
TAGS = "/tags/:id",
EVENTS = "/events/:id",
FEED = "/feed/:id",
JOBADS = "/jobads/:id",
SIGNUPS = "/signup/:id",
SIGNUPS_EDIT = "/signup/:id/edit",
SIGNUP_FORMS = "/signupForm/:id",
SIGNUP_FORMS_EMAIL = "/signupForm/:id/sendemail",
SIGNUP_FORMS_SIGNUPS = "/signupForm/:id/signups",
AUTH_TOKEN_GENERATE = "/token",
AUTH_TOKEN_VERIFY = "/token/verify",
AUTH_TOKEN_REFRESH = "/token/refresh",
}
export type API = {
path: APIPath;
urlParams?: {
id?: string | number;
};
queryParams?: {
limit?: number;
offset?: number;
since?: Date;
uuid?: string;
};
authenticated?: boolean;
};
type Headers = {
Authorization?: string;
};
const getAuthHeader = (): string => {
const jwt = getAccessTokenCookie();
return `Bearer ${jwt}`;
};
const getHeaders = (auth?: boolean): Headers => {
if (auth) {
return {
Authorization: getAuthHeader(),
};
}
return {};
};
const fillUrlParams = (apiPath: APIPath, params: API["urlParams"] = {}): string => {
const path = apiPath
.split("/")
.map((urlComponent) => {
// fill in each placeholder component like ':id' with value from params
if (urlComponent.startsWith(":")) {
const key = urlComponent.substring(1);
const value = params[key] ?? "";
return value;
}
return urlComponent;
})
.filter(Boolean)
.join("/");
// code above strips leading and trailing '/' from path
return `/${path}/`;
};
const callBackendAPI = async <RequestType, ResponseType>(
path: APIPath,
urlParams: API["urlParams"],
queryParams: API["queryParams"],
method: AxiosRequestConfig["method"],
headers: Headers,
requestBody: RequestType,
): Promise<ResponseType> => {
const url = fillUrlParams(path, urlParams);
const request: AxiosRequestConfig = {
url,
method,
headers,
params: queryParams,
data: requestBody,
responseType: "json",
};
const response = await axiosInstance.request<ResponseType>(request);
const arrayResp = (response.data as { results?: ResponseType });
if (Array.isArray(arrayResp.results)) {
return arrayResp.results;
}
return response.data;
};
export const getBackendAPI = async <ResponseType>({
path, urlParams, queryParams, authenticated,
}: API): Promise<ResponseType> => {
const headers = getHeaders(authenticated);
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> => {
const headers = getHeaders(authenticated);
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> => {
const headers = getHeaders(authenticated);
return callBackendAPI<RequestType, ResponseType>(path, urlParams, queryParams, "PUT", headers, body);
};
export const deleteBackendAPI = async <ResponseType>({
path, urlParams, queryParams, authenticated,
}: API): Promise<ResponseType> => {
const headers = getHeaders(authenticated);
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,
});
+38 -56
View File
@@ -1,11 +1,10 @@
/* eslint-disable no-console */
import axios from "axios";
import Event from "@models/Event";
import { getAuthHeader } from "@utils/auth";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
} from "./backend";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/events/`;
export interface Options {
interface Options {
limit?: number;
offset?: number;
auth?: boolean;
@@ -13,83 +12,66 @@ export interface Options {
}
class EventApi {
static async getEvent(id: number, auth = false): Promise<Event> {
static getEvent = async (id: number, auth = false): Promise<Event> => {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}${id}/`, {
headers,
return await getBackendAPI<Event>({
path: APIPath.EVENTS, urlParams: { id }, authenticated: auth,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async getEvents(options: Options = {}): Promise<Event[]> {
const {
since, limit, offset, auth,
} = options;
static getEvents = async ({
since, limit, offset, auth,
}: Options = {}): Promise<Event[]> => {
try {
const params = {
since,
limit,
offset,
};
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}`, {
headers,
params,
});
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
static async createEvent(data: Event): Promise<Event> {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
return await getBackendAPI<Event[]>({
path: APIPath.EVENTS,
queryParams: {
since,
limit,
offset,
},
authenticated: auth,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async updateEvent(data: Event): Promise<Event> {
static createEvent = async (data: Event): Promise<Event> => {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
return await postBackendAPI<Event, Event>({
path: APIPath.EVENTS, authenticated: true,
}, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static async deleteEvent(id: number) {
static updateEvent = async (data: Event): Promise<Event> => {
try {
const resp = await axios.delete(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
return await putBackendAPI<Event, Event>({
path: APIPath.EVENTS, urlParams: { id: data.id }, authenticated: true,
}, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static deleteEvent = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
} catch (err) {
console.error(err);
throw err;
}
};
}
export default EventApi;
+39 -57
View File
@@ -1,89 +1,71 @@
/* eslint-disable no-console */
import axios from "axios";
import Post from "@models/Feed";
import { getAuthHeader } from "@utils/auth";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
} from "./backend";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/feed/`;
export interface Options {
interface Options {
limit?: number;
offset?: number;
auth?: boolean;
}
class FeedApi {
static async getFeed(options: Options = {}): Promise<Post[]> {
const {
limit, offset, auth,
} = options;
const params = {
limit,
offset,
};
const headers = auth ? { Authorization: getAuthHeader() } : null;
static getPost = async (id: number, auth?: boolean): Promise<Post> => {
try {
const resp = await axios.get(URL, { params, headers });
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
static async getPost(id: number, options: Options = {}): Promise<Post> {
const { auth } = options;
const headers = auth ? { Authorization: getAuthHeader() } : null;
try {
const resp = await axios.get(`${URL}${id}/`, { headers });
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async createPost(data: Post): Promise<Post> {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
},
return await getBackendAPI<Post>({
path: APIPath.FEED, urlParams: { id }, authenticated: auth,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async updatePost(data: Post): Promise<Post> {
static getFeed = async ({ limit, offset, auth }: Options = {}): Promise<Post[]> => {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
return await getBackendAPI<Post[]>({
path: APIPath.FEED,
queryParams: {
limit,
offset,
},
authenticated: auth,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async deletePost(id: number) {
static createPost = async (data: Post): Promise<Post> => {
try {
const resp = await axios.delete(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
return await postBackendAPI<Post, Post>({ path: APIPath.FEED, authenticated: true }, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static updatePost = async (data: Post): Promise<Post> => {
try {
return await putBackendAPI<Post, Post>({
path: APIPath.FEED, urlParams: { id: data.id }, authenticated: true,
}, data);
} catch (err) {
console.error(err);
throw err;
}
};
static deletePost = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.EVENTS, urlParams: { id }, authenticated: true });
} catch (err) {
console.error(err);
throw err;
}
};
}
export default FeedApi;
+38 -56
View File
@@ -1,11 +1,10 @@
/* eslint-disable no-console */
import axios from "axios";
import JobAd from "@models/JobAd";
import { getAuthHeader } from "@utils/auth";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
} from "./backend";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/jobads/`;
export interface Options {
interface Options {
since?: Date;
limit?: number;
offset?: number;
@@ -13,83 +12,66 @@ export interface Options {
}
class JobAdApi {
static async getJobAds(options: Options = {}): Promise<JobAd[]> {
const {
since, limit, offset, auth,
} = options;
static getJobAd = async (id: number, auth = false): Promise<JobAd> => {
try {
const params = {
since,
limit,
offset,
};
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}`, {
headers,
params,
return await getBackendAPI({
path: APIPath.JOBADS, urlParams: { id }, authenticated: auth,
});
return resp.data.results;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async getJobAd(id: number, auth = false): Promise<JobAd> {
static getJobAds = async ({
since, limit, offset, auth,
}: Options = {}): Promise<JobAd[]> => {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${URL}${id}/`, {
headers,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
static async createJobAd(data: JobAd): Promise<JobAd> {
try {
const resp = await axios.post(URL, data, {
headers: {
Authorization: getAuthHeader(),
return await getBackendAPI<JobAd[]>({
path: APIPath.JOBADS,
queryParams: {
since,
limit,
offset,
},
authenticated: auth,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async updateJobAd(data: JobAd): Promise<JobAd> {
static createJobAd = async (data: JobAd): Promise<JobAd> => {
try {
const putUrl = `${URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
return await postBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS, authenticated: true,
}, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static async deleteJobAd(id: number) {
static updateJobAd = async (data: JobAd): Promise<JobAd> => {
try {
const resp = await axios.delete(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
return await putBackendAPI<JobAd, JobAd>({
path: APIPath.JOBADS, urlParams: { id: data.id }, authenticated: true,
}, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static deleteJobAd = async (id: number): Promise<void> => {
try {
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.JOBADS, urlParams: { id }, authenticated: true });
} catch (err) {
console.error(err);
throw err;
}
};
}
export default JobAdApi;
+66 -95
View File
@@ -1,182 +1,153 @@
/* eslint-disable no-console */
import axios from "axios";
import { Signup, SignupForm } from "@models/Signup";
import { getAuthHeader } from "@utils/auth";
import {
APIPath, deleteBackendAPI, getBackendAPI, postBackendAPI, putBackendAPI,
} from "./backend";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/signup/`;
export const FORM_URL = `${process.env.NEXT_PUBLIC_API_URL}/signupForm/`;
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Options {
// limit?: number;
// offset?: number;
// auth?: boolean;
}
export type EmailRequest = {
mode: "all" | "actual" | "reserve";
subject: string;
content: string;
};
class SignupApi {
static async getSignup(id: number): Promise<Signup> {
static getSignup = async (id: number): Promise<Signup> => {
try {
const resp = await axios.get(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
return await getBackendAPI<Signup>({
path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async createSignup(data: Signup): Promise<Signup> {
static createSignup = async (data: Signup): Promise<Signup> => {
try {
const resp = await axios.post(URL, data);
return resp.data;
return await postBackendAPI<Signup, Signup>({
path: APIPath.SIGNUPS,
}, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static async updateSignup(data: Signup, uuid: string): Promise<Signup> {
static updateSignup = async (data: Signup, uuid: string): Promise<Signup> => {
try {
const { id } = data;
if (!id) throw new Error("SignupId required!");
const resp = await axios.put(`${URL}${id}/edit/`, data, {
params: { uuid },
});
return resp.data;
return await putBackendAPI<Signup, Signup>({
path: APIPath.SIGNUPS_EDIT,
urlParams: {
id,
},
queryParams: {
uuid,
},
}, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static async getSignupUUID(id: number, uuid: string): Promise<Signup> {
static getSignupUUID = async (id: number, uuid: string): Promise<Signup> => {
try {
const resp = await axios.get(`${URL}${id}/edit/`, {
params: {
return await getBackendAPI<Signup>({
path: APIPath.SIGNUPS_EDIT,
urlParams: {
id,
},
queryParams: {
uuid,
},
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async deleteSignup(id: number) {
static deleteSignup = async (id: number): Promise<void> => {
try {
const resp = await axios.delete(`${URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUPS, urlParams: { id }, authenticated: true });
} catch (err) {
console.error(err);
throw err;
}
}
};
static async getForms(auth = false): Promise<SignupForm[]> {
static getForm = async (id: number, auth = false): Promise<SignupForm> => {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(FORM_URL, {
headers,
return await getBackendAPI<SignupForm>({
path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: auth,
});
const { results } = resp.data;
return results;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async getForm(id: number, auth = false): Promise<SignupForm> {
static getForms = async (auth = false): Promise<SignupForm[]> => {
try {
const headers = auth ? { Authorization: getAuthHeader() } : null;
const resp = await axios.get(`${FORM_URL}${id}/`, {
headers,
return await getBackendAPI<SignupForm[]>({
path: APIPath.SIGNUP_FORMS, authenticated: auth,
});
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
};
static async createForm(data: SignupForm): Promise<SignupForm> {
static createForm = async (data: SignupForm): Promise<SignupForm> => {
try {
const resp = await axios.post(FORM_URL, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
return await postBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS, authenticated: true,
}, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static async updateForm(data: SignupForm): Promise<SignupForm> {
static updateForm = async (data: SignupForm): Promise<SignupForm> => {
try {
const putUrl = `${FORM_URL}${data.id}/`;
const resp = await axios.put(putUrl, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
return await putBackendAPI<SignupForm, SignupForm>({
path: APIPath.SIGNUP_FORMS, urlParams: { id: data.id }, authenticated: true,
}, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static async deleteForm(id: number) {
static deleteForm = async (id: number): Promise<void> => {
try {
const resp = await axios.delete(`${FORM_URL}${id}`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
await deleteBackendAPI<{ message: "OK" }>({ path: APIPath.SIGNUP_FORMS, urlParams: { id }, authenticated: true });
} catch (err) {
console.error(err);
throw err;
}
}
};
static async signupFormSendEmail(data: any, id: number): Promise<any> {
static signupFormSendEmail = async (data: EmailRequest, id: number): Promise<void> => {
try {
const resp = await axios.post(`${FORM_URL}${id}/sendemail/`, data, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
await postBackendAPI<EmailRequest, { message: "Email sent" }>({ path: APIPath.SIGNUP_FORMS_EMAIL, urlParams: { id }, authenticated: true }, data);
} catch (err) {
console.error(err);
throw err;
}
}
};
static async getSignups(id: number): Promise<Signup[]> {
static getSignups = async (id: number): Promise<Signup[]> => {
try {
const resp = await axios.get(`${FORM_URL}${id}/signups/`, {
headers: {
Authorization: getAuthHeader(),
},
});
return resp.data;
return await getBackendAPI<Signup[]>({ path: APIPath.SIGNUP_FORMS_SIGNUPS, urlParams: { id }, authenticated: true });
} catch (err) {
console.error(err);
throw err;
}
}
};
}
export default SignupApi;
+4 -14
View File
@@ -1,26 +1,16 @@
/* eslint-disable no-console */
import axios from "axios";
import Tag from "@models/Tag";
export const URL = `${process.env.NEXT_PUBLIC_API_URL}/tags/`;
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Options {
// limit?: number;
// offset?: number;
// auth?: boolean;
}
import { APIPath, getBackendAPI } from "./backend";
class TagApi {
static async getTags(): Promise<Tag[]> {
static getTags = async (): Promise<Tag[]> => {
try {
const resp = await axios.get(URL);
return resp.data.results;
return await getBackendAPI<Tag[]>({ path: APIPath.TAGS });
} catch (err) {
console.error(err);
throw err;
}
}
};
}
export default TagApi;
+1
View File
@@ -49,6 +49,7 @@ const Panel = styled.div<{ $visible?: boolean }>`
interface AccordionProps {
title: string;
children: React.ReactNode;
}
const Accordion: React.FC<AccordionProps> = ({ title, children }) => {
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
const Icon = "/img/add-icon.png";
+2 -1
View File
@@ -6,9 +6,10 @@ interface ButtonProps {
onClick: () => void;
buttonStyle: "hero" | "filled" | "filter" | "bordered";
selected?: boolean;
children: React.ReactNode;
}
const StyledButton = styled.button<{ $selected: boolean }>`
const StyledButton = styled.button<{ $selected?: boolean }>`
border-radius: none;
padding: 0.8rem 2rem;
margin: 0.5rem;
+2 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import colors from "@theme/colors";
import Link from "@components/Link";
@@ -43,6 +43,7 @@ const StyledCard = styled.article`
}
h3 {
hyphens: auto;
padding: 0.5rem;
font-size: 1.5rem;
font-weight: 300;
+1 -1
View File
@@ -23,5 +23,5 @@ export default styled(ChangeLanguageButton)`
font-size: 4rem;
background: none;
border: none;
width: fit-content;
width: 2cm;
`;
+17 -9
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import colors from "@theme/colors";
@@ -18,13 +18,13 @@ const Row = styled.div`
const ImageContainer = styled.div`
position: relative;
height: 5rem;
width: 5rem;
height: 8rem;
width: 8rem;
flex-shrink: 0;
img {
padding: 0.5rem !important;
border-radius: 50%;
border-radius: 15%;
}
`;
@@ -32,16 +32,24 @@ const Info = styled.div`
display: flex;
flex-direction: column;
align-items: flex-start;
padding: 0.25rem;
margin-left: -20px;
min-width: 150px;
padding: 2rem;
padding-top: 10px;
color: ${colors.darkBlue};
& > p {
font-size: 0.8rem;
font-size: 1rem;
margin: 0;
}
& > h3 {
& > a {
font-weight: 400;
font-size: 0.9rem;
}
& > h3 {
font-size: 1.2rem;
font-weight: 500;
}
`;
@@ -66,7 +74,7 @@ const ContactCard: React.FC<ContactCardProps> = ({
src={image}
alt={name}
layout="fill"
objectFit="scale-down"
objectFit="cover"
/>
</ImageContainer>
) : null}
@@ -74,7 +82,7 @@ const ContactCard: React.FC<ContactCardProps> = ({
<h3>{name}</h3>
<p>{role_fi || role_en}</p>
{phone ? <p>{phone}</p> : null}
{email ? <p>{email}</p> : null}
{email ? <a href={`mailto:${email}`}>{email}</a> : null}
</Info>
</Row>
</Card>
+2 -2
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image, { ImageProps } from "next/image";
import Image, { ImageProps } from "next/legacy/image";
import styled, { keyframes, Keyframes } from "styled-components";
interface CrossFadeImagesProps {
@@ -70,9 +70,9 @@ const CrossFadeImages: React.FC<CrossFadeImagesProps> = ({
$duration={len * SINGLE_IMAGE_TIME}
>
{ images.map((image, idx) => (
// eslint-disable-next-line react/no-array-index-key
<div key={idx} className={idx > 0 ? "not-first" : undefined}>
<AnimatedImage
key={image}
src={image}
objectFit="cover"
width={width}
+61
View File
@@ -0,0 +1,61 @@
import React, { useRef } from "react";
import { useDrag, useDrop } from "react-dnd";
const type = "Draggable";
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
const [, drop] = useDrop({
// 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
if (!ref.current) {
return;
}
const dragIndex = item.index;
// current element where the dragged element is hovered on
const hoverIndex = index;
// If the dragged element is hovered in the same place, then do nothing
if (dragIndex === hoverIndex) {
return;
}
// If it is dragged around other elements, then move the image and set the state with position changes
handleDrag(dragIndex, hoverIndex);
/*
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(() => ({
// 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
item: { id, index },
// method to collect additional data for drop handling like whether is currently being dragged
collect: (monitor) => ({
isDragging: monitor.isDragging(),
}),
}));
/*
Initialize drag and drop into the element using its reference.
Here we initialize both drag and drop on the same element (i.e., Image component)
*/
drag(drop(ref));
return (
<div ref={ref}>{children}</div>
);
};
export default Draggable;
+2 -1
View File
@@ -6,13 +6,14 @@ interface DropDownBoxProps {
onMouseEnter: () => void;
onMouseLeave: () => void;
visible: boolean;
children: React.ReactNode;
}
const Box = styled.div`
background-color: ${colors.white};
border: 1px solid ${colors.black};
margin-top: 0.8rem;
position: absolute;
/* margin-top: 0.8rem; hides cool onhover effect but fixes a gap problem */
left: 0;
top: 2.5rem;
z-index: 20;
+2 -1
View File
@@ -1,8 +1,9 @@
/* eslint-disable react/no-invalid-html-attribute */
import React from "react";
const Icons = (): JSX.Element => (
<>
<link rel="shortcut icon" href="/favicons/favicon.ico" />
<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" />
+77
View File
@@ -0,0 +1,77 @@
import {
Card,
PageLink,
CardSection,
} from "@components/index";
import Event from "@models/Event";
import noop from "@utils/noop";
import { Lang, getTranslateFunc } from "../../i18n";
const cardTimeOpts: Intl.DateTimeFormatOptions = {
day: "numeric",
month: "numeric",
year: "numeric",
hour: "numeric",
minute: "2-digit",
};
type EventsProps = {
events: Event[];
lang: Lang
};
const Events: React.FC<EventsProps> = ({ events, lang }) => {
const isFi = lang === "fi";
const t = getTranslateFunc(lang);
const buttonText = `${t("Lue lisää")}\xa0`;
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) => ({
...e,
title: isFi ? e.title_fi : e.title_en,
description: isFi ? e.description_fi : e.description_en,
content: isFi ? e.content_fi : e.content_en,
location: isFi ? e.location_fi : e.location_en,
startDate: new Date(e.start_time).toLocaleString(locale, cardTimeOpts),
endDate: new Date(e.end_time).toLocaleString(locale, cardTimeOpts),
}));
return (
<CardSection id="#events">
{filteredEvents.map((event) => (
<Card
key={event.id}
title={event.title}
startTime={new Date(event.start_time).toLocaleString(locale, cardTimeOpts)}
text={event.description}
link={`/events/${event.id}`}
image={{
src: event.image || event.tags[0].icon,
alt: event.title,
}}
buttonOnClick={noop}
buttonText={buttonText}
data-e2e="event-card"
/>
))}
<aside>
<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>
);
};
export default Events;
+73
View File
@@ -0,0 +1,73 @@
import {
Card,
PageLink,
CardSection,
} from "@components/index";
import Post from "@models/Feed";
import noop from "@utils/noop";
import { Lang, getTranslateFunc } from "../../i18n";
const cardTimeOpts: Intl.DateTimeFormatOptions = {
day: "numeric",
month: "numeric",
year: "numeric",
hour: "numeric",
minute: "2-digit",
};
type PostsProps = {
feed: Post[];
lang: Lang
};
const Posts: React.FC<PostsProps> = ({ feed: posts, lang }) => {
const isFi = lang === "fi";
const t = getTranslateFunc(lang);
const buttonText = `${t("Lue lisää")}\xa0`;
const allNewsText = t("Lue tuoreimmat uutiset");
const allNewsDesc = `${t("uutiset")}\xa0`;
const meetingNotesText = t("Hallituksen pöytäkirjat");
const meetingNotesDesc = `${t("ja hallitukset kuulumiset")}\xa0`;
const galleryText = t("Kuvia tapahtumista");
const galleryDesc = `${t("kuvagalleriassa")}\xa0`;
const locale = isFi ? "fi-FI" : "en-GB";
const filteredFeed = posts.map((post) => ({
...post,
title: isFi ? post.title_fi : post.title_en,
description: isFi ? post.description_fi : post.description_en,
content: isFi ? post.content_fi : post.content_en,
publish_time: new Date(post.publish_time).toLocaleString(locale, cardTimeOpts),
}));
return (
<CardSection>
{filteredFeed.map((post) => (
<Card
key={post.id}
title={post.title}
text={post.description}
startTime={post.publish_time}
link={`/feed/${post.id}`}
buttonOnClick={noop}
buttonText={buttonText}
/>
))}
<aside>
<PageLink to="/kilta/toiminta#uutiset" desc={allNewsDesc}>
{allNewsText}
</PageLink>
<PageLink to="https://static.sahkoinsinoorikilta.fi/Poytakirjat/" desc={meetingNotesDesc}>
{meetingNotesText}
</PageLink>
<PageLink to="https://sik.kuvat.fi" desc={galleryDesc}>
{galleryText}
</PageLink>
</aside>
</CardSection>
);
};
export default Posts;
+30 -2
View File
@@ -1,6 +1,7 @@
import React from "react";
import styled from "styled-components";
import { Link } from "@components/index";
import Icon, { IconType } from "@components/Icon";
import colors from "@theme/colors";
import breakpoints from "@theme/breakpoints";
@@ -28,6 +29,7 @@ const Content = styled.div`
h4 {
color: ${colors.lightBlue};
padding: 1.5rem 0;
text-align: center;
}
a {
@@ -67,6 +69,27 @@ const Map = styled.div`
}
`;
const SomeContainer = styled.div`
display: flex;
flex-flow: column nowrap;
justify-content: space-between;
a {
display: flex;
flex-flow: row nowrap;
justify-content: center;
margin-right: 0.5rem;
color: inherit;
text-decoration: none;
}
svg{
width: 24px;
height: 24px;
fill: ${colors.white};
}
`;
const FooterContent: React.FC = () => (
<Content>
<div>
@@ -77,10 +100,8 @@ 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>
@@ -92,6 +113,13 @@ const FooterContent: React.FC = () => (
<Link to="https://sik.kuvat.fi">Kuvagalleria</Link>
<Link to="https://static.sahkoinsinoorikilta.fi/logot-ja-grafiikka/">Logot ja grafiikka</Link>
</div>
<div>
<SomeContainer>
<Icon name={IconType.Facebook} link="https://www.facebook.com/AaltoYliopistonSIK/" />
<Icon name={IconType.Instagram} link="https://www.instagram.com/sahkoinsinoorikilta/" />
<Icon name={IconType.LinkedIn} link="https://www.linkedin.com/groups/8103057/" />
</SomeContainer>
</div>
</Columns>
</MarginSpace>
</div>
-58
View File
@@ -1,58 +0,0 @@
import React from "react";
const Logo = (): JSX.Element => (
// eslint-disable-next-line react/no-danger
<head dangerouslySetInnerHTML={{
__html:
`<!--
-\` o\` .s h\` -///.
.o+/o \`d m /s\`\`\`y: -+:.
.///. \`-. -m\` m::/ \`d /s.\`.y: \`h..o/
/o.\`.y- ..\` :\` \`\`\`\` \` .://. ho+/o- \`y.
./+ +o\` .y- . \` .y- \`+//\`
/+y/ :/+/. .-::/+++++//:--\` o. \`.h--/
\` \`/s. hNNMMMMMMMMMMNNy o::d\` -o-
:+. . .\` mMMMMMMMMMMMMMMd --- \`/y++
-o+-o: \`-/oNMMMMMMMMMMMMMMNo:-\` :o:\` .
\`:--..\`-/\` \`-+ymNMMMMMMMMMMMMMMMMMMMMNmy+-\` \`\` \`\` -++++.
\`h+/y/: \`\` .odNMMMMMMMMMNNmmmmmmNNMMMMMMMMMNdo..:sdd/ d. .h
\`sh\` \`+mds:.:yNMMMMMMMmds+:-...\`\`...-:+ydmMMMMMMMNmMmh+- . o/--+o
\`\`\`\`\`\`+\` .hMMMMMNMMMMMMMms:. .:yMMMMNds:.\`-+hms .::. \`--
\`yo/y+/ :mMMMMMMMMMMMNy:\` \`\`... \`.+ymMNh+-\`.:sdMNds:\`\` -/oom:
.oos /NMMMMMMMMMMNy- \`-oydmNNM :h+:odNNms/.\`.+hmMNh+-\`.:sh- .\`\`y/.:\`
-s\` /MMMMMMMMMMMd: \`-smMMMMMMMM /MMMNh+-\`\`:sdMNms:.\`.+hNMNh: hy+/-
.NMMMMMMMMMMy\` -hMMMMMMMMMMM /MMo.\`./ymMNh+-\`\`:sdMNms:\`\`./\` \`
yMMMMMMMMMMy \`sMMMMMMMMMMMMM /MM+odNNmy/.\`.+yNMNh+- \`-odMNo
\`:odMMMMMMd\` \`hMMMMMMMMMMMMMM /MMMNdo-\`\`-odMMms/\` \`/ymMMdo-
\`NMMMMM- sMMMMMMMMMMMMMMM /MMN+\`\`/yNMNdo- \`-odMMMMMN\`
/MMMMMd .MMMMMMMMMMMMMMMM /MMMMNMMNy/\` \`/ymMMdmMMMMM/
sMMMMMo +MMMMMMMMMMMMMMMM /MMMMdmMMdsodMMNy/\` oMMMMMs
yMMMMM+ +MMMMMMMMMMMMMNdh /MMm/ \`oNMMMMMy. +MMMMMy
oMMMMMs .MMMMMMMMMMMs- /MMMMNMMNy/:smMMdo: sMMMMMo
/MMMMMd oMMMMMMMMN- /MMMMdmMMmo:\` .+hNMNNMMMMM/
\`+ /hy. \`NMMMMM: oMMMMMMM+ /MMm/\` .ohNMNy/. \`/ymMMMMM\` .-/+o-
.N\`m+oh \`/smMMMMMMm\` /NMMMMMo /MMMMNy/. \`/ymMNdo:\`\`-ohNMNy/. sNysd.
+yh..- yMMMMMMMMMMh\` .sNMMMN/ /MM//ymMNdo:\`\`-ohNMNy/.\`\`/ymMo \`-smh.
::--..:- .NMMMMMMMMMMh. .sNMMMh: /MMs:\`\`-ohNNmy/.\`./ymMNdo:\`\`-\` \`h- \`/.
/oNodm:s :NMMMMMMMMMMm/ \`+hNMNms: /MMNNmy/.\`./ymMNdo:\`\`-ohNNmo smys/-
\`dds. :NMMMMMMMMMMMh: \`.+hNMM :s-./ymMNdo-\`\`-ohNNmy/.\`./y- \`s.\`-/+
.s. ./s. -mMMMMMMMMMMMNh/. .:s .\` \`-odNNmy/.\`./ymMNdo\` -\` -.
/yhN:\`\` .yMMMMMmNMMMMMMmy/-\` \`:odN/ \`./ymMNdo-\`\`-ods\` \`oyy//d.
--\`ydyy- /ddo-\`-smMMMMMMMNmho/-\` \`mMNdo. \`:odNNmy/ \` oo-\`-+y-
oyo-.:s\` \`\` ./hmMMMMMMMMMNmhs+y/.\`.\` \`./yh/ :-./yy+
\` \`/hNy/:. ./sdmMMMMMMMMMM\`-+hm/ . ho\`\`\`-/
.s:my::-\`\`.-\` .-/NMMMMMMMdNMMM/ \`\`yydmsso\`
m- .sysho+ mMMMMMMMMMMMN: /h:\`:yd-
. hh\` :M- \`\` hNNNMMMMMmh+- \`-+o+\`:dy. -\`
/h+/yh\`.h+ .\` \`.-::://:. --/\` ym:/M+ \`+:
\`-:- -ms .md\`\`/\` .\`: syyys.\`ydhos/
s+ \`dymoyd\`-yss/ \`: .\` .\` \`syho- .M: yd oo
:y\`/Nm. /do/- /M\` Nm/.M: sd-\`/M:\`hy++d+
/- .y+oN: sd NyhhM: om/-+m- .:-\`
\`-:- o+ h/ /h: -/+:\`
-->`,
}}
/>
);
export default Logo;
+7
View File
@@ -26,6 +26,13 @@ const Sticky = styled.div<{ $isHidden?: boolean; $mobileMenuOpen?: boolean }>`
transition: all 200ms ease-out;
height: ${(p) => (p.$mobileMenuOpen ? "100vh" : "unset")};
/* tape to allow mobile dropdown menu scrolling */
@media screen and (max-width: ${breakpoints.mobile}) {
overflow-y: ${(p) => (p.$mobileMenuOpen ? "auto" : "visible")};
overflow-x: hidden;
-webkit-overflow-scrolling: touch; /* apparently some ios optimization for smoother scrolin' */
}
${(p) => (p.$isHidden ? (`
transition: all 200ms ease-in;
transform: translateY(-100%);
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react";
import Image from "next/image";
import Image from "next/legacy/image";
import styled from "styled-components";
import { Link } from "@components/index";
+5 -1
View File
@@ -23,7 +23,11 @@ const Container = styled.div`
}
`;
const Hero: React.FC = ({ children }) => (
type HeroProps = {
children: React.ReactNode;
};
const Hero: React.FC<HeroProps> = ({ children }) => (
<Container>
{children}
</Container>
+1
View File
@@ -35,6 +35,7 @@ type Colors = "darkBlue" | "lightTurquoise";
interface HeroAsideProps {
bgColor: Colors;
children: React.ReactNode;
}
// TODO: Color combos
@@ -6,6 +6,7 @@ import breakpoints from "@theme/breakpoints";
interface HeroPrimarySectionProps {
header: string;
text?: string;
children?: React.ReactNode;
}
const Section = styled.section`
@@ -22,6 +22,7 @@ const Item = styled.div`
interface HeroSecondarySectionItemProps {
note?: string;
children: React.ReactNode;
}
export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> = ({ note, children }) => (
@@ -36,6 +37,7 @@ export const HeroSecondarySectionItem: React.FC<HeroSecondarySectionItemProps> =
const Section = styled.section`
background-color: ${colors.green1};
color: ${colors.darkBlue};
width: 100%;
padding: 3rem;
h1 {
@@ -51,6 +53,7 @@ const Items = styled.div`
interface HeroSecondarySectionProps {
heading: string;
children: React.ReactNode;
}
const HeroSecondarySection: React.FC<HeroSecondarySectionProps> = ({ heading, children }) => (
+25 -7
View File
@@ -15,7 +15,7 @@ interface IconProps {
onClick?: React.MouseEventHandler<HTMLSpanElement>;
}
const nameToIcon = (name: IconType): JSX.Element | string => {
const nameToIcon = (name: IconType): JSX.Element | null => {
if (name === IconType.Facebook) {
return (
<svg
@@ -70,16 +70,34 @@ const nameToIcon = (name: IconType): JSX.Element | string => {
}
if (name === IconType.FinlandFlag) {
return (
<span role="img">
🇫🇮
</span>
<svg
role="img"
viewBox="0 0 640 480"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<title>Finland flag</title>
<path fill="#fff" d="M0 0h640v480H0z" />
<path fill="#002f6c" d="M0 174.5h640v131H0z" />
<path fill="#002f6c" d="M175.5 0h130.9v480h-131z" />
</svg>
);
}
if (name === IconType.GBFlag) {
return (
<span role="img">
🇬🇧
</span>
<svg
role="img"
viewBox="0 0 640 480"
width="64"
xmlns="http://www.w3.org/2000/svg"
>
<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="M241 0v480h160V0H241zM0 160v160h640V160H0z" />
<path fill="#C8102E" d="M0 193v96h640v-96H0zM273 0v480h96V0h-96z" />
</svg>
);
}
return null;
+5 -1
View File
@@ -6,7 +6,11 @@ const Box = styled.div`
text-align: center;
`;
const InfoBox: React.FC = ({ children }) => (
type InfoBoxProps = {
children?: React.ReactNode
};
const InfoBox: React.FC<InfoBoxProps> = ({ children }) => (
<Box>
{children}
</Box>
+18 -8
View File
@@ -2,6 +2,7 @@ import React from "react";
import NextJSLink, { LinkProps } from "next/link";
interface Props extends Omit<LinkProps, "href" | "as"> {
children?: React.ReactNode;
to: string;
template?: string;
target?: string;
@@ -15,18 +16,27 @@ const Link: React.FC<Props> = ({
}) => {
if (template) {
return (
<NextJSLink href={template} passHref={passHref} as={to} {...props}>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} />
</NextJSLink>
<NextJSLink
href={template}
passHref={passHref}
as={to}
{...props}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
);
}
if (to.startsWith("/") || to.startsWith("#")) {
return (
<NextJSLink href={to} passHref={passHref} {...props}>
{/* eslint-disable-next-line jsx-a11y/anchor-has-content */}
<a onClick={onClick} onMouseEnter={onMouseEnter} onMouseLeave={onMouseLeave} {...props} />
</NextJSLink>
<NextJSLink
href={to}
passHref={passHref}
{...props}
onClick={onClick}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
/>
);
}
+1
View File
@@ -6,6 +6,7 @@ import { Link } from "@components/index";
interface NavbarChildLinkProps {
to: string;
children: React.ReactNode;
}
const StyledLink = styled(Link)`
+1
View File
@@ -38,6 +38,7 @@ interface NavbarDropdownLinkProps {
to: string;
text: string;
exploded?: boolean; // if exploded, show items directly underneath without a dropdown menu
children?: React.ReactNode;
}
const NavbarDropdownLink: React.FC<NavbarDropdownLinkProps> = ({
+20 -24
View File
@@ -10,20 +10,29 @@ 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/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="/" text="New students " exploded={mobile}>
<NavbarChildLink to="/newStudent/fuksi">Fukseille</NavbarChildLink>
<NavbarChildLink to="/newStudent/fukseille_en">For Freshmen</NavbarChildLink>
<NavbarChildLink to="/newStudent/forExchangers">For Exchange/MSc students</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} />
</>
);
const Nav = styled.div`
flex: 1 0 auto;
display: flex;
@@ -47,6 +56,11 @@ const Nav = styled.div`
@media screen and (max-width: ${breakpoints.mobile}) {
justify-content: center;
margin-left: 0;
/* line 59 */
border-top: 2px solid ${colors.lightBlue}; /* Add line above */
padding-top: 0.5rem; /* Add some spacing */
padding-bottom: 0.5rem; /* Add some spacing */
cursor: pointer; /* Make entire nav clickable */
}
svg {
@@ -68,22 +82,12 @@ const DesktopContainer = styled.div`
}
`;
const SomeContainer = styled.div`
display: flex;
flex-flow: row nowrap;
a {
display: flex;
flex-flow: row nowrap;
justify-content: center;
margin: 1rem;
}
`;
const MobileMenu = styled.div`
display: flex;
margin: 0 1rem;
align-items: center;
cursor: pointer;
padding: 0 50%; /* Large clickable area horizontally cheeze */
span {
display: flex;
@@ -93,9 +97,6 @@ const MobileMenu = styled.div`
display: none;
}
@media screen and (max-width: ${breakpoints.mobile}) {
margin-left: 3rem;
}
`;
interface NavigationProps {
@@ -109,13 +110,8 @@ const Navigation: React.FC<NavigationProps> = ({ onMobileMenuOpen }) => {
<DesktopContainer>
{desktopItems}
</DesktopContainer>
<SomeContainer>
<Icon name={IconType.Facebook} link="https://www.facebook.com/AaltoYliopistonSIK/" />
<Icon name={IconType.Instagram} link="https://www.instagram.com/sahkoinsinoorikilta/" />
<Icon name={IconType.LinkedIn} link="https://www.linkedin.com/groups/8103057/" />
</SomeContainer>
<MobileMenu>
<Icon name={IconType.HamburgerMenu} onClick={onMobileMenuOpen} />
<MobileMenu onClick={onMobileMenuOpen}>
<Icon name={IconType.HamburgerMenu} />
</MobileMenu>
</Nav>
);
+2 -2
View File
@@ -4,8 +4,8 @@ import colors from "@theme/colors";
import { renderNavigationItems } from "./Navigation";
const Nav = styled.nav`
padding: 1rem 2rem;
padding: 1rem 1rem;
padding-bottom: 20rem;
a {
fill: ${colors.lightBlue};
color: ${colors.lightBlue};
+1
View File
@@ -6,6 +6,7 @@ import Link from "@components/Link";
interface PageLinkProps {
to: string;
desc: string;
children: React.ReactNode;
}
const StyledPageLink = styled.div`
+1
View File
@@ -52,6 +52,7 @@ const StyledSection = styled.section`
display: flex;
flex-direction: column;
justify-content: space-between;
z-index: 1;
@media screen and (max-width: ${breakpoints.mobile}) {
align-items: center;
+12
View File
@@ -0,0 +1,12 @@
import styled from "styled-components";
const StyledSelect = styled.select`
padding: 0.25rem;
margin: 0.5rem;
`;
const SelectWrapper = styled.div`
padding: 0.5rem;
`;
export { StyledSelect, SelectWrapper };
@@ -5,20 +5,18 @@ import Checkbox from "./Checkbox";
// See https://github.com/rjsf-team/react-jsonschema-form/blob/master/packages/core/src/components/widgets/CheckboxesWidget.js
function selectValue(value, selected, all) {
const selectValue = (value, selected, all) => {
const at = all.indexOf(value);
const updated = selected.slice(0, at).concat(value, selected.slice(at));
// As inserting values at predefined index positions doesn't work with empty
// arrays, we need to reorder the updated selection to match the initial order
return updated.sort((a, b) => all.indexOf(a) > all.indexOf(b));
}
};
function deselectValue(value, selected) {
return selected.filter((v) => v !== value);
}
const deselectValue = (value, selected) => selected.filter((v) => v !== value);
type CheckboxesProps = Omit<WidgetProps, "options"> & {
options: any;
options: Record<string, any>;
};
const CheckboxContainer = styled.div`
@@ -32,12 +30,13 @@ const Checkboxes: React.FC<CheckboxesProps> = ({
return (
<div className="checkboxes" id={id}>
{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 checkbox = (
<Checkbox
id={`${id}_${index}`}
id={key}
checked={checked}
disabled={disabled || itemDisabled || readonly}
autoFocus={autofocus && index === 0}
@@ -54,11 +53,11 @@ const Checkboxes: React.FC<CheckboxesProps> = ({
</Checkbox>
);
return inline ? (
<label key={index} className={`checkbox-inline ${disabledCls}`}>
<label key={key} className={`checkbox-inline ${disabledCls}`}>
{checkbox}
</label>
) : (
<CheckboxContainer key={index} className={disabledCls}>
<CheckboxContainer key={key} className={disabledCls}>
{checkbox}
</CheckboxContainer>
);
@@ -4,9 +4,7 @@ import ReactMde from "react-mde";
import { WidgetProps } from "@rjsf/core";
import MarkdownStyles from "@views/common/MarkdownStyles";
type MarkdownEditorWidgetProps = Omit<WidgetProps, "options"> & {
options: unknown;
};
type MarkdownEditorWidgetProps = WidgetProps;
const Container = styled.div`
background: white;
@@ -4,7 +4,14 @@ import { WidgetProps } from "@rjsf/core";
import RadioButton from "./RadioButton";
type RadioButtonWidgetProps = Omit<WidgetProps, "options"> & {
options: any;
options: {
enumOptions: {
value: string;
label: string;
}[];
enumDisabled: string[];
inline: boolean;
};
};
const RadioButtonContainer = styled.div`
@@ -31,7 +38,8 @@ const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
// this is a temporary fix for radio button rendering bug in React, facebook/react#7630.
return (
<div className="field-radio-group" id={id}>
{enumOptions.map((option, i) => {
{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" : "";
@@ -42,7 +50,7 @@ const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
required={required}
value={option.value}
disabled={disabled || itemDisabled || readonly}
autoFocus={autofocus && i === 0}
autoFocus={autofocus && index === 0}
onChange={() => onChange(option.value)}
onBlur={onBlur && ((event) => onBlur(id, event.target.value))}
onFocus={onFocus && ((event) => onFocus(id, event.target.value))}
@@ -52,11 +60,11 @@ const RadioButtonWidget: React.FC<RadioButtonWidgetProps> = (props) => {
);
return inline ? (
<label key={i} className={`radio-inline ${disabledCls}`}>
<label key={key} className={`radio-inline ${disabledCls}`}>
{radio}
</label>
) : (
<RadioButtonContainer key={i} className={disabledCls}>
<RadioButtonContainer key={key} className={disabledCls}>
{radio}
</RadioButtonContainer>
);
@@ -1,49 +1,72 @@
import React from "react";
import Checkbox from "@components/Widgets/Checkbox/Checkbox";
import { SignupFormQuestion } from "@models/Signup";
import { Lang } from "../../../i18n";
import {
Question, InputProps, optionTypes, SignupQuestionError,
InputProps, optionTypes, SignupQuestionError,
} from "./common";
interface OptionsWidgetProps {
inputProps: InputProps;
onChange: (value: Question[]) => void;
onChange: (value: SignupFormQuestion[]) => void;
}
class OptionsWidget extends React.Component<OptionsWidgetProps> {
handleListOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
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());
// eslint-disable-next-line no-param-reassign
questions[index].options = lst;
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);
};
handleTextOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
handleInfoTextOptionsChange = (questions: SignupFormQuestion[], index: number, lang: Lang): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value;
// eslint-disable-next-line no-param-reassign
questions[index].options = val as unknown as string[]; // TODO: Check type
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);
};
handleIntegerOptionsChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
handleIntegerOptionsChange = (questions: SignupFormQuestion[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value;
if (val !== "") {
const lst = val.split(";").map((p) => p.trimLeft());
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 = lst.splice(0, 2);
questions[index].options.enum = lst.splice(0, 2) as unknown[] as string[];
} else {
// eslint-disable-next-line no-param-reassign
questions[index].options = [];
questions[index].options.enum = [];
}
onChange(questions);
};
handleRequiredChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
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
@@ -67,7 +90,7 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
render(): JSX.Element {
const { inputProps } = this.props;
const {
type, value, questions, index,
value, type, questions, index,
} = inputProps;
if (!optionTypes.includes(type)) {
throw new SignupQuestionError(`Question widget type "${type}" not in types array.`);
@@ -82,25 +105,29 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
<>
<input
type="text"
placeholder="Write something informative"
value={questions[index].options}
onChange={this.handleTextOptionsChange(questions, index)}
placeholder="Write something informative in Finnish"
value={questions[index].description_fi}
onChange={this.handleInfoTextOptionsChange(questions, index, "fi")}
required
/>
<input
type="text"
placeholder="Write something informative in English"
value={questions[index].description_en}
onChange={this.handleInfoTextOptionsChange(questions, index, "en")}
required
/>
{this.requiredField()}
</>
);
}
if (type === "integer") {
const lst = value as string[];
const joinedValue = lst.join(";");
return (
<>
<input
type="text"
placeholder="Minimum;Maximum"
value={joinedValue}
value={value.enum.join(";")}
onChange={this.handleIntegerOptionsChange(questions, index)}
/>
{this.requiredField()}
@@ -109,15 +136,20 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
}
if (type === "radiobutton") {
const lst = value as string[];
const joinedValue = lst.join(";");
return (
<>
<input
type="text"
placeholder="Kyllä;ei;ehkä"
value={value.enumNames_fi.join(";")}
onChange={this.handleListOptionsChange(questions, index, "fi")}
required
/>
<input
type="text"
placeholder="Yes;no;maybe"
value={joinedValue}
onChange={this.handleListOptionsChange(questions, index)}
value={value.enumNames_en.join(";")}
onChange={this.handleListOptionsChange(questions, index, "en")}
required
/>
</>
@@ -125,15 +157,20 @@ class OptionsWidget extends React.Component<OptionsWidgetProps> {
}
if (type === "checkbox") {
const lst = value as string[];
const joinedValue = lst.join(";");
return (
<>
<input
type="text"
placeholder="A;B;C"
value={joinedValue}
onChange={this.handleListOptionsChange(questions, index)}
placeholder="Yksi;Kaksi;Kolme"
value={value.enumNames_fi.join(";")}
onChange={this.handleListOptionsChange(questions, index, "fi")}
required
/>
<input
type="text"
placeholder="One;Two;Three"
value={value.enumNames_en.join(";")}
onChange={this.handleListOptionsChange(questions, index, "en")}
required
/>
{this.requiredField()}
@@ -1,8 +1,9 @@
import React, { ReactNode } from "react";
import React from "react";
import styled from "styled-components";
import { Draggable } from "react-beautiful-dnd";
import Draggable from "@components/Draggable";
import colors from "@theme/colors";
import { Question, InputProps } from "./common";
import { SignupFormQuestion } from "@models/Signup";
import { Lang } from "../../../i18n";
import OptionsWidget from "./OptionsWidget";
import TypeWidget from "./TypeWidget";
import QuestionElement from "./Question";
@@ -16,77 +17,70 @@ const WidgetRow = styled.div`
`;
interface QuestionListProps {
questions: Question[];
innerRef: React.Ref<HTMLDivElement>;
placeholder: ReactNode;
onChange: (value: Question[]) => void;
questions: SignupFormQuestion[];
onChange: (value: SignupFormQuestion[]) => void;
}
class QuestionList extends React.Component<QuestionListProps> {
renderTextWidget = ({ questions, value, index }: InputProps): JSX.Element => (
<input type="text" value={value} onChange={this.handleNameInputChange(questions, index)} />
);
handleNameInputChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLInputElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value;
// eslint-disable-next-line no-param-reassign
questions[index].name = val;
const QuestionList: React.FC<QuestionListProps> = ({ questions, onChange }): JSX.Element => {
const handleDrag = (srcIndex, dstIndex) => {
const srcCopy = { ...questions[srcIndex] };
questions.splice(srcIndex, 1);
questions.splice(dstIndex, 0, srcCopy);
onChange(questions);
};
handleElementRemove = (questions: Question[], index: number) => (): void => {
const { onChange } = this.props;
const handleElementRemove = (index: number) => (): void => {
const newQuestions = [...questions];
newQuestions.splice(index, 1);
onChange(newQuestions);
};
renderQuestions(): JSX.Element[] {
const { questions, onChange } = this.props;
return questions.map((q, index) => {
const nameWidgetProps = {
value: q.name, type: "text", questions, index,
};
const nameWidget = this.renderTextWidget(nameWidgetProps);
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 dataProps = {
value: q.options, type: q.type, questions, index,
};
const optionsWidget = <OptionsWidget inputProps={dataProps} onChange={onChange} />;
const typeSelectWidget = <TypeWidget inputProps={dataProps} onChange={onChange} />;
return (
<Draggable draggableId={q.id} key={q.id} index={index}>
{(provided) => (
<WidgetRow
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
return (
<div data-e2e="admin-signup-question">
{questions.map((q, index) => {
const inputProps = {
value: q.options,
type: q.type,
questions,
index,
};
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}
>
<WidgetRow>
<QuestionElement
onClick={this.handleElementRemove(questions, index)}
onClick={handleElementRemove(index)}
>
{nameWidget}
<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>
</WidgetRow>
)}
</Draggable>
);
});
}
render(): JSX.Element {
const { placeholder, innerRef } = this.props;
return (
<div ref={innerRef} data-e2e="admin-signup-question">
{this.renderQuestions()}
{placeholder}
</div>
);
}
}
</Draggable>
);
})}
</div>
);
};
export default QuestionList;
@@ -1,11 +1,10 @@
import React from "react";
import styled from "styled-components";
import shortid from "shortid";
import { DragDropContext, Droppable } from "react-beautiful-dnd";
import colors from "@theme/colors";
import AddIcon from "@components/AddIcon";
import { SignupFormQuestion } from "@models/Signup";
import QuestionList from "./QuestionList";
import { Question } from "./common";
const Widget = styled.div`
& > button {
@@ -34,58 +33,39 @@ const AddQuestionButton = styled.button`
interface SignupQuestionsWidgetProps {
value: string;
onChange: (value: string) => void;
onFocus: () => void;
required: boolean;
disabled: boolean;
}
const SignupQuestionsWidget: React.FC<SignupQuestionsWidgetProps> = ({ value, onFocus, onChange }) => {
const onValueChange = (questions: Question[]) => {
const SignupQuestionsWidget: React.FC<SignupQuestionsWidgetProps> = ({ value, onChange }) => {
const onValueChange = (questions: SignupFormQuestion[]) => {
const newValue = JSON.stringify(questions);
onChange(newValue);
};
const handleNewRowClick = (questions) => () => {
const newRow: Question = {
const newRow: SignupFormQuestion = {
id: shortid.generate(),
name: `Question #${questions.length + 1}`,
options: [],
title_fi: `Kysymys #${questions.length + 1}`,
title_en: `Question #${questions.length + 1}`,
options: {
enum: [],
enumNames_fi: [],
enumNames_en: [],
},
type: "text",
};
const newQuestions: Question[] = questions.concat([newRow]);
const newQuestions: SignupFormQuestion[] = questions.concat([newRow]);
onValueChange(newQuestions);
};
const handleDragEnd = (questions: Question[]) => (result) => {
const srcIndex = result.source.index;
const dstIndex = result.destination.index;
const srcCopy = { ...questions[srcIndex] };
questions.splice(srcIndex, 1);
questions.splice(dstIndex, 0, srcCopy);
onValueChange(questions);
};
const questions = JSON.parse(value) as Question[];
const questions: SignupFormQuestion[] = JSON.parse(value);
return (
<Widget>
<DragDropContext
onDragEnd={handleDragEnd(questions)}
onDragStart={onFocus}
>
<Droppable droppableId="questions">
{(provided) => (
<QuestionList
{...provided.droppableProps}
innerRef={provided.innerRef}
questions={questions}
onChange={onValueChange}
placeholder={provided.placeholder}
/>
)}
</Droppable>
</DragDropContext>
<QuestionList
questions={questions}
onChange={onValueChange}
/>
<AddQuestionButton type="button" onClick={handleNewRowClick(questions)} data-e2e="admin-signup-new-question">
<AddIcon />
New Question
@@ -1,32 +1,29 @@
import React from "react";
import { Question, InputProps, optionTypes } from "./common";
import { SignupFormQuestion } from "@models/Signup";
import { InputProps, optionTypes } from "./common";
interface TypeWidgetProps {
inputProps: InputProps;
onChange: (value: Question[]) => void;
onChange: (value: SignupFormQuestion[]) => void;
}
class TypeWidget extends React.Component<TypeWidgetProps> {
handleTypeChange = (questions: Question[], index: number): React.ChangeEventHandler<HTMLSelectElement> => (event) => {
const { onChange } = this.props;
const val = event.target.value as Question["type"];
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);
};
render(): JSX.Element {
const { inputProps } = this.props;
const { type, questions, index } = inputProps;
const options = optionTypes.map((t) => (
<option key={t} value={t}>{t}</option>
));
return (
<select onChange={this.handleTypeChange(questions, index)} value={type} name="type">
{options}
</select>
);
}
}
const { questions, type, index } = inputProps;
const options = optionTypes.map((t) => (
<option key={t} value={t}>{t}</option>
));
return (
<select onChange={handleTypeChange(questions, index)} value={type} name="type">
{options}
</select>
);
};
export default TypeWidget;
@@ -1,19 +1,23 @@
import type { SignupFormQuestion } from "@models/Signup";
export interface Question {
id: string;
name: string;
type: OptionTypes;
options: string[];
enum?: string[];
enumNames?: string[];
description?: string;
required?: boolean;
}
export interface InputProps {
index: number;
value: string | string[];
questions: Question[];
value: SignupFormQuestion["options"];
questions: SignupFormQuestion[];
type: string;
}
type OptionTypes =
export type OptionTypes =
"text" |
"info" |
"integer" |
-56
View File
@@ -1,56 +0,0 @@
import { useRef } from "react";
import useSWR from "swr";
import isDeepEqual from "fast-deep-equal/react";
import axios, { AxiosRequestConfig } from "axios";
import Event from "@models/Event";
import { getAuthHeader } from "@utils/auth";
import { URL, Options } from "@api/eventApi";
const fetcher = (url: string, config: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
const generateFetchParams = (id = "", options: Options = {}) => {
const url = `${URL}${id}`;
const {
auth, since, limit, offset,
} = options;
return {
url,
config: {
params: {
since,
limit,
offset,
},
headers: auth ? { Authorization: getAuthHeader() } : null,
},
};
};
interface FetchArguments {
initialData?: Event | Event[],
id?: string;
options?: Options
}
const useFetchEvents = ({
initialData,
id = "",
options = {},
}: FetchArguments) => {
const { url, config } = generateFetchParams(id, options);
// Use ref, since config dependency is non-primitive => without this we have infinite fetch loop
const configRef = useRef(config);
if (!isDeepEqual(configRef.current, config)) {
configRef.current = config;
}
const { data, error } = useSWR([url, configRef.current], fetcher, { initialData });
return {
data: data?.results || data,
error,
};
};
export default useFetchEvents;
-53
View File
@@ -1,53 +0,0 @@
import { useRef } from "react";
import useSWR from "swr";
import isDeepEqual from "fast-deep-equal/react";
import axios, { AxiosRequestConfig } from "axios";
import Post from "@models/Feed";
import { getAuthHeader } from "@utils/auth";
import { URL, Options } from "@api/feedApi";
const feedFetcher = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
const generateFetchParams = (id = "", options: Options = {}) => {
const url = `${URL}${id}`;
const { auth, limit, offset } = options;
return {
url,
config: {
params: {
limit,
offset,
},
headers: auth ? { Authorization: getAuthHeader() } : null,
},
};
};
interface FetchArguments {
initialData?: Post | Post[],
id?: string;
options?: Options
}
const useFetchFeed = ({
initialData,
id = "",
options = {},
}: FetchArguments) => {
const { url, config } = generateFetchParams(id, options);
// Use ref, since config dependency is non-primitive => without this we have infinite fetch loop
const configRef = useRef(config);
if (!isDeepEqual(configRef.current, config)) {
configRef.current = config;
}
const { data, error } = useSWR([url, configRef.current], feedFetcher, { initialData });
return {
data: data?.results || data,
error,
};
};
export default useFetchFeed;
-56
View File
@@ -1,56 +0,0 @@
import { useRef } from "react";
import useSWR from "swr";
import isDeepEqual from "fast-deep-equal/react";
import axios, { AxiosRequestConfig } from "axios";
import JobAd from "@models/JobAd";
import { getAuthHeader } from "@utils/auth";
import { URL, Options } from "@api/jobAdApi";
const jobAdFetcher = (url: string, config?: AxiosRequestConfig) => axios.get(url, config).then((res) => res.data);
const generateFetchParams = (id = "", options: Options = {}) => {
const url = `${URL}${id}`;
const {
since, limit, offset, auth,
} = options;
return {
url,
config: {
params: {
since,
limit,
offset,
},
headers: auth ? { Authorization: getAuthHeader() } : null,
},
};
};
interface FetchArguments {
initialData?: JobAd | JobAd[],
id?: string;
options?: Options;
}
const useFetchJobAds = ({
initialData,
id = "",
options = {},
}: FetchArguments) => {
const { url, config } = generateFetchParams(id, options);
// Use ref, since config dependency is non-primitive => without this we have infinite fetch loop
const configRef = useRef(config);
if (!isDeepEqual(configRef.current, config)) {
configRef.current = config;
}
const { data, error } = useSWR([url, configRef.current], jobAdFetcher, { initialData });
return {
data: data?.results || data,
error,
};
};
export default useFetchJobAds;
+14
View File
@@ -0,0 +1,14 @@
import { useEffect, useState } from "react";
const useIsTouchDevice = () => {
const [isTouchDevice, setTouchDevice] = useState(false);
useEffect(() => {
// simple way to check whether the device support touch (it doesn't check all fallback, it supports only modern browsers)
if (window !== undefined && "ontouchstart" in window) {
setTouchDevice(true);
}
}, []);
return isTouchDevice;
};
export default useIsTouchDevice;
+13 -6
View File
@@ -1,10 +1,10 @@
import React, {
createContext, useContext, useReducer,
createContext, useContext, useMemo, useReducer,
} from "react";
import fi from "./locales/fi/common.json";
import en from "./locales/en/common.json";
type Lang = "fi" | "en";
export type Lang = "fi" | "en";
const LOCAL_STORAGE_KEY = "locale";
type TranslateFunc = (key: string) => string;
@@ -26,6 +26,11 @@ const translateFi: TranslateFunc = (key) => {
return res || key;
};
export const getTranslateFunc = (language: Lang): TranslateFunc => {
if (language === "en") return translateEn;
return translateFi;
};
interface Store {
language: Lang;
changeLanguage: React.Dispatch<Lang>,
@@ -62,8 +67,7 @@ const Reducer = (state: Store, action: Lang) => {
};
const LocaleContext = createContext(initialState);
const LocaleStore: React.FC = ({ children }) => {
const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState);
const changeLanguage = (action: Lang) => {
dispatch(action);
@@ -73,8 +77,11 @@ const LocaleStore: React.FC = ({ children }) => {
// Just ignore if fails to store value in user's browser
}
};
const localeValue = useMemo(() => ({ ...state, changeLanguage }), [state]);
return (
<LocaleContext.Provider value={{ ...state, changeLanguage }}>
<LocaleContext.Provider value={localeValue}>
{children}
</LocaleContext.Provider>
);
@@ -84,7 +91,7 @@ export default LocaleStore;
const useTranslation = () => {
const { language, changeLanguage } = useContext(LocaleContext);
const t = language === "en" ? translateEn : translateFi;
const t = getTranslateFunc(language);
return {
t,
+11 -1
View File
@@ -6,7 +6,17 @@
"Päättyy": "Ends at",
"Lataa lisää": "Load more",
"Tapahtumat": "Events",
"Kaikki tapahtumat": "All events",
"löydät tapahtumakalenterista": "you can find all events from the event calendar",
"Uutiset": "News",
"uutiset": "news",
"Lue tuoreimmat uutiset": "Read news",
"Hallituksen pöytäkirjat": "Board meeting records",
"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",
@@ -40,7 +50,7 @@
"Se aukeaa":
"Signup opens at",
"Ilmoittauminen sulkeutuu":
"Ilmoittautuminen sulkeutuu":
"Signup closes at",
"Ilmoittauminen on umpeutunut!":
+21 -3
View File
@@ -1,19 +1,37 @@
import { Question } from "@components/Widgets/SignupQuestionsWidget/common";
import { OptionTypes } from "@components/Widgets/SignupQuestionsWidget/common";
export interface Signup {
id?: number;
id?: number; // Database id for completed signup
submit_id?: string; // Signup request idempotency key
signupForm_id: number;
answer: string;
}
// Describes how forms are stored in backend
export interface SignupFormQuestion {
id: string;
title_fi: string;
title_en: string;
description_fi?: string;
description_en?: string;
type: OptionTypes;
options: {
enum: string[];
enumNames_fi: string[];
enumNames_en: string[];
};
required?: boolean;
}
export interface SignupForm {
id?: number;
title_fi: string;
title_en: string;
visible: boolean;
isOpen: boolean;
start_time: string;
end_time: string;
questions: Question[];
email_content: string;
questions: SignupFormQuestion[];
signups: string[];
quota: number;
schema: {
+36 -23
View File
@@ -1,16 +1,21 @@
import React from "react";
import { DndProvider } from "react-dnd";
import { HTML5Backend } from "react-dnd-html5-backend";
import { TouchBackend } from "react-dnd-touch-backend";
import Head from "next/head";
import { AppProps } from "next/app";
import styled, { createGlobalStyle } from "styled-components";
import { ToastContainer } from "react-toastify";
import colors from "@theme/colors";
import breakpoints from "@theme/breakpoints";
import LocaleStore from "../i18n";
import "react-mde/lib/styles/css/react-mde-all.css";
import "react-toastify/dist/ReactToastify.css";
import "normalize.css";
import useIsTouchDevice from "@hooks/useIsTouchDevice";
import LocaleStore from "../i18n";
const fontFamily = "'Montserrat', sans-serif";
const fontSize = 12; // 16px
const lineHeight = 1.5;
@@ -127,27 +132,35 @@ const AppContainer = styled.div`
background-color: ${colors.white};
`;
const Web20App = ({ Component, pageProps }: AppProps): JSX.Element => (
<>
<Head>
<meta httpEquiv="Content-Type" content="text/html; charset=utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aalto-yliopiston Sähköinsinöörikilta ry</title>
<meta
name="description"
content="Aalto-yliopiston Sähköinsinöörikilta ry on Otaniemessä vaikuttava opiskelijajärjestö, joka on perustettu vuonna 1921. Kilta järjestää kaikenlaista toimintaa liittyen opintoihin ja vapaa-ajan viettoon."
/>
<meta name="keywords" content="SIK AYY" />
</Head>
<GlobalCommonStyles />
<LocaleStore>
<AppContainer>
<Component {...pageProps} />
</AppContainer>
</LocaleStore>
<ToastContainer position="bottom-right" />
</>
);
const Web20App = ({ Component, pageProps }: AppProps): JSX.Element => {
const isTouchDevice = useIsTouchDevice();
// Assigning backend based on touch support on the device
const backendForDND = isTouchDevice ? TouchBackend : HTML5Backend;
return (
<>
<Head>
<meta httpEquiv="Content-Type" content="text/html; charset=utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Aalto-yliopiston Sähköinsinöörikilta ry</title>
<meta
name="description"
content="Aalto-yliopiston Sähköinsinöörikilta ry on Otaniemessä vaikuttava opiskelijajärjestö, joka on perustettu vuonna 1921. Kilta järjestää kaikenlaista toimintaa liittyen opintoihin ja vapaa-ajan viettoon."
/>
<meta name="keywords" content="SIK AYY" />
</Head>
<GlobalCommonStyles />
<LocaleStore>
<AppContainer>
<DndProvider backend={backendForDND}>
<Component {...pageProps} />
</DndProvider>
</AppContainer>
</LocaleStore>
<ToastContainer position="bottom-right" />
</>
);
};
export default Web20App;
+6 -13
View File
@@ -1,13 +1,12 @@
import React from "react";
import Document, {
Html, Head, Main, NextScript, DocumentContext, DocumentInitialProps,
Html, Head, Main, NextScript, DocumentContext,
} from "next/document";
import { ServerStyleSheet } from "styled-components";
import Favicons from "@components/Favicons";
import HTMLLogo from "@components/HTMLLogo";
export default class MyDocument extends Document<{ styleTags: unknown }> {
static async getInitialProps(ctx: DocumentContext): Promise<DocumentInitialProps> {
export default class MyDocument extends Document {
static async getInitialProps(ctx: DocumentContext) {
const sheet = new ServerStyleSheet();
const originalRenderPage = ctx.renderPage;
try {
@@ -17,12 +16,7 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
const initialProps = await Document.getInitialProps(ctx);
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
styles: [initialProps.styles, sheet.getStyleElement()],
};
} finally {
sheet.seal();
@@ -30,16 +24,15 @@ export default class MyDocument extends Document<{ styleTags: unknown }> {
}
render(): JSX.Element {
const { styleTags } = this.props;
const { styles } = this.props;
return (
<Html lang="fi">
<Head>
<HTMLLogo />
<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>
{styleTags}
{styles}
<Main />
<NextScript />
</body>
+65
View File
@@ -0,0 +1,65 @@
import { NextPage, NextPageContext } from "next";
import NextErrorComponent, { ErrorProps } from "next/error";
import * as Sentry from "@sentry/nextjs";
type MyErrorProps = ErrorProps & {
hasGetInitialPropsRun: boolean;
err: Error & {
statusCode?: number;
};
};
const MyError: NextPage<MyErrorProps> = ({ statusCode, hasGetInitialPropsRun, err }) => {
if (!hasGetInitialPropsRun && err) {
// getInitialProps is not called in case of
// https://github.com/vercel/next.js/issues/8592. As a workaround, we pass
// err via _app.js so it can be captured
Sentry.captureException(err);
// Flushing is not required in this case as it only happens on the client
}
return <NextErrorComponent statusCode={statusCode} />;
};
MyError.getInitialProps = async (context: NextPageContext) => {
const { err, asPath } = context;
const defaultProps = await NextErrorComponent.getInitialProps(context);
const errorInitialProps: MyErrorProps = {
...defaultProps,
err,
// Workaround for https://github.com/vercel/next.js/issues/8592, mark when
hasGetInitialPropsRun: true,
};
// Running on the server, the response object (`res`) is available.
//
// Next.js will pass an err on the server if a page's data fetching methods
// threw or returned a Promise that rejected
//
// Running on the client (browser), Next.js will provide an err if:
//
// - a page's `getInitialProps` threw or returned a Promise that rejected
// - an exception was thrown somewhere in the React lifecycle (render,
// componentDidMount, etc) that was caught by Next.js's React Error
// Boundary. Read more about what types of exceptions are caught by Error
// Boundaries: https://reactjs.org/docs/error-boundaries.html
if (err) {
Sentry.captureException(err);
// Flushing before returning is necessary if deploying to Vercel, see
// https://vercel.com/docs/platform/limits#streaming-responses
await Sentry.flush(2000);
return errorInitialProps;
}
// If this point is reached, getInitialProps was called without any
// information about what the error might be. This is unexpected and may
// indicate a bug introduced in Next.js, so record it in Sentry
Sentry.captureException(
new Error(`_error.js getInitialProps missing data at path: ${asPath}`),
);
await Sentry.flush(2000);
return errorInitialProps;
};
export default MyError;
+5 -5
View File
@@ -19,7 +19,7 @@ const widgets = {
markdownEditor: MarkdownEditorWidget,
};
const buildSchema = (formData: Event, signupForms: SignupForm[], tags: Tag[]) => {
const buildSchema = (formData: Event | undefined, signupForms: SignupForm[], tags: Tag[]) => {
const date = new Date(); const
tomorrowDate = new Date();
const currentDatetime = date.toISOString();
@@ -180,11 +180,11 @@ const EventCreatePage: NextPage = () => {
useEffect(() => {
TagApi.getTags()
.then((res) => setTags(res))
.catch((err) => setError(err));
.catch((err) => setError(err.message));
SignupApi.getForms(true)
.then((res) => setSignupForms(res))
.catch((err) => setError(err));
.catch((err) => setError(err.message));
const eventId = id && Number(id);
if (eventId !== undefined) {
@@ -194,7 +194,7 @@ const EventCreatePage: NextPage = () => {
tags: (res.tags).map((inst) => inst.id) as any,
signupForm: (res.signupForm).map((inst) => inst.id) as any,
}))
.catch((err) => setError(err));
.catch((err) => setError(err.message));
}
}, [id]);
@@ -230,7 +230,7 @@ const EventCreatePage: NextPage = () => {
}
} catch (err) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
setError(err);
setError(err.message);
}
};
+105 -37
View File
@@ -1,6 +1,7 @@
import React from "react";
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import { formatRelative } from "date-fns";
import useSWR from "swr";
import { formatRelative, formatISO } from "date-fns";
import { toast } from "react-toastify";
import styled from "styled-components";
import AdminListCommon from "@views/admin/AdminListCommon";
@@ -8,7 +9,8 @@ import { Button, Link } from "@components/index";
import AddLink from "@components/AddLink";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import useFetchEvents from "@hooks/useFetchEvents";
import { fetcher, APIPath, API } from "@api/backend";
import { StyledSelect, SelectWrapper } from "@components/Select";
const URL = "/admin/events";
@@ -32,47 +34,113 @@ const confirmDelete = async (event: Event) => {
}
};
const renderData = (events: Event[]) => {
if (!events || events.length === 0) {
const Renderer: React.FC = () => {
const api: API = { path: APIPath.EVENTS, authenticated: true };
const { data: events, error } = useSWR<Event[]>(api, fetcher);
const [sort, setSort] = useState<string>("start_time");
const [order, setOrder] = useState<string>("descending");
const [filter, setFilter] = useState<string>("all");
const eventSort = (a, b) => {
let result = 0;
if (order === "descending") {
if (["start_time", "end_time"].includes(sort)) {
result = new Date(b[sort]).getTime() - new Date(a[sort]).getTime();
} else if (sort === "id") {
result = b[sort] - a[sort];
}
} else if (order === "ascending") {
if (["start_time", "end_time"].includes(sort)) {
result = new Date(a[sort]).getTime() - new Date(b[sort]).getTime();
} else if (sort === "id") {
result = a[sort] - b[sort];
}
}
return result;
};
const dateFilter = (a) => {
let result = true;
if (filter === "upcoming") {
result = new Date(a.end_time).getTime() > Date.now();
} else if (filter === "past") {
result = new Date(a.end_time).getTime() < Date.now();
}
return result;
};
useEffect(() => {
}, [sort, order, filter, events]);
if (error) {
console.error(error);
return (
<div>
Failed loading events.
</div>
);
}
if (!events?.length) {
return <div>No events.</div>;
}
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
</tr>
</thead>
<tbody>
{events.map((event) => (
<tr key={event.id}>
<td><Link to={`${URL}/${event.id}`}>{event.title_fi}</Link></td>
<td>{formatRelative(new Date(event.start_time), new Date())}</td>
<td>{formatRelative(new Date(event.end_time), new Date())}</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(event)}>
Delete
</StyledButton>
</td>
<div>
<SelectWrapper>
Sort by:
<StyledSelect name="" onChange={(e) => setSort(e.target.value)}>
<option value="start_time">Start time</option>
<option value="end_time">End time</option>
<option value="id">Creation order</option>
</StyledSelect>
Order:
<StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
<option value="descending">Descending</option>
<option value="ascending">Ascending</option>
</StyledSelect>
Filter:
<StyledSelect name="" onChange={(e) => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="upcoming">Upcoming</option>
<option value="past">Past</option>
</StyledSelect>
</SelectWrapper>
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
</tr>
))}
</tbody>
</table>
</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>{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>
);
};
const AdminEventPage: NextPage = () => {
const { data } = useFetchEvents({ options: { auth: true } });
return (
<AdminListCommon>
<h1>Events</h1>
<AddLink text="Create event" to={`${URL}/create`} data-e2e="create-event" />
{renderData(data)}
</AdminListCommon>
);
};
const AdminEventPage: NextPage = () => (
<AdminListCommon>
<h1>Events</h1>
<AddLink text="Create event" to={`${URL}/create`} data-e2e="create-event" />
<Renderer />
</AdminListCommon>
);
export default AdminEventPage;
+4 -4
View File
@@ -146,16 +146,16 @@ const FeedCreatePage: NextPage = () => {
useEffect(() => {
TagApi.getTags()
.then((res) => setTags(res))
.catch((err) => setError(err));
.catch((err) => setError(err.message));
const feedId = id && Number(id);
if (feedId !== undefined) {
FeedApi.getPost(feedId, { auth: true })
FeedApi.getPost(feedId, true)
.then((res) => setFormData({
...res,
tags: (res.tags).map((inst) => inst.id) as any,
}))
.catch((err) => setError(err));
.catch((err) => setError(err.message));
}
}, [id]);
@@ -179,7 +179,7 @@ const FeedCreatePage: NextPage = () => {
}
} catch (err) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
setError(err);
setError(err.message);
}
};
+74 -38
View File
@@ -1,6 +1,7 @@
import React from "react";
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import { formatRelative } from "date-fns";
import useSWR from "swr";
import { formatRelative, formatISO } from "date-fns";
import { toast } from "react-toastify";
import styled from "styled-components";
import AdminListCommon from "@views/admin/AdminListCommon";
@@ -8,7 +9,8 @@ import { Button, Link } from "@components/index";
import AddLink from "@components/AddLink";
import Post from "@models/Feed";
import PostApi from "@api/feedApi";
import useFetchFeed from "@hooks/useFetchFeed";
import { fetcher, APIPath, API } from "@api/backend";
import { SelectWrapper, StyledSelect } from "@components/Select";
const URL = "/admin/feed";
@@ -32,47 +34,81 @@ const confirmDelete = async (post: Post) => {
}
};
const renderData = (feed: Post[]) => {
if (!feed || feed.length === 0) {
return <div>No posts.</div>;
const Renderer: React.FC = () => {
const api: API = { path: APIPath.FEED, authenticated: true };
const { data: feed, error } = useSWR<Post[]>(api, fetcher);
const [order, setOrder] = useState<string>("descending");
const feedSort = (a, b) => {
let result = 0;
if (order === "descending") {
result = new Date(b.publish_time).getTime() - new Date(a.publish_time).getTime();
} else if (order === "ascending") {
result = new Date(a.publish_time).getTime() - new Date(b.publish_time).getTime();
}
return result;
};
useEffect(() => {
}, [order, feed]);
if (error) {
console.error(error);
return (
<div>
Failed loading feed
</div>
);
}
if (!feed?.length) {
return (
<div>No posts.</div>
);
}
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Publish time</th>
</tr>
</thead>
<tbody>
{feed.map((post) => (
<tr key={post.id}>
<td><Link to={`${URL}/${post.id}`}>{post.title_fi}</Link></td>
<td>{post.description_fi}</td>
<td>{formatRelative(new Date(post.publish_time), new Date())}</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(post)}>
Delete
</StyledButton>
</td>
<div>
<SelectWrapper>
Order:
<StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
<option value="descending">Descending</option>
<option value="ascending">Ascending</option>
</StyledSelect>
</SelectWrapper>
<table>
<thead>
<tr>
<th>Title</th>
<th>Description</th>
<th>Publish time</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody>
{feed.sort(feedSort).map((post) => (
<tr key={post.id}>
<td><Link to={`${URL}/${post.id}`}>{post.title_fi}</Link></td>
<td>{post.description_fi}</td>
<td>{formatISO(new Date(post.publish_time), { representation: "date" })}</td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(post)}>
Delete
</StyledButton>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
};
const AdminFeedPage: NextPage = () => {
const { data } = useFetchFeed({ options: { auth: true } });
return (
<AdminListCommon>
<h1>Feed</h1>
<AddLink text="Create news post" to={`${URL}/create`} />
{renderData(data)}
</AdminListCommon>
);
};
const AdminFeedPage: NextPage = () => (
<AdminListCommon>
<h1>Feed</h1>
<AddLink text="Create news post" to={`${URL}/create`} />
<Renderer />
</AdminListCommon>
);
export default AdminFeedPage;
+2 -2
View File
@@ -123,7 +123,7 @@ const JobAdCreatePage: NextPage = () => {
if (jobId !== undefined) {
JobAdApi.getJobAd(jobId, true)
.then((res) => setFormData(res))
.catch((err) => setError(err));
.catch((err) => setError(err.message));
}
}, [id]);
@@ -143,7 +143,7 @@ const JobAdCreatePage: NextPage = () => {
}
} catch (err) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
setError(err);
setError(err.message);
}
};
+23 -15
View File
@@ -1,14 +1,15 @@
import React from "react";
import { NextPage } from "next";
import { formatRelative } from "date-fns";
import useSWR from "swr";
import { formatRelative, formatISO } from "date-fns";
import { toast } from "react-toastify";
import styled from "styled-components";
import AdminListCommon from "@views/admin/AdminListCommon";
import { Button, Link } from "@components/index";
import AddLink from "@components/AddLink";
import JobAd from "@models/JobAd";
import useFetchJobAds from "@hooks/useFetchJobAds";
import JobAdApi from "@api/jobAdApi";
import { fetcher, APIPath, API } from "@api/backend";
const URL = "/admin/jobads";
@@ -32,8 +33,18 @@ const confirmDelete = async (jobad: JobAd) => {
}
};
const renderData = (jobAds: JobAd[]) => {
if (!jobAds || jobAds.length === 0) {
const Renderer: React.FC = () => {
const api: API = { path: APIPath.JOBADS, authenticated: true };
const { data: jobAds, error } = useSWR<JobAd[]>(api, fetcher);
if (error) {
console.error(error);
return (
<div>
Failed loading jobads
</div>
);
}
if (!jobAds?.length) {
return <div>No advertisements.</div>;
}
@@ -53,7 +64,7 @@ const renderData = (jobAds: JobAd[]) => {
<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>
@@ -68,15 +79,12 @@ const renderData = (jobAds: JobAd[]) => {
);
};
const AdminJobAdPage: NextPage = () => {
const { data } = useFetchJobAds({ options: { auth: true } });
return (
<AdminListCommon>
<h1>Job advertisements</h1>
<AddLink text="Create job ad" to={`${URL}/create`} />
{renderData(data)}
</AdminListCommon>
);
};
const AdminJobAdPage: NextPage = () => (
<AdminListCommon>
<h1>Job advertisements</h1>
<AddLink text="Create job ad" to={`${URL}/create`} />
<Renderer />
</AdminListCommon>
);
export default AdminJobAdPage;
+8 -6
View File
@@ -1,8 +1,11 @@
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";
import { generateToken, setTokenCookie, isAuthenticated } from "@utils/auth";
import { authenticate, login } from "@api/auth";
import AdminPageWrapper from "@views/common/AdminPageWrapper";
const Main = styled.div`
@@ -20,8 +23,8 @@ const AdminLoginPage: NextPage = () => {
const next = router.query.next as string || DEFAULT_REDIRECT;
useEffect(() => {
isAuthenticated().then((res) => {
if (res) {
authenticate().then((authResult) => {
if (authResult) {
router.push(next);
}
});
@@ -30,8 +33,7 @@ const AdminLoginPage: NextPage = () => {
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
try {
const token = await generateToken(username, password);
setTokenCookie(token);
await login(username, password);
router.push(next);
} catch (err) {
setError("Failed to log in!");
+2 -2
View File
@@ -1,12 +1,12 @@
import { NextPage } from "next";
import { useRouter } from "next/router";
import { deleteTokenCookie } from "@utils/auth";
import { deleteTokenCookies } from "@utils/auth";
const AdminLogoutPage: NextPage = () => {
const router = useRouter();
// client-side-only code
if (typeof window !== "undefined") {
deleteTokenCookie();
deleteTokenCookies();
router.push("/admin/login");
}
return null;
+5 -5
View File
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react";
import { NextPage } from "next";
import { useRouter } from "next/router";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
import { SignupForm } from "@models/Signup";
import { SignupForm, SignupFormQuestion } from "@models/Signup";
import SignupApi from "@api/signupApi";
import DatetimeWidget from "@components/Widgets/DatetimeWidget";
import SignupQuestionsWidget from "@components/Widgets/SignupQuestionsWidget/SignupQuestionsWidget";
@@ -110,7 +110,7 @@ const SignupCreatePage: NextPage = () => {
useEffect(() => {
const suId = id && Number(id);
if (suId !== undefined) {
if (suId !== undefined && !Number.isNaN(suId)) {
SignupApi.getForm(suId, true)
.then((res) => {
setFormData({
@@ -118,13 +118,13 @@ const SignupCreatePage: NextPage = () => {
questions: JSON.stringify(res.questions) as any,
});
})
.catch((err) => setError(err));
.catch((err) => setError(err.message));
}
}, [id]);
const onSubmit = async (data: any) => {
try {
const questions = JSON.parse(data.formData.questions);
const questions: SignupFormQuestion[] = JSON.parse(data.formData.questions);
const payload: SignupForm = {
...data.formData,
questions,
@@ -150,7 +150,7 @@ const SignupCreatePage: NextPage = () => {
}
} catch (err) {
toast.error("Uh oh! Something went wrong! Try again later. 😟");
setError(err);
setError(err.message);
}
};
+3 -3
View File
@@ -5,7 +5,7 @@ import { toast } from "react-toastify";
import AdminCreateCommon from "@views/admin/AdminCreateCommon";
import MarkdownEditorWidget from "@components/Widgets/MarkdownEditorWidget";
import { SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi";
import SignupApi, { EmailRequest } from "@api/signupApi";
const widgets = {
markdownEditor: MarkdownEditorWidget,
@@ -67,11 +67,11 @@ const SignupEmailPage: NextPage = () => {
const onSubmit = async (data) => {
try {
const payload = data.formData;
const payload: EmailRequest = data.formData;
await SignupApi.signupFormSendEmail(payload, Number(id));
toast.success("Email sent successfully 😎");
} catch (err) {
setError(err);
setError(err.message);
toast.error("Uh oh! Something went wrong! Try again later. 😟");
}
};
+37 -24
View File
@@ -26,13 +26,19 @@ const SignupEmailPage: NextPage = () => {
const { id } = router.query;
useEffect(() => {
const formId = Number(id);
SignupApi.getForm(formId, true)
.then((res) => setSignupForm(res));
SignupApi.getSignups(formId).then((res) => setSignups(res));
const formId = id && Number(id);
if (formId !== undefined && !Number.isNaN(formId)) {
SignupApi.getForm(formId, true).then((res) => {
setSignupForm(res);
});
SignupApi.getSignups(formId).then((res) => {
setSignups(res);
});
}
}, [id]);
const title = signupForm ? signupForm.title_fi : "Loading...";
const confirmDelete = async (signup: Signup, question: any) => {
if (window.confirm(`Delete: ${signup.id}: ${signup.answer[question.id]}; Are you sure?`) === true) {
try {
@@ -45,27 +51,25 @@ const SignupEmailPage: NextPage = () => {
}
};
const title = signupForm ? signupForm.title_fi : "Loading...";
const renderData = () => {
if (!signupForm || !signups || signups.length === 0) {
return <div>No signups.</div>;
}
// 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.name,
id: q.id,
})) : [];
// 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,
})) : [];
// 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]));
// Add reserve signup "header"
if (signupForm?.quota) {
CSVData.splice(signupForm.quota, 0, ["RESERVE-SIGNUPS"]);
}
// 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]));
// Add reserve signup "header"
if (signupForm?.quota) {
CSVData.splice(signupForm.quota, 0, ["RESERVE-SIGNUPS"]);
}
return (
<AdminListCommon>
<h1>
{title}
: Sign-ups
</h1>
return (
<table>
<thead>
<tr>
@@ -81,7 +85,6 @@ const SignupEmailPage: NextPage = () => {
</th>
</tr>
</thead>
<tbody>
{signups.map((s) => (
<tr key={s.id}>
@@ -99,6 +102,16 @@ const SignupEmailPage: NextPage = () => {
))}
</tbody>
</table>
);
};
return (
<AdminListCommon>
<h1>
{title}
: Sign-ups
</h1>
{renderData()}
</AdminListCommon>
);
};
+109 -46
View File
@@ -1,6 +1,7 @@
import React, { useEffect, useState } from "react";
import React, { useState, useEffect } from "react";
import { NextPage } from "next";
import { formatRelative } from "date-fns";
import useSWR from "swr";
import { formatRelative, formatISO } from "date-fns";
import { toast } from "react-toastify";
import styled from "styled-components";
import AdminListCommon from "@views/admin/AdminListCommon";
@@ -8,6 +9,8 @@ import { Button, Link } from "@components/index";
import AddLink from "@components/AddLink";
import { SignupForm } from "@models/Signup";
import SignupApi from "@api/signupApi";
import { fetcher, APIPath, API } from "@api/backend";
import { SelectWrapper, StyledSelect } from "@components/Select";
const URL = "/admin/signups";
@@ -31,57 +34,117 @@ const confirmDelete = async (signup: SignupForm) => {
}
};
const renderData = (signupForms: SignupForm[]) => {
if (!signupForms || signupForms.length === 0) {
const Renderer: React.FC = () => {
const api: API = { path: APIPath.SIGNUP_FORMS, authenticated: true };
const { data: signupForms, error } = useSWR<SignupForm[]>(api, fetcher);
const [sort, setSort] = useState<string>("start_time");
const [order, setOrder] = useState<string>("descending");
const [filter, setFilter] = useState<string>("all");
const signupFormSort = (a, b) => {
let result = 0;
if (order === "descending") {
if (["start_time", "end_time"].includes(sort)) {
result = new Date(b[sort]).getTime() - new Date(a[sort]).getTime();
} else if (sort === "id") {
result = b[sort] - a[sort];
}
} else if (order === "ascending") {
if (["start_time", "end_time"].includes(sort)) {
result = new Date(a[sort]).getTime() - new Date(b[sort]).getTime();
} else if (sort === "id") {
result = a[sort] - b[sort];
}
}
return result;
};
const dateFilter = (a) => {
let result = true;
if (filter === "upcoming") {
result = new Date(a.end_time).getTime() > Date.now();
} else if (filter === "past") {
result = new Date(a.end_time).getTime() < Date.now();
}
return result;
};
useEffect(() => {
}, [sort, order, filter, signupForms]);
if (error) {
console.error(error);
return (
<div>
Failed loading events.
</div>
);
}
if (!signupForms?.length) {
return <div>No signup forms.</div>;
}
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
<th>Sign-ups</th>
<th>Send email</th>
</tr>
</thead>
<tbody>
{signupForms.map((signupForm) => (
<tr key={signupForm.id}>
<td><Link to={`${URL}/${signupForm.id}`}>{signupForm.title_fi}</Link></td>
<td>{formatRelative(new Date(signupForm.start_time), new Date())}</td>
<td>{formatRelative(new Date(signupForm.end_time), new Date())}</td>
<td><Link to={`${URL}/${signupForm.id}/list`}>View</Link></td>
<td><Link to={`${URL}/${signupForm.id}/email`}>Send</Link></td>
<td>
<StyledButton $colorOverride="red" buttonStyle="filled" onClick={() => confirmDelete(signupForm)}>
Delete
</StyledButton>
</td>
<div>
<SelectWrapper>
Sort by:
<StyledSelect name="" onChange={(e) => setSort(e.target.value)}>
<option value="start_time">Start time</option>
<option value="end_time">End time</option>
<option value="id">Creation order</option>
</StyledSelect>
Order:
<StyledSelect name="" onChange={(e) => setOrder(e.target.value)}>
<option value="descending">Descending</option>
<option value="ascending">Ascending</option>
</StyledSelect>
Filter:
<StyledSelect name="" onChange={(e) => setFilter(e.target.value)}>
<option value="all">All</option>
<option value="upcoming">Upcoming</option>
<option value="past">Past</option>
</StyledSelect>
</SelectWrapper>
<table>
<thead>
<tr>
<th>Title</th>
<th>Start time</th>
<th>End time</th>
<th>Sign-ups</th>
<th>Send email</th>
</tr>
))}
</tbody>
</table>
</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>{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>
);
};
const AdminSignupPage: NextPage = () => {
const [forms, setForms] = useState<SignupForm[]>(null);
useEffect(() => {
SignupApi.getForms(true)
.then((res) => setForms(res));
}, []);
return (
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
{renderData(forms)}
</AdminListCommon>
);
};
const AdminSignupPage: NextPage = () => (
<AdminListCommon>
<h1>Sign-up forms</h1>
<AddLink text="Create signup form" to={`${URL}/create`} data-e2e="create-signup" />
<Renderer />
</AdminListCommon>
);
export default AdminSignupPage;
+26 -29
View File
@@ -14,14 +14,13 @@ interface InitialProps {
const EventPage: NextPage<InitialProps> = ({ event }) => {
const router = useRouter();
const { id } = router.query;
if (router.isFallback) return <LoadingView />;
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/${event.id}`} />
</Head>
<PageWrapper>
<EventPageView event={event} />
@@ -30,36 +29,34 @@ const EventPage: NextPage<InitialProps> = ({ event }) => {
);
};
export const getStaticPaths: GetStaticPaths = async () => {
const allEvents = await EventApi.getEvents();
const paths = allEvents.map((e: Event) => ({
params: {
id: String(e.id),
},
}
));
return {
paths,
fallback: true,
};
};
export const getStaticPaths: GetStaticPaths = async () => ({
paths: [],
fallback: "blocking",
});
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) {
notFound = true;
const id = Number(params?.id);
if (!id) {
return {
notFound: true,
revalidate: 10,
};
}
try {
const event = await EventApi.getEvent(id);
return {
props: {
event,
},
revalidate: 10, // Required for deleting hidden pages
};
} catch {
return {
notFound: true,
revalidate: 10,
};
}
return {
props: {
event,
},
revalidate: 10, // Required for deleting hidden pages
notFound,
};
};
export default EventPage;
+25 -29
View File
@@ -14,14 +14,13 @@ interface InitialProps {
const FeedPage: NextPage<InitialProps> = ({ post }) => {
const router = useRouter();
const { id } = router.query;
if (router.isFallback) return <LoadingView />;
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/${post.id}`} />
</Head>
<PageWrapper>
<FeedPageView post={post} />
@@ -30,37 +29,34 @@ const FeedPage: NextPage<InitialProps> = ({ post }) => {
);
};
export const getStaticPaths: GetStaticPaths = async () => {
const feed = await FeedApi.getFeed();
const paths = feed.map((post: Post) => ({
params: {
id: String(post.id),
},
}
));
return {
paths,
fallback: true,
};
};
export const getStaticPaths: GetStaticPaths = async () => ({
paths: [],
fallback: "blocking",
});
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) {
notFound = true;
const id = Number(params?.id);
if (!id) {
return {
notFound: true,
revalidate: 10,
};
}
return {
props: {
post,
},
revalidate: 10, // Required for deleting hidden pages
notFound,
};
try {
const post = await FeedApi.getPost(id);
return {
props: {
post,
},
revalidate: 10, // Required for deleting hidden pages
};
} catch {
return {
notFound: true,
revalidate: 10,
};
}
};
export default FeedPage;
+22 -15
View File
@@ -1,21 +1,25 @@
import React from "react";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import useSWR from "swr";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import useFetchEvents from "@hooks/useFetchEvents";
import Post from "@models/Feed";
import FeedApi from "@api/feedApi";
import useFetchFeed from "@hooks/useFetchFeed";
import InEnglishPageView from "@views/InEnglishPage/InEnglishPageView";
import PageWrapper from "@views/common/PageWrapper";
import { fetcher, APIPath, API } from "@api/backend";
const eventOptions = {
limit: 4,
const eventApi: API = {
path: APIPath.EVENTS,
queryParams: {
limit: 4,
},
};
const feedOptions = {
limit: 4,
const feedApi: API = {
path: APIPath.FEED,
queryParams: {
limit: 4,
},
};
interface InitialProps {
@@ -24,8 +28,8 @@ interface InitialProps {
}
const InEnglishPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
const eventResult = useFetchEvents({ initialData: initialEvents, options: eventOptions });
const feedResult = useFetchFeed({ initialData: initialFeed, options: feedOptions });
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialFeed });
return (
<>
@@ -33,19 +37,22 @@ const InEnglishPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) =
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/in_english`} />
</Head>
<PageWrapper>
<InEnglishPageView events={eventResult.data as Event[]} feed={feedResult.data} />
<InEnglishPageView events={events} feed={feed} />
</PageWrapper>
</>
);
};
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
const initialEvents = await EventApi.getEvents(eventOptions);
const initialFeed = await FeedApi.getFeed(feedOptions);
const [eventsResult, feedResult] = await Promise.allSettled([
fetcher<Event[]>(eventApi),
fetcher<Post[]>(feedApi),
]);
return {
props: {
initialEvents,
initialFeed,
initialEvents: eventsResult.status === "fulfilled" ? eventsResult.value : [],
initialFeed: feedResult.status === "fulfilled" ? feedResult.value : [],
},
revalidate: 10,
};
+22 -16
View File
@@ -1,31 +1,34 @@
import React from "react";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import useSWR from "swr";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import useFetchEvents from "@hooks/useFetchEvents";
import Post from "@models/Feed";
import FeedApi from "@api/feedApi";
import useFetchFeed from "@hooks/useFetchFeed";
import FrontPageView from "@views/FrontPage/FrontPageView";
import PageWrapper from "@views/common/PageWrapper";
import { fetcher, API, APIPath } from "@api/backend";
const eventOptions = {
limit: 4,
const eventApi: API = {
path: APIPath.EVENTS,
queryParams: {
limit: 4,
},
};
const feedOptions = {
limit: 4,
const feedApi: API = {
path: APIPath.FEED,
queryParams: {
limit: 4,
},
};
interface InitialProps {
initialEvents: Event[];
initialFeed: Post[];
}
const FrontPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
const eventResult = useFetchEvents({ initialData: initialEvents, options: eventOptions });
const feedResult = useFetchFeed({ initialData: initialFeed, options: feedOptions });
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialFeed });
return (
<>
@@ -33,19 +36,22 @@ const FrontPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/`} />
</Head>
<PageWrapper>
<FrontPageView events={eventResult.data as Event[]} feed={feedResult.data} />
<FrontPageView events={events} feed={feed} />
</PageWrapper>
</>
);
};
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
const initialEvents = await EventApi.getEvents(eventOptions);
const initialFeed = await FeedApi.getFeed(feedOptions);
const [eventsResult, feedResult] = await Promise.allSettled([
fetcher<Event[]>(eventApi),
fetcher<Post[]>(feedApi),
]);
return {
props: {
initialEvents,
initialFeed,
initialEvents: eventsResult.status === "fulfilled" ? eventsResult.value : [],
initialFeed: feedResult.status === "fulfilled" ? feedResult.value : [],
},
revalidate: 10,
};
+18
View File
@@ -0,0 +1,18 @@
import React from "react";
import { NextPage } from "next";
import Head from "next/head";
import GuildroomPageView from "@views/GuildroomPage/GuildroomPageView";
import PageWrapper from "@views/common/PageWrapper";
const GuildroomPage: NextPage = () => (
<>
<Head>
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/guildroom`} />
</Head>
<PageWrapper>
<GuildroomPageView />
</PageWrapper>
</>
);
export default GuildroomPage;
+18
View File
@@ -0,0 +1,18 @@
import React from "react";
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;
+18
View File
@@ -0,0 +1,18 @@
import React from "react";
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;
+18
View File
@@ -0,0 +1,18 @@
import React from "react";
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;
+18
View File
@@ -0,0 +1,18 @@
import React from "react";
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;
+20 -11
View File
@@ -1,23 +1,29 @@
import React from "react";
import { NextPage, GetStaticProps } from "next";
import Head from "next/head";
import useSWR from "swr";
import Event from "@models/Event";
import EventApi from "@api/eventApi";
import useFetchEvents from "@hooks/useFetchEvents";
import Post from "@models/Feed";
import FeedApi from "@api/feedApi";
import useFetchFeed from "@hooks/useFetchFeed";
import ActualPageView from "@views/ActualPage/ActualPageView";
import PageWrapper from "@views/common/PageWrapper";
import { fetcher, APIPath, API } from "@api/backend";
interface InitialProps {
initialEvents: Event[];
initialFeed: Post[];
}
const eventApi: API = {
path: APIPath.EVENTS,
};
const feedApi: API = {
path: APIPath.FEED,
};
const ActualPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
const eventResult = useFetchEvents({ initialData: initialEvents });
const feedResult = useFetchFeed({ initialData: initialFeed });
const { data: events } = useSWR<Event[]>(eventApi, fetcher, { fallbackData: initialEvents });
const { data: feed } = useSWR<Post[]>(feedApi, fetcher, { fallbackData: initialFeed });
return (
<>
@@ -25,19 +31,22 @@ const ActualPage: NextPage<InitialProps> = ({ initialEvents, initialFeed }) => {
<link rel="canonical" href={`${process.env.NEXT_PUBLIC_SITE_URL}/kilta/toiminta`} />
</Head>
<PageWrapper>
<ActualPageView events={eventResult.data} feed={feedResult.data} />
<ActualPageView events={events} feed={feed} />
</PageWrapper>
</>
);
};
export const getStaticProps: GetStaticProps<InitialProps> = async () => {
const initialEvents = await EventApi.getEvents();
const initialFeed = await FeedApi.getFeed();
const [eventsResult, feedResult] = await Promise.allSettled([
fetcher<Event[]>(eventApi),
fetcher<Post[]>(feedApi),
]);
return {
props: {
initialEvents,
initialFeed,
initialEvents: eventsResult.status === "fulfilled" ? eventsResult.value : [],
initialFeed: feedResult.status === "fulfilled" ? feedResult.value : [],
},
revalidate: 10,
};
+18
View File
@@ -0,0 +1,18 @@
import React from "react";
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;

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