feat(client): add language selector, language detector and privacy/tos pages

This commit is contained in:
Amruth Pillai 2022-03-08 22:57:47 +01:00
parent bf9da32465
commit a131bb3652
No known key found for this signature in database
GPG Key ID: E3C57DF9B80855AD
32 changed files with 423 additions and 2100 deletions

View File

@ -1,11 +0,0 @@
{
"$schema": "https://unpkg.com/@changesets/config@1.7.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"ignore": [],
"baseBranch": "v3",
"access": "restricted",
"updateInternalDependencies": "patch"
}

View File

@ -23,7 +23,7 @@ GOOGLE_CLIENT_SECRET=change-me
GOOGLE_API_KEY=change-me
# SendGrid (Optional)
SENDGRID_API_KEY=change-me
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID=change-me
SENDGRID_FROM_NAME="Reactive Resume"
SENDGRID_FROM_EMAIL="noreply@rxresu.me"
SENDGRID_API_KEY=
SENDGRID_FORGOT_PASSWORD_TEMPLATE_ID=
SENDGRID_FROM_NAME=
SENDGRID_FROM_EMAIL=

View File

@ -1,9 +1,8 @@
name: Build and Push Docker Image
on:
push:
branches:
- v3
release:
types: [published]
jobs:
docker_client:

View File

@ -1,7 +0,0 @@
# Changelog | Reactive Resume
## 3.0.0
### Major Changes
- Initial Release of Reactive Resume v3

View File

@ -34,8 +34,8 @@ WORKDIR /app
RUN apk add --no-cache curl \
&& curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
COPY --from=builder /app/pnpm-*.yaml .
COPY --from=builder /app/package.json .
COPY --from=builder /app/pnpm-*.yaml ./
COPY --from=builder /app/package.json ./
COPY --from=builder /app/client/.next ./client/.next
COPY --from=builder /app/client/public ./client/public
COPY --from=builder /app/client/next.config.js ./client/next.config.js

View File

@ -19,11 +19,11 @@ import { useMutation } from 'react-query';
import Heading from '@/components/shared/Heading';
import ThemeSwitch from '@/components/shared/ThemeSwitch';
import { Language, languageMap, languages } from '@/config/languages';
import { Language, languages } from '@/config/languages';
import { ServerError } from '@/services/axios';
import queryClient from '@/services/react-query';
import { loadSampleData, LoadSampleDataParams, resetResume, ResetResumeParams } from '@/services/resume';
import { setTheme, togglePageBreakLine, togglePageOrientation } from '@/store/build/buildSlice';
import { setLanguage, setTheme, togglePageBreakLine, togglePageOrientation } from '@/store/build/buildSlice';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setResumeState } from '@/store/resume/resumeSlice';
import { dateFormatOptions } from '@/utils/date';
@ -35,13 +35,13 @@ const Settings = () => {
const resume = useAppSelector((state) => state.resume);
const theme = useAppSelector((state) => state.build.theme);
const language = useAppSelector((state) => state.build.language);
const breakLine = useAppSelector((state) => state.build.page.breakLine);
const orientation = useAppSelector((state) => state.build.page.orientation);
const id: number = useMemo(() => get(resume, 'id'), [resume]);
const slug: string = useMemo(() => get(resume, 'slug'), [resume]);
const username: string = useMemo(() => get(resume, 'user.username'), [resume]);
const language: string = useMemo(() => get(resume, 'metadata.language'), [resume]);
const dateConfig: DateConfig = useMemo(() => get(resume, 'metadata.date'), [resume]);
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
@ -58,8 +58,9 @@ const Settings = () => {
const handleChangeDateFormat = (value: string | null) =>
dispatch(setResumeState({ path: 'metadata.date.format', value }));
const handleChangeLanguage = (value: Language | null) =>
dispatch(setResumeState({ path: 'metadata.language', value: value?.code }));
const handleChangeLanguage = (value: Language | null) => {
dispatch(setLanguage({ language: value?.code || 'en' }));
};
const handleLoadSampleData = async () => {
await loadSampleDataMutation({ id });
@ -121,7 +122,10 @@ const Settings = () => {
disableClearable
className="my-2 w-full"
options={languages}
value={languageMap[language]}
value={language}
isOptionEqualToValue={(a, b) => a.code === b.code}
onChange={(_, value) => handleChangeLanguage(value)}
renderInput={(params) => <TextField {...params} />}
getOptionLabel={(language) => {
if (language.localName) {
return `${language.name} (${language.localName})`;
@ -129,9 +133,6 @@ const Settings = () => {
return language.name;
}}
isOptionEqualToValue={(a, b) => a.code === b.code}
onChange={(_, value) => handleChangeLanguage(value)}
renderInput={(params) => <TextField {...params} />}
/>
</ListItem>
</>

View File

@ -9,6 +9,11 @@ export const languages: Language[] = [
code: 'en',
name: 'English',
},
{
code: 'ta',
name: 'Tamil',
localName: 'தமிழ்',
},
];
export const languageMap: Record<string, Language> = languages.reduce(

View File

@ -3,9 +3,8 @@ const path = require('path');
const i18nConfig = {
i18n: {
defaultLocale: 'en',
locales: ['en'],
locales: ['en', 'ta'],
},
debug: false,
nsSeparator: '.',
localePath: path.resolve('./public/locales'),
ns: ['common', 'modals', 'landing', 'dashboard', 'builder'],

View File

@ -13,9 +13,9 @@
"@emotion/styled": "^11.8.1",
"@hookform/resolvers": "2.8.8",
"@monaco-editor/react": "^4.3.1",
"@mui/icons-material": "^5.4.4",
"@mui/lab": "^5.0.0-alpha.71",
"@mui/material": "^5.4.4",
"@mui/icons-material": "^5.5.0",
"@mui/lab": "^5.0.0-alpha.72",
"@mui/material": "^5.5.0",
"@reduxjs/toolkit": "^1.8.0",
"axios": "^0.26.0",
"clsx": "^1.1.1",
@ -54,6 +54,7 @@
"devDependencies": {
"@babel/core": "^7.17.5",
"@reactive-resume/schema": "workspace:*",
"@tailwindcss/typography": "^0.5.2",
"@types/downloadjs": "^1.4.3",
"@types/lodash": "^4.14.179",
"@types/node": "17.0.21",
@ -66,7 +67,7 @@
"autoprefixer": "^10.4.2",
"eslint": "^8.10.0",
"eslint-config-next": "12.1.0",
"postcss": "^8.4.7",
"postcss": "^8.4.8",
"prettier": "^2.5.1",
"sass": "^1.49.9",
"tailwindcss": "^3.0.23",

View File

@ -87,8 +87,8 @@ const Home: NextPage = () => {
<ul className="list-inside list-disc leading-loose">
<li>{t('landing.features.list.free')}</li>
<li>{t('landing.features.list.ads')}</li>
<li>{t('landing.features.list.tracking')}</li>
<li>{t('landing.features.list.ads')}</li>
<li>{t('landing.features.list.languages')}</li>
<li>{t('landing.features.list.import')}</li>
<li>{t('landing.features.list.export')}</li>

View File

@ -0,0 +1,125 @@
import { NextPage } from 'next';
const PrivacyPolicy: NextPage = () => (
<div className="mx-auto my-12 prose dark:prose-invert">
<h1>Privacy Policy</h1>
<p>
Amruth Pillai built Reactive Resume as a free and open source app. This service is provided by Amruth Pillai at no
cost and is intended for use as is.
</p>
<p>
This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal
Information if anyone decided to use my Service.
</p>
<p>
If you choose to use my Service, then you agree to the collection and use of information in relation to this
policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or
share your information with anyone except as described in this Privacy Policy.
</p>
<p>
The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which are accessible
at Reactive Resume unless otherwise defined in this Privacy Policy.
</p>
<h3>Information Collection and Use</h3>
<p>
For a better experience, while using our Service, I may require you to provide us with certain personally
identifiable information, including but not limited to Name, Email Address, Resume Information. The information
that I request will be retained on your device and is not collected by me in any way.
</p>
<h3>Log Data</h3>
<p>
I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and
information (through third-party products) on your phone called Log Data. This Log Data may include information
such as your device Internet Protocol (IP) address, device name, operating system version, the configuration of
the app when utilizing my Service, the time and date of your use of the Service, and other statistics.
</p>
<h3>Cookies</h3>
<p>
Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are
sent to your browser from the websites that you visit and are stored on your device&apos;s internal memory.
</p>
<p>
This Service does not use these cookies explicitly. However, the app may use third-party code and libraries that
use cookies to collect information and improve their services. You have the option to either accept or refuse
these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may
not be able to use some portions of this Service.
</p>
<h3>Service Providers</h3>
<p>I may employ third-party companies and individuals due to the following reasons:</p>
<ul>
<li>To facilitate our Service;</li>
<li>To provide the Service on our behalf;</li>
<li>To perform Service-related services; or</li>
<li>To assist us in analyzing how our Service is used.</li>
</ul>
<p>
I want to inform users of this Service that these third parties have access to their Personal Information. The
reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use
the information for any other purpose.
</p>
<h3>Security</h3>
<p>
I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable
means of protecting it. But remember that no method of transmission over the internet, or method of electronic
storage is 100% secure and reliable, and I cannot guarantee its absolute security.
</p>
<h3>Links to Other Sites</h3>
<p>
This Service may contain links to other sites. If you click on a third-party link, you will be directed to that
site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the
Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy
policies, or practices of any third-party sites or services.
</p>
<h3>Childrens Privacy</h3>
<div>
<p>
I do not knowingly collect personally identifiable information from children. I encourage all children to never
submit any personally identifiable information through the Application and/or Services. I encourage parents and
legal guardians to monitor their children&apos;s Internet usage and to help enforce this Policy by instructing
their children never to provide personally identifiable information through the Application and/or Services
without their permission. If you have reason to believe that a child has provided personally identifiable
information to us through the Application and/or Services, please contact us. You must also be at least 16 years
of age to consent to the processing of your personally identifiable information in your country (in some
countries we may allow your parent or guardian to do so on your behalf).
</p>
</div>
<h3>Changes to This Privacy Policy</h3>
<p>
I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any
changes. I will notify you of any changes by posting the new Privacy Policy on this page.
</p>
<p>This policy is effective as of 2022-01-08</p>
<h3>Contact Us</h3>
<p>
If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at
im.amruth@gmail.com.
</p>
</div>
);
export default PrivacyPolicy;

View File

@ -0,0 +1,107 @@
import { NextPage } from 'next';
const TermsOfService: NextPage = () => (
<div className="mx-auto my-12 prose dark:prose-invert">
<h1>Terms of Service</h1>
<p>
<s>
By downloading or using the app, these terms will automatically apply to you - you should make sure therefore
that you read them carefully before using the app. You&apos;re not allowed to copy or modify the app, any part
of the app, or our trademarks in any way. You&apos;re not allowed to attempt to extract the source code of the
app, and you also shouldn&apos;t try to translate the app into other languages or make derivative versions. The
app itself, and all the trademarks, copyright, database rights, and other intellectual property rights related
to it, still belong to Amruth Pillai.
</s>
</p>
<h3>
<strong>
Do whatever you want, governed by the{' '}
<a href="https://choosealicense.com/licenses/mit/" target="_blank" rel="noreferrer">
MIT license
</a>
.
</strong>
</h3>
<p>
Amruth Pillai is committed to ensuring that the app is as useful and efficient as possible. For that reason, we
reserve the right to make changes to the app or to charge for its services, at any time and for any reason. We
will never charge you for the app or its services without making it very clear to you exactly what you&apos;re
paying for.
</p>
<p>
The Reactive Resume app stores and processes personal data that you have provided to us, to provide my Service.
It&apos;s your responsibility to keep your phone and access to the app secure. We therefore recommend that you do
not jailbreak or root your phone, which is the process of removing software restrictions and limitations imposed
by the official operating system of your device. It could make your phone vulnerable to malware/viruses/malicious
programs, compromise your phone&apos;s security features and it could mean that the Reactive Resume app won&apos;t
work properly or at all.
</p>
<p>
You should be aware that there are certain things that Amruth Pillai will not take responsibility for. Certain
functions of the app will require the app to have an active internet connection. The connection can be Wi-Fi or
provided by your mobile network provider, but Amruth Pillai cannot take responsibility for the app not working at
full functionality if you don&apos;t have access to Wi-Fi, and you don&apos;t have any of your data allowance
left.
</p>
<p></p>
<p>
If you&apos;re using the app outside of an area with Wi-Fi, you should remember that the terms of the agreement
with your mobile network provider will still apply. As a result, you may be charged by your mobile provider for
the cost of data for the duration of the connection while accessing the app, or other third-party charges. In
using the app, you&apos;re accepting responsibility for any such charges, including roaming data charges if you
use the app outside of your home territory (i.e. region or country) without turning off data roaming. If you are
not the bill payer for the device on which you&apos;re using the app, please be aware that we assume that you have
received permission from the bill payer for using the app.
</p>
<p>
Along the same lines, Amruth Pillai cannot always take responsibility for the way you use the app i.e. You need to
make sure that your device stays charged if it runs out of battery and you can&apos;t turn it on to avail the
Service, Amruth Pillai cannot accept responsibility.
</p>
<p>
With respect to Amruth Pillai&apos;s responsibility for your use of the app, when you&apos;re using the app,
it&apos;s important to bear in mind that although we endeavor to ensure that it is updated and correct at all
times, we do rely on third parties to provide information to us so that we can make it available to you. Amruth
Pillai accepts no liability for any loss, direct or indirect, you experience as a result of relying wholly on this
functionality of the app.
</p>
<p>
At some point, we may wish to update the app. The app is currently available on Android the requirements for the
system(and for any additional systems we decide to extend the availability of the app to) may change, and
you&apos;ll need to download the updates if you want to keep using the app. Amruth Pillai does not promise that it
will always update the app so that it is relevant to you and/or works with the Android version that you have
installed on your device. However, you promise to always accept updates to the application when offered to you, We
may also wish to stop providing the app, and may terminate use of it at any time without giving notice of
termination to you. Unless we tell you otherwise, upon any termination, (a) the rights and licenses granted to you
in these terms will end; (b) you must stop using the app, and (if needed) delete it from your device.
</p>
<h3>Changes to Terms of Service</h3>
<p>
I may update our Terms of Service from time to time. Thus, you are advised to review this page periodically for
any changes. I will notify you of any changes by posting the new Terms of Service on this page.
</p>
<p>These terms of service are effective as of 2022-01-08</p>
<h3>Contact Us</h3>
<p>
If you have any questions or suggestions about the terms of service, do not hesitate to contact me at
im.amruth@gmail.com.
</p>
</div>
);
export default TermsOfService;

View File

@ -14,7 +14,7 @@
"import": "Import data from LinkedIn, JSON Resume",
"languages": "Accessible in multiple languages",
"more": "And much more exciting features, <1>read all about it here</1>",
"tracking": "No Tracking (no 🍪s too)"
"tracking": "No Tracking"
}
},
"links": {

View File

@ -1,351 +0,0 @@
{
"common": {
"actions": {
"add": "Add New {{token}}",
"delete": "Delete {{token}}",
"edit": "Edit {{token}}"
},
"columns": {
"heading": "Columns",
"tooltip": "Change number of columns"
},
"form": {
"date": {
"label": "Date"
},
"description": {
"label": "Description"
},
"email": {
"label": "Email Address"
},
"end-date": {
"help-text": "Leave this field blank, if still present",
"label": "End Date"
},
"keywords": {
"label": "Keywords"
},
"level": {
"label": "Level"
},
"levelNum": {
"label": "Level (Number)"
},
"name": {
"label": "Name"
},
"phone": {
"label": "Phone Number"
},
"position": {
"label": "Position"
},
"start-date": {
"label": "Start Date"
},
"subtitle": {
"label": "Subtitle"
},
"summary": {
"label": "Summary"
},
"title": {
"label": "Title"
},
"url": {
"label": "Website"
}
},
"glossary": {
"page": "Page"
},
"list": {
"empty-text": "This list is empty."
},
"tooltip": {
"delete-section": "Delete Section",
"rename-section": "Rename Section",
"toggle-visibility": "Toggle Visibility"
}
},
"controller": {
"tooltip": {
"center-artboard": "Center Artboard",
"copy-link": "Copy Link to Resume",
"export-pdf": "Export PDF",
"toggle-orientation": "Toggle Page Orientation",
"toggle-page-break-line": "Toggle Page Break Line",
"toggle-sidebars": "Toggle Sidebars",
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out"
}
},
"header": {
"menu": {
"delete": "Delete",
"duplicate": "Duplicate",
"rename": "Rename",
"share-link": "Share Link",
"tooltips": {
"delete": "Are you sure you want to delete this resume? This is an irreversible action.",
"share-link": "You need to change the visibility of your resume to public to make it visible to others."
}
}
},
"leftSidebar": {
"sections": {
"awards": {
"form": {
"awarder": {
"label": "Awarder"
}
}
},
"basics": {
"actions": {
"photo-filters": "Photo Filters"
},
"heading": "Basics",
"headline": {
"label": "Headline"
},
"name": {
"label": "Full Name"
},
"photo-filters": {
"effects": {
"border": {
"label": "Border"
},
"grayscale": {
"label": "Grayscale"
},
"heading": "Effects"
},
"shape": {
"heading": "Shape"
},
"size": {
"heading": "Size (in px)"
}
},
"photo-upload": {
"tooltip": {
"remove": "Remove Photo",
"upload": "Upload Photo"
}
}
},
"certifications": {
"form": {
"issuer": {
"label": "Issuer"
}
}
},
"education": {
"form": {
"area-study": {
"label": "Area of Study"
},
"courses": {
"label": "Courses"
},
"degree": {
"label": "Degree"
},
"grade": {
"label": "Grade"
},
"institution": {
"label": "Institution"
}
}
},
"location": {
"address": {
"label": "Address"
},
"city": {
"label": "City"
},
"country": {
"label": "Country"
},
"heading": "Location",
"postal-code": {
"label": "Postal Code"
},
"region": {
"label": "Region"
}
},
"profiles": {
"form": {
"network": {
"label": "Network"
},
"username": {
"label": "Username"
}
},
"heading": "Profiles",
"heading_one": "Profile"
},
"publications": {
"form": {
"publisher": {
"label": "Publisher"
}
}
},
"references": {
"form": {
"relationship": {
"label": "Relationship"
}
}
},
"section": {
"heading": "Section"
},
"volunteer": {
"form": {
"organization": {
"label": "Organization"
}
}
}
}
},
"rightSidebar": {
"sections": {
"css": {
"heading": "Custom CSS"
},
"export": {
"heading": "Export",
"json": {
"primary": "JSON",
"secondary": "Download a JSON version of your resume that can be imported back into Reactive Resume."
},
"pdf": {
"loading": {
"primary": "Generating PDF",
"secondary": "Please wait as your PDF gets generated, this may take upto 15 seconds."
},
"normal": {
"primary": "PDF",
"secondary": "Download a PDF of your resume that you can print and send out to your dream job. This file cannot be imported back for further editing."
}
}
},
"layout": {
"heading": "Layout",
"tooltip": {
"reset-layout": "Reset Layout"
}
},
"links": {
"bugs-features": {
"body": "Something stopping you from making a resume? Or do you have an amazing idea to add? Raise an issue on GitHub to get started.",
"button": "GitHub Issues",
"heading": "Bugs? Feature Requests?"
},
"donate": {
"body": "If you liked using Reactive Resume, please consider donating as much as you can to the cause of keeping the app up and running, without ads and free forever.",
"button": "Buy me a coffee",
"heading": "Donate to Reactive Resume"
},
"github": "Source Code",
"heading": "Links"
},
"settings": {
"global": {
"date": {
"primary": "Date",
"secondary": "Date format to use across the app"
},
"heading": "Global",
"language": {
"primary": "Language",
"secondary": "Display language to use across the app"
},
"theme": {
"primary": "Theme"
}
},
"heading": "Settings",
"page": {
"break-line": {
"primary": "Break Line",
"secondary": "Show a line on all pages to mark the height of an A4 page"
},
"heading": "Page",
"orientation": {
"primary": "Orientation",
"secondary": "Whether to display pages horizontally or vertically"
}
},
"resume": {
"heading": "Resume",
"reset": {
"primary": "Reset Everything",
"secondary": "Made too many mistakes? Click here to reset all changes and start from scratch. Be careful, this action cannot be reversed."
},
"sample": {
"primary": "Load Sample Data",
"secondary": "Not sure where to begin? Click here to load some sample data to see how a complete resume looks like."
}
}
},
"sharing": {
"heading": "Sharing",
"short-url": {
"label": "Prefer Short URL"
},
"visibility": {
"subtitle": "Allow anyone with a link to view your resume",
"title": "Public"
}
},
"templates": {
"heading": "Templates"
},
"theme": {
"form": {
"background": {
"label": "Background"
},
"primary": {
"label": "Primary"
},
"text": {
"label": "Text"
}
},
"heading": "Theme"
},
"typography": {
"form": {
"font-family": {
"label": "Font Family"
},
"font-size": {
"label": "Font Size"
}
},
"heading": "Typography",
"widgets": {
"body": {
"label": "Body"
},
"headings": {
"label": "Headings"
}
}
}
}
}
}

View File

@ -1,27 +0,0 @@
{
"avatar": {
"menu": {
"greeting": "Hello",
"logout": "Logout"
}
},
"description": "Reactive Resume is a free and open source resume builder that's built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3.",
"footer": {
"credit": "A passion project by <1>Amruth Pillai</1>",
"license": "By the community, for the community."
},
"markdown": {
"help-text": "This section supports <1>markdown</1> formatting."
},
"subtitle": "A free and open source resume builder.",
"title": "Reactive Resume",
"toast": {
"error": {
"upload-file-size": "Please upload only files under 2 megabytes.",
"upload-photo-size": "Please upload only photos under 2 megabytes, preferrably square."
},
"success": {
"resume-link-copied": "A link to your resume has been copied to your clipboard."
}
}
}

View File

@ -1,25 +0,0 @@
{
"create-resume": {
"subtitle": "Start from scratch",
"title": "Create New Resume"
},
"import-external": {
"subtitle": "LinkedIn, JSON Resume, Reactive Resume",
"title": "Import from External Sources"
},
"resume": {
"menu": {
"delete": "Delete",
"duplicate": "Duplicate",
"open": "Open",
"rename": "Rename",
"share-link": "Share Link",
"tooltips": {
"delete": "Are you sure you want to delete this resume? This is an irreversible action.",
"share-link": "You need to change the visibility of your resume to public to make it visible to others."
}
},
"timestamp": "Last updated {{timestamp}} ago"
},
"title": "Dashboard"
}

View File

@ -1,34 +0,0 @@
{
"actions": {
"app": "Go to App",
"login": "Login",
"logout": "Logout",
"register": "Register"
},
"features": {
"heading": "Features",
"list": {
"ads": "No Advertising",
"export": "Export your resume to JSON or PDF format",
"free": "Free, forever",
"import": "Import data from LinkedIn, JSON Resume",
"languages": "Accessible in multiple languages",
"more": "And much more exciting features, <1>read all about it here</1>",
"tracking": "No Tracking (no 🍪s too)"
}
},
"links": {
"heading": "Links",
"links": {
"donate": "Donate to Reactive Resume",
"github": "Source Code on GitHub"
}
},
"screenshots": {
"heading": "Screenshots"
},
"summary": {
"body": "Reactive Resume is a free and open source resume builder that's built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3. With this app, you can create multiple resumes, share them with recruiters or friends through a unique link and print it as a PDF, all for free, no ads, no tracking, without losing the integrity and privacy of your data.",
"heading": "Summary"
}
}

View File

@ -1,134 +0,0 @@
{
"auth": {
"forgot-password": {
"actions": {
"send-email": "Send Reset Password Email"
},
"body": "Just enter the email address associated with the account you would like to recover.",
"form": {
"email": {
"label": "Email Address"
}
},
"heading": "Forgot your password?",
"help-text": "If the account exists, you will receive an email with a link to reset your password."
},
"login": {
"actions": {
"login": "Login",
"login-google": "Login with Google"
},
"body": "Please enter your username and password associated with your account to login and access, manage and share your resumes.",
"form": {
"password": {
"label": "Password"
},
"username": {
"help-text": "You can also enter your email address",
"label": "Username"
}
},
"heading": "Login to your account",
"recover-text": "In case you have forgotten your password, you can <1>recover your account</1> here.",
"register-text": "If you don't have one, you can <1>create an account</1> here."
},
"register": {
"actions": {
"register": "Register"
},
"body": "Please enter your personal information to create an account.",
"form": {
"confirm-password": {
"label": "Confirm Password"
},
"email": {
"label": "Email Address"
},
"name": {
"label": "Full Name"
},
"password": {
"label": "Password"
},
"username": {
"label": "Username"
}
},
"heading": "Create an account",
"loginText": "If you already have an account, you can <1>login here</1>."
},
"reset-password": {
"actions": {
"set-password": "Set New Password"
},
"body": "Enter a new password for your account.",
"form": {
"confirm-password": {
"label": "Confirm Password"
},
"password": {
"label": "Password"
}
},
"heading": "Reset your password"
}
},
"dashboard": {
"create-resume": {
"actions": {
"create-resume": "Create Resume"
},
"body": "Start building your resume by giving it a name. It could be in reference to the role you are applying for, or just your favorite snack.",
"form": {
"name": {
"label": "Name"
},
"public": {
"label": "Is Publicly Accessible?"
},
"slug": {
"label": "Slug"
}
},
"heading": "Create a new resume"
},
"import-external": {
"heading": "Import from External Sources",
"json-resume": {
"actions": {
"upload-json": "Upload JSON"
},
"body": "If you have a <1>validated JSON Resume</1> ready to go, you can use it to fast-track your development on Reactive Resume. Click the button below and upload a valid JSON file to begin.",
"heading": "Import From JSON Resume"
},
"linkedin": {
"actions": {
"upload-archive": "Upload ZIP Archive"
},
"body": "You can save time by exporting your data from LinkedIn and using it to auto-fill fields on Reactive Resume. Head on over to the <1>Data Privacy</1> section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP archive below.",
"heading": "Import From LinkedIn"
},
"reactive-resume": {
"actions": {
"upload-json": "Upload JSON"
},
"body": "If you have a JSON that was exported with the current version of Reactive Resume, you can import it back here to get an editable version again.",
"heading": "Import From Reactive Resume"
}
},
"rename-resume": {
"actions": {
"rename-resume": "Rename Resume"
},
"form": {
"name": {
"label": "Name"
},
"slug": {
"label": "Slug"
}
},
"heading": "Rename your resume"
}
}
}

View File

@ -1,351 +0,0 @@
{
"common": {
"actions": {
"add": "Add New {{token}}",
"delete": "Delete {{token}}",
"edit": "Edit {{token}}"
},
"columns": {
"heading": "Columns",
"tooltip": "Change number of columns"
},
"form": {
"date": {
"label": "Date"
},
"description": {
"label": "Description"
},
"email": {
"label": "Email Address"
},
"end-date": {
"help-text": "Leave this field blank, if still present",
"label": "End Date"
},
"keywords": {
"label": "Keywords"
},
"level": {
"label": "Level"
},
"levelNum": {
"label": "Level (Number)"
},
"name": {
"label": "Name"
},
"phone": {
"label": "Phone Number"
},
"position": {
"label": "Position"
},
"start-date": {
"label": "Start Date"
},
"subtitle": {
"label": "Subtitle"
},
"summary": {
"label": "Summary"
},
"title": {
"label": "Title"
},
"url": {
"label": "Website"
}
},
"glossary": {
"page": "Page"
},
"list": {
"empty-text": "This list is empty."
},
"tooltip": {
"delete-section": "Delete Section",
"rename-section": "Rename Section",
"toggle-visibility": "Toggle Visibility"
}
},
"controller": {
"tooltip": {
"center-artboard": "Center Artboard",
"copy-link": "Copy Link to Resume",
"export-pdf": "Export PDF",
"toggle-orientation": "Toggle Page Orientation",
"toggle-page-break-line": "Toggle Page Break Line",
"toggle-sidebars": "Toggle Sidebars",
"zoom-in": "Zoom In",
"zoom-out": "Zoom Out"
}
},
"header": {
"menu": {
"delete": "Delete",
"duplicate": "Duplicate",
"rename": "Rename",
"share-link": "Share Link",
"tooltips": {
"delete": "Are you sure you want to delete this resume? This is an irreversible action.",
"share-link": "You need to change the visibility of your resume to public to make it visible to others."
}
}
},
"leftSidebar": {
"sections": {
"awards": {
"form": {
"awarder": {
"label": "Awarder"
}
}
},
"basics": {
"actions": {
"photo-filters": "Photo Filters"
},
"heading": "Basics",
"headline": {
"label": "Headline"
},
"name": {
"label": "Full Name"
},
"photo-filters": {
"effects": {
"border": {
"label": "Border"
},
"grayscale": {
"label": "Grayscale"
},
"heading": "Effects"
},
"shape": {
"heading": "Shape"
},
"size": {
"heading": "Size (in px)"
}
},
"photo-upload": {
"tooltip": {
"remove": "Remove Photo",
"upload": "Upload Photo"
}
}
},
"certifications": {
"form": {
"issuer": {
"label": "Issuer"
}
}
},
"education": {
"form": {
"area-study": {
"label": "Area of Study"
},
"courses": {
"label": "Courses"
},
"degree": {
"label": "Degree"
},
"grade": {
"label": "Grade"
},
"institution": {
"label": "Institution"
}
}
},
"location": {
"address": {
"label": "Address"
},
"city": {
"label": "City"
},
"country": {
"label": "Country"
},
"heading": "Location",
"postal-code": {
"label": "Postal Code"
},
"region": {
"label": "Region"
}
},
"profiles": {
"form": {
"network": {
"label": "Network"
},
"username": {
"label": "Username"
}
},
"heading": "Profiles",
"heading_one": "Profile"
},
"publications": {
"form": {
"publisher": {
"label": "Publisher"
}
}
},
"references": {
"form": {
"relationship": {
"label": "Relationship"
}
}
},
"section": {
"heading": "Section"
},
"volunteer": {
"form": {
"organization": {
"label": "Organization"
}
}
}
}
},
"rightSidebar": {
"sections": {
"css": {
"heading": "Custom CSS"
},
"export": {
"heading": "Export",
"json": {
"primary": "JSON",
"secondary": "Download a JSON version of your resume that can be imported back into Reactive Resume."
},
"pdf": {
"loading": {
"primary": "Generating PDF",
"secondary": "Please wait as your PDF gets generated, this may take upto 15 seconds."
},
"normal": {
"primary": "PDF",
"secondary": "Download a PDF of your resume that you can print and send out to your dream job. This file cannot be imported back for further editing."
}
}
},
"layout": {
"heading": "Layout",
"tooltip": {
"reset-layout": "Reset Layout"
}
},
"links": {
"bugs-features": {
"body": "Something stopping you from making a resume? Or do you have an amazing idea to add? Raise an issue on GitHub to get started.",
"button": "GitHub Issues",
"heading": "Bugs? Feature Requests?"
},
"donate": {
"body": "If you liked using Reactive Resume, please consider donating as much as you can to the cause of keeping the app up and running, without ads and free forever.",
"button": "Buy me a coffee",
"heading": "Donate to Reactive Resume"
},
"github": "Source Code",
"heading": "Links"
},
"settings": {
"global": {
"date": {
"primary": "Date",
"secondary": "Date format to use across the app"
},
"heading": "Global",
"language": {
"primary": "Language",
"secondary": "Display language to use across the app"
},
"theme": {
"primary": "Theme"
}
},
"heading": "Settings",
"page": {
"break-line": {
"primary": "Break Line",
"secondary": "Show a line on all pages to mark the height of an A4 page"
},
"heading": "Page",
"orientation": {
"primary": "Orientation",
"secondary": "Whether to display pages horizontally or vertically"
}
},
"resume": {
"heading": "Resume",
"reset": {
"primary": "Reset Everything",
"secondary": "Made too many mistakes? Click here to reset all changes and start from scratch. Be careful, this action cannot be reversed."
},
"sample": {
"primary": "Load Sample Data",
"secondary": "Not sure where to begin? Click here to load some sample data to see how a complete resume looks like."
}
}
},
"sharing": {
"heading": "Sharing",
"short-url": {
"label": "Prefer Short URL"
},
"visibility": {
"subtitle": "Allow anyone with a link to view your resume",
"title": "Public"
}
},
"templates": {
"heading": "Templates"
},
"theme": {
"form": {
"background": {
"label": "Background"
},
"primary": {
"label": "Primary"
},
"text": {
"label": "Text"
}
},
"heading": "Theme"
},
"typography": {
"form": {
"font-family": {
"label": "Font Family"
},
"font-size": {
"label": "Font Size"
}
},
"heading": "Typography",
"widgets": {
"body": {
"label": "Body"
},
"headings": {
"label": "Headings"
}
}
}
}
}
}

View File

@ -1,25 +0,0 @@
{
"create-resume": {
"subtitle": "Start from scratch",
"title": "Create New Resume"
},
"import-external": {
"subtitle": "LinkedIn, JSON Resume, Reactive Resume",
"title": "Import from External Sources"
},
"resume": {
"menu": {
"delete": "Delete",
"duplicate": "Duplicate",
"open": "Open",
"rename": "Rename",
"share-link": "Share Link",
"tooltips": {
"delete": "Are you sure you want to delete this resume? This is an irreversible action.",
"share-link": "You need to change the visibility of your resume to public to make it visible to others."
}
},
"timestamp": "Last updated {{timestamp}} ago"
},
"title": "Dashboard"
}

View File

@ -1,34 +0,0 @@
{
"actions": {
"app": "Go to App",
"login": "Login",
"logout": "வெளியேறு",
"register": "Register"
},
"features": {
"heading": "Features",
"list": {
"ads": "No Advertising",
"export": "Export your resume to JSON or PDF format",
"free": "Free, forever",
"import": "Import data from LinkedIn, JSON Resume",
"languages": "Accessible in multiple languages",
"more": "And much more exciting features, <1>read all about it here</1>",
"tracking": "No Tracking (no 🍪s too)"
}
},
"links": {
"heading": "Links",
"links": {
"donate": "Donate to Reactive Resume",
"github": "Source Code on GitHub"
}
},
"screenshots": {
"heading": "Screenshots"
},
"summary": {
"body": "Reactive Resume is a free and open source resume builder that's built to make the mundane tasks of creating, updating and sharing your resume as easy as 1, 2, 3. With this app, you can create multiple resumes, share them with recruiters or friends through a unique link and print it as a PDF, all for free, no ads, no tracking, without losing the integrity and privacy of your data.",
"heading": "Summary"
}
}

View File

@ -1,134 +0,0 @@
{
"auth": {
"forgot-password": {
"actions": {
"send-email": "Send Reset Password Email"
},
"body": "Just enter the email address associated with the account you would like to recover.",
"form": {
"email": {
"label": "Email Address"
}
},
"heading": "Forgot your password?",
"help-text": "If the account exists, you will receive an email with a link to reset your password."
},
"login": {
"actions": {
"login": "Login",
"login-google": "Login with Google"
},
"body": "Please enter your username and password associated with your account to login and access, manage and share your resumes.",
"form": {
"password": {
"label": "Password"
},
"username": {
"help-text": "You can also enter your email address",
"label": "Username"
}
},
"heading": "Login to your account",
"recover-text": "In case you have forgotten your password, you can <1>recover your account</1> here.",
"register-text": "If you don't have one, you can <1>create an account</1> here."
},
"register": {
"actions": {
"register": "Register"
},
"body": "Please enter your personal information to create an account.",
"form": {
"confirm-password": {
"label": "Confirm Password"
},
"email": {
"label": "Email Address"
},
"name": {
"label": "Full Name"
},
"password": {
"label": "Password"
},
"username": {
"label": "Username"
}
},
"heading": "Create an account",
"loginText": "If you already have an account, you can <1>login here</1>."
},
"reset-password": {
"actions": {
"set-password": "Set New Password"
},
"body": "Enter a new password for your account.",
"form": {
"confirm-password": {
"label": "Confirm Password"
},
"password": {
"label": "Password"
}
},
"heading": "Reset your password"
}
},
"dashboard": {
"create-resume": {
"actions": {
"create-resume": "Create Resume"
},
"body": "Start building your resume by giving it a name. It could be in reference to the role you are applying for, or just your favorite snack.",
"form": {
"name": {
"label": "Name"
},
"public": {
"label": "Is Publicly Accessible?"
},
"slug": {
"label": "Slug"
}
},
"heading": "Create a new resume"
},
"import-external": {
"heading": "Import from External Sources",
"json-resume": {
"actions": {
"upload-json": "Upload JSON"
},
"body": "If you have a <1>validated JSON Resume</1> ready to go, you can use it to fast-track your development on Reactive Resume. Click the button below and upload a valid JSON file to begin.",
"heading": "Import From JSON Resume"
},
"linkedin": {
"actions": {
"upload-archive": "Upload ZIP Archive"
},
"body": "You can save time by exporting your data from LinkedIn and using it to auto-fill fields on Reactive Resume. Head on over to the <1>Data Privacy</1> section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP archive below.",
"heading": "Import From LinkedIn"
},
"reactive-resume": {
"actions": {
"upload-json": "Upload JSON"
},
"body": "If you have a JSON that was exported with the current version of Reactive Resume, you can import it back here to get an editable version again.",
"heading": "Import From Reactive Resume"
}
},
"rename-resume": {
"actions": {
"rename-resume": "Rename Resume"
},
"form": {
"name": {
"label": "Name"
},
"slug": {
"label": "Slug"
}
},
"heading": "Rename your resume"
}
}
}

View File

@ -1,5 +1,8 @@
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
import merge from 'lodash/merge';
import { Language, languageMap } from '@/config/languages';
export type Theme = 'light' | 'dark';
export type Sidebar = 'left' | 'right';
@ -10,6 +13,7 @@ export type Orientation = 'horizontal' | 'vertical';
export type BuildState = {
theme?: Theme;
language: Language;
sidebar: Record<Sidebar, SidebarState>;
page: {
breakLine: boolean;
@ -18,6 +22,7 @@ export type BuildState = {
};
const initialState: BuildState = {
language: languageMap['en'],
sidebar: {
left: { open: false },
right: { open: false },
@ -30,6 +35,8 @@ const initialState: BuildState = {
type SetThemePayload = { theme: Theme };
type SetLanguagePayload = { language: string };
type ToggleSidebarPayload = { sidebar: Sidebar };
type SetSidebarStatePayload = { sidebar: Sidebar; state: SidebarState };
@ -43,6 +50,11 @@ export const buildSlice = createSlice({
state.theme = theme;
},
setLanguage: (state, action: PayloadAction<SetLanguagePayload>) => {
const { language } = action.payload;
state.language = languageMap[language];
},
toggleSidebar: (state, action: PayloadAction<ToggleSidebarPayload>) => {
const { sidebar } = action.payload;
@ -64,7 +76,7 @@ export const buildSlice = createSlice({
},
});
export const { setTheme, toggleSidebar, setSidebarState, togglePageBreakLine, togglePageOrientation } =
export const { setTheme, setLanguage, toggleSidebar, setSidebarState, togglePageBreakLine, togglePageOrientation } =
buildSlice.actions;
export default buildSlice.reducer;

View File

@ -1,5 +1,6 @@
const { join } = require('path');
const colors = require('tailwindcss/colors');
const typography = require('@tailwindcss/typography');
module.exports = {
darkMode: 'class',
@ -20,4 +21,5 @@ module.exports = {
},
},
},
plugins: [typography],
};

View File

@ -1,4 +1,26 @@
import { useRouter } from 'next/router';
import { useEffect } from 'react';
import { useAppSelector } from '@/store/hooks';
const LocaleWrapper: React.FC = ({ children }) => {
const router = useRouter();
const language = useAppSelector((state) => state.build.language);
useEffect(() => {
if (!language) return;
const { code } = language;
const { pathname, asPath, query, locale } = router;
document.cookie = `NEXT_LOCALE=${code}; path=/; expires=2147483647`;
if (locale !== code) {
router.push({ pathname, query }, asPath, { locale: code });
}
}, [router, language]);
return <>{children}</>;
};

View File

@ -1,24 +1,24 @@
import { ThemeProvider, useMediaQuery } from '@mui/material';
import { ThemeProvider } from '@mui/material';
import { useEffect, useMemo } from 'react';
import { darkTheme, lightTheme } from '@/config/theme';
import { setTheme, Theme } from '@/store/build/buildSlice';
import { setTheme } from '@/store/build/buildSlice';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
const ThemeWrapper: React.FC = ({ children }) => {
const dispatch = useAppDispatch();
const prefersDarkMode = useMediaQuery('(prefers-color-scheme: dark)');
const theme: Theme | undefined = useAppSelector((state) => state.build.theme);
const theme = useAppSelector((state) => state.build.theme);
const isDarkMode = useMemo(() => theme === 'dark', [theme]);
const muiTheme = useMemo(() => (isDarkMode ? darkTheme : lightTheme), [isDarkMode]);
useEffect(() => {
if (theme === undefined) {
dispatch(setTheme({ theme: prefersDarkMode ? 'dark' : 'light' }));
dispatch(setTheme({ theme: 'dark' }));
}
}, [theme, dispatch, prefersDarkMode]);
}, [theme, dispatch]);
useEffect(() => {
if (isDarkMode) {

View File

@ -33,7 +33,6 @@
"env-cmd": "^10.1.0"
},
"devDependencies": {
"@changesets/cli": "^2.21.1",
"@typescript-eslint/eslint-plugin": "^5.13.0",
"@typescript-eslint/parser": "^5.13.0",
"cz-conventional-changelog": "^3.3.0",

File diff suppressed because it is too large Load Diff

View File

@ -28,5 +28,4 @@ export type Metadata = {
template: string;
typography: Typography;
date: DateConfig;
language: string;
};

View File

@ -33,8 +33,8 @@ WORKDIR /app
RUN curl -f https://get.pnpm.io/v6.16.js | node - add --global pnpm
COPY --from=builder /app/pnpm-*.yaml .
COPY --from=builder /app/package.json .
COPY --from=builder /app/pnpm-*.yaml ./
COPY --from=builder /app/package.json ./
COPY --from=builder /app/server/dist ./server/dist
COPY --from=builder /app/server/package.json ./server/package.json

View File

@ -10,7 +10,7 @@
"lint": "eslint --fix --ext .ts ./src"
},
"dependencies": {
"@nestjs/axios": "^0.0.6",
"@nestjs/axios": "^0.0.7",
"@nestjs/common": "^8.4.0",
"@nestjs/config": "^1.2.0",
"@nestjs/core": "^8.4.0",
@ -44,13 +44,13 @@
"playwright-chromium": "^1.19.2",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.4",
"rxjs": "^7.5.5",
"typeorm": "^0.2.45",
"uuid": "^8.3.2"
},
"devDependencies": {
"@nestjs/cli": "^8.2.2",
"@nestjs/schematics": "^8.0.7",
"@nestjs/schematics": "^8.0.8",
"@reactive-resume/schema": "workspace:*",
"@types/bcrypt": "^5.0.0",
"@types/cookie-parser": "^1.4.2",

View File

@ -142,7 +142,6 @@ const defaultState: Partial<Resume> = {
['skills', 'interests', 'languages', 'awards', 'certifications', 'publications'],
],
],
language: 'en',
template: 'kakuna',
typography: {
family: {