diff --git a/Dockerfile b/Dockerfile
index d5542e07..a36867b0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM node:18-alpine as builder
+FROM node:18-alpine AS builder
WORKDIR /app
COPY . .
RUN npm install --include=dev
@@ -11,4 +11,4 @@ COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/static ./.next/static
EXPOSE 3000
-CMD ["node", "server.js"]
\ No newline at end of file
+CMD ["node", "server.js"]
diff --git a/README.md b/README.md
index 20456509..caca000a 100644
--- a/README.md
+++ b/README.md
@@ -41,7 +41,7 @@ You can learn more about the resume parser algorithm in the ["Resume Parser Algo
| **PDF Reader** | [PDF.js](https://github.com/mozilla/pdf.js) | PDF.js reads content from PDF files and is used by the resume parser at its first step to read a resume PDF’s content. |
| **PDF Renderer** | [React-pdf](https://github.com/diegomura/react-pdf) | React-pdf creates PDF files and is used by the resume builder to create a downloadable PDF file. |
-## 📁 Project Structure
+## Project Structure
OpenResume is created with the NextJS web framework and follows its project structure. The source code can be found in `src/app`. There are a total of 4 page routes as shown in the table below. (Code path is relative to `src/app`)
@@ -56,7 +56,7 @@ OpenResume is created with the NextJS web framework and follows its project stru
### Method 1: npm
-1. Download the repo `git clone https://github.com/xitanggg/open-resume.git`
+1. Download the repo `git clone https://github.com/ADITYANAIR01/open-resume.git`
2. Change the directory `cd open-resume`
3. Install the dependency `npm install`
4. Start a development server `npm run dev`
@@ -64,8 +64,23 @@ OpenResume is created with the NextJS web framework and follows its project stru
### Method 2: Docker
-1. Download the repo `git clone https://github.com/xitanggg/open-resume.git`
+1. Download the repo `git clone https://github.com/ADITYANAIR01/open-resume.git/`
2. Change the directory `cd open-resume`
3. Build the container `docker build -t open-resume .`
-4. Start the container `docker run -p 3000:3000 open-resume`
+4. Start the container `docker run --name open-resume -p 3000:3000 open-resume`
5. Open your browser and visit [http://localhost:3000](http://localhost:3000) to see OpenResume live
+
+## 🔄 Synced Forked Improvements Done
+
+The following updates were synced from the latest fork improvements:
+
+- Reorderable contact info section with a GitHub field
+- Objective moved below contact info in form and PDF
+- Clickable project link field (embedded in project name)
+- Comma-separated skills mode with pill UI and left/right reordering
+- Comma-separated skills rendered as pill boxes in PDF and live preview
+- Skill pill color now matches theme and updates dynamically
+- Added 6 new dark theme colors to the theme picker
+- Fixed `react-contenteditable` cursor jump bug in bullet textarea
+- Fixed `ResumePDFText` import bug in skills PDF
+
diff --git a/package-lock.json b/package-lock.json
index 4cb0ec16..da680eda 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -19,11 +19,9 @@
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"next": "13.4.4",
- "pdfjs": "^2.5.0",
"pdfjs-dist": "^3.7.107",
"postcss": "8.4.24",
"react": "18.2.0",
- "react-contenteditable": "^3.3.7",
"react-dom": "18.2.0",
"react-frame-component": "^5.2.6",
"react-redux": "^8.0.7",
@@ -1658,28 +1656,6 @@
}
}
},
- "node_modules/@rkusa/linebreak": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rkusa/linebreak/-/linebreak-1.0.0.tgz",
- "integrity": "sha512-yCSm87XA1aYMgfcABSxcIkk3JtCw3AihNceHY+DnZGLvVP/g2z3UWZbi0xIoYpZWAJEVPr5Zt3QE37Q80wF1pA==",
- "dependencies": {
- "unicode-trie": "^0.3.0"
- }
- },
- "node_modules/@rkusa/linebreak/node_modules/pako": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
- "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
- },
- "node_modules/@rkusa/linebreak/node_modules/unicode-trie": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz",
- "integrity": "sha512-WgVuO0M2jDl7hVfbPgXv2LUrD81HM0bQj/bvLGiw6fJ4Zo8nNFnDrA0/hU2Te/wz6pjxCm5cxJwtLjo2eyV51Q==",
- "dependencies": {
- "pako": "^0.2.5",
- "tiny-inflate": "^1.0.0"
- }
- },
"node_modules/@rushstack/eslint-patch": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.0.tgz",
@@ -2761,9 +2737,9 @@
}
},
"node_modules/caniuse-lite": {
- "version": "1.0.30001492",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz",
- "integrity": "sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw==",
+ "version": "1.0.30001777",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
+ "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
"funding": [
{
"type": "opencollective",
@@ -2777,7 +2753,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
- ]
+ ],
+ "license": "CC-BY-4.0"
},
"node_modules/canvas": {
"version": "2.11.2",
@@ -7091,21 +7068,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
- "node_modules/opentype.js": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz",
- "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==",
- "dependencies": {
- "string.prototype.codepointat": "^0.2.1",
- "tiny-inflate": "^1.0.3"
- },
- "bin": {
- "ot": "bin/ot"
- },
- "engines": {
- "node": ">= 8.0.0"
- }
- },
"node_modules/optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -7256,22 +7218,6 @@
"node": ">=8"
}
},
- "node_modules/pdfjs": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/pdfjs/-/pdfjs-2.5.0.tgz",
- "integrity": "sha512-SGd2QuVp/4WfbRAkb4Jh9+WrB/NULqAhijvvHX3Gkf6vgMLoQeYmLQE54UImWCuiy9/kAM7UMfoDuslP++NRaA==",
- "dependencies": {
- "@rkusa/linebreak": "^1.0.0",
- "opentype.js": "^1.3.3",
- "pako": "^2.0.3",
- "readable-stream": "^3.6.0",
- "unorm": "^1.6.0",
- "uuid": "^8.3.1"
- },
- "engines": {
- "node": ">=7"
- }
- },
"node_modules/pdfjs-dist": {
"version": "3.7.107",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.7.107.tgz",
@@ -7284,11 +7230,6 @@
"path2d-polyfill": "^2.0.1"
}
},
- "node_modules/pdfjs/node_modules/pako": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
- "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
- },
"node_modules/picocolors": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
@@ -7674,18 +7615,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/react-contenteditable": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.7.tgz",
- "integrity": "sha512-GA9NbC0DkDdpN3iGvib/OMHWTJzDX2cfkgy5Tt98JJAbA3kLnyrNbBIpsSpPpq7T8d3scD39DHP+j8mAM7BIfQ==",
- "dependencies": {
- "fast-deep-equal": "^3.1.3",
- "prop-types": "^15.7.1"
- },
- "peerDependencies": {
- "react": ">=16.3"
- }
- },
"node_modules/react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -7772,6 +7701,7 @@
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "optional": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -8352,11 +8282,6 @@
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"devOptional": true
},
- "node_modules/string.prototype.codepointat": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
- "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
- },
"node_modules/string.prototype.matchall": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
@@ -8895,14 +8820,6 @@
"node": ">= 4.0.0"
}
},
- "node_modules/unorm": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz",
- "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA==",
- "engines": {
- "node": ">= 0.4.0"
- }
- },
"node_modules/untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
@@ -8971,14 +8888,6 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
- "node_modules/uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
- "bin": {
- "uuid": "dist/bin/uuid"
- }
- },
"node_modules/v8-to-istanbul": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
@@ -10508,30 +10417,6 @@
"reselect": "^4.1.8"
}
},
- "@rkusa/linebreak": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@rkusa/linebreak/-/linebreak-1.0.0.tgz",
- "integrity": "sha512-yCSm87XA1aYMgfcABSxcIkk3JtCw3AihNceHY+DnZGLvVP/g2z3UWZbi0xIoYpZWAJEVPr5Zt3QE37Q80wF1pA==",
- "requires": {
- "unicode-trie": "^0.3.0"
- },
- "dependencies": {
- "pako": {
- "version": "0.2.9",
- "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz",
- "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA=="
- },
- "unicode-trie": {
- "version": "0.3.1",
- "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-0.3.1.tgz",
- "integrity": "sha512-WgVuO0M2jDl7hVfbPgXv2LUrD81HM0bQj/bvLGiw6fJ4Zo8nNFnDrA0/hU2Te/wz6pjxCm5cxJwtLjo2eyV51Q==",
- "requires": {
- "pako": "^0.2.5",
- "tiny-inflate": "^1.0.0"
- }
- }
- }
- },
"@rushstack/eslint-patch": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.3.0.tgz",
@@ -11354,9 +11239,9 @@
"integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA=="
},
"caniuse-lite": {
- "version": "1.0.30001492",
- "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001492.tgz",
- "integrity": "sha512-2efF8SAZwgAX1FJr87KWhvuJxnGJKOnctQa8xLOskAXNXq8oiuqgl6u1kk3fFpsp3GgvzlRjiK1sl63hNtFADw=="
+ "version": "1.0.30001777",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
+ "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ=="
},
"canvas": {
"version": "2.11.2",
@@ -14510,15 +14395,6 @@
"is-wsl": "^2.2.0"
}
},
- "opentype.js": {
- "version": "1.3.4",
- "resolved": "https://registry.npmjs.org/opentype.js/-/opentype.js-1.3.4.tgz",
- "integrity": "sha512-d2JE9RP/6uagpQAVtJoF0pJJA/fgai89Cc50Yp0EJHk+eLp6QQ7gBoblsnubRULNY132I0J1QKMJ+JTbMqz4sw==",
- "requires": {
- "string.prototype.codepointat": "^0.2.1",
- "tiny-inflate": "^1.0.3"
- }
- },
"optionator": {
"version": "0.9.1",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
@@ -14624,26 +14500,6 @@
"integrity": "sha512-ad/3bsalbbWhmBo0D6FZ4RNMwsLsPpL6gnvhuSaU5Vm7b06Kr5ubSltQQ0T7YKsiJQO+g22zJ4dJKNTXIyOXtA==",
"optional": true
},
- "pdfjs": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/pdfjs/-/pdfjs-2.5.0.tgz",
- "integrity": "sha512-SGd2QuVp/4WfbRAkb4Jh9+WrB/NULqAhijvvHX3Gkf6vgMLoQeYmLQE54UImWCuiy9/kAM7UMfoDuslP++NRaA==",
- "requires": {
- "@rkusa/linebreak": "^1.0.0",
- "opentype.js": "^1.3.3",
- "pako": "^2.0.3",
- "readable-stream": "^3.6.0",
- "unorm": "^1.6.0",
- "uuid": "^8.3.1"
- },
- "dependencies": {
- "pako": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz",
- "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug=="
- }
- }
- },
"pdfjs-dist": {
"version": "3.7.107",
"resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-3.7.107.tgz",
@@ -14888,15 +14744,6 @@
"loose-envify": "^1.1.0"
}
},
- "react-contenteditable": {
- "version": "3.3.7",
- "resolved": "https://registry.npmjs.org/react-contenteditable/-/react-contenteditable-3.3.7.tgz",
- "integrity": "sha512-GA9NbC0DkDdpN3iGvib/OMHWTJzDX2cfkgy5Tt98JJAbA3kLnyrNbBIpsSpPpq7T8d3scD39DHP+j8mAM7BIfQ==",
- "requires": {
- "fast-deep-equal": "^3.1.3",
- "prop-types": "^15.7.1"
- }
- },
"react-dom": {
"version": "18.2.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.2.0.tgz",
@@ -14949,6 +14796,7 @@
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "optional": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
@@ -15360,11 +15208,6 @@
}
}
},
- "string.prototype.codepointat": {
- "version": "0.2.1",
- "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
- "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
- },
"string.prototype.matchall": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
@@ -15758,11 +15601,6 @@
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"dev": true
},
- "unorm": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/unorm/-/unorm-1.6.0.tgz",
- "integrity": "sha512-b2/KCUlYZUeA7JFUuRJZPUtr4gZvBh7tavtv4fvk4+KV9pfGiR6CQAQAWl49ZpR3ts2dk4FYkP7EIgDJoiOLDA=="
- },
"untildify": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
@@ -15806,11 +15644,6 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
- "uuid": {
- "version": "8.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
- "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
- },
"v8-to-istanbul": {
"version": "9.1.0",
"resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-9.1.0.tgz",
diff --git a/package.json b/package.json
index 49258b93..a39a89b4 100644
--- a/package.json
+++ b/package.json
@@ -22,11 +22,9 @@
"eslint": "8.41.0",
"eslint-config-next": "13.4.4",
"next": "13.4.4",
- "pdfjs": "^2.5.0",
"pdfjs-dist": "^3.7.107",
"postcss": "8.4.24",
"react": "18.2.0",
- "react-contenteditable": "^3.3.7",
"react-dom": "18.2.0",
"react-frame-component": "^5.2.6",
"react-redux": "^8.0.7",
diff --git a/src/app/components/Resume/ResumeControlBar.tsx b/src/app/components/Resume/ResumeControlBar.tsx
index ec812005..291d8b47 100644
--- a/src/app/components/Resume/ResumeControlBar.tsx
+++ b/src/app/components/Resume/ResumeControlBar.tsx
@@ -27,10 +27,17 @@ const ResumeControlBar = ({
});
const [instance, update] = usePDF({ document });
+ const isDownloadReady = Boolean(instance.url) && !instance.loading && !instance.error;
// Hook to update pdf when document changes
useEffect(() => {
- update();
+ const timeoutId = window.setTimeout(() => {
+ update();
+ }, 150);
+
+ return () => {
+ window.clearTimeout(timeoutId);
+ };
}, [update, document]);
return (
@@ -60,13 +67,30 @@ const ResumeControlBar = ({
{
+ if (!isDownloadReady) {
+ event.preventDefault();
+ }
+ }}
>
+ Unable to generate PDF right now. +
+ )} ); }; diff --git a/src/app/components/Resume/ResumePDF/ResumePDFCustom.tsx b/src/app/components/Resume/ResumePDF/ResumePDFCustom.tsx index fe8c2303..e58b2e49 100644 --- a/src/app/components/Resume/ResumePDF/ResumePDFCustom.tsx +++ b/src/app/components/Resume/ResumePDF/ResumePDFCustom.tsx @@ -2,8 +2,9 @@ import { View } from "@react-pdf/renderer"; import { ResumePDFSection, ResumePDFBulletList, + ResumePDFText, } from "components/Resume/ResumePDF/common"; -import { styles } from "components/Resume/ResumePDF/styles"; +import { styles, spacing } from "components/Resume/ResumePDF/styles"; import type { ResumeCustom } from "lib/redux/types"; export const ResumePDFCustom = ({ @@ -11,21 +12,50 @@ export const ResumePDFCustom = ({ custom, themeColor, showBulletPoints, + customDisplayMode = "bullet", }: { heading: string; custom: ResumeCustom; themeColor: string; showBulletPoints: boolean; + customDisplayMode?: "bullet" | "comma"; }) => { const { descriptions } = custom; return (+ Parsing and preparing your resume. This can take a few seconds. +
+ )}Note: {!playgroundView ? "Import" : "Parser"} works best on single column resume
> )} + {errorMessage && ( +{errorMessage}
+ )} @@ -207,3 +241,6 @@ const getFileSizeString = (fileSizeB: number) => { return fileSizeMB.toPrecision(3) + " MB"; } }; + +const isPdfFile = (file: File) => + file.type === "application/pdf" || /\.pdf$/i.test(file.name); diff --git a/src/app/components/ResumeForm/CustomForm.tsx b/src/app/components/ResumeForm/CustomForm.tsx index 4597dfd0..5f84412c 100644 --- a/src/app/components/ResumeForm/CustomForm.tsx +++ b/src/app/components/ResumeForm/CustomForm.tsx @@ -1,11 +1,14 @@ import { Form } from "components/ResumeForm/Form"; import { BulletListIconButton } from "components/ResumeForm/Form/IconButton"; -import { BulletListTextarea } from "components/ResumeForm/Form/InputGroup"; +import { BulletListTextarea, InputGroupWrapper } from "components/ResumeForm/Form/InputGroup"; +import { CommaInput } from "components/ResumeForm/Form/CommaInput"; import { useAppDispatch, useAppSelector } from "lib/redux/hooks"; import { changeCustom, selectCustom } from "lib/redux/resumeSlice"; import { selectShowBulletPoints, changeShowBulletPoints, + selectCustomDisplayMode, + changeCustomDisplayMode, } from "lib/redux/settingsSlice"; export const CustomForm = () => { @@ -14,6 +17,7 @@ export const CustomForm = () => { const { descriptions } = custom; const form = "custom"; const showBulletPoints = useAppSelector(selectShowBulletPoints(form)); + const customDisplayMode = useAppSelector(selectCustomDisplayMode) ?? "bullet"; const handleCustomChange = (field: "descriptions", value: string[]) => { dispatch(changeCustom({ field, value })); @@ -26,23 +30,68 @@ export const CustomForm = () => { return ( ); diff --git a/src/app/components/ResumeForm/Form/CommaInput.tsx b/src/app/components/ResumeForm/Form/CommaInput.tsx new file mode 100644 index 00000000..88598dce --- /dev/null +++ b/src/app/components/ResumeForm/Form/CommaInput.tsx @@ -0,0 +1,96 @@ +import { useState } from "react"; +import { ChevronLeftIcon, ChevronRightIcon, XMarkIcon } from "@heroicons/react/24/outline"; + +export const CommaInput = ({ + items, + onChange, + placeholder, +}: { + items: string[]; + onChange: (value: string[]) => void; + placeholder?: string; +}) => { + const [inputValue, setInputValue] = useState(""); + + const addItems = (raw: string) => { + const newItems = raw + .split(",") + .map((s) => s.trim()) + .filter((s) => s.length > 0); + if (newItems.length > 0) { + onChange([...items, ...newItems]); + setInputValue(""); + } + }; + + const deleteItem = (idx: number) => { + onChange(items.filter((_, i) => i !== idx)); + }; + + const moveItem = (idx: number, direction: "left" | "right") => { + const newItems = [...items]; + const swapIdx = direction === "left" ? idx - 1 : idx + 1; + const temp = newItems[idx]; + newItems[idx] = newItems[swapIdx]; + newItems[swapIdx] = temp; + onChange(newItems); + }; + + return ( ++ Contact Info{" "} + (drag ↕ to reorder) +
+ {contactOrder.map((field, idx) => { + const config = CONTACT_FIELD_CONFIG[field]; + if (!config) return null; + const isFirst = idx === 0; + const isLast = idx === contactOrder.length - 1; + return ( +