mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2026-10-01 01:04:29 +10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
269dbc600f | ||
|
|
9b29a44429 | ||
|
|
e9fa161148 | ||
|
|
299d6d4878 | ||
|
|
ea63ae6e72 | ||
|
|
4d50cd9b71 | ||
|
|
8b3f3bcc35 | ||
|
|
f73a5117a1 | ||
|
|
2d1e13b9b5 | ||
|
|
b6c274eeb6 | ||
|
|
4b78a7c9dd | ||
|
|
87e2f2f391 | ||
|
|
f237c42093 | ||
|
|
a80f86e99e | ||
|
|
eb62ed99ce | ||
|
|
45fc32323f | ||
|
|
f1d69da019 | ||
|
|
9d131fdb99 | ||
|
|
29ef6307e0 |
@@ -66,6 +66,10 @@ FLAG_DISABLE_SIGNUPS="false"
|
||||
# This flag disables email/password login. Disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true.
|
||||
FLAG_DISABLE_EMAIL_AUTH="false"
|
||||
|
||||
# This flag disables the image processing.
|
||||
# This is useful if you are using a machine with limited resources, like a Raspberry Pi.
|
||||
FLAG_DISABLE_IMAGE_PROCESSING="false"
|
||||
|
||||
# --- Others ---
|
||||
# Google Cloud API Key (optional)
|
||||
# This is not used within Reactive Resume, but in src/scripts/fonts/generate.ts to generate a list of fonts served by Google Fonts.
|
||||
|
||||
@@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
## Project Overview
|
||||
|
||||
Reactive Resume is a free, open-source resume builder built with TanStack Start (React 19 + Vite), using ORPC for type-safe RPC APIs, Drizzle ORM with PostgreSQL, and Better Auth for authentication.
|
||||
Reactive Resume is a free, open-source resume builder built with TanStack Start (React 19 + Vite 8), using ORPC for type-safe RPC APIs, Drizzle ORM with PostgreSQL, Nitro as the server runtime, and Better Auth for authentication. It is a PWA with 47 supported locales and 13 resume templates.
|
||||
|
||||
## Development Commands
|
||||
|
||||
@@ -18,10 +18,10 @@ pnpm build
|
||||
# Start production server
|
||||
pnpm start
|
||||
|
||||
# Linting (uses Biome)
|
||||
# Linting and formatting (uses Biome)
|
||||
pnpm lint
|
||||
|
||||
# Type checking
|
||||
# Type checking (uses tsgo)
|
||||
pnpm typecheck
|
||||
|
||||
# Database operations
|
||||
@@ -33,10 +33,12 @@ pnpm db:studio # Open Drizzle Studio
|
||||
# Extract i18n strings for translation
|
||||
pnpm lingui:extract
|
||||
|
||||
# Find unused exports
|
||||
# Find unused exports / dead code
|
||||
dotenvx run -- pnpm knip
|
||||
```
|
||||
|
||||
**There is no test framework configured.** No unit, integration, or E2E tests exist in the codebase.
|
||||
|
||||
## Local Development Setup
|
||||
|
||||
1. Copy `.env.example` to `.env` and configure environment variables
|
||||
@@ -48,27 +50,52 @@ dotenvx run -- pnpm knip
|
||||
- Adminer for DB management (port 8080)
|
||||
3. Run `pnpm dev`
|
||||
|
||||
Database migrations run automatically on server startup via the Nitro plugin at `plugins/1.migrate.ts`.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Directory Structure
|
||||
|
||||
- `src/routes/` - TanStack Router file-based routing
|
||||
- `src/integrations/` - External service integrations (auth, database, ORPC, AI, email)
|
||||
- `src/integrations/orpc/router` - oRPC server routers
|
||||
- `src/integrations/orpc/services` - oRPC server services
|
||||
- `src/integrations/` - External service integrations (auth, database, ORPC, AI, email, import)
|
||||
- `src/integrations/orpc/router/` - oRPC server routers (procedure definitions)
|
||||
- `src/integrations/orpc/services/` - oRPC server services (business logic)
|
||||
- `src/integrations/orpc/dto/` - Data transfer objects
|
||||
- `src/integrations/orpc/context.ts` - Auth and request context setup
|
||||
- `src/components/` - React components organized by feature
|
||||
- `src/components/ui/` - Shadcn UI components (Radix + Phosphor icons)
|
||||
- `src/schema/` - Zod schemas for validation
|
||||
- `plugins/` - Nitro server plugins (eg. auto-migration on startup)
|
||||
- `src/hooks/` - Custom React hooks
|
||||
- `plugins/` - Nitro server plugins (auto-migration on startup)
|
||||
- `migrations/` - Drizzle database migrations
|
||||
- `locales/` - i18n translation files (managed by Lingui)
|
||||
- `docs/` - Documentation (Mintlify)
|
||||
|
||||
### Key Integrations (`src/integrations/`)
|
||||
|
||||
- **auth/** - Better Auth configuration and client
|
||||
- **auth/** - Better Auth configuration (session-based + API key via `x-api-key` header)
|
||||
- **drizzle/** - Database schema and client (PostgreSQL)
|
||||
- **orpc/** - Type-safe RPC router with procedures for ai, auth, flags, printer, resume, statistics, storage
|
||||
- **query/** - TanStack Query client configuration
|
||||
- **ai/** - AI provider integrations (OpenAI, Anthropic, Google Gemini, Ollama)
|
||||
- **email/** - Nodemailer integration (falls back to console logging if SMTP is not configured)
|
||||
- **import/** - Resume file parsing/import
|
||||
|
||||
### ORPC Procedure Types
|
||||
|
||||
Three procedure types exist in `src/integrations/orpc/context.ts`:
|
||||
- `publicProcedure` - No authentication required
|
||||
- `protectedProcedure` - Requires authenticated user (session or API key)
|
||||
- `serverOnlyProcedure` - Server-side calls only
|
||||
|
||||
Procedures follow this pattern:
|
||||
```ts
|
||||
const handler = protectedProcedure
|
||||
.route({ method: "GET", path: "/resumes/{id}", tags: ["Resumes"], ... })
|
||||
.input(schema)
|
||||
.output(schema)
|
||||
.handler(async ({ context, input }) => { ... })
|
||||
```
|
||||
|
||||
### Resume Data Model
|
||||
|
||||
@@ -80,14 +107,17 @@ The resume schema is defined in `src/schema/resume/data.ts`. Key concepts:
|
||||
|
||||
### Resume Templates
|
||||
|
||||
Templates are React components in `src/components/resume/templates/`. Each template (azurill, bronzor, chikorita, etc.) renders the resume data with different visual styles. Templates use shared components from `src/components/resume/shared/`.
|
||||
13 templates in `src/components/resume/templates/` (Pokemon-themed names):
|
||||
azurill, bronzor, chikorita, ditgar, ditto, gengar, glalie, kakuna, lapras, leafish, onyx, pikachu, rhyhorn
|
||||
|
||||
Shared rendering components live in `src/components/resume/shared/`.
|
||||
|
||||
### Database Schema
|
||||
|
||||
Defined in `src/integrations/drizzle/schema.ts`:
|
||||
- `user`, `session`, `account`, `verification`, `twoFactor`, `passkey`, `apikey` - Better Auth tables
|
||||
- `resume` - Stores Resume Data as JSONB (defined in `src/schema/resume/data.ts`)
|
||||
- `resumeStatistics` - Views/Download for Resume Tracking
|
||||
- `resumeStatistics` - Views/Download tracking
|
||||
|
||||
### Routing
|
||||
|
||||
@@ -98,19 +128,39 @@ Uses TanStack Router with file-based routing. Key routes:
|
||||
- `/builder/$resumeId/` - Resume editor
|
||||
- `/printer/$resumeId/` - PDF rendering endpoint
|
||||
- `/api/` - Public API endpoints
|
||||
- `/mcp/` - MCP server endpoint for LLM integration
|
||||
|
||||
Routes use `createFileRoute()` with `beforeLoad()` for auth guards and `loader()` for server-side data fetching.
|
||||
|
||||
### MCP Server
|
||||
|
||||
An MCP (Model Context Protocol) server is available at `/mcp/` for LLM-based resume interaction. It requires an `x-api-key` header for authentication. Configuration is in `src/routes/mcp/` with helper modules for resources, prompts, and tools.
|
||||
|
||||
### State Management
|
||||
|
||||
- **Zustand** - Client-side state (resume editor state in `src/components/resume/store/`)
|
||||
- **Zundo** - Undo/redo history for resume edits (built on Zustand)
|
||||
- **TanStack Query** - Server state and caching (configured via ORPC integration)
|
||||
|
||||
### Global Providers
|
||||
|
||||
Defined in `src/routes/__root.tsx`:
|
||||
- I18nProvider (Lingui), ThemeProvider, MotionConfig, IconContext (Phosphor Icons)
|
||||
- ConfirmDialogProvider, PromptDialogProvider, DialogManager, CommandPalette, Toaster
|
||||
|
||||
## Code Style
|
||||
|
||||
- Uses Biome for linting and formatting
|
||||
- Uses **Biome** for linting and formatting (`biome.json`)
|
||||
- Tab indentation, double quotes, 120 character line width
|
||||
- Imports are auto-organized; unused imports are errors
|
||||
- a11y rules are disabled
|
||||
- Path alias: `@/` maps to `src/`
|
||||
- Tailwind CSS v4 with sorted class names (enforced by Biome)
|
||||
- Uses `cn()` utility for conditional class names
|
||||
- Tailwind CSS v4 with sorted class names (enforced by Biome's `useSortedClasses`)
|
||||
- Uses `cn()` utility (from `@/utils/style`) for conditional class names
|
||||
- Uses `cva()` for component variants
|
||||
- Shadcn UI components in `src/components/ui/` (Radix UI + Phosphor icons, zinc base color)
|
||||
- i18n strings use Lingui macros: `<Trans>`, `t`, `msg`
|
||||
- TypeScript strict mode enabled; `noUnusedLocals` and `noUnusedParameters` enforced
|
||||
|
||||
## Environment Variables
|
||||
|
||||
@@ -119,5 +169,21 @@ Key variables (see `.env.example` for full list):
|
||||
- `DATABASE_URL` - PostgreSQL connection string
|
||||
- `AUTH_SECRET` - Secret for authentication
|
||||
- `PRINTER_ENDPOINT` - WebSocket endpoint for PDF printer service
|
||||
- `S3_*` - S3-compatible storage configuration
|
||||
- `FLAG_*` - Feature flags
|
||||
- `PRINTER_APP_URL` - Internal URL for printer to reach the app (important for Docker)
|
||||
- `S3_*` - S3-compatible storage configuration (falls back to local `/data` filesystem)
|
||||
- `SMTP_*` - Email configuration (falls back to console logging)
|
||||
- `GOOGLE_CLIENT_ID/SECRET` - Google OAuth (optional)
|
||||
- `GITHUB_CLIENT_ID/SECRET` - GitHub OAuth (optional)
|
||||
- `OAUTH_*` - Custom OAuth provider (optional)
|
||||
- `FLAG_DEBUG_PRINTER` - Debug PDF printing endpoint
|
||||
- `FLAG_DISABLE_SIGNUPS` - Block new account registration
|
||||
- `FLAG_DISABLE_EMAIL_AUTH` - Disable email/password login
|
||||
- `FLAG_DISABLE_IMAGE_PROCESSING` - Disable image processing
|
||||
|
||||
## Build & Deployment
|
||||
|
||||
- **Build output**: `.output/` directory (Nitro server bundle)
|
||||
- **Production start**: `node .output/server/index.mjs`
|
||||
- **Docker**: Multi-stage Dockerfile with Node 24-slim base
|
||||
- **Health check**: `GET /api/health`
|
||||
- **PWA**: Configured via vite-plugin-pwa with auto-update, standalone display, dark theme
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
<img src="https://img.shields.io/github/stars/amruthpillai/Reactive-Resume?style=flat-square" alt="GitHub Stars">
|
||||
<img src="https://img.shields.io/github/license/amruthpillai/Reactive-Resume?style=flat-square" alt="License" />
|
||||
<img src="https://img.shields.io/docker/pulls/amruthpillai/reactive-resume?style=flat-square" alt="Docker Pulls" />
|
||||
<a href="https://discord.gg/EE8yFqW4"><img src="https://img.shields.io/discord/1173518977851473940?style=flat-square&label=discord" alt="Discord" /></a>
|
||||
<a href="https://discord.gg/aSyA5ZSxpb"><img src="https://img.shields.io/discord/1173518977851473940?style=flat-square&label=discord" alt="Discord" /></a>
|
||||
<a href="https://crowdin.com/project/reactive-resume"><img src="https://badges.crowdin.net/reactive-resume/localized.svg?style=flat-square" alt="Crowdin" /></a>
|
||||
<a href="https://github.com/sponsors/AmruthPillai"><img src="https://img.shields.io/github/sponsors/AmruthPillai?style=flat-square&label=sponsors" alt="Sponsors" /></a>
|
||||
<a href="https://opencollective.com/reactive-resume"><img src="https://img.shields.io/opencollective/backers/reactive-resume?style=flat-square&label=donations" alt="Donations" /></a>
|
||||
|
||||
+5
-5
@@ -53,11 +53,11 @@ services:
|
||||
|
||||
# As an alternative to browserless, you can also use a lightweight image like chromedp/headless-shell:latest
|
||||
# See https://docs.rxresu.me/self-hosting/docker#alternative-printer-options for more information.
|
||||
# chrome:
|
||||
# image: chromedp/headless-shell:latest
|
||||
# restart: unless-stopped
|
||||
# ports:
|
||||
# - "9222:9222"
|
||||
chrome:
|
||||
image: chromedp/headless-shell:latest
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "9222:9222"
|
||||
|
||||
seaweedfs:
|
||||
image: chrislusf/seaweedfs:latest
|
||||
|
||||
@@ -4,6 +4,34 @@ description: "List of all notable changes and updates to Reactive Resume"
|
||||
rss: true
|
||||
---
|
||||
|
||||
<Update label="v5.0.10" description="24th February 2026">
|
||||
- **Fixes**
|
||||
- Show section titles for summary-type custom sections in the resume builder. [#2744](https://github.com/amruthpillai/reactive-resume/pull/2744)
|
||||
- Prevent browser password managers and Edge autofill/save prompts from appearing on AI settings API key fields. [#2719](https://github.com/amruthpillai/reactive-resume/pull/2719)
|
||||
- Replace deprecated Tailwind CSS classes: use `inset-s-*`/`inset-e-*` instead of `start-*`/`end-*`.
|
||||
- Fix PDF downloader to work correctly in offline mode. [#2743](https://github.com/amruthpillai/reactive-resume/pull/2743)
|
||||
- Make bold formatting visible for `<strong>` in the resume rich text editor (uses plain `font-weight: bold` fallback if the CSS variable is unset). Fixes [#2730](https://github.com/amruthpillai/reactive-resume/issues/2730)
|
||||
- Prevent credentials sign-in from dropping `session_token` Set-Cookie, improves login reliability. [#2718](https://github.com/amruthpillai/reactive-resume/pull/2718)
|
||||
- Remove redundant `resume-` prefix from download filename; add spacing between pages in shared view. [#2709](https://github.com/amruthpillai/reactive-resume/pull/2709)
|
||||
- Normalize autocomplete tokens for login and register forms. [#2714](https://github.com/amruthpillai/reactive-resume/pull/2714)
|
||||
- Fix improper chips reordering and update dependency/translations. [#2711](https://github.com/amruthpillai/reactive-resume/issues/2711)
|
||||
- Fix issue with clipping of heading in Lapras resume template.
|
||||
- Remove error-causing plugins in oRPC integration.
|
||||
- Remove duplicate database indexes; add index for `created_at` on user and resume tables.
|
||||
|
||||
- **Features & Improvements**
|
||||
- Add new feature flag: `FLAG_DISABLE_IMAGE_PROCESSING` (allows disabling of image processing site-wide).
|
||||
- Update Discord invite link in the app and documentation.
|
||||
- Add comprehensive codebase and architecture documentation in `CLAUDE.md`.
|
||||
- Sync latest translations from Crowdin (updated and added multiple languages).
|
||||
|
||||
- **Other**
|
||||
- Refactor: rename auth utility from `originWith` to `withHostname`, preserve localhost/127.0.0.1 sibling trust.
|
||||
- General dependency updates and code style improvements.
|
||||
- Documentation and README updates.
|
||||
</Update>
|
||||
|
||||
|
||||
<Update label="v5.0.9" description="9th February 2026">
|
||||
- Add Computer Modern web fonts to the font selector, allowing the user to choose from a variety of "Computer Modern" (LaTeX) fonts.
|
||||
- This lets you create a resume that looks just like a LaTeX document, with the same fonts and styles.
|
||||
|
||||
@@ -104,7 +104,7 @@ Reactive Resume is built with modern web technologies:
|
||||
<Card title="Reddit" icon="reddit" href="https://reddit.com/r/reactiveresume">
|
||||
Join our Reddit community to get help and connect with other users.
|
||||
</Card>
|
||||
<Card title="Discord" icon="discord" href="https://discord.gg/EE8yFqW4">
|
||||
<Card title="Discord" icon="discord" href="https://discord.gg/aSyA5ZSxpb">
|
||||
Join our Discord server to get help and connect with other users.
|
||||
</Card>
|
||||
<Card title="Sponsor" icon="heart" href="https://opencollective.com/reactive-resume">
|
||||
|
||||
@@ -193,6 +193,7 @@ Here's a complete list of environment variables you can configure:
|
||||
| `FLAG_DEBUG_PRINTER` | Used for debugging the printer route | `false` |
|
||||
| `FLAG_DISABLE_SIGNUPS` | Disables new user signups | `false` |
|
||||
| `FLAG_DISABLE_EMAIL_AUTH` | Disables email/password login (SSO only) | `false` |
|
||||
| `FLAG_DISABLE_IMAGE_PROCESSING` | Disables image processing | `false` |
|
||||
|
||||
> **Note:** Some variables are only required for using related features (OAuth, SMTP, S3, etc.) and can be left unset if unused.
|
||||
|
||||
|
||||
@@ -363,6 +363,7 @@ openssl rand -hex 32
|
||||
- **`FLAG_DEBUG_PRINTER`**: Bypasses the printer-only access restriction (useful when debugging `/printer/{resumeId}`). Recommended: keep `"false"` in production.
|
||||
- **`FLAG_DISABLE_SIGNUPS`**: Disables new signups (web app and server). Useful for private instances.
|
||||
- **`FLAG_DISABLE_EMAIL_AUTH`**: Disables email/password login entirely. Also disables email verification, forgot password, and reset password flows. Users can still sign up via social auth (Google/GitHub/Custom OAuth), unless FLAG_DISABLE_SIGNUPS is also set to true. Useful when only SSO is required.
|
||||
- **`FLAG_DISABLE_IMAGE_PROCESSING`**: Disables image processing. This is useful if you are using a machine with limited resources, like a Raspberry Pi.
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import { defineConfig } from "drizzle-kit";
|
||||
import invariant from "tiny-invariant";
|
||||
|
||||
invariant(process.env.DATABASE_URL, "DATABASE_URL is not set");
|
||||
|
||||
export default defineConfig({
|
||||
out: "./migrations",
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
"project": ["src/**/*.{js,jsx,ts,tsx,mdx,css}"],
|
||||
"ignoreBinaries": ["mint"],
|
||||
"ignoreDependencies": ["npm-check-updates", "node-addon-api", "node-gyp"],
|
||||
"ignore": ["src/schema/**/*.ts", "src/components/**/*.{ts,tsx}", "src/routeTree.gen.ts"]
|
||||
"ignore": ["src/schema/**/*.ts", "src/components/**/*.{ts,tsx}"]
|
||||
}
|
||||
|
||||
Generated
BIN
Binary file not shown.
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: am\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:59\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Amharic\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ar\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:30\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Arabic\n"
|
||||
"Plural-Forms: nplurals=6; plural=(n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: az\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Azerbaijani\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: bg\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Bulgarian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: bn\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Bengali\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ca\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Catalan\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: cs\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Czech\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: da\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Danish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: de\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: German\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: el\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Greek\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+9
-9
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: es\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-17 09:51\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Spanish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
@@ -209,7 +209,7 @@ msgstr "Añadir fila antes"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Añadido"
|
||||
|
||||
#: src/utils/locale.ts
|
||||
msgid "Afrikaans"
|
||||
@@ -222,7 +222,7 @@ msgstr "IA"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "AI Chat"
|
||||
msgstr ""
|
||||
msgstr "Chat con IA"
|
||||
|
||||
#: src/utils/locale.ts
|
||||
msgid "Albanian"
|
||||
@@ -337,7 +337,7 @@ msgstr "Inteligencia artificial"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Ask me to update your resume..."
|
||||
msgstr ""
|
||||
msgstr "Pídame que actualice su currículum..."
|
||||
|
||||
#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx
|
||||
msgid "Aspect Ratio"
|
||||
@@ -535,7 +535,7 @@ msgstr "Círculo"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Clear chat history"
|
||||
msgstr ""
|
||||
msgstr "Limpiar historial del chat"
|
||||
|
||||
#: src/components/ui/multiple-combobox.tsx
|
||||
msgid "Clear selection"
|
||||
@@ -946,7 +946,7 @@ msgstr "Neerlandés"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "e.g. Change my name to..."
|
||||
msgstr ""
|
||||
msgstr "ej. Cambiar mi nombre a..."
|
||||
|
||||
#: src/utils/resume/section.tsx
|
||||
msgid "Education"
|
||||
@@ -1893,7 +1893,7 @@ msgstr "Quitar contraseña"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Removed"
|
||||
msgstr ""
|
||||
msgstr "Eliminado"
|
||||
|
||||
#: src/routes/builder/$resumeId/-sidebar/right/sections/sharing.tsx
|
||||
msgid "Removing password protection..."
|
||||
@@ -2357,7 +2357,7 @@ msgstr "Ha habido un problema al generar el PDF, inténtalo de nuevo dentro de u
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Thinking..."
|
||||
msgstr ""
|
||||
msgstr "Pensando..."
|
||||
|
||||
#: src/routes/builder/$resumeId/-components/header.tsx
|
||||
#: src/routes/dashboard/resumes/-components/menus/context-menu.tsx
|
||||
@@ -2624,7 +2624,7 @@ msgstr "Actualiza tu contraseña"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Updated"
|
||||
msgstr ""
|
||||
msgstr "Actualizado"
|
||||
|
||||
#: src/dialogs/auth/change-password.tsx
|
||||
msgid "Updating your password..."
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: fa\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Persian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: fi\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Finnish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+9
-9
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: fr\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-11 19:56\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: French\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
@@ -209,7 +209,7 @@ msgstr "Ajouter une ligne avant"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
msgstr "Ajouté"
|
||||
|
||||
#: src/utils/locale.ts
|
||||
msgid "Afrikaans"
|
||||
@@ -222,7 +222,7 @@ msgstr "IA"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "AI Chat"
|
||||
msgstr ""
|
||||
msgstr "Chat avec l'IA"
|
||||
|
||||
#: src/utils/locale.ts
|
||||
msgid "Albanian"
|
||||
@@ -337,7 +337,7 @@ msgstr "Intelligence artificielle"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Ask me to update your resume..."
|
||||
msgstr ""
|
||||
msgstr "Demandez-moi de mettre à jour votre CV."
|
||||
|
||||
#: src/routes/builder/$resumeId/-sidebar/left/sections/picture.tsx
|
||||
msgid "Aspect Ratio"
|
||||
@@ -535,7 +535,7 @@ msgstr "Cercle"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Clear chat history"
|
||||
msgstr ""
|
||||
msgstr "Effacer l'historique de chat"
|
||||
|
||||
#: src/components/ui/multiple-combobox.tsx
|
||||
msgid "Clear selection"
|
||||
@@ -946,7 +946,7 @@ msgstr "Néerlandais"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "e.g. Change my name to..."
|
||||
msgstr ""
|
||||
msgstr "Par ex. : Changer mon nom en"
|
||||
|
||||
#: src/utils/resume/section.tsx
|
||||
msgid "Education"
|
||||
@@ -1893,7 +1893,7 @@ msgstr "Supprimer le mot de passe"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Removed"
|
||||
msgstr ""
|
||||
msgstr "Supprimé"
|
||||
|
||||
#: src/routes/builder/$resumeId/-sidebar/right/sections/sharing.tsx
|
||||
msgid "Removing password protection..."
|
||||
@@ -2357,7 +2357,7 @@ msgstr "Un problème est survenu lors de la génération du PDF, veuillez réess
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Thinking..."
|
||||
msgstr ""
|
||||
msgstr "Réflexion..."
|
||||
|
||||
#: src/routes/builder/$resumeId/-components/header.tsx
|
||||
#: src/routes/dashboard/resumes/-components/menus/context-menu.tsx
|
||||
@@ -2624,7 +2624,7 @@ msgstr "Modifier votre mot de passe"
|
||||
|
||||
#: src/components/ai/chat.tsx
|
||||
msgid "Updated"
|
||||
msgstr ""
|
||||
msgstr "Mis à jour"
|
||||
|
||||
#: src/dialogs/auth/change-password.tsx
|
||||
msgid "Updating your password..."
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: he\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Hebrew\n"
|
||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: hi\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Hindi\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: hu\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Hungarian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: id\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Indonesian\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: it\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Italian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ja\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: km\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Khmer\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: kn\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Kannada\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ko\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Korean\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: lt\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Lithuanian\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && (n%100>19 || n%100<11) ? 0 : (n%10>=2 && n%10<=9) && (n%100>19 || n%100<11) ? 1 : n%1!=0 ? 2: 3);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: lv\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Latvian\n"
|
||||
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n != 0 ? 1 : 2;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ml\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Malayalam\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: mr\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Marathi\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ms\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Malay\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ne\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:59\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Nepali\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: nl\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Dutch\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: no\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Norwegian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: or\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Odia\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: pl\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Polish\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1 ? 0 : (n%10>=2 && n%10<=4) && (n%100<12 || n%100>14) ? 1 : n!=1 && (n%10>=0 && n%10<=1) || (n%10>=5 && n%10<=9) || (n%100>=12 && n%100<=14) ? 2 : 3);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: pt\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese, Brazilian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: pt\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Portuguese\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ro\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:30\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Romanian\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : (n==0 || (n%100>0 && n%100<20)) ? 1 : 2);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ru\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Russian\n"
|
||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: sk\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Slovak\n"
|
||||
"Plural-Forms: nplurals=4; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 3;\n"
|
||||
|
||||
Generated
+1
-44
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: sl\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Slovenian\n"
|
||||
"Plural-Forms: nplurals=4; plural=n%100==1 ? 0 : n%100==2 ? 1 : n%100==3 || n%100==4 ? 2 : 3;\n"
|
||||
@@ -211,15 +211,6 @@ msgstr "Dodaj vrstico pred"
|
||||
msgid "Added"
|
||||
msgstr ""
|
||||
|
||||
#. placeholder {0}: passkey.createdAt.toLocaleDateString()
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "Added on {0}"
|
||||
msgstr "Dodano {0}"
|
||||
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "Adding your passkey..."
|
||||
msgstr "Dodajanje vaše ključa za prijavo..."
|
||||
|
||||
#: src/utils/locale.ts
|
||||
msgid "Afrikaans"
|
||||
msgstr "Afriščina"
|
||||
@@ -313,10 +304,6 @@ msgstr ""
|
||||
msgid "Are you sure you want to delete this API key?"
|
||||
msgstr "Ali ste prepričani, da želite izbrisati ta API ključ?"
|
||||
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "Are you sure you want to delete this passkey?"
|
||||
msgstr "Ali ste prepričani, da želite izbrisati ta ključ za prijavo?"
|
||||
|
||||
#: src/routes/builder/$resumeId/-components/header.tsx
|
||||
#: src/routes/dashboard/resumes/-components/menus/context-menu.tsx
|
||||
#: src/routes/dashboard/resumes/-components/menus/dropdown-menu.tsx
|
||||
@@ -840,10 +827,6 @@ msgstr "Brišem vaš račun..."
|
||||
msgid "Deleting your API key..."
|
||||
msgstr "Brišem vaš API ključ ..."
|
||||
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "Deleting your passkey..."
|
||||
msgstr "Brišem vaš varnostni ključ..."
|
||||
|
||||
#: src/routes/builder/$resumeId/-components/header.tsx
|
||||
#: src/routes/dashboard/resumes/-components/menus/context-menu.tsx
|
||||
#: src/routes/dashboard/resumes/-components/menus/dropdown-menu.tsx
|
||||
@@ -1701,10 +1684,6 @@ msgstr "Stran {pageNumber} od {totalNumberOfPages}"
|
||||
msgid "Paragraph"
|
||||
msgstr "Odstavek"
|
||||
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "Passkeys"
|
||||
msgstr "Ključi za preverjanje pristnosti"
|
||||
|
||||
#: src/routes/_home/-sections/features.tsx
|
||||
msgid "Passkeys & 2FA"
|
||||
msgstr "Ključi za prijavo in dvofaktorska avtentikacija"
|
||||
@@ -1904,10 +1883,6 @@ msgstr "Reference"
|
||||
msgid "Refresh"
|
||||
msgstr "Osveži"
|
||||
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "Register New Device"
|
||||
msgstr "Registriraj novo napravo"
|
||||
|
||||
#: src/routes/auth/forgot-password.tsx
|
||||
msgid "Remember your password? <0><1>Sign in now <2/></1></0>"
|
||||
msgstr "Se spomnite gesla? <0><1>Prijavite se zdaj <2/></1></0>"
|
||||
@@ -2141,7 +2116,6 @@ msgstr "Prijavite se z GitHub, Google ali poljubnim ponudnikom OAuth."
|
||||
msgid "Sign up"
|
||||
msgstr "Registracija"
|
||||
|
||||
#: src/routes/auth/-components/social-auth.tsx
|
||||
#: src/routes/auth/-components/social-auth.tsx
|
||||
#: src/routes/auth/-components/social-auth.tsx
|
||||
#: src/routes/auth/login.tsx
|
||||
@@ -2741,10 +2715,6 @@ msgstr "Prostovoljec"
|
||||
msgid "Website"
|
||||
msgstr "Spletna stran"
|
||||
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "What do you want to call this passkey?"
|
||||
msgstr "Kako bi želeli poimenovati ta dostopni ključ?"
|
||||
|
||||
#: src/routes/builder/$resumeId/-sidebar/left/shared/section-menu.tsx
|
||||
msgid "What do you want to rename this section to?"
|
||||
msgstr "Kako želite preimenovati ta razdelek?"
|
||||
@@ -2791,11 +2761,6 @@ msgstr "Za prijavo lahko uporabite tudi svoje uporabniško ime."
|
||||
msgid "You can share your resume via a unique public URL, protect it with a password, or download it as a PDF to share directly. The choice is yours!"
|
||||
msgstr "Svoj življenjepis lahko delite prek edinstvenega javnega URL-ja, ga zaščitite z geslom ali prenesete kot PDF za neposredno deljenje. Izbira je vaša!"
|
||||
|
||||
#. placeholder {0}: passkey.name ?? "(WebAuthn Device)"
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "You cannot use the passkey \"{0}\" anymore to sign in after deletion. This action cannot be undone."
|
||||
msgstr "Ključa \"{0}\" po izbrisu ne boste mogli več uporabiti za prijavo. To dejanje je nepovratno."
|
||||
|
||||
#: src/hooks/use-form-blocker.tsx
|
||||
msgid "You have unsaved changes that will be lost."
|
||||
msgstr ""
|
||||
@@ -2829,14 +2794,6 @@ msgstr "Vaši podatki so shranjeni lokalno"
|
||||
msgid "Your data is stored securely and is never shared with third parties. You can also self-host Reactive Resume on your own servers for complete control over your data."
|
||||
msgstr "Vaši podatki so varno shranjeni in se nikoli ne delijo s tretjimi osebami. Reactive Življenjepis lahko tudi gostite sami na svojih strežnikih za popoln nadzor nad svojimi podatki."
|
||||
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "Your passkey has been added successfully."
|
||||
msgstr "Vaš dostopni ključ je bil uspešno dodan."
|
||||
|
||||
#: src/routes/dashboard/settings/authentication/-components/passkeys.tsx
|
||||
msgid "Your passkey has been deleted successfully."
|
||||
msgstr "Vaš dostopni ključ je bil uspešno izbrisan."
|
||||
|
||||
#: src/routes/auth/reset-password.tsx
|
||||
msgid "Your password has been reset successfully. You can now sign in with your new password."
|
||||
msgstr "Vaše geslo je bilo uspešno ponastavljeno. Zdaj se lahko prijavite z novim geslom."
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: sq\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Albanian\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: sr\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Serbian (Cyrillic)\n"
|
||||
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: sv\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Swedish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: ta\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Tamil\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: te\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Telugu\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: th\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Thai\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: tr\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Turkish\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: uk\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Ukrainian\n"
|
||||
"Plural-Forms: nplurals=4; plural=((n%10==1 && n%100!=11) ? 0 : ((n%10 >= 2 && n%10 <=4 && (n%100 < 12 || n%100 > 14)) ? 1 : ((n%10 == 0 || (n%10 >= 5 && n%10 <=9)) || (n%100 >= 11 && n%100 <= 14)) ? 2 : 3));\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: uz\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Uzbek\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: vi\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Vietnamese\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: zh\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Simplified\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
Generated
+1
-1
@@ -8,7 +8,7 @@ msgstr ""
|
||||
"Language: zh\n"
|
||||
"Project-Id-Version: reactive-resume\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"PO-Revision-Date: 2026-02-09 00:58\n"
|
||||
"PO-Revision-Date: 2026-02-09 22:31\n"
|
||||
"Last-Translator: \n"
|
||||
"Language-Team: Chinese Traditional\n"
|
||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
DROP INDEX "user_email_index";--> statement-breakpoint
|
||||
DROP INDEX "user_username_index";--> statement-breakpoint
|
||||
DROP INDEX "resume_statistics_resume_id_index";--> statement-breakpoint
|
||||
DROP INDEX "verification_identifier_index";--> statement-breakpoint
|
||||
CREATE INDEX "resume_created_at_index" ON "resume" ("created_at");--> statement-breakpoint
|
||||
CREATE INDEX "user_created_at_index" ON "user" ("created_at");
|
||||
File diff suppressed because it is too large
Load Diff
+54
-55
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "reactive-resume",
|
||||
"description": "Reactive Resume is a free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume.",
|
||||
"version": "5.0.9",
|
||||
"version": "5.0.10",
|
||||
"license": "MIT",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.29.2+sha512.bef43fa759d91fd2da4b319a5a0d13ef7a45bb985a3d7342058470f9d2051a3ba8674e629672654686ef9443ad13a82da2beb9eeb3e0221c87b8154fff9d74b8",
|
||||
"packageManager": "pnpm@10.30.2+sha512.36cdc707e7b7940a988c9c1ecf88d084f8514b5c3f085f53a2e244c2921d3b2545bc20dd4ebe1fc245feec463bb298aecea7a63ed1f7680b877dc6379d8d0cb4",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/amruthpillai/reactive-resume.git"
|
||||
@@ -31,79 +31,78 @@
|
||||
"typecheck": "tsgo --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ai-sdk/anthropic": "^3.0.39",
|
||||
"@ai-sdk/google": "^3.0.23",
|
||||
"@ai-sdk/openai": "^3.0.26",
|
||||
"@ai-sdk/react": "^3.0.80",
|
||||
"@aws-sdk/client-s3": "^3.986.0",
|
||||
"@better-auth/core": "1.5.0-beta.13",
|
||||
"@ai-sdk/anthropic": "^3.0.46",
|
||||
"@ai-sdk/google": "^3.0.30",
|
||||
"@ai-sdk/openai": "^3.0.31",
|
||||
"@ai-sdk/react": "^3.0.99",
|
||||
"@aws-sdk/client-s3": "^3.996.0",
|
||||
"@better-auth/core": "1.5.0-beta.18",
|
||||
"@dnd-kit/core": "^6.3.1",
|
||||
"@dnd-kit/sortable": "^10.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.2",
|
||||
"@fontsource-variable/ibm-plex-sans": "^5.2.8",
|
||||
"@hookform/resolvers": "^5.2.2",
|
||||
"@lingui/core": "^5.9.0",
|
||||
"@lingui/react": "^5.9.0",
|
||||
"@lingui/core": "^5.9.2",
|
||||
"@lingui/react": "^5.9.2",
|
||||
"@monaco-editor/react": "4.8.0-rc.3",
|
||||
"@orpc/client": "^1.13.4",
|
||||
"@orpc/json-schema": "^1.13.4",
|
||||
"@orpc/openapi": "^1.13.4",
|
||||
"@orpc/server": "^1.13.4",
|
||||
"@orpc/tanstack-query": "^1.13.4",
|
||||
"@orpc/zod": "^1.13.4",
|
||||
"@orpc/client": "^1.13.5",
|
||||
"@orpc/json-schema": "^1.13.5",
|
||||
"@orpc/openapi": "^1.13.5",
|
||||
"@orpc/server": "^1.13.5",
|
||||
"@orpc/tanstack-query": "^1.13.5",
|
||||
"@orpc/zod": "^1.13.5",
|
||||
"@phosphor-icons/react": "^2.1.10",
|
||||
"@phosphor-icons/web": "^2.1.2",
|
||||
"@sindresorhus/slugify": "^3.0.0",
|
||||
"@t3-oss/env-core": "^0.13.10",
|
||||
"@tanstack/react-query": "5.90.20",
|
||||
"@tanstack/react-router": "^1.159.5",
|
||||
"@tanstack/react-router-ssr-query": "^1.159.5",
|
||||
"@tanstack/react-start": "^1.159.5",
|
||||
"@tanstack/zod-adapter": "^1.159.5",
|
||||
"@tiptap/extension-highlight": "^3.19.0",
|
||||
"@tiptap/extension-table": "^3.19.0",
|
||||
"@tiptap/extension-text-align": "^3.19.0",
|
||||
"@tiptap/pm": "^3.19.0",
|
||||
"@tiptap/react": "^3.19.0",
|
||||
"@tiptap/starter-kit": "^3.19.0",
|
||||
"ai": "^6.0.78",
|
||||
"ai-sdk-ollama": "^3.5.0",
|
||||
"@tanstack/react-query": "5.90.21",
|
||||
"@tanstack/react-router": "^1.162.8",
|
||||
"@tanstack/react-router-ssr-query": "^1.162.8",
|
||||
"@tanstack/react-start": "^1.162.8",
|
||||
"@tanstack/zod-adapter": "^1.162.8",
|
||||
"@tiptap/extension-highlight": "^3.20.0",
|
||||
"@tiptap/extension-table": "^3.20.0",
|
||||
"@tiptap/extension-text-align": "^3.20.0",
|
||||
"@tiptap/pm": "^3.20.0",
|
||||
"@tiptap/react": "^3.20.0",
|
||||
"@tiptap/starter-kit": "^3.20.0",
|
||||
"ai": "^6.0.97",
|
||||
"ai-sdk-ollama": "^3.7.1",
|
||||
"bcrypt": "^6.0.0",
|
||||
"better-auth": "1.5.0-beta.13",
|
||||
"better-auth": "1.5.0-beta.18",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk": "^1.1.1",
|
||||
"dompurify": "^3.3.1",
|
||||
"drizzle-orm": "^1.0.0-beta.15-859cf75",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"@modelcontextprotocol/sdk": "^1.26.0",
|
||||
"@modelcontextprotocol/sdk": "^1.27.0",
|
||||
"es-toolkit": "^1.44.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"fast-json-patch": "^3.1.1",
|
||||
"fuse.js": "^7.1.0",
|
||||
"immer": "^11.1.3",
|
||||
"immer": "^11.1.4",
|
||||
"input-otp": "^1.4.2",
|
||||
"js-cookie": "^3.0.5",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"motion": "^12.34.0",
|
||||
"motion": "^12.34.3",
|
||||
"nodemailer": "^8.0.1",
|
||||
"pg": "^8.18.0",
|
||||
"puppeteer-core": "^24.37.2",
|
||||
"puppeteer-core": "^24.37.5",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"radix-ui": "^1.4.3",
|
||||
"react": "^19.2.4",
|
||||
"react-dom": "^19.2.4",
|
||||
"react-hook-form": "^7.71.1",
|
||||
"react-hook-form": "^7.71.2",
|
||||
"react-hotkeys-hook": "^5.2.4",
|
||||
"react-resizable-panels": "^4.6.2",
|
||||
"react-window": "^2.2.6",
|
||||
"react-resizable-panels": "^4.6.5",
|
||||
"react-window": "^2.2.7",
|
||||
"react-zoom-pan-pinch": "^3.7.0",
|
||||
"shadcn": "^3.8.4",
|
||||
"shadcn": "^3.8.5",
|
||||
"sharp": "^0.34.5",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.4.0",
|
||||
"tailwindcss": "^4.1.18",
|
||||
"tiny-invariant": "^1.3.3",
|
||||
"tailwind-merge": "^3.5.0",
|
||||
"tailwindcss": "^4.2.1",
|
||||
"ts-pattern": "^5.9.0",
|
||||
"tw-animate-css": "^1.4.0",
|
||||
"unique-names-generator": "^4.7.1",
|
||||
@@ -115,34 +114,34 @@
|
||||
"zustand": "^5.0.11"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.3.14",
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.9.0",
|
||||
"@lingui/cli": "^5.9.0",
|
||||
"@lingui/vite-plugin": "^5.9.0",
|
||||
"@tailwindcss/vite": "^4.1.18",
|
||||
"@biomejs/biome": "^2.4.4",
|
||||
"@lingui/babel-plugin-lingui-macro": "^5.9.2",
|
||||
"@lingui/cli": "^5.9.2",
|
||||
"@lingui/vite-plugin": "^5.9.2",
|
||||
"@tailwindcss/vite": "^4.2.1",
|
||||
"@types/bcrypt": "^6.0.0",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/node": "^25.2.2",
|
||||
"@types/nodemailer": "^7.0.9",
|
||||
"@types/node": "^25.3.0",
|
||||
"@types/nodemailer": "^7.0.11",
|
||||
"@types/pg": "^8.16.0",
|
||||
"@types/react": "^19.2.13",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260209.1",
|
||||
"@vitejs/plugin-react": "^5.1.3",
|
||||
"@typescript/native-preview": "7.0.0-dev.20260224.1",
|
||||
"@vitejs/plugin-react": "^5.1.4",
|
||||
"babel-plugin-macros": "^3.1.0",
|
||||
"drizzle-kit": "^1.0.0-beta.15-859cf75",
|
||||
"knip": "^5.83.1",
|
||||
"knip": "^5.85.0",
|
||||
"nitro": "npm:nitro-nightly@latest",
|
||||
"node-addon-api": "^8.5.0",
|
||||
"node-gyp": "^12.2.0",
|
||||
"npm-check-updates": "^19.3.2",
|
||||
"npm-check-updates": "^19.4.1",
|
||||
"tsx": "^4.21.0",
|
||||
"vite": "^8.0.0-beta.13",
|
||||
"vite": "^8.0.0-beta.15",
|
||||
"vite-plugin-pwa": "^1.2.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"vite": "^8.0.0-beta.13"
|
||||
"vite": "^8.0.0-beta.15"
|
||||
},
|
||||
"onlyBuiltDependencies": [
|
||||
"@prisma/engines",
|
||||
|
||||
Generated
+1789
-1982
File diff suppressed because it is too large
Load Diff
@@ -33,10 +33,10 @@ export const Spotlight = ({
|
||||
<motion.div
|
||||
animate={{ x: [0, xOffset, 0] }}
|
||||
transition={{ duration, repeat: Infinity, repeatType: "reverse", ease: "easeInOut" }}
|
||||
className="pointer-events-none absolute start-0 top-0 z-40 h-svh w-svw"
|
||||
className="pointer-events-none absolute inset-s-0 top-0 z-40 h-svh w-svw"
|
||||
>
|
||||
<div
|
||||
className="absolute start-0 top-0"
|
||||
className="absolute inset-s-0 top-0"
|
||||
style={{
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
@@ -46,7 +46,7 @@ export const Spotlight = ({
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute start-0 top-0 origin-top-left"
|
||||
className="absolute inset-s-0 top-0 origin-top-left"
|
||||
style={{
|
||||
height: `${height}px`,
|
||||
width: `${smallWidth}px`,
|
||||
@@ -56,7 +56,7 @@ export const Spotlight = ({
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute start-0 top-0 origin-top-left"
|
||||
className="absolute inset-s-0 top-0 origin-top-left"
|
||||
style={{
|
||||
height: `${height}px`,
|
||||
width: `${smallWidth}px`,
|
||||
@@ -68,7 +68,7 @@ export const Spotlight = ({
|
||||
|
||||
<motion.div
|
||||
animate={{ x: [0, -xOffset, 0] }}
|
||||
className="pointer-events-none absolute end-0 top-0 z-40 h-svh w-svw"
|
||||
className="pointer-events-none absolute inset-e-0 top-0 z-40 h-svh w-svw"
|
||||
transition={{
|
||||
duration,
|
||||
repeat: Infinity,
|
||||
@@ -77,7 +77,7 @@ export const Spotlight = ({
|
||||
}}
|
||||
>
|
||||
<div
|
||||
className="absolute end-0 top-0"
|
||||
className="absolute inset-e-0 top-0"
|
||||
style={{
|
||||
width: `${width}px`,
|
||||
height: `${height}px`,
|
||||
@@ -87,7 +87,7 @@ export const Spotlight = ({
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute end-0 top-0 origin-top-right"
|
||||
className="absolute inset-e-0 top-0 origin-top-right"
|
||||
style={{
|
||||
height: `${height}px`,
|
||||
width: `${smallWidth}px`,
|
||||
@@ -97,7 +97,7 @@ export const Spotlight = ({
|
||||
/>
|
||||
|
||||
<div
|
||||
className="absolute end-0 top-0 origin-top-right"
|
||||
className="absolute inset-e-0 top-0 origin-top-right"
|
||||
style={{
|
||||
height: `${height}px`,
|
||||
width: `${smallWidth}px`,
|
||||
|
||||
@@ -1,6 +1,22 @@
|
||||
import {
|
||||
closestCenter,
|
||||
DndContext,
|
||||
type DragEndEvent,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from "@dnd-kit/core";
|
||||
import {
|
||||
horizontalListSortingStrategy,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
useSortable,
|
||||
} from "@dnd-kit/sortable";
|
||||
import { CSS } from "@dnd-kit/utilities";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { PencilSimpleIcon, XIcon } from "@phosphor-icons/react";
|
||||
import { motion, Reorder, useDragControls } from "motion/react";
|
||||
import { motion } from "motion/react";
|
||||
import * as React from "react";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -12,6 +28,7 @@ const RETURN_KEY = "Enter";
|
||||
const COMMA_KEY = ",";
|
||||
|
||||
type ChipItemProps = {
|
||||
id: string;
|
||||
chip: string;
|
||||
index: number;
|
||||
isEditing: boolean;
|
||||
@@ -19,35 +36,37 @@ type ChipItemProps = {
|
||||
onRemove: (index: number) => void;
|
||||
};
|
||||
|
||||
function ChipItem({ chip, index, isEditing, onEdit, onRemove }: ChipItemProps) {
|
||||
const controls = useDragControls();
|
||||
function ChipItem({ id, chip, index, isEditing, onEdit, onRemove }: ChipItemProps) {
|
||||
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({ id });
|
||||
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
zIndex: isDragging ? 10 : undefined,
|
||||
opacity: isDragging ? 0.6 : 1,
|
||||
};
|
||||
|
||||
return (
|
||||
<Reorder.Item
|
||||
value={chip}
|
||||
dragListener={false}
|
||||
dragControls={controls}
|
||||
initial={{ opacity: 1, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
exit={{ opacity: 0, scale: 0.9 }}
|
||||
className="relative"
|
||||
<div
|
||||
style={style}
|
||||
ref={setNodeRef}
|
||||
className="relative touch-none"
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
>
|
||||
<Badge
|
||||
variant="outline"
|
||||
className={cn(
|
||||
"flex h-7 cursor-grab select-none items-center gap-0 overflow-hidden px-3 active:cursor-grabbing",
|
||||
isEditing && "border-primary ring-1 ring-primary",
|
||||
isDragging && "opacity-80",
|
||||
)}
|
||||
onPointerDown={(e) => {
|
||||
e.preventDefault();
|
||||
controls.start(e);
|
||||
}}
|
||||
>
|
||||
<span className="max-w-32 truncate">{chip}</span>
|
||||
|
||||
<motion.div
|
||||
initial={false}
|
||||
animate={{ width: isHovered ? 40 : 0, marginInlineStart: isHovered ? 8 : 0, opacity: isHovered ? 1 : 0 }}
|
||||
@@ -80,7 +99,7 @@ function ChipItem({ chip, index, isEditing, onEdit, onRemove }: ChipItemProps) {
|
||||
</button>
|
||||
</motion.div>
|
||||
</Badge>
|
||||
</Reorder.Item>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -116,7 +135,6 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
|
||||
const trimmed = newValue.trim();
|
||||
if (!trimmed || index < 0 || index >= chips.length) return;
|
||||
|
||||
// Check if the new value already exists at a different index
|
||||
const existingIndex = chips.findIndex((c, i) => c === trimmed && i !== index);
|
||||
if (existingIndex !== -1) return;
|
||||
|
||||
@@ -133,12 +151,10 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
|
||||
const newChips = chips.slice(0, index).concat(chips.slice(index + 1));
|
||||
setChips(newChips);
|
||||
|
||||
// If we were editing this chip, clear edit mode
|
||||
if (editingIndex === index) {
|
||||
setEditingIndex(null);
|
||||
setInput("");
|
||||
} else if (editingIndex !== null && editingIndex > index) {
|
||||
// Adjust editing index if we removed a chip before it
|
||||
setEditingIndex(editingIndex - 1);
|
||||
}
|
||||
},
|
||||
@@ -156,7 +172,6 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
|
||||
|
||||
const handleReorder = React.useCallback(
|
||||
(newOrder: string[]) => {
|
||||
// Update editingIndex to follow the chip being edited
|
||||
if (editingIndex !== null) {
|
||||
const editingChip = chips[editingIndex];
|
||||
const newIndex = newOrder.indexOf(editingChip);
|
||||
@@ -169,14 +184,37 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
|
||||
[chips, editingIndex, setChips],
|
||||
);
|
||||
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor, {
|
||||
activationConstraint: { distance: 3 },
|
||||
}),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
}),
|
||||
);
|
||||
|
||||
const handleDragEnd = React.useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const { active, over } = event;
|
||||
if (!over || active.id === over.id) return;
|
||||
const oldIndex = chips.indexOf(active.id as string);
|
||||
const newIndex = chips.indexOf(over.id as string);
|
||||
if (oldIndex !== -1 && newIndex !== -1 && oldIndex !== newIndex) {
|
||||
const newOrder = Array.from(chips);
|
||||
const [removed] = newOrder.splice(oldIndex, 1);
|
||||
newOrder.splice(newIndex, 0, removed);
|
||||
handleReorder(newOrder);
|
||||
}
|
||||
},
|
||||
[chips, handleReorder],
|
||||
);
|
||||
|
||||
const handleInputChange = React.useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const newValue = e.target.value;
|
||||
|
||||
// When editing, don't split on comma - allow full text editing
|
||||
if (editingIndex !== null) {
|
||||
if (newValue.includes(",")) {
|
||||
// Save current edit
|
||||
updateChip(editingIndex, newValue.replace(",", ""));
|
||||
setEditingIndex(null);
|
||||
setInput("");
|
||||
@@ -186,7 +224,6 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
|
||||
return;
|
||||
}
|
||||
|
||||
// When adding new chips, split on comma
|
||||
if (newValue.includes(",")) {
|
||||
const parts = newValue.split(",");
|
||||
parts.slice(0, -1).forEach(addChip);
|
||||
@@ -204,19 +241,16 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
|
||||
e.preventDefault();
|
||||
|
||||
if (editingIndex !== null) {
|
||||
// Save edit
|
||||
if (input.trim()) {
|
||||
updateChip(editingIndex, input);
|
||||
}
|
||||
setEditingIndex(null);
|
||||
setInput("");
|
||||
} else if (input.trim()) {
|
||||
// Add new chip
|
||||
addChip(input);
|
||||
setInput("");
|
||||
}
|
||||
} else if (e.key === "Escape" && editingIndex !== null) {
|
||||
// Cancel edit
|
||||
setEditingIndex(null);
|
||||
setInput("");
|
||||
}
|
||||
@@ -227,18 +261,23 @@ export function ChipInput({ value, defaultValue = [], onChange, className, ...pr
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-2", className)} {...props}>
|
||||
{chips.length > 0 && (
|
||||
<Reorder.Group axis="x" values={chips} onReorder={handleReorder} className="flex flex-wrap gap-1.5">
|
||||
{chips.map((chip, idx) => (
|
||||
<ChipItem
|
||||
key={`${chip}-${idx}`}
|
||||
chip={chip}
|
||||
index={idx}
|
||||
isEditing={editingIndex === idx}
|
||||
onEdit={handleEdit}
|
||||
onRemove={removeChip}
|
||||
/>
|
||||
))}
|
||||
</Reorder.Group>
|
||||
<DndContext sensors={sensors} collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
|
||||
<SortableContext items={chips} strategy={horizontalListSortingStrategy}>
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{chips.map((chip, idx) => (
|
||||
<ChipItem
|
||||
key={`${chip}-${idx}`}
|
||||
id={chip}
|
||||
chip={chip}
|
||||
index={idx}
|
||||
isEditing={editingIndex === idx}
|
||||
onEdit={handleEdit}
|
||||
onRemove={removeChip}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
|
||||
@@ -167,11 +167,11 @@
|
||||
}
|
||||
|
||||
strong {
|
||||
font-weight: var(--page-body-font-weight-bold);
|
||||
font-weight: var(--page-body-font-weight-bold, bold);
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: var(--page-body-font-weight-bold);
|
||||
font-weight: var(--page-body-font-weight-bold, bold);
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 0.15rem;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,8 @@ export function BreakpointIndicator({ position = "bottom-right" }: BreakpointInd
|
||||
const isBottom = position.includes("bottom");
|
||||
|
||||
const top = isTop ? "top-0" : "bottom-0";
|
||||
const right = isRight ? "end-0" : "start-0";
|
||||
const left = isLeft ? "start-0" : "end-0";
|
||||
const right = isRight ? "inset-e-0" : "inset-s-0";
|
||||
const left = isLeft ? "inset-s-0" : "inset-e-0";
|
||||
const bottom = isBottom ? "bottom-0" : "top-0";
|
||||
|
||||
return (
|
||||
|
||||
@@ -147,7 +147,7 @@ function PageContainer({ pageIndex, pageLayout, pageClassName, showPageNumbers =
|
||||
return (
|
||||
<div data-page-index={pageIndex} className="relative">
|
||||
{showPageNumbers && totalNumberOfPages > 1 && (
|
||||
<div className="absolute start-0 -top-6 print:hidden">
|
||||
<div className="absolute inset-s-0 -top-6 print:hidden">
|
||||
<span className="font-medium text-foreground text-xs">
|
||||
<Trans>
|
||||
Page {pageNumber} of {totalNumberOfPages}
|
||||
@@ -161,7 +161,7 @@ function PageContainer({ pageIndex, pageLayout, pageClassName, showPageNumbers =
|
||||
</div>
|
||||
|
||||
{metadata.page.format !== "free-form" && pageHeight > maxPageHeight && (
|
||||
<div className="absolute start-0 top-full mt-4 print:hidden">
|
||||
<div className="absolute inset-s-0 top-full mt-4 print:hidden">
|
||||
<a
|
||||
rel="noopener"
|
||||
target="_blank"
|
||||
|
||||
@@ -174,7 +174,7 @@ export function getSectionComponent(
|
||||
|
||||
return (
|
||||
<section className={cn(`page-section page-section-custom page-section-${id}`, sectionClassName)}>
|
||||
{customSection.type !== "summary" && customSection.type !== "cover-letter" && (
|
||||
{customSection.type !== "cover-letter" && (
|
||||
<h6 className="mb-1.5 text-(--page-primary-color)">{customSection.title}</h6>
|
||||
)}
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ export function ChikoritaTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
<div className="template-chikorita page-content">
|
||||
{/* Sidebar Background */}
|
||||
{!fullWidth && (
|
||||
<div className="page-sidebar-background pointer-events-none absolute inset-y-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color) ltr:end-0 rtl:start-0" />
|
||||
<div className="page-sidebar-background pointer-events-none absolute inset-y-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color) ltr:inset-e-0 rtl:inset-s-0" />
|
||||
)}
|
||||
|
||||
<div className="flex">
|
||||
|
||||
@@ -37,7 +37,7 @@ export function DitgarTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
<div className="template-ditgar page-content">
|
||||
{/* Sidebar Background */}
|
||||
{(!fullWidth || isFirstPage) && (
|
||||
<div className="page-sidebar-background pointer-events-none absolute inset-y-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)/20 ltr:start-0 rtl:end-0" />
|
||||
<div className="page-sidebar-background pointer-events-none absolute inset-y-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)/20 ltr:inset-s-0 rtl:inset-e-0" />
|
||||
)}
|
||||
|
||||
<div className="flex">
|
||||
|
||||
@@ -27,7 +27,7 @@ export function GengarTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
<div className="template-gengar page-content">
|
||||
{/* Sidebar Background */}
|
||||
{(!fullWidth || isFirstPage) && (
|
||||
<div className="page-sidebar-background pointer-events-none absolute inset-y-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)/20 ltr:start-0 rtl:end-0" />
|
||||
<div className="page-sidebar-background pointer-events-none absolute inset-y-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)/20 ltr:inset-s-0 rtl:inset-e-0" />
|
||||
)}
|
||||
|
||||
<div className="flex">
|
||||
|
||||
@@ -27,7 +27,7 @@ export function GlalieTemplate({ pageIndex, pageLayout }: TemplateProps) {
|
||||
<div className="template-glalie page-content">
|
||||
{/* Sidebar Background */}
|
||||
{(!fullWidth || isFirstPage) && (
|
||||
<div className="page-sidebar-background pointer-events-none absolute inset-y-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)/20 ltr:start-0 rtl:end-0" />
|
||||
<div className="page-sidebar-background pointer-events-none absolute inset-y-0 z-0 w-(--page-sidebar-width) shrink-0 bg-(--page-primary-color)/20 ltr:inset-s-0 rtl:inset-e-0" />
|
||||
)}
|
||||
|
||||
<div className="flex">
|
||||
|
||||
@@ -14,6 +14,9 @@ const sectionClassName = cn(
|
||||
|
||||
// Section Heading
|
||||
"[&>h6]:-mt-(--heading-negative-margin) [&>h6]:max-w-fit [&>h6]:bg-(--page-background-color) [&>h6]:px-4",
|
||||
|
||||
// Push the first section of a page down, to avoid clipping the header
|
||||
"group-data-[layout=main]:first-of-type:mt-4",
|
||||
);
|
||||
|
||||
/**
|
||||
|
||||
@@ -49,7 +49,7 @@ function AlertDescription({ className, ...props }: React.ComponentProps<"div">)
|
||||
}
|
||||
|
||||
function AlertAction({ className, ...props }: React.ComponentProps<"div">) {
|
||||
return <div data-slot="alert-action" className={cn("absolute end-3 top-2.5", className)} {...props} />;
|
||||
return <div data-slot="alert-action" className={cn("absolute inset-e-3 top-2.5", className)} {...props} />;
|
||||
}
|
||||
|
||||
export { Alert, AlertTitle, AlertDescription, AlertAction };
|
||||
|
||||
@@ -50,7 +50,7 @@ function AvatarBadge({ className, ...props }: React.ComponentProps<"span">) {
|
||||
<span
|
||||
data-slot="avatar-badge"
|
||||
className={cn(
|
||||
"absolute end-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background",
|
||||
"absolute inset-e-0 bottom-0 z-10 inline-flex select-none items-center justify-center rounded-full bg-primary text-primary-foreground bg-blend-color ring-2 ring-background",
|
||||
"group-data-[size=sm]/avatar:size-2 group-data-[size=sm]/avatar:[&>svg]:hidden",
|
||||
"group-data-[size=default]/avatar:size-2.5 group-data-[size=default]/avatar:[&>svg]:size-2",
|
||||
"group-data-[size=lg]/avatar:size-3 group-data-[size=lg]/avatar:[&>svg]:size-2",
|
||||
|
||||
@@ -129,7 +129,7 @@ function ContextMenuCheckboxItem({
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute end-2">
|
||||
<span className="pointer-events-none absolute inset-e-2">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
@@ -153,7 +153,7 @@ function ContextMenuRadioItem({
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute end-2">
|
||||
<span className="pointer-events-none absolute inset-e-2">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
|
||||
@@ -102,7 +102,7 @@ function DropdownMenuCheckboxItem({ className, children, checked, disabled, ...p
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
|
||||
<span className="pointer-events-none absolute inset-s-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicatorPrimitive initial={{ opacity: 0, scale: 0.5 }} animate={{ opacity: 1, scale: 1 }}>
|
||||
<CheckIcon className="size-4" />
|
||||
</DropdownMenuItemIndicatorPrimitive>
|
||||
@@ -131,7 +131,7 @@ function DropdownMenuRadioItem({ className, children, disabled, ...props }: Drop
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="pointer-events-none absolute start-2 flex size-3.5 items-center justify-center">
|
||||
<span className="pointer-events-none absolute inset-s-2 flex size-3.5 items-center justify-center">
|
||||
<DropdownMenuItemIndicatorPrimitive layoutId="dropdown-menu-item-indicator-radio">
|
||||
<CircleIcon className="size-2 fill-current" />
|
||||
</DropdownMenuItemIndicatorPrimitive>
|
||||
|
||||
@@ -27,7 +27,7 @@ function ResizableSeparator({
|
||||
<ResizablePrimitive.Separator
|
||||
data-slot="resizable-handle"
|
||||
className={cn(
|
||||
"relative flex w-px items-center justify-center bg-transparent transition-colors after:absolute after:inset-y-0 after:start-1/2 after:w-1 after:-translate-x-1/2 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[separator='active']:bg-sky-700 data-[separator='hover']:bg-sky-700 data-[panel-group-direction=vertical]:after:start-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||
"relative flex w-px items-center justify-center bg-transparent transition-colors after:absolute after:inset-s-1/2 after:inset-y-0 after:w-1 after:-translate-x-1/2 focus-visible:outline-hidden data-[panel-group-direction=vertical]:h-px data-[panel-group-direction=vertical]:w-full data-[separator='active']:bg-sky-700 data-[separator='hover']:bg-sky-700 data-[panel-group-direction=vertical]:after:inset-s-0 data-[panel-group-direction=vertical]:after:h-1 data-[panel-group-direction=vertical]:after:w-full data-[panel-group-direction=vertical]:after:translate-x-0 data-[panel-group-direction=vertical]:after:-translate-y-1/2 [&[data-panel-group-direction=vertical]>div]:rotate-90",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -50,7 +50,7 @@ function SheetContent({
|
||||
data-slot="sheet-content"
|
||||
data-side={side}
|
||||
className={cn(
|
||||
"data-[side=right]:data-closed:slide-out-to-end-10 data-[side=right]:data-open:slide-in-from-end-10 data-[side=left]:data-closed:slide-out-to-start-10 data-[side=left]:data-open:slide-in-from-start-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col gap-4 bg-background bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=left]:start-0 data-[side=right]:end-0 data-[side=top]:top-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-closed:animate-out data-open:animate-in data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
|
||||
"data-[side=right]:data-closed:slide-out-to-end-10 data-[side=right]:data-open:slide-in-from-end-10 data-[side=left]:data-closed:slide-out-to-start-10 data-[side=left]:data-open:slide-in-from-start-10 data-[side=top]:data-closed:slide-out-to-top-10 data-[side=top]:data-open:slide-in-from-top-10 data-closed:fade-out-0 data-open:fade-in-0 data-[side=bottom]:data-closed:slide-out-to-bottom-10 data-[side=bottom]:data-open:slide-in-from-bottom-10 fixed z-50 flex flex-col gap-4 bg-background bg-clip-padding text-sm shadow-lg transition duration-200 ease-in-out data-[side=left]:inset-s-0 data-[side=right]:inset-e-0 data-[side=bottom]:inset-x-0 data-[side=top]:inset-x-0 data-[side=left]:inset-y-0 data-[side=right]:inset-y-0 data-[side=top]:top-0 data-[side=bottom]:bottom-0 data-[side=bottom]:h-auto data-[side=left]:h-full data-[side=right]:h-full data-[side=top]:h-auto data-[side=left]:w-3/4 data-[side=right]:w-3/4 data-closed:animate-out data-open:animate-in data-[side=bottom]:border-t data-[side=left]:border-r data-[side=top]:border-b data-[side=right]:border-l data-[side=left]:sm:max-w-sm data-[side=right]:sm:max-w-sm",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -58,7 +58,7 @@ function SheetContent({
|
||||
{children}
|
||||
{showCloseButton && (
|
||||
<SheetPrimitive.Close data-slot="sheet-close" asChild>
|
||||
<Button variant="ghost" className="absolute end-4 top-4" size="icon-sm">
|
||||
<Button variant="ghost" className="absolute inset-e-4 top-4" size="icon-sm">
|
||||
<XIcon />
|
||||
<span className="sr-only">Close</span>
|
||||
</Button>
|
||||
|
||||
@@ -203,8 +203,8 @@ function Sidebar({
|
||||
className={cn(
|
||||
"fixed inset-y-0 z-10 hidden h-svh w-(--sidebar-width) transition-[left,right,width] ease-in-out md:flex",
|
||||
side === "left"
|
||||
? "start-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
||||
: "end-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
||||
? "inset-s-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]"
|
||||
: "inset-e-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]",
|
||||
variant === "floating" || variant === "inset"
|
||||
? "p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)+(--spacing(4))+2px)]"
|
||||
: "group-data-[collapsible=icon]:w-(--sidebar-width-icon) group-data-[side=left]:border-r group-data-[side=right]:border-l",
|
||||
@@ -258,12 +258,12 @@ function SidebarRail({ className, ...props }: React.ComponentProps<"button">) {
|
||||
onClick={toggleSidebar}
|
||||
title="Toggle Sidebar"
|
||||
className={cn(
|
||||
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-in-out after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=right]:start-0 group-data-[side=left]:-end-4 sm:flex",
|
||||
"absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-in-out after:absolute after:inset-s-1/2 after:inset-y-0 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-inset-e-4 group-data-[side=right]:inset-s-0 sm:flex",
|
||||
"in-data-[side=left]:cursor-w-resize in-data-[side=right]:cursor-e-resize",
|
||||
"[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize",
|
||||
"group-data-[collapsible=offcanvas]:translate-x-0 hover:group-data-[collapsible=offcanvas]:bg-sidebar group-data-[collapsible=offcanvas]:after:left-full",
|
||||
"[[data-side=left][data-collapsible=offcanvas]_&]:-end-2",
|
||||
"[[data-side=right][data-collapsible=offcanvas]_&]:-start-2",
|
||||
"[[data-side=left][data-collapsible=offcanvas]_&]:-inset-e-2",
|
||||
"[[data-side=right][data-collapsible=offcanvas]_&]:-inset-s-2",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -387,7 +387,7 @@ function SidebarGroupAction({
|
||||
data-slot="sidebar-group-action"
|
||||
data-sidebar="group-action"
|
||||
className={cn(
|
||||
"absolute end-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"absolute inset-e-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"after:absolute after:-inset-2 md:after:hidden",
|
||||
"group-data-[collapsible=icon]:hidden",
|
||||
className,
|
||||
@@ -512,7 +512,7 @@ function SidebarMenuAction({
|
||||
data-slot="sidebar-menu-action"
|
||||
data-sidebar="menu-action"
|
||||
className={cn(
|
||||
"absolute end-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"absolute inset-e-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-hidden ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0",
|
||||
"after:absolute after:-inset-2 md:after:hidden",
|
||||
"peer-data-[size=sm]/menu-button:top-1",
|
||||
"peer-data-[size=default]/menu-button:top-1.5",
|
||||
@@ -533,7 +533,7 @@ function SidebarMenuBadge({ className, ...props }: React.ComponentProps<"div">)
|
||||
data-slot="sidebar-menu-badge"
|
||||
data-sidebar="menu-badge"
|
||||
className={cn(
|
||||
"pointer-events-none absolute end-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums",
|
||||
"pointer-events-none absolute inset-e-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 font-medium text-sidebar-foreground text-xs tabular-nums",
|
||||
"peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground",
|
||||
"peer-data-[size=sm]/menu-button:top-1",
|
||||
"peer-data-[size=default]/menu-button:top-1.5",
|
||||
|
||||
@@ -34,7 +34,7 @@ function Switch({ className, pressedWidth = 20, startIcon, endIcon, thumbIcon, .
|
||||
{thumbIcon && (
|
||||
<SwitchIconPrimitive
|
||||
position="thumb"
|
||||
className="-translate-1/2 absolute start-1/2 top-1/2 text-neutral-400 dark:text-neutral-500 [&_svg]:size-[9px]"
|
||||
className="-translate-1/2 absolute inset-s-1/2 top-1/2 text-neutral-400 dark:text-neutral-500 [&_svg]:size-[9px]"
|
||||
>
|
||||
{thumbIcon}
|
||||
</SwitchIconPrimitive>
|
||||
@@ -44,7 +44,7 @@ function Switch({ className, pressedWidth = 20, startIcon, endIcon, thumbIcon, .
|
||||
{startIcon && (
|
||||
<SwitchIconPrimitive
|
||||
position="left"
|
||||
className="absolute start-0.5 top-1/2 -translate-y-1/2 text-neutral-400 dark:text-neutral-500 [&_svg]:size-[9px]"
|
||||
className="absolute inset-s-0.5 top-1/2 -translate-y-1/2 text-neutral-400 dark:text-neutral-500 [&_svg]:size-[9px]"
|
||||
>
|
||||
{startIcon}
|
||||
</SwitchIconPrimitive>
|
||||
@@ -52,7 +52,7 @@ function Switch({ className, pressedWidth = 20, startIcon, endIcon, thumbIcon, .
|
||||
{endIcon && (
|
||||
<SwitchIconPrimitive
|
||||
position="right"
|
||||
className="absolute end-0.5 top-1/2 -translate-y-1/2 text-neutral-500 dark:text-neutral-400 [&_svg]:size-[9px]"
|
||||
className="absolute inset-e-0.5 top-1/2 -translate-y-1/2 text-neutral-500 dark:text-neutral-400 [&_svg]:size-[9px]"
|
||||
>
|
||||
{endIcon}
|
||||
</SwitchIconPrimitive>
|
||||
|
||||
@@ -52,7 +52,7 @@ function TabsTrigger({ className, ...props }: React.ComponentProps<typeof TabsPr
|
||||
"relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 whitespace-nowrap rounded-full border border-transparent px-3 py-1 font-medium text-foreground/60 text-sm transition-all hover:text-foreground focus-visible:border-ring focus-visible:outline-1 focus-visible:outline-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg:not([class*='size-'])]:size-4 [&_svg]:pointer-events-none [&_svg]:shrink-0",
|
||||
"group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
|
||||
"data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
|
||||
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-end-1 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
||||
"after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=vertical]/tabs:after:-inset-e-1 group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
import type { MessageDescriptor } from "@lingui/core";
|
||||
import { msg } from "@lingui/core/macro";
|
||||
import { useLingui } from "@lingui/react";
|
||||
import { Trans } from "@lingui/react/macro";
|
||||
import { PencilSimpleLineIcon, PlusIcon } from "@phosphor-icons/react";
|
||||
import { useForm, useFormContext } from "react-hook-form";
|
||||
import type z from "zod";
|
||||
import { useResumeStore } from "@/components/resume/store/resume";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Combobox } from "@/components/ui/combobox";
|
||||
import { DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
@@ -18,21 +22,21 @@ const formSchema = customSectionSchema;
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
const SECTION_TYPE_OPTIONS: { value: CustomSectionType; label: string }[] = [
|
||||
{ value: "summary", label: "Summary" },
|
||||
{ value: "experience", label: "Experience" },
|
||||
{ value: "education", label: "Education" },
|
||||
{ value: "projects", label: "Projects" },
|
||||
{ value: "profiles", label: "Profiles" },
|
||||
{ value: "skills", label: "Skills" },
|
||||
{ value: "languages", label: "Languages" },
|
||||
{ value: "interests", label: "Interests" },
|
||||
{ value: "awards", label: "Awards" },
|
||||
{ value: "certifications", label: "Certifications" },
|
||||
{ value: "publications", label: "Publications" },
|
||||
{ value: "volunteer", label: "Volunteer" },
|
||||
{ value: "references", label: "References" },
|
||||
{ value: "cover-letter", label: "Cover Letter" },
|
||||
const SECTION_TYPE_OPTIONS: { value: CustomSectionType; label: MessageDescriptor }[] = [
|
||||
{ value: "summary", label: msg`Summary` },
|
||||
{ value: "experience", label: msg`Experience` },
|
||||
{ value: "education", label: msg`Education` },
|
||||
{ value: "projects", label: msg`Projects` },
|
||||
{ value: "profiles", label: msg`Profiles` },
|
||||
{ value: "skills", label: msg`Skills` },
|
||||
{ value: "languages", label: msg`Languages` },
|
||||
{ value: "interests", label: msg`Interests` },
|
||||
{ value: "awards", label: msg`Awards` },
|
||||
{ value: "certifications", label: msg`Certifications` },
|
||||
{ value: "publications", label: msg`Publications` },
|
||||
{ value: "volunteer", label: msg`Volunteer` },
|
||||
{ value: "references", label: msg`References` },
|
||||
{ value: "cover-letter", label: msg`Cover Letter` },
|
||||
];
|
||||
|
||||
export function CreateCustomSectionDialog({ data }: DialogProps<"resume.sections.custom.create">) {
|
||||
@@ -150,6 +154,7 @@ export function UpdateCustomSectionDialog({ data }: DialogProps<"resume.sections
|
||||
}
|
||||
|
||||
function CustomSectionForm({ isUpdate = false }: { isUpdate?: boolean }) {
|
||||
const { i18n } = useLingui();
|
||||
const form = useFormContext<FormValues>();
|
||||
|
||||
return (
|
||||
@@ -179,17 +184,16 @@ function CustomSectionForm({ isUpdate = false }: { isUpdate?: boolean }) {
|
||||
<Trans>Section Type</Trans>
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<select
|
||||
<Combobox
|
||||
{...field}
|
||||
value={field.value}
|
||||
disabled={isUpdate}
|
||||
className="flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-xs transition-colors file:border-0 file:bg-transparent file:font-medium file:text-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
{SECTION_TYPE_OPTIONS.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
onValueChange={field.onChange}
|
||||
options={SECTION_TYPE_OPTIONS.map((option) => ({
|
||||
value: option.value,
|
||||
label: i18n.t(option.label),
|
||||
}))}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
@@ -3,7 +3,6 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
import { betterAuth } from "better-auth/minimal";
|
||||
import { apiKey, type GenericOAuthConfig, genericOAuth, openAPI, twoFactor } from "better-auth/plugins";
|
||||
import { username } from "better-auth/plugins/username";
|
||||
import { tanstackStartCookies } from "better-auth/tanstack-start";
|
||||
import { and, eq, or } from "drizzle-orm";
|
||||
import { db } from "@/integrations/drizzle/client";
|
||||
import { env } from "@/utils/env";
|
||||
@@ -20,6 +19,24 @@ function isCustomOAuthProviderEnabled() {
|
||||
return Boolean(env.OAUTH_CLIENT_ID) && Boolean(env.OAUTH_CLIENT_SECRET) && (hasDiscovery || hasManual);
|
||||
}
|
||||
|
||||
function getTrustedOrigins(): string[] {
|
||||
const appUrl = new URL(env.APP_URL);
|
||||
const trustedOrigins = new Set<string>([appUrl.origin.replace(/\/$/, "")]);
|
||||
const LOCAL_ORIGINS = ["localhost", "127.0.0.1"];
|
||||
|
||||
if (LOCAL_ORIGINS.includes(appUrl.hostname)) {
|
||||
for (const hostname of LOCAL_ORIGINS) {
|
||||
if (hostname !== appUrl.hostname) {
|
||||
const altUrl = new URL(env.APP_URL);
|
||||
altUrl.hostname = hostname;
|
||||
trustedOrigins.add(altUrl.origin.replace(/\/$/, ""));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(trustedOrigins);
|
||||
}
|
||||
|
||||
const getAuthConfig = () => {
|
||||
const authConfigs: GenericOAuthConfig[] = [];
|
||||
|
||||
@@ -69,7 +86,7 @@ const getAuthConfig = () => {
|
||||
database: drizzleAdapter(db, { schema, provider: "pg" }),
|
||||
|
||||
telemetry: { enabled: false },
|
||||
trustedOrigins: [env.APP_URL],
|
||||
trustedOrigins: getTrustedOrigins(),
|
||||
advanced: {
|
||||
database: { generateId },
|
||||
useSecureCookies: env.APP_URL.startsWith("https://"),
|
||||
@@ -229,7 +246,6 @@ const getAuthConfig = () => {
|
||||
}),
|
||||
twoFactor({ issuer: "Reactive Resume" }),
|
||||
genericOAuth({ config: authConfigs }),
|
||||
tanstackStartCookies(),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
@@ -24,7 +24,7 @@ export const user = pg.pgTable(
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
},
|
||||
(t) => [pg.index().on(t.email), pg.index().on(t.username)],
|
||||
(t) => [pg.index().on(t.createdAt.asc())],
|
||||
);
|
||||
|
||||
export const session = pg.pgTable(
|
||||
@@ -84,26 +84,22 @@ export const account = pg.pgTable(
|
||||
(t) => [pg.index().on(t.userId)],
|
||||
);
|
||||
|
||||
export const verification = pg.pgTable(
|
||||
"verification",
|
||||
{
|
||||
id: pg
|
||||
.uuid("id")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => generateId()),
|
||||
identifier: pg.text("identifier").notNull().unique(),
|
||||
value: pg.text("value").notNull(),
|
||||
expiresAt: pg.timestamp("expires_at", { withTimezone: true }).notNull(),
|
||||
createdAt: pg.timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: pg
|
||||
.timestamp("updated_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
},
|
||||
(t) => [pg.index().on(t.identifier)],
|
||||
);
|
||||
export const verification = pg.pgTable("verification", {
|
||||
id: pg
|
||||
.uuid("id")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => generateId()),
|
||||
identifier: pg.text("identifier").notNull().unique(),
|
||||
value: pg.text("value").notNull(),
|
||||
expiresAt: pg.timestamp("expires_at", { withTimezone: true }).notNull(),
|
||||
createdAt: pg.timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: pg
|
||||
.timestamp("updated_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
});
|
||||
|
||||
export const twoFactor = pg.pgTable(
|
||||
"two_factor",
|
||||
@@ -192,37 +188,34 @@ export const resume = pg.pgTable(
|
||||
(t) => [
|
||||
pg.unique().on(t.slug, t.userId),
|
||||
pg.index().on(t.userId),
|
||||
pg.index().on(t.createdAt.asc()),
|
||||
pg.index().on(t.userId, t.updatedAt.desc()),
|
||||
pg.index().on(t.isPublic, t.slug, t.userId),
|
||||
],
|
||||
);
|
||||
|
||||
export const resumeStatistics = pg.pgTable(
|
||||
"resume_statistics",
|
||||
{
|
||||
id: pg
|
||||
.uuid("id")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => generateId()),
|
||||
views: pg.integer("views").notNull().default(0),
|
||||
downloads: pg.integer("downloads").notNull().default(0),
|
||||
lastViewedAt: pg.timestamp("last_viewed_at", { withTimezone: true }),
|
||||
lastDownloadedAt: pg.timestamp("last_downloaded_at", { withTimezone: true }),
|
||||
resumeId: pg
|
||||
.uuid("resume_id")
|
||||
.unique()
|
||||
.notNull()
|
||||
.references(() => resume.id, { onDelete: "cascade" }),
|
||||
createdAt: pg.timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: pg
|
||||
.timestamp("updated_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
},
|
||||
(t) => [pg.index().on(t.resumeId)],
|
||||
);
|
||||
export const resumeStatistics = pg.pgTable("resume_statistics", {
|
||||
id: pg
|
||||
.uuid("id")
|
||||
.notNull()
|
||||
.primaryKey()
|
||||
.$defaultFn(() => generateId()),
|
||||
views: pg.integer("views").notNull().default(0),
|
||||
downloads: pg.integer("downloads").notNull().default(0),
|
||||
lastViewedAt: pg.timestamp("last_viewed_at", { withTimezone: true }),
|
||||
lastDownloadedAt: pg.timestamp("last_downloaded_at", { withTimezone: true }),
|
||||
resumeId: pg
|
||||
.uuid("resume_id")
|
||||
.unique()
|
||||
.notNull()
|
||||
.references(() => resume.id, { onDelete: "cascade" }),
|
||||
createdAt: pg.timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: pg
|
||||
.timestamp("updated_at", { withTimezone: true })
|
||||
.notNull()
|
||||
.defaultNow()
|
||||
.$onUpdate(() => /* @__PURE__ */ new Date()),
|
||||
});
|
||||
|
||||
export const apikey = pg.pgTable(
|
||||
"apikey",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { createORPCClient, onError } from "@orpc/client";
|
||||
import { RPCLink } from "@orpc/client/fetch";
|
||||
import { BatchLinkPlugin, SimpleCsrfProtectionLinkPlugin } from "@orpc/client/plugins";
|
||||
import { BatchLinkPlugin } from "@orpc/client/plugins";
|
||||
import { createRouterClient, type InferRouterInputs, type InferRouterOutputs, type RouterClient } from "@orpc/server";
|
||||
import { createTanstackQueryUtils } from "@orpc/tanstack-query";
|
||||
import { createIsomorphicFn } from "@tanstack/react-start";
|
||||
@@ -32,7 +32,6 @@ export const getORPCClient = createIsomorphicFn()
|
||||
url: `${window.location.origin}/api/rpc`,
|
||||
fetch: (request, init) => fetch(request, { ...init, credentials: "include" }),
|
||||
plugins: [
|
||||
new SimpleCsrfProtectionLinkPlugin(),
|
||||
new BatchLinkPlugin({
|
||||
mode: typeof window === "undefined" ? "buffered" : "streaming",
|
||||
groups: [{ condition: () => true, context: {} }],
|
||||
|
||||
@@ -78,7 +78,7 @@ export const storageRouter = {
|
||||
},
|
||||
})
|
||||
.handler(async ({ context, input }): Promise<void> => {
|
||||
// The filename is now the full path from the URL (e.g., "uploads/userId/pictures/timestamp.webp")
|
||||
// The filename is now the full path from the URL (e.g., "uploads/userId/pictures/timestamp.ext")
|
||||
// We need to extract just the path portion that matches the storage key
|
||||
const key = input.filename.startsWith("uploads/")
|
||||
? input.filename
|
||||
|
||||
@@ -173,7 +173,7 @@ export const printerService = {
|
||||
? getComputedStyle(container).getPropertyValue("--page-height").trim()
|
||||
: null;
|
||||
const currentHeight = containerHeight || rootHeight;
|
||||
const heightValue = Math.max(Number.parseFloat(currentHeight), minPageHeight);
|
||||
const heightValue = Math.min(Number.parseFloat(currentHeight), minPageHeight);
|
||||
|
||||
if (!Number.isNaN(heightValue)) {
|
||||
// Subtract top + bottom margins from page height
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
PutObjectCommand,
|
||||
S3Client,
|
||||
} from "@aws-sdk/client-s3";
|
||||
import sharp from "sharp";
|
||||
import { env } from "@/utils/env";
|
||||
|
||||
interface StorageWriteInput {
|
||||
@@ -90,6 +89,16 @@ interface ProcessedImage {
|
||||
export async function processImageForUpload(file: File): Promise<ProcessedImage> {
|
||||
const fileBuffer = await file.arrayBuffer();
|
||||
|
||||
console.log("FLAG_DISABLE_IMAGE_PROCESSING", env.FLAG_DISABLE_IMAGE_PROCESSING);
|
||||
if (env.FLAG_DISABLE_IMAGE_PROCESSING) {
|
||||
return {
|
||||
data: new Uint8Array(fileBuffer),
|
||||
contentType: file.type,
|
||||
};
|
||||
}
|
||||
|
||||
const sharp = (await import("sharp")).default;
|
||||
|
||||
const processedBuffer = await sharp(fileBuffer)
|
||||
.resize(800, 800, { fit: "inside", withoutEnlargement: true })
|
||||
.webp({ preset: "picture" })
|
||||
|
||||
@@ -16,18 +16,14 @@ import { cn } from "@/utils/style";
|
||||
export const Route = createFileRoute("/$username/$slug")({
|
||||
component: RouteComponent,
|
||||
loader: async ({ context, params: { username, slug } }) => {
|
||||
try {
|
||||
// Ignore .well-known requests
|
||||
if (username === ".well-known") throw notFound();
|
||||
// Ignore .well-known requests
|
||||
if (username === ".well-known") throw notFound();
|
||||
|
||||
const resume = await context.queryClient.ensureQueryData(
|
||||
orpc.resume.getBySlug.queryOptions({ input: { username, slug } }),
|
||||
);
|
||||
const resume = await context.queryClient.ensureQueryData(
|
||||
orpc.resume.getBySlug.queryOptions({ input: { username, slug } }),
|
||||
);
|
||||
|
||||
return { resume };
|
||||
} catch {
|
||||
throw notFound();
|
||||
}
|
||||
return { resume };
|
||||
},
|
||||
head: ({ loaderData }) => ({
|
||||
meta: [{ title: loaderData ? `${loaderData.resume.name} - Reactive Resume` : "Reactive Resume" }],
|
||||
@@ -69,7 +65,7 @@ function RouteComponent() {
|
||||
const handleDownload = useCallback(async () => {
|
||||
if (!resume) return;
|
||||
const { url } = await printResumeAsPDF({ id: resume.id });
|
||||
downloadFromUrl(url, `resume-${resume.name}.pdf`);
|
||||
downloadFromUrl(url, `${resume.name}.pdf`);
|
||||
}, [resume, printResumeAsPDF]);
|
||||
|
||||
if (!isReady) return <LoadingScreen />;
|
||||
@@ -79,14 +75,14 @@ function RouteComponent() {
|
||||
<div
|
||||
className={cn("mx-auto max-w-[210mm]", "print:m-0 print:block print:max-w-full print:px-0", "md:my-4 md:px-4")}
|
||||
>
|
||||
<ResumePreview pageClassName="print:w-full! w-full max-w-full" />
|
||||
<ResumePreview className="space-y-4" pageClassName="print:w-full! w-full max-w-full" />
|
||||
</div>
|
||||
|
||||
<Button
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
disabled={isPrinting}
|
||||
className="fixed end-4 bottom-4 z-50 hidden rounded-full px-4 md:inline-flex print:hidden"
|
||||
className="fixed inset-e-4 bottom-4 z-50 hidden rounded-full px-4 md:inline-flex print:hidden"
|
||||
onClick={handleDownload}
|
||||
>
|
||||
{isPrinting ? <Spinner /> : <DownloadSimpleIcon />}
|
||||
|
||||
@@ -131,8 +131,8 @@ export const DonationBanner = () => (
|
||||
<FloatingIcon icon={HeartIcon} className="bottom-[20%] left-[20%]" delay={2.5} />
|
||||
|
||||
{/* Gradient Orbs */}
|
||||
<div className="absolute -start-32 top-1/4 size-64 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute -end-32 bottom-1/4 size-64 rounded-full bg-rose-500/5 blur-3xl" />
|
||||
<div className="absolute -inset-s-32 top-1/4 size-64 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute -inset-e-32 bottom-1/4 size-64 rounded-full bg-rose-500/5 blur-3xl" />
|
||||
</div>
|
||||
|
||||
<div className="container relative px-8">
|
||||
@@ -146,8 +146,8 @@ export const DonationBanner = () => (
|
||||
>
|
||||
<div aria-hidden="true" className="relative mb-6">
|
||||
<PulsingHeart />
|
||||
<SparkleEffect className="-end-4 -top-2" />
|
||||
<SparkleEffect className="-start-3 bottom-0" />
|
||||
<SparkleEffect className="-inset-e-4 -top-2" />
|
||||
<SparkleEffect className="-inset-s-3 bottom-0" />
|
||||
</div>
|
||||
|
||||
<motion.h2
|
||||
|
||||
@@ -35,7 +35,7 @@ const getCommunityLinks = (): FooterLinkItem[] => [
|
||||
{ url: "https://github.com/amruthpillai/reactive-resume/issues", label: t`Report an issue` },
|
||||
{ url: "https://crowdin.com/project/reactive-resume", label: t`Translations` },
|
||||
{ url: "https://reddit.com/r/reactiveresume", label: t`Subreddit` },
|
||||
{ url: "https://discord.gg/EE8yFqW4", label: t`Discord` },
|
||||
{ url: "https://discord.gg/aSyA5ZSxpb", label: t`Discord` },
|
||||
];
|
||||
|
||||
const socialLinks: SocialLink[] = [
|
||||
@@ -134,7 +134,7 @@ function FooterLink({ url, label }: FooterLinkItem) {
|
||||
initial={{ width: 0, opacity: 0 }}
|
||||
animate={isHovered ? { width: "100%", opacity: 1 } : { width: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.25, ease: "easeOut" }}
|
||||
className="pointer-events-none absolute start-0 -bottom-0.5 h-px rounded bg-primary"
|
||||
className="pointer-events-none absolute inset-s-0 -bottom-0.5 h-px rounded bg-primary"
|
||||
/>
|
||||
</a>
|
||||
</li>
|
||||
|
||||
@@ -119,7 +119,7 @@ export function Hero() {
|
||||
<motion.div
|
||||
aria-hidden="true"
|
||||
role="presentation"
|
||||
className="absolute start-1/2 bottom-8 -translate-x-1/2"
|
||||
className="absolute inset-s-1/2 bottom-8 -translate-x-1/2"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 2, duration: 1 }}
|
||||
|
||||
@@ -7,8 +7,8 @@ export function Prefooter() {
|
||||
<section id="prefooter" className="relative overflow-hidden py-16 md:py-24">
|
||||
{/* Background decoration */}
|
||||
<div aria-hidden="true" className="pointer-events-none absolute inset-0">
|
||||
<div className="absolute start-1/4 top-0 size-96 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute end-1/4 bottom-0 size-96 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute inset-s-1/4 top-0 size-96 rounded-full bg-primary/5 blur-3xl" />
|
||||
<div className="absolute inset-e-1/4 bottom-0 size-96 rounded-full bg-primary/5 blur-3xl" />
|
||||
</div>
|
||||
|
||||
<div className="relative space-y-8">
|
||||
|
||||
@@ -47,7 +47,7 @@ function StatisticCard({ statistic, index }: StatisticCardProps) {
|
||||
{/* Background decoration */}
|
||||
<div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
|
||||
<motion.div
|
||||
className="absolute start-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-primary/2"
|
||||
className="absolute inset-s-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 text-primary/2"
|
||||
initial={{ scale: 0.8, opacity: 0 }}
|
||||
whileInView={{ scale: 1, opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
|
||||
@@ -23,7 +23,7 @@ function TemplateItem({ metadata }: TemplateItemProps) {
|
||||
<div className="absolute inset-0 bg-linear-to-t from-black/70 via-black/20 to-transparent opacity-0 transition-opacity duration-300 group-hover:opacity-100" />
|
||||
|
||||
{/* Template name on hover */}
|
||||
<div className="absolute start-0 end-0 bottom-0 translate-y-full p-4 transition-transform duration-300 group-hover:translate-y-0">
|
||||
<div className="absolute inset-x-0 bottom-0 translate-y-full p-4 transition-transform duration-300 group-hover:translate-y-0">
|
||||
<p className="font-semibold text-white drop-shadow-lg">{metadata.name}</p>
|
||||
</div>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user