17 Commits
v1.1.0 ... main

Author SHA1 Message Date
47777acd74 Docker tweaks and PDF width fix
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 4s
2025-05-06 18:07:52 -04:00
32d98f4bc0 Updates 2025-05-06 14:03:50 -04:00
ef60832bc2 Add CORS for GETs
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 4s
2025-05-01 22:11:06 -04:00
4f782560de downgrade tus server
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 41s
2025-05-01 21:59:27 -04:00
979cf7320e Switch to alpine
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 43s
2025-05-01 21:41:35 -04:00
1f9f11e1be Fix crash (thank you gpt o3!)
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 5s
2025-05-01 21:18:38 -04:00
1d183c37f8 Cors is gone..? reverting old changes
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 5s
2025-05-01 21:05:40 -04:00
3aeec4d23e Fix PDF once and for all
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 57s
2025-05-01 20:46:50 -04:00
debfcf7226 Attempt 2
All checks were successful
Publish Development Docker Image / Publish Development Docker Image (push) Successful in 8s
Publish Docker Image / Publish Docker Image (push) Successful in 27s
2025-04-07 17:33:54 -04:00
15bbc27238 Attempt to propegate app version properly
All checks were successful
Publish Development Docker Image / Publish Development Docker Image (push) Successful in 8s
2025-04-07 17:17:05 -04:00
16bd44c599 Fix app version?
All checks were successful
Publish Development Docker Image / Publish Development Docker Image (push) Successful in 9s
2025-04-07 17:06:40 -04:00
d13b16c032 Update TUS backend
All checks were successful
Publish Development Docker Image / Publish Development Docker Image (push) Successful in 1m34s
2025-04-07 16:54:17 -04:00
d144f7385b Quietly embed version to homepage 2025-04-07 15:08:07 -04:00
0283c293ef An attempt to fix the PDF iframe not loading properly when the API domain is third-partyBasic printer CRUD
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 39s
2025-04-06 18:08:05 -04:00
8d75849c55 More reliable docker-compose.yml 2024-11-10 14:48:43 -05:00
58b44dddad Update Dockerfile
All checks were successful
Publish Docker Image / Publish Docker Image (push) Successful in 1m51s
2024-11-08 12:07:04 -05:00
c9de531389 Remove defaults, as they override the optional values
Some checks failed
Publish Docker Image / Publish Docker Image (push) Failing after 12s
2024-10-26 16:37:49 -04:00
172 changed files with 1918 additions and 1872 deletions

0
.dockerignore Normal file → Executable file
View File

0
.editorconfig Normal file → Executable file
View File

View File

@ -1,46 +0,0 @@
# These environment variables will be used by default if you do not create any
# yourself in .env. This file should be safe to check into your version control
# system. Any custom values should go in .env and .env should *not* be checked
# into version control.
# location of the test database for api service scenarios (defaults to ./.redwood/test.db if not set)
# TEST_DATABASE_URL=file:./.redwood/test.db
# disables Prisma CLI update notifier
PRISMA_HIDE_UPDATE_MESSAGE=true
# Option to override the current environment's default api-side log level
# See: https://redwoodjs.com/docs/logger for level options, defaults to "trace" otherwise.
# Most applications want "debug" or "info" during dev, "trace" when you have issues and "warn" in production.
# Ordered by how verbose they are: trace | debug | info | warn | error | silent
# LOG_LEVEL=debug
FIRST_NAME=firstname
LAST_NAME=lastname
DEFAULT_THEME=light
COUNTRY=US
STATE=New York
CITY=Manhattan
SMTP_HOST=smtp.example.com
SMTP_PORT=465
SMTP_SECURE=true
SMTP_USER=noreply@example.com
EMAIL_FROM=noreply@example.com
EMAIL_TO=email@example.com
SMTP_PASSWORD=password
DOMAIN=example.com
API_DOMAIN=api.example.com
# Must not end with "/"
ADDRESS_PROD=https://portfolio.example.com
ADDRESS_DEV=http://localhost:8910
API_ADDRESS_PROD=https://api-portfolio.example.com
API_ADDRESS_DEV=http://localhost:8911
MAX_HTTP_CONNECTIONS_PER_MINUTE=60
DATABASE_URL=postgresql://user:password@localhost:5432/rw_portfolio

0
.env.example Normal file → Executable file
View File

17
.gitea/workflows/ci.yml Normal file → Executable file
View File

@ -10,20 +10,27 @@ jobs:
build:
name: Publish Docker Image
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v3
- name: Login to Registry
run: echo "${{ secrets.ACCESS_TOKEN }}" | docker login git.altaiar.dev -u "${{ secrets.USERNAME }}" --password-stdin
run: echo "${{ secrets.ACCESS_TOKEN }}" \
| docker login git.altaiar.dev -u "${{ secrets.USERNAME }}" --password-stdin
- name: Build & Tag Image
run: |
docker build -t git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} .
docker tag git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} git.altaiar.dev/${{ gitea.repository }}:latest
docker build \
--build-arg APP_VERSION=${{ gitea.ref_name }} \
--label org.opencontainers.image.version=${{ gitea.ref_name }} \
-t git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} .
docker tag \
git.altaiar.dev/${{ gitea.repository }}:${{ gitea.ref_name }} \
git.altaiar.dev/${{ gitea.repository }}:latest
- name: Push Images
run: |

0
.gitignore vendored Normal file → Executable file
View File

0
.redwood/README.md Normal file → Executable file
View File

0
.vscode/extensions.json vendored Normal file → Executable file
View File

0
.vscode/launch.json vendored Normal file → Executable file
View File

0
.vscode/settings.json vendored Normal file → Executable file
View File

0
.vscode/tasks.json vendored Normal file → Executable file
View File

0
.yarnrc.yml Normal file → Executable file
View File

83
Dockerfile Normal file → Executable file
View File

@ -1,15 +1,9 @@
# base
# ----
FROM node:20-bookworm-slim as base
FROM node:lts-alpine AS base
RUN corepack enable
ARG APP_VERSION=dev
ENV APP_VERSION=${APP_VERSION}
# We tried to make the Dockerfile as lean as possible. In some cases, that means we excluded a dependency your project needs.
# By far the most common is Python. If you're running into build errors because `python3` isn't available,
# add `python3 make gcc \` before the `openssl \` line below and in other stages as necessary:
RUN apt-get update && apt-get install -y \
openssl \
&& rm -rf /var/lib/apt/lists/*
RUN apk add --no-cache openssl && corepack enable
USER node
WORKDIR /home/node/app
@ -29,14 +23,8 @@ RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \
COPY --chown=node:node redwood.toml .
COPY --chown=node:node graphql.config.js .
COPY --chown=node:node .env.defaults .env.defaults
# api build
# ---------
FROM base as api_build
# If your api side build relies on build-time environment variables,
# specify them here as ARGs. (But don't put secrets in your Dockerfile!)
FROM base AS api_build
ARG ADDRESS_PROD
ARG ADDRESS_DEV
@ -52,13 +40,12 @@ ARG EMAIL_FROM
ARG EMAIL_TO
ARG FIRST_NAME
ARG LAST_NAME
ARG APP_VERSION
COPY --chown=node:node api api
RUN yarn rw build api
# web prerender build
# -------------------
FROM api_build as web_build_with_prerender
FROM api_build AS web_build_with_prerender
ARG FIRST_NAME
ARG LAST_NAME
@ -68,13 +55,14 @@ ARG CITY
ARG DEFAULT_THEME
ARG API_ADDRESS_PROD
ARG API_ADDRESS_DEV
ARG APP_VERSION
ENV APP_VERSION=${APP_VERSION}
COPY --chown=node:node web web
RUN yarn rw build web
# web build
# ---------
FROM base as web_build
FROM base AS web_build
ARG FIRST_NAME
ARG LAST_NAME
@ -84,19 +72,19 @@ ARG CITY
ARG DEFAULT_THEME
ARG API_ADDRESS_PROD
ARG API_ADDRESS_DEV
ARG APP_VERSION
ENV APP_VERSION=${APP_VERSION}
COPY --chown=node:node web web
RUN yarn rw build web --no-prerender
# api serve
# ---------
FROM node:20-bookworm-slim as api_serve
FROM node:lts-alpine AS api_serve
RUN corepack enable
RUN apk add --no-cache openssl && corepack enable
RUN apt-get update && apt-get install -y \
openssl \
&& rm -rf /var/lib/apt/lists/*
RUN mkdir -p /home/node/app/api/files_prod \
&& chown -R node:node /home/node/app/api/files_prod
USER node
WORKDIR /home/node/app
@ -115,26 +103,19 @@ RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \
COPY --chown=node:node redwood.toml .
COPY --chown=node:node graphql.config.js .
COPY --chown=node:node .env.defaults .env.defaults
COPY --chown=node:node --from=api_build /home/node/app/api/dist /home/node/app/api/dist
COPY --chown=node:node --from=api_build /home/node/app/api/db /home/node/app/api/db
COPY --chown=node:node --from=api_build /home/node/app/node_modules/.prisma /home/node/app/node_modules/.prisma
ENV NODE_ENV=production
ARG APP_VERSION
# default api serve command
# ---------
# If you are using a custom server file, you must use the following
# command to launch your server instead of the default api-server below.
# This is important if you intend to configure GraphQL to use Realtime.
ENV NODE_ENV=production
ENV APP_VERSION=${APP_VERSION}
CMD [ "./api/dist/server.js" ]
# CMD [ "node_modules/.bin/rw-server", "api" ]
# web serve
# ---------
FROM node:20-bookworm-slim as web_serve
FROM node:lts-alpine AS web_serve
RUN corepack enable
@ -155,30 +136,18 @@ RUN --mount=type=cache,target=/home/node/.yarn/berry/cache,uid=1000 \
COPY --chown=node:node redwood.toml .
COPY --chown=node:node graphql.config.js .
COPY --chown=node:node .env.defaults .env.defaults
COPY --chown=node:node --from=web_build /home/node/app/web/dist /home/node/app/web/dist
ARG APP_VERSION
ENV APP_VERSION=${APP_VERSION}
ENV NODE_ENV=production \
API_PROXY_TARGET=http://api:8911
# We use the shell form here for variable expansion.
CMD "node_modules/.bin/rw-web-server" "--api-proxy-target" "$API_PROXY_TARGET"
# console
# -------
FROM base as console
# To add more packages:
#
# ```
# USER root
#
# RUN apt-get update && apt-get install -y \
# curl
#
# USER node
# ```
FROM base AS console
COPY --chown=node:node api api
COPY --chown=node:node web web

18
README.md Normal file → Executable file
View File

@ -16,6 +16,7 @@ services:
portfolio:
container_name: portfolio
image: git.altaiar.dev/ahmed/portfolio:latest
restart: unless-stopped
environment:
- NODE_ENV=production
- API_PROXY_TARGET=http://localhost:8911
@ -44,7 +45,8 @@ services:
- 8910:8910 # Web
- 8911:8911 # API
depends_on:
- db
db:
condition: service_healthy
volumes:
- files:/home/node/app/api/files_prod
command: >
@ -54,14 +56,19 @@ services:
yarn rw prisma db seed &&
yarn rw serve"
db:
container_name: portfolio-db
image: postgres:16-bookworm
environment:
- POSTGRES_USER=redwood
- POSTGRES_PASSWORD=changeme
- POSTGRES_PASSWORD=changeme # Change to a more secure password
- POSTGRES_DB=portfolio
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d DATABASE_URL"] # Replace DATABASE_URL with the database URL from the portfolio container
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgres:/var/lib/postgresql/data
@ -69,11 +76,6 @@ volumes:
postgres:
files: # For persistent file storage across upgrades
```
## Fix Files Ownership
The `files` volume in Docker is owned by `root`, since the portfolio container runs under the `node` user, file uploads will fail. Run this command to give ownership to the `node` user:
```
sudo docker exec -u root portfolio chown -R node:node /home/node/app/api/files_prod
```
## Logging In
- Once the container is up and running, head to `/login` (`https://portfolio.example.com/login`), default credentials are below
- If you would like to change the password, head to `/forgot-password` (`https://portfolio.example.com/forgot-password`), the username is `admin`

0
api/db/migrations/20240810184713_user/migration.sql Normal file → Executable file
View File

0
api/db/migrations/20240819213158_social/migration.sql Normal file → Executable file
View File

View File

0
api/db/migrations/20240824001030_project/migration.sql Normal file → Executable file
View File

View File

View File

0
api/db/migrations/20240927031102_/migration.sql Normal file → Executable file
View File

0
api/db/migrations/20240929164343_/migration.sql Normal file → Executable file
View File

View File

0
api/db/migrations/20241005014130_/migration.sql Normal file → Executable file
View File

0
api/db/migrations/20241015183037_matrix/migration.sql Normal file → Executable file
View File

0
api/db/migrations/migration_lock.toml Normal file → Executable file
View File

0
api/db/schema.prisma Normal file → Executable file
View File

0
api/jest.config.js Normal file → Executable file
View File

12
api/package.json Normal file → Executable file
View File

@ -5,12 +5,12 @@
"dependencies": {
"@fastify/cors": "^9.0.1",
"@fastify/rate-limit": "^9.1.0",
"@redwoodjs/api": "8.4.0",
"@redwoodjs/api-server": "8.4.0",
"@redwoodjs/auth-dbauth-api": "8.4.0",
"@redwoodjs/graphql-server": "8.4.0",
"@tus/file-store": "^1.4.0",
"@tus/server": "^1.7.0",
"@redwoodjs/api": "8.6.1",
"@redwoodjs/api-server": "8.6.1",
"@redwoodjs/auth-dbauth-api": "8.6.1",
"@redwoodjs/graphql-server": "8.6.1",
"@tus/file-store": "1.4.0",
"@tus/server": "1.7.0",
"countries-list": "^3.1.1",
"graphql-scalars": "^1.23.0",
"nodemailer": "^6.9.14"

0
api/quick-lint-js.config Normal file → Executable file
View File

0
api/src/directives/requireAuth/requireAuth.ts Normal file → Executable file
View File

0
api/src/directives/skipAuth/skipAuth.ts Normal file → Executable file
View File

0
api/src/functions/auth.ts Normal file → Executable file
View File

0
api/src/functions/graphql.ts Normal file → Executable file
View File

0
api/src/graphql/.keep Normal file → Executable file
View File

0
api/src/graphql/portrait.sdl.ts Normal file → Executable file
View File

0
api/src/graphql/projects.sdl.ts Normal file → Executable file
View File

0
api/src/graphql/resume.sdl.ts Normal file → Executable file
View File

0
api/src/graphql/scalars.sdl.ts Normal file → Executable file
View File

0
api/src/graphql/socials.sdl.ts Normal file → Executable file
View File

0
api/src/graphql/tags.sdl.ts Normal file → Executable file
View File

0
api/src/graphql/title.sdl.ts Normal file → Executable file
View File

0
api/src/lib/auth.ts Normal file → Executable file
View File

16
api/src/lib/cors.ts Normal file → Executable file
View File

@ -2,10 +2,17 @@ import type { FastifyReply } from 'fastify'
import { isProduction } from '@redwoodjs/api/logger'
export const setCorsHeaders = (res: FastifyReply) => {
export const setCorsHeaders = (
res: FastifyReply,
isPublic: boolean = false
) => {
res.raw.setHeader(
'Access-Control-Allow-Origin',
isProduction ? process.env.ADDRESS_PROD : process.env.ADDRESS_DEV
isPublic
? '*'
: isProduction
? process.env.ADDRESS_PROD
: process.env.ADDRESS_DEV
)
res.raw.setHeader(
'Access-Control-Allow-Methods',
@ -16,4 +23,9 @@ export const setCorsHeaders = (res: FastifyReply) => {
'Origin, X-Requested-With, Content-Type, Accept, Authorization, Tus-Resumable, Upload-Length, Upload-Metadata, Upload-Offset'
)
res.raw.setHeader('Access-Control-Allow-Credentials', 'true')
res.raw.setHeader(
'Access-Control-Expose-Headers',
'Upload-Offset, Upload-Length, Upload-Metadata, Tus-Version,' +
'Tus-Resumable, Tus-Max-Size, Tus-Extension, Tus-Checksum-Algorithm'
)
}

0
api/src/lib/db.ts Normal file → Executable file
View File

0
api/src/lib/email.ts Normal file → Executable file
View File

0
api/src/lib/logger.ts Normal file → Executable file
View File

12
api/src/lib/tus.ts Normal file → Executable file
View File

@ -25,10 +25,16 @@ export const handleTusUpload = (
tusHandler: Server,
isPublicEndpoint: boolean
) => {
res.hijack()
if (req.method === 'GET' && isPublicEndpoint) {
setCorsHeaders(res)
}
if (isProduction) {
if (req.method === 'OPTIONS') handleOptionsRequest(res)
else if (isPublicEndpoint && req.method === 'GET')
tusHandler.handle(req.raw, res.raw)
void tusHandler.handle(req.raw, res.raw)
else if (['GET', 'POST', 'HEAD', 'PATCH'].includes(req.method)) {
if (req.headers.cookie) handleAuthenticatedRequest(req, res, tusHandler)
else {
@ -40,8 +46,8 @@ export const handleTusUpload = (
res.raw.end('Method not allowed')
}
} else {
setCorsHeaders(res)
tusHandler.handle(req.raw, res.raw)
setCorsHeaders(res, isPublicEndpoint)
void tusHandler.handle(req.raw, res.raw)
}
}

17
api/src/server.ts Normal file → Executable file
View File

@ -28,6 +28,8 @@ enum Theme {
'Invalid DEFAULT_THEME environment variable, please select either light or dark'
)
logger.info(`Portfolio ${process.env.APP_VERSION}`)
const server = await createServer({
logger,
configureApiServer: async (server) => {
@ -52,7 +54,10 @@ enum Theme {
datastore: new FileStore({
directory: `./files_${isProduction ? 'prod' : 'dev'}`,
}),
onResponseError: (_req, res, _err) => logger.error(res),
onResponseError(_, err) {
logger.error(err)
return { status_code: 500, body: 'Internal Server Error' }
},
})
server.addContentTypeParser(
@ -60,12 +65,14 @@ enum Theme {
(_request, _payload, done) => done(null)
)
server.all('/files', (req, res) =>
server.all('/files', (req, res) => {
res.hijack()
handleTusUpload(req, res, tusServer, false)
)
server.all('/files/*', (req, res) =>
})
server.all('/files/*', (req, res) => {
res.hijack()
handleTusUpload(req, res, tusServer, true)
)
})
await server.start()
})()

0
api/src/services/.keep Normal file → Executable file
View File

0
api/src/services/portrait/portrait.ts Normal file → Executable file
View File

0
api/src/services/projects/projects.ts Normal file → Executable file
View File

0
api/src/services/resume/resume.ts Normal file → Executable file
View File

0
api/src/services/socials/socials.ts Normal file → Executable file
View File

0
api/src/services/tags/tags.ts Normal file → Executable file
View File

0
api/src/services/title/title.ts Normal file → Executable file
View File

2
api/tsconfig.json Normal file → Executable file
View File

@ -5,7 +5,7 @@
"esModuleInterop": true,
"target": "ES2023",
"module": "Node16",
"moduleResolution": "Node16",
"moduleResolution": "node16",
"skipLibCheck": false,
"rootDirs": [
"./src",

13
docker-compose.yml Normal file → Executable file
View File

@ -4,6 +4,7 @@ services:
portfolio:
container_name: portfolio
image: git.altaiar.dev/ahmed/portfolio:latest
restart: unless-stopped
environment:
- NODE_ENV=production
- API_PROXY_TARGET=http://localhost:8911
@ -32,7 +33,8 @@ services:
- 8910:8910 # Web
- 8911:8911 # API
depends_on:
- db
db:
condition: service_healthy
volumes:
- files:/home/node/app/api/files_prod
command: >
@ -42,14 +44,19 @@ services:
yarn rw prisma db seed &&
yarn rw serve"
db:
container_name: portfolio-db
image: postgres:16-bookworm
environment:
- POSTGRES_USER=redwood
- POSTGRES_PASSWORD=changeme
- POSTGRES_PASSWORD=changeme # Change to a more secure password
- POSTGRES_DB=portfolio
restart: unless-stopped
healthcheck:
test: ["CMD-SHELL", "pg_isready -d DATABASE_URL"] # Replace DATABASE_URL with the database URL from the portfolio container
interval: 10s
timeout: 5s
retries: 5
volumes:
- postgres:/var/lib/postgresql/data

0
graphql.config.js Normal file → Executable file
View File

0
jest.config.js Normal file → Executable file
View File

6
package.json Normal file → Executable file
View File

@ -7,9 +7,9 @@
]
},
"devDependencies": {
"@redwoodjs/auth-dbauth-setup": "8.4.0",
"@redwoodjs/core": "8.4.0",
"@redwoodjs/project-config": "8.4.0",
"@redwoodjs/auth-dbauth-setup": "8.6.1",
"@redwoodjs/core": "8.6.1",
"@redwoodjs/project-config": "8.6.1",
"prettier-plugin-tailwindcss": "0.4.1"
},
"eslintConfig": {

0
prettier.config.mjs Normal file → Executable file
View File

2
redwood.toml Normal file → Executable file
View File

@ -9,7 +9,7 @@
title = "${FIRST_NAME} ${LAST_NAME}"
port = 8910
apiUrl = "/api"
includeEnvironmentVariables = ["FIRST_NAME", "LAST_NAME", "COUNTRY", "STATE", "CITY", "DEFAULT_THEME", "API_ADDRESS_PROD", "API_ADDRESS_DEV"]
includeEnvironmentVariables = ["FIRST_NAME", "LAST_NAME", "COUNTRY", "STATE", "CITY", "DEFAULT_THEME", "API_ADDRESS_PROD", "API_ADDRESS_DEV", "APP_VERSION"]
[generate]
tests = false
stories = false

0
scripts/.keep Normal file → Executable file
View File

0
scripts/seed.ts Normal file → Executable file
View File

0
scripts/tsconfig.json Normal file → Executable file
View File

0
web/config/postcss.config.js Normal file → Executable file
View File

0
web/config/tailwind.config.js Normal file → Executable file
View File

0
web/jest.config.js Normal file → Executable file
View File

15
web/package.json Normal file → Executable file
View File

@ -14,11 +14,11 @@
"@icons-pack/react-simple-icons": "^10.0.0",
"@mdi/js": "^7.4.47",
"@mdi/react": "^1.6.1",
"@redwoodjs/auth-dbauth-web": "8.4.0",
"@redwoodjs/forms": "8.4.0",
"@redwoodjs/router": "8.4.0",
"@redwoodjs/web": "8.4.0",
"@redwoodjs/web-server": "8.4.0",
"@redwoodjs/auth-dbauth-web": "8.6.1",
"@redwoodjs/forms": "8.6.1",
"@redwoodjs/router": "8.6.1",
"@redwoodjs/web": "8.6.1",
"@redwoodjs/web-server": "8.6.1",
"@tailwindcss/typography": "^0.5.15",
"@tiptap/extension-link": "^2.8.0",
"@tiptap/extension-text-style": "^2.8.0",
@ -41,10 +41,11 @@
"react": "18.3.1",
"react-colorful": "^5.6.1",
"react-dom": "18.3.1",
"react-html-parser": "^2.0.2"
"react-html-parser": "^2.0.2",
"react-pdf": "^9.2.1"
},
"devDependencies": {
"@redwoodjs/vite": "8.4.0",
"@redwoodjs/vite": "8.6.1",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
"@types/react-html-parser": "^2",

0
web/public/README.md Normal file → Executable file
View File

0
web/public/favicon.png Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 757 B

After

Width:  |  Height:  |  Size: 757 B

0
web/public/no_portrait.webp Normal file → Executable file
View File

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.8 KiB

0
web/public/no_resume.pdf Normal file → Executable file
View File

0
web/public/robots.txt Normal file → Executable file
View File

0
web/quick-lint-js.config Normal file → Executable file
View File

0
web/src/App.tsx Normal file → Executable file
View File

0
web/src/Routes.tsx Normal file → Executable file
View File

0
web/src/auth.ts Normal file → Executable file
View File

0
web/src/components/.keep Normal file → Executable file
View File

0
web/src/components/Cell/CellEmpty/CellEmpty.tsx Normal file → Executable file
View File

0
web/src/components/Cell/CellFailure/CellFailure.tsx Normal file → Executable file
View File

0
web/src/components/Cell/CellLoading/CellLoading.tsx Normal file → Executable file
View File

0
web/src/components/ColorPicker/ColorPicker.tsx Normal file → Executable file
View File

View File

View File

0
web/src/components/DatePicker/DatePicker.tsx Normal file → Executable file
View File

0
web/src/components/FormTextList/FormTextList.tsx Normal file → Executable file
View File

71
web/src/components/PDF/PDF.tsx Normal file → Executable file
View File

@ -1,7 +1,16 @@
import { useState } from 'react'
import { useState, useRef, useEffect } from 'react'
import { mdiAlertOutline } from '@mdi/js'
import { mdiOpenInNew } from '@mdi/js'
import Icon from '@mdi/react'
import { Document, Page as PdfPage, pdfjs } from 'react-pdf'
import 'react-pdf/dist/Page/AnnotationLayer.css'
import 'react-pdf/dist/Page/TextLayer.css'
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
'pdfjs-dist/build/pdf.worker.min.mjs',
import.meta.url
).toString()
interface PDFProps {
url: string
@ -9,28 +18,52 @@ interface PDFProps {
}
const PDF = ({ url, form = false }: PDFProps) => {
const [error, setError] = useState<boolean>(false)
const [numPages, setNumPages] = useState<number>(0)
function onLoadSuccess({ numPages }: { numPages: number }) {
setNumPages(numPages)
}
return error ? (
<div role="alert" className="alert alert-warning">
<Icon path={mdiAlertOutline} className="size-7" />
<span>
Could not load PDF, this is common in in-app browsers, try opening this
page in a regular browser
</span>
</div>
) : (
<iframe
src={url}
title="PDF"
const containerRef = useRef<HTMLDivElement>(null)
const [containerWidth, setContainerWidth] = useState<number>(0)
useEffect(() => {
function updateWidth() {
if (containerRef.current) {
setContainerWidth(containerRef.current.clientWidth)
}
}
updateWidth()
window.addEventListener('resize', updateWidth)
return () => window.removeEventListener('resize', updateWidth)
}, [])
return (
<div
ref={containerRef}
className="overflow-auto flex justify-center"
style={{
width: 'calc(100vw - 1rem)',
height: `calc(100vh - ${form ? '8.5rem' : '6rem'})`,
}}
className="rounded-xl"
onError={() => setError(true)}
onLoad={() => setError(false)}
/>
>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
className="fixed top-20 left-0 z-10 m-2 p-2 rounded-xl btn btn-square btn-ghost shadow-lg"
>
<Icon path={mdiOpenInNew} size={1} className="text-gray-600" />
</a>
<Document file={url} onLoadSuccess={onLoadSuccess}>
{Array.from({ length: numPages }, (_, i) => (
<PdfPage
key={i}
pageNumber={i + 1}
width={Math.min(containerWidth, 800)}
/>
))}
</Document>
</div>
)
}

View File

View File

View File

View File

View File

0
web/src/components/Project/NewProject/NewProject.tsx Normal file → Executable file
View File

0
web/src/components/Project/Project/Project.tsx Normal file → Executable file
View File

0
web/src/components/Project/ProjectCell/ProjectCell.tsx Normal file → Executable file
View File

0
web/src/components/Project/ProjectForm/ProjectForm.tsx Normal file → Executable file
View File

0
web/src/components/Project/Projects/Projects.tsx Normal file → Executable file
View File

Some files were not shown because too many files have changed in this diff Show More