Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a3ab778a84 | |||
| 4de56eef0b | |||
| b3eae06a5b | |||
| f8a711a869 | |||
| 19fbe71506 | |||
| 469f4f4da6 | |||
| cd2a58a76d | |||
| 84b2b450c6 | |||
| 7d3fd6857c | |||
| 9f44bf57f6 | |||
| 9d9211fa9c |
@@ -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
@@ -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
|
||||||
|
|||||||
Vendored
+2
-1
@@ -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
@@ -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));
|
||||||
|
|||||||
Generated
+33146
-27123
File diff suppressed because it is too large
Load Diff
+100
-99
@@ -1,102 +1,103 @@
|
|||||||
{
|
{
|
||||||
"name": "web2.0-frontend",
|
"name": "web2.0-frontend",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"description": "Web 2.0 Frontend. React, Typescript.",
|
"description": "Web 2.0 Frontend. React, Typescript.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-frontend.git"
|
"url": "https://gitlab.com/sahkoinsinoorikilta/vtmk/web2.0-frontend.git"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"react",
|
"react",
|
||||||
"next.js",
|
"next.js",
|
||||||
"typescript",
|
"typescript",
|
||||||
"styled-components"
|
"styled-components"
|
||||||
],
|
],
|
||||||
"author": "Aarni Halinen",
|
"author": "Aarni Halinen",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"homepage": "https://sahkoinsinoorikilta.fi",
|
"homepage": "https://sahkoinsinoorikilta.fi",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"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",
|
||||||
"build-analyze": "ANALYZE=true npm run build",
|
"testcafe:ci": "testcafe 'chrome:headless --no-sandbox --disable-dev-shm-usage' --config-file testcafe.json",
|
||||||
"prepare": "husky install"
|
"build-analyze": "ANALYZE=true npm run build",
|
||||||
},
|
"prepare": "husky install"
|
||||||
"devDependencies": {
|
},
|
||||||
"@types/jest": "^27.4.1",
|
"devDependencies": {
|
||||||
"@types/js-cookie": "^3.0.1",
|
"@types/jest": "^30.0.0",
|
||||||
"@types/node": "^16.11.36",
|
"@types/js-cookie": "^3.0.1",
|
||||||
"@types/react": "^18.0.15",
|
"@types/node": "^16.11.36",
|
||||||
"@types/react-csv": "^1.1.3",
|
"@types/react": "^18.0.15",
|
||||||
"@types/react-dom": "^18.0.6",
|
"@types/react-csv": "^1.1.3",
|
||||||
"@types/shortid": "^0.0.29",
|
"@types/react-dom": "^18.0.6",
|
||||||
"@types/styled-components": "^5.1.25",
|
"@types/shortid": "^0.0.29",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
"@types/styled-components": "^5.1.25",
|
||||||
"@typescript-eslint/parser": "^5.18.0",
|
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||||
"babel-plugin-styled-components": "^2.0.7",
|
"@typescript-eslint/parser": "^5.18.0",
|
||||||
"eslint": "^8.13.0",
|
"babel-plugin-styled-components": "^2.0.7",
|
||||||
"eslint-config-airbnb": "^19.0.4",
|
"eslint": "^8.57.1",
|
||||||
"eslint-config-airbnb-typescript": "^17.0.0",
|
"eslint-config-airbnb": "^19.0.4",
|
||||||
"eslint-config-next": "^13.1.6",
|
"eslint-config-airbnb-typescript": "^17.0.0",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-config-next": "^13.5.11",
|
||||||
"husky": "^7.0.4",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"jest": "^27.5.1",
|
"husky": "^7.0.4",
|
||||||
"next-sitemap": "^3.1.11",
|
"jest": "^30.2.0",
|
||||||
"npm-run-all": "^4.1.5",
|
"next-sitemap": "^3.1.11",
|
||||||
"postcss-jsx": "^0.36.4",
|
"npm-run-all": "^4.1.5",
|
||||||
"postcss-syntax": "^0.36.2",
|
"postcss-jsx": "^0.36.4",
|
||||||
"stylelint": "^14.2.0",
|
"postcss-syntax": "^0.36.2",
|
||||||
"stylelint-config-recommended": "^6.0.0",
|
"stylelint": "^14.2.0",
|
||||||
"stylelint-config-styled-components": "^0.1.1",
|
"stylelint-config-recommended": "^6.0.0",
|
||||||
"testcafe": "^1.18.5",
|
"stylelint-config-styled-components": "^0.1.1",
|
||||||
"ts-jest": "^27.1.4",
|
"testcafe": "^3.7.2",
|
||||||
"typescript": "^4.6.3"
|
"ts-jest": "^29.4.6",
|
||||||
},
|
"typescript": "^5.9.3"
|
||||||
"dependencies": {
|
},
|
||||||
"@next/bundle-analyzer": "^12.2.3",
|
"dependencies": {
|
||||||
"@rjsf/core": "^4.2.0",
|
"@next/bundle-analyzer": "^12.2.3",
|
||||||
"@sentry/nextjs": "^7.34.0",
|
"@rjsf/core": "^4.2.0",
|
||||||
"axios": "^0.26.1",
|
"@sentry/nextjs": "^10.29.0",
|
||||||
"date-fns": "^2.28.0",
|
"axios": "^1.13.2",
|
||||||
"fast-deep-equal": "^3.1.3",
|
"date-fns": "^2.28.0",
|
||||||
"js-cookie": "^3.0.1",
|
"fast-deep-equal": "^3.1.3",
|
||||||
"lodash": "^4.17.21",
|
"js-cookie": "^3.0.1",
|
||||||
"mqtt": "^5.14.1",
|
"lodash": "^4.17.21",
|
||||||
"next": "^13.1.6",
|
"mqtt": "^5.14.1",
|
||||||
"normalize.css": "^8.0.1",
|
"next": "^16.0.8",
|
||||||
"react": "^18.2.0",
|
"normalize.css": "^8.0.1",
|
||||||
"react-csv": "^2.2.2",
|
"react": "^18.2.0",
|
||||||
"react-dnd": "15.0.2",
|
"react-csv": "^2.2.2",
|
||||||
"react-dnd-html5-backend": "15.0.2",
|
"react-dnd": "15.0.2",
|
||||||
"react-dnd-touch-backend": "15.0.2",
|
"react-dnd-html5-backend": "15.0.2",
|
||||||
"react-dom": "^18.2.0",
|
"react-dnd-touch-backend": "15.0.2",
|
||||||
"react-is": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"react-markdown": "^8.0.3",
|
"react-is": "^18.2.0",
|
||||||
"react-mde": "^11.5.0",
|
"react-markdown": "^8.0.3",
|
||||||
"react-toastify": "^9.0.7",
|
"react-mde": "^11.5.0",
|
||||||
"rehype-raw": "^6.1.1",
|
"react-toastify": "^9.0.7",
|
||||||
"rehype-sanitize": "^5.0.1",
|
"rehype-raw": "^6.1.1",
|
||||||
"sharp": "^0.30.3",
|
"rehype-sanitize": "^5.0.1",
|
||||||
"shortid": "^2.2.16",
|
"sharp": "^0.34.5",
|
||||||
"styled-components": "^5.3.5",
|
"shortid": "^2.2.16",
|
||||||
"swr": "^1.2.2",
|
"styled-components": "^5.3.5",
|
||||||
"uuid": "^13.0.0"
|
"swr": "^1.2.2",
|
||||||
},
|
"uuid": "^13.0.0"
|
||||||
"overrides": {
|
},
|
||||||
"react-mde": {
|
"overrides": {
|
||||||
"react": "$react",
|
"react-mde": {
|
||||||
"react-dom": "$react-dom"
|
"react": "$react",
|
||||||
|
"react-dom": "$react-dom"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,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";
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -69,14 +69,15 @@ const CrossFadeImages: React.FC<CrossFadeImagesProps> = ({
|
|||||||
$animation={animation}
|
$animation={animation}
|
||||||
$duration={len * SINGLE_IMAGE_TIME}
|
$duration={len * SINGLE_IMAGE_TIME}
|
||||||
>
|
>
|
||||||
{ images.map((image, idx) => (
|
{images.map((image, idx) => (
|
||||||
// eslint-disable-next-line react/no-array-index-key
|
// eslint-disable-next-line react/no-array-index-key
|
||||||
<div key={idx} className={idx > 0 ? "not-first" : undefined}>
|
<div key={idx} className={idx > 0 ? "not-first" : undefined}>
|
||||||
<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]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
+20
-10
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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,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>
|
||||||
|
|||||||
@@ -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
@@ -8,5 +8,5 @@
|
|||||||
},
|
},
|
||||||
"skipJsErrors": true,
|
"skipJsErrors": true,
|
||||||
"appCommand": "npm run serve",
|
"appCommand": "npm run serve",
|
||||||
"appInitDelay": 2000
|
"appInitDelay": 10000
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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/");
|
||||||
|
|||||||
@@ -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
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user