- adds pages for a variety of guides
- add images to the many of the guides and docs pages
This commit is contained in:
Amruth Pillai
2026-01-23 14:18:48 +01:00
parent 895af548a0
commit ed74fb67f2
59 changed files with 325 additions and 139 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ name: ✨ Feature Request
description: Suggest an feature or idea that you would like to see in Reactive Resume
title: "[Feature] <title>"
labels: [feature, v5, needs triage]
labels: [enhancement, v5, needs triage]
assignees: "AmruthPillai"
body:
+15
View File
@@ -1,6 +1,21 @@
name: reactive_resume
services:
adminer:
image: adminer:latest
restart: unless-stopped
ports:
- "8080:8080"
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080"]
start_period: 10s
interval: 30s
timeout: 10s
retries: 3
postgres:
image: postgres:latest
restart: unless-stopped
+3 -1
View File
@@ -7,7 +7,8 @@
"seo": {
"indexing": "all",
"metatags": {
"canonical": "https://docs.rxresu.me"
"canonical": "https://docs.rxresu.me",
"og:image": "https://rxresu.me/opengraph/banner.jpg"
}
},
"fonts": {
@@ -51,6 +52,7 @@
"pages": [
"guides/creating-your-first-resume",
"guides/choosing-a-template",
"guides/moving-items-between-sections",
"guides/exporting-your-resume",
"guides/sharing-your-resume-publicly",
"guides/using-private-notes",
+21 -3
View File
@@ -4,7 +4,25 @@ description: "List of all notable changes and updates to Reactive Resume"
rss: true
---
<Update label="v5.0.0" description="23rd January 2026">
## New Features
- Brand new UI/UX and refreshed design.
<Update label="v5.0.0" description="26th January 2026">
This has been a major overhaul from the previous version of Reactive Resume. The app has been completely redesigned and rebuilt from scratch, to be more intuitive and user-friendly.
**Here are some of the key changes from the previous version:**
- 2 new templates: _Ditgar_ and _Lapras_
- Authentication via Passkeys
- New user interface and refreshed design
- API Access and Reference Documentation
- AI Integration with OpenAI, Google, Anthropic, and Ollama
- A better font selector, with real-time preview of the font
- More comprehensive documentation, with guides for specific features
- An improved templates gallery, with clear overview of each template
- Ability to import resumes from PDF or DOCX (requires AI Integration)
- Reliable server infrastructure, with improved performance and scalability
- Ability to move items between sections and pages, for better organization
- A more powerful CSS Editor, with better autocomplete and syntax highlighting
- Extended Custom Sections to include a type, for better customization and organization
- Ability to choose the kind of icons to display for level indicators (stars, circles, custom icons, etc.)
- Ability to resize how wide/narrow the sidebar should be on the resume, or convert a page to be full width
There's still a lot more that I'm forgetting, but I'm sure you'll find out soon enough as you explore the new version. I hope you enjoy building your resume now, better than ever. If you have any feedback, please feel free to [contact me](https://amruthpillai.com/#contact) or [open an issue](https://github.com/amruthpillai/reactive-resume/issues) on GitHub.
</Update>
+5 -1
View File
@@ -4,7 +4,7 @@ description: "Welcome to the documentation for Reactive Resume, a free and open-
---
<Frame>
<img src="/images/banner.jpg" alt="Reactive Resume Banner" style={{ borderRadius: '0.75rem' }} />
<img src="/images/getting-started/banner.webp" alt="Reactive Resume Banner" />
</Frame>
## What is Reactive Resume?
@@ -28,6 +28,10 @@ description: "Welcome to the documentation for Reactive Resume, a free and open-
## Key Features
<Frame>
<img src="/images/getting-started/infographic.webp" alt="An infographic of the major features of Reactive Resume" />
</Frame>
<AccordionGroup>
<Accordion title="Completely Free & Open Source" icon="code-branch">
Reactive Resume is licensed under MIT. You can use it for free, modify it, and even host your own instance. The entire codebase is available on [GitHub](https://github.com/amruthpillai/reactive-resume).
+28 -28
View File
@@ -13,42 +13,38 @@ Changing your resume template is simple and can be done at any time without losi
<Step title="Open your resume in the builder">
Navigate to your Dashboard and click on the resume you want to edit.
{/* TODO: Add screenshot of dashboard with resume cards */}
<Frame caption="Your resumes dashboard showing all your resume cards">
<img src="/images/choosing-template/dashboard-placeholder.jpg" alt="Dashboard showing resume cards" style={{ aspectRatio: "210/297" }} />
<Frame caption="Screenshot of your resumes dashboard showing all your resume">
<img src="/images/guides/choosing-a-template/screenshot-1.webp" alt="Screenshot of your resumes dashboard showing all your resume" />
</Frame>
</Step>
<Step title="Open the right sidebar">
In the resume builder, look for the right sidebar. This is where you'll find all the design and layout options.
{/* TODO: Add screenshot of builder with right sidebar visible */}
<Frame caption="The resume builder with the right sidebar open">
<img src="/images/choosing-template/builder-sidebar-placeholder.jpg" alt="Resume builder showing the right sidebar" style={{ aspectRatio: "210/297" }} />
</Frame>
</Step>
<Step title="Navigate to the Template section">
In the right sidebar, find and click on the **Template** section to expand it.
{/* TODO: Add screenshot of template section in sidebar */}
<Frame caption="The Template section in the right sidebar">
<img src="/images/choosing-template/template-section-placeholder.jpg" alt="Template section expanded in the sidebar" style={{ aspectRatio: "210/297" }} />
<Frame caption="Screenshot of the template section in the right sidebar">
<img src="/images/guides/choosing-a-template/screenshot-2.webp" alt="Screenshot of the template section in the right sidebar" />
</Frame>
</Step>
<Step title="Select your new template">
Browse through the available templates and click on the one you want to use. Your resume will instantly update to reflect the new design.
{/* TODO: Add screenshot showing template selection */}
<Frame caption="Selecting a new template from the available options">
<img src="/images/choosing-template/template-selection-placeholder.jpg" alt="Template selection dropdown or grid" style={{ aspectRatio: "210/297" }} />
<Frame caption="Screenshot of selecting a new template from the gallery">
<img src="/images/guides/choosing-a-template/screenshot-3.webp" alt="Screenshot of selecting a new template from the gallery" />
</Frame>
</Step>
<Step title="Review and adjust">
After changing the template, review your resume in the live preview. You may want to adjust spacing, colors, or layout to optimize for the new design.
<Frame caption="Screenshot of the resume with a new template applied">
<img src="/images/guides/choosing-a-template/screenshot-4.webp" alt="Screenshot of the resume with a new template applied" />
</Frame>
<Tip>
Different templates may display your content differently. Some templates work better with shorter content, while others are designed to handle more detailed information.
</Tip>
@@ -88,55 +84,59 @@ Reactive Resume includes a variety of professionally designed templates. Each te
All templates support the same features and sections. The difference is purely in how they present your information visually.
</Info>
<Columns cols={2}>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
<Frame caption="Azurill">
<img src="/images/templates/azurill.jpg" alt="Azurill template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/azurill.webp" alt="Azurill template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Bronzor">
<img src="/images/templates/bronzor.jpg" alt="Bronzor template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/bronzor.webp" alt="Bronzor template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Chikorita">
<img src="/images/templates/chikorita.jpg" alt="Chikorita template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/chikorita.webp" alt="Chikorita template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Ditto">
<img src="/images/templates/ditto.jpg" alt="Ditto template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/ditto.webp" alt="Ditto template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Ditgar">
<img src="/images/templates/ditgar.webp" alt="Ditgar template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Gengar">
<img src="/images/templates/gengar.jpg" alt="Gengar template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/gengar.webp" alt="Gengar template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Glalie">
<img src="/images/templates/glalie.jpg" alt="Glalie template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/glalie.webp" alt="Glalie template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Kakuna">
<img src="/images/templates/kakuna.jpg" alt="Kakuna template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/kakuna.webp" alt="Kakuna template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Lapras">
<img src="/images/templates/lapras.jpg" alt="Lapras template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/lapras.webp" alt="Lapras template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Leafish">
<img src="/images/templates/leafish.jpg" alt="Leafish template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/leafish.webp" alt="Leafish template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Onyx">
<img src="/images/templates/onyx.jpg" alt="Onyx template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/onyx.webp" alt="Onyx template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Pikachu">
<img src="/images/templates/pikachu.jpg" alt="Pikachu template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/pikachu.webp" alt="Pikachu template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
<Frame caption="Rhyhorn">
<img src="/images/templates/rhyhorn.jpg" alt="Rhyhorn template preview" style={{ aspectRatio: "210/297" }} />
<img src="/images/templates/rhyhorn.webp" alt="Rhyhorn template preview" style={{ aspectRatio: "210/297" }} />
</Frame>
</Columns>
</div>
---
+2 -18
View File
@@ -6,18 +6,10 @@ description: "Learn how to create an account on Reactive Resume and get started
<Steps>
<Step title="Visit the homepage">
Head over to [https://rxresu.me](https://rxresu.me) and click on the <Badge>Get Started</Badge> button.
<Frame>
<img src="/images/creating-an-account/get-started-button.png" alt="Get Started button on homepage" />
</Frame>
</Step>
<Step title="Navigate to the sign up page">
You should see a link that says <Badge>Don't have an account? Create one now →</Badge>. Click on that link to go to the sign up page and you should see a form like this:
<Frame>
<img src="/images/creating-an-account/create-account-link.png" alt="Create account link on login page" />
</Frame>
You should see a link that says <Badge>Don't have an account? Create one now →</Badge>. Click on that link to go to the sign up page and you should see a form.
</Step>
<Step title="Fill in your details">
@@ -27,11 +19,7 @@ description: "Learn how to create an account on Reactive Resume and get started
- **Email Address**: A valid email address you have access to.
- **Username**: Choose a unique username (this will be used in your public resume URLs)
- **Password**: Create a strong password
<Frame>
<img src="/images/creating-an-account/sign-up-form.png" alt="Sign up form" />
</Frame>
<Warning>
Make sure to choose a username you're happy with, as it will be part of your public resume URL (e.g., `rxresu.me/your-username/resume-slug`).
</Warning>
@@ -62,10 +50,6 @@ description: "Learn how to create an account on Reactive Resume and get started
- Create your first resume
- Import an existing resume
- Manage your account settings
<Frame>
<img src="/images/creating-an-account/dashboard.png" alt="Reactive Resume dashboard" />
</Frame>
</Step>
</Steps>
@@ -0,0 +1,133 @@
---
title: "Moving items between sections"
description: "Learn how to move resume items from one section to another, or to a different page, to better organize your content and split lengthy sections across multiple pages."
---
If you have a long work history or extensive project list, you may want to split items across multiple pages or reorganize them into different sections. The **Move to** feature lets you do exactly that—quickly relocate any item to another section or page with just a few clicks.
## Why move items?
- **Split lengthy sections**: If your Experience section spans more than one page, you can move older roles to a custom section on page 2.
- **Reorganize content**: Move a project from "Projects" to a custom "Open Source" section, or relocate a skill to a different grouping.
- **Fine-tune page layout**: Control exactly which items appear on which page for a polished, balanced resume.
## How to move an item
<Steps>
<Step title="Open the resume builder">
Navigate to your resume and open it in the builder.
<Info>
Make sure you have at least one item in a section (e.g., an experience entry, project, or skill) before proceeding.
</Info>
</Step>
<Step title="Locate the item you want to move">
In the left sidebar, find the section containing the item you want to relocate. Click on the section to expand it and view all items.
<Frame caption="Screenshot of the left sidebar with an expanded section containing multiple items">
<img src="/images/guides/moving-items-between-sections/screenshot-1.webp" alt="Screenshot of the left sidebar with an expanded section containing multiple items" />
</Frame>
</Step>
<Step title="Open the item dropdown menu">
Each item has a **dropdown menu** (three-dot icon or chevron) on the right side. Click this icon to reveal the available actions.
<Frame caption="Screenshot of the dropdown menu icon on a section item">
<img src="/images/guides/moving-items-between-sections/screenshot-2.webp" alt="Screenshot of the dropdown menu icon on a section item" />
</Frame>
</Step>
<Step title="Hover over 'Move to'">
In the dropdown menu, hover over or click the <Badge>Move to</Badge> option. This will open a submenu showing all available destinations.
</Step>
<Step title="Select a destination">
The submenu displays available destinations organized by:
- **Existing sections** of the same type (e.g., other Experience sections)
- **Pages** where you can place the item
- **Custom sections** if any exist
Click on your desired destination to move the item there.
<Frame caption="Screenshot of the 'Move to' submenu with destination options">
<img src="/images/guides/moving-items-between-sections/screenshot-3.webp" alt="Screenshot of the 'Move to' submenu with destination options" />
</Frame>
<Tip>
If a custom section of the same type doesn't exist on your target page, Reactive Resume will **automatically create one** for you. This makes it easy to split sections across pages without manual setup.
</Tip>
</Step>
<Step title="Verify the move">
After selecting a destination, the item will be moved immediately. You can verify by:
- Checking the destination section in the left sidebar
- Looking at the resume preview to see where the item now appears
<Frame caption="Screenshot of the item in its new location">
<img src="/images/guides/moving-items-between-sections/screenshot-4.webp" alt="Screenshot of the item in its new location" />
</Frame>
</Step>
</Steps>
## Example: Splitting Work Experience across pages
A common use case is when your work history is too long to fit on a single page. Here's how to handle it:
<Steps>
<Step title="Identify items to move">
Review your Experience section and decide which roles should appear on page 1 (typically your most recent and relevant positions) and which can go on page 2.
</Step>
<Step title="Move older positions">
For each older position you want to relocate:
1. Open the item's dropdown menu
2. Click <Badge>Move to</Badge>
3. Select **Page 2** (or the appropriate page)
<Info>
If no Experience section exists on page 2, a new custom section will be created automatically with the same type, so your formatting stays consistent.
</Info>
</Step>
<Step title="Review the result">
Check the resume preview to ensure:
- Page 1 contains your most important, recent roles
- Page 2 continues with your earlier experience
- The section headings and styling remain consistent
{/* TODO: Add screenshot showing a resume with Experience split across page 1 and page 2 */}
</Step>
</Steps>
## Tips for organizing multi-page resumes
<Tip>
**Keep it logical**: Maintain chronological order within each page. Move complete job entries rather than splitting a single role across pages.
</Tip>
<Tip>
**Use custom section names**: After moving items to a new custom section, you can rename it (e.g., "Earlier Experience" or "Additional Projects") to provide context for recruiters.
</Tip>
<Warning>
Moving items reorganizes your content but doesn't affect the data itself. You can always move items back or to different sections as needed.
</Warning>
## Troubleshooting
### I don't see the "Move to" option
Make sure you're clicking the dropdown menu on a **section item** (like an individual job or project), not the section header itself. The Move to feature is only available for items within sections.
### The destination I want isn't listed
The Move to submenu shows destinations compatible with the item type. For example, an Experience item can only be moved to other Experience-type sections. If you need to change an item's type entirely, you may need to recreate it in the desired section.
### My custom section wasn't created
If you're moving to a page that already has a section of the same type, the item will be added to that existing section rather than creating a new one. This is by design to avoid duplicate sections.
+17 -28
View File
@@ -11,6 +11,10 @@ The **Custom CSS** panel lets you write your own CSS rules to change how your re
- **Auto-save**: there is no “Save” button—your CSS is saved automatically when it changes.
- **Scoped styling (mostly)**: to avoid affecting the rest of the app, your CSS is *usually* scoped to the resume preview.
<Frame caption="Screenshot of the Custom CSS section in the right sidebar">
<img src="/images/guides/using-custom-css/screenshot-1.webp" alt="Screenshot of the Custom CSS section in the right sidebar" />
</Frame>
## Where to find it
In the resume builder, open the **right sidebar** and select **Custom CSS** (the `CSS` section).
@@ -54,19 +58,19 @@ Example:
### Formatting tip (to avoid scoping edge-cases)
Keep each rules selector on **one line**, for example:
Keep each rule's selector on **one line**, for example:
```css
.page-section-experience .section-item-title { font-weight: 700; }
```
Avoid splitting a selector across multiple lines unless youve confirmed it behaves as expected.
Avoid splitting a selector across multiple lines unless you've confirmed it behaves as expected.
## Finding the right selectors
### Use autocomplete in the editor
In the Custom CSS editor, type a `.` (dot). Youll get suggestions for commonly-used class selectors, including:
In the Custom CSS editor, type a `.` (dot). You'll get suggestions for commonly-used class selectors, including:
- **Page-level**: `.page`, `.page-content`, `.page-header`, `.page-basics`, `.page-main`, `.page-sidebar`, `.page-picture`
- **Section-level**: `.page-section`, `.section-content`
@@ -77,7 +81,7 @@ In the Custom CSS editor, type a `.` (dot). Youll get suggestions for commonl
### Stable “starter selectors”
If youre not sure where to start, these are usually safe:
If you're not sure where to start, these are usually safe:
- `.page` (the resume page container)
- `.page-section` (each resume section)
@@ -196,9 +200,9 @@ Example (tighten spacing without changing your layout settings):
```css
.section-item-link a {
display: inline-block;
padding: 2pt 6pt;
padding: 3pt 9pt;
border-radius: 999pt;
border: 1pt solid color-mix(in srgb, var(--page-primary-color) 45%, transparent);
border: 2pt solid color-mix(in srgb, var(--page-primary-color) 45%, transparent);
text-decoration: none;
}
```
@@ -241,40 +245,25 @@ Example (tighten spacing without changing your layout settings):
}
```
### 9) Highlight a specific section (example: Experience)
### 9) Highlight a specific section (example: Profiles)
```css
.page-section-experience {
background: color-mix(in srgb, var(--page-primary-color) 6%, transparent);
.page-section-profiles {
background: color-mix(in srgb, var(--page-primary-color) 10%, transparent);
border-radius: 8pt;
padding: 8pt;
}
```
### 10) Print-only adjustments (manually scoped)
```css
@media print {
.resume-preview-container .page {
-webkit-print-color-adjust: exact;
print-color-adjust: exact;
}
.resume-preview-container a {
text-decoration: none;
}
padding: 8pt 12pt;
}
```
## Troubleshooting
### My CSS doesnt do anything
### My CSS doesn't do anything
- Make sure **Enable** is turned on.
- Prefer targeting `.page`, `.page-section`, and `.section-item` instead of `body`.
- Use your browser devtools to **inspect** the preview and confirm the element/class names.
### My `@media` rules affect the whole app / dont apply
### My `@media` rules affect the whole app / don't apply
At-rules arent auto-scoped. Prefix selectors inside them with `.resume-preview-container` as shown above.
At-rules aren't auto-scoped. Prefix selectors inside them with `.resume-preview-container` as shown above.
+4
View File
@@ -17,6 +17,10 @@ Think of it as a built-in notebook attached to each resume where you can jot dow
In the resume builder, open the **right sidebar** and select **Notes** from the available sections.
<Frame caption="Screenshot of the Notes section in the right sidebar">
<img src="/images/guides/using-private-notes/screenshot-1.webp" alt="Screenshot of the Notes section in the right sidebar" />
</Frame>
## Use cases
Private Notes are designed to help you stay organized during your job search. Here are some practical ways to use them:
Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 244 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 243 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 261 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 256 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 274 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 249 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 310 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 239 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 252 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 301 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 277 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 319 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 KiB

+3 -5
View File
@@ -172,8 +172,6 @@ services:
restart: unless-stopped
ports:
- "4000:3000"
extra_hosts:
- "host.docker.internal:host-gateway"
environment:
- HEALTH=true
- CONCURRENT=20
@@ -181,7 +179,7 @@ services:
# Optional: Set a token for authentication
# - TOKEN=your-secret-token
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure?token=your-secret-token"]
interval: 10s
timeout: 5s
retries: 10
@@ -203,7 +201,7 @@ services:
printer:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"]
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
@@ -413,7 +411,7 @@ The Docker Compose configuration includes a health check that periodically calls
```yaml
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"]
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
+8 -10
View File
@@ -82,12 +82,11 @@ services:
- TIMEOUT=120000
- CONCURRENT=10
- HEALTH=true
- TOKEN=your-secret-token
networks:
- reactive_resume_network
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure?token=your-secret-token"]
interval: 30s
timeout: 10s
retries: 3
@@ -118,7 +117,7 @@ services:
- "traefik.http.routers.reactive-resume.tls.certresolver=letsencrypt"
- "traefik.http.services.reactive-resume.loadbalancer.server.port=3000"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"]
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
@@ -190,12 +189,11 @@ services:
- TIMEOUT=120000
- CONCURRENT=10
- HEALTH=true
- TOKEN=your-secret-token
networks:
- reactive_resume_network
extra_hosts:
- "host.docker.internal:host-gateway"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure?token=your-secret-token"]
interval: 30s
timeout: 10s
retries: 3
@@ -220,7 +218,7 @@ services:
printer:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"]
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
@@ -358,7 +356,7 @@ services:
# Optional: Set a token for authentication
# - TOKEN=your-secret-token
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/"]
test: ["CMD", "curl", "-f", "http://localhost:3000/pressure?token=your-secret-token"]
interval: 30s
timeout: 10s
retries: 3
@@ -458,7 +456,7 @@ services:
- S3_ENDPOINT=$S3_ENDPOINT
- S3_BUCKET=$S3_BUCKET
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health", "||", "exit", "1"]
test: ["CMD", "curl", "-f", "http://localhost:3000/api/health"]
interval: 30s
timeout: 10s
retries: 3
+3 -3
View File
@@ -56,10 +56,10 @@
"@sindresorhus/slugify": "^3.0.0",
"@t3-oss/env-core": "^0.13.10",
"@tanstack/react-query": "5.90.19",
"@tanstack/react-router": "^1.154.12",
"@tanstack/react-router-ssr-query": "^1.154.12",
"@tanstack/react-router": "^1.154.13",
"@tanstack/react-router-ssr-query": "^1.154.13",
"@tanstack/react-start": "^1.154.12",
"@tanstack/zod-adapter": "^1.154.12",
"@tanstack/zod-adapter": "^1.154.13",
"@tiptap/extension-highlight": "^3.16.0",
"@tiptap/extension-table": "^3.16.0",
"@tiptap/extension-text-align": "^3.16.0",
+51 -19
View File
@@ -87,17 +87,17 @@ importers:
specifier: 5.90.19
version: 5.90.19(react@19.2.3)
'@tanstack/react-router':
specifier: ^1.154.12
version: 1.154.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
specifier: ^1.154.13
version: 1.154.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@tanstack/react-router-ssr-query':
specifier: ^1.154.12
version: 1.154.12(@tanstack/query-core@5.90.19)(@tanstack/react-query@5.90.19(react@19.2.3))(@tanstack/react-router@1.154.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.154.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
specifier: ^1.154.13
version: 1.154.13(@tanstack/query-core@5.90.19)(@tanstack/react-query@5.90.19(react@19.2.3))(@tanstack/react-router@1.154.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.154.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@tanstack/react-start':
specifier: ^1.154.12
version: 1.154.12(crossws@0.4.3(srvx@0.10.1))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(rolldown-vite@7.3.1(@types/node@25.0.10)(esbuild@0.27.2)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0))
'@tanstack/zod-adapter':
specifier: ^1.154.12
version: 1.154.12(@tanstack/react-router@1.154.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.6)
specifier: ^1.154.13
version: 1.154.13(@tanstack/react-router@1.154.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.6)
'@tiptap/extension-highlight':
specifier: ^3.16.0
version: 3.16.0(@tiptap/core@3.16.0(@tiptap/pm@3.16.0))
@@ -3897,8 +3897,8 @@ packages:
peerDependencies:
react: ^18 || ^19
'@tanstack/react-router-ssr-query@1.154.12':
resolution: {integrity: sha512-STzIAfnNcSf1SNi5r27AwynhIIvVvsvzdOOomP4tFn2QLkEwXGZkmbppS98OB3rXKsyVnOMlX37rhA+a1jQaQA==}
'@tanstack/react-router-ssr-query@1.154.13':
resolution: {integrity: sha512-Sp5uxYEhhdvQZpcf+NV30/ByL2zLypizefzNeXxmPsvvV8NE8NT/tiOkk+kS+cwsmEoiKf0SqwvG9H+NcwQFfw==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/query-core': '>=5.90.0'
@@ -3914,6 +3914,13 @@ packages:
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
'@tanstack/react-router@1.154.13':
resolution: {integrity: sha512-/Vdqt7w4dmW2ARf85Qav6TBytn8iJiqle4BGJKDYsVuPtMuQKJnxNpKPaCN+H0DiV4KHoFoQrcPVyr9hqy/ltg==}
engines: {node: '>=12'}
peerDependencies:
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
'@tanstack/react-start-client@1.154.12':
resolution: {integrity: sha512-BQuWw8B0kwsrZCKTyKCtWAnnoHvFMXMQvl/y8ZT0tue2ArxsHwxm0tUe794P/EdIO4x1iHjl1R/OW9YgQChrsw==}
engines: {node: '>=22.12.0'}
@@ -3946,6 +3953,10 @@ packages:
resolution: {integrity: sha512-p+TKxkXcLGtCwwW237D8pV4f6ea2K1pzc/e65ljugoTawsA/YR2/gmTSBDTUsSYy6Tmu4mMJmZ0Q4zNkcfCS3g==}
engines: {node: '>=12'}
'@tanstack/router-core@1.154.13':
resolution: {integrity: sha512-ZtSM/qcytCDI6Pt5eZLwYhDbVheXI54HN29J+PpC6Lp6xUq/Q2ZOMwb/wJF942EDeqTjxRtl7W9Bpvz7DBwq8g==}
engines: {node: '>=12'}
'@tanstack/router-generator@1.154.12':
resolution: {integrity: sha512-cjr3KS3Esnyh05CWl78KgK2Z9kTjeFasZXcSUrh//TzzU72eXQ+dzKppD3kMsjuyRfUxAfdufsR9GDNMMuLk9w==}
engines: {node: '>=12'}
@@ -3971,8 +3982,8 @@ packages:
webpack:
optional: true
'@tanstack/router-ssr-query-core@1.154.12':
resolution: {integrity: sha512-p3mUZRGlXKRaHxglDCGvuJd55jIGr7qdrHvwiKQkPP1fksfafMsuS5vhjzu6anV2TtmfUEKRJbbQs/s8PJfuxw==}
'@tanstack/router-ssr-query-core@1.154.13':
resolution: {integrity: sha512-QfyrUJA7klsM+erqpJTtFAMI0k6UrvEzh4wNPq8dIVM8rP43wbWBZhX3oC9O13lY9KC+wm9k4R98VwIv2TBClQ==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/query-core': '>=5.90.0'
@@ -4011,8 +4022,8 @@ packages:
resolution: {integrity: sha512-cHHDnewHozgjpI+MIVp9tcib6lYEQK5MyUr0ChHpHFGBl8Xei55rohFK0I0ve/GKoHeioaK42Smd8OixPp6CTg==}
engines: {node: '>=12'}
'@tanstack/zod-adapter@1.154.12':
resolution: {integrity: sha512-QdlI1wJQTNsy1mKdhHkO+Z9J3OfoJSJN4NGeuFsNC2M129gX5bmMxfEGaK475GN7/9OgeF5cizJ4IZTc1ckZsQ==}
'@tanstack/zod-adapter@1.154.13':
resolution: {integrity: sha512-FlDY8+qEf4Cuyq7YuSi3uVYDYFmf3ZNsublRaNQq73S2ZIn4Lg/rxtDZWIEopHPTBA0x2/lIMn1TfVDrK3LZMA==}
engines: {node: '>=12'}
peerDependencies:
'@tanstack/react-router': '>=1.43.2'
@@ -12560,12 +12571,12 @@ snapshots:
'@tanstack/query-core': 5.90.19
react: 19.2.3
'@tanstack/react-router-ssr-query@1.154.12(@tanstack/query-core@5.90.19)(@tanstack/react-query@5.90.19(react@19.2.3))(@tanstack/react-router@1.154.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.154.12)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
'@tanstack/react-router-ssr-query@1.154.13(@tanstack/query-core@5.90.19)(@tanstack/react-query@5.90.19(react@19.2.3))(@tanstack/react-router@1.154.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(@tanstack/router-core@1.154.13)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@tanstack/query-core': 5.90.19
'@tanstack/react-query': 5.90.19(react@19.2.3)
'@tanstack/react-router': 1.154.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@tanstack/router-ssr-query-core': 1.154.12(@tanstack/query-core@5.90.19)(@tanstack/router-core@1.154.12)
'@tanstack/react-router': 1.154.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@tanstack/router-ssr-query-core': 1.154.13(@tanstack/query-core@5.90.19)(@tanstack/router-core@1.154.13)
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
transitivePeerDependencies:
@@ -12582,6 +12593,17 @@ snapshots:
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
'@tanstack/react-router@1.154.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@tanstack/history': 1.154.7
'@tanstack/react-store': 0.8.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@tanstack/router-core': 1.154.13
isbot: 5.1.33
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
'@tanstack/react-start-client@1.154.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3)':
dependencies:
'@tanstack/react-router': 1.154.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
@@ -12641,6 +12663,16 @@ snapshots:
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
'@tanstack/router-core@1.154.13':
dependencies:
'@tanstack/history': 1.154.7
'@tanstack/store': 0.8.0
cookie-es: 2.0.0
seroval: 1.4.2
seroval-plugins: 1.4.2(seroval@1.4.2)
tiny-invariant: 1.3.3
tiny-warning: 1.0.3
'@tanstack/router-generator@1.154.12':
dependencies:
'@tanstack/router-core': 1.154.12
@@ -12676,10 +12708,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@tanstack/router-ssr-query-core@1.154.12(@tanstack/query-core@5.90.19)(@tanstack/router-core@1.154.12)':
'@tanstack/router-ssr-query-core@1.154.13(@tanstack/query-core@5.90.19)(@tanstack/router-core@1.154.13)':
dependencies:
'@tanstack/query-core': 5.90.19
'@tanstack/router-core': 1.154.12
'@tanstack/router-core': 1.154.13
'@tanstack/router-utils@1.154.7':
dependencies:
@@ -12755,9 +12787,9 @@ snapshots:
'@tanstack/virtual-file-routes@1.154.7': {}
'@tanstack/zod-adapter@1.154.12(@tanstack/react-router@1.154.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.6)':
'@tanstack/zod-adapter@1.154.13(@tanstack/react-router@1.154.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(zod@4.3.6)':
dependencies:
'@tanstack/react-router': 1.154.12(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
'@tanstack/react-router': 1.154.13(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
zod: 4.3.6
'@tediousjs/connection-string@0.5.0': {}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

+2 -14
View File
@@ -9,27 +9,15 @@ export async function resetDatabase() {
const pool = new Pool({ connectionString: env.DATABASE_URL });
const db = drizzle({ client: pool });
// Extract the username from a PostgreSQL connection string like: postgresql://postgres:password@localhost:5432/db
const username = (() => {
try {
const match = env.DATABASE_URL.match(/^postgres(?:ql)?:\/\/([^:]+):[^@]+@/);
return match ? match[1] : undefined;
} catch {
return undefined;
}
})();
console.log("🔑 Username:", username);
try {
await db.transaction(async (tx) => {
await tx.execute(sql`DROP SCHEMA drizzle CASCADE`);
await tx.execute(sql`CREATE SCHEMA drizzle`);
await tx.execute(sql.raw(`GRANT ALL ON SCHEMA drizzle TO ${username}`));
await tx.execute(sql`GRANT ALL ON SCHEMA drizzle TO postgres`);
await tx.execute(sql`DROP SCHEMA public CASCADE`);
await tx.execute(sql`CREATE SCHEMA public`);
await tx.execute(sql.raw(`GRANT ALL ON SCHEMA public TO ${username}`));
await tx.execute(sql`GRANT ALL ON SCHEMA public TO postgres`);
});
console.log("✅ Database reset completed");
+18 -2
View File
@@ -1,10 +1,18 @@
import { useEffect } from "react";
import { useIsMounted } from "usehooks-ts";
import type z from "zod";
import webfontlist from "@/components/typography/webfontlist.json";
import type { typographySchema } from "@/schema/resume/data";
export function useWebfonts(typography: z.infer<typeof typographySchema>) {
const isMounted = useIsMounted();
useEffect(() => {
if (!isMounted()) return;
const body = document.body;
if (body) body.setAttribute("data-wf-loaded", "false");
async function loadFont(family: string, weights: string[]) {
const font = webfontlist.find((font) => font.family === family);
if (!font) return;
@@ -36,6 +44,14 @@ export function useWebfonts(typography: z.infer<typeof typographySchema>) {
Promise.all([
loadFont(bodyTypography.fontFamily, bodyTypography.fontWeights),
loadFont(headingTypography.fontFamily, headingTypography.fontWeights),
]);
}, [typography]);
]).then(() => {
if (isMounted() && body) body.setAttribute("data-wf-loaded", "true");
});
return () => {
if (isMounted()) {
if (body) body.removeAttribute("data-wf-loaded");
}
};
}, [isMounted, typography]);
}
@@ -99,6 +99,7 @@ export const printerService = {
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle0" });
await page.waitForFunction(() => document.body.getAttribute("data-wf-loaded") === "true", { timeout: 5_000 });
const pdfBuffer = await page.pdf({
width: pageDimensions[format].width,
@@ -173,6 +174,7 @@ export const printerService = {
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle0" });
await page.waitForFunction(() => document.body.getAttribute("data-wf-loaded") === "true", { timeout: 5_000 });
const screenshotBuffer = await page.screenshot({ type: "webp", quality: 80 });
@@ -55,16 +55,18 @@ const CSS_SELECTORS = [
".experience-item-header",
".experience-item-title",
".experience-item-name",
".experience-item-description",
".experience-item-company",
".experience-item-position",
".experience-item-location",
".experience-item-period",
".experience-item-link",
".experience-item-metadata",
".experience-item-description",
".education-item",
".education-item-header",
".education-item-title",
".education-item-name",
".education-item-description",
".education-item-link",
".education-item-metadata",
".projects-item",
".projects-item-header",
".projects-item-title",
@@ -199,9 +201,10 @@ export default function CSSMonacoEditor({ theme, defaultValue, onChange }: Props
return (
<Editor
language="css"
theme={theme === "dark" ? "vs-dark" : "light"}
defaultValue={defaultValue}
onChange={onChange}
defaultValue={defaultValue}
theme={theme === "dark" ? "vs-dark" : "light"}
options={{ tabSize: 2, wordWrap: "on", minimap: { enabled: false } }}
/>
);
}
+1 -1
View File
@@ -621,7 +621,7 @@ export const defaultResumeData: ResumeData = {
export const sampleResumeData: ResumeData = {
picture: {
hidden: false,
url: "https://i.imgur.com/o4Jpt1p.jpeg",
url: `https://rxresu.me/photos/sample-picture.jpg`,
size: 100,
rotation: 0,
aspectRatio: 1,