Compare commits

...

54 Commits

Author SHA1 Message Date
a5dc15dc08 Merge pull request #2146 from AmruthPillai/feat/custom-css
Implement Custom CSS Feature
2025-01-13 01:07:16 +01:00
eab996f7e7 some more updates 2025-01-13 01:07:01 +01:00
43c5a33773 Implement Custom CSS Feature 2025-01-13 00:31:49 +01:00
7fb0226ddc update to react-router v7 2025-01-12 19:41:18 +01:00
db6e7a7480 fix deploy step 2025-01-12 18:10:58 +01:00
6335ad1571 fixes #2082, fixes #2066 - fallback to cuid2 if filename contains non-latin characters 2025-01-12 18:06:44 +01:00
2d62504895 Merge pull request #2144 from AmruthPillai/chore/update-dependencies
Update dependencies, bump release version and a lot more
2025-01-12 17:43:07 +01:00
e34d0cebe5 fix deploy step in publish-docker-image 2025-01-12 17:42:51 +01:00
0053d696ff Update ESLint configuration and schemas to use Zod library
- Changed ESLint configuration to target TypeScript files and added parser options for better integration.
- Updated various schemas across the application to replace `nestjs-zod/z` imports with `zod` for consistency.
- Refactored password validation in authentication schemas to use `z.string()` instead of `z.password()`.
- Enhanced date handling in user and resume schemas by introducing a new `dateSchema` utility.
- Updated `.ncurc.json` to target minor upgrades for dependencies.
2025-01-12 17:34:45 +01:00
6fb0a72a56 fix dependencies to latest minor, add swc to no-update list 2025-01-12 17:01:20 +01:00
5b3e91e34d update release version 2025-01-12 16:55:59 +01:00
d1a5a41e4d Enhance UI and Configuration:
- Added missing `type="button"` attributes to various toolbar buttons in the rich-input component for better accessibility.
- Updated Gengar template to ensure the summary section is only displayed when populated.
- Changed boolean values in Docker Compose files from unquoted to quoted strings for consistency and to prevent potential parsing issues.
2025-01-12 16:50:07 +01:00
d0a07686a5 Merge pull request #1997 from 2368883029/main
Fix(client): Fix Unintentional Form Submission on Rich-input Toolbar Actions
2025-01-12 16:41:35 +01:00
ffa4747ed6 Merge pull request #2002 from ilatypov/main
Allow the headless browser connect to the app.
2025-01-12 16:40:44 +01:00
b963813910 Merge branch 'main' into main 2025-01-12 16:40:36 +01:00
58950ed0ef Merge pull request #2008 from busches/patch-1
fix(docs): Add missing themes to bug-report
2025-01-12 16:38:15 +01:00
1c7a6c952f Merge pull request #2010 from busches/fix-2007
fix(Gengar): Hide Summary section unless populated
2025-01-12 16:37:01 +01:00
3ae651fece resolve formatting and linting issues 2025-01-12 16:35:35 +01:00
bd52983780 Merge branch 'main' into fix-2007 2025-01-12 16:35:10 +01:00
39daed3502 Merge branch 'infinia-yzl-main' 2025-01-12 16:25:25 +01:00
dda47f51ec Merge branch 'main' of github.com:infinia-yzl/Reactive-Resume into infinia-yzl-main 2025-01-12 16:24:54 +01:00
374b9bcc58 Merge branch 'main' of github.com:AmruthPillai/Reactive-Resume 2025-01-12 16:23:48 +01:00
f0b18019d5 fix allowOverflow definition on scroll-area 2025-01-12 16:23:46 +01:00
5c10f3d866 Merge pull request #2024 from plecrx/patch-1
fix(storage-controller): rm duplicate userID & use correct filename parameter
2025-01-12 16:09:05 +01:00
5d839e5420 Merge pull request #2009 from busches/patch-2
fix(docs): Update CONTRIBUTING.md for Node 20
2025-01-12 16:07:44 +01:00
febbdefc0b Merge pull request #2035 from noreb001/bugfix/CardClippingIssue
bugfix: Fixed clipping issue with cards on dashboard page
2025-01-12 16:03:25 +01:00
6110440682 resolves #2047, fix summary text in chikorita template, when in sidebar 2025-01-12 15:58:28 +01:00
d0a174d7b7 fix localhost translation logic in printer service 2025-01-12 15:52:30 +01:00
6708570c49 Merge pull request #2091 from raymondyangdev/main
fix(templates): Add right border after website in Rhyhorn
2025-01-12 15:38:54 +01:00
bb4bbf4174 Merge pull request #2095 from jbilinski/upstream-0
fix(printer): refine "localhost" matching in printer.service.ts
2025-01-12 15:37:38 +01:00
a3ef6520e7 - refactor(templates): update sidebar display conditionals for consistency
- Modified multiple template files to change the sidebar length conditionals from `!== 0` to `> 0` for improved readability and consistency across components.

- Added Code of Conduct
2025-01-12 15:36:01 +01:00
0fec5ce86d Merge pull request #2103 from 0x346e3730/feat/full-width-main-if-no-sidebar
feat(templates): make main full-width if sidebar is empty
2025-01-12 15:31:42 +01:00
26e34b6b83 chore: update Prettier configuration and add Tailwind CSS plugin; refactor CSS classes for consistency
- Updated .prettierrc to include Tailwind CSS plugin and functions.
- Added prettier-plugin-tailwindcss to package.json and pnpm-lock.yaml.
- Refactored CSS classes in main.css and various TSX files for improved consistency and readability.
- Adjusted spacing in several components to enhance layout and maintain uniformity.
2025-01-12 15:29:23 +01:00
007243f2c3 Merge pull request #2114 from Karanrajsinh/main
Fix #1660 [Mobile Frontend Issue] :  Section-Dialog Overflow In Mobile Device Along With Some Changes In Spacing And Font Size Of Sidebar
2025-01-12 15:28:18 +01:00
9d0a0bba86 Merge branch 'main' into main 2025-01-12 15:17:55 +01:00
89a44cc33a - normalize username and email fields to lowercase, resolves #1740
- add autoComplete attributes to auth flow for easier sign in/sign up
2025-01-12 15:13:11 +01:00
92856b6f06 Merge pull request #1978 from googleknight/fix/profile-picture-adjustment-crash
fix(edit profile picture): fix the page crash happening when clicking on circular border radius
2025-01-12 13:46:26 +01:00
c968188080 Merge pull request #2133 from smuwonge/main
docs: remove period for consistency in feature list
2025-01-12 13:35:06 +01:00
1c5c4d0117 fix: allow the headless browser connect to the app 2025-01-02 20:04:17 -05:00
d6fee1e3a6 docs: remove period for consistency in feature list 2025-01-02 14:31:52 -08:00
d84aeee968 fix(builderpage): resolve section-dialog height overflow for mobile and adjust sidebar font and spacing styles 2024-12-01 10:17:01 +05:30
c87242142d feat(templates): make main full-width if sidebar is empty 2024-11-14 09:39:57 +04:00
2c84976e28 Merge pull request #2 from jbilinski/jbilinski-patch-1
fix: refine "localhost" matching in printer.service.ts
2024-11-07 10:18:43 -08:00
31ed9f41a0 fix: refine "localhost" matching in printer.service.ts
changed localhost matching for local dev environments from a url string match to more specific regex that matches host property.
This was causing the "about:blank", no output error in (edge) case of printing a pdf from any url with string "localhost"
2024-11-07 10:16:35 -08:00
700b98fcb7 fix(templates): Add right border in header after website field with custom fields in Rhyhorn 2024-11-05 17:35:14 +13:00
Ben
3f01a9e58e fixed clipping issue with cards on dashboard page 2024-09-06 12:43:27 +00:00
b9de35f0d9 fix(storage-controller): rm duplicate userID & use correct filename parameter 2024-08-23 16:45:50 +02:00
5827576ffb chore(package/react-zoom-pan-pinch): Bump react-zoom-pan-pinch version to 3.6.1 from 3.4.4 2024-08-14 17:01:05 +08:00
acc9becf1a feat(toggle-pan): Add toggle to switch between scroll to pan and scroll to zoom
- Modify wheel behaviour to pan by default for a more natural behaviour with trackpads (https://github.com/BetterTyped/react-zoom-pan-pinch/pull/447)
- Bump `react-zoom-pan-pinch` version to `3.6.1` from `3.4.4`
2024-08-14 16:25:10 +08:00
c2837838ee fix(Gengar): Hide Summary section unless populated 2024-08-04 14:36:58 -05:00
304fd93ece Update CONTRIBUTING.md for Node 20
package.json and GitHub Actions both run with Node 20
2024-08-04 14:20:41 -05:00
491bbcadcc Add missing themes to bug-report
A few themes were missing when I filled out a bug report.
2024-08-04 14:18:47 -05:00
5bc0230a5a Fix: Can't insert image in a category other than summary 2024-07-21 00:54:00 -04:00
c9a2c27b2d fix(edit profile picture): fix the crash happening when clicking on circular border radius 2024-06-27 14:59:13 +05:30
142 changed files with 5803 additions and 4713 deletions

View File

@ -76,7 +76,10 @@ body:
- Bronzor
- Chikorita
- Ditto
- Gengar
- Glalie
- Kakuna
- Leafish
- Nosepass
- Onyx
- Pikachu

View File

@ -153,7 +153,7 @@ jobs:
password: ${{ secrets.DOCKER_TOKEN }}
- name: Deploy the latest image on rxresu.me
run: curl -X POST ${{ secrets.SERVICE_WEBHOOK }}
run: curl -kX POST ${{ secrets.SERVICE_WEBHOOK }}
- name: Inform about the release on Discord
uses: sarisia/actions-status-discord@v1.14.3

View File

@ -1,7 +1,14 @@
{
"$schema": "https://raw.githubusercontent.com/raineorshine/npm-check-updates/main/src/types/RunOptions.json",
"upgrade": true,
"target": "minor",
"install": "always",
"packageManager": "pnpm",
"reject": ["eslint", "eslint-plugin-unused-imports", "@reactive-resume/*"]
"reject": [
"eslint",
"@swc/*",
"@swc-node/*",
"@reactive-resume/*",
"eslint-plugin-unused-imports"
]
}

View File

@ -1,4 +1,6 @@
{
"printWidth": 100,
"endOfLine": "auto"
"endOfLine": "auto",
"plugins": ["prettier-plugin-tailwindcss"],
"tailwindFunctions": ["cn", "cva"]
}

12
.vscode/settings.json vendored
View File

@ -1,15 +1,9 @@
{
"css.validate": false,
"vitest.disableWorkspaceWarning": true,
"typescript.tsdk": "node_modules/typescript/lib",
"tailwindCSS.experimental.classRegex": [
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
],
"yaml.schemas": {
"https://json.schemastore.org/github-workflow.json": ".github/workflows/*.yml",
"https://raw.githubusercontent.com/compose-spec/compose-spec/master/schema/compose-spec.json": [
"tools/compose/*"
]
},
"i18n-ally.localesPaths": ["apps/client/src/locales"],
"vitest.disableWorkspaceWarning": true
]
}

132
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,132 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the overall
community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances of
any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
[INSERT CONTACT METHOD].
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of
actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or permanent
ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the
community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.1, available at
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
[https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations

View File

@ -9,7 +9,7 @@ To run the development environment of the application locally on your computer,
#### Requirements
- Docker (with Docker Compose)
- Node.js 18 or higher (with pnpm)
- Node.js 20 or higher (with pnpm)
### 1. Fork and Clone the Repository

View File

@ -48,7 +48,7 @@ Start creating your standout resume with Reactive Resume today!
- **Bring your own OpenAI API key** and unlock features such as improving your writing, fixing spelling and grammar or changing the tone of your text in one-click
- Translate your resume into any language using ChatGPT and import it back for easier editing
- Create single page resumes or a resume that spans multiple pages easily
- Customize the colours and layouts to add a personal touch to your resume.
- Customize the colours and layouts to add a personal touch to your resume
- Customise your page layout as you like just by dragging-and-dropping sections
- Create custom sections that are specific to your industry if the existing ones don't fit
- Jot down personal notes specific to your resume that's only visible to you

View File

@ -40,12 +40,5 @@
<!-- Phosphor Icons -->
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<!-- Simple Icons -->
<link
type="text/css"
rel="stylesheet"
href="https://unpkg.com/simple-icons-font@v14/font/simple-icons.min.css"
/>
</body>
</html>

View File

@ -1,5 +1,3 @@
import { cn } from "@reactive-resume/utils";
type BrandIconProps = {
slug: string;
};
@ -8,12 +6,12 @@ export const BrandIcon = ({ slug }: BrandIconProps) => {
if (slug === "linkedin") {
return (
<img
alt="LinkedIn"
alt="linkedin"
className="size-4"
src={`${window.location.origin}/support-logos/linkedin.svg`}
/>
);
}
return <i className={cn("si si--color text-[1rem]", `si-${slug}`)} />;
return <img alt={slug} className="size-4" src={`https://cdn.simpleicons.org/${slug}`} />;
};

View File

@ -1,6 +1,6 @@
import { StrictMode } from "react";
import * as ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import { RouterProvider } from "react-router";
import { router } from "./router";

View File

@ -1,5 +1,5 @@
import { useEffect, useMemo } from "react";
import { Outlet } from "react-router-dom";
import { Outlet } from "react-router";
import webfontloader from "webfontloader";
import { useArtboardStore } from "../store/artboard";
@ -55,5 +55,11 @@ export const ArtboardPage = () => {
}
}, [metadata]);
return <Outlet />;
return (
<>
{metadata.css.visible && <style lang="css">{`[data-page] { ${metadata.css.value} }`}</style>}
<Outlet />
</>
);
};

View File

@ -1,7 +1,7 @@
import { SectionKey } from "@reactive-resume/schema";
import { pageSizeMap, Template } from "@reactive-resume/utils";
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useMemo, useRef } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
import { MM_TO_PX, Page } from "../components/page";
@ -9,9 +9,12 @@ import { useArtboardStore } from "../store/artboard";
import { getTemplate } from "../templates";
export const BuilderLayout = () => {
const [wheelPanning, setWheelPanning] = useState(true);
const transformRef = useRef<ReactZoomPanPinchRef>(null);
const format = useArtboardStore((state) => state.resume.metadata.page.format);
const layout = useArtboardStore((state) => state.resume.metadata.layout);
const format = useArtboardStore((state) => state.resume.metadata.page.format);
const template = useArtboardStore((state) => state.resume.metadata.template as Template);
const Template = useMemo(() => getTemplate(template), [template]);
@ -27,6 +30,9 @@ export const BuilderLayout = () => {
transformRef.current?.resetTransform(0);
setTimeout(() => transformRef.current?.centerView(0.8, 0), 10);
}
if (event.data.type === "TOGGLE_PAN_MODE") {
setWheelPanning(event.data.panMode);
}
};
window.addEventListener("message", handleMessage);
@ -44,6 +50,8 @@ export const BuilderLayout = () => {
minScale={0.4}
initialScale={0.8}
limitToBounds={false}
wheel={{ wheelDisabled: wheelPanning }}
panning={{ wheelPanning: wheelPanning }}
>
<TransformComponent
wrapperClass="!w-screen !h-screen"

View File

@ -1,5 +1,5 @@
import { useEffect } from "react";
import { Outlet } from "react-router-dom";
import { Outlet } from "react-router";
import { useArtboardStore } from "../store/artboard";

View File

@ -1,4 +1,4 @@
import { createBrowserRouter, createRoutesFromChildren, Route } from "react-router-dom";
import { createBrowserRouter, createRoutesFromChildren, Route } from "react-router";
import { ArtboardPage } from "../pages/artboard";
import { BuilderLayout } from "../pages/builder";
@ -6,7 +6,7 @@ import { PreviewLayout } from "../pages/preview";
import { Providers } from "../providers";
export const routes = createRoutesFromChildren(
<Route element={<Providers />}>
<Route element={<Providers />} hydrateFallbackElement={<div>Loading...</div>}>
<Route path="artboard" element={<ArtboardPage />}>
<Route path="builder" element={<BuilderLayout />} />
<Route path="preview" element={<PreviewLayout />} />

View File

@ -21,5 +21,5 @@
}
.wysiwyg {
@apply prose max-w-none prose-foreground prose-headings:mt-0 prose-headings:mb-2 prose-p:mt-0 prose-p:mb-2 prose-ul:mt-0 prose-ul:mb-2 prose-li:mt-0 prose-li:mb-2 prose-ol:mt-0 prose-ol:mb-2 prose-img:mt-0 prose-img:mb-2 prose-hr:mt-0 prose-hr:mb-2 prose-p:leading-normal prose-li:leading-normal prose-a:break-all;
@apply prose-foreground prose max-w-none prose-headings:mb-2 prose-headings:mt-0 prose-p:mb-2 prose-p:mt-0 prose-p:leading-normal prose-a:break-all prose-ol:mb-2 prose-ol:mt-0 prose-ul:mb-2 prose-ul:mt-0 prose-li:mb-2 prose-li:mt-0 prose-li:leading-normal prose-img:mb-2 prose-img:mt-0 prose-hr:mb-2 prose-hr:mt-0;
}

View File

@ -562,7 +562,9 @@ export const Azurill = ({ columns, isFirstPage = false }: TemplateProps) => {
))}
</div>
<div className="main group col-span-2 space-y-4">
<div
className={cn("main group space-y-4", sidebar.length > 0 ? "col-span-2" : "col-span-3")}
>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}

View File

@ -128,7 +128,8 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
return (
<div className="flex items-center gap-x-1.5">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
{!iconOnRight &&
(icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-white" />)}
<a
href={url.href}
target="_blank"
@ -137,7 +138,8 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
>
{label ?? (url.label || url.href)}
</a>
{iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
{iconOnRight &&
(icon ?? <i className="ph ph-bold ph-link text-primary group-[.sidebar]:text-white" />)}
</div>
);
};
@ -208,7 +210,10 @@ const Section = <T,>({
</div>
{summary !== undefined && !isEmptyString(summary) && (
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
<div
dangerouslySetInnerHTML={{ __html: summary }}
className="wysiwyg group-[.sidebar]:prose-invert"
/>
)}
{level !== undefined && level > 0 && <Rating level={level} />}
@ -566,7 +571,12 @@ export const Chikorita = ({ columns, isFirstPage = false }: TemplateProps) => {
return (
<div className="grid min-h-[inherit] grid-cols-3">
<div className="main p-custom group col-span-2 space-y-4">
<div
className={cn(
"main p-custom group space-y-4",
sidebar.length > 0 ? "col-span-2" : "col-span-3",
)}
>
{isFirstPage && <Header />}
{main.map((section) => (
@ -574,7 +584,12 @@ export const Chikorita = ({ columns, isFirstPage = false }: TemplateProps) => {
))}
</div>
<div className="sidebar p-custom group h-full space-y-4 bg-primary text-background">
<div
className={cn(
"sidebar p-custom group h-full space-y-4 bg-primary text-background",
sidebar.length === 0 && "hidden",
)}
>
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}

View File

@ -609,7 +609,12 @@ export const Ditto = ({ columns, isFirstPage = false }: TemplateProps) => {
))}
</div>
<div className="main p-custom group col-span-2 space-y-4">
<div
className={cn(
"main p-custom group space-y-4",
sidebar.length > 0 ? "col-span-2" : "col-span-3",
)}
>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}

View File

@ -83,17 +83,20 @@ const Header = () => {
const Summary = () => {
const section = useArtboardStore((state) => state.resume.sections.summary);
const primaryColor = useArtboardStore((state) => state.resume.metadata.theme.primary);
if (!section.visible || isEmptyString(section.content)) return null;
return (
<section id={section.id}>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
style={{ columns: section.columns }}
/>
</section>
<div className="p-custom space-y-4" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}>
<section id={section.id}>
<div
dangerouslySetInnerHTML={{ __html: section.content }}
className="wysiwyg"
style={{ columns: section.columns }}
/>
</section>
</div>
);
};
@ -587,15 +590,8 @@ export const Gengar = ({ columns, isFirstPage = false }: TemplateProps) => {
</div>
</div>
<div className="main group col-span-2">
{isFirstPage && (
<div
className="p-custom space-y-4"
style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}
>
<Summary />
</div>
)}
<div className={cn("main group", sidebar.length > 0 ? "col-span-2" : "col-span-3")}>
{isFirstPage && <Summary />}
<div className="p-custom space-y-4">
{main.map((section) => (

View File

@ -579,7 +579,7 @@ export const Glalie = ({ columns, isFirstPage = false }: TemplateProps) => {
return (
<div className="grid min-h-[inherit] grid-cols-3">
<div
className="sidebar p-custom group space-y-4"
className={cn("sidebar p-custom group space-y-4", sidebar.length === 0 && "hidden")}
style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}
>
{isFirstPage && <Header />}
@ -589,7 +589,12 @@ export const Glalie = ({ columns, isFirstPage = false }: TemplateProps) => {
))}
</div>
<div className="main p-custom group col-span-2 space-y-4">
<div
className={cn(
"main p-custom group space-y-4",
sidebar.length > 0 ? "col-span-2" : "col-span-3",
)}
>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}

View File

@ -519,13 +519,13 @@ export const Leafish = ({ columns, isFirstPage = false }: TemplateProps) => {
{isFirstPage && <Header />}
<div className="p-custom grid grid-cols-2 items-start space-x-6">
<div className="grid gap-y-4">
<div className={cn("grid gap-y-4", sidebar.length === 0 && "col-span-2")}>
{main.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}
</div>
<div className="grid gap-y-4">
<div className={cn("grid gap-y-4", sidebar.length === 0 && "hidden")}>
{sidebar.map((section) => (
<Fragment key={section}>{mapSectionToComponent(section)}</Fragment>
))}

View File

@ -609,7 +609,7 @@ export const Pikachu = ({ columns, isFirstPage = false }: TemplateProps) => {
))}
</div>
<div className="main group col-span-2 space-y-4">
<div className={cn("main group space-y-4", sidebar.length > 0 ? "col-span-2" : "col-span-3")}>
{isFirstPage && <Header />}
{main.map((section) => (

View File

@ -125,7 +125,7 @@ const Link = ({ url, icon, iconOnRight, label, className }: LinkProps) => {
if (!isUrl(url.href)) return null;
return (
<div className="flex items-center gap-x-1.5">
<div className="flex items-center gap-x-1.5 border-r pr-2 last:border-r-0 last:pr-0">
{!iconOnRight && (icon ?? <i className="ph ph-bold ph-link text-primary" />)}
<a
href={url.href}

View File

@ -3,11 +3,14 @@
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"files": ["*.ts", "*.tsx"],
"extends": [
"plugin:tailwindcss/recommended",
"plugin:@tanstack/eslint-plugin-query/recommended"
],
"parserOptions": {
"projectService": "./apps/client/tsconfig.json"
},
"settings": {
"tailwindcss": {
"callees": ["cn", "clsx", "cva"],
@ -39,8 +42,59 @@
"lingui/no-unlocalized-strings": [
2,
{
"ignoreFunction": ["cn"],
"ignoreAttribute": ["alt"]
"ignore": [
// Ignore strings which are a single "word" (no spaces)
// and doesn't start with an uppercase letter
"^(?![A-Z])\\S+$",
// Ignore UPPERCASE literals
// Example: const test = "FOO"
"^[A-Z0-9_-]+$"
],
"ignoreNames": [
// Ignore matching className (case-insensitive)
{ "regex": { "pattern": "className", "flags": "i" } },
// Ignore UPPERCASE names
// Example: test.FOO = "ola!"
{ "regex": { "pattern": "^[A-Z0-9_-]+$" } },
"id",
"src",
"srcSet",
"styleName",
"placeholder",
"alt",
"type",
"width",
"height",
"displayName",
"Authorization"
],
"ignoreFunctions": [
"cn",
"cva",
"track",
"Error",
"console.*",
"*headers.set",
"*.addEventListener",
"*.removeEventListener",
"*.postMessage",
"*.getElementById",
"*.dispatch",
"*.commit",
"*.includes",
"*.indexOf",
"*.endsWith",
"*.startsWith",
"require"
],
// Following settings require typed linting https://typescript-eslint.io/getting-started/typed-linting/
"useTsTypes": true,
"ignoreMethodsOnTypes": [
// Ignore specified methods on Map and Set types
"Map.get",
"Map.has",
"Set.has"
]
}
],
"lingui/t-call-in-function": 2,

View File

@ -35,7 +35,7 @@
<!-- Styles -->
<link rel="stylesheet" href="/src/styles/main.css" />
</head>
<body class="text-sm antialiased bg-background text-foreground print:bg-white print:m-0">
<body class="bg-background text-sm text-foreground antialiased print:m-0 print:bg-white">
<div id="root"></div>
<!-- Scripts -->
@ -43,12 +43,5 @@
<!-- Phosphor Icons -->
<script src="https://unpkg.com/@phosphor-icons/web"></script>
<!-- Simple Icons -->
<link
type="text/css"
rel="stylesheet"
href="https://unpkg.com/simple-icons-font@v14/font/simple-icons.min.css"
/>
</body>
</html>

View File

@ -0,0 +1,155 @@
code[class*="language-"],
pre[class*="language-"] {
color: #f8f8f2;
background: none;
font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
border-radius: 0.3em;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: #2b2b2b;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.1em;
border-radius: 0.3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #d4d0ab;
}
.token.punctuation {
color: #fefefe;
}
.token.property,
.token.tag,
.token.constant,
.token.symbol,
.token.deleted {
color: #ffa07a;
}
.token.boolean,
.token.number {
color: #00e0e0;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #abe338;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string,
.token.variable {
color: #00e0e0;
}
.token.atrule,
.token.attr-value,
.token.function {
color: #ffd700;
}
.token.keyword {
color: #00e0e0;
}
.token.regex,
.token.important {
color: #ffd700;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
@media screen and (forced-colors: active) {
code[class*="language-"],
pre[class*="language-"] {
color: windowText;
background: window;
}
:not(pre) > code[class*="language-"],
pre[class*="language-"] {
background: window;
}
.token.important {
background: highlight;
color: window;
font-weight: normal;
}
.token.atrule,
.token.attr-value,
.token.function,
.token.keyword,
.token.operator,
.token.selector {
font-weight: bold;
}
.token.attr-value,
.token.comment,
.token.doctype,
.token.function,
.token.keyword,
.token.operator,
.token.property,
.token.string {
color: highlight;
}
.token.attr-value,
.token.url {
font-weight: normal;
}
}

View File

@ -0,0 +1,167 @@
code[class*="language-"],
pre[class*="language-"] {
color: #393a34;
font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
direction: ltr;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
font-size: 0.9em;
line-height: 1.2em;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre > code[class*="language-"] {
font-size: 1em;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
background: #c1def1;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
background: #c1def1;
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: 0.5em 0;
overflow: auto;
border: 1px solid #dddddd;
background-color: white;
}
/* Inline code */
:not(pre) > code[class*="language-"] {
padding: 0.2em;
padding-top: 1px;
padding-bottom: 1px;
background: #f8f8f8;
border: 1px solid #dddddd;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #008000;
font-style: italic;
}
.token.namespace {
opacity: 0.7;
}
.token.string {
color: #a31515;
}
.token.punctuation,
.token.operator {
color: #393a34; /* no highlight */
}
.token.url,
.token.symbol,
.token.number,
.token.boolean,
.token.variable,
.token.constant,
.token.inserted {
color: #36acaa;
}
.token.atrule,
.token.keyword,
.token.attr-value,
.language-autohotkey .token.selector,
.language-json .token.boolean,
.language-json .token.number,
code[class*="language-css"] {
color: #0000ff;
}
.token.function {
color: #393a34;
}
.token.deleted,
.language-autohotkey .token.tag {
color: #9a050f;
}
.token.selector,
.language-autohotkey .token.keyword {
color: #00009f;
}
.token.important {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.class-name,
.language-json .token.property {
color: #2b91af;
}
.token.tag,
.token.selector {
color: #800000;
}
.token.attr-name,
.token.property,
.token.regex,
.token.entity {
color: #ff0000;
}
.token.directive.tag .tag {
background: #ffff00;
color: #393a34;
}
/* overrides color-values for the Line Numbers plugin
* http://prismjs.com/plugins/line-numbers/
*/
.line-numbers.line-numbers .line-numbers-rows {
border-right-color: #a5a5a5;
}
.line-numbers .line-numbers-rows > span:before {
color: #2b91af;
}
/* overrides color-values for the Line Highlight plugin
* http://prismjs.com/plugins/line-highlight/
*/
.line-highlight.line-highlight {
background: rgba(193, 222, 241, 0.2);
background: -webkit-linear-gradient(left, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0));
background: linear-gradient(to right, rgba(193, 222, 241, 0.2) 70%, rgba(221, 222, 241, 0));
}

View File

@ -289,7 +289,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -314,7 +314,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -314,7 +314,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -315,7 +315,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -289,7 +289,7 @@
[[], []]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -288,7 +288,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -287,7 +287,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -289,7 +289,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -306,7 +306,7 @@
[["projects", "certifications", "skills", "languages", "references"], []]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -287,7 +287,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -315,7 +315,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -288,7 +288,7 @@
]
],
"css": {
"value": ".section {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"value": "* {\n\toutline: 1px solid #000;\n\toutline-offset: 4px;\n}",
"visible": false
},
"page": {

View File

@ -7,7 +7,7 @@ import {
DropdownMenuTrigger,
KeyboardShortcut,
} from "@reactive-resume/ui";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { useLogout } from "../services/auth";
@ -26,7 +26,7 @@ export const UserOptions = ({ children }: Props) => {
<DropdownMenuContent side="top" align="start" className="w-48">
<DropdownMenuItem
onClick={() => {
navigate("/dashboard/settings");
void navigate("/dashboard/settings");
}}
>
{t`Settings`}

View File

@ -2,7 +2,7 @@ import { t } from "@lingui/macro";
import { deepSearchAndParseDates, ErrorMessage } from "@reactive-resume/utils";
import _axios from "axios";
import createAuthRefreshInterceptor from "axios-auth-refresh";
import { redirect } from "react-router-dom";
import { redirect } from "react-router";
import { refreshToken } from "@/client/services/auth";

View File

@ -1,6 +1,6 @@
import { StrictMode } from "react";
import * as ReactDOM from "react-dom/client";
import { RouterProvider } from "react-router-dom";
import { RouterProvider } from "react-router";
import { router } from "./router";

View File

@ -16,7 +16,7 @@ import {
import { useRef } from "react";
import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { z } from "zod";
import { useBackupOtp } from "@/client/services/auth";
@ -39,7 +39,7 @@ export const BackupOtpPage = () => {
try {
await backupOtp(data);
navigate("/dashboard");
void navigate("/dashboard");
} catch {
form.reset();
}
@ -77,6 +77,7 @@ export const BackupOtpPage = () => {
<Input
pattern="[a-z0-9]{10}"
placeholder="a1b2c3d4e5"
autoComplete="one-time-code"
title={t`Backup Codes may contain only lowercase letters or numbers, and must be exactly 10 characters.`}
{...field}
/>
@ -91,7 +92,7 @@ export const BackupOtpPage = () => {
variant="link"
className="px-5"
onClick={() => {
navigate(-1);
void navigate(-1);
}}
>
<ArrowLeft size={14} className="mr-2" />

View File

@ -17,7 +17,7 @@ import {
import { useState } from "react";
import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { z } from "zod";
import { useForgotPassword } from "@/client/services/auth";
@ -81,7 +81,7 @@ export const ForgotPasswordPage = () => {
<FormItem>
<FormLabel>{t`Email`}</FormLabel>
<FormControl>
<Input placeholder="john.doe@example.com" {...field} />
<Input placeholder="john.doe@example.com" autoComplete="email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -93,7 +93,7 @@ export const ForgotPasswordPage = () => {
variant="link"
className="px-5"
onClick={() => {
navigate(-1);
void navigate(-1);
}}
>
<ArrowLeft size={14} className="mr-2" />

View File

@ -1,7 +1,7 @@
import { t } from "@lingui/macro";
import { cn } from "@reactive-resume/utils";
import { useMemo } from "react";
import { Link, matchRoutes, Outlet, useLocation } from "react-router-dom";
import { Link, matchRoutes, Outlet, useLocation } from "react-router";
import { LocaleSwitch } from "@/client/components/locale-switch";
import { Logo } from "@/client/components/logo";

View File

@ -20,7 +20,7 @@ import { cn } from "@reactive-resume/utils";
import { useRef } from "react";
import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { z } from "zod";
import { useLogin } from "@/client/services/auth";
@ -89,7 +89,12 @@ export const LoginPage = () => {
<FormItem>
<FormLabel>{t`Email`}</FormLabel>
<FormControl>
<Input placeholder="john.doe@example.com" {...field} />
<Input
autoComplete="email"
className="lowercase"
placeholder="john.doe@example.com"
{...field}
/>
</FormControl>
<FormDescription>{t`You can also enter your username.`}</FormDescription>
<FormMessage />
@ -104,7 +109,7 @@ export const LoginPage = () => {
<FormItem>
<FormLabel>{t`Password`}</FormLabel>
<FormControl>
<Input type="password" {...field} />
<Input type="password" autoComplete="password" {...field} />
</FormControl>
<FormDescription>
<Trans>

View File

@ -20,7 +20,7 @@ import { cn } from "@reactive-resume/utils";
import { useRef } from "react";
import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form";
import { Link, useNavigate } from "react-router-dom";
import { Link, useNavigate } from "react-router";
import { z } from "zod";
import { useRegister } from "@/client/services/auth";
@ -51,7 +51,7 @@ export const RegisterPage = () => {
try {
await register(data);
navigate("/auth/verify-email");
void navigate("/auth/verify-email");
} catch {
form.reset();
}
@ -119,6 +119,7 @@ export const RegisterPage = () => {
<FormLabel>{t`Username`}</FormLabel>
<FormControl>
<Input
className="lowercase"
placeholder={t({
message: "john.doe",
context:
@ -140,6 +141,7 @@ export const RegisterPage = () => {
<FormLabel>{t`Email`}</FormLabel>
<FormControl>
<Input
className="lowercase"
placeholder={t({
message: "john.doe@example.com",
context:

View File

@ -16,7 +16,7 @@ import {
import { useEffect, useRef } from "react";
import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form";
import { useNavigate, useSearchParams } from "react-router-dom";
import { useNavigate, useSearchParams } from "react-router";
import { z } from "zod";
import { useResetPassword } from "@/client/services/auth";
@ -42,7 +42,7 @@ export const ResetPasswordPage = () => {
try {
await resetPassword(data);
navigate("/auth/login");
void navigate("/auth/login");
} catch {
form.reset();
}
@ -50,7 +50,7 @@ export const ResetPasswordPage = () => {
// Redirect the user to the forgot password page if the token is not present.
useEffect(() => {
if (!token) navigate("/auth/forgot-password");
if (!token) void navigate("/auth/forgot-password");
}, [token, navigate]);
return (
@ -82,7 +82,7 @@ export const ResetPasswordPage = () => {
<FormItem>
<FormLabel>{t`Password`}</FormLabel>
<FormControl>
<Input type="password" {...field} />
<Input type="password" autoComplete="new-password" {...field} />
</FormControl>
<FormDescription>
<Trans>

View File

@ -3,7 +3,7 @@ import { ArrowRight, Info, SealCheck } from "@phosphor-icons/react";
import { Alert, AlertDescription, AlertTitle, Button } from "@reactive-resume/ui";
import { useEffect } from "react";
import { Helmet } from "react-helmet-async";
import { Link, useNavigate, useSearchParams } from "react-router-dom";
import { Link, useNavigate, useSearchParams } from "react-router";
import { useToast } from "@/client/hooks/use-toast";
import { queryClient } from "@/client/libs/query-client";
@ -28,7 +28,7 @@ export const VerifyEmailPage = () => {
title: t`Your email address has been verified successfully.`,
});
navigate("/dashboard/resumes", { replace: true });
void navigate("/dashboard/resumes", { replace: true });
};
if (!token) return;

View File

@ -16,7 +16,7 @@ import {
import { useRef } from "react";
import { Helmet } from "react-helmet-async";
import { useForm } from "react-hook-form";
import { Link, useNavigate } from "react-router-dom";
import { Link, useNavigate } from "react-router";
import { z } from "zod";
import { useVerifyOtp } from "@/client/services/auth";
@ -39,7 +39,7 @@ export const VerifyOtpPage = () => {
try {
await verifyOtp(data);
navigate("/dashboard");
void navigate("/dashboard");
} catch {
form.reset();
}
@ -81,7 +81,7 @@ export const VerifyOtpPage = () => {
<FormItem>
<FormLabel>{t`One-Time Password`}</FormLabel>
<FormControl>
<Input placeholder="123456" {...field} />
<Input placeholder="123456" autoComplete="one-time-code" {...field} />
</FormControl>
<FormMessage />
</FormItem>

View File

@ -2,7 +2,7 @@ import { t } from "@lingui/macro";
import { HouseSimple, Lock, SidebarSimple } from "@phosphor-icons/react";
import { Button, Tooltip } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { useBuilderStore } from "@/client/stores/builder";
import { useResumeStore } from "@/client/stores/resume";

View File

@ -2,6 +2,7 @@ import { t } from "@lingui/macro";
import {
ArrowClockwise,
ArrowCounterClockwise,
ArrowsOutCardinal,
CircleNotch,
ClockClockwise,
CubeFocus,
@ -9,11 +10,13 @@ import {
Hash,
LineSegment,
LinkSimple,
MagnifyingGlass,
MagnifyingGlassMinus,
MagnifyingGlassPlus,
} from "@phosphor-icons/react";
import { Button, Separator, Toggle, Tooltip } from "@reactive-resume/ui";
import { motion } from "framer-motion";
import { useState } from "react";
import { useToast } from "@/client/hooks/use-toast";
import { usePrintResume } from "@/client/services/resume";
@ -27,6 +30,9 @@ const openInNewTab = (url: string) => {
export const BuilderToolbar = () => {
const { toast } = useToast();
const [panMode, setPanMode] = useState<boolean>(true);
const setValue = useResumeStore((state) => state.setValue);
const undo = useTemporalResumeStore((state) => state.undo);
const redo = useTemporalResumeStore((state) => state.redo);
@ -59,6 +65,10 @@ export const BuilderToolbar = () => {
const onZoomOut = () => frameRef?.contentWindow?.postMessage({ type: "ZOOM_OUT" }, "*");
const onResetView = () => frameRef?.contentWindow?.postMessage({ type: "RESET_VIEW" }, "*");
const onCenterView = () => frameRef?.contentWindow?.postMessage({ type: "CENTER_VIEW" }, "*");
const onTogglePanMode = () => {
setPanMode(!panMode);
frameRef?.contentWindow?.postMessage({ type: "TOGGLE_PAN_MODE", panMode: !panMode }, "*");
};
return (
<motion.div className="fixed inset-x-0 bottom-0 mx-auto hidden py-6 text-center md:block">
@ -91,6 +101,14 @@ export const BuilderToolbar = () => {
<Separator orientation="vertical" className="h-9" />
<Tooltip content={panMode ? t`Scroll to Pan` : t`Scroll to Zoom`}>
<Toggle className="rounded-none" pressed={panMode} onPressedChange={onTogglePanMode}>
{panMode ? <ArrowsOutCardinal /> : <MagnifyingGlass />}
</Toggle>
</Tooltip>
<Separator orientation="vertical" className="h-9" />
<Tooltip content={t`Zoom In`}>
<Button size="icon" variant="ghost" className="rounded-none" onClick={onZoomIn}>
<MagnifyingGlassPlus />

View File

@ -1,7 +1,7 @@
import { useBreakpoint } from "@reactive-resume/hooks";
import { Panel, PanelGroup, PanelResizeHandle, Sheet, SheetContent } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { Outlet } from "react-router-dom";
import { Outlet } from "react-router";
import { useBuilderStore } from "@/client/stores/builder";

View File

@ -2,7 +2,7 @@ import { t } from "@lingui/macro";
import { ResumeDto } from "@reactive-resume/dto";
import { useCallback, useEffect } from "react";
import { Helmet } from "react-helmet-async";
import { LoaderFunction, redirect } from "react-router-dom";
import { LoaderFunction, redirect } from "react-router";
import { queryClient } from "@/client/libs/query-client";
import { findResumeById } from "@/client/services/resume";

View File

@ -17,7 +17,7 @@ import {
} from "@reactive-resume/schema";
import { Button, ScrollArea, Separator } from "@reactive-resume/ui";
import { Fragment, useRef } from "react";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { Icon } from "@/client/components/icon";
import { UserAvatar } from "@/client/components/user-avatar";
@ -161,7 +161,7 @@ export const LeftSidebar = () => {
</div>
<ScrollArea orientation="vertical" className="h-screen flex-1 pb-16 lg:pb-0">
<div ref={containterRef} className="grid gap-y-6 p-6 @container/left">
<div ref={containterRef} className="grid gap-y-10 p-6 @container/left">
<BasicsSection />
<Separator />
<SummarySection />

View File

@ -18,7 +18,7 @@ export const BasicsSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("basics")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Basics`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Basics`}</h2>
</div>
</header>
@ -27,7 +27,7 @@ export const BasicsSection = () => {
<PictureSection />
</div>
<div className="space-y-1.5 sm:col-span-2">
<div className="space-y-4 sm:col-span-2">
<Label htmlFor="basics.name">{t`Full Name`}</Label>
<Input
id="basics.name"

View File

@ -61,8 +61,9 @@ export const PictureOptions = () => {
return borderRadiusToStringMap[radius];
}, [picture.borderRadius]);
const onBorderRadiusChange = (value: BorderRadius) => {
setValue("basics.picture.borderRadius", stringToBorderRadiusMap[value]);
const onBorderRadiusChange = (value: string) => {
if (!value) return;
setValue("basics.picture.borderRadius", stringToBorderRadiusMap[value as BorderRadius]);
};
return (

View File

@ -100,7 +100,7 @@ export const SectionBase = <T extends SectionItem>({ id, title, description }: P
<div className="flex items-center gap-x-4">
{getSectionIcon(id)}
<h2 className="line-clamp-1 text-3xl font-bold">{section.name}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{section.name}</h2>
</div>
<div className="flex items-center gap-x-2">
@ -161,7 +161,11 @@ export const SectionBase = <T extends SectionItem>({ id, title, description }: P
{section.items.length > 0 && (
<footer className="flex items-center justify-end">
<Button variant="outline" className="ml-auto gap-x-2" onClick={onCreate}>
<Button
variant="outline"
className="ml-auto gap-x-2 text-xs lg:text-sm"
onClick={onCreate}
>
<Plus />
<span>
{t({

View File

@ -20,6 +20,7 @@ import {
DialogHeader,
DialogTitle,
Form,
ScrollArea,
} from "@reactive-resume/ui";
import { produce } from "immer";
import get from "lodash.get";
@ -146,36 +147,41 @@ export const SectionDialog = <T extends SectionItem>({
<Dialog open={isOpen} onOpenChange={close}>
<DialogContent className="z-50">
<Form {...form}>
<form className="space-y-6" onSubmit={form.handleSubmit(onSubmit)}>
<DialogHeader>
<DialogTitle>
<div className="flex items-center space-x-2.5">
{isCreate && <Plus />}
{isUpdate && <PencilSimple />}
{isDuplicate && <CopySimple />}
<h2>
{isCreate && t`Create a new item`}
{isUpdate && t`Update an existing item`}
{isDuplicate && t`Duplicate an existing item`}
</h2>
</div>
</DialogTitle>
<ScrollArea>
<form
className="max-h-[60vh] space-y-6 lg:max-h-fit"
onSubmit={form.handleSubmit(onSubmit)}
>
<DialogHeader>
<DialogTitle>
<div className="flex items-center space-x-2.5">
{isCreate && <Plus />}
{isUpdate && <PencilSimple />}
{isDuplicate && <CopySimple />}
<h2>
{isCreate && t`Create a new item`}
{isUpdate && t`Update an existing item`}
{isDuplicate && t`Duplicate an existing item`}
</h2>
</div>
</DialogTitle>
<VisuallyHidden>
<DialogDescription />
</VisuallyHidden>
</DialogHeader>
<VisuallyHidden>
<DialogDescription />
</VisuallyHidden>
</DialogHeader>
{children}
{children}
<DialogFooter>
<Button type="submit">
{isCreate && t`Create`}
{isUpdate && t`Save Changes`}
{isDuplicate && t`Duplicate`}
</Button>
</DialogFooter>
</form>
<DialogFooter>
<Button type="submit">
{isCreate && t`Create`}
{isUpdate && t`Save Changes`}
{isDuplicate && t`Duplicate`}
</Button>
</DialogFooter>
</form>
</ScrollArea>
</Form>
</DialogContent>
</Dialog>

View File

@ -82,7 +82,7 @@ export const SectionOptions = ({ id }: Props) => {
<List weight="bold" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-48">
<DropdownMenuContent className="mr-4 w-48">
{hasItems && (
<>
<DropdownMenuItem onClick={onCreate}>

View File

@ -20,7 +20,7 @@ export const SummarySection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("summary")}
<h2 className="line-clamp-1 text-3xl font-bold">{section.name}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{section.name}</h2>
</div>
<div className="flex items-center gap-x-2">

View File

@ -5,6 +5,7 @@ import { useRef } from "react";
import { Copyright } from "@/client/components/copyright";
import { ThemeSwitch } from "@/client/components/theme-switch";
import { CssSection } from "./sections/css";
import { ExportSection } from "./sections/export";
import { InformationSection } from "./sections/information";
import { LayoutSection } from "./sections/layout";
@ -37,6 +38,8 @@ export const RightSidebar = () => {
<Separator />
<ThemeSection />
<Separator />
<CssSection />
<Separator />
<PageSection />
<Separator />
<SharingSection />
@ -85,6 +88,13 @@ export const RightSidebar = () => {
scrollIntoView("#theme");
}}
/>
<SectionIcon
id="css"
name={t`Custom CSS`}
onClick={() => {
scrollIntoView("#css");
}}
/>
<SectionIcon
id="page"
name={t`Page`}

View File

@ -0,0 +1,58 @@
import { t } from "@lingui/macro";
import { useTheme } from "@reactive-resume/hooks";
import { Label, Switch } from "@reactive-resume/ui";
import Prism from "prismjs";
import { Helmet } from "react-helmet-async";
import CodeEditor from "react-simple-code-editor";
import { useResumeStore } from "@/client/stores/resume";
import { getSectionIcon } from "../shared/section-icon";
export const CssSection = () => {
const { isDarkMode } = useTheme();
const setValue = useResumeStore((state) => state.setValue);
const css = useResumeStore((state) => state.resume.data.metadata.css);
return (
<section id="css" className="grid gap-y-6">
<Helmet>
{isDarkMode && <link rel="stylesheet" href="/styles/prism-dark.css" />}
{!isDarkMode && <link rel="stylesheet" href="/styles/prism-light.css" />}
</Helmet>
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("css")}
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Custom CSS`}</h2>
</div>
</header>
<main className="space-y-4">
<div className="flex items-center gap-x-4">
<Switch
id="metadata.css.visible"
checked={css.visible}
onCheckedChange={(checked) => {
setValue("metadata.css.visible", checked);
}}
/>
<Label htmlFor="metadata.css.visible">{t`Apply Custom CSS`}</Label>
</div>
<div className="rounded border p-4">
<CodeEditor
tabSize={4}
value={css.value}
className="language-css font-mono"
highlight={(code) => Prism.highlight(code, Prism.languages.css, "css")}
onValueChange={(value) => {
setValue("metadata.css.value", value);
}}
/>
</div>
</main>
</section>
);
};

View File

@ -37,7 +37,7 @@ export const ExportSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("export")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Export`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Export`}</h2>
</div>
</header>

View File

@ -114,7 +114,7 @@ export const InformationSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("information")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Information`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Information`}</h2>
</div>
</header>

View File

@ -18,7 +18,7 @@ import {
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
import { t, Trans } from "@lingui/macro";
import { t } from "@lingui/macro";
import { ArrowCounterClockwise, DotsSixVertical, Plus, TrashSimple } from "@phosphor-icons/react";
import { defaultMetadata } from "@reactive-resume/schema";
import { Button, Portal, Tooltip } from "@reactive-resume/ui";
@ -92,9 +92,7 @@ type SectionProps = {
};
const Section = ({ id, isDragging = false }: SectionProps) => {
const name = useResumeStore((state) =>
get(state.resume.data.sections, `${id}.name`, id),
) as string;
const name = useResumeStore((state) => get(state.resume.data.sections, `${id}.name`, id));
return (
<div
@ -204,7 +202,7 @@ export const LayoutSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("layout")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Layout`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Layout`}</h2>
</div>
<Tooltip content={t`Reset Layout`}>
@ -229,13 +227,12 @@ export const LayoutSection = () => {
const main = page[0];
const sidebar = page[1];
const pageNumber = pageIndex + 1;
return (
<div key={pageIndex} className="rounded border p-3 pb-4">
<div className="flex items-center justify-between">
<p className="mb-3 text-xs font-bold">
<Trans>Page {pageIndex + 1}</Trans>
</p>
<p className="mb-3 text-xs font-bold">{t`Page ${pageNumber}`}</p>
{pageIndex !== 0 && (
<Tooltip content={t`Remove Page`}>
@ -268,7 +265,7 @@ export const LayoutSection = () => {
<Button variant="outline" className="ml-auto" onClick={onAddPage}>
<Plus />
<span className="ml-2">{t`Add New Page`}</span>
<span className="ml-2 text-xs lg:text-sm">{t`Add New Page`}</span>
</Button>
</main>
</section>

View File

@ -14,7 +14,7 @@ export const NotesSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("notes")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Notes`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Notes`}</h2>
</div>
</header>

View File

@ -23,11 +23,11 @@ export const PageSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("page")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Page`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Page`}</h2>
</div>
</header>
<main className="grid gap-y-4">
<main className="grid gap-y-6">
<div className="space-y-1.5">
<Label>{t`Format`}</Label>
<Select

View File

@ -36,7 +36,7 @@ export const SharingSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("sharing")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Sharing`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Sharing`}</h2>
</div>
</header>

View File

@ -20,7 +20,7 @@ export const StatisticsSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("statistics")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Statistics`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Statistics`}</h2>
</div>
</header>

View File

@ -16,11 +16,11 @@ export const TemplateSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("template")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Template`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Template`}</h2>
</div>
</header>
<main className="grid grid-cols-2 gap-5 @lg/right:grid-cols-3 @2xl/right:grid-cols-4">
<main className="grid grid-cols-2 gap-8 @lg/right:grid-cols-3 @2xl/right:grid-cols-4">
{templatesList.map((template, index) => (
<AspectRatio key={template} ratio={1 / 1.4142}>
<motion.div

View File

@ -17,11 +17,11 @@ export const ThemeSection = () => {
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("theme")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Theme`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Theme`}</h2>
</div>
</header>
<main className="grid gap-y-4">
<main className="grid gap-y-6">
<div className="mb-2 grid grid-cols-6 flex-wrap justify-items-center gap-y-4 @xs/right:grid-cols-9">
{colors.map((color) => (
<div

View File

@ -58,15 +58,15 @@ export const TypographySection = () => {
}, [typography.font.family]);
return (
<section id="typography" className="grid gap-y-6">
<section id="typography" className="grid gap-y-8">
<header className="flex items-center justify-between">
<div className="flex items-center gap-x-4">
{getSectionIcon("typography")}
<h2 className="line-clamp-1 text-3xl font-bold">{t`Typography`}</h2>
<h2 className="line-clamp-1 text-2xl font-bold lg:text-3xl">{t`Typography`}</h2>
</div>
</header>
<main className="grid gap-y-4">
<main className="grid gap-y-6">
<div className="grid grid-cols-2 gap-4">
{fontSuggestions.map((font) => (
<Button
@ -75,7 +75,7 @@ export const TypographySection = () => {
style={{ fontFamily: font }}
disabled={typography.font.family === font}
className={cn(
"flex h-12 items-center justify-center overflow-hidden rounded border text-center text-sm ring-primary transition-colors hover:bg-secondary-accent focus:outline-none focus:ring-1 disabled:opacity-100",
"flex h-12 items-center justify-center overflow-hidden rounded border text-center text-xs ring-primary transition-colors hover:bg-secondary-accent focus:outline-none focus:ring-1 disabled:opacity-100 lg:text-sm",
typography.font.family === font && "ring-1",
)}
onClick={() => {
@ -167,7 +167,7 @@ export const TypographySection = () => {
<div className="space-y-1.5">
<Label>{t`Options`}</Label>
<div className="flex items-center gap-x-4 py-2">
<div className="flex items-center gap-x-4 py-1">
<Switch
id="metadata.typography.hideIcons"
checked={typography.hideIcons}
@ -178,7 +178,7 @@ export const TypographySection = () => {
<Label htmlFor="metadata.typography.hideIcons">{t`Hide Icons`}</Label>
</div>
<div className="flex items-center gap-x-4 py-2">
<div className="flex items-center gap-x-4 py-1">
<Switch
id="metadata.typography.underlineLinks"
checked={typography.underlineLinks}

View File

@ -1,4 +1,5 @@
import {
Code,
DiamondsFour,
DownloadSimple,
IconProps,
@ -19,6 +20,7 @@ export type MetadataKey =
| "layout"
| "typography"
| "theme"
| "css"
| "page"
| "locale"
| "sharing"
@ -45,6 +47,9 @@ export const getSectionIcon = (id: MetadataKey, props: IconProps = {}) => {
case "theme": {
return <Palette size={18} {...props} />;
}
case "css": {
return <Code size={18} {...props} />;
}
case "page": {
return <ReadCvLogo size={18} {...props} />;
}

View File

@ -3,7 +3,7 @@ import { FadersHorizontal, ReadCvLogo } from "@phosphor-icons/react";
import { Button, KeyboardShortcut, Separator } from "@reactive-resume/ui";
import { cn } from "@reactive-resume/utils";
import { motion } from "framer-motion";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { Link, useLocation, useNavigate } from "react-router";
import useKeyboardShortcut from "use-keyboard-shortcut";
import { Copyright } from "@/client/components/copyright";
@ -71,12 +71,12 @@ export const Sidebar = ({ setOpen }: SidebarProps) => {
const navigate = useNavigate();
useKeyboardShortcut(["shift", "r"], () => {
navigate("/dashboard/resumes");
void navigate("/dashboard/resumes");
setOpen?.(false);
});
useKeyboardShortcut(["shift", "s"], () => {
navigate("/dashboard/settings");
void navigate("/dashboard/settings");
setOpen?.(false);
});

View File

@ -2,7 +2,7 @@ import { SidebarSimple } from "@phosphor-icons/react";
import { Button, Sheet, SheetClose, SheetContent, SheetTrigger } from "@reactive-resume/ui";
import { motion } from "framer-motion";
import { useState } from "react";
import { Outlet } from "react-router-dom";
import { Outlet } from "react-router";
import { Sidebar } from "./_components/sidebar";

View File

@ -33,7 +33,8 @@ import {
Input,
Tooltip,
} from "@reactive-resume/ui";
import { cn, generateRandomName, kebabCase } from "@reactive-resume/utils";
import { cn, generateRandomName } from "@reactive-resume/utils";
import slugify from "@sindresorhus/slugify";
import { useEffect } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
@ -42,7 +43,7 @@ import { useCreateResume, useDeleteResume, useUpdateResume } from "@/client/serv
import { useImportResume } from "@/client/services/resume/import";
import { useDialog } from "@/client/stores/dialog";
const formSchema = createResumeSchema.extend({ id: idSchema.optional() });
const formSchema = createResumeSchema.extend({ id: idSchema.optional(), slug: z.string() });
type FormValues = z.infer<typeof formSchema>;
@ -71,7 +72,7 @@ export const ResumeDialog = () => {
}, [isOpen, payload]);
useEffect(() => {
const slug = kebabCase(form.watch("title"));
const slug = slugify(form.watch("title"));
form.setValue("slug", slug);
}, [form.watch("title")]);
@ -122,7 +123,7 @@ export const ResumeDialog = () => {
const onGenerateRandomName = () => {
const name = generateRandomName();
form.setValue("title", name);
form.setValue("slug", kebabCase(name));
form.setValue("slug", slugify(name));
};
const onCreateSample = async () => {
@ -130,7 +131,7 @@ export const ResumeDialog = () => {
await duplicateResume({
title: randomName,
slug: kebabCase(randomName),
slug: slugify(randomName),
data: sampleResume,
});

View File

@ -18,7 +18,7 @@ import {
import { cn } from "@reactive-resume/utils";
import dayjs from "dayjs";
import { AnimatePresence, motion } from "framer-motion";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { useDialog } from "@/client/stores/dialog";
@ -37,7 +37,7 @@ export const ResumeCard = ({ resume }: Props) => {
const lastUpdated = dayjs().to(resume.updatedAt);
const onOpen = () => {
navigate(`/builder/${resume.id}`);
void navigate(`/builder/${resume.id}`);
};
const onUpdate = () => {

View File

@ -22,7 +22,7 @@ import {
DropdownMenuTrigger,
} from "@reactive-resume/ui";
import dayjs from "dayjs";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { useDialog } from "@/client/stores/dialog";
@ -40,7 +40,7 @@ export const ResumeListItem = ({ resume }: Props) => {
const lastUpdated = dayjs().to(resume.updatedAt);
const onOpen = () => {
navigate(`/builder/${resume.id}`);
void navigate(`/builder/${resume.id}`);
};
const onUpdate = () => {

View File

@ -49,7 +49,10 @@ export const ResumesPage = () => {
</TabsList>
</div>
<ScrollArea className="h-[calc(100vh-140px)] lg:h-[calc(100vh-88px)]">
<ScrollArea
allowOverflow
className="h-[calc(100vh-140px)] overflow-visible lg:h-[calc(100vh-88px)]"
>
<TabsContent value="grid">
<GridView />
</TabsContent>

View File

@ -151,7 +151,7 @@ export const AccountSettings = () => {
<FormItem>
<FormLabel>{t`Name`}</FormLabel>
<FormControl>
<Input {...field} />
<Input autoComplete="name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -165,7 +165,7 @@ export const AccountSettings = () => {
<FormItem>
<FormLabel>{t`Username`}</FormLabel>
<FormControl>
<Input {...field} />
<Input autoComplete="username" className="lowercase" {...field} />
</FormControl>
<FormMessage />
</FormItem>
@ -179,7 +179,7 @@ export const AccountSettings = () => {
<FormItem>
<FormLabel>{t`Email`}</FormLabel>
<FormControl>
<Input type="email" {...field} />
<Input type="email" autoComplete="email" className="lowercase" {...field} />
</FormControl>
<FormDescription
className={cn(

View File

@ -11,7 +11,7 @@ import {
Input,
} from "@reactive-resume/ui";
import { useForm } from "react-hook-form";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { useCounter } from "usehooks-ts";
import { z } from "zod";
@ -52,7 +52,7 @@ export const DangerZoneSettings = () => {
title: t`Your account and all your data has been deleted successfully. Goodbye!`,
});
navigate("/");
void navigate("/");
}
};

View File

@ -84,7 +84,7 @@ export const SecuritySettings = () => {
<FormItem>
<FormLabel>{t`New Password`}</FormLabel>
<FormControl>
<Input type="password" {...field} />
<Input type="password" autoComplete="new-password" {...field} />
</FormControl>
</FormItem>
)}
@ -97,7 +97,7 @@ export const SecuritySettings = () => {
<FormItem>
<FormLabel>{t`Confirm New Password`}</FormLabel>
<FormControl>
<Input type="password" {...field} />
<Input type="password" autoComplete="new-password" {...field} />
</FormControl>
{fieldState.error && (
<FormDescription className="text-error-foreground">

View File

@ -1,6 +1,6 @@
import { t } from "@lingui/macro";
import { Separator } from "@reactive-resume/ui";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { Copyright } from "@/client/components/copyright";
import { LocaleSwitch } from "@/client/components/locale-switch";

View File

@ -1,5 +1,5 @@
import { motion } from "framer-motion";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { Logo } from "@/client/components/logo";

View File

@ -1,5 +1,5 @@
import { ScrollArea } from "@reactive-resume/ui";
import { Outlet } from "react-router-dom";
import { Outlet } from "react-router";
import { Footer } from "./components/footer";
import { Header } from "./components/header";

View File

@ -1,7 +1,7 @@
import { t } from "@lingui/macro";
import { Book, SignOut } from "@phosphor-icons/react";
import { Button } from "@reactive-resume/ui";
import { Link } from "react-router-dom";
import { Link } from "react-router";
import { useLogout } from "@/client/services/auth";
import { useAuthStore } from "@/client/stores/auth";

View File

@ -9,9 +9,9 @@ type Statistic = {
export const StatisticsSection = () => {
const stats: Statistic[] = [
{ name: t`GitHub Stars`, value: 19_500 },
{ name: t`Users Signed Up`, value: 500_000 },
{ name: t`Resumes Generated`, value: 700_000 },
{ name: t`GitHub Stars`, value: 27_000 },
{ name: t`Users Signed Up`, value: 650_000 },
{ name: t`Resumes Generated`, value: 840_000 },
];
return (

View File

@ -51,7 +51,7 @@ export const SupportSection = () => (
</a>
<a href="https://paypal.me/amruthde" rel="noreferrer noopener nofollow" target="_blank">
{/* eslint-disable-next-line lingui/no-unlocalized-strings */}
<img src="/support-logos/paypal.svg" className=" max-h-[28px]" alt="PayPal" />
<img src="/support-logos/paypal.svg" className="max-h-[28px]" alt="PayPal" />
</a>
</div>

View File

@ -42,7 +42,7 @@ export const TemplatesSection = () => (
alt={template}
loading="lazy"
src={`/templates/jpg/${template}.jpg`}
className=" aspect-[1/1.4142] h-[400px] rounded object-cover lg:h-[600px]"
className="aspect-[1/1.4142] h-[400px] rounded object-cover lg:h-[600px]"
/>
</motion.a>
))}

View File

@ -5,7 +5,7 @@ import { Button } from "@reactive-resume/ui";
import { pageSizeMap } from "@reactive-resume/utils";
import { useCallback, useEffect, useRef } from "react";
import { Helmet } from "react-helmet-async";
import { Link, LoaderFunction, redirect, useLoaderData } from "react-router-dom";
import { Link, LoaderFunction, redirect, useLoaderData } from "react-router";
import { Icon } from "@/client/components/icon";
import { ThemeSwitch } from "@/client/components/theme-switch";
@ -22,8 +22,8 @@ export const PublicResumePage = () => {
const { printResume, loading } = usePrintResume();
const { id, title, data: resume } = useLoaderData() as ResumeDto;
const format = resume.metadata.page.format;
const { id, title, data: resume } = useLoaderData();
const format = resume.metadata.page.format as keyof typeof pageSizeMap;
const updateResumeInFrame = useCallback(() => {
if (!frameRef.current?.contentWindow) return;

View File

@ -1,7 +1,7 @@
import { TooltipProvider } from "@reactive-resume/ui";
import { QueryClientProvider } from "@tanstack/react-query";
import { HelmetProvider } from "react-helmet-async";
import { Outlet } from "react-router-dom";
import { Outlet } from "react-router";
import { helmetContext } from "../constants/helmet";
import { queryClient } from "../libs/query-client";

View File

@ -1,4 +1,4 @@
import { Navigate, Outlet, useLocation } from "react-router-dom";
import { Navigate, Outlet, useLocation } from "react-router";
import { useUser } from "@/client/services/user";

View File

@ -1,4 +1,4 @@
import { Navigate, Outlet, useSearchParams } from "react-router-dom";
import { Navigate, Outlet, useSearchParams } from "react-router";
import { useAuthStore } from "@/client/stores/auth";

View File

@ -1,4 +1,4 @@
import { createBrowserRouter, createRoutesFromElements, Navigate, Route } from "react-router-dom";
import { createBrowserRouter, createRoutesFromElements, Navigate, Route } from "react-router";
import { BackupOtpPage } from "../pages/auth/backup-otp/page";
import { ForgotPasswordPage } from "../pages/auth/forgot-password/page";
@ -23,7 +23,8 @@ import { GuestGuard } from "./guards/guest";
import { authLoader } from "./loaders/auth";
export const routes = createRoutesFromElements(
<Route element={<Providers />}>
// eslint-disable-next-line lingui/no-unlocalized-strings
<Route element={<Providers />} hydrateFallbackElement={<div>Loading...</div>}>
<Route element={<HomeLayout />}>
<Route path="/" element={<HomePage />} />

View File

@ -1,5 +1,5 @@
import { authResponseSchema, UserDto } from "@reactive-resume/dto";
import { LoaderFunction, redirect } from "react-router-dom";
import { LoaderFunction, redirect } from "react-router";
import { USER_KEY } from "@/client/constants/query-keys";
import { queryClient } from "@/client/libs/query-client";

View File

@ -1,7 +1,7 @@
import { AuthResponseDto, LoginDto } from "@reactive-resume/dto";
import { useMutation } from "@tanstack/react-query";
import { AxiosResponse } from "axios";
import { useNavigate } from "react-router-dom";
import { useNavigate } from "react-router";
import { axios } from "@/client/libs/axios";
import { queryClient } from "@/client/libs/query-client";
@ -28,7 +28,7 @@ export const useLogin = () => {
mutationFn: login,
onSuccess: (data) => {
if (data.status === "2fa_required") {
navigate("/auth/verify-otp");
void navigate("/auth/verify-otp");
return;
}

View File

@ -1,5 +1,5 @@
import { idSchema } from "@reactive-resume/schema";
import { z } from "nestjs-zod/z";
import { z } from "zod";
export const payloadSchema = z.object({
id: idSchema,

View File

@ -1,4 +1,4 @@
import { z } from "nestjs-zod/z";
import { z } from "zod";
export const configSchema = z.object({
NODE_ENV: z.enum(["development", "production"]).default("production"),

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