1
0
mirror of https://github.com/XFox111/my-website.git synced 2026-04-22 07:28:01 +03:00

chore(deps): dependency bumps + related refactoring (fixes #19)

This commit is contained in:
2025-01-26 21:50:56 +00:00
parent 9d369ad4d2
commit 457fbf33e5
44 changed files with 657 additions and 601 deletions
-36
View File
@@ -1,36 +0,0 @@
{
"extends": [
"next/core-web-vitals",
"eslint:recommended"
],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true
}
},
"rules": {
"react/no-unescaped-entities": "off",
"indent": [
"warn",
"tab",
{
"SwitchCase": 1
}
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
],
"no-unused-vars": "warn",
"react/react-in-jsx-scope": "off",
"react/jsx-uses-react": "off",
"no-unreachable": "warn",
"no-empty": "warn"
}
}
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.alertBox
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.button
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.banner
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.footer
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.header
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.navigation
{
+4 -4
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.dialog
{
@@ -15,6 +15,9 @@
color: unset;
border: none;
transition: right $durationNormal $curveEasyEaseMax;
right: -350px;
&::backdrop
{
-webkit-backdrop-filter: blur(8px);
@@ -64,9 +67,6 @@
}
}
transition: right $durationNormal $curveEasyEaseMax;
right: -350px;
&.show
{
right: 0;
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.socials
{
+3 -2
View File
@@ -1,11 +1,12 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.section
{
@include centerTwo;
min-height: 75vh;
align-items: end;
@include centerTwo;
.content
{
@include flex(column);
+1 -1
View File
@@ -3,7 +3,7 @@ import React from "react";
const ThirdPartyAttribution: React.FC = () => <>
<p>
Iconography, typography, and color palette of this website are based on
Microsoft's <a target="_blank" href="https://fluentui.dev/">Fluent Design System</a> licensed under
Microsoft&apos;s <a target="_blank" href="https://fluentui.dev/">Fluent Design System</a> licensed under
the <a target="_blank" href="https://github.com/microsoft/fluentui/blob/main/LICENSE">MIT License</a>.
</p>
<p>
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.title
{
+3 -2
View File
@@ -1,12 +1,13 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.section
{
@include centerTwo;
@include body2($fontFamilyBaseAlt);
color: $colorNeutralForeground2;
align-items: center;
@include centerTwo;
> div:first-child
{
@include flex(column);
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.section
{
+24 -20
View File
@@ -3,28 +3,31 @@
import Button from "@/_components/Button";
import SocialLinks from "@/_components/SocialLinks";
import contacts from "@/_data/contacts";
import FormStatusTracker from "@/_utils/FormStatusTracker";
import React, { InputHTMLAttributes, useEffect, useMemo, useState } from "react";
import { useFormState } from "react-dom";
import { getSitekey } from "@/_utils/turnstile";
import React, { InputHTMLAttributes, useActionState, useEffect, useMemo, useState } from "react";
import Turnstile from "react-turnstile";
import sendInquiry, { FormStatus } from "../_utils/sendInquiry";
import cls from "./ContactSection.module.scss";
import { getSitekey } from "@/_utils/turnstile";
const defaultState: FormStatus = { status: "idle" };
const ContactSection: React.FC = () =>
{
const [pending, setPending] = useState<boolean>(false);
const [{ status, message }, formAction] = useFormState<FormStatus, FormData>(sendInquiry, defaultState);
// Added state-backing for the form fields to prevent it from resetting on submit
// See https://github.com/facebook/react/issues/29034
// FIXME: Remove form state once #29034 is fixed
const [form, setForm] = useState<{ email: string, subject: string, message: string; }>({ email: "", subject: "", message: "" });
const [{ status, message }, formAction, isPending] = useActionState<FormStatus, FormData>(sendInquiry, defaultState);
const { telephone: phone, email, socials } = contacts;
const [cfSitekey, setCfSitekey] = useState<string | undefined | null>(null);
const sharedProps: InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement> = useMemo(() => ({
required: true,
disabled: pending,
readOnly: status === "success"
}), [status, pending]);
disabled: isPending,
readOnly: status === "success",
"data-clarity-mask": true
}), [status, isPending]);
useEffect(() =>
{
@@ -33,7 +36,7 @@ const ContactSection: React.FC = () =>
return (
<section id="contacts" className={ cls.section }>
<h2>Let's get in touch</h2>
<h2>Let&apos;s get in touch</h2>
<div className={ cls.content }>
@@ -41,19 +44,20 @@ const ContactSection: React.FC = () =>
<h3>Inquiries, requests or proposals</h3>
<form className={ cls.container } action={ formAction }>
<FormStatusTracker onPendingChanged={ setPending } />
<input name="email" type="email" { ...sharedProps } data-clarity-mask
<input name="email" type="email" { ...sharedProps }
value={ form.email } onChange={ e => setForm({ ...form, email: e.target.value }) }
autoComplete="email" spellCheck="false"
maxLength={ 60 }
placeholder="Email" />
<input name="subject" type="text" { ...sharedProps } data-clarity-mask
<input name="subject" type="text" { ...sharedProps }
value={ form.subject } onChange={ e => setForm({ ...form, subject: e.target.value }) }
autoComplete="off" spellCheck="true"
maxLength={ 120 }
placeholder="Subject" />
<textarea name="message" { ...sharedProps } className={ cls.textarea } data-clarity-mask
<textarea name="message" { ...sharedProps } className={ cls.textarea }
value={ form.message } onChange={ e => setForm({ ...form, message: e.target.value }) }
autoComplete="off" spellCheck="true"
minLength={ 100 } maxLength={ 2000 }
placeholder="Message (min 100 characters)" />
@@ -72,18 +76,18 @@ const ContactSection: React.FC = () =>
</div>
<div className={ cls.formToolbar }>
<div className={ `${cls.status} ${pending ? "" : cls[status]}` }>
{ pending &&
<div className={ `${cls.status} ${isPending ? "" : cls[status]}` }>
{ isPending &&
<span role="alert" aria-live="assertive">
Sending
</span>
}
{ !pending && status === "success" &&
{ !isPending && status === "success" &&
<span role="alert" aria-live="assertive">
Message successfully sent
</span>
}
{ !pending && status === "error" &&
{ !isPending && status === "error" &&
<span role="alert" aria-live="assertive">
{ message ?? "Something went wrong" }
</span>
@@ -91,7 +95,7 @@ const ContactSection: React.FC = () =>
</div>
{ status !== "success" &&
<Button type="submit" disabled={ pending }>
<Button type="submit" disabled={ isPending }>
Submit
</Button>
}
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.section
{
+1 -1
View File
@@ -37,7 +37,7 @@ const ExperienceSection: React.FC = () => (
function getAriaLabel(item: WorkplaceEntry): string
{
let str: string[] = [];
const str: string[] = [];
if (item.year)
str.push(`${item.year} -`);
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.section
{
@@ -30,8 +30,9 @@
.defaultImg
{
@include slideIn;
align-self: center;
@include slideIn;
}
.projectItem
+3 -2
View File
@@ -1,10 +1,11 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.section
{
@include centerTwo;
align-items: center;
@include centerTwo;
.illustrations
{
justify-self: center;
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.list
{
@@ -1,4 +1,4 @@
@import "./theme.scss";
@use "./theme" as *;
:root
{
@@ -35,6 +35,12 @@ body
max-width: 100vw;
overflow-x: hidden;
font-synthesis: none;
text-rendering: geometricPrecision;
background-color: $colorNeutralBackground1;
color: $colorNeutralForeground1;
> main
{
@include maxCenter;
@@ -43,12 +49,6 @@ body
margin-top: 56px;
}
font-synthesis: none;
text-rendering: geometricPrecision;
background-color: $colorNeutralBackground1;
color: $colorNeutralForeground1;
::selection
{
background-color: $colorNeutralBackgroundInverted;
+5
View File
@@ -0,0 +1,5 @@
@forward "dark";
@forward "light";
@forward "typography";
@forward "mixins";
@forward "tokens";
@@ -1,3 +1,6 @@
@use "tokens" as *;
@use "typography" as *;
@mixin maxCenter($maxWidth: 1400px)
{
align-self: center;
@@ -1,8 +1,3 @@
@import "./theme.dark.scss";
@import "./theme.light.scss";
@import "./theme.typography.scss";
@import "./theme.mixins.scss";
// Border radius
$borderRadiusNone: 0;
$borderRadiusSmall: 2px;
@@ -1,3 +1,5 @@
@use "tokens" as *;
@mixin caption2($font: $fontFamilyBase)
{
@include setTypography($font, $fontSizeBase100, $fontWeightRegular, $lineHeightBase100);
-32
View File
@@ -1,32 +0,0 @@
"use client";
import React, { useEffect } from "react";
import { useFormStatus } from "react-dom";
// Since useFormStatus requires to be inside of the form, I moved it to a separate helper component.
// Could it be done better? Probably.
// Did I do it? No.
/** Renders a React component that tracks the form status and calls the provided callback function when the form status changes. */
const FormStatusTracker: React.FC<FormStatusTrackerProps> = ({ onPendingChanged }) =>
{
const { pending } = useFormStatus();
useEffect(() =>
{
onPendingChanged(pending);
}, [pending, onPendingChanged]);
return null;
};
export default FormStatusTracker;
/** Props for the `FormStatusTracker` component. */
export type FormStatusTrackerProps =
{
/** The callback function that is called when the form status changes. */
// For some reason ESLint shows a warning for "unused" "pending" parameter.
// eslint-disable-next-line no-unused-vars
onPendingChanged: (pending: boolean) => void;
};
-1
View File
@@ -1,4 +1,3 @@
/* eslint-disable no-unused-vars */
declare global
{
interface Window
+3 -3
View File
@@ -35,9 +35,9 @@ function setCookie(name: string, value: string | number, maxAge: number): void
function getCookie(name: string): string | undefined
{
let cookieName = name + "=";
let rawCookies = decodeURIComponent(window.document.cookie);
let cookies = rawCookies.split(";");
const cookieName = name + "=";
const rawCookies = decodeURIComponent(window.document.cookie);
const cookies = rawCookies.split(";");
for (const cookie of cookies)
if (cookie.trim().startsWith(cookieName))
-2
View File
@@ -43,8 +43,6 @@ export default async function sendInquiry(_: FormStatus, formData: FormData): Pr
message: "Challenge has expired. Try again"
};
console.error(error);
return {
status: "error",
message: "Something went wrong"
+2 -3
View File
@@ -13,12 +13,11 @@ export async function verifyTurnstile(token: string): Promise<[false, TurnstileE
return [true];
const formData = new FormData();
console.log(headers().get("CF-Connecting-IP"));
const headerList = await headers();
formData.append("secret", process.env.CF_SECRET);
formData.append("response", token);
formData.append("remoteip", headers().get("CF-Connecting-IP") ?? "");
formData.append("remoteip", headerList.get("CF-Connecting-IP") ?? "");
const response = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify",
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.page
{
+4 -4
View File
@@ -42,13 +42,13 @@ const AttributionPage: React.FC = () => (
see the <a href="https://privacy.microsoft.com/privacystatement" target="_blank">Microsoft Privacy Statement</a>.
</p>
<p>
If the "Do Not Track" option is enabled in your browser,
If the &quot;Do Not Track&quot; option is enabled in your browser,
the website will not execute any tracking code.
</p>
{ requireExplicitConsent() &&
<p>
If you previously gave your consent to use cookies,
you can revoke it by clicking "Revoke my consent" button on this page below
you can revoke it by clicking &quot;Revoke my consent&quot; button on this page below
(the button is available only if the consent was given).
Recorded data will be deleted after 30-day retention period.
</p>
@@ -99,7 +99,7 @@ const AttributionPage: React.FC = () => (
<p>Copyright &copy; { new Date().getFullYear() } { Package.author.name }</p>
<p>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
of this software and associated documentation files (the &quot;Software&quot;), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
@@ -110,7 +110,7 @@ const AttributionPage: React.FC = () => (
copies or substantial portions of the Software.
</p>
<p>
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
THE SOFTWARE IS PROVIDED &quot;AS IS&quot;, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+1 -1
View File
@@ -7,7 +7,7 @@ import Footer from "./_components/Footer";
import Header from "./_components/Header";
import { analyticsEnabled, requireExplicitConsent } from "./_utils/analytics/server";
import fonts from "./fonts";
import "./globals.scss";
import "./_styles/globals.scss";
export const viewport: Viewport =
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "./theme.scss";
@use "@/_styles/theme" as *;
.root
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "./theme.scss";
@use "@/_styles/theme" as *;
.page
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "./theme.scss";
@use "@/_styles/theme" as *;
.page
{
+1 -1
View File
@@ -1,4 +1,4 @@
@import "../theme.scss";
@use "@/_styles/theme" as *;
.page
{
+36
View File
@@ -0,0 +1,36 @@
import { FlatCompat } from "@eslint/eslintrc";
import js from "@eslint/js";
const compat = new FlatCompat({
baseDirectory: import.meta.dirname,
recommendedConfig: js.configs.recommended
});
const eslintConfig =
[
...compat.config({
extends: ["eslint:recommended", "next/core-web-vitals", "next/typescript"],
rules:
{
"@typescript-eslint/no-explicit-any": ["off"],
"@typescript-eslint/no-unused-vars": ["warn"],
"indent": [
"warn",
"tab",
{
"SwitchCase": 1
}
],
"quotes": [
"error",
"double"
],
"semi": [
"error",
"always"
],
}
})
];
export default eslintConfig;
-47
View File
@@ -1,47 +0,0 @@
// @ts-check
/** @type {import("next-safe").nextSafe} */
// @ts-ignore
const nextSafe = require("next-safe");
/** @type {boolean} */
const isDev = process.env.NODE_ENV !== "production";
/** @type {import("next").NextConfig} */
const nextConfig = {
output: "standalone",
reactStrictMode: true,
productionBrowserSourceMaps: true,
async headers()
{
return [
{
source: "/((?!api|_next/static|_next/image|favicon.ico|clarity.js|icon.svg).*)",
headers: nextSafe({
isDev: false,
contentSecurityPolicy:
{
"script-src": isDev ?
"'self' 'unsafe-inline' https://*.clarity.ms https://c.bing.com https://*.cloudflare.com 'unsafe-eval'" :
"'self' 'unsafe-inline' https://*.clarity.ms https://c.bing.com https://*.cloudflare.com",
"connect-src": isDev ?
"'self' https://*.clarity.ms https://c.bing.com webpack://*" :
"'self' https://*.clarity.ms https://c.bing.com",
"style-src": "'self' 'unsafe-inline'",
"frame-src": "https://*.cloudflare.com 'none'",
// @ts-ignore
"prefetch-src": false
},
permissionsPolicy: false
})
}
];
}
};
module.exports = nextConfig;
+68
View File
@@ -0,0 +1,68 @@
// @ts-check
/** @type {boolean} */
const isDev = process.env.NODE_ENV !== "production";
/** @type {import("next").NextConfig} */
const nextConfig = {
output: "standalone",
reactStrictMode: true,
productionBrowserSourceMaps: true,
async headers()
{
const cspPolicy = generateCspPolicy(isDev);
return [
{
source: "/((?!api|_next/static|_next/image|favicon.ico|clarity.js|icon.svg).*)",
headers:
[
{ key: "Content-Security-Policy", value: cspPolicy },
{ key: "X-Content-Security-Policy", value: cspPolicy },
{ key: "X-WebKit-CSP", value: cspPolicy },
{ key: "Referrer-Policy", value: "no-referrer" },
{ key: "X-Content-Type-Options", value: "nosniff" },
{ key: "X-Frame-Options", value: "DENY" },
{ key: "X-XSS-Protection", value: "1; mode=block" },
]
}
];
}
};
/**
* @param {boolean} isDev
* @returns {string}
*/
function generateCspPolicy(isDev)
{
const csp =
{
"base-uri": "'none'",
"child-src": "'none'",
"connect-src": "'self' https://*.clarity.ms https://c.bing.com",
"default-src": "'self'",
"font-src": "'self'",
"form-action": "'self'",
"frame-ancestors": "'none'",
"frame-src": "https://*.cloudflare.com",
"img-src": "'self'",
"manifest-src": "'self'",
"media-src": "'self'",
"object-src": "'none'",
"script-src": "'self' https://*.clarity.ms https://c.bing.com https://*.cloudflare.com 'unsafe-inline'",
"style-src": "'self' 'unsafe-inline'",
"worker-src": "'self'"
};
if (isDev)
{
csp["connect-src"] = [csp["connect-src"].trim(), "webpack://*"].join(" ");
csp["script-src"] = [csp["script-src"].trim(), "'unsafe-eval'"].join(" ");
}
return Object.entries(csp).map(i => i.join(" ")).join(";");
}
export default nextConfig;
+18 -14
View File
@@ -21,26 +21,30 @@
"lint": "next lint"
},
"dependencies": {
"@fluentui/react-icons": "^2.0.270",
"next": "14.2.21",
"next-safe": "^3.5.0",
"nodemailer": "^6.9.16",
"@fluentui/react-icons": "^2.0.271",
"next": "^15.1.6",
"nodemailer": "^6.10.0",
"pdf-lib": "^1.17.1",
"react": "^18",
"react-dom": "^18",
"react-social-icons": "^6.18.0",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-social-icons": "^6.20.0",
"react-turnstile": "^1.1.4",
"sass": "^1.83.1",
"sharp": "^0.33.5",
"zod": "^3.24.1"
},
"devDependencies": {
"@types/node": "^22",
"@next/eslint-plugin-next": "^15.1.6",
"@types/node": "^22.10.10",
"@types/nodemailer": "^6.4.17",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "^15.1.4",
"typescript": "^5"
"@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3",
"eslint": "^9.19.0",
"eslint-config-next": "^15.1.6",
"sass": "^1.83.4",
"typescript": "^5.7.3",
"typescript-eslint": "^8.21.0"
},
"resolutions": {
"react": "^19.0.0"
}
}
+450 -396
View File
File diff suppressed because it is too large Load Diff