mirror of
https://github.com/AmruthPillai/Reactive-Resume.git
synced 2025-11-10 20:42:29 +10:00
Compare commits
54 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a5dc15dc08 | |||
| eab996f7e7 | |||
| 43c5a33773 | |||
| 7fb0226ddc | |||
| db6e7a7480 | |||
| 6335ad1571 | |||
| 2d62504895 | |||
| e34d0cebe5 | |||
| 0053d696ff | |||
| 6fb0a72a56 | |||
| 5b3e91e34d | |||
| d1a5a41e4d | |||
| d0a07686a5 | |||
| ffa4747ed6 | |||
| b963813910 | |||
| 58950ed0ef | |||
| 1c7a6c952f | |||
| 3ae651fece | |||
| bd52983780 | |||
| 39daed3502 | |||
| dda47f51ec | |||
| 374b9bcc58 | |||
| f0b18019d5 | |||
| 5c10f3d866 | |||
| 5d839e5420 | |||
| febbdefc0b | |||
| 6110440682 | |||
| d0a174d7b7 | |||
| 6708570c49 | |||
| bb4bbf4174 | |||
| a3ef6520e7 | |||
| 0fec5ce86d | |||
| 26e34b6b83 | |||
| 007243f2c3 | |||
| 9d0a0bba86 | |||
| 89a44cc33a | |||
| 92856b6f06 | |||
| c968188080 | |||
| 1c5c4d0117 | |||
| d6fee1e3a6 | |||
| d84aeee968 | |||
| c87242142d | |||
| 2c84976e28 | |||
| 31ed9f41a0 | |||
| 700b98fcb7 | |||
| 3f01a9e58e | |||
| b9de35f0d9 | |||
| 5827576ffb | |||
| acc9becf1a | |||
| c2837838ee | |||
| 304fd93ece | |||
| 491bbcadcc | |||
| 5bc0230a5a | |||
| c9a2c27b2d |
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
3
.github/ISSUE_TEMPLATE/bug-report.yml
vendored
@ -76,7 +76,10 @@ body:
|
||||
- Bronzor
|
||||
- Chikorita
|
||||
- Ditto
|
||||
- Gengar
|
||||
- Glalie
|
||||
- Kakuna
|
||||
- Leafish
|
||||
- Nosepass
|
||||
- Onyx
|
||||
- Pikachu
|
||||
|
||||
2
.github/workflows/publish-docker-image.yml
vendored
2
.github/workflows/publish-docker-image.yml
vendored
@ -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
|
||||
|
||||
@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"endOfLine": "auto"
|
||||
"endOfLine": "auto",
|
||||
"plugins": ["prettier-plugin-tailwindcss"],
|
||||
"tailwindFunctions": ["cn", "cva"]
|
||||
}
|
||||
|
||||
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@ -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
132
CODE_OF_CONDUCT.md
Normal 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
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}`} />;
|
||||
};
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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 />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { useEffect } from "react";
|
||||
import { Outlet } from "react-router-dom";
|
||||
import { Outlet } from "react-router";
|
||||
|
||||
import { useArtboardStore } from "../store/artboard";
|
||||
|
||||
|
||||
@ -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 />} />
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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) => (
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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) => (
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
155
apps/client/public/styles/prism-dark.css
Normal file
155
apps/client/public/styles/prism-dark.css
Normal 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;
|
||||
}
|
||||
}
|
||||
167
apps/client/public/styles/prism-light.css
Normal file
167
apps/client/public/styles/prism-light.css
Normal 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));
|
||||
}
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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": {
|
||||
|
||||
@ -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`}
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 />
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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({
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}>
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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`}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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} />;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
});
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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,
|
||||
});
|
||||
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -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("/");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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>
|
||||
|
||||
|
||||
@ -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>
|
||||
))}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
|
||||
@ -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 />} />
|
||||
|
||||
|
||||
@ -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";
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
Reference in New Issue
Block a user