Merge branch 'main' into feat/add-docs-workspace

This commit is contained in:
Amruth Pillai 2022-03-11 19:48:35 +01:00
commit 2ff6761630
No known key found for this signature in database
GPG Key ID: E3C57DF9B80855AD
132 changed files with 4243 additions and 308 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -15,4 +15,7 @@ node_modules
# Docker
Dockerfile
.dockerignore
docker-compose.yml
docker-compose.yml
# Android App
/app

View File

@ -4,6 +4,7 @@ SECRET_KEY=change-me
# URLs
PUBLIC_URL=http://localhost:3000
PUBLIC_SERVER_URL=http://localhost:3100
# Database
POSTGRES_HOST=localhost

View File

@ -1,5 +1,6 @@
{
"root": true,
"ignorePatterns": ["/app"],
"parser": "@typescript-eslint/parser",
"extends": ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"],
"plugins": ["@typescript-eslint/eslint-plugin", "simple-import-sort", "unused-imports"],

16
.github/workflows/close-stale.yml vendored Normal file
View File

@ -0,0 +1,16 @@
name: 'Close stale issues and PRs'
on:
schedule:
- cron: '0 0 * * *'
jobs:
stale:
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v5.0.0
with:
stale-pr-message: 'This PR is stale because it has been open for 30 days with no activity. Remove the stale label or comment on this PR, otherwise it would be closed in 5 days.'
stale-issue-message: 'This issue is stale because it has been open for 30 days with no activity. Remove the stale label or comment on this issue, otherwise it would be closed in 5 days.'
days-before-stale: 30
days-before-close: 5

View File

@ -13,9 +13,9 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v3.0.0
- id: slug
name: Get Short Commit SHA
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- id: version
name: Get Version
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Login to Docker
uses: docker/login-action@v1.14.1
@ -31,7 +31,7 @@ jobs:
file: client/Dockerfile
tags: |
amruthpillai/reactive-resume:client-latest
amruthpillai/reactive-resume:client-${{ steps.slug.outputs.sha8 }}
amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }}
docker_server:
name: Docker (Server)
@ -41,9 +41,9 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v3.0.0
- id: slug
name: Get Short Commit SHA
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- id: version
name: Get Version
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Login to Docker
uses: docker/login-action@v1.14.1
@ -59,7 +59,7 @@ jobs:
file: server/Dockerfile
tags: |
amruthpillai/reactive-resume:server-latest
amruthpillai/reactive-resume:server-${{ steps.slug.outputs.sha8 }}
amruthpillai/reactive-resume:server-${{ steps.version.outputs.tag }}
github_client:
name: GitHub (Client)
@ -69,9 +69,9 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v3.0.0
- id: slug
name: Get Short Commit SHA
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- id: version
name: Get Version
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1.14.1
@ -88,7 +88,7 @@ jobs:
file: client/Dockerfile
tags: |
ghcr.io/amruthpillai/reactive-resume:client-latest
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.slug.outputs.sha8 }}
ghcr.io/amruthpillai/reactive-resume:client-${{ steps.version.outputs.tag }}
github_server:
name: GitHub (Server)
@ -98,9 +98,9 @@ jobs:
- name: Checkout the repository
uses: actions/checkout@v3.0.0
- id: slug
name: Get Short Commit SHA
run: echo "::set-output name=sha8::$(echo ${GITHUB_SHA} | cut -c1-8)"
- id: version
name: Get Version
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Login to GitHub Container Registry
uses: docker/login-action@v1.14.1
@ -117,4 +117,4 @@ jobs:
file: server/Dockerfile
tags: |
ghcr.io/amruthpillai/reactive-resume:server-latest
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.slug.outputs.sha8 }}
ghcr.io/amruthpillai/reactive-resume:server-${{ steps.version.outputs.tag }}

3
.gitignore vendored
View File

@ -5,3 +5,6 @@
# Project Dependencies
node_modules
# macOS
.DS_Store

View File

@ -1,6 +1,3 @@
# Global
node_modules
# Schema
schema/dist
@ -11,6 +8,25 @@ server/dist
client/.next
client/public/__ENV.js
# IDEs
.vscode
# Project Metadata
LICENSE
README.md
CHANGELOG.md
# Project Dependencies
node_modules
# Docker
Dockerfile
.dockerignore
docker-compose.yml
# Android App
/app
# Docs
docs/build
docs/.docusaurus

26
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,26 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Debug: Server",
"port": 9229,
"restart": true,
"stopOnEntry": false,
"protocol": "inspector"
},
{
"name": "Debug: Client",
"type": "node-terminal",
"request": "launch",
"command": "pnpm run dev:client",
"console": "integratedTerminal",
"serverReadyAction": {
"pattern": "started server on .+, url: (https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}

24
.vscode/settings.json vendored
View File

@ -1,15 +1,25 @@
{
"css.validate": false,
"scss.validate": false,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.wordWrap": "on",
"eslint.workingDirectories": ["schema", "client", "server"],
"i18n-ally.enabledFrameworks": ["i18next"],
"i18n-ally.localesPaths": ["client/public/locales"],
"i18n-ally.sourceLanguage": "en",
"i18n-ally.keystyle": "nested"
}
"eslint.workingDirectories": [
"schema",
"client",
"server"
],
"i18n-ally.enabledFrameworks": [
"react"
],
"i18n-ally.keystyle": "nested",
"i18n-ally.localesPaths": [
"client/public/locales"
],
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.sortKeys": true,
"scss.validate": false
}

View File

@ -1,19 +1,20 @@
## v3.0.0-beta.1 (2022-03-09)
# Changelog
### New feature:
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
- **client/landing**: add testimonials section to landing page([`6f02048`](https://github.com/AmruthPillai/Reactive-Resume/commit/6f02048ebd29b2a5b53aa291e0cdd10df93d032f)) (by Amruth Pillai)
- **client**: add language selector, language detector and privacy/tos pages([`a131bb3`](https://github.com/AmruthPillai/Reactive-Resume/commit/a131bb36525bf85eaee5cdb65542289cdfcff36e)) (by Amruth Pillai)
## [3.0.0-beta.6](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.0.0-beta.5...v3.0.0-beta.6) (2022-03-11)
### Bugs fixed:
### Features
- **pnpm**: install deps to update pnpm-lock.yaml([`54fd97b`](https://github.com/AmruthPillai/Reactive-Resume/commit/54fd97b5ecce629456a0dd1848981658136bbaa9)) (by Amruth Pillai)
- **mail.service**: use sendgrid api instead of nodemailer for better deliverability([`9df1219`](https://github.com/AmruthPillai/Reactive-Resume/commit/9df12194bf465d2f9c040c642036e05edef8d945)) (by Amruth Pillai)
- **printer.service**: add --disable-dev-shm-usage flag to chromium headless playwright browser([`e96b090`](https://github.com/AmruthPillai/Reactive-Resume/commit/e96b09090485fefc044dfc8e3daa9f52e123d946)) (by Amruth Pillai)
- **playwright**: use playwright docker image due to runtime error([`2696a54`](https://github.com/AmruthPillai/Reactive-Resume/commit/2696a54d176dd8821be97881447e075c05f9e8fb)) (by Amruth Pillai)
- **databasemodule**: make ssl optional, pass ca cert as base64 env([`c738f31`](https://github.com/AmruthPillai/Reactive-Resume/commit/c738f311dabdbe77770bb3c33959ac121d60019e)) (by Amruth Pillai)
- **i18n**: load locales from file system, instead of http-backend([`a4983ac`](https://github.com/AmruthPillai/Reactive-Resume/commit/a4983ac6bc35efee5b10de0768203dec9110b866)) (by Amruth Pillai)
* **lang:** add language switcher on the landing page, in the footer ([8bc7d25](https://github.com/AmruthPillai/Reactive-Resume/commit/8bc7d2599ef6af7a07bfbe886c43844152b0d9f7))
### Performance improves:
### Bug Fixes
- **app**: working docker build stage, with github actions ci to push image([`5104ea6`](https://github.com/AmruthPillai/Reactive-Resume/commit/5104ea6438d5e37d6c591949d6b3861cef4295b7)) (by Amruth Pillai)
* **i18n:** add missing translation keys, update lang/locale logic ([7d8828a](https://github.com/AmruthPillai/Reactive-Resume/commit/7d8828a358d653bb162877a64c75028eb82678cd))
* **webkit:** fix issue with webkit not supporting .at() ([2654cba](https://github.com/AmruthPillai/Reactive-Resume/commit/2654cba039eb73d33257c36fa90a52cabc9fda96))
## [3.0.0-beta.5](https://github.com/AmruthPillai/Reactive-Resume/compare/v3.0.0-beta.4...v3.0.0-beta.5) (2022-03-10)
### Bug Fixes
* **app:** fix issue with using swipelayout ([972e8b1](https://github.com/AmruthPillai/Reactive-Resume/commit/972e8b1bcf9ad44d8915bf23d189711672937bc0))

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 Amruth Pillai
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -2,10 +2,12 @@
# Reactive Resume
![Project Version](https://img.shields.io/github/package-json/v/AmruthPillai/Reactive-Resume?style=flat-square)
![Project License](https://img.shields.io/github/license/AmruthPillai/Reactive-Resume?style=flat-square)
[![Docker Pulls](https://img.shields.io/docker/pulls/amruthpillai/reactive-resume?style=flat-square)](https://hub.docker.com/r/amruthpillai/reactive-resume)
[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2FAmruthPillai%2FReactive-Resume.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2FAmruthPillai%2FReactive-Resume?ref=badge_shield)
## [Go to App](https://beta.rxresu.me) | [Docs](https://docs.rxresu.me)
## [Go to App](https://rxresu.me) | [Docs](https://docs.rxresu.me)
Reactive Resume is a free and open source resume builder thats 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 through a unique link and print as PDF, all for free, no advertisements, without losing the integrity and privacy of your data.
@ -15,13 +17,13 @@ You have complete control over what goes into your resume, how it looks, what co
- Free, forever
- No Advertising
- No Tracking (no 🍪s too)
- No User Tracking
- Sync your data across devices
- Accessible in multiple languages
- Import data from [LinkedIn](https://www.linkedin.com/), [JSON Resume](https://jsonresume.org/)
- Manage multiple resumes with one account
- Open Source (with large community support)
- Send your resume to others with a unique sharable link
- Accessible in multiple languages, [help translate here](https://translate.rxresu.me/)
- Pick any font from [Google Fonts](https://fonts.google.com/) to use on your resume
- Choose from 6 vibrant templates and more coming soon
- Export your resume to JSON or PDF format with just one click
@ -31,6 +33,15 @@ You have complete control over what goes into your resume, how it looks, what co
- Tailor-made Backend and Database, isolated from Google, Amazon etc.
- **Oh, and did I mention that it's free?**
## Languages
- English
- German (Deutsch)
- Kannada (ಕನ್ನಡ) (@aksh1251)
- Tamil (தமிழ்)
Help by [translating Reactive Resume](https://translate.rxresu.me) to your language!
## Tutorial
The docs include an extensive [Tutorial](https://docs.rxresu.me/tutorial) section which outline the features of Reactive Resume and help you through building your first resume on the app.
@ -41,9 +52,7 @@ For extensive information on how to build the app on your local machine, head ov
## Contributing
Please refer to the project's style and contribution guidelines for submitting pull requests.
In general, this project follows the "fork-and-pull" Git workflow.
This project makes use of [conventional commits](https://www.conventionalcommits.org/en/v1.0.0/) style and workflow for commit messages to ensure that the CHANGELOG is auto-generated. In general, this project follows the "fork-and-pull" Git workflow.
1. **Fork** the repo on GitHub
2. **Clone** the project to your own machine
@ -61,19 +70,19 @@ Use the [GitHub Issues](https://github.com/AmruthPillai/Reactive-Resume/issues/n
Reactive Resume would be nothing without the folks who supported me and kept the project alive in the beginning, and your cotinued support is what keeps me going. If you found Reactive Resume to be useful, helpful or just insightful and appreciate the effort I took to make the project, please consider donating as little or as much as your can.
[☕️ Buy me a coffee](https://www.buymeacoffee.com/AmruthPillai)
### [☕️ Buy me a coffee](https://www.buymeacoffee.com/AmruthPillai) | [💸 PayPal](https://paypal.me/RajaRajanA)
## Infrastructure
- [Next.js](), frontend
- [NestJS](), backend
- [PostgreSQL](), database
- [DigitalOcean](), infrastructure provider
- [Crowdin](), translation management platform
- [Next.js](https://nextjs.org/), frontend
- [NestJS](https://nestjs.com/), backend
- [PostgreSQL](https://www.postgresql.org/), database
- [DigitalOcean](https://www.digitalocean.com/), infrastructure provider
- [Crowdin](https://translate.rxresu.me/), translation management platform
 
<a href="https://www.digitalocean.com/">
<a href="https://pillai.xyz/digitalocean">
<img src="https://opensource.nyc3.cdn.digitaloceanspaces.com/attribution/assets/PoweredByDO/DO_Powered_by_Badge_blue.svg" width="200px" />
</a>

33
app/.gitignore vendored Normal file
View File

@ -0,0 +1,33 @@
# Gradle files
.gradle/
build/
# Local configuration file (sdk path, etc)
local.properties
# Log/OS Files
*.log
# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.apk
output.json
# IntelliJ
*.iml
.idea/
misc.xml
deploymentTargetDropDown.xml
render.experimental.xml
# Keystore files
*.jks
*.keystore
# Google Services (e.g. APIs or Firebase)
google-services.json
# Android Profiling
*.hprof

8
app/app/.editorconfig Normal file
View File

@ -0,0 +1,8 @@
root = true
[*]
charset = utf-8
indent_size = 4
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

2
app/app/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/build
/release

46
app/app/build.gradle Normal file
View File

@ -0,0 +1,46 @@
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
}
android {
compileSdk 32
defaultConfig {
applicationId "me.rxresu.app"
minSdk 21
targetSdk 32
versionCode 2
versionName "1.0"
resConfigs "en"
}
buildTypes {
release {
minifyEnabled true
shrinkResources true
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
buildFeatures {
viewBinding true
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'com.google.android.material:material:1.5.0'
}

21
app/app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.rxresu.app">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ReactiveResume.NoActionBar">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="@style/Theme.ReactiveResume.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,76 @@
package me.rxresu.app
import android.annotation.SuppressLint
import android.graphics.Bitmap
import android.os.Bundle
import android.view.KeyEvent
import android.webkit.WebResourceError
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.appcompat.app.AppCompatActivity
class MainActivity : AppCompatActivity() {
private lateinit var webView: WebView
private var isLoaded: Boolean = false
private var webURL = "https://rxresu.me"
@SuppressLint("SetJavaScriptEnabled")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
webView = findViewById(R.id.webview)
webView.settings.javaScriptEnabled = true
webView.settings.userAgentString = "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Mobile Safari/537.36"
}
override fun onResume() {
if (!isLoaded) loadWebView()
super.onResume()
}
private fun loadWebView() {
webView.loadUrl(webURL)
webView.webViewClient = object : WebViewClient() {
override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean {
val url = request?.url.toString()
view?.loadUrl(url)
return super.shouldOverrideUrlLoading(view, request)
}
override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {
super.onPageStarted(view, url, favicon)
}
override fun onPageFinished(view: WebView?, url: String?) {
isLoaded = true
super.onPageFinished(view, url)
}
override fun onReceivedError(view: WebView, request: WebResourceRequest, error: WebResourceError) {
isLoaded = false
super.onReceivedError(view, request, error)
}
}
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_DOWN) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (webView.canGoBack()) {
webView.goBack()
}
return true
}
}
return super.onKeyDown(keyCode, event)
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<include
layout="@layout/content_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<WebView
android:id="@+id/webview"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">Reactive Resume</string>
</resources>

View File

@ -0,0 +1,12 @@
<resources>
<style name="Theme.ReactiveResume" parent="Theme.MaterialComponents.DayNight.DarkActionBar" />
<style name="Theme.ReactiveResume.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>
<style name="Theme.ReactiveResume.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar" />
<style name="Theme.ReactiveResume.PopupOverlay" parent="ThemeOverlay.AppCompat.Light" />
</resources>

9
app/build.gradle Normal file
View File

@ -0,0 +1,9 @@
plugins {
id 'com.android.application' version '7.1.2' apply false
id 'com.android.library' version '7.1.2' apply false
id 'org.jetbrains.kotlin.android' version '1.6.10' apply false
}
task clean(type: Delete) {
delete rootProject.buildDir
}

23
app/gradle.properties Normal file
View File

@ -0,0 +1,23 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app"s APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

BIN
app/gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Wed Mar 09 21:34:49 CET 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
distributionPath=wrapper/dists
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

185
app/gradlew vendored Executable file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
app/gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

16
app/settings.gradle Normal file
View File

@ -0,0 +1,16 @@
pluginManagement {
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "Reactive Resume"
include ':app'

View File

@ -36,13 +36,14 @@ RUN apk add --no-cache curl \
COPY --from=builder /app/pnpm-*.yaml ./
COPY --from=builder /app/package.json ./
COPY --from=builder /app/client/package.json ./client/package.json
RUN pnpm install -F client --frozen-lockfile --prod
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
COPY --from=builder /app/client/next-i18next.config.js ./client/next-i18next.config.js
COPY --from=builder /app/client/package.json ./client/package.json
RUN pnpm install -F client --frozen-lockfile --prod
EXPOSE 3000

View File

@ -62,7 +62,7 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
const url = await mutateAsync({ username, slug });
download(url);
download(`/api${url}`);
};
return (
@ -74,19 +74,19 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
})}
>
<div className={styles.controller}>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.zoom-in')}>
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.zoom-in') as string}>
<ButtonBase onClick={() => zoomIn(0.25)}>
<ZoomIn fontSize="medium" />
</ButtonBase>
</Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.zoom-out')}>
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.zoom-out') as string}>
<ButtonBase onClick={() => zoomOut(0.25)}>
<ZoomOut fontSize="medium" />
</ButtonBase>
</Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.center-artboard')}>
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.center-artboard') as string}>
<ButtonBase onClick={() => centerView(0.95)}>
<FilterCenterFocus fontSize="medium" />
</ButtonBase>
@ -96,7 +96,7 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
{isDesktop && (
<>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-orientation')}>
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-orientation') as string}>
<ButtonBase onClick={handleTogglePageOrientation}>
{orientation === 'vertical' ? (
<AlignHorizontalCenter fontSize="medium" />
@ -106,13 +106,13 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
</ButtonBase>
</Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-page-break-line')}>
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-page-break-line') as string}>
<ButtonBase onClick={handleTogglePageBreakLine}>
<InsertPageBreak fontSize="medium" />
</ButtonBase>
</Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.toggle-sidebars')}>
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.toggle-sidebars') as string}>
<ButtonBase onClick={handleToggleSidebar}>
<ViewSidebar fontSize="medium" />
</ButtonBase>
@ -122,13 +122,13 @@ const ArtboardController: React.FC<ReactZoomPanPinchRef> = ({ zoomIn, zoomOut, c
</>
)}
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.copy-link')}>
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.copy-link') as string}>
<ButtonBase onClick={handleCopyLink}>
<Link fontSize="medium" />
</ButtonBase>
</Tooltip>
<Tooltip arrow placement="top" title={t<string>('builder.controller.tooltip.export-pdf')}>
<Tooltip arrow placement="top" title={t('builder.controller.tooltip.export-pdf') as string}>
<ButtonBase onClick={handleExportPDF} disabled={isLoading}>
<Download fontSize="medium" />
</ButtonBase>

View File

@ -184,7 +184,7 @@ const Header = () => {
<ListItemText>{t('builder.header.menu.share-link')}</ListItemText>
</MenuItem>
) : (
<Tooltip arrow placement="right" title={t<string>('builder.header.menu.tooltips.share-link')}>
<Tooltip arrow placement="right" title={t('builder.header.menu.tooltips.share-link') as string}>
<div>
<MenuItem>
<ListItemIcon>
@ -196,7 +196,7 @@ const Header = () => {
</Tooltip>
)}
<Tooltip arrow placement="right" title={t<string>('builder.header.menu.tooltips.delete')}>
<Tooltip arrow placement="right" title={t('builder.header.menu.tooltips.delete') as string}>
<MenuItem onClick={handleDelete}>
<ListItemIcon>
<Delete className="scale-90" />

View File

@ -58,14 +58,14 @@ const PhotoFilters = () => {
<div className="flex items-center">
<FormControlLabel
label={t<string>('builder.leftSidebar.sections.basics.photo-filters.effects.grayscale.label')}
label={t('builder.leftSidebar.sections.basics.photo-filters.effects.grayscale.label') as string}
control={
<Checkbox color="secondary" checked={grayscale} onChange={(_, value) => handleSetGrayscale(value)} />
}
/>
<FormControlLabel
label={t<string>('builder.leftSidebar.sections.basics.photo-filters.effects.border.label')}
label={t('builder.leftSidebar.sections.basics.photo-filters.effects.border.label') as string}
control={<Checkbox color="secondary" checked={border} onChange={(_, value) => handleSetBorder(value)} />}
/>
</div>

View File

@ -67,8 +67,8 @@ const PhotoUpload: React.FC = () => {
<Tooltip
title={
isEmpty(photo.url)
? t<string>('builder.leftSidebar.sections.basics.photo-upload.tooltip.upload')
: t<string>('builder.leftSidebar.sections.basics.photo-upload.tooltip.remove')
? (t('builder.leftSidebar.sections.basics.photo-upload.tooltip.upload') as string)
: (t('builder.leftSidebar.sections.basics.photo-upload.tooltip.remove') as string)
}
>
<Avatar sx={{ width: 96, height: 96 }} src={photo.url} />

View File

@ -41,7 +41,7 @@ const Profiles = () => {
<footer className="flex justify-end">
<Button variant="outlined" startIcon={<Add />} onClick={handleAdd}>
{t('builder.common.actions.add', {
section: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
})}
</Button>
</footer>

View File

@ -41,14 +41,14 @@ const Section: React.FC<Props> = ({
const visibility = useAppSelector<boolean>((state) => get(state.resume, `${path}.visible`, true));
const handleAdd = () => {
const id = path.split('.').at(-1) as string;
const id = path.split('.')[1];
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
dispatch(setModalState({ modal, state: { open: true, payload: { path } } }));
};
const handleEdit = (item: ListItem) => {
const id = path.split('.').at(-1) as string;
const id = path.split('.')[1];
const modal: ModalName = validate(id) ? 'builder.sections.custom' : `builder.${path}`;
const payload = validate(id) ? { path, item } : { item };

View File

@ -32,7 +32,7 @@ const SectionSettings: React.FC<Props> = ({ path }) => {
return (
<div>
<Tooltip title={t<string>('builder.common.columns.tooltip')}>
<Tooltip title={t('builder.common.columns.tooltip') as string}>
<ButtonBase onClick={handleClick} sx={{ padding: 1, borderRadius: 1 }} className="opacity-50 hover:opacity-75">
<ViewWeek /> <span className="ml-1.5 text-xs">{columns}</span>
</ButtonBase>

View File

@ -47,7 +47,7 @@ const Export = () => {
const url = await mutateAsync({ username, slug });
download(url);
download(`/api${url}`);
};
return (

View File

@ -62,7 +62,7 @@ const Layout = () => {
path="metadata.layout"
name={t('builder.rightSidebar.sections.layout.heading')}
action={
<Tooltip title={t<string>('builder.rightSidebar.sections.layout.tooltip.reset-layout')}>
<Tooltip title={t('builder.rightSidebar.sections.layout.tooltip.reset-layout') as string}>
<IconButton onClick={handleResetLayout}>
<Restore />
</IconButton>
@ -81,7 +81,7 @@ const Layout = () => {
<div className={clsx(styles.delete, { hidden: pageIndex === 0 })}>
<Tooltip
title={t<string>('builder.common.actions.delete', { token: t('builder.common.glossary.page') })}
title={t('builder.common.actions.delete', { token: t('builder.common.glossary.page') }) as string}
>
<IconButton size="small" onClick={() => handleDeletePage(pageIndex)}>
<Close fontSize="small" />

View File

@ -13,17 +13,18 @@ import {
import { DateConfig, Resume } from '@reactive-resume/schema';
import dayjs from 'dayjs';
import get from 'lodash/get';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { useMemo } from 'react';
import { useMutation } from 'react-query';
import Heading from '@/components/shared/Heading';
import ThemeSwitch from '@/components/shared/ThemeSwitch';
import { Language, languages } from '@/config/languages';
import { Language, languageMap, languages } from '@/config/languages';
import { ServerError } from '@/services/axios';
import queryClient from '@/services/react-query';
import { loadSampleData, LoadSampleDataParams, resetResume, ResetResumeParams } from '@/services/resume';
import { setLanguage, setTheme, togglePageBreakLine, togglePageOrientation } from '@/store/build/buildSlice';
import { setTheme, togglePageBreakLine, togglePageOrientation } from '@/store/build/buildSlice';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setResumeState } from '@/store/resume/resumeSlice';
import { dateFormatOptions } from '@/utils/date';
@ -33,9 +34,10 @@ const Settings = () => {
const dispatch = useAppDispatch();
const { locale, ...router } = useRouter();
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);
@ -59,7 +61,12 @@ const Settings = () => {
dispatch(setResumeState({ path: 'metadata.date.format', value }));
const handleChangeLanguage = (value: Language | null) => {
dispatch(setLanguage({ language: value?.code || 'en' }));
const { pathname, asPath, query, push } = router;
const code = value?.code || 'en';
document.cookie = `NEXT_LOCALE=${code}; path=/; expires=2147483647`;
push({ pathname, query }, asPath, { locale: code });
};
const handleLoadSampleData = async () => {
@ -122,7 +129,7 @@ const Settings = () => {
disableClearable
className="my-2 w-full"
options={languages}
value={language}
value={languageMap[locale ?? 'en']}
isOptionEqualToValue={(a, b) => a.code === b.code}
onChange={(_, value) => handleChangeLanguage(value)}
renderInput={(params) => <TextField {...params} />}

View File

@ -63,7 +63,7 @@ const Sharing = () => {
<div className="mt-1 flex w-full">
<FormControlLabel
label={t<string>('builder.rightSidebar.sections.sharing.short-url.label')}
label={t('builder.rightSidebar.sections.sharing.short-url.label') as string}
control={
<Checkbox className="mr-1" checked={showShortUrl} onChange={(_, value) => setShowShortUrl(value)} />
}

View File

@ -161,7 +161,7 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
<ListItemText>{t('dashboard.resume.menu.share-link')}</ListItemText>
</MenuItem>
) : (
<Tooltip arrow placement="right" title={t<string>('dashboard.resume.menu.tooltips.share-link')}>
<Tooltip arrow placement="right" title={t('dashboard.resume.menu.tooltips.share-link') as string}>
<div>
<MenuItem>
<ListItemIcon>
@ -173,7 +173,7 @@ const ResumePreview: React.FC<Props> = ({ resume }) => {
</Tooltip>
)}
<Tooltip arrow placement="right" title={t<string>('dashboard.resume.menu.tooltips.delete')}>
<Tooltip arrow placement="right" title={t('dashboard.resume.menu.tooltips.delete') as string}>
<MenuItem onClick={handleDelete}>
<ListItemIcon>
<DeleteOutline className="scale-90" />

View File

@ -12,7 +12,7 @@ const ColorAvatar: React.FC<Props> = ({ color, size = 20, onClick }) => {
return (
<IconButton onClick={handleClick}>
<Avatar sx={{ bgcolor: color, width: size, height: size }}> </Avatar>
<Avatar sx={{ bgcolor: color, width: size, height: size }}>&nbsp;</Avatar>
</IconButton>
);
};

View File

@ -9,8 +9,9 @@ const Footer: React.FC<Props> = ({ className }) => {
const { t } = useTranslation();
return (
<footer className={clsx('text-xs', className)}>
<div className={clsx('text-xs', className)}>
<p>{t('common.footer.license')}</p>
<p>
<Trans t={t} i18nKey="common.footer.credit">
A passion project by
@ -19,7 +20,7 @@ const Footer: React.FC<Props> = ({ className }) => {
</a>
</Trans>
</p>
</footer>
</div>
);
};

View File

@ -37,7 +37,7 @@ const Heading: React.FC<Props> = ({
const [editMode, setEditMode] = useState(false);
const id = useMemo(() => path && path.split('.').at(-1), [path]);
const id = useMemo(() => path.split('.')[1], [path]);
const icon = sections.find((x) => x.id === id)?.icon || <Grade />;
@ -72,19 +72,19 @@ const Heading: React.FC<Props> = ({
})}
>
{isEditable && (
<Tooltip title={t<string>('builder.common.tooltip.rename-section')}>
<Tooltip title={t('builder.common.tooltip.rename-section') as string}>
<IconButton onClick={toggleEditMode}>{editMode ? <Check /> : <DriveFileRenameOutline />}</IconButton>
</Tooltip>
)}
{isHideable && (
<Tooltip title={t<string>('builder.common.tooltip.toggle-visibility')}>
<Tooltip title={t('builder.common.tooltip.toggle-visibility') as string}>
<IconButton onClick={toggleVisibility}>{visibility ? <Visibility /> : <VisibilityOff />}</IconButton>
</Tooltip>
)}
{isDeletable && (
<Tooltip title={t<string>('builder.common.tooltip.delete-section')}>
<Tooltip title={t('builder.common.tooltip.delete-section') as string}>
<IconButton onClick={handleDelete}>
<Delete />
</IconButton>

View File

@ -0,0 +1,13 @@
.popover {
width: 460px;
@apply px-4 py-2;
}
.container {
@apply grid grid-cols-3 items-center justify-center gap-x-2;
}
.language {
@apply py-2 px-4 cursor-pointer text-center hover:underline;
}

View File

@ -0,0 +1,66 @@
import { Language } from '@mui/icons-material';
import { IconButton, Popover } from '@mui/material';
import { useRouter } from 'next/router';
import { useTranslation } from 'next-i18next';
import { MouseEvent, useState } from 'react';
import { languages } from '@/config/languages';
import styles from './LanguageSwitcher.module.scss';
const LanguageSwitcher = () => {
const router = useRouter();
const { t } = useTranslation();
const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null);
const handleClick = (event: MouseEvent<HTMLButtonElement>) => setAnchorEl(event.currentTarget);
const handleClose = () => setAnchorEl(null);
const handleChangeLanguage = (locale: string) => {
const { pathname, asPath, query } = router;
document.cookie = `NEXT_LOCALE=${locale}; path=/; expires=2147483647`;
router.push({ pathname, query }, asPath, { locale });
};
return (
<div>
<IconButton onClick={handleClick}>
<Language />
</IconButton>
<Popover
anchorEl={anchorEl}
open={Boolean(anchorEl)}
onClose={handleClose}
anchorOrigin={{
vertical: 'top',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
>
<div className={styles.popover}>
<div className={styles.container}>
{languages.map(({ code, name, localName }) => (
<p key={code} className={styles.language} onClick={() => handleChangeLanguage(code)}>
{name} {localName && `(${localName})`}
</p>
))}
<a href="https://translate.rxresu.me" target="_blank" rel="noreferrer" className={styles.language}>
{t('common.footer.language.missing')}
</a>
</div>
</div>
</Popover>
</div>
);
};
export default LanguageSwitcher;

View File

@ -3,6 +3,7 @@ import { Divider, IconButton, ListItemIcon, ListItemText, Menu, MenuItem, Toolti
import { ListItem as ListItemType } from '@reactive-resume/schema';
import clsx from 'clsx';
import isFunction from 'lodash/isFunction';
import { useTranslation } from 'next-i18next';
import React, { useRef, useState } from 'react';
import { DropTargetMonitor, useDrag, useDrop, XYCoord } from 'react-dnd';
@ -26,6 +27,8 @@ type Props = {
};
const ListItem: React.FC<Props> = ({ item, index, title, subtitle, onMove, onEdit, onDelete, onDuplicate }) => {
const { t } = useTranslation();
const ref = useRef<HTMLDivElement>(null);
const [anchorEl, setAnchorEl] = useState<Element | null>(null);
@ -122,29 +125,25 @@ const ListItem: React.FC<Props> = ({ item, index, title, subtitle, onMove, onEdi
<ListItemIcon>
<DriveFileRenameOutline className="scale-90" />
</ListItemIcon>
<ListItemText>Edit</ListItemText>
<ListItemText>{t('builder.common.list.actions.edit')}</ListItemText>
</MenuItem>
<MenuItem onClick={() => handleDuplicate(item)}>
<ListItemIcon>
<FileCopy className="scale-90" />
</ListItemIcon>
<ListItemText>Duplicate</ListItemText>
<ListItemText>{t('builder.common.list.actions.duplicate')}</ListItemText>
</MenuItem>
<Divider />
<Tooltip
arrow
placement="right"
title="Are you sure you want to delete this item? This is an irreversible action."
>
<Tooltip arrow placement="right" title={t('builder.common.tooltip.delete-item') as string}>
<div>
<MenuItem onClick={() => handleDelete(item)}>
<ListItemIcon>
<DeleteOutline className="scale-90" />
</ListItemIcon>
<ListItemText>Delete</ListItemText>
<ListItemText>{t('builder.common.list.actions.delete')}</ListItemText>
</MenuItem>
</div>
</Tooltip>

View File

@ -9,6 +9,21 @@ export const languages: Language[] = [
code: 'en',
name: 'English',
},
{
code: 'kn',
name: 'Kannada',
localName: 'ಕನ್ನಡ',
},
{
code: 'ta',
name: 'Tamil',
localName: 'தமிழ்',
},
{
code: 'de',
name: 'German',
localName: 'Deutsch',
},
];
export const languageMap: Record<string, Language> = languages.reduce(

View File

@ -2,6 +2,9 @@
export const FONTS_QUERY = 'fonts';
export const RESUMES_QUERY = 'resumes';
// Regular Expressions
export const VALID_URL_REGEX = /[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
// Date Formats
export const FILENAME_TIMESTAMP = 'DDMMYYYYHHmmss';

View File

@ -122,7 +122,7 @@ const LoginModal: React.FC = () => {
startIcon={<Google />}
onClick={handleLoginWithGoogle}
>
{t('modals.auth.login.actions.login-google')}
{t('modals.auth.login.actions.google')}
</Button>
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>

View File

@ -1,13 +1,15 @@
import env from '@beam-australia/react-env';
import { joiResolver } from '@hookform/resolvers/joi';
import { HowToReg } from '@mui/icons-material';
import { Google, HowToReg } from '@mui/icons-material';
import { Button, TextField } from '@mui/material';
import Joi from 'joi';
import { Trans, useTranslation } from 'next-i18next';
import { GoogleLoginResponse, GoogleLoginResponseOffline, useGoogleLogin } from 'react-google-login';
import { Controller, useForm } from 'react-hook-form';
import { useMutation } from 'react-query';
import BaseModal from '@/components/shared/BaseModal';
import { register as registerUser, RegisterParams } from '@/services/auth';
import { loginWithGoogle, LoginWithGoogleParams, register as registerUser, RegisterParams } from '@/services/auth';
import { ServerError } from '@/services/axios';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
@ -56,6 +58,19 @@ const RegisterModal: React.FC = () => {
const { mutateAsync, isLoading } = useMutation<void, ServerError, RegisterParams>(registerUser);
const { mutateAsync: loginWithGoogleMutation } = useMutation<void, ServerError, LoginWithGoogleParams>(
loginWithGoogle
);
const { signIn } = useGoogleLogin({
clientId: env('GOOGLE_CLIENT_ID'),
onSuccess: async (response: GoogleLoginResponse | GoogleLoginResponseOffline) => {
await loginWithGoogleMutation({ accessToken: (response as GoogleLoginResponse).accessToken });
handleClose();
},
});
const handleClose = () => {
dispatch(setModalState({ modal: 'auth.register', state: { open: false } }));
reset();
@ -63,7 +78,6 @@ const RegisterModal: React.FC = () => {
const onSubmit = async ({ name, username, email, password }: FormData) => {
await mutateAsync({ name, username, email, password });
handleClose();
};
@ -72,6 +86,10 @@ const RegisterModal: React.FC = () => {
dispatch(setModalState({ modal: 'auth.login', state: { open: true } }));
};
const handleLoginWithGoogle = () => {
signIn();
};
return (
<BaseModal
icon={<HowToReg />}
@ -79,9 +97,21 @@ const RegisterModal: React.FC = () => {
heading={t('modals.auth.register.heading')}
handleClose={handleClose}
footerChildren={
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
{t('modals.auth.register.actions.register')}
</Button>
<>
<Button
type="submit"
variant="outlined"
disabled={isLoading}
startIcon={<Google />}
onClick={handleLoginWithGoogle}
>
{t('modals.auth.register.actions.google')}
</Button>
<Button type="submit" onClick={handleSubmit(onSubmit)} disabled={isLoading}>
{t('modals.auth.register.actions.register')}
</Button>
</>
}
>
<p>{t('modals.auth.register.body')}</p>

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -34,9 +35,7 @@ const schema = Joi.object<FormData>().keys({
title: Joi.string().required(),
awarder: Joi.string().required(),
date: Joi.string().allow(''),
url: Joi.string()
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
summary: Joi.string().allow(''),
});
@ -154,6 +153,7 @@ const AwardModal: React.FC = () => {
render={({ field, fieldState }) => (
<TextField
label={t('builder.common.form.url.label')}
placeholder="https://"
error={!!fieldState.error}
helperText={fieldState.error?.message}
{...field}

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -34,9 +35,7 @@ const schema = Joi.object<FormData>().keys({
name: Joi.string().required(),
issuer: Joi.string().required(),
date: Joi.string().allow(''),
url: Joi.string()
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
summary: Joi.string().allow(''),
});
@ -154,6 +153,7 @@ const CertificateModal: React.FC = () => {
render={({ field, fieldState }) => (
<TextField
label={t('builder.common.form.url.label')}
placeholder="https://"
error={!!fieldState.error}
helperText={fieldState.error?.message}
{...field}

View File

@ -14,6 +14,7 @@ import { Controller, useForm } from 'react-hook-form';
import ArrayInput from '@/components/shared/ArrayInput';
import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -47,9 +48,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''),
end: Joi.string().allow(''),
}),
url: Joi.string()
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
level: Joi.string().allow(''),
levelNum: Joi.number().min(0).max(10),
summary: Joi.string().allow(''),
@ -194,6 +193,7 @@ const CustomModal: React.FC = () => {
render={({ field, fieldState }) => (
<TextField
label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2"
error={!!fieldState.error}
helperText={fieldState.error?.message}

View File

@ -14,6 +14,7 @@ import { Controller, useForm } from 'react-hook-form';
import ArrayInput from '@/components/shared/ArrayInput';
import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -46,9 +47,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''),
end: Joi.string().allow(''),
}),
url: Joi.string()
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
summary: Joi.string().allow(''),
courses: Joi.array().items(Joi.string().optional()),
});
@ -217,6 +216,7 @@ const EducationModal: React.FC = () => {
render={({ field, fieldState }) => (
<TextField
label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2"
error={!!fieldState.error}
helperText={fieldState.error?.message}

View File

@ -10,6 +10,7 @@ import { useEffect, useMemo } from 'react';
import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -21,17 +22,14 @@ const path = 'sections.profile';
const defaultState: FormData = {
network: '',
username: '',
url: 'https://',
url: '',
};
const schema = Joi.object<FormData>({
id: Joi.string(),
network: Joi.string().required(),
username: Joi.string().required(),
url: Joi.string()
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.default('https://')
.allow(''),
url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
});
const ProfileModal: React.FC = () => {
@ -45,10 +43,10 @@ const ProfileModal: React.FC = () => {
const isEditMode = useMemo(() => !!item, [item]);
const addText = t('builder.common.actions.add', {
section: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
});
const editText = t('builder.common.actions.edit', {
section: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
token: t('builder.leftSidebar.sections.profiles.heading', { count: 1 }),
});
const { reset, control, handleSubmit } = useForm<FormData>({
@ -131,6 +129,7 @@ const ProfileModal: React.FC = () => {
<TextField
label={t('builder.common.form.url.label')}
className="col-span-2"
placeholder="https://"
error={!!fieldState.error}
helperText={fieldState.error?.message}
{...field}

View File

@ -14,6 +14,7 @@ import { Controller, useForm } from 'react-hook-form';
import ArrayInput from '@/components/shared/ArrayInput';
import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -42,9 +43,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''),
end: Joi.string().allow(''),
}),
url: Joi.string()
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
summary: Joi.string().allow(''),
keywords: Joi.array().items(Joi.string().optional()),
});
@ -187,6 +186,7 @@ const ProjectModal: React.FC = () => {
render={({ field, fieldState }) => (
<TextField
label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2"
error={!!fieldState.error}
helperText={fieldState.error?.message}

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -34,9 +35,7 @@ const schema = Joi.object<FormData>().keys({
name: Joi.string().required(),
publisher: Joi.string().required(),
date: Joi.string().allow(''),
url: Joi.string()
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
summary: Joi.string().allow(''),
});
@ -154,6 +153,7 @@ const PublicationModal: React.FC = () => {
render={({ field, fieldState }) => (
<TextField
label={t('builder.common.form.url.label')}
placeholder="https://"
error={!!fieldState.error}
helperText={fieldState.error?.message}
{...field}

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -40,9 +41,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''),
end: Joi.string().allow(''),
}),
url: Joi.string()
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
summary: Joi.string().allow(''),
});
@ -184,6 +183,7 @@ const VolunteerModal: React.FC = () => {
render={({ field, fieldState }) => (
<TextField
label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2"
error={!!fieldState.error}
helperText={fieldState.error?.message}

View File

@ -13,6 +13,7 @@ import { Controller, useForm } from 'react-hook-form';
import BaseModal from '@/components/shared/BaseModal';
import MarkdownSupported from '@/components/shared/MarkdownSupported';
import { VALID_URL_REGEX } from '@/constants/index';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { setModalState } from '@/store/modal/modalSlice';
import { addItem, editItem } from '@/store/resume/resumeSlice';
@ -40,9 +41,7 @@ const schema = Joi.object<FormData>().keys({
start: Joi.string().allow(''),
end: Joi.string().allow(''),
}),
url: Joi.string()
.pattern(/[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/, { name: 'valid URL' })
.allow(''),
url: Joi.string().pattern(VALID_URL_REGEX, { name: 'valid URL' }).allow(''),
summary: Joi.string().allow(''),
});
@ -184,6 +183,7 @@ const WorkModal: React.FC = () => {
render={({ field, fieldState }) => (
<TextField
label={t('builder.common.form.url.label')}
placeholder="https://"
className="col-span-2"
error={!!fieldState.error}
helperText={fieldState.error?.message}

View File

@ -125,7 +125,7 @@ const CreateResumeModal: React.FC = () => {
<FormGroup>
<FormControlLabel
label={t<string>('modals.dashboard.create-resume.form.public.label')}
label={t('modals.dashboard.create-resume.form.public.label') as string}
control={
<Controller
name="isPublic"

View File

@ -84,7 +84,7 @@ const ImportExternalModal: React.FC = () => {
<p className="mb-2">
<Trans t={t} i18nKey="modals.dashboard.import-external.linkedin.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
Head over to the
<a
href="https://www.linkedin.com/psettings/member-data"
className="underline"
@ -93,7 +93,7 @@ const ImportExternalModal: React.FC = () => {
>
Data Privacy
</a>
section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP archive below.
section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP file below.
</Trans>
</p>

View File

@ -3,7 +3,7 @@ const path = require('path');
const i18nConfig = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'ta'],
locales: ['en', 'kn'],
},
nsSeparator: '.',
localePath: path.resolve('./public/locales'),

View File

@ -17,7 +17,7 @@
"@mui/lab": "^5.0.0-alpha.72",
"@mui/material": "^5.5.0",
"@reduxjs/toolkit": "^1.8.0",
"axios": "^0.26.0",
"axios": "^0.26.1",
"clsx": "^1.1.1",
"dayjs": "^1.10.8",
"downloadjs": "^1.4.7",

View File

@ -90,7 +90,7 @@ const Preview: NextPage<Props> = ({ username, slug, resume: initialData }) => {
try {
const url = await mutateAsync({ username, slug });
download(url);
download(`/api${url}`);
} catch {
toast.error('Something went wrong, please try again later.');
}

View File

@ -9,6 +9,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations';
import Testimony from '@/components/landing/Testimony';
import Footer from '@/components/shared/Footer';
import LanguageSwitcher from '@/components/shared/LanguageSwitcher';
import Logo from '@/components/shared/Logo';
import NoSSR from '@/components/shared/NoSSR';
import { screenshots } from '@/config/screenshots';
@ -147,6 +148,18 @@ const Home: NextPage = () => {
<h6>{t('landing.links.heading')}</h6>
<div>
<Link href="/meta/privacy" passHref>
<Button variant="text" startIcon={<LinkIcon />}>
{t('landing.links.links.privacy')}
</Button>
</Link>
<Link href="/meta/service" passHref>
<Button variant="text" startIcon={<LinkIcon />}>
{t('landing.links.links.service')}
</Button>
</Link>
<a href={GITHUB_URL} target="_blank" rel="noreferrer">
<Button variant="text" startIcon={<LinkIcon />}>
{t('landing.links.links.github')}
@ -162,15 +175,19 @@ const Home: NextPage = () => {
</section>
<section className={styles.section}>
<a href="https://www.digitalocean.com/" target="_blank" rel="noreferrer">
<a href="https://pillai.xyz/digitalocean" target="_blank" rel="noreferrer">
<Image src="/images/sponsors/digitalocean.svg" alt="Powered By DigitalOcean" width={200} height={40} />
</a>
</section>
<footer>
<Footer className="font-semibold leading-5 opacity-50" />
<div className={styles.version}>
<Footer className="font-semibold leading-5 opacity-50" />
<div>v{process.env.appVersion}</div>
<div>v{process.env.appVersion}</div>
</div>
<LanguageSwitcher />
</footer>
</main>
);

View File

@ -17,7 +17,7 @@ const TermsOfService: NextPage = () => (
<h3>
<strong>
Do whatever you want, governed by the{' '}
Do whatever you want, distributed under the{' '}
<a href="https://choosealicense.com/licenses/mit/" target="_blank" rel="noreferrer">
MIT license
</a>

View File

@ -60,7 +60,7 @@ const Preview: NextPage<Props> = ({ shortId }) => {
try {
const url = await mutateAsync({ username: resume.user.username, slug: resume.slug });
download(url);
download(`/api${url}`);
} catch {
toast.error('Something went wrong, please try again later.');
}

View File

@ -0,0 +1,357 @@
{
"common": {
"actions": {
"add": "Neue {{token}} hinzufügen",
"delete": "Löschen {{token}}",
"edit": "Bearbeiten {{token}}"
},
"columns": {
"heading": "Spalten",
"tooltip": "Anzahl der Spalten ändern"
},
"form": {
"date": {
"label": "Datum"
},
"description": {
"label": "Beschreibung"
},
"email": {
"label": "E-Mail Adresse"
},
"end-date": {
"help-text": "Dieses Feld leer lassen, wenn noch vorhanden",
"label": "Enddatum"
},
"keywords": {
"label": "Stichwörter"
},
"level": {
"label": "Ebene"
},
"levelNum": {
"label": "Ebene (Anzahl)"
},
"name": {
"label": "Name"
},
"phone": {
"label": "Telefonnummer"
},
"position": {
"label": "Position"
},
"start-date": {
"label": "Startdatum"
},
"subtitle": {
"label": "Untertitel"
},
"summary": {
"label": "Zusammenfassung"
},
"title": {
"label": "Titel"
},
"url": {
"label": "Webseite"
}
},
"glossary": {
"page": "Seite"
},
"list": {
"empty-text": "Diese Liste ist leer.",
"actions": {
"edit": "Bearbeiten",
"duplicate": "Duplizieren",
"delete": "Löschen"
}
},
"tooltip": {
"delete-section": "Abschnitt löschen",
"rename-section": "Abschnitt umbenennen",
"toggle-visibility": "Sichtbarkeit umschalten",
"delete-item": "Sind Sie sicher, dass Sie dieses Element löschen möchten? Dies ist eine unumkehrbare Aktion."
}
},
"controller": {
"tooltip": {
"center-artboard": "Artboard zentrieren",
"copy-link": "Link zum Fortsetzen kopieren",
"export-pdf": "PDF exportieren",
"toggle-orientation": "Seitenausrichtung umschalten",
"toggle-page-break-line": "Pausenzeile umschalten",
"toggle-sidebars": "Seitenleisten umschalten",
"zoom-in": "Vergrößern",
"zoom-out": "Verkleinern"
}
},
"header": {
"menu": {
"delete": "Löschen",
"duplicate": "Duplizieren",
"rename": "Umbenennen",
"share-link": "Link teilen",
"tooltips": {
"delete": "Sind Sie sicher, dass Sie diesen Lebenslauf löschen möchten? Dies ist eine unumkehrbare Aktion.",
"share-link": "Du musst die Sichtbarkeit deines Lebenslaufs in die Öffentlichkeit ändern, um ihn für andere sichtbar zu machen."
}
}
},
"leftSidebar": {
"sections": {
"awards": {
"form": {
"awarder": {
"label": "Auszeichnung"
}
}
},
"basics": {
"actions": {
"photo-filters": "Fotofilter"
},
"heading": "Grundlagen",
"headline": {
"label": "Überschrift"
},
"name": {
"label": "Voller Name"
},
"photo-filters": {
"effects": {
"border": {
"label": "Grenze"
},
"grayscale": {
"label": "Graustufen"
},
"heading": "Effekte"
},
"shape": {
"heading": "Form"
},
"size": {
"heading": "Größe (in px)"
}
},
"photo-upload": {
"tooltip": {
"remove": "Foto entfernen",
"upload": "Foto hochladen"
}
}
},
"certifications": {
"form": {
"issuer": {
"label": "Aussteller"
}
}
},
"education": {
"form": {
"area-study": {
"label": "Studienbereich"
},
"courses": {
"label": "Kurse"
},
"degree": {
"label": "Grad"
},
"grade": {
"label": "Note"
},
"institution": {
"label": "Institution"
}
}
},
"location": {
"address": {
"label": "Adresse"
},
"city": {
"label": "Stadt"
},
"country": {
"label": "Land"
},
"heading": "Standort",
"postal-code": {
"label": "Postleitzahl"
},
"region": {
"label": "Region"
}
},
"profiles": {
"form": {
"network": {
"label": "Netzwerk"
},
"username": {
"label": "Benutzername"
}
},
"heading": "Profiles",
"heading_one": "Profil"
},
"publications": {
"form": {
"publisher": {
"label": "Herausgeber"
}
}
},
"references": {
"form": {
"relationship": {
"label": "Beziehung"
}
}
},
"section": {
"heading": "Abschnitt"
},
"volunteer": {
"form": {
"organization": {
"label": "Organisation"
}
}
}
}
},
"rightSidebar": {
"sections": {
"css": {
"heading": "Benutzerdefiniertes CSS"
},
"export": {
"heading": "Exportieren",
"json": {
"primary": "JSON",
"secondary": "Laden Sie eine JSON-Version Ihres Lebenslaufs herunter, die Sie wieder in Reactive Resume importieren können."
},
"pdf": {
"loading": {
"primary": "PDF wird erstellt",
"secondary": "Bitte warten Sie, wenn Ihr PDF generiert wird, dies kann bis zu 15 Sekunden dauern."
},
"normal": {
"primary": "PDF",
"secondary": "Laden Sie sich ein PDF Ihres Lebenslaufs herunter, das Sie ausdrucken und an Ihren Traumjob senden können. Diese Datei kann nicht zur weiteren Bearbeitung importiert werden."
}
}
},
"layout": {
"heading": "Layout",
"tooltip": {
"reset-layout": "Layout zurücksetzen"
}
},
"links": {
"bugs-features": {
"body": "Hält Sie etwas davon ab, einen Lebenslauf zu erstellen? Oder haben Sie eine tolle Idee, die Sie hinzufügen möchten? Erhöhen Sie einen Eintrag auf GitHub, um loszulegen.",
"button": "GitHub Themen",
"heading": "Fehler? Feature-Anfragen?"
},
"donate": {
"body": "Wenn Ihnen Reactive Resume gefallen hat, denken Sie bitte darüber nach, so viel wie möglich zu spenden, damit die App für immer kostenlos und werbefrei bleibt.",
"button": "Kaufe mir einen Kaffee",
"heading": "Spenden an Reactive Resume"
},
"github": "Quellcode",
"heading": "Links"
},
"settings": {
"global": {
"date": {
"primary": "Datum",
"secondary": "Datumsformat für die gesamte App"
},
"heading": "Globale",
"language": {
"primary": "Sprache",
"secondary": "Sprache anzeigen, die in der gesamten App verwendet wird"
},
"theme": {
"primary": "Thema"
}
},
"heading": "Einstellungen",
"page": {
"break-line": {
"primary": "Linie anhalten",
"secondary": "Zeile auf allen Seiten anzeigen, um die Höhe einer A4-Seite zu markieren"
},
"heading": "Seite",
"orientation": {
"primary": "Ausrichtung",
"secondary": "Ob Seiten horizontal oder vertikal angezeigt werden sollen"
}
},
"resume": {
"heading": "Fortsetzen",
"reset": {
"primary": "Alles zurücksetzen",
"secondary": "Zu viele Fehler gemacht? Klicken Sie hier, um alle Änderungen zurückzusetzen und bei Null zu beginnen. Sei vorsichtig, diese Aktion kann nicht rückgängig gemacht werden."
},
"sample": {
"primary": "Beispieldaten laden",
"secondary": "Nicht sicher, wo man anfangen soll? Klicken Sie hier, um ein paar Beispieldaten zu laden, um zu sehen, wie ein vollständiger Lebenslauf aussieht."
}
}
},
"sharing": {
"heading": "Teilen",
"short-url": {
"label": "Kurze URL bevorzugen"
},
"visibility": {
"subtitle": "Erlaube jemandem mit einem Link deinen Lebenslauf anzusehen",
"title": "Öffentlich"
}
},
"templates": {
"heading": "Vorlagen"
},
"theme": {
"form": {
"background": {
"label": "Hintergrund"
},
"primary": {
"label": "Primär"
},
"text": {
"label": "Text"
}
},
"heading": "Thema"
},
"typography": {
"form": {
"font-family": {
"label": "Schriftfamilie"
},
"font-size": {
"label": "Schriftgröße"
}
},
"heading": "Typographie",
"widgets": {
"body": {
"label": "Körper"
},
"headings": {
"label": "Überschriften"
}
}
}
}
}
}

View File

@ -0,0 +1,29 @@
{
"avatar": {
"menu": {
"greeting": "Hallo",
"logout": "Abmelden"
}
},
"footer": {
"credit": "Ein Herzensprojekt von <1>Amruth Pillai</1>",
"language": {
"missing": "Ihre Sprache fehlt?"
},
"license": "Von der Gemeinschaft, für die Gemeinschaft."
},
"markdown": {
"help-text": "Dieser Abschnitt unterstützt <1>Markdown</1> Formatierung."
},
"subtitle": "Ein freier und Open-Source-Lebenslauf-Builder.",
"title": "Reaktives Fortsetzen",
"toast": {
"error": {
"upload-file-size": "Bitte laden Sie nur Dateien unter 2 Megabytes hoch.",
"upload-photo-size": "Bitte laden Sie nur Fotos unter 2 Megabytes hoch, vorzugsweise quadratisch."
},
"success": {
"resume-link-copied": "Ein Link zu deinem Lebenslauf wurde in deine Zwischenablage kopiert."
}
}
}

View File

@ -0,0 +1,25 @@
{
"create-resume": {
"subtitle": "Bei Null anfangen",
"title": "Neuen Lebenslauf erstellen"
},
"import-external": {
"subtitle": "LinkedIn, JSON Resume, reaktives Fortsetzen",
"title": "Aus externen Quellen importieren"
},
"resume": {
"menu": {
"delete": "Löschen",
"duplicate": "Duplizieren",
"open": "Öffnen",
"rename": "Umbenennen",
"share-link": "Link teilen",
"tooltips": {
"delete": "Sind Sie sicher, dass Sie diesen Lebenslauf löschen möchten? Dies ist eine unumkehrbare Aktion.",
"share-link": "Du musst die Sichtbarkeit deines Lebenslaufs in die Öffentlichkeit ändern, um ihn für andere sichtbar zu machen."
}
},
"timestamp": "Zuletzt vor {{timestamp}} aktualisiert"
},
"title": "Dashboard"
}

View File

@ -0,0 +1,41 @@
{
"actions": {
"app": "Gehe zu App",
"login": "Anmelden",
"logout": "Abmelden",
"register": "Registrieren"
},
"features": {
"heading": "Eigenschaften",
"list": {
"ads": "Keine Werbung",
"export": "Exportieren Sie Ihren Lebenslauf in JSON oder PDF Format",
"free": "Frei, für immer",
"import": "Importiere Daten von LinkedIn, JSON Fortsetzen",
"languages": "In mehreren Sprachen zugänglich",
"more": "Und viel mehr aufregende Features, <1>lesen Sie alles hier</1>",
"tracking": "Keine Benutzerverfolgung"
}
},
"links": {
"heading": "Links",
"links": {
"donate": "Spenden",
"github": "Quellcode",
"privacy": "Datenschutzerklärung",
"service": "Nutzungsbedingungen"
}
},
"screenshots": {
"heading": "Screenshots"
},
"testimonials": {
"heading": "Referenzen",
"body": "Gut oder schlecht, ich würde gerne Ihre Meinung über Reactive Resume und wie die Erfahrung war für Sie.<br/>Hier sind einige der Nachrichten, die von Benutzern auf der ganzen Welt gesendet werden.",
"contact": "Du kannst mich über <1>meine E-Mail</1> oder über das Kontaktformular auf <3>meiner Website</3>erreichen."
},
"summary": {
"body": "Reactive Resume ist ein freier und Open-Source-Lebenslauf-Builder, der gebaut wurde, um die weltlichen Aufgaben zu machen, zu erstellen, Aktualisieren und teilen Sie Ihren Lebenslauf so einfach wie 1, 2, 3. Mit dieser App kannst du mehrere Bewerbungen erstellen, sie mit Recruitern oder Freunden über einen einzigartigen Link teilen und sie als PDF ausdrucken. alle kostenlos, keine Werbung, keine Verfolgung, ohne die Integrität und Privatsphäre Ihrer Daten zu verlieren.",
"heading": "Zusammenfassung"
}
}

View File

@ -0,0 +1,135 @@
{
"auth": {
"forgot-password": {
"actions": {
"send-email": "Passwort zurücksetzen E-Mail senden"
},
"body": "Geben Sie einfach die E-Mail-Adresse ein, die mit dem Konto verknüpft ist, das Sie wiederherstellen möchten.",
"form": {
"email": {
"label": "E-Mail Adresse"
}
},
"heading": "Passwort vergessen?",
"help-text": "Wenn das Konto existiert, erhalten Sie eine E-Mail mit einem Link zum Zurücksetzen Ihres Passworts."
},
"login": {
"actions": {
"login": "Anmelden",
"google": "Mit Google anmelden"
},
"body": "Bitte geben Sie Ihren Benutzernamen und Ihr Passwort ein, um sich anzumelden und zuzugreifen, Ihre Bewerbungen zu verwalten und weiterzugeben.",
"form": {
"password": {
"label": "Passwort"
},
"username": {
"help-text": "Sie können auch Ihre E-Mail-Adresse eingeben",
"label": "Benutzername"
}
},
"heading": "Bei Ihrem Konto anmelden",
"recover-text": "Falls Sie Ihr Passwort vergessen haben, können Sie <1>Ihr Konto wiederherstellen</1> hier einrichten.",
"register-text": "Wenn Sie keinen haben, können Sie hier <1>ein Konto erstellen</1> anlegen."
},
"register": {
"actions": {
"register": "Registrieren",
"google": "Mit Google registrieren"
},
"body": "Bitte geben Sie Ihre persönlichen Daten ein, um ein Konto zu erstellen.",
"form": {
"confirm-password": {
"label": "Passwort bestätigen"
},
"email": {
"label": "E-Mail Adresse"
},
"name": {
"label": "Voller Name"
},
"password": {
"label": "Passwort"
},
"username": {
"label": "Benutzername"
}
},
"heading": "Ein Konto erstellen",
"loginText": "Wenn Sie bereits ein Konto haben, können Sie sich hier <1>anmelden</1>."
},
"reset-password": {
"actions": {
"set-password": "Neues Passwort setzen"
},
"body": "Geben Sie ein neues Passwort für Ihr Konto ein.",
"form": {
"confirm-password": {
"label": "Passwort bestätigen"
},
"password": {
"label": "Passwort"
}
},
"heading": "Passwort zurücksetzen"
}
},
"dashboard": {
"create-resume": {
"actions": {
"create-resume": "Lebenslauf erstellen"
},
"body": "Erstelle deinen Lebenslauf, indem du ihm einen Namen gibst. Er könnte sich auf die Rolle beziehen, die Sie beantragen, oder nur auf Ihren Lieblings-Snack.",
"form": {
"name": {
"label": "Name"
},
"public": {
"label": "Ist öffentlich zugänglich?"
},
"slug": {
"label": "Slug"
}
},
"heading": "Neuen Lebenslauf erstellen"
},
"import-external": {
"heading": "Aus externen Quellen importieren",
"json-resume": {
"actions": {
"upload-json": "JSON hochladen"
},
"body": "Wenn du einen <1>validierten JSON Resume</1> bereit hast, kannst du damit deine Entwicklung auf reaktiven Resume beschleunigen. Klicken Sie auf den Button unten und laden Sie eine gültige JSON-Datei hoch, um zu beginnen.",
"heading": "Import vom JSON-Lebenslauf"
},
"linkedin": {
"actions": {
"upload-archive": "ZIP-Archiv hochladen"
},
"body": "Sie können Zeit sparen, indem Sie Ihre Daten aus LinkedIn exportieren und sie zum automatischen Ausfüllen von Feldern auf Reactive Resume verwenden. Gehen Sie zum Abschnitt <1>Datenschutz </1> auf LinkedIn und fordern Sie ein Archiv Ihrer Daten an. Sobald diese verfügbar sind, laden Sie die ZIP-Datei unten hoch.",
"heading": "Aus LinkedIn importieren"
},
"reactive-resume": {
"actions": {
"upload-json": "JSON hochladen"
},
"body": "Wenn Sie ein JSON haben, das mit der aktuellen Version von Reactive Resume exportiert wurde, Sie können es hier wieder importieren um wieder eine editierbare Version zu erhalten. Vorherige Versionen von Reactive Resume werden derzeit leider nicht unterstützt.",
"heading": "Import vom reaktiven Lebenslauf"
}
},
"rename-resume": {
"actions": {
"rename-resume": "Lebenslauf umbenennen"
},
"form": {
"name": {
"label": "Name"
},
"slug": {
"label": "Slug"
}
},
"heading": "Lebenslauf umbenennen"
}
}
}

View File

@ -61,12 +61,18 @@
"page": "Page"
},
"list": {
"empty-text": "This list is empty."
"empty-text": "This list is empty.",
"actions": {
"edit": "Edit",
"duplicate": "Duplicate",
"delete": "Delete"
}
},
"tooltip": {
"delete-section": "Delete Section",
"rename-section": "Rename Section",
"toggle-visibility": "Toggle Visibility"
"toggle-visibility": "Toggle Visibility",
"delete-item": "Are you sure you want to delete this item? This is an irreversible action."
}
},
"controller": {

View File

@ -5,9 +5,11 @@
"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>",
"language": {
"missing": "Missing your language?"
},
"license": "By the community, for the community."
},
"markdown": {

View File

@ -20,8 +20,10 @@
"links": {
"heading": "Links",
"links": {
"donate": "Donate to Reactive Resume",
"github": "Source Code on GitHub"
"donate": "Donate",
"github": "Source Code",
"privacy": "Privacy Policy",
"service": "Terms of Service"
}
},
"screenshots": {
@ -30,7 +32,7 @@
"testimonials": {
"heading": "Testimonials",
"body": "Good or bad, I would love to hear your opinion on Reactive Resume and how the experience has been for you.<br/>Here are some of the messages sent in by users across the world.",
"contact": "You can reach out to me through <1>my email</1> or through the contact form on <2>my website</2>."
"contact": "You can reach out to me through <1>my email</1> or through the contact form on <3>my website</3>."
},
"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.",

View File

@ -16,7 +16,7 @@
"login": {
"actions": {
"login": "Login",
"login-google": "Login with Google"
"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": {
@ -34,7 +34,8 @@
},
"register": {
"actions": {
"register": "Register"
"register": "Register",
"google": "Register with Google"
},
"body": "Please enter your personal information to create an account.",
"form": {
@ -105,14 +106,14 @@
"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.",
"body": "You can save time by exporting your data from LinkedIn and using it to auto-fill fields on Reactive Resume. Head over to the <1>Data Privacy</1> section on LinkedIn and request an archive of your data. Once it is available, upload the ZIP file 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.",
"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. Previous versions of Reactive Resume are unfortunately not supported at the moment.",
"heading": "Import From Reactive Resume"
}
},

View File

@ -0,0 +1,357 @@
{
"common": {
"actions": {
"add": "नया जोड़ें {{token}}",
"delete": "हटाएं {{token}}",
"edit": "संपादित करें {{token}}"
},
"columns": {
"heading": "कॉलम",
"tooltip": "स्तंभों की संख्या बदलें"
},
"form": {
"date": {
"label": "तारीख"
},
"description": {
"label": "विवरण"
},
"email": {
"label": "ईमेल पता"
},
"end-date": {
"help-text": "इस फ़ील्ड को खाली छोड़ दें, यदि अभी भी मौजूद है",
"label": "अंतिम तिथि"
},
"keywords": {
"label": "कीवर्ड"
},
"level": {
"label": "स्तर"
},
"levelNum": {
"label": "स्तर (संख्या)"
},
"name": {
"label": "नाम"
},
"phone": {
"label": "फ़ोन नंबर"
},
"position": {
"label": "स्थान"
},
"start-date": {
"label": "आरंभ करने की तिथि"
},
"subtitle": {
"label": "उपशीर्षक"
},
"summary": {
"label": "सारांश"
},
"title": {
"label": "शीर्षक"
},
"url": {
"label": "वेबसाइट"
}
},
"glossary": {
"page": "पृष्ठ"
},
"list": {
"empty-text": "यह सूची खाली है।",
"actions": {
"edit": "संपादित करें",
"duplicate": "डुप्लिकेट",
"delete": "हटाएं"
}
},
"tooltip": {
"delete-section": "अनुभाग हटाएं",
"rename-section": "अनुभाग का नाम बदलें",
"toggle-visibility": "दृश्यता टॉगल करें",
"delete-item": "क्या आप सुनिश्चित रूप से इस आइटम को मिटाना चाहते हैं? यह एक अपरिवर्तनीय क्रिया है।"
}
},
"controller": {
"tooltip": {
"center-artboard": "केंद्र आर्टबोर्ड",
"copy-link": "फिर से शुरू करने के लिए लिंक कॉपी करें",
"export-pdf": "पीडीएफ निर्यात करें",
"toggle-orientation": "टॉगल पेज ओरिएंटेशन",
"toggle-page-break-line": "टॉगल पेज ब्रेक लाइन",
"toggle-sidebars": "साइडबार टॉगल करें",
"zoom-in": "ज़ूम इन",
"zoom-out": "ज़ूम आउट"
}
},
"header": {
"menu": {
"delete": "हटाएं",
"duplicate": "डुप्लिकेट",
"rename": "नाम बदलें",
"share-link": "लिंक शेयर करें",
"tooltips": {
"delete": "क्या आप वाकई इस रेज़्यूमे को हटाना चाहते हैं? यह एक अपरिवर्तनीय क्रिया है।",
"share-link": "आपको अपने रेज़्यूमे की दृश्यता को सार्वजनिक करने की आवश्यकता है ताकि इसे दूसरों को दिखाई दे।"
}
}
},
"leftSidebar": {
"sections": {
"awards": {
"form": {
"awarder": {
"label": "पुरस्कार देने वाला"
}
}
},
"basics": {
"actions": {
"photo-filters": "फोटो फिल्टर"
},
"heading": "मूल बातें",
"headline": {
"label": "शीर्षक"
},
"name": {
"label": "पूरा नाम"
},
"photo-filters": {
"effects": {
"border": {
"label": "बॉर्डर"
},
"grayscale": {
"label": "स्केल"
},
"heading": "प्रभाव"
},
"shape": {
"heading": "आकार"
},
"size": {
"heading": "आकार (पीएक्स में)"
}
},
"photo-upload": {
"tooltip": {
"remove": "फोटो हटाएं",
"upload": "फोटो अपलोड करें"
}
}
},
"certifications": {
"form": {
"issuer": {
"label": "जारीकर्ता"
}
}
},
"education": {
"form": {
"area-study": {
"label": "अध्ययन का क्षेत्र"
},
"courses": {
"label": "पाठ्यक्रम"
},
"degree": {
"label": "डिग्री"
},
"grade": {
"label": "ग्रेड"
},
"institution": {
"label": "संस्थान"
}
}
},
"location": {
"address": {
"label": "पता"
},
"city": {
"label": "शहर"
},
"country": {
"label": "देश"
},
"heading": "स्थान",
"postal-code": {
"label": "डाक कोड"
},
"region": {
"label": "क्षेत्र"
}
},
"profiles": {
"form": {
"network": {
"label": "नेटवर्क"
},
"username": {
"label": "उपयोगकर्ता नाम"
}
},
"heading": "प्रोफाइल",
"heading_one": "प्रोफ़ाइल"
},
"publications": {
"form": {
"publisher": {
"label": "प्रकाशक"
}
}
},
"references": {
"form": {
"relationship": {
"label": "संबंध"
}
}
},
"section": {
"heading": "अनुभाग"
},
"volunteer": {
"form": {
"organization": {
"label": "संगठन"
}
}
}
}
},
"rightSidebar": {
"sections": {
"css": {
"heading": "कस्टम सीएसएस"
},
"export": {
"heading": "निर्यात",
"json": {
"primary": "JSON",
"secondary": "अपने रेज़्यूमे का JSON संस्करण डाउनलोड करें जिसे रिएक्टिव रेज़्यूमे में वापस आयात किया जा सकता है।"
},
"pdf": {
"loading": {
"primary": "पीडीएफ उत्पन्न करना",
"secondary": "कृपया प्रतीक्षा करें क्योंकि आपका PDF जनरेट हो गया है, इसमें 15 सेकंड तक लग सकते हैं।"
},
"normal": {
"primary": "पीडीएफ",
"secondary": "अपने रिज्यूमे का एक पीडीएफ डाउनलोड करें जिसे आप प्रिंट कर सकते हैं और अपने सपनों की नौकरी के लिए भेज सकते हैं। आगे के संपादन के लिए इस फ़ाइल को वापस आयात नहीं किया जा सकता है।"
}
}
},
"layout": {
"heading": "ख़ाका",
"tooltip": {
"reset-layout": "लेआउट रीसेट करें"
}
},
"links": {
"bugs-features": {
"body": "कुछ आपको रिज्यूमे बनाने से रोक रहा है? या क्या आपके पास जोड़ने के लिए एक अद्भुत विचार है? आरंभ करने के लिए गिटहब पर एक मुद्दा उठाएं।",
"button": "गिटहब मुद्दे",
"heading": "कीड़े? सुविधा का अनुरोध?"
},
"donate": {
"body": "यदि आप रिएक्टिव रिज्यूमे का उपयोग करना पसंद करते हैं, तो कृपया ऐप को चालू रखने और विज्ञापनों के बिना और हमेशा के लिए मुक्त रखने के लिए जितना हो सके दान करने पर विचार करें।",
"button": "मेरे लिए एक कॉफी खरीदें",
"heading": "प्रतिक्रियाशील फिर से शुरू करने के लिए दान करें"
},
"github": "सोर्स कोड",
"heading": "लिंक"
},
"settings": {
"global": {
"date": {
"primary": "तारीख",
"secondary": "पूरे ऐप में उपयोग करने के लिए दिनांक प्रारूप"
},
"heading": "वैश्विक",
"language": {
"primary": "भाषा",
"secondary": "पूरे ऐप में उपयोग करने के लिए भाषा प्रदर्शित करें"
},
"theme": {
"primary": "विषय"
}
},
"heading": "समायोजन",
"page": {
"break-line": {
"primary": "अंतराल वाली लकीर",
"secondary": "A4 पृष्ठ की ऊंचाई को चिह्नित करने के लिए सभी पृष्ठों पर एक पंक्ति दिखाएं"
},
"heading": "पृष्ठ",
"orientation": {
"primary": "अभिविन्यास",
"secondary": "पृष्ठों को क्षैतिज या लंबवत रूप से प्रदर्शित करना है या नहीं"
}
},
"resume": {
"heading": "फिर शुरू करना",
"reset": {
"primary": "सब कुछ रीसेट करें",
"secondary": "बहुत सारी गलतियाँ कीं? सभी परिवर्तनों को रीसेट करने और नए सिरे से शुरू करने के लिए यहां क्लिक करें। सावधान रहें, इस क्रिया को उलटा नहीं किया जा सकता है।"
},
"sample": {
"primary": "नमूना डेटा लोड करें",
"secondary": "पता नहीं कहां से शुरू करना है? कुछ नमूना डेटा लोड करने के लिए यहां क्लिक करें यह देखने के लिए कि एक पूरा रिज्यूमे कैसा दिखता है।"
}
}
},
"sharing": {
"heading": "शेयरिंग",
"short-url": {
"label": "लघु URL को प्राथमिकता दें"
},
"visibility": {
"subtitle": "लिंक वाले किसी भी व्यक्ति को अपना बायोडाटा देखने की अनुमति दें",
"title": "जनता"
}
},
"templates": {
"heading": "टेम्पलेट्स"
},
"theme": {
"form": {
"background": {
"label": "पृष्ठभूमि"
},
"primary": {
"label": "मुख्य"
},
"text": {
"label": "मूलपाठ"
}
},
"heading": "विषय"
},
"typography": {
"form": {
"font-family": {
"label": "फ़ॉन्ट परिवार"
},
"font-size": {
"label": "फ़ॉन्ट आकार"
}
},
"heading": "टाइपोग्राफी",
"widgets": {
"body": {
"label": "शरीर"
},
"headings": {
"label": "शीर्षकों"
}
}
}
}
}
}

Some files were not shown because too many files have changed in this diff Show More