Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added resume download button in docs format #193

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -1,6 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"

yarn pretty-quick --staged
yarn lint
yarn test
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"dayjs": "^1.11.1",
"export-from-json": "^1.6.0",
"framer-motion": "^6.3.0",
"html-docx-js": "^0.3.1",
"html-react-parser": "^3.0.1",
"immer": "^9.0.12",
"jodit": "^3.18.6",
Expand All @@ -52,6 +53,7 @@
"@testing-library/react": "^13.0.1",
"@testing-library/user-event": "^14.1.1",
"@types/color": "^3.0.3",
"@types/html-docx-js": "^0.3.4",
"@types/node": "17.0.23",
"@types/react": "18.0.1",
"@types/react-dom": "18.0.0",
Expand Down
155 changes: 83 additions & 72 deletions src/modules/builder/nav-bar/NavBarLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ChangeEvent, useCallback, useRef, useState } from 'react';
import { NavBarActions, NavBarMenu, StyledButton } from './atoms';
import { ChangeEvent, useCallback, useRef, useState } from "react";
import { NavBarActions, NavBarMenu, StyledButton } from "./atoms";
import {
useDatabases,
useFrameworks,
Expand All @@ -8,24 +8,25 @@ import {
usePractices,
useTechnologies,
useTools,
} from 'src/stores/skills';
} from "src/stores/skills";

import { AVAILABLE_TEMPLATES } from 'src/helpers/constants';
import DEFAULT_RESUME_JSON from 'src/helpers/constants/resume-data.json';
import Image from 'next/image';
import Link from 'next/link';
import { NavMenuItem } from './components/MenuItem';
import { PrintResume } from './components/PrintResume';
import { TemplateSelect } from './components/TemplateSelect';
import { ThemeSelect } from './components/ThemeSelect';
import { Toast } from 'src/helpers/common/atoms/Toast';
import exportFromJSON from 'export-from-json';
import { useActivity } from 'src/stores/activity';
import { useAwards } from 'src/stores/awards';
import { useBasicDetails } from 'src/stores/basic';
import { useEducations } from 'src/stores/education';
import { useExperiences } from 'src/stores/experience';
import { useVoluteeringStore } from 'src/stores/volunteering';
import { AVAILABLE_TEMPLATES } from "src/helpers/constants";
import DEFAULT_RESUME_JSON from "src/helpers/constants/resume-data.json";
import Image from "next/image";
import Link from "next/link";
import { NavMenuItem } from "./components/MenuItem";
import { PrintResume } from "./components/PrintResume";
import { TemplateSelect } from "./components/TemplateSelect";
import { ThemeSelect } from "./components/ThemeSelect";
import { Toast } from "src/helpers/common/atoms/Toast";
import exportFromJSON from "export-from-json";
import { useActivity } from "src/stores/activity";
import { useAwards } from "src/stores/awards";
import { useBasicDetails } from "src/stores/basic";
import { useEducations } from "src/stores/education";
import { useExperiences } from "src/stores/experience";
import { ResumeDocDownload } from "./components/ResumeDocDownload";
import { useVoluteeringStore } from "src/stores/volunteering";

const TOTAL_TEMPLATES_AVAILABLE = Object.keys(AVAILABLE_TEMPLATES).length;

Expand Down Expand Up @@ -55,7 +56,8 @@ const NavBarLayout = () => {
},
activities: useActivity.getState().activities,
};
const fileName = updatedResumeJson.basics.name + '_' + new Date().toLocaleString();
const fileName =
updatedResumeJson.basics.name + "_" + new Date().toLocaleString();
const exportType = exportFromJSON.types.json;
exportFromJSON({
data: updatedResumeJson,
Expand All @@ -64,64 +66,72 @@ const NavBarLayout = () => {
});
}, []);

const handleFileChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
const fileObj = event.target.files && event.target.files[0];
if (!fileObj) {
return;
}
const handleFileChange = useCallback(
(event: ChangeEvent<HTMLInputElement>) => {
const fileObj = event.target.files && event.target.files[0];
if (!fileObj) {
return;
}

const reader = new FileReader();
const reader = new FileReader();

reader.readAsText(fileObj);
reader.readAsText(fileObj);

event.target.value = ''; // To read the same file
event.target.value = ""; // To read the same file

reader.onload = (e) => {
if (typeof e.target?.result === 'string') {
const uploadedResumeJSON = JSON.parse(e.target?.result);
const {
basics = {},
skills = {},
work = [],
education = [],
activities = {
involvements: '',
achievements: '',
},
volunteer = [],
awards = [],
} = uploadedResumeJSON;
const {
languages = [],
frameworks = [],
libraries = [],
databases = [],
technologies = [],
practices = [],
tools = [],
} = skills;
useBasicDetails.getState().reset(basics);
useLanguages.getState().reset(languages);
useFrameworks.getState().reset(frameworks);
useLibraries.getState().reset(libraries);
useDatabases.getState().reset(databases);
useTechnologies.getState().reset(technologies);
usePractices.getState().reset(practices);
useTools.getState().reset(tools);
useExperiences.getState().reset(work);
useEducations.getState().reset(education);
useVoluteeringStore.getState().reset(volunteer);
useAwards.getState().reset(awards);
useActivity.getState().reset(activities);
setOpenToast(true);
}
};
}, []);
reader.onload = (e) => {
if (typeof e.target?.result === "string") {
const uploadedResumeJSON = JSON.parse(e.target?.result);
const {
basics = {},
skills = {},
work = [],
education = [],
activities = {
involvements: "",
achievements: "",
},
volunteer = [],
awards = [],
} = uploadedResumeJSON;
const {
languages = [],
frameworks = [],
libraries = [],
databases = [],
technologies = [],
practices = [],
tools = [],
} = skills;
useBasicDetails.getState().reset(basics);
useLanguages.getState().reset(languages);
useFrameworks.getState().reset(frameworks);
useLibraries.getState().reset(libraries);
useDatabases.getState().reset(databases);
useTechnologies.getState().reset(technologies);
usePractices.getState().reset(practices);
useTools.getState().reset(tools);
useExperiences.getState().reset(work);
useEducations.getState().reset(education);
useVoluteeringStore.getState().reset(volunteer);
useAwards.getState().reset(awards);
useActivity.getState().reset(activities);
setOpenToast(true);
}
};
},
[]
);

return (
<nav className="h-14 w-full bg-resume-800 relative flex py-2.5 pl-5 pr-4 items-center shadow-level-8dp z-20 print:hidden">
<Link href="/">
<Image src={'/icons/resume-icon.png'} alt="logo" height="36" width="36" />
<Image
src={"/icons/resume-icon.png"}
alt="logo"
height="36"
width="36"
/>
</Link>
<div className="flex-auto flex justify-between items-center ml-5">
<NavBarMenu>
Expand All @@ -144,7 +154,7 @@ const NavBarLayout = () => {
}
}}
>
Import{' '}
Import{" "}
<input
type="file"
hidden
Expand All @@ -153,6 +163,7 @@ const NavBarLayout = () => {
onChange={handleFileChange}
/>
</StyledButton>
<ResumeDocDownload />
<PrintResume />
</NavBarActions>
</div>
Expand All @@ -161,7 +172,7 @@ const NavBarLayout = () => {
onClose={() => {
setOpenToast(false);
}}
content={'Resume data was successfully imported.'}
content={"Resume data was successfully imported."}
/>
</nav>
);
Expand Down
25 changes: 25 additions & 0 deletions src/modules/builder/nav-bar/components/ResumeDocDownload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useEffect } from "react";
import { StyledButton } from "../atoms";
import htmlDocx from "html-docx-js/dist/html-docx";

export const ResumeDocDownload = () => {
const handleDownloadDoc = () => {
const resumeContent = document.getElementById("resume-content"); // Adjust the ID according to your actual content
const fileName = `Resume_Builder_${Date.now()}.doc`;

const content = resumeContent?.innerHTML;

const converted = htmlDocx.asBlob(content, { orientation: "portrait" });

const link = document.createElement("a");
link.href = URL.createObjectURL(converted);
link.download = fileName;
link.click();
};

return (
<StyledButton onClick={handleDownloadDoc} variant="outlined">
Download as DOC
</StyledButton>
);
};
26 changes: 16 additions & 10 deletions src/modules/builder/resume/ResumeLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Context, createContext, useEffect } from 'react';
import { Context, createContext, useEffect } from "react";

import { AVAILABLE_TEMPLATES } from 'src/helpers/constants';
import { ThemeProvider } from '@mui/material/styles';
import { useResumeStore } from 'src/stores/useResumeStore';
import { useTemplates } from 'src/stores/useTemplate';
import { useThemes } from 'src/stores/themes';
import { useZoom } from 'src/stores/useZoom';
import { AVAILABLE_TEMPLATES } from "src/helpers/constants";
import { ThemeProvider } from "@mui/material/styles";
import { useResumeStore } from "src/stores/useResumeStore";
import { useTemplates } from "src/stores/useTemplate";
import { useThemes } from "src/stores/themes";
import { useZoom } from "src/stores/useZoom";

// TODO: need to define types
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand All @@ -22,19 +22,25 @@ export const ResumeLayout = () => {

useEffect(() => {
const selectedTemplateId =
localStorage.getItem('selectedTemplateId') || AVAILABLE_TEMPLATES['modern'].id;
useTemplates.getState().setTemplate(AVAILABLE_TEMPLATES[selectedTemplateId]);
localStorage.getItem("selectedTemplateId") ||
AVAILABLE_TEMPLATES["modern"].id;
useTemplates
.getState()
.setTemplate(AVAILABLE_TEMPLATES[selectedTemplateId]);
}, []);

return (
<div className="mx-5 print:mx-0 mb-2 print:mb-0">
<div
id="resume-content"
style={{ transform: `scale(${zoom})` }}
className="origin-top transition-all duration-300 ease-linear print:!scale-100"
>
<div className="w-[210mm] h-[296mm] bg-white my-0 mx-auto">
<StateContext.Provider value={resumeData}>
<ThemeProvider theme={selectedTheme}>{Template && <Template />}</ThemeProvider>
<ThemeProvider theme={selectedTheme}>
{Template && <Template />}
</ThemeProvider>
</StateContext.Provider>
</div>
</div>
Expand Down
Loading