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:
@@ -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,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.alertBox
|
.alertBox
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.button
|
.button
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.banner
|
.banner
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.footer
|
.footer
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.header
|
.header
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.navigation
|
.navigation
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.dialog
|
.dialog
|
||||||
{
|
{
|
||||||
@@ -15,6 +15,9 @@
|
|||||||
color: unset;
|
color: unset;
|
||||||
border: none;
|
border: none;
|
||||||
|
|
||||||
|
transition: right $durationNormal $curveEasyEaseMax;
|
||||||
|
right: -350px;
|
||||||
|
|
||||||
&::backdrop
|
&::backdrop
|
||||||
{
|
{
|
||||||
-webkit-backdrop-filter: blur(8px);
|
-webkit-backdrop-filter: blur(8px);
|
||||||
@@ -64,9 +67,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
transition: right $durationNormal $curveEasyEaseMax;
|
|
||||||
right: -350px;
|
|
||||||
|
|
||||||
&.show
|
&.show
|
||||||
{
|
{
|
||||||
right: 0;
|
right: 0;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.socials
|
.socials
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.section
|
.section
|
||||||
{
|
{
|
||||||
@include centerTwo;
|
|
||||||
min-height: 75vh;
|
min-height: 75vh;
|
||||||
align-items: end;
|
align-items: end;
|
||||||
|
|
||||||
|
@include centerTwo;
|
||||||
|
|
||||||
.content
|
.content
|
||||||
{
|
{
|
||||||
@include flex(column);
|
@include flex(column);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import React from "react";
|
|||||||
const ThirdPartyAttribution: React.FC = () => <>
|
const ThirdPartyAttribution: React.FC = () => <>
|
||||||
<p>
|
<p>
|
||||||
Iconography, typography, and color palette of this website are based on
|
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'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>.
|
the <a target="_blank" href="https://github.com/microsoft/fluentui/blob/main/LICENSE">MIT License</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.title
|
.title
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.section
|
.section
|
||||||
{
|
{
|
||||||
@include centerTwo;
|
|
||||||
@include body2($fontFamilyBaseAlt);
|
@include body2($fontFamilyBaseAlt);
|
||||||
color: $colorNeutralForeground2;
|
color: $colorNeutralForeground2;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
@include centerTwo;
|
||||||
|
|
||||||
> div:first-child
|
> div:first-child
|
||||||
{
|
{
|
||||||
@include flex(column);
|
@include flex(column);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.section
|
.section
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,28 +3,31 @@
|
|||||||
import Button from "@/_components/Button";
|
import Button from "@/_components/Button";
|
||||||
import SocialLinks from "@/_components/SocialLinks";
|
import SocialLinks from "@/_components/SocialLinks";
|
||||||
import contacts from "@/_data/contacts";
|
import contacts from "@/_data/contacts";
|
||||||
import FormStatusTracker from "@/_utils/FormStatusTracker";
|
import { getSitekey } from "@/_utils/turnstile";
|
||||||
import React, { InputHTMLAttributes, useEffect, useMemo, useState } from "react";
|
import React, { InputHTMLAttributes, useActionState, useEffect, useMemo, useState } from "react";
|
||||||
import { useFormState } from "react-dom";
|
|
||||||
import Turnstile from "react-turnstile";
|
import Turnstile from "react-turnstile";
|
||||||
import sendInquiry, { FormStatus } from "../_utils/sendInquiry";
|
import sendInquiry, { FormStatus } from "../_utils/sendInquiry";
|
||||||
import cls from "./ContactSection.module.scss";
|
import cls from "./ContactSection.module.scss";
|
||||||
import { getSitekey } from "@/_utils/turnstile";
|
|
||||||
|
|
||||||
const defaultState: FormStatus = { status: "idle" };
|
const defaultState: FormStatus = { status: "idle" };
|
||||||
|
|
||||||
const ContactSection: React.FC = () =>
|
const ContactSection: React.FC = () =>
|
||||||
{
|
{
|
||||||
const [pending, setPending] = useState<boolean>(false);
|
// Added state-backing for the form fields to prevent it from resetting on submit
|
||||||
const [{ status, message }, formAction] = useFormState<FormStatus, FormData>(sendInquiry, defaultState);
|
// 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 { telephone: phone, email, socials } = contacts;
|
||||||
const [cfSitekey, setCfSitekey] = useState<string | undefined | null>(null);
|
const [cfSitekey, setCfSitekey] = useState<string | undefined | null>(null);
|
||||||
|
|
||||||
const sharedProps: InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement> = useMemo(() => ({
|
const sharedProps: InputHTMLAttributes<HTMLInputElement | HTMLTextAreaElement> = useMemo(() => ({
|
||||||
required: true,
|
required: true,
|
||||||
disabled: pending,
|
disabled: isPending,
|
||||||
readOnly: status === "success"
|
readOnly: status === "success",
|
||||||
}), [status, pending]);
|
"data-clarity-mask": true
|
||||||
|
}), [status, isPending]);
|
||||||
|
|
||||||
useEffect(() =>
|
useEffect(() =>
|
||||||
{
|
{
|
||||||
@@ -33,7 +36,7 @@ const ContactSection: React.FC = () =>
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="contacts" className={ cls.section }>
|
<section id="contacts" className={ cls.section }>
|
||||||
<h2>Let's get in touch</h2>
|
<h2>Let's get in touch</h2>
|
||||||
|
|
||||||
<div className={ cls.content }>
|
<div className={ cls.content }>
|
||||||
|
|
||||||
@@ -41,19 +44,20 @@ const ContactSection: React.FC = () =>
|
|||||||
<h3>Inquiries, requests or proposals</h3>
|
<h3>Inquiries, requests or proposals</h3>
|
||||||
|
|
||||||
<form className={ cls.container } action={ formAction }>
|
<form className={ cls.container } action={ formAction }>
|
||||||
<FormStatusTracker onPendingChanged={ setPending } />
|
<input name="email" type="email" { ...sharedProps }
|
||||||
|
value={ form.email } onChange={ e => setForm({ ...form, email: e.target.value }) }
|
||||||
<input name="email" type="email" { ...sharedProps } data-clarity-mask
|
|
||||||
autoComplete="email" spellCheck="false"
|
autoComplete="email" spellCheck="false"
|
||||||
maxLength={ 60 }
|
maxLength={ 60 }
|
||||||
placeholder="Email" />
|
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"
|
autoComplete="off" spellCheck="true"
|
||||||
maxLength={ 120 }
|
maxLength={ 120 }
|
||||||
placeholder="Subject" />
|
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"
|
autoComplete="off" spellCheck="true"
|
||||||
minLength={ 100 } maxLength={ 2000 }
|
minLength={ 100 } maxLength={ 2000 }
|
||||||
placeholder="Message (min 100 characters)" />
|
placeholder="Message (min 100 characters)" />
|
||||||
@@ -72,18 +76,18 @@ const ContactSection: React.FC = () =>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={ cls.formToolbar }>
|
<div className={ cls.formToolbar }>
|
||||||
<div className={ `${cls.status} ${pending ? "" : cls[status]}` }>
|
<div className={ `${cls.status} ${isPending ? "" : cls[status]}` }>
|
||||||
{ pending &&
|
{ isPending &&
|
||||||
<span role="alert" aria-live="assertive">
|
<span role="alert" aria-live="assertive">
|
||||||
Sending
|
Sending
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
{ !pending && status === "success" &&
|
{ !isPending && status === "success" &&
|
||||||
<span role="alert" aria-live="assertive">
|
<span role="alert" aria-live="assertive">
|
||||||
Message successfully sent
|
Message successfully sent
|
||||||
</span>
|
</span>
|
||||||
}
|
}
|
||||||
{ !pending && status === "error" &&
|
{ !isPending && status === "error" &&
|
||||||
<span role="alert" aria-live="assertive">
|
<span role="alert" aria-live="assertive">
|
||||||
{ message ?? "Something went wrong" }
|
{ message ?? "Something went wrong" }
|
||||||
</span>
|
</span>
|
||||||
@@ -91,7 +95,7 @@ const ContactSection: React.FC = () =>
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{ status !== "success" &&
|
{ status !== "success" &&
|
||||||
<Button type="submit" disabled={ pending }>
|
<Button type="submit" disabled={ isPending }>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.section
|
.section
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ const ExperienceSection: React.FC = () => (
|
|||||||
|
|
||||||
function getAriaLabel(item: WorkplaceEntry): string
|
function getAriaLabel(item: WorkplaceEntry): string
|
||||||
{
|
{
|
||||||
let str: string[] = [];
|
const str: string[] = [];
|
||||||
|
|
||||||
if (item.year)
|
if (item.year)
|
||||||
str.push(`${item.year} -`);
|
str.push(`${item.year} -`);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.section
|
.section
|
||||||
{
|
{
|
||||||
@@ -30,8 +30,9 @@
|
|||||||
|
|
||||||
.defaultImg
|
.defaultImg
|
||||||
{
|
{
|
||||||
@include slideIn;
|
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
|
||||||
|
@include slideIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
.projectItem
|
.projectItem
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.section
|
.section
|
||||||
{
|
{
|
||||||
@include centerTwo;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
@include centerTwo;
|
||||||
|
|
||||||
.illustrations
|
.illustrations
|
||||||
{
|
{
|
||||||
justify-self: center;
|
justify-self: center;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.list
|
.list
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "./theme.scss";
|
@use "./theme" as *;
|
||||||
|
|
||||||
:root
|
:root
|
||||||
{
|
{
|
||||||
@@ -35,6 +35,12 @@ body
|
|||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
|
font-synthesis: none;
|
||||||
|
text-rendering: geometricPrecision;
|
||||||
|
|
||||||
|
background-color: $colorNeutralBackground1;
|
||||||
|
color: $colorNeutralForeground1;
|
||||||
|
|
||||||
> main
|
> main
|
||||||
{
|
{
|
||||||
@include maxCenter;
|
@include maxCenter;
|
||||||
@@ -43,12 +49,6 @@ body
|
|||||||
margin-top: 56px;
|
margin-top: 56px;
|
||||||
}
|
}
|
||||||
|
|
||||||
font-synthesis: none;
|
|
||||||
text-rendering: geometricPrecision;
|
|
||||||
|
|
||||||
background-color: $colorNeutralBackground1;
|
|
||||||
color: $colorNeutralForeground1;
|
|
||||||
|
|
||||||
::selection
|
::selection
|
||||||
{
|
{
|
||||||
background-color: $colorNeutralBackgroundInverted;
|
background-color: $colorNeutralBackgroundInverted;
|
||||||
@@ -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)
|
@mixin maxCenter($maxWidth: 1400px)
|
||||||
{
|
{
|
||||||
align-self: center;
|
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
|
// Border radius
|
||||||
$borderRadiusNone: 0;
|
$borderRadiusNone: 0;
|
||||||
$borderRadiusSmall: 2px;
|
$borderRadiusSmall: 2px;
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@use "tokens" as *;
|
||||||
|
|
||||||
@mixin caption2($font: $fontFamilyBase)
|
@mixin caption2($font: $fontFamilyBase)
|
||||||
{
|
{
|
||||||
@include setTypography($font, $fontSizeBase100, $fontWeightRegular, $lineHeightBase100);
|
@include setTypography($font, $fontSizeBase100, $fontWeightRegular, $lineHeightBase100);
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
Vendored
-1
@@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable no-unused-vars */
|
|
||||||
declare global
|
declare global
|
||||||
{
|
{
|
||||||
interface Window
|
interface Window
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ function setCookie(name: string, value: string | number, maxAge: number): void
|
|||||||
|
|
||||||
function getCookie(name: string): string | undefined
|
function getCookie(name: string): string | undefined
|
||||||
{
|
{
|
||||||
let cookieName = name + "=";
|
const cookieName = name + "=";
|
||||||
let rawCookies = decodeURIComponent(window.document.cookie);
|
const rawCookies = decodeURIComponent(window.document.cookie);
|
||||||
let cookies = rawCookies.split(";");
|
const cookies = rawCookies.split(";");
|
||||||
|
|
||||||
for (const cookie of cookies)
|
for (const cookie of cookies)
|
||||||
if (cookie.trim().startsWith(cookieName))
|
if (cookie.trim().startsWith(cookieName))
|
||||||
|
|||||||
@@ -43,8 +43,6 @@ export default async function sendInquiry(_: FormStatus, formData: FormData): Pr
|
|||||||
message: "Challenge has expired. Try again"
|
message: "Challenge has expired. Try again"
|
||||||
};
|
};
|
||||||
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "error",
|
status: "error",
|
||||||
message: "Something went wrong"
|
message: "Something went wrong"
|
||||||
|
|||||||
@@ -13,12 +13,11 @@ export async function verifyTurnstile(token: string): Promise<[false, TurnstileE
|
|||||||
return [true];
|
return [true];
|
||||||
|
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
|
const headerList = await headers();
|
||||||
console.log(headers().get("CF-Connecting-IP"));
|
|
||||||
|
|
||||||
formData.append("secret", process.env.CF_SECRET);
|
formData.append("secret", process.env.CF_SECRET);
|
||||||
formData.append("response", token);
|
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",
|
const response = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify",
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.page
|
.page
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -42,13 +42,13 @@ const AttributionPage: React.FC = () => (
|
|||||||
see the <a href="https://privacy.microsoft.com/privacystatement" target="_blank">Microsoft Privacy Statement</a>.
|
see the <a href="https://privacy.microsoft.com/privacystatement" target="_blank">Microsoft Privacy Statement</a>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
If the "Do Not Track" option is enabled in your browser,
|
If the "Do Not Track" option is enabled in your browser,
|
||||||
the website will not execute any tracking code.
|
the website will not execute any tracking code.
|
||||||
</p>
|
</p>
|
||||||
{ requireExplicitConsent() &&
|
{ requireExplicitConsent() &&
|
||||||
<p>
|
<p>
|
||||||
If you previously gave your consent to use cookies,
|
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 "Revoke my consent" button on this page below
|
||||||
(the button is available only if the consent was given).
|
(the button is available only if the consent was given).
|
||||||
Recorded data will be deleted after 30-day retention period.
|
Recorded data will be deleted after 30-day retention period.
|
||||||
</p>
|
</p>
|
||||||
@@ -99,7 +99,7 @@ const AttributionPage: React.FC = () => (
|
|||||||
<p>Copyright © { new Date().getFullYear() } { Package.author.name }</p>
|
<p>Copyright © { new Date().getFullYear() } { Package.author.name }</p>
|
||||||
<p>
|
<p>
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
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 "Software"), to deal
|
||||||
in the Software without restriction, including without limitation the rights
|
in the Software without restriction, including without limitation the rights
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
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.
|
copies or substantial portions of the Software.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
|||||||
+1
-1
@@ -7,7 +7,7 @@ import Footer from "./_components/Footer";
|
|||||||
import Header from "./_components/Header";
|
import Header from "./_components/Header";
|
||||||
import { analyticsEnabled, requireExplicitConsent } from "./_utils/analytics/server";
|
import { analyticsEnabled, requireExplicitConsent } from "./_utils/analytics/server";
|
||||||
import fonts from "./fonts";
|
import fonts from "./fonts";
|
||||||
import "./globals.scss";
|
import "./_styles/globals.scss";
|
||||||
|
|
||||||
export const viewport: Viewport =
|
export const viewport: Viewport =
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "./theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.root
|
.root
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "./theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.page
|
.page
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "./theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.page
|
.page
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@import "../theme.scss";
|
@use "@/_styles/theme" as *;
|
||||||
|
|
||||||
.page
|
.page
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
|
||||||
@@ -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
@@ -21,26 +21,30 @@
|
|||||||
"lint": "next lint"
|
"lint": "next lint"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fluentui/react-icons": "^2.0.270",
|
"@fluentui/react-icons": "^2.0.271",
|
||||||
"next": "14.2.21",
|
"next": "^15.1.6",
|
||||||
"next-safe": "^3.5.0",
|
"nodemailer": "^6.10.0",
|
||||||
"nodemailer": "^6.9.16",
|
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"react": "^18",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^18",
|
"react-dom": "^19.0.0",
|
||||||
"react-social-icons": "^6.18.0",
|
"react-social-icons": "^6.20.0",
|
||||||
"react-turnstile": "^1.1.4",
|
"react-turnstile": "^1.1.4",
|
||||||
"sass": "^1.83.1",
|
|
||||||
"sharp": "^0.33.5",
|
"sharp": "^0.33.5",
|
||||||
"zod": "^3.24.1"
|
"zod": "^3.24.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22",
|
"@next/eslint-plugin-next": "^15.1.6",
|
||||||
|
"@types/node": "^22.10.10",
|
||||||
"@types/nodemailer": "^6.4.17",
|
"@types/nodemailer": "^6.4.17",
|
||||||
"@types/react": "^18",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^19.0.3",
|
||||||
"eslint": "^8",
|
"eslint": "^9.19.0",
|
||||||
"eslint-config-next": "^15.1.4",
|
"eslint-config-next": "^15.1.6",
|
||||||
"typescript": "^5"
|
"sass": "^1.83.4",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.21.0"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"react": "^19.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user