Compare commits

...

23 Commits

Author SHA1 Message Date
jadera a3ab778a84 uskomaton säätö 2025-12-10 21:10:56 +02:00
jadera 4de56eef0b vitttttttuuuu 2025-12-10 20:51:29 +02:00
jadera b3eae06a5b vithu 2025-12-10 20:43:27 +02:00
jadera f8a711a869 vittu 2025-12-10 20:33:05 +02:00
jadera 19fbe71506 switch to testcafe image 2025-12-10 20:25:43 +02:00
jadera 469f4f4da6 chown test files to runner 2025-12-10 20:14:06 +02:00
jadera cd2a58a76d testcafe testing 2025-12-10 20:04:40 +02:00
jadera 84b2b450c6 drop testcafe node back to 16 2025-12-10 19:46:42 +02:00
jadera 7d3fd6857c fix crossfade from legacy 2025-12-10 19:38:18 +02:00
jadera 9f44bf57f6 node bump 16->20 2025-12-10 19:27:47 +02:00
jadera 9d9211fa9c testing version bumps
extras

kakka
2025-12-10 19:21:50 +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
Justus Ojala 42ce058dc9 Update guild room custodians 2025-09-23 21:38:58 +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 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
36 changed files with 33576 additions and 26397 deletions
+1
View File
@@ -1,3 +1,4 @@
NEXT_PUBLIC_DEPLOY_ENV=local NEXT_PUBLIC_DEPLOY_ENV=local
NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api NEXT_PUBLIC_API_URL=https://api.dev.sahkoinsinoorikilta.fi/api
NEXT_PUBLIC_SITE_URL=https://dev.sahkoinsinoorikilta.fi NEXT_PUBLIC_SITE_URL=https://dev.sahkoinsinoorikilta.fi
NEXT_MQTT_HOST=mqtt.dev.sahkoinsinoorikilta.fi
+1
View File
@@ -5,3 +5,4 @@ node_modules
# don't lint nyc coverage output # don't lint nyc coverage output
coverage coverage
next-env.d.ts next-env.d.ts
tests
+17 -8
View File
@@ -8,7 +8,7 @@ stages:
- deploy - deploy
install: install:
image: node:16 image: node:20
stage: setup stage: setup
script: script:
- npm ci - npm ci
@@ -21,7 +21,7 @@ install:
expire_in: 1 week expire_in: 1 week
audit: audit:
image: node:16 image: node:20
needs: ["install"] needs: ["install"]
allow_failure: true allow_failure: true
stage: audit stage: audit
@@ -29,27 +29,27 @@ audit:
- npm audit --audit-level=critical - npm audit --audit-level=critical
es:lint: es:lint:
image: node:16 image: node:20
needs: ["install"] needs: ["install"]
stage: lint stage: lint
script: script:
- npm run lint:es - npm run lint:es
css:lint: css:lint:
image: node:16 image: node:20
needs: ["install"] needs: ["install"]
stage: lint stage: lint
script: script:
- npm run lint:css - npm run lint:css
# test:unit: # test:unit:
# image: node:16 # image: node:20
# stage: test # stage: test
# script: # script:
# - npm run test:unit # - npm run test:unit
build: build:
image: node:16 image: node:20
needs: ["install"] needs: ["install"]
stage: build stage: build
script: script:
@@ -67,11 +67,20 @@ build:
- .next/cache/ - .next/cache/
test:e2e: test:e2e:
image: circleci/node:16-browsers # Use a Cypress image which has Node 20 + Chrome and runs as root (solves permission issues)
image: cypress/browsers:latest
needs: ["install", "build"] needs: ["install", "build"]
stage: test stage: test
script: script:
- npm run testcafe # No sudo needed if running as root.
# If permissions are still wrong, we CAN fix them because we are root.
# But usually, being root means we can overwrite/rm the files anyway.
# 1. Clean install to ensure native modules match this container's environment
- npm ci
# 2. Run TestCafe (using the CI script for headless mode)
- npm run testcafe:ci
artifacts: artifacts:
paths: paths:
- e2e-screenshots - e2e-screenshots
+2 -1
View File
@@ -1,5 +1,6 @@
/// <reference types="next" /> /// <reference types="next" />
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited // NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information. // see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
+46 -21
View File
@@ -1,29 +1,54 @@
// web2.0-frontend/next.config.js
const { withSentryConfig } = require("@sentry/nextjs"); const { withSentryConfig } = require("@sentry/nextjs");
const withBundleAnalyzer = require("@next/bundle-analyzer")({ const withBundleAnalyzer = require("@next/bundle-analyzer")({
enabled: process.env.ANALYZE === "true", enabled: process.env.ANALYZE === "true",
}); });
const sentryWebpackPluginOptions = { /** @type {import('next').NextConfig} */
// Additional config options for the Sentry Webpack plugin. Keep in mind that const nextConfig = {
// the following options are set automatically, and overriding them is not compiler: {
// recommended: styledComponents: true,
// 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: { images: {
domains: [ remotePatterns: [
"api.sahkoinsinoorikilta.fi", {
"static.sahkoinsinoorikilta.fi", protocol: "https",
"api.dev.sahkoinsinoorikilta.fi", hostname: "api.sahkoinsinoorikilta.fi",
},
{
protocol: "https",
hostname: "static.sahkoinsinoorikilta.fi",
},
{
protocol: "https",
hostname: "api.dev.sahkoinsinoorikilta.fi",
},
], ],
}, },
sentry: { // Note: The 'sentry' key is removed from here as it is no longer supported in v8
hideSourceMaps: true, // Hide source maps, see: https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/#configure-source-maps };
},
}, sentryWebpackPluginOptions)); // Sentry options are now passed as a single object in the second argument
const sentryOptions = {
// For all available options, see:
// https://github.com/getsentry/sentry-webpack-plugin#options
// Suppresses all logs
silent: true,
// Hides source maps from generated client bundles
hideSourceMaps: true,
// Automatically tree-shake Sentry logger statements to reduce bundle size
disableLogger: true,
// Upload a larger set of source maps for prettier stack traces (increases build time)
widenClientFileUpload: true,
// (Optional) You can add org and project if not set in environment variables
// org: "your-org-slug",
// project: "your-project-slug",
};
// Wrap the config with Sentry first, then Bundle Analyzer
module.exports = withBundleAnalyzer(withSentryConfig(nextConfig, sentryOptions));
+15530 -8567
View File
File diff suppressed because it is too large Load Diff
+19 -16
View File
@@ -20,21 +20,22 @@
"postbuild": "next-sitemap", "postbuild": "next-sitemap",
"export": "next export", "export": "next export",
"lint": "npm run lint:es && npm run lint:css", "lint": "npm run lint:es && npm run lint:css",
"lint:es": "next lint", "lint:es": "eslint . --ext .js,.jsx,.ts,.tsx",
"lint:es:fix": "next lint --fix", "lint:es:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix",
"lint:css": "stylelint \"./src/**/*.{ts,tsx}\"", "lint:css": "stylelint \"./src/**/*.{ts,tsx}\"",
"dev": "next dev", "dev": "next dev --webpack",
"start": "next dev", "start": "next dev --webpack",
"start-prod": "next start --port ${SERVER_PORT:=80}", "start-prod": "next start --port ${SERVER_PORT:=80}",
"serve": "next start --port 3000", "serve": "next start --port 3000",
"test:unit": "jest --coverage", "test:unit": "jest --coverage",
"test": "npm run testcafe", "test": "npm run testcafe",
"testcafe": "testcafe --config-file testcafe.json", "testcafe": "testcafe --config-file testcafe.json",
"testcafe:ci": "testcafe 'chrome:headless --no-sandbox --disable-dev-shm-usage' --config-file testcafe.json",
"build-analyze": "ANALYZE=true npm run build", "build-analyze": "ANALYZE=true npm run build",
"prepare": "husky install" "prepare": "husky install"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^27.4.1", "@types/jest": "^30.0.0",
"@types/js-cookie": "^3.0.1", "@types/js-cookie": "^3.0.1",
"@types/node": "^16.11.36", "@types/node": "^16.11.36",
"@types/react": "^18.0.15", "@types/react": "^18.0.15",
@@ -45,13 +46,13 @@
"@typescript-eslint/eslint-plugin": "^5.18.0", "@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0", "@typescript-eslint/parser": "^5.18.0",
"babel-plugin-styled-components": "^2.0.7", "babel-plugin-styled-components": "^2.0.7",
"eslint": "^8.13.0", "eslint": "^8.57.1",
"eslint-config-airbnb": "^19.0.4", "eslint-config-airbnb": "^19.0.4",
"eslint-config-airbnb-typescript": "^17.0.0", "eslint-config-airbnb-typescript": "^17.0.0",
"eslint-config-next": "^13.1.6", "eslint-config-next": "^13.5.11",
"eslint-plugin-import": "^2.26.0", "eslint-plugin-import": "^2.26.0",
"husky": "^7.0.4", "husky": "^7.0.4",
"jest": "^27.5.1", "jest": "^30.2.0",
"next-sitemap": "^3.1.11", "next-sitemap": "^3.1.11",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"postcss-jsx": "^0.36.4", "postcss-jsx": "^0.36.4",
@@ -59,20 +60,21 @@
"stylelint": "^14.2.0", "stylelint": "^14.2.0",
"stylelint-config-recommended": "^6.0.0", "stylelint-config-recommended": "^6.0.0",
"stylelint-config-styled-components": "^0.1.1", "stylelint-config-styled-components": "^0.1.1",
"testcafe": "^1.18.5", "testcafe": "^3.7.2",
"ts-jest": "^27.1.4", "ts-jest": "^29.4.6",
"typescript": "^4.6.3" "typescript": "^5.9.3"
}, },
"dependencies": { "dependencies": {
"@next/bundle-analyzer": "^12.2.3", "@next/bundle-analyzer": "^12.2.3",
"@rjsf/core": "^4.2.0", "@rjsf/core": "^4.2.0",
"@sentry/nextjs": "^7.34.0", "@sentry/nextjs": "^10.29.0",
"axios": "^0.26.1", "axios": "^1.13.2",
"date-fns": "^2.28.0", "date-fns": "^2.28.0",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"js-cookie": "^3.0.1", "js-cookie": "^3.0.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"next": "^13.1.6", "mqtt": "^5.14.1",
"next": "^16.0.8",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-csv": "^2.2.2", "react-csv": "^2.2.2",
@@ -86,10 +88,11 @@
"react-toastify": "^9.0.7", "react-toastify": "^9.0.7",
"rehype-raw": "^6.1.1", "rehype-raw": "^6.1.1",
"rehype-sanitize": "^5.0.1", "rehype-sanitize": "^5.0.1",
"sharp": "^0.30.3", "sharp": "^0.34.5",
"shortid": "^2.2.16", "shortid": "^2.2.16",
"styled-components": "^5.3.5", "styled-components": "^5.3.5",
"swr": "^1.2.2" "swr": "^1.2.2",
"uuid": "^13.0.0"
}, },
"overrides": { "overrides": {
"react-mde": { "react-mde": {
+18
View File
@@ -0,0 +1,18 @@
// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on).
// The config you add here will be used whenever one of the edge features is loaded.
// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally.
// 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,
// Adjust this value in production, or use tracesSampler for greater control
// tracesSampleRate: 1.0, // Commented out as client/server configs don't have it
// Setting this option to true will print useful information to the console while you're setting up Sentry.
// debug: false, // Commented out as client/server configs don't have it
});
+1 -1
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
const Icon = "/img/add-icon.png"; const Icon = "/img/add-icon.png";
+3 -2
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import colors from "@theme/colors"; import colors from "@theme/colors";
import Link from "@components/Link"; import Link from "@components/Link";
@@ -22,6 +22,7 @@ const StyledCard = styled.article`
display: flex; display: flex;
flex-direction: column; flex-direction: column;
color: ${colors.black}; color: ${colors.black};
position: relative;
margin: 1rem; margin: 1rem;
text-align: center; text-align: center;
@@ -72,7 +73,7 @@ const WrappedCard: React.FC<WrappedCardProps> = ({
}) => ( }) => (
<StyledCard {...props}> <StyledCard {...props}>
{image && ( {image && (
<Image src={image.src} alt={image.alt} layout="responsive" width={0} height={0} objectFit="scale-down" /> <Image src={image.src} alt={image.alt} fill sizes="(max-width: 640px) 100vw, (max-width: 1024px) 50vw, 33vw" style={{ objectFit: "scale-down" }} />
)} )}
<p>{startTime}</p> <p>{startTime}</p>
<h3>{title}</h3> <h3>{title}</h3>
+4 -3
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import colors from "@theme/colors"; import colors from "@theme/colors";
@@ -73,8 +73,9 @@ const ContactCard: React.FC<ContactCardProps> = ({
<Image <Image
src={image} src={image}
alt={name} alt={name}
layout="fill" fill
objectFit="cover" sizes="128px"
style={{ objectFit: "cover" }}
/> />
</ImageContainer> </ImageContainer>
) : null} ) : null}
+7 -6
View File
@@ -1,16 +1,16 @@
import React from "react"; import React from "react";
import Image, { ImageProps } from "next/legacy/image"; import Image from "next/image";
import styled, { keyframes, Keyframes } from "styled-components"; import styled, { keyframes, Keyframes } from "styled-components";
interface CrossFadeImagesProps { interface CrossFadeImagesProps {
width: ImageProps["width"]; width: number | string;
height: ImageProps["height"]; height: number | string;
images: string[]; images: string[];
presentationTime: number; presentationTime: number;
fadeTime: number; fadeTime: number;
} }
const AnimatedImage = styled(Image)<{ layout: string; $delay: number }>` const AnimatedImage = styled(Image) <{ $delay: number }>`
animation-delay: ${(p) => p.$delay}s; animation-delay: ${(p) => p.$delay}s;
`; `;
@@ -75,8 +75,9 @@ const CrossFadeImages: React.FC<CrossFadeImagesProps> = ({
<AnimatedImage <AnimatedImage
src={image} src={image}
objectFit="cover" objectFit="cover"
width={width} alt=""
height={height} width={width as any}
height={height as any}
layout="responsive" layout="responsive"
$delay={delays[idx]} $delay={delays[idx]}
/> />
+4 -3
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import { Link } from "@components/index"; import { Link } from "@components/index";
@@ -18,8 +18,9 @@ const HeaderLogo: React.FC = () => (
<Image <Image
src={TitleImage} src={TitleImage}
alt="logo" alt="logo"
layout="fill" fill
objectFit="scale-down" sizes="200px"
style={{ objectFit: "scale-down" }}
/> />
</Logo> </Logo>
); );
+1 -1
View File
@@ -19,7 +19,7 @@ export const renderNavigationItems = (mobile = false): JSX.Element => (
<NavbarChildLink to="https://sik.kuvat.fi">Kuvagalleria</NavbarChildLink> <NavbarChildLink to="https://sik.kuvat.fi">Kuvagalleria</NavbarChildLink>
<NavbarChildLink to="/kilta/kilta-avustus">Kilta-avustus</NavbarChildLink> <NavbarChildLink to="/kilta/kilta-avustus">Kilta-avustus</NavbarChildLink>
</NavbarDropdownLink> </NavbarDropdownLink>
<NavbarDropdownLink to="/newStudent" text="New students " exploded={mobile}> <NavbarDropdownLink to="/" text="New students " exploded={mobile}>
<NavbarChildLink to="/newStudent/fuksi">Fukseille</NavbarChildLink> <NavbarChildLink to="/newStudent/fuksi">Fukseille</NavbarChildLink>
<NavbarChildLink to="/newStudent/fukseille_en">For Freshmen</NavbarChildLink> <NavbarChildLink to="/newStudent/fukseille_en">For Freshmen</NavbarChildLink>
<NavbarChildLink to="/newStudent/forExchangers">For Exchange/MSc students</NavbarChildLink> <NavbarChildLink to="/newStudent/forExchangers">For Exchange/MSc students</NavbarChildLink>
+2 -2
View File
@@ -4,8 +4,8 @@ import colors from "@theme/colors";
import { renderNavigationItems } from "./Navigation"; import { renderNavigationItems } from "./Navigation";
const Nav = styled.nav` const Nav = styled.nav`
padding: 1rem 2rem; padding: 1rem 1rem;
padding-bottom: 20rem;
a { a {
fill: ${colors.lightBlue}; fill: ${colors.lightBlue};
color: ${colors.lightBlue}; color: ${colors.lightBlue};
+20 -10
View File
@@ -1,5 +1,5 @@
import React, { import React, {
createContext, useContext, useMemo, useReducer, createContext, useContext, useMemo, useReducer, useEffect,
} from "react"; } from "react";
import fi from "./locales/fi/common.json"; import fi from "./locales/fi/common.json";
import en from "./locales/en/common.json"; import en from "./locales/en/common.json";
@@ -36,16 +36,10 @@ interface Store {
changeLanguage: React.Dispatch<Lang>, changeLanguage: React.Dispatch<Lang>,
} }
let initialLanguage: Lang = "fi"; // Always default to 'fi' for the initial state to ensure Server/Client match.
try { // We will hydrate the user's preference in useEffect below.
const storedLang = localStorage.getItem(LOCAL_STORAGE_KEY) as Lang;
initialLanguage = storedLang;
} catch (err) {
// Just ignore if fails to get value from browser (server etc.)
}
const initialState: Store = { const initialState: Store = {
language: initialLanguage, language: "fi",
changeLanguage: null, changeLanguage: null,
}; };
@@ -69,6 +63,22 @@ const Reducer = (state: Store, action: Lang) => {
const LocaleContext = createContext(initialState); const LocaleContext = createContext(initialState);
const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({ children }) => { const LocaleStore: React.FC<{ children?: React.ReactNode }> = ({ children }) => {
const [state, dispatch] = useReducer(Reducer, initialState); const [state, dispatch] = useReducer(Reducer, initialState);
// Sync with localStorage only after the component has mounted (client-side)
useEffect(() => {
try {
const storedLang = localStorage.getItem(LOCAL_STORAGE_KEY) as Lang;
if (storedLang && (storedLang === "fi" || storedLang === "en")) {
// Only dispatch if different to prevent unnecessary re-renders
if (storedLang !== state.language) {
dispatch(storedLang);
}
}
} catch (err) {
// Just ignore if fails to get value from browser
}
}, []); // Empty dependency array ensures this runs once on mount
const changeLanguage = (action: Lang) => { const changeLanguage = (action: Lang) => {
dispatch(action); dispatch(action);
try { try {
@@ -1,7 +1,4 @@
// This file configures the initialization of Sentry on the browser. /* eslint-disable import/prefer-default-export */
// 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"; import * as Sentry from "@sentry/nextjs";
const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN; const SENTRY_DSN = process.env.SENTRY_DSN || process.env.NEXT_PUBLIC_SENTRY_DSN;
@@ -14,3 +11,6 @@ Sentry.init({
// `release` value here - use the environment variable `SENTRY_RELEASE`, so // `release` value here - use the environment variable `SENTRY_RELEASE`, so
// that it will also get attached to your source maps // that it will also get attached to your source maps
}); });
// This is required by Sentry v8 to instrument navigation (tracing)
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
+13
View File
@@ -0,0 +1,13 @@
import * as Sentry from "@sentry/nextjs";
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
await import("../sentry.server.config");
}
if (process.env.NEXT_RUNTIME === "edge") {
await import("../sentry.edge.config");
}
}
export const onRequestError = Sentry.captureRequestError;
+2 -1
View File
@@ -1,7 +1,8 @@
import { OptionTypes } from "@components/Widgets/SignupQuestionsWidget/common"; import { OptionTypes } from "@components/Widgets/SignupQuestionsWidget/common";
export interface Signup { export interface Signup {
id?: number; id?: number; // Database id for completed signup
submit_id?: string; // Signup request idempotency key
signupForm_id: number; signupForm_id: number;
answer: string; answer: string;
} }
+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;
+3
View File
@@ -13,6 +13,7 @@ import PageWrapper from "@views/common/PageWrapper";
import LoadingView from "@views/common/LoadingView"; import LoadingView from "@views/common/LoadingView";
import noop from "@utils/noop"; import noop from "@utils/noop";
import NotFoundPage from "@pages/404"; import NotFoundPage from "@pages/404";
import { v4 as uuid } from "uuid";
type InitialProps = { type InitialProps = {
initialForm: SignupForm; initialForm: SignupForm;
@@ -23,6 +24,7 @@ const FORM_URL = `${process.env.NEXT_PUBLIC_API_URL}/signupForm/`;
const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => { const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
const router = useRouter(); const router = useRouter();
const id = String(initialForm?.id ?? ""); const id = String(initialForm?.id ?? "");
const SUBMIT_ID = uuid(); // Submission key, generated on page refresh
const URL = `${FORM_URL}${id}/`; const URL = `${FORM_URL}${id}/`;
const { data: signupForm, error } = useSWR<SignupForm>(URL, (url) => axios.get(url).then((res) => res.data), { fallbackData: initialForm }); const { data: signupForm, error } = useSWR<SignupForm>(URL, (url) => axios.get(url).then((res) => res.data), { fallbackData: initialForm });
@@ -43,6 +45,7 @@ const SignUpPage: NextPage<InitialProps> = ({ initialForm }) => {
const onSubmit = async ({ formData }: ISubmitEvent<string>) => { const onSubmit = async ({ formData }: ISubmitEvent<string>) => {
const payload: Signup = { const payload: Signup = {
submit_id: SUBMIT_ID, // This is for preventing duplicate requests; NOT RELATED TO THE SIGNUP ID IN DATABASE
signupForm_id: signupForm.id, signupForm_id: signupForm.id,
answer: formData, answer: formData,
}; };
+1 -1
View File
@@ -54,7 +54,7 @@
"name_en": "Guild Room Representative", "name_en": "Guild Room Representative",
"representatives": [ "representatives": [
{ {
"name": "Justus Ojala" "name": "Milja Kuusela"
}, },
{ {
"name": "Aaro Rasilainen" "name": "Aaro Rasilainen"
+2 -3
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import rehypeRaw from "rehype-raw"; import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize"; import rehypeSanitize from "rehype-sanitize";
@@ -81,10 +81,9 @@ const EventPageView: React.FC<EventPageViewProps> = ({ event }) => {
<Image <Image
src={event.image || event.tags[0].icon} src={event.image || event.tags[0].icon}
alt={title} alt={title}
objectFit="scale-down"
layout="responsive"
width={16} width={16}
height={9} height={9}
style={{ objectFit: "scale-down" }}
/> />
</h1> </h1>
<div> <div>
+2 -3
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import rehypeRaw from "rehype-raw"; import rehypeRaw from "rehype-raw";
import rehypeSanitize from "rehype-sanitize"; import rehypeSanitize from "rehype-sanitize";
@@ -65,10 +65,9 @@ const FeedPageView: React.FC<FeedPageViewProps> = ({ post }) => {
<Image <Image
src={post.image} src={post.image}
alt={title} alt={title}
objectFit="scale-down"
layout="responsive"
width={16} width={16}
height={9} height={9}
style={{ objectFit: "scale-down" }}
/> />
)} )}
</h1> </h1>
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import { import {
CTASection, TextSection, InfoBox, PageLink, Link, CTASection, TextSection, InfoBox, PageLink, Link,
@@ -61,10 +61,9 @@ const ForFreshmenPageView: React.FC = () => (
<Image <Image
src="https://static.sahkoinsinoorikilta.fi/FTMK/IMG_6539.JPG" src="https://static.sahkoinsinoorikilta.fi/FTMK/IMG_6539.JPG"
alt="Kipparit" alt="Kipparit"
layout="responsive"
width={100} width={100}
height={80} height={80}
objectFit="contain" style={{ objectFit: "contain" }}
/> />
</ImageContainer> </ImageContainer>
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import { import {
CTASection, TextSection, InfoBox, PageLink, Link, CTASection, TextSection, InfoBox, PageLink, Link,
@@ -78,10 +78,9 @@ const ForIntlPageView: React.FC = () => (
<Image <Image
src="https://static.sahkoinsinoorikilta.fi/FTMK/Captains2025.jpg" src="https://static.sahkoinsinoorikilta.fi/FTMK/Captains2025.jpg"
alt="Kipparit" alt="Kipparit"
layout="responsive"
width={100} width={100}
height={80} height={80}
objectFit="contain" style={{ objectFit: "contain" }}
/> />
</ImageContainer> </ImageContainer>
+2 -3
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import { import {
CTASection, TextSection, InfoBox, PageLink, Link, CTASection, TextSection, InfoBox, PageLink, Link,
@@ -60,10 +60,9 @@ const FreshmenPageView: React.FC = () => (
<Image <Image
src="https://static.sahkoinsinoorikilta.fi/FTMK/IMG_6539.JPG" src="https://static.sahkoinsinoorikilta.fi/FTMK/IMG_6539.JPG"
alt="Kipparit" alt="Kipparit"
layout="responsive"
width={100} width={100}
height={80} height={80}
objectFit="contain" style={{ objectFit: "contain" }}
/> />
</ImageContainer> </ImageContainer>
+1 -1
View File
@@ -30,7 +30,7 @@ const FrontPageHero: React.FC = () => (
<HeroAsideItem <HeroAsideItem
header="Vasta-aloittaneelle opiskelijalle" header="Vasta-aloittaneelle opiskelijalle"
text="Fuksikasvatusta ja ISOtoimintaa" text="Fuksikasvatusta ja ISOtoimintaa"
link="/kilta/fuksi" link="/newStudent/fuksi"
linkText="Fuksit&nbsp; " linkText="Fuksit&nbsp; "
/> />
<HeroAsideItem <HeroAsideItem
+14 -14
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import styled from "styled-components"; import styled from "styled-components";
import { import {
Divider, Divider,
@@ -93,43 +93,43 @@ const FrontPageView: React.FC<FrontPageViewProps> = ({ events, feed }) => (
<SponsorReel> <SponsorReel>
<div> <div>
<Link to="https://new.abb.com/fi/"> <Link to="https://new.abb.com/fi/">
<Image src={ABB} alt="ABB" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={ABB} alt="ABB" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://caruna.fi/"> <Link to="https://caruna.fi/">
<Image src={Caruna} alt="Caruna" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Caruna} alt="Caruna" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.nokia.com/"> <Link to="https://www.nokia.com/">
<Image src={Nokia} alt="Nokia" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Nokia} alt="Nokia" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.ensto.com/fi/"> <Link to="https://www.ensto.com/fi/">
<Image src={Ensto} alt="Ensto" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Ensto} alt="Ensto" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.esett.com/"> <Link to="https://www.esett.com/">
<Image src={eSett} alt="eSett" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={eSett} alt="eSett" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.fingrid.fi/"> <Link to="https://www.fingrid.fi/">
<Image src={Fingrid} alt="Fingrid" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Fingrid} alt="Fingrid" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.okmetic.com/fi/"> <Link to="https://www.okmetic.com/fi/">
<Image src={Okmetic} alt="Okmetic" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Okmetic} alt="Okmetic" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.granlund.fi/"> <Link to="https://www.granlund.fi/">
<Image src={Granlund} alt="Granlund" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Granlund} alt="Granlund" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.eaton.com/fi/fi-fi.html"> <Link to="https://www.eaton.com/fi/fi-fi.html">
<Image src={Eaton} alt="Eaton" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Eaton} alt="Eaton" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.ericsson.com/en"> <Link to="https://www.ericsson.com/en">
<Image src={Ericsson} alt="Ericsson" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Ericsson} alt="Ericsson" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.saab.com/fi/markets/finland"> <Link to="https://www.saab.com/fi/markets/finland">
<Image src={Saab} alt="Saab" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Saab} alt="Saab" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.stul.fi/"> <Link to="https://www.stul.fi/">
<Image src={STUL} alt="STUL" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={STUL} alt="STUL" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
<Link to="https://www.metso.com/fi/"> <Link to="https://www.metso.com/fi/">
<Image src={Metso} alt="Metso" layout="responsive" width={200} height={100} objectFit="contain" /> <Image src={Metso} alt="Metso" width={200} height={100} style={{ objectFit: "contain" }} />
</Link> </Link>
</div> </div>
<Link to="/yritysyhteistyo">Haluatko kuulla lisää yhteistyöstä kanssamme?</Link> <Link to="/yritysyhteistyo">Haluatko kuulla lisää yhteistyöstä kanssamme?</Link>
@@ -0,0 +1,97 @@
import { useState, useEffect } from "react";
import mqtt from "mqtt";
import { TextSection } from "@components/index";
import styled from "styled-components";
const CoffeeTitle = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 3rem;
font-weight: bold;
`;
const Cups = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 7rem;
font-variant-numeric: tabular-nums;
`;
const Time = styled.div`
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 1rem;
`;
const GuildroomView = () => {
const [brewing, setBrewing] = useState<boolean>(false);
const [time, setTime] = useState<number>(0);
const [cups, setCups] = useState<number>(0);
const [client, setClient] = useState<mqtt.MqttClient | null>(null);
const [status, setStatus] = useState<boolean>(false);
useEffect(() => {
setStatus(false);
if (process.env.NEXT_PUBLIC_MQTT_HOST) {
setClient(mqtt.connect(`wss://${process.env.NEXT_PUBLIC_MQTT_HOST}`));
} else {
console.error("MQTT host undefined");
}
}, []);
useEffect(() => {
if (client) {
client.on("connect", () => {
setStatus(true);
client.subscribe("sik/kiltahuone/kahvivaaka/#", (err) => {
if (!err) {
console.log("Connected to MQTT server!");
}
});
});
client.on("error", (err) => {
console.error("Connection error: ", err);
client.end();
});
client.on("reconnect", () => {
setStatus(false);
});
client.on("offline", () => {
setStatus(false);
});
client.on("message", (topic, message) => {
if (topic === "sik/kiltahuone/kahvivaaka/cups") {
setCups(Number(message.toString()));
}
if (topic === "sik/kiltahuone/kahvivaaka/brewtime") {
setTime(Number(message.toString()));
}
if (topic === "sik/kiltahuone/kahvivaaka/brewing") {
setBrewing(Boolean(message.toString()));
}
});
}
}, [client]);
if (!status) {
return (
<CoffeeTitle style={{ margin: "10%" }}>NO MQTT CONNECTION</CoffeeTitle>
);
}
return (
<div style={{ margin: "10%" }}>
<CoffeeTitle>{brewing ? "Brewing more..." : "Cups left"}</CoffeeTitle>
<Cups>{cups}</Cups>
<Time>Brewed {time} min ago</Time>
</div>
);
};
export default GuildroomView;
@@ -19,6 +19,19 @@ const MembershipPageView: React.FC = () => (
Killan ulkojäseneksi voidaan hyväksyä jäsenmaksun maksanut henkilö, joita ei voida hyväksyä varsinaiseksi jäseneksi. Killan ulkojäseneksi voidaan hyväksyä jäsenmaksun maksanut henkilö, joita ei voida hyväksyä varsinaiseksi jäseneksi.
Killan kannatusjäseneksi voidaan hyväksyä henkilö tai yhteisö, joka haluaa tukea killan toimintaa. Killan kannatusjäseneksi voidaan hyväksyä henkilö tai yhteisö, joka haluaa tukea killan toimintaa.
</p> </p>
<p>
Killan sääntöjen mukaan jäsenmaksuista määrätään seuraavasti:
<br />
</p>
<p>
<h5>8 § Jäsenmaksut</h5>
<br />
Jäsenet ovat velvollisia suorittamaan lukuvuosittain killalle jäsenmaksun.
Kunniajäsenet ovat vapautettuja jäsenmaksuista.
</p>
<p>
Jäsenmaksujen suuruudet määrää killan yleinen kokous.
</p>
<h6 id="jasenmaksu">Jäsenmaksu</h6> <h6 id="jasenmaksu">Jäsenmaksu</h6>
<p> <p>
@@ -36,6 +49,11 @@ const MembershipPageView: React.FC = () => (
Jäsenrekisterin tietosuojaseloste Jäsenrekisterin tietosuojaseloste
</Link> </Link>
</p> </p>
<p>
<Link to="https://static.sahkoinsinoorikilta.fi/saannot/killansaannot.pdf">
Killan säännöt
</Link>
</p>
</div> </div>
</TextSection> </TextSection>
</> </>
+2 -3
View File
@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import Image from "next/legacy/image"; import Image from "next/image";
import { import {
CTASection, TextSection, PageLink, Link, CTASection, TextSection, PageLink, Link,
} from "@components/index"; } from "@components/index";
@@ -81,8 +81,7 @@ const StudiesPageView: React.FC = () => (
alt="Ville Viikari" alt="Ville Viikari"
width={300} width={300}
height={150} height={150}
layout="responsive" style={{ objectFit: "contain" }}
objectFit="contain"
/> />
<h6>Ville Viikari</h6> <h6>Ville Viikari</h6>
<p> <p>
+1 -1
View File
@@ -8,5 +8,5 @@
}, },
"skipJsErrors": true, "skipJsErrors": true,
"appCommand": "npm run serve", "appCommand": "npm run serve",
"appInitDelay": 2000 "appInitDelay": 10000
} }
+1 -1
View File
@@ -1,6 +1,6 @@
import { Selector } from "testcafe"; import { Selector } from "testcafe";
import { import {
getSiteRoot, getPageUrl, deleteForm, doLogin, generateAccessToken, getPostRequestLogger, waitForLogger getSiteRoot, getPageUrl, deleteForm, doLogin, generateAccessToken, getPostRequestLogger, waitForLogger,
} from "../utils"; } from "../utils";
const LOGGER = getPostRequestLogger("signupForm/"); const LOGGER = getPostRequestLogger("signupForm/");
+2 -2
View File
@@ -159,10 +159,10 @@ export const generateTestEvent = async (formIds = [], jwt_access: string) => (
export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)); export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
export const waitForLogger = async (logger: RequestLogger) => { export const waitForLogger = async (logger: RequestLogger) => {
for (let i = 0; i < 50; i++) { for (let i = 0; i < 50; i += 1) {
await sleep(100); await sleep(100);
if (logger.requests.length > 0) { if (logger.requests.length > 0) {
return; return;
} }
} }
} };
+1 -1
View File
@@ -5,7 +5,7 @@
"esModuleInterop": true, "esModuleInterop": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "react-jsx",
"lib": [ "lib": [
"dom", "dom",
"esnext" "esnext"