Compare commits
79 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 65ba13e503 | |||
| 44ec9e1d43 | |||
| 7c53949741 | |||
| 6d37769e38 | |||
| c525f8d2cc | |||
| 04dfcae898 | |||
| 9b5a99a8ca | |||
| c44f1c5282 | |||
| 24dfa99034 | |||
| b995a6b6c0 | |||
| 3e76a52306 | |||
| 4e91a2e2ef | |||
| 4314912d5a | |||
| 93da5157ff | |||
| cd21860535 | |||
| 7054623678 | |||
| ec4e43d4fc | |||
| 1d4529128f | |||
| 60ed3e2a8d | |||
| 5b67e7c0b4 | |||
| 1399d3c44b | |||
| eb543cf32d | |||
| 9c6d9833d6 | |||
| f50cbd71b7 | |||
| 3d5b3db321 | |||
| e438602773 | |||
| f42b29e4ac | |||
| 92995d9c2b | |||
| b21f1648c4 | |||
| 94c04b44df | |||
| 54ed0678bf | |||
| 817ec96963 | |||
| 8ad5458e2a | |||
| a87c5edd60 | |||
| e4327736bd | |||
| 1fddbe5f92 | |||
| 2c482e7df8 | |||
| c8edcd3dad | |||
| a82c25c7cb | |||
| 73b423030f | |||
| 809551d0f8 | |||
| e795ec64d6 | |||
| f8373e4798 | |||
| a31c434fbc | |||
| 1fa8aae80a | |||
| 27b60a4df9 | |||
| d21983aab4 | |||
| 9406d78653 | |||
| c7ae0e94d7 | |||
| 308a8e3ae3 | |||
| 4c90cc1838 | |||
| 460a40711e | |||
| 18cf814779 | |||
| a9656afbbf | |||
| 385fe008ce | |||
| 7e25e853d7 | |||
| de5adbe3d2 | |||
| c239ae3f49 | |||
| 1bfdff5b30 | |||
| 63db927924 | |||
| 9a34e4af27 | |||
| b5589338ec | |||
| a19059aa76 | |||
| 15f962310b | |||
| a32def2086 | |||
| 21af624096 | |||
| 227870ac78 | |||
| 33cb3dbd6a | |||
| eb7813ac6f | |||
| 0f8f2fe560 | |||
| 51f38f0884 | |||
| 6b93fd179d | |||
| 9385f36832 | |||
| a5dc15dc08 | |||
| eab996f7e7 | |||
| 43c5a33773 | |||
| 7fb0226ddc | |||
| db6e7a7480 | |||
| 6335ad1571 |
11
.env.example
@ -68,3 +68,14 @@ STORAGE_SKIP_BUCKET_CHECK=false
|
|||||||
# GOOGLE_CLIENT_ID=
|
# GOOGLE_CLIENT_ID=
|
||||||
# GOOGLE_CLIENT_SECRET=
|
# GOOGLE_CLIENT_SECRET=
|
||||||
# GOOGLE_CALLBACK_URL=http://localhost:5173/api/auth/google/callback
|
# GOOGLE_CALLBACK_URL=http://localhost:5173/api/auth/google/callback
|
||||||
|
|
||||||
|
# OpenID (Optional)
|
||||||
|
# VITE_OPENID_NAME=
|
||||||
|
# OPENID_AUTHORIZATION_URL=
|
||||||
|
# OPENID_CALLBACK_URL=http://localhost:5173/api/auth/openid/callback
|
||||||
|
# OPENID_CLIENT_ID=
|
||||||
|
# OPENID_CLIENT_SECRET=
|
||||||
|
# OPENID_ISSUER=
|
||||||
|
# OPENID_SCOPE=openid profile email
|
||||||
|
# OPENID_TOKEN_URL=
|
||||||
|
# OPENID_USER_INFO_URL=
|
||||||
|
|||||||
@ -78,6 +78,7 @@
|
|||||||
"@typescript-eslint/no-misused-promises": "off",
|
"@typescript-eslint/no-misused-promises": "off",
|
||||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||||
|
"@typescript-eslint/consistent-type-imports": "error",
|
||||||
"@typescript-eslint/restrict-template-expressions": "off",
|
"@typescript-eslint/restrict-template-expressions": "off",
|
||||||
"@typescript-eslint/no-redundant-type-constituents": "off",
|
"@typescript-eslint/no-redundant-type-constituents": "off",
|
||||||
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
|
"@typescript-eslint/consistent-type-definitions": ["error", "type"],
|
||||||
|
|||||||
2
.github/workflows/lint-test-build.yml
vendored
@ -39,7 +39,7 @@ jobs:
|
|||||||
run: pnpm run lint
|
run: pnpm run lint
|
||||||
|
|
||||||
- name: Format
|
- name: Format
|
||||||
run: pnpm run format:check
|
run: pnpm run format
|
||||||
|
|
||||||
- name: Test
|
- name: Test
|
||||||
run: pnpm run test
|
run: pnpm run test
|
||||||
|
|||||||
2
.github/workflows/publish-docker-image.yml
vendored
@ -153,7 +153,7 @@ jobs:
|
|||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
|
||||||
- name: Deploy the latest image on rxresu.me
|
- name: Deploy the latest image on rxresu.me
|
||||||
run: curl -Xk POST ${{ secrets.SERVICE_WEBHOOK }}
|
run: curl -kX POST ${{ secrets.SERVICE_WEBHOOK }}
|
||||||
|
|
||||||
- name: Inform about the release on Discord
|
- name: Inform about the release on Discord
|
||||||
uses: sarisia/actions-status-discord@v1.14.3
|
uses: sarisia/actions-status-discord@v1.14.3
|
||||||
|
|||||||
1
.gitignore
vendored
@ -40,6 +40,7 @@ Thumbs.db
|
|||||||
# Generated Files
|
# Generated Files
|
||||||
.nx
|
.nx
|
||||||
.swc
|
.swc
|
||||||
|
.turbo
|
||||||
fly.toml
|
fly.toml
|
||||||
stats.html
|
stats.html
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,9 @@
|
|||||||
"install": "always",
|
"install": "always",
|
||||||
"packageManager": "pnpm",
|
"packageManager": "pnpm",
|
||||||
"reject": [
|
"reject": [
|
||||||
|
"nx",
|
||||||
"eslint",
|
"eslint",
|
||||||
|
"@nx/*",
|
||||||
"@swc/*",
|
"@swc/*",
|
||||||
"@swc-node/*",
|
"@swc-node/*",
|
||||||
"@reactive-resume/*",
|
"@reactive-resume/*",
|
||||||
|
|||||||
4
.vscode/settings.json
vendored
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"css.validate": false,
|
"css.validate": false,
|
||||||
"vitest.disableWorkspaceWarning": true,
|
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"tailwindCSS.experimental.classRegex": [
|
"tailwindCSS.experimental.classRegex": [
|
||||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||||
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
["cn\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||||
]
|
],
|
||||||
|
"i18n-ally.localesPaths": ["apps/client/src/locales"]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ ARG NX_CLOUD_ACCESS_TOKEN
|
|||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|
||||||
RUN corepack enable pnpm && corepack prepare pnpm --activate
|
RUN corepack enable
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
|
|||||||
17
README.md
@ -1,4 +1,15 @@
|
|||||||

|
<div align="center" markdown="1">
|
||||||
|
<a href="https://go.warp.dev/Reactive-Resume">
|
||||||
|
<img alt="Warp Sponsorship" width="400" src="https://github.com/warpdotdev/brand-assets/blob/main/Github/Sponsor/Warp-Github-LG-03.png?raw=true" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
### [Warp, built for coding with multiple AI agents.](https://go.warp.dev/Reactive-Resume)
|
||||||
|
|
||||||
|
[Available for MacOS, Linux, & Windows](https://go.warp.dev/Reactive-Resume)<br>
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<img alt="Reactive Resume" width="800" src="https://i.imgur.com/FFc4nyZ.jpg" />
|
||||||
|
|
||||||

|

|
||||||
[](https://hub.docker.com/repository/docker/amruthpillai/reactive-resume)
|
[](https://hub.docker.com/repository/docker/amruthpillai/reactive-resume)
|
||||||
@ -12,6 +23,8 @@ A free and open-source resume builder that simplifies the process of creating, u
|
|||||||
|
|
||||||
### [Go to App](https://rxresu.me/) | [Docs](https://docs.rxresu.me/)
|
### [Go to App](https://rxresu.me/) | [Docs](https://docs.rxresu.me/)
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
## Description
|
## Description
|
||||||
|
|
||||||
Reactive Resume is a free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume. With zero user tracking or advertising, your privacy is a top priority. The platform is extremely user-friendly and can be self-hosted in less than 30 seconds if you wish to own your data completely.
|
Reactive Resume is a free and open-source resume builder that simplifies the process of creating, updating, and sharing your resume. With zero user tracking or advertising, your privacy is a top priority. The platform is extremely user-friendly and can be self-hosted in less than 30 seconds if you wish to own your data completely.
|
||||||
@ -93,7 +106,7 @@ _By the community, for the community._
|
|||||||
A passion project by [Amruth Pillai](https://www.amruthpillai.com/)
|
A passion project by [Amruth Pillai](https://www.amruthpillai.com/)
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<a href="https://www.digitalocean.com/?utm_medium=opensource&utm_source=Reactive-Resume">
|
<a href="https://m.do.co/c/ceae1fff245e">
|
||||||
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="200px">
|
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="200px">
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@ -40,12 +40,5 @@
|
|||||||
|
|
||||||
<!-- Phosphor Icons -->
|
<!-- Phosphor Icons -->
|
||||||
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@ -1,13 +1,14 @@
|
|||||||
import { cn } from "@reactive-resume/utils";
|
import { forwardRef } from "react";
|
||||||
|
|
||||||
type BrandIconProps = {
|
type BrandIconProps = {
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BrandIcon = ({ slug }: BrandIconProps) => {
|
export const BrandIcon = forwardRef<HTMLImageElement, BrandIconProps>(({ slug }, ref) => {
|
||||||
if (slug === "linkedin") {
|
if (slug === "linkedin") {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
|
ref={ref}
|
||||||
alt="LinkedIn"
|
alt="LinkedIn"
|
||||||
className="size-4"
|
className="size-4"
|
||||||
src={`${window.location.origin}/support-logos/linkedin.svg`}
|
src={`${window.location.origin}/support-logos/linkedin.svg`}
|
||||||
@ -15,5 +16,9 @@ export const BrandIcon = ({ slug }: BrandIconProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <i className={cn("si si--color text-[1rem]", `si-${slug}`)} />;
|
return (
|
||||||
};
|
<img ref={ref} alt={slug} className="size-4" src={`https://cdn.simpleicons.org/${slug}`} />
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
BrandIcon.displayName = "BrandIcon";
|
||||||
|
|||||||
5
apps/artboard/src/constants/helmet.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { HelmetData } from "react-helmet-async";
|
||||||
|
|
||||||
|
export const helmetData = new HelmetData({});
|
||||||
|
|
||||||
|
export const helmetContext = helmetData.context;
|
||||||
@ -1,6 +1,6 @@
|
|||||||
import { StrictMode } from "react";
|
import { StrictMode } from "react";
|
||||||
import * as ReactDOM from "react-dom/client";
|
import * as ReactDOM from "react-dom/client";
|
||||||
import { RouterProvider } from "react-router-dom";
|
import { RouterProvider } from "react-router";
|
||||||
|
|
||||||
import { router } from "./router";
|
import { router } from "./router";
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,12 @@
|
|||||||
import { useEffect, useMemo } from "react";
|
import { useEffect, useMemo } from "react";
|
||||||
import { Outlet } from "react-router-dom";
|
import { Helmet } from "react-helmet-async";
|
||||||
|
import { Outlet } from "react-router";
|
||||||
import webfontloader from "webfontloader";
|
import webfontloader from "webfontloader";
|
||||||
|
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
|
|
||||||
export const ArtboardPage = () => {
|
export const ArtboardPage = () => {
|
||||||
|
const name = useArtboardStore((state) => state.resume.basics.name);
|
||||||
const metadata = useArtboardStore((state) => state.resume.metadata);
|
const metadata = useArtboardStore((state) => state.resume.metadata);
|
||||||
|
|
||||||
const fontString = useMemo(() => {
|
const fontString = useMemo(() => {
|
||||||
@ -55,5 +57,18 @@ export const ArtboardPage = () => {
|
|||||||
}
|
}
|
||||||
}, [metadata]);
|
}, [metadata]);
|
||||||
|
|
||||||
return <Outlet />;
|
return (
|
||||||
|
<>
|
||||||
|
<Helmet>
|
||||||
|
<title>{name} | Reactive Resume</title>
|
||||||
|
{metadata.css.visible && (
|
||||||
|
<style id="custom-css" lang="css">
|
||||||
|
{metadata.css.value}
|
||||||
|
</style>
|
||||||
|
)}
|
||||||
|
</Helmet>
|
||||||
|
|
||||||
|
<Outlet />
|
||||||
|
</>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import { SectionKey } from "@reactive-resume/schema";
|
import type { SectionKey } from "@reactive-resume/schema";
|
||||||
import { pageSizeMap, Template } from "@reactive-resume/utils";
|
import type { Template } from "@reactive-resume/utils";
|
||||||
|
import { pageSizeMap } from "@reactive-resume/utils";
|
||||||
import { AnimatePresence, motion } from "framer-motion";
|
import { AnimatePresence, motion } from "framer-motion";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useMemo, useRef, useState } from "react";
|
||||||
import { ReactZoomPanPinchRef, TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
import type { ReactZoomPanPinchRef } from "react-zoom-pan-pinch";
|
||||||
|
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
||||||
|
|
||||||
import { MM_TO_PX, Page } from "../components/page";
|
import { MM_TO_PX, Page } from "../components/page";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { SectionKey } from "@reactive-resume/schema";
|
import type { SectionKey } from "@reactive-resume/schema";
|
||||||
import { Template } from "@reactive-resume/utils";
|
import type { Template } from "@reactive-resume/utils";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import { Page } from "../components/page";
|
import { Page } from "../components/page";
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { Outlet } from "react-router-dom";
|
import { HelmetProvider } from "react-helmet-async";
|
||||||
|
import { Outlet } from "react-router";
|
||||||
|
|
||||||
|
import { helmetContext } from "../constants/helmet";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
|
|
||||||
export const Providers = () => {
|
export const Providers = () => {
|
||||||
@ -10,35 +12,28 @@ export const Providers = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleMessage = (event: MessageEvent) => {
|
const handleMessage = (event: MessageEvent) => {
|
||||||
if (event.origin !== window.location.origin) return;
|
if (event.origin !== window.location.origin) return;
|
||||||
|
|
||||||
if (event.data.type === "SET_RESUME") setResume(event.data.payload);
|
if (event.data.type === "SET_RESUME") setResume(event.data.payload);
|
||||||
if (event.data.type === "SET_THEME") {
|
|
||||||
event.data.payload === "dark"
|
|
||||||
? document.documentElement.classList.add("dark")
|
|
||||||
: document.documentElement.classList.remove("dark");
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const resumeData = window.localStorage.getItem("resume");
|
window.addEventListener("message", handleMessage, false);
|
||||||
if (resumeData) {
|
|
||||||
setResume(JSON.parse(resumeData));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
window.addEventListener("message", handleMessage);
|
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("message", handleMessage);
|
window.removeEventListener("message", handleMessage, false);
|
||||||
};
|
};
|
||||||
}, [setResume]);
|
}, []);
|
||||||
|
|
||||||
// Only for testing, in production this will be fetched from window.postMessage
|
useEffect(() => {
|
||||||
// useEffect(() => {
|
const resumeData = window.localStorage.getItem("resume");
|
||||||
// setResume(sampleResume);
|
|
||||||
// }, [setResume]);
|
if (resumeData) setResume(JSON.parse(resumeData));
|
||||||
|
}, [window.localStorage.getItem("resume")]);
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
if (!resume) return null;
|
if (!resume) return null;
|
||||||
|
|
||||||
return <Outlet />;
|
return (
|
||||||
|
<HelmetProvider context={helmetContext}>
|
||||||
|
<Outlet />
|
||||||
|
</HelmetProvider>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 { ArtboardPage } from "../pages/artboard";
|
||||||
import { BuilderLayout } from "../pages/builder";
|
import { BuilderLayout } from "../pages/builder";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ResumeData } from "@reactive-resume/schema";
|
import type { ResumeData } from "@reactive-resume/schema";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
|
|
||||||
export type ArtboardStore = {
|
export type ArtboardStore = {
|
||||||
|
|||||||
@ -8,6 +8,10 @@
|
|||||||
@apply border-current;
|
@apply border-current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
@apply antialiased;
|
@apply antialiased;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Profile,
|
Profile,
|
||||||
@ -15,16 +13,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, isEmptyString, isUrl, linearTransform } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, isEmptyString, isUrl, linearTransform, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -100,9 +98,9 @@ const Summary = () => {
|
|||||||
<div className="absolute left-[-4.5px] top-[8px] hidden size-[8px] rounded-full bg-primary group-[.main]:block" />
|
<div className="absolute left-[-4.5px] top-[8px] hidden size-[8px] rounded-full bg-primary group-[.main]:block" />
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</main>
|
</main>
|
||||||
</section>
|
</section>
|
||||||
@ -226,7 +224,10 @@ const Section = <T,>({
|
|||||||
<div>{children?.(item as T)}</div>
|
<div>{children?.(item as T)}</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Profile,
|
Profile,
|
||||||
@ -15,16 +13,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -91,9 +89,9 @@ const Summary = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg col-span-4"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg col-span-4"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -207,7 +205,10 @@ const Section = <T,>({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Profile,
|
Profile,
|
||||||
@ -15,16 +13,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -91,9 +89,9 @@ const Summary = () => {
|
|||||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -211,7 +209,7 @@ const Section = <T,>({
|
|||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: summary }}
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
className="wysiwyg group-[.sidebar]:prose-invert"
|
className="wysiwyg group-[.sidebar]:prose-invert"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Profile,
|
Profile,
|
||||||
@ -15,16 +13,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -111,9 +109,9 @@ const Summary = () => {
|
|||||||
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
|
<h4 className="mb-2 text-base font-bold">{section.name}</h4>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -232,7 +230,10 @@ const Section = <T,>({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Profile,
|
Profile,
|
||||||
@ -15,16 +13,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, hexToRgb, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, hexToRgb, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -91,9 +89,9 @@ const Summary = () => {
|
|||||||
<div className="p-custom space-y-4" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}>
|
<div className="p-custom space-y-4" style={{ backgroundColor: hexToRgb(primaryColor, 0.2) }}>
|
||||||
<section id={section.id}>
|
<section id={section.id}>
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
@ -212,7 +210,10 @@ const Section = <T,>({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Profile,
|
Profile,
|
||||||
@ -15,16 +13,23 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, hexToRgb, isEmptyString, isUrl, linearTransform } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import {
|
||||||
|
cn,
|
||||||
|
hexToRgb,
|
||||||
|
isEmptyString,
|
||||||
|
isUrl,
|
||||||
|
linearTransform,
|
||||||
|
sanitize,
|
||||||
|
} from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -91,9 +96,9 @@ const Summary = () => {
|
|||||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -215,7 +220,10 @@ const Section = <T,>({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { Template } from "@reactive-resume/utils";
|
import type { Template } from "@reactive-resume/utils";
|
||||||
|
|
||||||
import { Azurill } from "./azurill";
|
import { Azurill } from "./azurill";
|
||||||
import { Bronzor } from "./bronzor";
|
import { Bronzor } from "./bronzor";
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Project,
|
Project,
|
||||||
@ -14,16 +12,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -110,9 +108,9 @@ const Summary = () => {
|
|||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -223,7 +221,10 @@ const Section = <T,>({
|
|||||||
<div>{children?.(item as T)}</div>
|
<div>{children?.(item as T)}</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Project,
|
Project,
|
||||||
@ -14,16 +12,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, hexToRgb, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, hexToRgb, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -44,9 +42,9 @@ const Header = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -218,7 +216,10 @@ const Section = <T,>({
|
|||||||
<div>{children?.(item as T)}</div>
|
<div>{children?.(item as T)}</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Profile,
|
Profile,
|
||||||
@ -15,16 +13,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -107,9 +105,9 @@ const Summary = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@ -219,7 +217,10 @@ const Section = <T,>({
|
|||||||
{url !== undefined && section.separateLinks && <Link url={url} />}
|
{url !== undefined && section.separateLinks && <Link url={url} />}
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{keywords !== undefined && keywords.length > 0 && (
|
{keywords !== undefined && keywords.length > 0 && (
|
||||||
@ -254,7 +255,10 @@ const Section = <T,>({
|
|||||||
{url !== undefined && section.separateLinks && <Link url={url} />}
|
{url !== undefined && section.separateLinks && <Link url={url} />}
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{keywords !== undefined && keywords.length > 0 && (
|
{keywords !== undefined && keywords.length > 0 && (
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Project,
|
Project,
|
||||||
@ -14,16 +12,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import React, { Fragment } from "react";
|
import React, { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -111,9 +109,9 @@ const Summary = () => {
|
|||||||
<h4 className="font-bold text-primary">{section.name}</h4>
|
<h4 className="font-bold text-primary">{section.name}</h4>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -225,7 +223,10 @@ const Section = <T,>({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Profile,
|
Profile,
|
||||||
@ -15,16 +13,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -112,9 +110,9 @@ const Summary = () => {
|
|||||||
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
|
<h4 className="mb-2 border-b border-primary text-base font-bold">{section.name}</h4>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -240,7 +238,10 @@ const Section = <T,>({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
import {
|
import type {
|
||||||
Award,
|
Award,
|
||||||
Certification,
|
Certification,
|
||||||
CustomSection,
|
CustomSection,
|
||||||
CustomSectionGroup,
|
CustomSectionGroup,
|
||||||
Education,
|
|
||||||
Experience,
|
|
||||||
Interest,
|
Interest,
|
||||||
Language,
|
Language,
|
||||||
Profile,
|
Profile,
|
||||||
@ -15,16 +13,16 @@ import {
|
|||||||
SectionWithItem,
|
SectionWithItem,
|
||||||
Skill,
|
Skill,
|
||||||
URL,
|
URL,
|
||||||
Volunteer,
|
|
||||||
} from "@reactive-resume/schema";
|
} from "@reactive-resume/schema";
|
||||||
import { cn, isEmptyString, isUrl } from "@reactive-resume/utils";
|
import { Education, Experience, Volunteer } from "@reactive-resume/schema";
|
||||||
|
import { cn, isEmptyString, isUrl, sanitize } from "@reactive-resume/utils";
|
||||||
import get from "lodash.get";
|
import get from "lodash.get";
|
||||||
import { Fragment } from "react";
|
import { Fragment } from "react";
|
||||||
|
|
||||||
import { BrandIcon } from "../components/brand-icon";
|
import { BrandIcon } from "../components/brand-icon";
|
||||||
import { Picture } from "../components/picture";
|
import { Picture } from "../components/picture";
|
||||||
import { useArtboardStore } from "../store/artboard";
|
import { useArtboardStore } from "../store/artboard";
|
||||||
import { TemplateProps } from "../types/template";
|
import type { TemplateProps } from "../types/template";
|
||||||
|
|
||||||
const Header = () => {
|
const Header = () => {
|
||||||
const basics = useArtboardStore((state) => state.resume.basics);
|
const basics = useArtboardStore((state) => state.resume.basics);
|
||||||
@ -92,9 +90,9 @@ const Summary = () => {
|
|||||||
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
<h4 className="mb-2 border-b pb-0.5 text-sm font-bold">{section.name}</h4>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
dangerouslySetInnerHTML={{ __html: section.content }}
|
dangerouslySetInnerHTML={{ __html: sanitize(section.content) }}
|
||||||
className="wysiwyg"
|
|
||||||
style={{ columns: section.columns }}
|
style={{ columns: section.columns }}
|
||||||
|
className="wysiwyg"
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
@ -206,7 +204,10 @@ const Section = <T,>({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{summary !== undefined && !isEmptyString(summary) && (
|
{summary !== undefined && !isEmptyString(summary) && (
|
||||||
<div dangerouslySetInnerHTML={{ __html: summary }} className="wysiwyg" />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: sanitize(summary) }}
|
||||||
|
className="wysiwyg"
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{level !== undefined && level > 0 && <Rating level={level} />}
|
{level !== undefined && level > 0 && <Rating level={level} />}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { SectionKey } from "@reactive-resume/schema";
|
import type { SectionKey } from "@reactive-resume/schema";
|
||||||
|
|
||||||
export type TemplateProps = {
|
export type TemplateProps = {
|
||||||
columns: SectionKey[][];
|
columns: SectionKey[][];
|
||||||
|
|||||||
@ -43,12 +43,5 @@
|
|||||||
|
|
||||||
<!-- Phosphor Icons -->
|
<!-- Phosphor Icons -->
|
||||||
<script src="https://unpkg.com/@phosphor-icons/web"></script>
|
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
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
@ -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));
|
||||||
|
}
|
||||||
0
apps/client/public/templates/jpg/azurill.jpg
Executable file → Normal file
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
0
apps/client/public/templates/jpg/bronzor.jpg
Executable file → Normal file
|
Before Width: | Height: | Size: 120 KiB After Width: | Height: | Size: 120 KiB |
0
apps/client/public/templates/jpg/chikorita.jpg
Executable file → Normal file
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 145 KiB |
0
apps/client/public/templates/jpg/ditto.jpg
Executable file → Normal file
|
Before Width: | Height: | Size: 138 KiB After Width: | Height: | Size: 138 KiB |
0
apps/client/public/templates/jpg/kakuna.jpg
Executable file → Normal file
|
Before Width: | Height: | Size: 119 KiB After Width: | Height: | Size: 119 KiB |
0
apps/client/public/templates/jpg/nosepass.jpg
Executable file → Normal file
|
Before Width: | Height: | Size: 99 KiB After Width: | Height: | Size: 99 KiB |
0
apps/client/public/templates/jpg/onyx.jpg
Executable file → Normal file
|
Before Width: | Height: | Size: 113 KiB After Width: | Height: | Size: 113 KiB |
0
apps/client/public/templates/jpg/pikachu.jpg
Executable file → Normal file
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 136 KiB |
0
apps/client/public/templates/jpg/rhyhorn.jpg
Executable file → Normal file
|
Before Width: | Height: | Size: 131 KiB After Width: | Height: | Size: 131 KiB |
@ -289,7 +289,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -314,7 +314,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -314,7 +314,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -315,7 +315,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -289,7 +289,7 @@
|
|||||||
[[], []]
|
[[], []]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -288,7 +288,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -287,7 +287,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -289,7 +289,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -306,7 +306,7 @@
|
|||||||
[["projects", "certifications", "skills", "languages", "references"], []]
|
[["projects", "certifications", "skills", "languages", "references"], []]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -287,7 +287,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -315,7 +315,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -288,7 +288,7 @@
|
|||||||
]
|
]
|
||||||
],
|
],
|
||||||
"css": {
|
"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
|
"visible": false
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
import {
|
import {
|
||||||
CaretDown,
|
CaretDownIcon,
|
||||||
ChatTeardropText,
|
ChatTeardropTextIcon,
|
||||||
CircleNotch,
|
CircleNotchIcon,
|
||||||
Exam,
|
ExamIcon,
|
||||||
MagicWand,
|
MagicWandIcon,
|
||||||
PenNib,
|
PenNibIcon,
|
||||||
} from "@phosphor-icons/react";
|
} from "@phosphor-icons/react";
|
||||||
import {
|
import {
|
||||||
Badge,
|
Badge,
|
||||||
@ -75,27 +75,31 @@ export const AiActions = ({ value, onChange, className }: Props) => {
|
|||||||
variant="primary"
|
variant="primary"
|
||||||
className="-rotate-90 bg-background px-2 text-[10px] leading-[10px]"
|
className="-rotate-90 bg-background px-2 text-[10px] leading-[10px]"
|
||||||
>
|
>
|
||||||
<MagicWand size={10} className="mr-1" />
|
<MagicWandIcon size={10} className="mr-1" />
|
||||||
{t`AI`}
|
{t`AI`}
|
||||||
</Badge>
|
</Badge>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button size="sm" variant="outline" disabled={!!loading} onClick={() => onClick("improve")}>
|
<Button size="sm" variant="outline" disabled={!!loading} onClick={() => onClick("improve")}>
|
||||||
{loading === "improve" ? <CircleNotch className="animate-spin" /> : <PenNib />}
|
{loading === "improve" ? <CircleNotchIcon className="animate-spin" /> : <PenNibIcon />}
|
||||||
<span className="ml-2 text-xs">{t`Improve Writing`}</span>
|
<span className="ml-2 text-xs">{t`Improve Writing`}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<Button size="sm" variant="outline" disabled={!!loading} onClick={() => onClick("fix")}>
|
<Button size="sm" variant="outline" disabled={!!loading} onClick={() => onClick("fix")}>
|
||||||
{loading === "fix" ? <CircleNotch className="animate-spin" /> : <Exam />}
|
{loading === "fix" ? <CircleNotchIcon className="animate-spin" /> : <ExamIcon />}
|
||||||
<span className="ml-2 text-xs">{t`Fix Spelling & Grammar`}</span>
|
<span className="ml-2 text-xs">{t`Fix Spelling & Grammar`}</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button size="sm" variant="outline" disabled={!!loading}>
|
<Button size="sm" variant="outline" disabled={!!loading}>
|
||||||
{loading === "tone" ? <CircleNotch className="animate-spin" /> : <ChatTeardropText />}
|
{loading === "tone" ? (
|
||||||
|
<CircleNotchIcon className="animate-spin" />
|
||||||
|
) : (
|
||||||
|
<ChatTeardropTextIcon />
|
||||||
|
)}
|
||||||
<span className="mx-2 text-xs">{t`Change Tone`}</span>
|
<span className="mx-2 text-xs">{t`Change Tone`}</span>
|
||||||
<CaretDown />
|
<CaretDownIcon />
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
|
|||||||
@ -1,13 +1,23 @@
|
|||||||
import { cn } from "@reactive-resume/utils";
|
import { forwardRef, useEffect } from "react";
|
||||||
|
import { useDebounceValue } from "usehooks-ts";
|
||||||
|
|
||||||
type BrandIconProps = {
|
type BrandIconProps = {
|
||||||
slug: string;
|
slug: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const BrandIcon = ({ slug }: BrandIconProps) => {
|
export const BrandIcon = forwardRef<HTMLImageElement, BrandIconProps>(({ slug }, ref) => {
|
||||||
if (slug === "linkedin") {
|
const [debouncedSlug, setValue] = useDebounceValue(slug, 600);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(slug);
|
||||||
|
}, [slug]);
|
||||||
|
|
||||||
|
if (!slug) return null;
|
||||||
|
|
||||||
|
if (debouncedSlug === "linkedin") {
|
||||||
return (
|
return (
|
||||||
<img
|
<img
|
||||||
|
ref={ref}
|
||||||
alt="LinkedIn"
|
alt="LinkedIn"
|
||||||
className="size-5"
|
className="size-5"
|
||||||
src={`${window.location.origin}/support-logos/linkedin.svg`}
|
src={`${window.location.origin}/support-logos/linkedin.svg`}
|
||||||
@ -15,5 +25,14 @@ export const BrandIcon = ({ slug }: BrandIconProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return <i className={cn("si si--color text-[1.25rem]", `si-${slug}`)} />;
|
return (
|
||||||
};
|
<img
|
||||||
|
ref={ref}
|
||||||
|
alt={debouncedSlug}
|
||||||
|
className="size-5"
|
||||||
|
src={`https://cdn.simpleicons.org/${debouncedSlug}`}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
BrandIcon.displayName = "BrandIcon";
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
import { CaretDown, Check } from "@phosphor-icons/react";
|
import { CaretDownIcon, CheckIcon } from "@phosphor-icons/react";
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Command,
|
Command,
|
||||||
@ -61,7 +61,7 @@ export const LocaleCombobox = ({ value, onValueChange }: Props) => {
|
|||||||
onValueChange(result.original.locale);
|
onValueChange(result.original.locale);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Check
|
<CheckIcon
|
||||||
className={cn(
|
className={cn(
|
||||||
"mr-2 size-4 opacity-0",
|
"mr-2 size-4 opacity-0",
|
||||||
value === original.locale && "opacity-100",
|
value === original.locale && "opacity-100",
|
||||||
@ -104,7 +104,7 @@ export const LocaleComboboxPopover = ({ value, onValueChange }: Props) => {
|
|||||||
<span className="line-clamp-1 text-left font-normal">
|
<span className="line-clamp-1 text-left font-normal">
|
||||||
{selected?.name} <span className="ml-1 text-xs opacity-50">({selected?.locale})</span>
|
{selected?.name} <span className="ml-1 text-xs opacity-50">({selected?.locale})</span>
|
||||||
</span>
|
</span>
|
||||||
<CaretDown
|
<CaretDownIcon
|
||||||
className={cn(
|
className={cn(
|
||||||
"ml-2 size-4 shrink-0 rotate-0 opacity-50 transition-transform",
|
"ml-2 size-4 shrink-0 rotate-0 opacity-50 transition-transform",
|
||||||
open && "rotate-180",
|
open && "rotate-180",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { useLingui } from "@lingui/react";
|
import { useLingui } from "@lingui/react";
|
||||||
import { Translate } from "@phosphor-icons/react";
|
import { TranslateIcon } from "@phosphor-icons/react";
|
||||||
import { Button, Popover, PopoverContent, PopoverTrigger } from "@reactive-resume/ui";
|
import { Button, Popover, PopoverContent, PopoverTrigger } from "@reactive-resume/ui";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
@ -14,7 +14,7 @@ export const LocaleSwitch = () => {
|
|||||||
<Popover open={open} onOpenChange={setOpen}>
|
<Popover open={open} onOpenChange={setOpen}>
|
||||||
<PopoverTrigger asChild>
|
<PopoverTrigger asChild>
|
||||||
<Button size="icon" variant="ghost">
|
<Button size="icon" variant="ghost">
|
||||||
<Translate size={20} />
|
<TranslateIcon size={20} />
|
||||||
</Button>
|
</Button>
|
||||||
</PopoverTrigger>
|
</PopoverTrigger>
|
||||||
<PopoverContent align="end" className="p-0">
|
<PopoverContent align="end" className="p-0">
|
||||||
|
|||||||
@ -1,14 +1,16 @@
|
|||||||
import { CloudSun, Moon, Sun } from "@phosphor-icons/react";
|
import { CloudSunIcon, MoonIcon, SunIcon } from "@phosphor-icons/react";
|
||||||
import { useTheme } from "@reactive-resume/hooks";
|
import { useTheme } from "@reactive-resume/hooks";
|
||||||
import { Button } from "@reactive-resume/ui";
|
import { Button } from "@reactive-resume/ui";
|
||||||
import { motion, Variants } from "framer-motion";
|
import type { Variants } from "framer-motion";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
size?: number;
|
size?: number;
|
||||||
|
className?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ThemeSwitch = ({ size = 20 }: Props) => {
|
export const ThemeSwitch = ({ size = 20, className }: Props) => {
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
|
|
||||||
const variants: Variants = useMemo(() => {
|
const variants: Variants = useMemo(() => {
|
||||||
@ -20,12 +22,12 @@ export const ThemeSwitch = ({ size = 20 }: Props) => {
|
|||||||
}, [size]);
|
}, [size]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Button size="icon" variant="ghost" onClick={toggleTheme}>
|
<Button size="icon" variant="ghost" className={className} onClick={toggleTheme}>
|
||||||
<div className="cursor-pointer overflow-hidden" style={{ width: size, height: size }}>
|
<div className="cursor-pointer overflow-hidden" style={{ width: size, height: size }}>
|
||||||
<motion.div animate={theme} variants={variants} className="flex">
|
<motion.div animate={theme} variants={variants} className="flex">
|
||||||
<Sun size={size} className="shrink-0" />
|
<SunIcon size={size} className="shrink-0" />
|
||||||
<CloudSun size={size} className="shrink-0" />
|
<CloudSunIcon size={size} className="shrink-0" />
|
||||||
<Moon size={size} className="shrink-0" />
|
<MoonIcon size={size} className="shrink-0" />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
KeyboardShortcut,
|
KeyboardShortcut,
|
||||||
} from "@reactive-resume/ui";
|
} from "@reactive-resume/ui";
|
||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router";
|
||||||
|
|
||||||
import { useLogout } from "../services/auth";
|
import { useLogout } from "../services/auth";
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ export const UserOptions = ({ children }: Props) => {
|
|||||||
<DropdownMenuContent side="top" align="start" className="w-48">
|
<DropdownMenuContent side="top" align="start" className="w-48">
|
||||||
<DropdownMenuItem
|
<DropdownMenuItem
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigate("/dashboard/settings");
|
void navigate("/dashboard/settings");
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t`Settings`}
|
{t`Settings`}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { ReactParallaxTiltProps } from "react-parallax-tilt";
|
import type { ReactParallaxTiltProps } from "react-parallax-tilt";
|
||||||
|
|
||||||
export const defaultTiltProps: ReactParallaxTiltProps = {
|
export const defaultTiltProps: ReactParallaxTiltProps = {
|
||||||
scale: 1.05,
|
scale: 1.05,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { QueryKey } from "@tanstack/react-query";
|
import type { QueryKey } from "@tanstack/react-query";
|
||||||
|
|
||||||
export const USER_KEY: QueryKey = ["user"];
|
export const USER_KEY: QueryKey = ["user"];
|
||||||
export const AUTH_PROVIDERS_KEY: QueryKey = ["auth", "providers"];
|
export const AUTH_PROVIDERS_KEY: QueryKey = ["auth", "providers"];
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { createId } from "@paralleldrive/cuid2";
|
import { createId } from "@paralleldrive/cuid2";
|
||||||
import { ToastActionElement, ToastProps } from "@reactive-resume/ui";
|
import type { ToastActionElement, ToastProps } from "@reactive-resume/ui";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
|
|
||||||
const TOAST_LIMIT = 1;
|
const TOAST_LIMIT = 1;
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { t } from "@lingui/macro";
|
import { t } from "@lingui/macro";
|
||||||
import { deepSearchAndParseDates, ErrorMessage } from "@reactive-resume/utils";
|
import type { ErrorMessage } from "@reactive-resume/utils";
|
||||||
|
import { deepSearchAndParseDates } from "@reactive-resume/utils";
|
||||||
import _axios from "axios";
|
import _axios from "axios";
|
||||||
import createAuthRefreshInterceptor from "axios-auth-refresh";
|
import createAuthRefreshInterceptor from "axios-auth-refresh";
|
||||||
import { redirect } from "react-router-dom";
|
import { redirect } from "react-router";
|
||||||
|
|
||||||
import { refreshToken } from "@/client/services/auth";
|
import { refreshToken } from "@/client/services/auth";
|
||||||
|
|
||||||
|
|||||||