mirror of
https://github.com/XFox111/my-website.git
synced 2026-07-02 19:52:45 +03:00
Compare commits
42 Commits
04052025.1
...
20250723.1
| Author | SHA1 | Date | |
|---|---|---|---|
| 7b442739d6 | |||
| fd96fd3343 | |||
| 03b9201b7f | |||
| 223309d9a6 | |||
| bcb22a7740 | |||
| 536c4559cb | |||
| 40e188991f | |||
| 3dc42cbf8d | |||
| 2e3bf0b63b | |||
| 6d4c931091 | |||
| 1992ee8f29 | |||
| d0bd5db044 | |||
| 1b95c8546e | |||
| 08706f8c4e | |||
| c6f026aef5 | |||
| ef3e81c308 | |||
| e2cf91f45d | |||
| 13bd78e176 | |||
| ce217459ef | |||
| ad88ed9915 | |||
| bc61f061fd | |||
| e0a5bf4ea4 | |||
| 95c1202844 | |||
| b12c4e95db | |||
| 3658a90424 | |||
| eb879594a9 | |||
| 1109a66a0d | |||
| f3d9becc80 | |||
| aea740b21e | |||
| 3ecdb75032 | |||
| cec7852c8f | |||
| d4336af44b | |||
| 510e6220e2 | |||
| 4bdb31c884 | |||
| 6e2986f4d6 | |||
| d1707fc53e | |||
| 52c3caa395 | |||
| 492659043a | |||
| 5f84634687 | |||
| db3b76c1eb | |||
| 20a5fef75c | |||
| 9a4d6966d0 |
@@ -2,12 +2,18 @@
|
||||
// README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node
|
||||
{
|
||||
"name": "my-website",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye",
|
||||
"image": "mcr.microsoft.com/devcontainers/base:bookworm",
|
||||
|
||||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-in-docker:2": {
|
||||
"version": "latest",
|
||||
"dockerDashComposeVersion": "none"
|
||||
"enableNonRootDocker": "true",
|
||||
"moby": "false"
|
||||
},
|
||||
"ghcr.io/devcontainers/features/node:1": {
|
||||
"version": "latest",
|
||||
"pnpmVersion": "none",
|
||||
"nvmVersion": "latest"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
+2
-1
@@ -11,7 +11,8 @@ SMTP_TO_EMAIL=email # Email to which emails will be sent
|
||||
|
||||
DOMAIN_NAME=example.com # Your domain name
|
||||
RESUME_URL=URL # Location of the resume PDF
|
||||
RESUME_HAS_REFS=false # Appends last page of the resume to a result PDF file
|
||||
ATS_RESUME_URL=URL # Location of the ATS-compatible resume PDF (optional, remove to disable)
|
||||
RESUME_HAS_REFS=false # Appends last page of the resume to a result PDF file (only appies to non-ATS version)
|
||||
ALERT_TEXT_URL=URL # URL of a txt file with urgent message to be displayed (see app/_components/AlertMessage.tsx)
|
||||
CLARITY_ID=string # Clarity Analytics ID (optional, remove to disable)
|
||||
CLARITY_CONSENT=1 # 1 if you need to request explicit consent from user, 0 if not (requires CLARITY_ID)
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*.module.scss linguist-detectable=false
|
||||
@@ -0,0 +1 @@
|
||||
* @XFox111
|
||||
+4
-12
@@ -10,9 +10,7 @@ updates:
|
||||
directory: "/"
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
- "XFox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
@@ -22,9 +20,7 @@ updates:
|
||||
directory: "/"
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
- "XFox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
@@ -34,9 +30,7 @@ updates:
|
||||
directory: "/"
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
- "XFox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
@@ -46,9 +40,7 @@ updates:
|
||||
directory: "/"
|
||||
target-branch: "next"
|
||||
assignees:
|
||||
- "xfox111"
|
||||
reviewers:
|
||||
- "xfox111"
|
||||
- "XFox111"
|
||||
schedule:
|
||||
interval: monthly
|
||||
rebase-strategy: disabled
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
name: Audit pipeline
|
||||
|
||||
on:
|
||||
push:
|
||||
paths-ignore:
|
||||
- '.devcontainer/*'
|
||||
- '.github/*'
|
||||
- '!.github/workflows/audit.yml'
|
||||
- '.vscode/*'
|
||||
- '**.md'
|
||||
- '.env*'
|
||||
- 'LICENSE'
|
||||
- 'COPYING'
|
||||
- '.git*'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '.devcontainer/*'
|
||||
- '.github/*'
|
||||
- '!.github/workflows/audit.yml'
|
||||
- '.vscode/*'
|
||||
- '**.md'
|
||||
- '.env*'
|
||||
- 'LICENSE'
|
||||
- 'COPYING'
|
||||
- '.git*'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
audit:
|
||||
runs-on: ubuntu-latest
|
||||
container: node:24
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- run: corepack enable
|
||||
- run: yarn install
|
||||
- run: yarn npm audit
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
tags: "my-website:ci"
|
||||
@@ -1,35 +1,10 @@
|
||||
name: "CI pipeline"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
paths-ignore:
|
||||
- '.devcontainer/*'
|
||||
- '.github/*'
|
||||
- '!.github/workflows/ci.yml'
|
||||
- '.vscode/*'
|
||||
- '**.md'
|
||||
- '.env*'
|
||||
- 'LICENSE'
|
||||
- 'COPYING'
|
||||
pull_request:
|
||||
branches: [ "main", "deps" ]
|
||||
paths-ignore:
|
||||
- '.devcontainer/*'
|
||||
- '.github/*'
|
||||
- '!.github/workflows/ci.yml'
|
||||
- '.vscode/*'
|
||||
- '**.md'
|
||||
- '.env*'
|
||||
- 'LICENSE'
|
||||
- 'COPYING'
|
||||
release:
|
||||
types:
|
||||
- published
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
push:
|
||||
type: boolean
|
||||
required: false
|
||||
default: false
|
||||
description: "Push to Docker Hub"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -49,17 +24,15 @@ jobs:
|
||||
ghcr.io/${{ github.repository }}
|
||||
tags: |
|
||||
latest
|
||||
${{ github.sha }}
|
||||
${{ github.ref_name }}
|
||||
|
||||
- name: "Login to Docker Hub"
|
||||
if: github.event_name != 'pull_request' || github.event.inputs.push == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: "Login to GitHub Container Registry"
|
||||
if: github.event_name != 'pull_request' || github.event.inputs.push == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
registry: ghcr.io
|
||||
@@ -69,5 +42,5 @@ jobs:
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: ${{ github.event_name != 'pull_request' || github.event.inputs.push == 'true' }}
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
|
||||
@@ -34,3 +34,7 @@ yarn-error.log*
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
||||
# yarn
|
||||
.pnp.*
|
||||
.yarn
|
||||
|
||||
Vendored
+17
@@ -56,5 +56,22 @@
|
||||
"label": "docker: build",
|
||||
"detail": "Build a Docker image"
|
||||
},
|
||||
{
|
||||
"type": "shell",
|
||||
"command": "docker",
|
||||
"args": [
|
||||
"run",
|
||||
"--rm",
|
||||
"--env-file",
|
||||
".env.local",
|
||||
"-p",
|
||||
"3000:3000",
|
||||
"my-website"
|
||||
],
|
||||
"group": "test",
|
||||
"problemMatcher": [],
|
||||
"label": "docker: run",
|
||||
"detail": "Run the Docker container"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
nodeLinker: node-modules
|
||||
|
||||
logFilters:
|
||||
- level: discard
|
||||
pattern: "react is listed by your project with version * (*), which doesn't satisfy what @fluentui/react-icons and other dependencies request*"
|
||||
+6
-3
@@ -1,4 +1,4 @@
|
||||
FROM node:23-alpine AS base
|
||||
FROM node:24-alpine AS base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
@@ -7,8 +7,9 @@ RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies
|
||||
COPY package.json yarn.lock ./
|
||||
RUN yarn --frozen-lockfile
|
||||
COPY package.json yarn.lock .yarnrc.yml ./
|
||||
RUN corepack enable
|
||||
RUN yarn install
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
@@ -16,6 +17,8 @@ WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
RUN corepack enable
|
||||
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
|
||||
@@ -14,10 +14,16 @@
|
||||
{
|
||||
font-size: $fontSizeHero900;
|
||||
color: $colorStatusWarningForeground1;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.title
|
||||
{
|
||||
@include body1Stronger;
|
||||
}
|
||||
|
||||
.message
|
||||
{
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@ const AlertMessage: React.FC = async () =>
|
||||
if (!response.ok || !alertText)
|
||||
return null;
|
||||
|
||||
const title: string = alertText.split("\n", 1)[0];
|
||||
const message: string = alertText.substring(title.length);
|
||||
const title: string = alertText.split("\n", 1)[0].trim();
|
||||
const message: string = alertText.substring(title.length + 1).trim();
|
||||
|
||||
return (
|
||||
<div role="alert" className={ cls.alertBox } aria-label={ alertText }>
|
||||
<ChatWarningRegular className={ cls.icon } />
|
||||
<div>
|
||||
<p className={ cls.title }>{ title }</p>
|
||||
<p dangerouslySetInnerHTML={ { __html: message } } />
|
||||
<p className={ cls.message } dangerouslySetInnerHTML={ { __html: message } } />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -23,8 +23,8 @@ const FrontSection: React.FC = () => (
|
||||
</div>
|
||||
|
||||
<div className={ cls.illustrations }>
|
||||
<Image className={ cls.main } src={ profilePicture.src } alt={ profilePicture.alt } priority />
|
||||
<Image className={ cls.secondary } src={ homeDecor.src } alt={ homeDecor.alt } />
|
||||
<Image className={ cls.main } src={ profilePicture.src } alt={ profilePicture.alt } loading="eager" />
|
||||
<Image className={ cls.secondary } src={ homeDecor.src } alt={ homeDecor.alt } loading="eager" />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -9,7 +9,7 @@ const TitleLogo: React.FC = () => (
|
||||
<Image src={ logo }
|
||||
alt="A fox jumping down, and a diagonal stripe in the background, forming letters X and F"
|
||||
aria-hidden
|
||||
priority />
|
||||
loading="eager" />
|
||||
<p>
|
||||
<span>xfox111</span>
|
||||
<sub>.net</sub>
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
export const bio: string[] =
|
||||
[
|
||||
"My name is Eugene Fox. I am a professional software developer primarily focused on .NET and React projects.",
|
||||
|
||||
"My journey as a programmer started in 2018 from a silly free-time hobby. Since then, I have released a couple of personal projects, some of which have become quite popular.",
|
||||
|
||||
"Graduated from Bonch-Bruevich University of Telecommunications in 2023 where I got my bachelor's degree in computer science. It was fun. Took part in a number of hackathons (usually first place for us) as well as science conferences (including those hosted by IEEE).",
|
||||
|
||||
"Also, before graduation I managed to work in several different companies in different IT fields (mostly software development, of course).",
|
||||
|
||||
"Out-of-box thinking, and constant self-improvement is my life strategy. New tool released? - Yes, please! GitHub is hosting another conference? - Sign me up! There is a new challenging task to complete? - Oh, boy, here we go again! So many things to learn, so little time to spare...",
|
||||
|
||||
"Overall, enthusiastic, fast learning and energetic person. Love coding and creating something new. Like to draw and compose music. Proud member of the furry community."
|
||||
];
|
||||
|
||||
export default bio;
|
||||
@@ -0,0 +1,14 @@
|
||||
export const Bio: React.FC = () => (
|
||||
<>
|
||||
<p>{ bioPremise }</p>
|
||||
<p>My journey as a programmer started in 2018 from a silly free-time hobby. Since then, I have released a couple of personal projects, some of which have become quite popular.</p>
|
||||
<p>Graduated from <a href="https://sut.ru/eng" target="_blank">Bonch-Bruevich University of Telecommunications</a> in 2023 where I got my Bachelor's degree in computer science. It was fun. Took part in a number of hackathons (usually first place for us) as well as science conferences (including those hosted by IEEE). In 2025 got Master's in Radiotechnology.</p>
|
||||
<p>Also, before graduation I managed to work in several different companies in different IT fields (mostly software development, of course).</p>
|
||||
<p>Out-of-box thinking, and constant self-improvement is my life strategy. New tool released? - Yes, please! GitHub is hosting another conference? - Sign me up! There is a new challenging task to complete? - Oh, boy, here we go again! So many things to learn, so little time to spare...</p>
|
||||
<p>Overall, enthusiastic, fast learning and energetic person. Love coding and creating something new. Like to draw and compose music. Aviasim enthusiast. Proud member of the furry community.</p>
|
||||
</>
|
||||
);
|
||||
|
||||
export const bioPremise: string = "My name is Eugene Fox. I am a professional software developer primarily focused on .NET and React projects.";
|
||||
|
||||
export default Bio;
|
||||
@@ -5,7 +5,7 @@ const experience: WorkplaceEntry[] =
|
||||
{ title: "Software Engineer", place: "[nordcloud]", tech: "ASP.NET, EF Core" },
|
||||
{ title: "CTO", year: "2022", place: "FoxDev Studio", tech: "Unity, Xamarin, .NET, React, Azure" },
|
||||
{ title: "Senior Software Engineer", year: "2023", place: "A-rial", tech: ".NET, React, DevOps" },
|
||||
{ title: "Senior Software Architect", year: "2024", place: "Ubitel", tech: ".NET, React, Embedded devices" },
|
||||
{ title: "Senior Software Architect", year: "2024", place: "A-rial", tech: ".NET, React, Embedded devices" },
|
||||
{ title: "Here", place: "Your company" },
|
||||
];
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import Package from "@/../package.json";
|
||||
import { Metadata } from "next";
|
||||
import { unstable_noStore } from "next/cache";
|
||||
import bio from "./bio";
|
||||
import { bioPremise } from "./bio";
|
||||
import socials from "./socials";
|
||||
|
||||
export const canonicalName: URL = new URL(`https://${process.env.DOMAIN_NAME}`);
|
||||
@@ -18,12 +18,12 @@ export async function generateMetadata(): Promise<Metadata>
|
||||
unstable_noStore();
|
||||
return {
|
||||
title: baseTitle,
|
||||
description: bio[0],
|
||||
description: bioPremise,
|
||||
metadataBase: canonicalName,
|
||||
openGraph:
|
||||
{
|
||||
title: baseTitle,
|
||||
description: bio[0],
|
||||
description: bioPremise,
|
||||
type: "profile",
|
||||
firstName: Package.author.name.split(" ")[0],
|
||||
lastName: Package.author.name.split(" ")[1],
|
||||
|
||||
@@ -24,17 +24,16 @@ const projects: Project[] =
|
||||
"In the end, I have created a big web service with mobile app and a customer portal, that could authenticate users on any website, and any device within a few seconds."
|
||||
],
|
||||
image: ezlogImg,
|
||||
link: "https://ezlog.app/about",
|
||||
link: "https://github.com/xfox111/easylogon-web",
|
||||
stack:
|
||||
[
|
||||
{ text: "C#/TypeScript", icon: ic.Code24Regular },
|
||||
{ text: ".NET 6", icon: ic.Server24Regular },
|
||||
{ text: "ReactJS", icon: ic.PhoneDesktop24Regular },
|
||||
{ text: "React/Vite", icon: ic.PhoneDesktop24Regular },
|
||||
{ text: "Xamarin.Forms", icon: ic.Phone24Regular },
|
||||
{ text: "SQL Server", icon: ic.Database24Regular },
|
||||
{ text: "Azure DevOps", icon: ic.Branch24Regular },
|
||||
{ text: "Azure Pipelines/AppCenter", icon: ic.FlashFlow24Regular },
|
||||
{ text: "AppCenter", icon: ic.HeartPulse24Regular }
|
||||
{ text: "Azure Pipelines/GitHub Actions", icon: ic.FlashFlow24Regular }
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -47,12 +46,12 @@ const projects: Project[] =
|
||||
],
|
||||
image: tabsAsideLight,
|
||||
imageDark: tabsAsideDark,
|
||||
link: "https://github.com/xfox111/TabsAsideExtension",
|
||||
link: "https://github.com/xfox111/TabsAsideExtension/tree/next",
|
||||
stack:
|
||||
[
|
||||
{ text: "ReactJS", icon: ic.Desktop24Regular },
|
||||
{ text: "TypeScript/SASS", icon: ic.Code24Regular },
|
||||
{ text: "Chrome/WebExt", icon: ic.FlashSettings24Regular },
|
||||
{ text: "React/WXT", icon: ic.Desktop24Regular },
|
||||
{ text: "TypeScript", icon: ic.Code24Regular },
|
||||
{ text: "Browser extension", icon: ic.FlashSettings24Regular },
|
||||
{ text: "Fluent UI", icon: ic.Color24Regular },
|
||||
{ text: "GitHub", icon: ic.Branch24Regular },
|
||||
{ text: "GitHub Actions", icon: ic.FlashFlow24Regular },
|
||||
@@ -91,7 +90,7 @@ const projects: Project[] =
|
||||
[
|
||||
{ text: "React/Vite", icon: ic.Desktop24Regular },
|
||||
{ text: "TypeScript", icon: ic.Code24Regular },
|
||||
{ text: "Chrome/WebExt", icon: ic.FlashSettings24Regular },
|
||||
{ text: "Browser extension", icon: ic.FlashSettings24Regular },
|
||||
{ text: "Fluent UI", icon: ic.Color24Regular },
|
||||
{ text: "GitHub", icon: ic.Branch24Regular },
|
||||
{ text: "GitHub Actions", icon: ic.FlashFlow24Regular },
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { aboutPicture } from "@/_assets/illustrations";
|
||||
import bio from "@/_data/bio";
|
||||
import Bio from "@/_data/bio";
|
||||
import Image from "next/image";
|
||||
import React from "react";
|
||||
import cls from "./AboutSection.module.scss";
|
||||
@@ -9,9 +9,7 @@ const AboutSection: React.FC = () => (
|
||||
<div>
|
||||
<h2>About me</h2>
|
||||
|
||||
{ bio.map((i, index) =>
|
||||
<p key={ index }>{ i }</p>
|
||||
) }
|
||||
<Bio />
|
||||
</div>
|
||||
|
||||
<Image src={ aboutPicture.src } alt={ aboutPicture.alt } />
|
||||
|
||||
@@ -51,10 +51,14 @@ const ProjectsSection: React.FC = () =>
|
||||
<div className={ cls.descriptions } aria-live="polite" aria-atomic>
|
||||
{ projects.map((project, index) =>
|
||||
<div key={ index } className={ cls.projectItem } hidden={ selection !== index }>
|
||||
<Image src={ project.image } alt={ project.title } data-theme={ project.imageDark ? "light" : "both" } />
|
||||
<Image
|
||||
src={ project.image } alt={ project.title }
|
||||
data-theme={ project.imageDark ? "light" : "both" }
|
||||
loading="eager" />
|
||||
|
||||
{/* This is a workaround since not all images can be theme-adaptive */ }
|
||||
{ project.imageDark &&
|
||||
<Image src={ project.imageDark } alt="" data-theme="dark" />
|
||||
<Image src={ project.imageDark } alt="" data-theme="dark" loading="eager" />
|
||||
}
|
||||
|
||||
<h3>{ project.title }</h3>
|
||||
@@ -80,7 +84,7 @@ const ProjectsSection: React.FC = () =>
|
||||
</div>
|
||||
) }
|
||||
<Image className={ cls.defaultImg } hidden={ selection !== undefined }
|
||||
src={ projectsImg.src } alt={ projectsImg.alt } />
|
||||
src={ projectsImg.src } alt={ projectsImg.alt } loading="eager" />
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -26,7 +26,7 @@ const SkillsSection: React.FC = () =>
|
||||
{ skills.map((i, index) =>
|
||||
<Image key={ index }
|
||||
src={ i.image.src } alt={ i.image.alt }
|
||||
hidden={ selection !== index } />
|
||||
hidden={ selection !== index } loading="eager" />
|
||||
) }
|
||||
|
||||
{ selection === 4 &&
|
||||
|
||||
+2
-2
@@ -5,8 +5,8 @@ import cls from "./loading.module.scss";
|
||||
|
||||
const LoadingPage: React.FC = () => (
|
||||
<div className={ cls.root } role="alert" aria-label="Loading page">
|
||||
<Image className={ cls.dark } src={ spinnerDark.src } alt={ spinnerDark.alt } priority unoptimized />
|
||||
<Image className={ cls.light } src={ spinnerLight.src } alt={ spinnerLight.alt } priority unoptimized />
|
||||
<Image className={ cls.dark } src={ spinnerDark.src } alt={ spinnerDark.alt } loading="eager" unoptimized />
|
||||
<Image className={ cls.light } src={ spinnerLight.src } alt={ spinnerLight.alt } loading="eager" unoptimized />
|
||||
</div>
|
||||
);
|
||||
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ const NotFoundPage: React.FC = () => (
|
||||
<main className={ `${cls.page} not-found` }>
|
||||
<div className={ cls.illustration }>
|
||||
<h1>404...</h1>
|
||||
<Image src={ notFoundImage.src } alt={ notFoundImage.alt } priority />
|
||||
<Image src={ notFoundImage.src } alt={ notFoundImage.alt } loading="eager" />
|
||||
</div>
|
||||
<div className={ cls.content }>
|
||||
<div className={ cls.caption }>
|
||||
|
||||
@@ -5,18 +5,22 @@ import { PDFDocument, PDFPage } from "pdf-lib";
|
||||
export async function GET(req: NextRequest): Promise<Response>
|
||||
{
|
||||
const type: string | null = req.nextUrl.searchParams.get("type");
|
||||
const isAts: boolean = req.nextUrl.searchParams.get("ats") === "true";
|
||||
const resume: Resume | undefined = findResume(type);
|
||||
const url: string | undefined = isAts ? process.env.ATS_RESUME_URL : process.env.RESUME_URL;
|
||||
|
||||
if (!resume)
|
||||
return error(400, "'type' parameter is invalid");
|
||||
|
||||
if (!process.env.RESUME_URL)
|
||||
const fileName: string = (isAts ? "(ATS) " + resume.fileName : resume.fileName).replaceAll("\"", "'");
|
||||
|
||||
if (!url)
|
||||
return error(500, "Cannot find file location.");
|
||||
|
||||
try
|
||||
{
|
||||
// Fetch the PDF file from the remote URL using the fetch API
|
||||
const response: Response = await fetch(process.env.RESUME_URL as string);
|
||||
const response: Response = await fetch(url);
|
||||
|
||||
if (!response.ok)
|
||||
return error(500, "Failed to fetch PDF file");
|
||||
@@ -30,7 +34,7 @@ export async function GET(req: NextRequest): Promise<Response>
|
||||
const [page, refs]: PDFPage[] = await newDoc.copyPages(srcDoc, [resume.pageIndex, srcDoc.getPageCount() - 1]);
|
||||
newDoc.addPage(page);
|
||||
|
||||
if (process.env.RESUME_HAS_REFS === "true")
|
||||
if (process.env.RESUME_HAS_REFS === "true" && isAts)
|
||||
newDoc.addPage(refs);
|
||||
|
||||
// Serialize the new PDF document
|
||||
@@ -43,7 +47,7 @@ export async function GET(req: NextRequest): Promise<Response>
|
||||
// Set response headers for PDF file
|
||||
headers: {
|
||||
"Content-Type": "application/pdf",
|
||||
"Content-Disposition": `inline; filename="${resume.fileName.replaceAll("\"", "'")}.pdf"`
|
||||
"Content-Disposition": `inline; filename="${fileName}.pdf"`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -20,6 +20,16 @@
|
||||
grid-auto-columns: 1fr;
|
||||
gap: $spacingXL;
|
||||
|
||||
.buttonContainer
|
||||
{
|
||||
@include flex(column);
|
||||
}
|
||||
|
||||
.atsLink
|
||||
{
|
||||
@include body1();
|
||||
}
|
||||
|
||||
.button
|
||||
{
|
||||
flex-flow: column;
|
||||
|
||||
+15
-8
@@ -21,15 +21,22 @@ const ResumePage: React.FC = () => (
|
||||
<h1>Who are you looking for?</h1>
|
||||
<div className={ cls.resumeButtons }>
|
||||
{ resumeList.map(i =>
|
||||
<Button key={ i.key } className={ cls.button }
|
||||
href={ `/resume/download?type=${i.key}` } download
|
||||
icon={
|
||||
<Image className={ cls.image } src={ i.image.src } priority draggable={ false }
|
||||
aria-hidden alt={ i.image.alt } />
|
||||
}>
|
||||
<div key={ i.key } className={ cls.buttonContainer }>
|
||||
<Button className={ cls.button }
|
||||
href={ `/resume/download?type=${i.key}` } download
|
||||
icon={
|
||||
<Image className={ cls.image } src={ i.image.src } loading="eager" draggable={ false }
|
||||
aria-hidden alt={ i.image.alt } />
|
||||
}>
|
||||
|
||||
{ i.label }
|
||||
</Button>
|
||||
{ i.label }
|
||||
</Button>
|
||||
{ process.env.ATS_RESUME_URL &&
|
||||
<a className={ cls.atsLink } href={ `/resume/download?type=${i.key}&ats=true` } download>
|
||||
ATS-compatible version
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
) }
|
||||
</div>
|
||||
|
||||
|
||||
+1
-1
@@ -51,7 +51,7 @@ function generateCspPolicy(isDev)
|
||||
"manifest-src": "'self'",
|
||||
"media-src": "'self'",
|
||||
"object-src": "'none'",
|
||||
"script-src": "'self' https://*.clarity.ms https://c.bing.com https://*.cloudflare.com 'unsafe-inline'",
|
||||
"script-src": "'self' https://*.clarity.ms https://c.bing.com https://*.cloudflare.com https://*.cloudflareinsights.com 'unsafe-inline'",
|
||||
"style-src": "'self' 'unsafe-inline'",
|
||||
"worker-src": "'self'"
|
||||
};
|
||||
|
||||
+14
-16
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "my-website",
|
||||
"version": "1.0.0",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"homepage": "https://xfox111.net",
|
||||
"license": "(MIT with exception)",
|
||||
@@ -21,30 +21,28 @@
|
||||
"lint": "next lint"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluentui/react-icons": "^2.0.298",
|
||||
"next": "^15.3.1",
|
||||
"nodemailer": "^7.0.0",
|
||||
"@fluentui/react-icons": "^2.0.305",
|
||||
"next": "^15.3.4",
|
||||
"nodemailer": "^7.0.4",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-social-icons": "^6.24.0",
|
||||
"react-turnstile": "^1.1.4",
|
||||
"sharp": "^0.34.1",
|
||||
"zod": "^3.24.3"
|
||||
"sharp": "^0.34.2",
|
||||
"zod": "^3.25.67"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@next/eslint-plugin-next": "^15.3.1",
|
||||
"@types/node": "^22.15.3",
|
||||
"@next/eslint-plugin-next": "^15.3.4",
|
||||
"@types/node": "^24.0.8",
|
||||
"@types/nodemailer": "^6.4.17",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.3",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"eslint": "^9.19.0",
|
||||
"eslint-config-next": "^15.3.1",
|
||||
"sass": "^1.87.0",
|
||||
"eslint-config-next": "^15.3.4",
|
||||
"sass": "^1.89.2",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.31.1"
|
||||
"typescript-eslint": "^8.35.1"
|
||||
},
|
||||
"resolutions": {
|
||||
"react": "^19.0.0"
|
||||
}
|
||||
"packageManager": "yarn@4.9.2"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user