├── .erb ├── mocks │ └── fileMock.js ├── img │ └── erb-logo.png ├── configs │ ├── .eslintrc │ ├── webpack.config.eslint.ts │ ├── webpack.paths.ts │ ├── webpack.config.base.ts │ ├── webpack.config.main.dev.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.main.prod.ts │ └── webpack.config.renderer.prod.ts └── scripts │ ├── .eslintrc │ ├── clean.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── link-modules.ts │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── check-build-exists.ts │ ├── notarize.js │ └── check-native-dep.js ├── screenshot.png ├── assets ├── icon.icns ├── icon.ico ├── icon.png ├── icons │ ├── 16x16.png │ ├── 24x24.png │ ├── 32x32.png │ ├── 48x48.png │ ├── 64x64.png │ ├── 96x96.png │ ├── 128x128.png │ ├── 256x256.png │ ├── 512x512.png │ └── 1024x1024.png ├── entitlements.mac.plist └── assets.d.ts ├── .vscode ├── extensions.json ├── settings.json └── launch.json ├── release └── app │ ├── yarn.lock │ ├── package-lock.json │ └── package.json ├── postcss.config.js ├── .github ├── FUNDING.yml ├── config.yml ├── ISSUE_TEMPLATE │ ├── 3-Feature_request.md │ ├── 2-Question.md │ └── 1-Bug_report.md ├── stale.yml └── workflows │ ├── test.yml │ ├── publish.yml │ └── codeql-analysis.yml ├── src ├── renderer │ ├── utils │ │ ├── functions │ │ │ ├── typography.jsx │ │ │ └── date.jsx │ │ ├── device │ │ │ ├── getIpAddress.jsx │ │ │ ├── getDeviceInfo.jsx │ │ │ ├── getLocationByIp.jsx │ │ │ ├── getUserLocation.jsx │ │ │ └── getOs.jsx │ │ ├── data │ │ │ └── users │ │ │ │ ├── activity_logs.jsx │ │ │ │ └── activity_login.jsx │ │ ├── security │ │ │ └── crypto.jsx │ │ └── database │ │ │ └── required_fields.jsx │ ├── preload.d.ts │ ├── screens │ │ ├── dashboard │ │ │ ├── index.jsx │ │ │ └── app │ │ │ │ ├── about │ │ │ │ ├── list.jsx │ │ │ │ ├── general │ │ │ │ │ ├── home │ │ │ │ │ │ └── view │ │ │ │ │ │ │ └── index.jsx │ │ │ │ │ └── donate │ │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ └── path.jsx │ │ │ │ ├── routes.jsx │ │ │ │ ├── config.jsx │ │ │ │ ├── features │ │ │ │ ├── list.jsx │ │ │ │ ├── path.jsx │ │ │ │ └── converter │ │ │ │ │ ├── csv-to-ods │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── odp-to-ppt │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── pdf-to-ppt │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── ppt-to-odp │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── txt-to-odt │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── csv-to-excel │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── excel-to-ods │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── html-to-docx │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── ods-to-excel │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ ├── odt-to-word │ │ │ │ │ └── view │ │ │ │ │ │ └── index.jsx │ │ │ │ │ └── pdf-to-excel │ │ │ │ │ └── view │ │ │ │ │ └── index.jsx │ │ │ │ └── index.jsx │ │ └── error │ │ │ └── 404 │ │ │ └── index.jsx │ ├── index.ejs │ ├── components │ │ ├── icons │ │ │ ├── close.jsx │ │ │ ├── create.jsx │ │ │ ├── search.jsx │ │ │ ├── refresh.jsx │ │ │ ├── categoryIcon.jsx │ │ │ ├── archive.jsx │ │ │ ├── favorite.jsx │ │ │ ├── update.jsx │ │ │ └── delete.jsx │ │ ├── navbar │ │ │ ├── privacy.jsx │ │ │ └── dash.jsx │ │ ├── skeleton │ │ │ └── text.jsx │ │ ├── error │ │ │ ├── index.jsx │ │ │ ├── comingsoon.jsx │ │ │ └── 404.jsx │ │ ├── loading │ │ │ └── main.jsx │ │ ├── tabs │ │ │ └── main.jsx │ │ ├── background │ │ │ ├── waveLayers.jsx │ │ │ ├── meshGradient.jsx │ │ │ ├── dottedGrid.jsx │ │ │ └── gradientBlobs.jsx │ │ └── web │ │ │ ├── footer.jsx │ │ │ └── hero.jsx │ ├── index.tsx │ ├── hooks │ │ ├── useRouteProvider.jsx │ │ ├── useSidebar.jsx │ │ └── useDarkMode.jsx │ ├── assets │ │ ├── error │ │ │ ├── 404.svg │ │ │ └── 404-dark.svg │ │ └── react.svg │ ├── App.jsx │ └── App.css ├── __tests__ │ └── App.test.tsx └── main │ ├── util.ts │ ├── functions │ ├── postProcessPDFExpired.tsx │ ├── postProcessSecure.tsx │ ├── getSofficePath.tsx │ ├── filterAction.tsx │ └── postProcessPDF.tsx │ └── preload.ts ├── tailwind.config.js ├── .editorconfig ├── .gitattributes ├── .gitignore ├── tsconfig.json ├── .eslintignore ├── LICENSE ├── .eslintrc.js ├── README.md └── CODE_OF_CONDUCT.md /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/screenshot.png -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icon.png -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /assets/icons/16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/16x16.png -------------------------------------------------------------------------------- /assets/icons/24x24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/24x24.png -------------------------------------------------------------------------------- /assets/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/32x32.png -------------------------------------------------------------------------------- /assets/icons/48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/48x48.png -------------------------------------------------------------------------------- /assets/icons/64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/64x64.png -------------------------------------------------------------------------------- /assets/icons/96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/96x96.png -------------------------------------------------------------------------------- /assets/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/128x128.png -------------------------------------------------------------------------------- /assets/icons/256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/256x256.png -------------------------------------------------------------------------------- /assets/icons/512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/512x512.png -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /assets/icons/1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/flamecorecloud/signature-one/HEAD/assets/icons/1024x1024.png -------------------------------------------------------------------------------- /release/app/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | '@tailwindcss/postcss': {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [electron-react-boilerplate] 4 | patreon: andikachamberlin 5 | -------------------------------------------------------------------------------- /.github/config.yml: -------------------------------------------------------------------------------- 1 | requiredHeaders: 2 | - Prerequisites 3 | - Expected Behavior 4 | - Current Behavior 5 | - Possible Solution 6 | - Your Environment 7 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/utils/functions/typography.jsx: -------------------------------------------------------------------------------- 1 | export const capitalizeWords = async (str) => { 2 | return str 3 | .split(/[_\s]+/) 4 | .map(word => word.charAt(0).toUpperCase() + word.slice(1)) 5 | .join(" "); 6 | } 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: [ 4 | './src/renderer/**/*.{js,jsx,ts,tsx,html}', 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /src/renderer/preload.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronHandler } from '../main/preload'; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-unused-vars 5 | interface Window { 6 | electron: ElectronHandler; 7 | } 8 | } 9 | 10 | export {}; 11 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import AppScreen from "./app" 3 | 4 | export default function Dashboard({ user = null, role = null }) { 5 | return ( 6 | 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import App from '../renderer/App'; 4 | 5 | describe('App', () => { 6 | it('should render', () => { 7 | expect(render()).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-jit 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /release/app/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signature-one", 3 | "version": "0.1.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "signature-one", 9 | "version": "0.1.1", 10 | "hasInstallScript": true, 11 | "license": "FMC" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Signature One 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/renderer/components/icons/close.jsx: -------------------------------------------------------------------------------- 1 | export default function CloseIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import { rimrafSync } from 'rimraf'; 2 | import fs from 'fs'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | const foldersToRemove = [ 6 | webpackPaths.distPath, 7 | webpackPaths.buildPath, 8 | webpackPaths.dllPath, 9 | ]; 10 | 11 | foldersToRemove.forEach((folder) => { 12 | if (fs.existsSync(folder)) rimrafSync(folder); 13 | }); 14 | -------------------------------------------------------------------------------- /src/renderer/components/icons/create.jsx: -------------------------------------------------------------------------------- 1 | export default function CreateIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/components/icons/search.jsx: -------------------------------------------------------------------------------- 1 | export default function SearchIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/utils/device/getIpAddress.jsx: -------------------------------------------------------------------------------- 1 | // utils/getIp.js 2 | export const getIpAddress = async () => { 3 | try { 4 | const res = await fetch("https://api.ipify.org?format=json"); 5 | const data = await res.json(); 6 | return data.ip; // misalnya "36.84.xxx.xxx" 7 | } catch (err) { 8 | console.error("Error fetching IP:", err); 9 | return null; 10 | } 11 | }; 12 | -------------------------------------------------------------------------------- /src/renderer/utils/device/getDeviceInfo.jsx: -------------------------------------------------------------------------------- 1 | import { UAParser } from "ua-parser-js"; 2 | 3 | export const getDeviceInfo = async () => { 4 | const parser = new UAParser(); 5 | const result = parser.getResult(); 6 | 7 | return { 8 | browser: result.browser.name + " " + result.browser.version, 9 | os: result.os.name + " " + result.os.version, 10 | device: result.device.model || "Desktop", 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config`, 12 | ), 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/3-Feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: You want something added to the boilerplate. 🎉 4 | labels: 'enhancement' 5 | --- 6 | 7 | 16 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | 5 | export function resolveHtmlPath(htmlFileName: string) { 6 | if (process.env.NODE_ENV === 'development') { 7 | const port = process.env.PORT || 1212; 8 | const url = new URL(`http://localhost:${port}`); 9 | url.pathname = htmlFileName; 10 | return url.href; 11 | } 12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/components/navbar/privacy.jsx: -------------------------------------------------------------------------------- 1 | export default function NavPrivacy() { 2 | return ( 3 |
4 | Privasi 5 | Persyaratan 6 | Tentang 7 |
8 | ) 9 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/2-Question.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Question 3 | about: Ask a question.❓ 4 | labels: 'question' 5 | --- 6 | 7 | ## Summary 8 | 9 | 10 | 11 | 20 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (_err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start`, 11 | ), 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /src/renderer/components/icons/refresh.jsx: -------------------------------------------------------------------------------- 1 | export default function RefreshIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath, appNodeModulesPath, erbNodeModulesPath } = 5 | webpackPaths; 6 | 7 | if (fs.existsSync(appNodeModulesPath)) { 8 | if (!fs.existsSync(srcNodeModulesPath)) { 9 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 10 | } 11 | if (!fs.existsSync(erbNodeModulesPath)) { 12 | fs.symlinkSync(appNodeModulesPath, erbNodeModulesPath, 'junction'); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import App from './App'; 3 | import './App.css'; 4 | 5 | const container = document.getElementById('root') as HTMLElement; 6 | const root = createRoot(container); 7 | root.render(); 8 | 9 | // calling IPC exposed from preload script 10 | window.electron?.ipcRenderer.once('ipc-example', (arg) => { 11 | // eslint-disable-next-line no-console 12 | console.log(arg); 13 | }); 14 | window.electron?.ipcRenderer.sendMessage('ipc-example', ['ping']); 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | src/resources 18 | .dev 19 | 20 | # OSX 21 | .DS_Store 22 | 23 | release/app/dist 24 | release/build 25 | .erb/dll 26 | 27 | .idea 28 | npm-debug.log.* 29 | *.css.d.ts 30 | *.sass.d.ts 31 | *.scss.d.ts 32 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "incremental": true, 4 | "target": "es2022", 5 | "module": "node16", 6 | "lib": ["dom", "es2022"], 7 | "jsx": "react-jsx", 8 | "strict": true, 9 | "sourceMap": true, 10 | "moduleResolution": "node16", 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "resolveJsonModule": true, 14 | "allowJs": true, 15 | "outDir": ".erb/dll" 16 | }, 17 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"] 18 | } 19 | -------------------------------------------------------------------------------- /src/renderer/components/icons/categoryIcon.jsx: -------------------------------------------------------------------------------- 1 | export default function CategoryIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } -------------------------------------------------------------------------------- /src/renderer/components/skeleton/text.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function SkeletonText() { 4 | return ( 5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { rimrafSync } from 'rimraf'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | export default function deleteSourceMaps() { 7 | if (fs.existsSync(webpackPaths.distMainPath)) 8 | rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), { 9 | glob: true, 10 | }); 11 | if (fs.existsSync(webpackPaths.distRendererPath)) 12 | rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), { 13 | glob: true, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/components/icons/archive.jsx: -------------------------------------------------------------------------------- 1 | export default function ArchiveIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } -------------------------------------------------------------------------------- /src/renderer/components/icons/favorite.jsx: -------------------------------------------------------------------------------- 1 | export default function FavoriteIcon() { 2 | return ( 3 | 4 | 5 | 6 | 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /src/renderer/components/icons/update.jsx: -------------------------------------------------------------------------------- 1 | export default function UpdateIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/utils/device/getLocationByIp.jsx: -------------------------------------------------------------------------------- 1 | export const getLocationByIP = async () => { 2 | try { 3 | const res = await fetch("https://ipapi.co/json/"); 4 | const data = await res.json(); 5 | 6 | return { 7 | ip: data.ip, 8 | city: data.city, 9 | region: data.region, 10 | country: data.country_name, 11 | lat: data.latitude, 12 | lng: data.longitude, 13 | }; 14 | } catch (err) { 15 | console.error("Error fetching location:", err); 16 | return null; 17 | } 18 | }; 19 | -------------------------------------------------------------------------------- /src/renderer/utils/data/users/activity_logs.jsx: -------------------------------------------------------------------------------- 1 | import { getDeviceInfo } from "../../device/getDeviceInfo"; 2 | import { getIpAddress } from "../../device/getIpAddress"; 3 | 4 | export const dataUsersActivityLogs = async (name, description = '-', target, status) => { 5 | 6 | const data = { 7 | "name" : name, 8 | "description" : description, 9 | "target" : target, 10 | "ip_address" : await getIpAddress() || "Unknown", 11 | "device": (await getDeviceInfo()).device || "Unknown", 12 | "logs_status": status, 13 | } 14 | 15 | return data; 16 | }; 17 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /src/renderer/utils/device/getUserLocation.jsx: -------------------------------------------------------------------------------- 1 | export const getUserLocation = () => { 2 | return new Promise((resolve, reject) => { 3 | if (!navigator.geolocation) { 4 | reject(new Error("Geolocation is not supported by your browser")); 5 | } 6 | 7 | navigator.geolocation.getCurrentPosition( 8 | (position) => { 9 | resolve( 10 | `${position.coords.latitude},${position.coords.longitude}` 11 | ); 12 | }, 13 | (error) => { 14 | reject(error); 15 | } 16 | ); 17 | }); 18 | }; 19 | -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signature-one", 3 | "version": "1.0.0", 4 | "description": "Signature application", 5 | "license": "FMC", 6 | "author": { 7 | "name": "Andika Chamberlin", 8 | "email": "htmlandika@gmail.com", 9 | "url": "https://andikachamberlin.com" 10 | }, 11 | "main": "./dist/main/main.js", 12 | "scripts": { 13 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 14 | "postinstall": "npm run rebuild && npm run link-modules", 15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 16 | }, 17 | "dependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /src/renderer/components/error/index.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import toast from "react-hot-toast"; 3 | import errorEN from "./errorEN.json"; 4 | 5 | export function ErrorLog(error, setError) { 6 | if (error) { 7 | const message = 8 | errorEN[error.code] || errorEN[error.message] || error.message; 9 | 10 | toast(message, { 11 | icon: "🖐", 12 | style: { 13 | borderRadius: "10px", 14 | background: "#333", 15 | color: "#fff", 16 | }, 17 | }); 18 | 19 | if (setError) setError(null); 20 | } 21 | } -------------------------------------------------------------------------------- /src/renderer/utils/device/getOs.jsx: -------------------------------------------------------------------------------- 1 | export const getOS = async () => { 2 | const userAgent = window.navigator.userAgent; 3 | 4 | if (/windows phone/i.test(userAgent)) { 5 | return "Windows Phone"; 6 | } 7 | if (/windows/i.test(userAgent)) { 8 | return "Windows"; 9 | } 10 | if (/android/i.test(userAgent)) { 11 | return "Android"; 12 | } 13 | if (/iPad|iPhone|iPod/.test(userAgent) && !window.MSStream) { 14 | return "iOS"; 15 | } 16 | if (/Macintosh/i.test(userAgent)) { 17 | return "MacOS"; 18 | } 19 | if (/Linux/i.test(userAgent)) { 20 | return "Linux"; 21 | } 22 | return "Unknown"; 23 | }; 24 | -------------------------------------------------------------------------------- /src/renderer/utils/data/users/activity_login.jsx: -------------------------------------------------------------------------------- 1 | import { getDeviceInfo } from "../../device/getDeviceInfo"; 2 | import { getIpAddress } from "../../device/getIpAddress"; 3 | import { getOS } from "../../device/getOs"; 4 | import { getUserLocation } from "../../device/getUserLocation"; 5 | 6 | export const dataUsersActivityLogin = async (name, status) => { 7 | 8 | const data = { 9 | "name" : name, 10 | "description" : '-', 11 | "ip_address" : await getIpAddress() || "Unknown", 12 | "device": (await getDeviceInfo()).device || "Unknown", 13 | "os": await getOS() || "Unknown", 14 | "location": await getUserLocation() || "Unknown", 15 | "login_status": status, 16 | } 17 | 18 | return data; 19 | }; 20 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/about/list.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | HomeModernIcon, 3 | InformationCircleIcon, 4 | HeartIcon, 5 | } from "@heroicons/react/24/outline"; 6 | 7 | export const PathRoute = [ 8 | { 9 | label: "General", 10 | items: [ 11 | { 12 | name: "About", 13 | path: "general/home", 14 | icon: , 15 | description: "Learn more about the app and the development team.", 16 | }, 17 | { 18 | name: "Donation", 19 | path: "general/donate", 20 | icon: , 21 | description: "Support the app’s development through donations.", 22 | }, 23 | ], 24 | }, 25 | ]; 26 | -------------------------------------------------------------------------------- /src/renderer/components/icons/delete.jsx: -------------------------------------------------------------------------------- 1 | export default function DeleteIcon() { 2 | return ( 3 | 4 | 5 | 6 | ); 7 | } 8 | -------------------------------------------------------------------------------- /src/renderer/hooks/useRouteProvider.jsx: -------------------------------------------------------------------------------- 1 | // RouteProvider.jsx 2 | import { createContext, useContext } from "react"; 3 | import useLocalStorage from "use-local-storage"; 4 | 5 | const RouteContext = createContext(); 6 | 7 | export const RouteProvider = ({ children }) => { 8 | const [routes, setRoutes] = useLocalStorage("@routes", "features"); 9 | const [path, setPath] = useLocalStorage("@path", "features/converter/pdf-to-sign"); 10 | const [params, setParams] = useLocalStorage("@params", null); 11 | 12 | return ( 13 | 14 | {children} 15 | 16 | ); 17 | }; 18 | 19 | export const useRoute = () => useContext(RouteContext); 20 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - discussion 8 | - security 9 | # Label to use when marking an issue as stale 10 | staleLabel: wontfix 11 | # Comment to post when marking an issue as stale. Set to `false` to disable 12 | markComment: > 13 | This issue has been automatically marked as stale because it has not had 14 | recent activity. It will be closed if no further activity occurs. Thank you 15 | for your contributions. 16 | # Comment to post when closing a stale issue. Set to `false` to disable 17 | closeComment: false 18 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".eslintrc": "jsonc", 4 | ".prettierrc": "jsonc", 5 | ".eslintignore": "ignore" 6 | }, 7 | 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "html", 12 | "typescriptreact" 13 | ], 14 | 15 | "javascript.validate.enable": false, 16 | "javascript.format.enable": false, 17 | "typescript.format.enable": false, 18 | 19 | "search.exclude": { 20 | ".git": true, 21 | ".eslintcache": true, 22 | ".erb/dll": true, 23 | "release/{build,app/dist}": true, 24 | "node_modules": true, 25 | "npm-debug.log.*": true, 26 | "test/**/__snapshots__": true, 27 | "package-lock.json": true, 28 | "*.{css,sass,scss}.d.ts": true 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | import React = require('react'); 5 | 6 | export const ReactComponent: React.FC>; 7 | 8 | const content: string; 9 | export default content; 10 | } 11 | 12 | declare module '*.png' { 13 | const content: string; 14 | export default content; 15 | } 16 | 17 | declare module '*.jpg' { 18 | const content: string; 19 | export default content; 20 | } 21 | 22 | declare module '*.scss' { 23 | const content: Styles; 24 | export default content; 25 | } 26 | 27 | declare module '*.sass' { 28 | const content: Styles; 29 | export default content; 30 | } 31 | 32 | declare module '*.css' { 33 | const content: Styles; 34 | export default content; 35 | } 36 | -------------------------------------------------------------------------------- /src/renderer/utils/functions/date.jsx: -------------------------------------------------------------------------------- 1 | export const formatEventTime = async (start, end, locale = "en-US") => { 2 | const dateOptions = { 3 | weekday: "long", 4 | month: "long", 5 | day: "numeric", 6 | }; 7 | 8 | const timeOptions = { 9 | hour: "numeric", 10 | minute: "2-digit", 11 | hour12: true, 12 | }; 13 | 14 | const startDate = new Date(start); 15 | const endDate = new Date(end); 16 | 17 | const datePart = new Intl.DateTimeFormat(locale, dateOptions).format(startDate); 18 | const startTime = new Intl.DateTimeFormat(locale, timeOptions).format(startDate); 19 | const endTime = new Intl.DateTimeFormat(locale, timeOptions).format(endDate); 20 | 21 | return `${datePart}⋅${startTime} – ${endTime}`; 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["run", "start"], 11 | "env": { 12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223" 13 | } 14 | }, 15 | { 16 | "name": "Electron: Renderer", 17 | "type": "chrome", 18 | "request": "attach", 19 | "port": 9223, 20 | "webRoot": "${workspaceFolder}", 21 | "timeout": 15000 22 | } 23 | ], 24 | "compounds": [ 25 | { 26 | "name": "Electron: All", 27 | "configurations": ["Electron: Main", "Electron: Renderer"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | test: 7 | runs-on: ${{ matrix.os }} 8 | 9 | strategy: 10 | matrix: 11 | os: [macos-latest, windows-latest, ubuntu-latest] 12 | 13 | steps: 14 | - name: Check out Git repository 15 | uses: actions/checkout@v3 16 | 17 | # - name: Install Node.js and NPM 18 | # uses: actions/setup-node@v3 19 | # with: 20 | # node-version: 22 21 | # cache: npm 22 | 23 | # - name: npm install 24 | # run: | 25 | # npm install 26 | 27 | # - name: npm test 28 | # env: 29 | # GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | # CI: true 31 | # run: | 32 | # npm run package 33 | # npm run lint 34 | # npm exec tsc 35 | # npm test 36 | -------------------------------------------------------------------------------- /src/renderer/components/loading/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Screen() { 4 | return ( 5 |
6 | 12 | 20 | 25 | 26 |
27 | ); 28 | } 29 | 30 | export default Screen; -------------------------------------------------------------------------------- /src/renderer/utils/security/crypto.jsx: -------------------------------------------------------------------------------- 1 | import CryptoJS from "crypto-js"; 2 | 3 | const SECRET = process.env.VITE_ENCRYPT_SECRET; 4 | const ENCRYPTION_KEY = CryptoJS.SHA256(SECRET); 5 | 6 | export function encryptAES256(text) { 7 | const iv = CryptoJS.lib.WordArray.random(16); // iv random 8 | const encrypted = CryptoJS.AES.encrypt(text, ENCRYPTION_KEY, { 9 | iv, 10 | mode: CryptoJS.mode.CBC, 11 | padding: CryptoJS.pad.Pkcs7, 12 | }); 13 | 14 | return { 15 | iv: CryptoJS.enc.Base64.stringify(iv), 16 | data: encrypted.toString(), // ciphertext base64 17 | }; 18 | } 19 | 20 | export function decryptAES256(encryptedData) { 21 | const iv = CryptoJS.enc.Base64.parse(encryptedData.iv); 22 | const decrypted = CryptoJS.AES.decrypt(encryptedData.data, ENCRYPTION_KEY, { 23 | iv, 24 | mode: CryptoJS.mode.CBC, 25 | padding: CryptoJS.pad.Pkcs7, 26 | }); 27 | 28 | return decrypted.toString(CryptoJS.enc.Utf8); 29 | } 30 | -------------------------------------------------------------------------------- /src/renderer/components/tabs/main.jsx: -------------------------------------------------------------------------------- 1 | // src/components/Tabs.jsx 2 | import { useState } from "react"; 3 | 4 | export function Tabs({ defaultValue, children }) { 5 | const [active, setActive] = useState(defaultValue); 6 | return
{children({ active, setActive })}
; 7 | } 8 | 9 | export function TabsList({ children }) { 10 | return
{children}
; 11 | } 12 | 13 | export function TabsTrigger({ value, active, setActive, children }) { 14 | return ( 15 | 24 | ); 25 | } 26 | 27 | export function TabsContent({ value, active, children }) { 28 | return active === value ?
{children}
: null; 29 | } 30 | -------------------------------------------------------------------------------- /src/renderer/utils/database/required_fields.jsx: -------------------------------------------------------------------------------- 1 | export function getCreateFields(authUser) { 2 | return { 3 | status: 'active', // inactive 4 | platform: 'web', // mobile, desktop 5 | version: 1, 6 | note: null, 7 | server: 1, 8 | source: 'system', // manual 9 | tags: [], 10 | meta: {}, 11 | attributes: {}, 12 | external_id: null, 13 | priority: 0, 14 | sync_status: null, // pending, synced 15 | is_deleted: false, 16 | is_category: false, 17 | is_favorite: false, 18 | checksum: null, 19 | usage_count: 0, 20 | deleted_at: null, 21 | created_by: authUser?.email ?? 'system', 22 | updated_by: authUser?.email ?? 'system', 23 | created_at: new Date(), 24 | updated_at: new Date(), 25 | last_accessed_at : null 26 | }; 27 | } 28 | 29 | export function getUpdateFields(authUser) { 30 | return { 31 | updated_by: authUser?.email ?? 'system', 32 | updated_at: new Date() 33 | }; 34 | } -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import { TextEncoder, TextDecoder } from 'node:util'; 6 | import webpackPaths from '../configs/webpack.paths'; 7 | 8 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 9 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 10 | 11 | if (!fs.existsSync(mainPath)) { 12 | throw new Error( 13 | chalk.whiteBright.bgRed.bold( 14 | 'The main process is not built yet. Build it by running "npm run build:main"', 15 | ), 16 | ); 17 | } 18 | 19 | if (!fs.existsSync(rendererPath)) { 20 | throw new Error( 21 | chalk.whiteBright.bgRed.bold( 22 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"', 23 | ), 24 | ); 25 | } 26 | 27 | // JSDOM does not implement TextEncoder and TextDecoder 28 | if (!global.TextEncoder) { 29 | global.TextEncoder = TextEncoder; 30 | } 31 | if (!global.TextDecoder) { 32 | // @ts-ignore 33 | global.TextDecoder = TextDecoder; 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/components/background/waveLayers.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function BgWaveLayers() { 4 | return ( 5 |
6 | {/* Waves */} 7 | 26 | 27 |
28 | ); 29 | } 30 | 31 | export default BgWaveLayers; 32 | -------------------------------------------------------------------------------- /src/renderer/components/error/comingsoon.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function ComingSoon() { 4 | return ( 5 |
6 |
7 | 10 | 🚀 Coming Soon 11 | 12 |

13 | Fitur Baru Segera Hadir 14 |

15 |

16 | Kami sedang menyiapkan integrasi menarik lainnya untuk membantu Anda 17 | bekerja lebih produktif. Nantikan update berikutnya! 18 |

19 |
20 |
21 | ); 22 | } 23 | -------------------------------------------------------------------------------- /.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize'); 2 | const { build } = require('../../package.json'); 3 | 4 | exports.default = async function notarizeMacos(context) { 5 | const { electronPlatformName, appOutDir } = context; 6 | if (electronPlatformName !== 'darwin') { 7 | return; 8 | } 9 | 10 | if (process.env.CI !== 'true') { 11 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 12 | return; 13 | } 14 | 15 | if ( 16 | !( 17 | 'APPLE_ID' in process.env && 18 | 'APPLE_ID_PASS' in process.env && 19 | 'APPLE_TEAM_ID' in process.env 20 | ) 21 | ) { 22 | console.warn( 23 | 'Skipping notarizing step. APPLE_ID, APPLE_ID_PASS, and APPLE_TEAM_ID env variables must be set', 24 | ); 25 | return; 26 | } 27 | 28 | const appName = context.packager.appInfo.productFilename; 29 | 30 | await notarize({ 31 | tool: 'notarytool', 32 | appBundleId: build.appId, 33 | appPath: `${appOutDir}/${appName}.app`, 34 | appleId: process.env.APPLE_ID, 35 | appleIdPassword: process.env.APPLE_ID_PASS, 36 | teamId: process.env.APPLE_TEAM_ID, 37 | }); 38 | }; 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015-present Electron React Boilerplate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/renderer/components/navbar/dash.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useRef, useEffect } from "react"; 2 | import { Settings } from "lucide-react"; 3 | import { useRoute } from "../../hooks/useRouteProvider"; 4 | 5 | export default function NavDash() { 6 | 7 | const { setRoutes, setPath } = useRoute(); 8 | 9 | const [open, setOpen] = useState(false); 10 | const dropdownRef = useRef(null); 11 | 12 | 13 | // Close dropdown jika klik di luar 14 | useEffect(() => { 15 | const handleClickOutside = (event) => { 16 | if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { 17 | setOpen(false); 18 | } 19 | }; 20 | document.addEventListener("mousedown", handleClickOutside); 21 | return () => { 22 | document.removeEventListener("mousedown", handleClickOutside); 23 | }; 24 | }, []); 25 | 26 | return ( 27 |
28 |
29 |

{process.env.VITE_APP_NAME}

30 |
31 |
32 | ); 33 | } 34 | -------------------------------------------------------------------------------- /src/main/functions/postProcessPDFExpired.tsx: -------------------------------------------------------------------------------- 1 | import { PDFDocument, PDFName, PDFString, PDFHexString } from 'pdf-lib'; 2 | import * as fs from 'fs'; 3 | 4 | export async function addExpiryVisualBlock(pdfDoc: PDFDocument, expiryDate: Date) { 5 | const jsCode = ` 6 | var expiry = new Date("${expiryDate.toISOString()}"); 7 | var now = new Date(); 8 | if (now > expiry) { 9 | for (var i = 0; i < this.numPages; i++) { 10 | var pageBox = this.getPageBox("Crop", i); 11 | this.addAnnot({ 12 | page: i, 13 | type: "Square", 14 | rect: pageBox, 15 | fillColor: color.white, 16 | strokeColor: color.white, 17 | author: "Expiry Script" 18 | }); 19 | } 20 | app.alert("This document has expired and is no longer viewable."); 21 | } 22 | `; 23 | 24 | const jsAction = pdfDoc.context.obj({ 25 | Type: PDFName.of('Action'), 26 | S: PDFName.of('JavaScript'), 27 | JS: PDFString.of(jsCode), 28 | }); 29 | 30 | pdfDoc.catalog.set(PDFName.of('OpenAction'), jsAction); 31 | pdfDoc.setSubject('Expires on ' + expiryDate.toISOString()); 32 | } 33 | 34 | // await addExpiryVisualBlock(pdfDoc, new Date('2025-10-21')); -------------------------------------------------------------------------------- /src/renderer/components/background/meshGradient.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function BgMeshGradient() { 4 | return ( 5 |
6 | {/* Mesh gradient */} 7 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | {/* overlay untuk dark/light balance */} 26 | 27 | 28 |
29 | 30 | ); 31 | } 32 | 33 | export default BgMeshGradient; 34 | -------------------------------------------------------------------------------- /src/renderer/hooks/useSidebar.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import useLocalStorage from "use-local-storage"; 3 | import { Bars3Icon } from "@heroicons/react/24/solid"; 4 | 5 | export default function useSidebar(initialValue = false) { 6 | const [sidebarOpen, setSidebarOpen] = useLocalStorage("@sidebar", initialValue); 7 | 8 | const toggleSidebar = () => setSidebarOpen(prev => !prev); 9 | 10 | const ToggleSidebarButton = () => { 11 | return ( 12 | 18 | ) 19 | } 20 | 21 | const SidebarOverlay = () => { 22 | return sidebarOpen && ( 23 |
setSidebarOpen(false)} 25 | className="fixed inset-0 bg-gray-50/60 dark:bg-gray-900/60 backdrop-blur-sm lg:hidden z-20" 26 | /> 27 | ); 28 | }; 29 | 30 | return { sidebarOpen, setSidebarOpen, ToggleSidebarButton, SidebarOverlay }; 31 | } 32 | -------------------------------------------------------------------------------- /src/renderer/components/background/dottedGrid.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function BgDottedGrid() { 4 | return ( 5 |
6 | {/* Dotted grid */} 7 | 28 |
29 | 30 | ); 31 | } 32 | 33 | export default BgDottedGrid; 34 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | plugins: ['@typescript-eslint'], 4 | rules: { 5 | // A temporary hack related to IDE not resolving correct package.json 6 | 'import/no-extraneous-dependencies': 'off', 7 | 'react/react-in-jsx-scope': 'off', 8 | 'react/jsx-filename-extension': 'off', 9 | 'import/extensions': 'off', 10 | 'import/no-unresolved': 'off', 11 | 'import/no-import-module-exports': 'off', 12 | 'no-shadow': 'off', 13 | '@typescript-eslint/no-shadow': 'error', 14 | 'no-unused-vars': 'off', 15 | '@typescript-eslint/no-unused-vars': 'error', 16 | }, 17 | parserOptions: { 18 | ecmaVersion: 2022, 19 | sourceType: 'module', 20 | }, 21 | settings: { 22 | 'import/resolver': { 23 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 24 | node: { 25 | extensions: ['.js', '.jsx', '.ts', '.tsx'], 26 | moduleDirectory: ['node_modules', 'src/'], 27 | }, 28 | webpack: { 29 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 30 | }, 31 | typescript: {}, 32 | }, 33 | 'import/parsers': { 34 | '@typescript-eslint/parser': ['.ts', '.tsx'], 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/routes.jsx: -------------------------------------------------------------------------------- 1 | export const MainRoute = [ 2 | { 3 | key: "features", 4 | title: "Features", 5 | icon: ( 6 | 7 | 8 | 9 | 10 | ), 11 | }, 12 | { 13 | key: "about", 14 | title: "About", 15 | icon: ( 16 | 17 | 18 | 19 | ), 20 | }, 21 | ]; 22 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const erbPath = path.join(__dirname, '..'); 6 | const erbNodeModulesPath = path.join(erbPath, 'node_modules'); 7 | 8 | const dllPath = path.join(__dirname, '../dll'); 9 | 10 | const srcPath = path.join(rootPath, 'src'); 11 | const srcMainPath = path.join(srcPath, 'main'); 12 | const srcRendererPath = path.join(srcPath, 'renderer'); 13 | 14 | const releasePath = path.join(rootPath, 'release'); 15 | const appPath = path.join(releasePath, 'app'); 16 | const appPackagePath = path.join(appPath, 'package.json'); 17 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 18 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 19 | 20 | const distPath = path.join(appPath, 'dist'); 21 | const distMainPath = path.join(distPath, 'main'); 22 | const distRendererPath = path.join(distPath, 'renderer'); 23 | 24 | const buildPath = path.join(releasePath, 'build'); 25 | 26 | export default { 27 | rootPath, 28 | erbNodeModulesPath, 29 | dllPath, 30 | srcPath, 31 | srcMainPath, 32 | srcRendererPath, 33 | releasePath, 34 | appPath, 35 | appPackagePath, 36 | appNodeModulesPath, 37 | srcNodeModulesPath, 38 | distPath, 39 | distMainPath, 40 | distRendererPath, 41 | buildPath, 42 | }; 43 | -------------------------------------------------------------------------------- /src/main/preload.ts: -------------------------------------------------------------------------------- 1 | // Disable no-unused-vars, broken for spread args 2 | /* eslint no-unused-vars: off */ 3 | import { contextBridge, ipcRenderer, IpcRendererEvent } from 'electron'; 4 | 5 | export type Channels = 'ipc-example'; 6 | 7 | const electronHandler = { 8 | ipcRenderer: { 9 | sendMessage(channel: Channels, ...args: unknown[]) { 10 | ipcRenderer.send(channel, ...args); 11 | }, 12 | on(channel: Channels, func: (...args: unknown[]) => void) { 13 | const subscription = (_event: IpcRendererEvent, ...args: unknown[]) => 14 | func(...args); 15 | ipcRenderer.on(channel, subscription); 16 | 17 | return () => { 18 | ipcRenderer.removeListener(channel, subscription); 19 | }; 20 | }, 21 | once(channel: Channels, func: (...args: unknown[]) => void) { 22 | ipcRenderer.once(channel, (_event, ...args) => func(...args)); 23 | }, 24 | }, 25 | }; 26 | 27 | contextBridge.exposeInMainWorld('electron', electronHandler); 28 | 29 | contextBridge.exposeInMainWorld('electronAPI', { 30 | uploadFile: (params:any) => ipcRenderer.invoke('upload-file', params), 31 | selectCertFile: () => ipcRenderer.invoke('select-cert-file'), 32 | onConvertProgress: (callback:any) => { 33 | ipcRenderer.on('convert-progress', (_event, data) => { 34 | callback(data); 35 | }); 36 | }, 37 | }); 38 | 39 | export type ElectronHandler = typeof electronHandler; 40 | -------------------------------------------------------------------------------- /src/renderer/hooks/useDarkMode.jsx: -------------------------------------------------------------------------------- 1 | // DarkModeToggle.jsx 2 | import React, { useState, useEffect } from "react"; 3 | import { SunIcon, MoonIcon } from "@heroicons/react/24/solid"; 4 | import useLocalStorage from "use-local-storage"; 5 | 6 | const UseDarkMode = () => { 7 | const isDark = window.matchMedia("(prefers-color-scheme: dark)").matches; 8 | const [darkMode, setDarkMode] = useLocalStorage("@themes", isDark); 9 | 10 | useEffect(() => { 11 | if (darkMode) { 12 | document.documentElement.classList.add("dark"); 13 | } else { 14 | document.documentElement.classList.remove("dark"); 15 | } 16 | 17 | const mql = window.matchMedia("(prefers-color-scheme: dark)"); 18 | const handler = (e) => setDarkMode(e.matches); 19 | 20 | mql.addEventListener("change", handler); 21 | return () => mql.removeEventListener("change", handler); 22 | }, [darkMode]); 23 | 24 | return ( 25 | 35 | ); 36 | }; 37 | 38 | export default UseDarkMode; 39 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | publish: 10 | # To enable auto publishing to github, update your electron publisher 11 | # config in package.json > "build" and remove the conditional below 12 | if: ${{ github.repository_owner == 'electron-react-boilerplate' }} 13 | 14 | runs-on: ${{ matrix.os }} 15 | 16 | strategy: 17 | matrix: 18 | os: [macos-latest] 19 | 20 | steps: 21 | - name: Checkout git repo 22 | uses: actions/checkout@v3 23 | 24 | - name: Install Node and NPM 25 | uses: actions/setup-node@v3 26 | with: 27 | node-version: 22 28 | cache: npm 29 | 30 | - name: Install and build 31 | run: | 32 | npm install 33 | npm run postinstall 34 | npm run build 35 | 36 | - name: Publish releases 37 | env: 38 | # The APPLE_* values are used for auto updates signing 39 | APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_ID_PASS }} 40 | APPLE_ID: ${{ secrets.APPLE_ID }} 41 | APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} 42 | CSC_LINK: ${{ secrets.CSC_LINK }} 43 | CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} 44 | # This is used for uploading release assets to github 45 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 46 | run: | 47 | npm exec electron-builder -- --publish always --win --mac --linux 48 | -------------------------------------------------------------------------------- /src/renderer/components/error/404.jsx: -------------------------------------------------------------------------------- 1 | export default function EmptyData({ message = "Data Not Found", description, onAction, actionLabel }) { 2 | return ( 3 |
4 | {/* Ilustrasi */} 5 | 13 | 18 | 19 | 20 | {/* Pesan */} 21 |

{message}

22 | { 23 | description && 24 |

{description}

25 | } 26 | 27 | {/* Optional action button */} 28 | {onAction && actionLabel && ( 29 | 35 | )} 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /src/renderer/components/background/gradientBlobs.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function BgGradientBlobs() { 4 | return ( 5 |
6 | 35 |
36 | ); 37 | } 38 | 39 | export default BgGradientBlobs; 40 | -------------------------------------------------------------------------------- /src/renderer/screens/error/404/index.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import ImgError404 from "./../../../assets/error/404.svg"; 3 | import ImgError404Dark from "./../../../assets/error/404-dark.svg"; 4 | 5 | export default function Error404() { 6 | return ( 7 |
8 | {/* Centered Content */} 9 |
10 |

11 | ERROR 12 |

13 | {404} 14 | {404} 19 |

20 | We can’t seem to find the page you are looking for! 21 |

22 | 26 | Back to Home Page 27 | 28 |
29 |
30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin'; 7 | import webpackPaths from './webpack.paths'; 8 | import { dependencies as externals } from '../../release/app/package.json'; 9 | 10 | const configuration: webpack.Configuration = { 11 | externals: [...Object.keys(externals || {})], 12 | 13 | stats: 'errors-only', 14 | 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.[jt]sx?$/, 19 | exclude: /node_modules/, 20 | use: { 21 | loader: 'ts-loader', 22 | options: { 23 | // Remove this line to enable type checking in webpack builds 24 | transpileOnly: true, 25 | compilerOptions: { 26 | module: 'nodenext', 27 | moduleResolution: 'nodenext', 28 | }, 29 | }, 30 | }, 31 | }, 32 | ], 33 | }, 34 | 35 | output: { 36 | path: webpackPaths.srcPath, 37 | // https://github.com/webpack/webpack/issues/1114 38 | library: { type: 'commonjs2' }, 39 | }, 40 | 41 | /** 42 | * Determine the array of extensions that should be used to resolve modules. 43 | */ 44 | resolve: { 45 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 46 | modules: [webpackPaths.srcPath, 'node_modules'], 47 | // There is no need to add aliases here, the paths in tsconfig get mirrored 48 | plugins: [new TsconfigPathsPlugins()], 49 | }, 50 | 51 | plugins: [new webpack.EnvironmentPlugin({ NODE_ENV: 'production' })], 52 | }; 53 | 54 | export default configuration; 55 | -------------------------------------------------------------------------------- /src/main/functions/postProcessSecure.tsx: -------------------------------------------------------------------------------- 1 | import crypto from 'crypto'; 2 | import fs from 'fs'; 3 | 4 | export function encryptWithExpiry(inputPath:any, outputPath:any, password:any, expiryDate:any) { 5 | const key = crypto.createHash('sha256').update(password).digest(); 6 | const iv = crypto.randomBytes(16); 7 | const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); 8 | 9 | const metadata = JSON.stringify({ expiry: expiryDate.toISOString() }); 10 | const metadataBuffer = Buffer.from(metadata); 11 | const metadataLength = Buffer.alloc(4); 12 | metadataLength.writeUInt32BE(metadataBuffer.length); 13 | 14 | const input = fs.readFileSync(inputPath); 15 | const encrypted = Buffer.concat([cipher.update(input), cipher.final()]); 16 | 17 | fs.writeFileSync(outputPath, Buffer.concat([iv, metadataLength, metadataBuffer, encrypted])); 18 | return outputPath; 19 | } 20 | 21 | export function decryptWithExpiry(inputPath:any, outputPath:any, password:any) { 22 | const fileBuffer = fs.readFileSync(inputPath); 23 | const iv = fileBuffer.subarray(0, 16); 24 | const metadataLength = fileBuffer.readUInt32BE(16); 25 | const metadataStart = 20; 26 | const metadataEnd = metadataStart + metadataLength; 27 | 28 | const metadataBuffer = fileBuffer.subarray(metadataStart, metadataEnd); 29 | const metadata = JSON.parse(metadataBuffer.toString()); 30 | const expiry = new Date(metadata.expiry); 31 | 32 | if (new Date() > expiry) { 33 | throw new Error('The file has expired and cannot be opened.'); 34 | } 35 | 36 | const encryptedData = fileBuffer.subarray(metadataEnd); 37 | const key = crypto.createHash('sha256').update(password).digest(); 38 | const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); 39 | 40 | const decrypted = Buffer.concat([decipher.update(encryptedData), decipher.final()]); 41 | fs.writeFileSync(outputPath, decrypted); 42 | return outputPath; 43 | } 44 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.dev.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for development electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 8 | import { merge } from 'webpack-merge'; 9 | import checkNodeEnv from '../scripts/check-node-env'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | 13 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 14 | // at the dev webpack config is not accidentally run in a production environment 15 | if (process.env.NODE_ENV === 'production') { 16 | checkNodeEnv('development'); 17 | } 18 | 19 | const configuration: webpack.Configuration = { 20 | devtool: 'inline-source-map', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-main', 25 | 26 | entry: { 27 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 28 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 29 | }, 30 | 31 | output: { 32 | path: webpackPaths.dllPath, 33 | filename: '[name].bundle.dev.js', 34 | library: { 35 | type: 'umd', 36 | }, 37 | }, 38 | 39 | plugins: [ 40 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 41 | // @ts-ignore 42 | new BundleAnalyzerPlugin({ 43 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 44 | analyzerPort: 8888, 45 | }), 46 | 47 | new webpack.DefinePlugin({ 48 | 'process.type': '"browser"', 49 | }), 50 | ], 51 | 52 | /** 53 | * Disables webpack processing of __dirname and __filename. 54 | * If you run the bundle in node.js it falls back to these values of node.js. 55 | * https://github.com/webpack/webpack/issues/2010 56 | */ 57 | node: { 58 | __dirname: false, 59 | __filename: false, 60 | }, 61 | }; 62 | 63 | export default merge(baseConfig, configuration); 64 | -------------------------------------------------------------------------------- /src/renderer/components/web/footer.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | 3 | export default function WebFooter() { 4 | 5 | return ( 6 |
7 |
8 |
9 |
10 | 11 | 16 | 17 |
18 | 22 | Privacy 23 | 24 | 28 | Terms 29 | 30 |
31 |

32 | © Copyright 2016 - {new Date().getFullYear()} PT. Flamecore Cloud Indonesia.{" "} 33 |

34 |
35 |
36 | 37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/functions/getSofficePath.tsx: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import { app } from 'electron'; 3 | 4 | export function getSofficePath() { 5 | let sofficePath = ''; 6 | 7 | if (app.isPackaged) { 8 | switch (process.platform) { 9 | case 'darwin': 10 | if (process.arch === 'arm64') { 11 | sofficePath = path.join( 12 | process.resourcesPath, 13 | 'libreoffice/mac/arm64/LibreOffice.app/Contents/MacOS/soffice' 14 | ); 15 | } else { 16 | sofficePath = path.join( 17 | process.resourcesPath, 18 | 'libreoffice/mac/x86-64/LibreOffice.app/Contents/MacOS/soffice' 19 | ); 20 | } 21 | break; 22 | case 'win32': 23 | sofficePath = path.join( 24 | process.resourcesPath, 25 | 'libreoffice/win/x86-64/soffice.exe' 26 | ); 27 | break; 28 | case 'linux': 29 | sofficePath = path.join( 30 | process.resourcesPath, 31 | 'libreoffice/linux/x86-64/soffice' 32 | ); 33 | break; 34 | default: 35 | throw new Error('Unsupported platform: ' + process.platform); 36 | } 37 | } else { 38 | // Dev mode 39 | switch (process.platform) { 40 | case 'darwin': 41 | sofficePath = path.join( 42 | __dirname, 43 | '../../src/resources/libreoffice/mac/' + 44 | (process.arch === 'arm64' ? 'arm64' : 'x86-64') + 45 | '/LibreOffice.app/Contents/MacOS/soffice' 46 | ); 47 | break; 48 | case 'win32': 49 | sofficePath = path.join( 50 | __dirname, 51 | '../../src/resources/libreoffice/win/x86-64/soffice.exe' 52 | ); 53 | break; 54 | case 'linux': 55 | sofficePath = path.join( 56 | __dirname, 57 | '../../src/resources/libreoffice/linux/x86-64/soffice' 58 | ); 59 | break; 60 | default: 61 | throw new Error('Unsupported platform: ' + process.platform); 62 | } 63 | } 64 | 65 | return sofficePath; 66 | } 67 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/1-Bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: You're having technical issues. 🐞 4 | labels: 'bug' 5 | --- 6 | 7 | 8 | 9 | ## Prerequisites 10 | 11 | 12 | 13 | - [ ] Using npm 14 | - [ ] Using an up-to-date [`main` branch](https://github.com/electron-react-boilerplate/electron-react-boilerplate/tree/main) 15 | - [ ] Using latest version of devtools. [Check the docs for how to update](https://electron-react-boilerplate.js.org/docs/dev-tools/) 16 | - [ ] Tried solutions mentioned in [#400](https://github.com/electron-react-boilerplate/electron-react-boilerplate/issues/400) 17 | - [ ] For issue in production release, add devtools output of `DEBUG_PROD=true npm run build && npm start` 18 | 19 | ## Expected Behavior 20 | 21 | 22 | 23 | ## Current Behavior 24 | 25 | 26 | 27 | ## Steps to Reproduce 28 | 29 | 30 | 31 | 32 | 1. 33 | 34 | 2. 35 | 36 | 3. 37 | 38 | 4. 39 | 40 | ## Possible Solution (Not obligatory) 41 | 42 | 43 | 44 | ## Context 45 | 46 | 47 | 48 | 49 | 50 | ## Your Environment 51 | 52 | 53 | 54 | - Node version : 55 | - electron-react-boilerplate version or branch : 56 | - Operating System and version : 57 | - Link to your project : 58 | 59 | 68 | -------------------------------------------------------------------------------- /src/renderer/assets/error/404.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/renderer/assets/error/404-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/about/general/home/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ArrowTopRightOnSquareIcon } from "@heroicons/react/24/solid"; 3 | 4 | export default function Screen() { 5 | return ( 6 |
7 |
8 |
9 | About 10 |
11 | 12 |

13 | About This Application 14 |

15 | 16 |

17 | This application is part of the Flamecore Cloud ecosystem — 18 | designed to make your digital work easier, faster, and more productive. 19 | We are constantly developing new integrations and features to enhance your experience. 20 | Stay tuned for upcoming updates and improvements! 21 |

22 | 23 | 43 |
44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.preload.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 5 | import baseConfig from './webpack.config.base'; 6 | import webpackPaths from './webpack.paths'; 7 | import checkNodeEnv from '../scripts/check-node-env'; 8 | 9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 10 | // at the dev webpack config is not accidentally run in a production environment 11 | if (process.env.NODE_ENV === 'production') { 12 | checkNodeEnv('development'); 13 | } 14 | 15 | const configuration: webpack.Configuration = { 16 | devtool: 'inline-source-map', 17 | 18 | mode: 'development', 19 | 20 | target: 'electron-preload', 21 | 22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'), 23 | 24 | output: { 25 | path: webpackPaths.dllPath, 26 | filename: 'preload.js', 27 | library: { 28 | type: 'umd', 29 | }, 30 | }, 31 | 32 | plugins: [ 33 | new BundleAnalyzerPlugin({ 34 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 35 | }), 36 | 37 | /** 38 | * Create global constants which can be configured at compile time. 39 | * 40 | * Useful for allowing different behaviour between development builds and 41 | * release builds 42 | * 43 | * NODE_ENV should be production so that modules do not perform certain 44 | * development checks 45 | * 46 | * By default, use 'development' as NODE_ENV. This can be overriden with 47 | * 'staging', for example, by changing the ENV variables in the npm scripts 48 | */ 49 | new webpack.EnvironmentPlugin({ 50 | NODE_ENV: 'development', 51 | }), 52 | 53 | new webpack.LoaderOptionsPlugin({ 54 | debug: true, 55 | }), 56 | ], 57 | 58 | /** 59 | * Disables webpack processing of __dirname and __filename. 60 | * If you run the bundle in node.js it falls back to these values of node.js. 61 | * https://github.com/webpack/webpack/issues/2010 62 | */ 63 | node: { 64 | __dirname: false, 65 | __filename: false, 66 | }, 67 | 68 | watch: true, 69 | }; 70 | 71 | export default merge(baseConfig, configuration); 72 | -------------------------------------------------------------------------------- /.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString(), 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency), 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.', 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":', 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold( 42 | 'cd ./release/app && npm install your-package', 43 | )} 44 | Read more about native dependencies at: 45 | ${chalk.bold( 46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure', 47 | )} 48 | `); 49 | process.exit(1); 50 | } 51 | } catch { 52 | console.log('Native dependencies could not be checked'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/renderer/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense, lazy, useEffect } from "react"; 2 | import { MemoryRouter as Router, Routes, Route, Navigate } from "react-router-dom"; 3 | import { Toaster, toast } from "react-hot-toast"; 4 | import LoadingScreen from "./components/loading/main"; 5 | import UseDarkMode from "./hooks/useDarkMode"; 6 | import Error from "./components/error"; 7 | import useSidebar from "./hooks/useSidebar"; 8 | import { RouteProvider } from "./hooks/useRouteProvider"; 9 | 10 | const Dashboard = lazy(() => import("./screens/dashboard")); 11 | const Error404 = lazy(() => import("./screens/error/404")); 12 | 13 | 14 | export default function App() { 15 | const { ToggleSidebarButton } = useSidebar(); 16 | 17 | return ( 18 | 19 | 20 |
21 | 22 | 23 | }> 24 | 25 | } /> 26 | 27 | } /> 28 | } /> 29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 | { 37 | process.env.NODE_ENV === "production" && ( 38 |
39 | 40 |
41 | ) 42 | } 43 | { 44 | process.env.NODE_ENV === "development" && ( 45 | 46 | ) 47 | } 48 |
49 |
50 |
51 |
52 | ); 53 | } 54 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const configuration: webpack.Configuration = { 19 | devtool: 'source-map', 20 | 21 | mode: 'production', 22 | 23 | target: 'electron-main', 24 | 25 | entry: { 26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.distMainPath, 32 | filename: '[name].js', 33 | library: { 34 | type: 'umd', 35 | }, 36 | }, 37 | 38 | optimization: { 39 | minimizer: [ 40 | new TerserPlugin({ 41 | parallel: true, 42 | }), 43 | ], 44 | }, 45 | 46 | plugins: [ 47 | new BundleAnalyzerPlugin({ 48 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 49 | analyzerPort: 8888, 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'production', 63 | DEBUG_PROD: false, 64 | START_MINIMIZED: false, 65 | }), 66 | 67 | new webpack.DefinePlugin({ 68 | 'process.type': '"browser"', 69 | }), 70 | ], 71 | 72 | /** 73 | * Disables webpack processing of __dirname and __filename. 74 | * If you run the bundle in node.js it falls back to these values of node.js. 75 | * https://github.com/webpack/webpack/issues/2010 76 | */ 77 | node: { 78 | __dirname: false, 79 | __filename: false, 80 | }, 81 | }; 82 | 83 | export default merge(baseConfig, configuration); 84 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/about/general/donate/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { HeartIcon, BanknotesIcon, ClipboardDocumentIcon, CheckCircleIcon } from "@heroicons/react/24/solid"; 3 | 4 | export default function Screen() { 5 | const [copied, setCopied] = useState(false); 6 | const bankNumber = "https://www.patreon.com/cw/andikachamberlin"; 7 | 8 | const handleCopy = () => { 9 | navigator.clipboard.writeText(bankNumber); 10 | setCopied(true); 11 | setTimeout(() => setCopied(false), 2000); 12 | }; 13 | 14 | return ( 15 |
16 |
17 |
18 | 19 |
20 | 21 |

22 | Support My Project ❤️ 23 |

24 |

25 | Your support helps me continue building and maintaining this project. 26 |

27 | 28 |
29 |
30 | 31 |
32 |

33 | Donate 34 |

35 | {/*

Bank Account Number:

*/} 36 | 37 |
38 | 39 | {bankNumber} 40 | 41 | 51 |
52 | 53 |

54 | Please confirm your donation after transfer. 55 |

56 |
57 | 58 |
59 | Every contribution means a lot. 60 |
Thank you for your kindness 💖 61 |
62 |
63 |
64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![version](https://img.shields.io/badge/version-1.0.0-blue) 2 | ![license](https://img.shields.io/badge/license-MIT-green) 3 | ![platform](https://img.shields.io/badge/platform-Windows%20|%20macOS%20|%20Linux-lightgrey) 4 | 5 | [![VirusTotal Scan](https://img.shields.io/badge/VirusTotal-Checked-brightgreen?logo=virustotal)](https://www.virustotal.com/gui/collection/cbf8800f9f2501d22b606e4dd3429e8b25e372147473bb362fdbdcebc4c764f5/iocs) 6 | 7 | # Signature One 8 | 9 | A desktop application for securely signing, encrypting, and managing the expiration of PDF documents. 10 | Built with **Electron + React + Node.js**. 11 | 12 | ## Application Preview 13 | 14 | ![Screenshot](screenshot.png) 15 | 16 | --- 17 | 18 | ## Downloads 19 | 20 | | Platform | File | Status | 21 | | -------- | ----------------------------------------------------------------------------------- | ------ | 22 | | Windows | [Signature-One-Setup.exe](https://github.com/flamecorecloud/signature-one/releases) | ✅ | 23 | | macOS | [Signature-One.dmg](https://github.com/flamecorecloud/signature-one/releases) | ✅ | 24 | | Linux | [Signature-One.AppImage](https://github.com/flamecorecloud/signature-one/releases) | ✅ | 25 | 26 | --- 27 | 28 | ## Key Features 29 | 30 | * Sign PDF files with a `.p12` certificate — fully offline 31 | * **Batch Sign** — sign multiple documents at once 32 | * **Batch Encrypt & Decrypt** PDF documents 33 | * Works entirely offline — ensuring maximum privacy 34 | * Available for Windows, macOS, and Linux 35 | 36 | --- 37 | 38 | ## Installation 39 | 40 | 1. Clone the repository: 41 | 42 | ```bash 43 | git clone https://github.com/flamecorecloud/signature-one.git 44 | cd signature-one 45 | ``` 46 | 2. Install dependencies: 47 | 48 | ```bash 49 | npm install 50 | ``` 51 | 3. Run the app in development mode: 52 | 53 | ```bash 54 | npm start 55 | 4. Generate Key.p12 56 | ```bash 57 | openssl genrsa -out private.key 2048 58 | 59 | openssl req -new -x509 -key private.key -out cert.crt -days 365 60 | 61 | openssl pkcs12 -export \ 62 | -inkey private.key \ 63 | -in cert.crt \ 64 | -out key.p12 65 | ``` 66 | Or use Certificate Generator (https://www.flamecore.cloud/self-signed-certificate) 67 | 5. Self Signed Certificate 68 | ```bash 69 | export CSC_LINK=.dev/fmc/key.p12 70 | export CSC_KEY_PASSWORD=password 71 | 6. Build the application: 72 | 73 | ```bash 74 | npm run package 75 | ``` 76 | 77 | --- 78 | 79 | ## Contact 80 | 81 | Developed by [Andika Chamberlin](https://andikachamberlin.com) 82 | Website: [https://flamecore.cloud](https://flamecore.cloud) 83 | Tutorial: [YouTube](https://www.youtube.com/watch?v=qrnuoWDYUIg) 84 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/config.jsx: -------------------------------------------------------------------------------- 1 | import { lazy } from "react"; 2 | 3 | export const PathRoutes = { 4 | "features": lazy(() => import("./features/path")), 5 | "about": lazy(() => import("./about/path")), 6 | }; 7 | 8 | export const PathComponents = { 9 | // Document 10 | "features/converter/pdf-to-sign": lazy(() => import("./features/converter/pdf-to-sign/view")), 11 | 12 | "features/converter/word-to-pdf": lazy(() => import("./features/converter/word-to-pdf/view")), 13 | "features/converter/txt-to-pdf": lazy(() => import("./features/converter/txt-to-pdf/view")), 14 | "features/converter/html-to-pdf": lazy(() => import("./features/converter/html-to-pdf/view")), 15 | "features/converter/rtf-to-pdf": lazy(() => import("./features/converter/rtf-to-pdf/view")), 16 | "features/converter/odt-to-pdf": lazy(() => import("./features/converter/odt-to-pdf/view")), 17 | "features/converter/html-to-docx": lazy(() => import("./features/converter/html-to-docx/view")), 18 | "features/converter/txt-to-odt": lazy(() => import("./features/converter/txt-to-odt/view")), 19 | "features/converter/word-to-odt": lazy(() => import("./features/converter/word-to-odt/view")), 20 | "features/converter/odt-to-word": lazy(() => import("./features/converter/odt-to-word/view")), 21 | 22 | "features/converter/excel-to-pdf": lazy(() => import("./features/converter/excel-to-pdf/view")), 23 | "features/converter/ods-to-pdf": lazy(() => import("./features/converter/ods-to-pdf/view")), 24 | "features/converter/csv-to-excel": lazy(() => import("./features/converter/csv-to-excel/view")), 25 | "features/converter/excel-to-ods": lazy(() => import("./features/converter/excel-to-ods/view")), 26 | "features/converter/ods-to-excel": lazy(() => import("./features/converter/ods-to-excel/view")), 27 | "features/converter/csv-to-ods": lazy(() => import("./features/converter/csv-to-ods/view")), 28 | 29 | "features/converter/ppt-to-pdf": lazy(() => import("./features/converter/ppt-to-pdf/view")), 30 | "features/converter/odp-to-pdf": lazy(() => import("./features/converter/odp-to-pdf/view")), 31 | "features/converter/ppt-to-odp": lazy(() => import("./features/converter/ppt-to-odp/view")), 32 | "features/converter/odp-to-ppt": lazy(() => import("./features/converter/odp-to-ppt/view")), 33 | 34 | "features/converter/pdf-to-word": lazy(() => import("./features/converter/pdf-to-word/view")), 35 | "features/converter/pdf-to-excel": lazy(() => import("./features/converter/pdf-to-excel/view")), 36 | "features/converter/pdf-to-ppt": lazy(() => import("./features/converter/pdf-to-ppt/view")), 37 | "features/converter/pdf-to-image": lazy(() => import("./features/converter/pdf-to-image/view")), 38 | 39 | "about/general/home": lazy(() => import("./about/general/home/view")), 40 | "about/general/donate": lazy(() => import("./about/general/donate/view")), 41 | }; 42 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ "main" ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ "main" ] 20 | schedule: 21 | - cron: '44 16 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /src/main/functions/filterAction.tsx: -------------------------------------------------------------------------------- 1 | export const converterMap = { 2 | // 🧾 Document Converter 3 | "pdf-to-sign": { 4 | filters: [{ name: 'Word Documents', extensions: ['pdf'] }], 5 | convertTo: 'pdf:writer_pdf_Export' 6 | }, 7 | "word-to-pdf": { 8 | filters: [{ name: 'Word Documents', extensions: ['doc', 'docx'] }], 9 | convertTo: 'pdf:writer_pdf_Export' 10 | }, 11 | "txt-to-pdf": { 12 | filters: [{ name: 'Text Files', extensions: ['txt'] }], 13 | convertTo: 'pdf:writer_pdf_Export' 14 | }, 15 | "html-to-pdf": { 16 | filters: [{ name: 'HTML Files', extensions: ['html', 'htm'] }], 17 | convertTo: 'pdf:writer_pdf_Export' 18 | }, 19 | "rtf-to-pdf": { 20 | filters: [{ name: 'RTF Files', extensions: ['rtf'] }], 21 | convertTo: 'pdf:writer_pdf_Export' 22 | }, 23 | "odt-to-pdf": { 24 | filters: [{ name: 'ODT Files', extensions: ['odt'] }], 25 | convertTo: 'pdf:writer_pdf_Export' 26 | }, 27 | "html-to-docx": { 28 | filters: [{ name: 'HTML Files', extensions: ['html', 'htm'] }], 29 | convertTo: 'docx:writer8' 30 | }, 31 | "txt-to-odt": { 32 | filters: [{ name: 'Text Files', extensions: ['txt'] }], 33 | convertTo: 'odt:writer8' 34 | }, 35 | "word-to-odt": { 36 | filters: [{ name: 'Word Documents', extensions: ['doc', 'docx'] }], 37 | convertTo: 'odt:writer8' 38 | }, 39 | "odt-to-word": { 40 | filters: [{ name: 'ODT Files', extensions: ['odt'] }], 41 | convertTo: 'docx:writer8' 42 | }, 43 | 44 | // 📊 Spreadsheet Converter 45 | "excel-to-pdf": { 46 | filters: [{ name: 'Excel Sheets', extensions: ['xls', 'xlsx'] }], 47 | convertTo: 'pdf:calc_pdf_Export' 48 | }, 49 | "ods-to-pdf": { 50 | filters: [{ name: 'ODS Files', extensions: ['ods'] }], 51 | convertTo: 'pdf:calc_pdf_Export' 52 | }, 53 | "csv-to-excel": { 54 | filters: [{ name: 'CSV Files', extensions: ['csv'] }], 55 | convertTo: 'xlsx:calc8' 56 | }, 57 | "excel-to-ods": { 58 | filters: [{ name: 'Excel Sheets', extensions: ['xls', 'xlsx'] }], 59 | convertTo: 'ods:calc8' 60 | }, 61 | "ods-to-excel": { 62 | filters: [{ name: 'ODS Files', extensions: ['ods'] }], 63 | convertTo: 'xlsx:calc8' 64 | }, 65 | "csv-to-ods": { 66 | filters: [{ name: 'CSV Files', extensions: ['csv'] }], 67 | convertTo: 'ods:calc8' 68 | }, 69 | 70 | // 📽 Presentation Converter 71 | "ppt-to-pdf": { 72 | filters: [{ name: 'PowerPoint Presentations', extensions: ['ppt', 'pptx'] }], 73 | convertTo: 'pdf:impress_pdf_Export' 74 | }, 75 | "odp-to-pdf": { 76 | filters: [{ name: 'ODP Files', extensions: ['odp'] }], 77 | convertTo: 'pdf:impress_pdf_Export' 78 | }, 79 | "ppt-to-odp": { 80 | filters: [{ name: 'PowerPoint Presentations', extensions: ['ppt', 'pptx'] }], 81 | convertTo: 'odp:impress8' 82 | }, 83 | "odp-to-ppt": { 84 | filters: [{ name: 'ODP Files', extensions: ['odp'] }], 85 | convertTo: 'ppt:impress8' 86 | }, 87 | 88 | // 📄 PDF Converter 89 | "pdf-to-word": { 90 | filters: [{ name: 'PDF Files', extensions: ['pdf'] }], 91 | convertTo: 'docx:writer8' 92 | }, 93 | "pdf-to-excel": { 94 | filters: [{ name: 'PDF Files', extensions: ['pdf'] }], 95 | convertTo: 'xlsx:calc8' 96 | }, 97 | "pdf-to-ppt": { 98 | filters: [{ name: 'PDF Files', extensions: ['pdf'] }], 99 | convertTo: 'ppt:impress8' 100 | }, 101 | "pdf-to-image": { 102 | filters: [{ name: 'PDF Files', extensions: ['pdf'] }], 103 | convertTo: 'png' 104 | } 105 | }; -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at electronreactboilerplate@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/renderer/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/list.jsx: -------------------------------------------------------------------------------- 1 | export const PathRoute = [ 2 | { 3 | label: "Document Signature", 4 | items: [ 5 | { name: "PDF to Sign", path: "converter/pdf-to-sign", color: "blue", icon: icon("PS", "purple") }, 6 | ], 7 | }, 8 | // { 9 | // label: "Document Converter", 10 | // items: [ 11 | // { name: "Word to PDF", path: "converter/word-to-pdf", color: "blue", icon: icon("WP", "blue") }, 12 | // { name: "Text to PDF", path: "converter/txt-to-pdf", color: "yellow", icon: icon("TP", "yellow") }, 13 | // { name: "HTML to PDF", path: "converter/html-to-pdf", color: "orange", icon: icon("HP", "orange") }, 14 | // { name: "RTF to PDF", path: "converter/rtf-to-pdf", color: "rose", icon: icon("RP", "rose") }, 15 | // { name: "ODT to PDF", path: "converter/odt-to-pdf", color: "purple", icon: icon("OP", "purple") }, 16 | // // { name: "HTML to Word", path: "converter/html-to-docx", color: "blue", icon: icon("HW", "blue") }, 17 | // { name: "TXT to ODT", path: "converter/txt-to-odt", color: "purple", icon: icon("TO", "purple") }, 18 | // { name: "Word to ODT", path: "converter/word-to-odt", color: "purple", icon: icon("WO", "purple") }, 19 | // // { name: "ODT to Word", path: "converter/odt-to-word", color: "blue", icon: icon("OW", "blue") }, 20 | // ], 21 | // }, 22 | // { 23 | // label: "Spreadsheet Converter", 24 | // items: [ 25 | // { name: "Excel to PDF", path: "converter/excel-to-pdf", color: "green", icon: icon("EP", "green") }, 26 | // { name: "ODS to PDF", path: "converter/ods-to-pdf", color: "purple", icon: icon("OP", "purple") }, 27 | // // { name: "CSV to Excel", path: "converter/csv-to-excel", color: "teal", icon: icon("CE", "teal") }, 28 | // { name: "Excel to ODS", path: "converter/excel-to-ods", color: "green", icon: icon("EO", "green") }, 29 | // // { name: "ODS to Excel", path: "converter/ods-to-excel", color: "green", icon: icon("OE", "green") }, 30 | // { name: "CSV to ODS", path: "converter/csv-to-ods", color: "teal", icon: icon("CO", "teal") }, 31 | // ], 32 | // }, 33 | // { 34 | // label: "Presentation Converter", 35 | // items: [ 36 | // { name: "PowerPoint to PDF", path: "converter/ppt-to-pdf", color: "red", icon: icon("PP", "red") }, 37 | // { name: "ODP to PDF", path: "converter/odp-to-pdf", color: "purple", icon: icon("OP", "purple") }, 38 | // { name: "PowerPoint to ODP", path: "converter/ppt-to-odp", color: "purple", icon: icon("PO", "purple") }, 39 | // // { name: "ODP to PowerPoint", path: "converter/odp-to-ppt", color: "red", icon: icon("OP", "red") }, 40 | // ], 41 | // }, 42 | // { 43 | // label: "PDF Converter", 44 | // items: [ 45 | // { name: "PDF to Word", path: "converter/pdf-to-word", color: "blue", icon: icon("PW", "blue") }, 46 | // { name: "PDF to Excel", path: "converter/pdf-to-excel", color: "green", icon: icon("PE", "green") }, 47 | // { name: "PDF to PowerPoint", path: "converter/pdf-to-ppt", color: "red", icon: icon("PP", "red") }, 48 | // { name: "PDF to Image", path: "converter/pdf-to-image", color: "pink", icon: icon("PI", "pink") }, 49 | // ], 50 | // }, 51 | ]; 52 | 53 | function icon(letter, color) { 54 | const colorMap = { 55 | blue: "border-blue-500 text-blue-600 bg-blue-50", 56 | green: "border-green-500 text-green-600 bg-green-50", 57 | red: "border-red-500 text-red-600 bg-red-50", 58 | yellow: "border-yellow-500 text-yellow-600 bg-yellow-50", 59 | orange: "border-orange-500 text-orange-600 bg-orange-50", 60 | purple: "border-purple-500 text-purple-600 bg-purple-50", 61 | pink: "border-pink-500 text-pink-600 bg-pink-50", 62 | teal: "border-teal-500 text-teal-600 bg-teal-50", 63 | gray: "border-gray-400 text-gray-600 bg-gray-100", 64 | }; 65 | 66 | const colorClass = colorMap[color] || colorMap.gray; 67 | 68 | return ( 69 |
70 |
{letter}
71 |
72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Build config for electron renderer process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import HtmlWebpackPlugin from 'html-webpack-plugin'; 8 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; 11 | import { merge } from 'webpack-merge'; 12 | import TerserPlugin from 'terser-webpack-plugin'; 13 | import baseConfig from './webpack.config.base'; 14 | import webpackPaths from './webpack.paths'; 15 | import checkNodeEnv from '../scripts/check-node-env'; 16 | import deleteSourceMaps from '../scripts/delete-source-maps'; 17 | 18 | checkNodeEnv('production'); 19 | deleteSourceMaps(); 20 | 21 | const configuration: webpack.Configuration = { 22 | devtool: 'source-map', 23 | 24 | mode: 'production', 25 | 26 | target: ['web', 'electron-renderer'], 27 | 28 | entry: [path.join(webpackPaths.srcRendererPath, 'index.tsx')], 29 | 30 | output: { 31 | path: webpackPaths.distRendererPath, 32 | publicPath: './', 33 | filename: 'renderer.js', 34 | library: { 35 | type: 'umd', 36 | }, 37 | }, 38 | 39 | module: { 40 | rules: [ 41 | { 42 | test: /\.s?(a|c)ss$/, 43 | use: [ 44 | MiniCssExtractPlugin.loader, 45 | { 46 | loader: 'css-loader', 47 | options: { 48 | modules: true, 49 | sourceMap: true, 50 | importLoaders: 1, 51 | }, 52 | }, 53 | 'sass-loader', 54 | ], 55 | include: /\.module\.s?(c|a)ss$/, 56 | }, 57 | { 58 | test: /\.s?(a|c)ss$/, 59 | use: [MiniCssExtractPlugin.loader, 'css-loader', 'sass-loader', 'postcss-loader'], 60 | exclude: /\.module\.s?(c|a)ss$/, 61 | }, 62 | // Fonts 63 | { 64 | test: /\.(woff|woff2|eot|ttf|otf)$/i, 65 | type: 'asset/resource', 66 | }, 67 | // Images 68 | { 69 | test: /\.(png|jpg|jpeg|gif)$/i, 70 | type: 'asset/resource', 71 | }, 72 | // SVG 73 | { 74 | test: /\.svg$/, 75 | use: [ 76 | { 77 | loader: '@svgr/webpack', 78 | options: { 79 | prettier: false, 80 | svgo: false, 81 | svgoConfig: { 82 | plugins: [{ removeViewBox: false }], 83 | }, 84 | titleProp: true, 85 | ref: true, 86 | }, 87 | }, 88 | 'file-loader', 89 | ], 90 | }, 91 | ], 92 | }, 93 | 94 | optimization: { 95 | minimize: true, 96 | minimizer: [new TerserPlugin(), new CssMinimizerPlugin()], 97 | }, 98 | 99 | plugins: [ 100 | /** 101 | * Create global constants which can be configured at compile time. 102 | * 103 | * Useful for allowing different behaviour between development builds and 104 | * release builds 105 | * 106 | * NODE_ENV should be production so that modules do not perform certain 107 | * development checks 108 | */ 109 | new webpack.EnvironmentPlugin({ 110 | NODE_ENV: 'production', 111 | DEBUG_PROD: false, 112 | VITE_APP_NAME : "CLOUD" 113 | }), 114 | 115 | new MiniCssExtractPlugin({ 116 | filename: 'style.css', 117 | }), 118 | 119 | new BundleAnalyzerPlugin({ 120 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 121 | analyzerPort: 8889, 122 | }), 123 | 124 | new HtmlWebpackPlugin({ 125 | filename: 'index.html', 126 | template: path.join(webpackPaths.srcRendererPath, 'index.ejs'), 127 | minify: { 128 | collapseWhitespace: true, 129 | removeAttributeQuotes: true, 130 | removeComments: true, 131 | }, 132 | isBrowser: false, 133 | isDevelopment: false, 134 | }), 135 | 136 | new webpack.DefinePlugin({ 137 | 'process.type': '"renderer"', 138 | }), 139 | ], 140 | }; 141 | 142 | export default merge(baseConfig, configuration); 143 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/about/path.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { PathRoute } from "./list"; 3 | import { useRoute } from "../../../../hooks/useRouteProvider"; 4 | 5 | export default function PathUsers() { 6 | 7 | const { routes, path, setPath } = useRoute(); 8 | 9 | const [query, setQuery] = useState(""); 10 | 11 | const filteredRoutes = PathRoute.map(section => ({ 12 | ...section, 13 | items: section.items.filter(item => 14 | item.name.toLowerCase().includes(query.toLowerCase()) 15 | ), 16 | })).filter(section => section.items.length > 0); 17 | 18 | return ( 19 | <> 20 | 21 |
22 | 23 | 28 | 39 | 40 | 41 | setQuery(e.target.value)} 45 | className="w-full py-1.5 pl-10 pr-4 text-gray-700 bg-white border rounded-md 46 | dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 47 | focus:border-blue-400 dark:focus:border-blue-300 48 | focus:ring-blue-300 focus:ring-opacity-40 49 | focus:outline-none focus:ring" 50 | placeholder="Search" 51 | /> 52 |
53 | 54 | 88 | 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/path.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { PathRoute } from "./list"; 3 | import { useRoute } from "../../../../hooks/useRouteProvider"; 4 | 5 | export default function PathUsers() { 6 | 7 | const { routes, path, setPath } = useRoute(); 8 | 9 | const [query, setQuery] = useState(""); 10 | 11 | const filteredRoutes = PathRoute.map(section => ({ 12 | ...section, 13 | items: section.items.filter(item => 14 | item.name.toLowerCase().includes(query.toLowerCase()) 15 | ), 16 | })).filter(section => section.items.length > 0); 17 | 18 | return ( 19 | <> 20 | 21 |
22 | 23 | 28 | 39 | 40 | 41 | setQuery(e.target.value)} 45 | className="w-full py-1.5 pl-10 pr-4 text-gray-700 bg-white border rounded-md 46 | dark:bg-gray-900 dark:text-gray-300 dark:border-gray-600 47 | focus:border-blue-400 dark:focus:border-blue-300 48 | focus:ring-blue-300 focus:ring-opacity-40 49 | focus:outline-none focus:ring" 50 | placeholder="Search" 51 | /> 52 |
53 | 54 | 88 | 89 | ); 90 | } 91 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import useSidebar from "../../../hooks/useSidebar"; 3 | import { MainRoute } from "./routes"; 4 | import { useRoute } from "../../../hooks/useRouteProvider"; 5 | import { PathRoutes, PathComponents } from "./config"; 6 | import SkeletonText from "../../../components/skeleton/text"; 7 | import ComingSoon from "../../../components/error/comingsoon"; 8 | import NavDash from "../../../components/navbar/dash"; 9 | 10 | export default function Screen({ user, role }) { 11 | const { routes, setRoutes, path } = useRoute(); 12 | const { sidebarOpen, SidebarOverlay } = useSidebar(); 13 | 14 | const PathRoute = PathRoutes[routes]; 15 | const PathComponent = PathComponents[path]; 16 | 17 | return ( 18 |
19 | {/* Sidebar */} 20 |
21 | 63 |
64 | 65 |
66 |
67 | 68 |
69 |
70 | }> 71 | {PathComponent ? : } 72 | 73 |
74 |
75 | 76 | 77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /src/renderer/App.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @theme inline { 4 | --color-background: var(--background); 5 | --color-foreground: var(--foreground); 6 | --font-sans: var(--font-geist-sans); 7 | --font-mono: var(--font-geist-mono); 8 | --color-sidebar-ring: var(--sidebar-ring); 9 | --color-sidebar-border: var(--sidebar-border); 10 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 11 | --color-sidebar-accent: var(--sidebar-accent); 12 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 13 | --color-sidebar-primary: var(--sidebar-primary); 14 | --color-sidebar-foreground: var(--sidebar-foreground); 15 | --color-sidebar: var(--sidebar); 16 | --color-chart-5: var(--chart-5); 17 | --color-chart-4: var(--chart-4); 18 | --color-chart-3: var(--chart-3); 19 | --color-chart-2: var(--chart-2); 20 | --color-chart-1: var(--chart-1); 21 | --color-ring: var(--ring); 22 | --color-input: var(--input); 23 | --color-border: var(--border); 24 | --color-destructive: var(--destructive); 25 | --color-accent-foreground: var(--accent-foreground); 26 | --color-accent: var(--accent); 27 | --color-muted-foreground: var(--muted-foreground); 28 | --color-muted: var(--muted); 29 | --color-secondary-foreground: var(--secondary-foreground); 30 | --color-secondary: var(--secondary); 31 | --color-primary-foreground: var(--primary-foreground); 32 | --color-primary: var(--primary); 33 | --color-popover-foreground: var(--popover-foreground); 34 | --color-popover: var(--popover); 35 | --color-card-foreground: var(--card-foreground); 36 | --color-card: var(--card); 37 | --radius-sm: calc(var(--radius) - 4px); 38 | --radius-md: calc(var(--radius) - 2px); 39 | --radius-lg: var(--radius); 40 | --radius-xl: calc(var(--radius) + 4px); 41 | } 42 | 43 | @custom-variant dark (&:where(.dark, .dark *)); 44 | 45 | body { 46 | font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 47 | } 48 | 49 | :root { 50 | --radius: 0.625rem; 51 | --background: oklch(1 0 0); 52 | --foreground: oklch(0.145 0 0); 53 | --card: oklch(1 0 0); 54 | --card-foreground: oklch(0.145 0 0); 55 | --popover: oklch(1 0 0); 56 | --popover-foreground: oklch(0.145 0 0); 57 | --primary: oklch(0.205 0 0); 58 | --primary-foreground: oklch(0.985 0 0); 59 | --secondary: oklch(0.97 0 0); 60 | --secondary-foreground: oklch(0.205 0 0); 61 | --muted: oklch(0.97 0 0); 62 | --muted-foreground: oklch(0.556 0 0); 63 | --accent: oklch(0.97 0 0); 64 | --accent-foreground: oklch(0.205 0 0); 65 | --destructive: oklch(0.577 0.245 27.325); 66 | --border: oklch(0.922 0 0); 67 | --input: oklch(0.922 0 0); 68 | --ring: oklch(0.708 0 0); 69 | --chart-1: oklch(0.646 0.222 41.116); 70 | --chart-2: oklch(0.6 0.118 184.704); 71 | --chart-3: oklch(0.398 0.07 227.392); 72 | --chart-4: oklch(0.828 0.189 84.429); 73 | --chart-5: oklch(0.769 0.188 70.08); 74 | --sidebar: oklch(0.985 0 0); 75 | --sidebar-foreground: oklch(0.145 0 0); 76 | --sidebar-primary: oklch(0.205 0 0); 77 | --sidebar-primary-foreground: oklch(0.985 0 0); 78 | --sidebar-accent: oklch(0.97 0 0); 79 | --sidebar-accent-foreground: oklch(0.205 0 0); 80 | --sidebar-border: oklch(0.922 0 0); 81 | --sidebar-ring: oklch(0.708 0 0); 82 | } 83 | 84 | .dark { 85 | --background: #111827; 86 | --foreground: oklch(0.985 0 0); 87 | --card: oklch(0.205 0 0); 88 | --card-foreground: oklch(0.985 0 0); 89 | --popover: oklch(0.205 0 0); 90 | --popover-foreground: oklch(0.985 0 0); 91 | --primary: oklch(0.922 0 0); 92 | --primary-foreground: oklch(0.205 0 0); 93 | --secondary: oklch(0.269 0 0); 94 | --secondary-foreground: oklch(0.985 0 0); 95 | --muted: oklch(0.269 0 0); 96 | --muted-foreground: oklch(0.708 0 0); 97 | --accent: oklch(0.269 0 0); 98 | --accent-foreground: oklch(0.985 0 0); 99 | --destructive: oklch(0.704 0.191 22.216); 100 | --border: oklch(1 0 0 / 10%); 101 | --input: oklch(1 0 0 / 15%); 102 | --ring: oklch(0.556 0 0); 103 | --chart-1: oklch(0.488 0.243 264.376); 104 | --chart-2: oklch(0.696 0.17 162.48); 105 | --chart-3: oklch(0.769 0.188 70.08); 106 | --chart-4: oklch(0.627 0.265 303.9); 107 | --chart-5: oklch(0.645 0.246 16.439); 108 | --sidebar: oklch(0.205 0 0); 109 | --sidebar-foreground: oklch(0.985 0 0); 110 | --sidebar-primary: oklch(0.488 0.243 264.376); 111 | --sidebar-primary-foreground: oklch(0.985 0 0); 112 | --sidebar-accent: oklch(0.269 0 0); 113 | --sidebar-accent-foreground: oklch(0.985 0 0); 114 | --sidebar-border: oklch(1 0 0 / 10%); 115 | --sidebar-ring: oklch(0.556 0 0); 116 | } 117 | 118 | @layer base { 119 | * { 120 | @apply border-border outline-ring/50; 121 | } 122 | body { 123 | @apply bg-background text-foreground; 124 | } 125 | } -------------------------------------------------------------------------------- /src/renderer/components/web/hero.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import BgMeshGradient from "../background/meshGradient"; 3 | 4 | export default function WebHero() { 5 | return ( 6 | <> 7 | 8 |
9 |
10 |
11 |
12 |
13 |

14 | All-in-One Digital Tools
15 | for Everyday Needs 16 |

17 |

18 | A single platform with various online tools and service to simplify your daily tasks. 19 | Fast, secure, and accessible anytime, anywhere. 20 |

21 | 22 | 50 | 51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 | 72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/main/functions/postProcessPDF.tsx: -------------------------------------------------------------------------------- 1 | import { PDFDocument, rgb, degrees } from 'pdf-lib'; 2 | import fs from 'fs'; 3 | import path from 'path'; 4 | import { plainAddPlaceholder } from '@signpdf/placeholder-plain'; 5 | import { SignPdf } from '@signpdf/signpdf'; 6 | import { P12Signer } from '@signpdf/signer-p12'; 7 | import { decryptWithExpiry, encryptWithExpiry } from './postProcessSecure'; 8 | // import { addExpiryVisualBlock } from './postProcessPDFExpired'; 9 | 10 | export async function postProcessPDF( 11 | outputFile: any, 12 | options: any, 13 | placeholder: any, 14 | action: any, 15 | ) { 16 | try { 17 | const { 18 | watermark, 19 | signPdf, 20 | certPath, 21 | certPassword, 22 | pdf, 23 | folder, 24 | encryptToggle, 25 | encryptPassword, 26 | expiryDate, 27 | decrypt, 28 | } = options; 29 | 30 | const outputDir = path.dirname(outputFile); 31 | if (!fs.existsSync(outputDir)) { 32 | fs.mkdirSync(outputDir, { recursive: true }); 33 | } 34 | 35 | if (decrypt) { 36 | const decryptDir = path.join(outputDir, folder || 'decrypt'); 37 | if (!fs.existsSync(decryptDir)) { 38 | fs.mkdirSync(decryptDir, { recursive: true }); 39 | } 40 | 41 | const baseName = path.basename(outputFile, '.pdf'); 42 | const encryptedFile = outputFile; 43 | const decryptedFile = path.join(decryptDir, `${baseName}-decrypted.pdf`); 44 | 45 | decryptWithExpiry(encryptedFile, decryptedFile, encryptPassword); 46 | console.log(`PDF decrypted successfully: ${decryptedFile}`); 47 | 48 | return { 49 | output: decryptedFile, 50 | status: 'success', 51 | message: 'Successfully decrypted and expiry applied', 52 | }; 53 | } else { 54 | 55 | const pdfBytes = fs.readFileSync(outputFile); 56 | const pdfDoc = await PDFDocument.load(pdfBytes); 57 | 58 | if (watermark) { 59 | const pages = pdfDoc.getPages(); 60 | for (const page of pages) { 61 | const { width, height } = page.getSize(); 62 | page.drawText(watermark, { 63 | x: width / 2 - 120, 64 | y: height / 2, 65 | size: 48, 66 | opacity: 0.15, 67 | rotate: degrees(45), 68 | color: rgb(0.8, 0.1, 0.1), 69 | }); 70 | } 71 | } 72 | 73 | const newPdfBytes = await pdfDoc.save({ useObjectStreams: false }); 74 | let pdfBuffer; 75 | 76 | let tempFile; 77 | if (signPdf) { 78 | if (action === 'pdf-to-sign') { 79 | fs.writeFileSync(outputFile, newPdfBytes); 80 | pdfBuffer = fs.readFileSync(outputFile); 81 | } else { 82 | tempFile = outputFile.replace('.pdf', '-unsign.pdf'); 83 | fs.writeFileSync(tempFile, newPdfBytes); 84 | pdfBuffer = fs.readFileSync(tempFile); 85 | } 86 | } else { 87 | fs.writeFileSync(outputFile, newPdfBytes); 88 | pdfBuffer = fs.readFileSync(outputFile); 89 | } 90 | if (signPdf && certPath) { 91 | const pdfWithPlaceholder = plainAddPlaceholder({ 92 | pdfBuffer, 93 | ...placeholder, 94 | }); 95 | 96 | const p12Buffer = fs.readFileSync(certPath); 97 | const signer = new SignPdf(); 98 | const p12Signer = new P12Signer(p12Buffer, { 99 | passphrase: certPassword, 100 | }); 101 | 102 | const signedPdf = await signer.sign(pdfWithPlaceholder, p12Signer); 103 | 104 | const signedDir = path.join(outputDir, folder || 'signed'); 105 | if (!fs.existsSync(signedDir)) { 106 | fs.mkdirSync(signedDir, { recursive: true }); 107 | } 108 | 109 | const baseName = path.basename(outputFile, '.pdf'); 110 | const signedFile = path.join(signedDir, `${baseName}-signed.pdf`); 111 | 112 | fs.writeFileSync(signedFile, signedPdf); 113 | console.log(`PDF signed successfully: ${signedFile}`); 114 | 115 | if (encryptToggle) { 116 | const expiryFile = signedFile.replace('.pdf', '-expiry.enc'); 117 | const expiryDateConvert = new Date(expiryDate); 118 | 119 | encryptWithExpiry( 120 | signedFile, 121 | expiryFile, 122 | encryptPassword, 123 | expiryDateConvert, 124 | ); 125 | console.log(`PDF encrypted ${expiryDate}`); 126 | } 127 | 128 | return { 129 | output: signedFile, 130 | status: 'success', 131 | message: 'Successfully', 132 | }; 133 | } else { 134 | fs.writeFileSync(outputFile, pdfBuffer); 135 | 136 | return { 137 | output: outputFile, 138 | status: 'success', 139 | message: 'Successfully', 140 | }; 141 | } 142 | } 143 | } catch (err: any) { 144 | return { 145 | output: outputFile, 146 | status: 'error', 147 | message: err.message, 148 | }; 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/csv-to-ods/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'csv-to-ods', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | CSV to ODS 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/odp-to-ppt/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'odp-to-ppt', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | ODP to PPT 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/pdf-to-ppt/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'pdf-to-ppt', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | PDF to PPT 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/ppt-to-odp/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'ppt-to-odp', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | PPT to ODP 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/txt-to-odt/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'txt-to-odt', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | TXT to ODT 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/csv-to-excel/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'csv-to-excel', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | CSV to Excel 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/excel-to-ods/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'excel-to-ods', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | Excel to ODS 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/html-to-docx/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'html-to-docx', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | HTML to WORD 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/ods-to-excel/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'ods-to-excel', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | ODS to Excel 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/odt-to-word/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'odt-to-word', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | ODT to WORD 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | -------------------------------------------------------------------------------- /src/renderer/screens/dashboard/app/features/converter/pdf-to-excel/view/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useRef, useCallback } from 'react'; 2 | import { 3 | Upload, 4 | FileText, 5 | CheckCircle, 6 | X, 7 | Loader2, 8 | AlertTriangle, 9 | Settings, 10 | } from 'lucide-react'; 11 | import SkeletonText from '../../../../../../../components/skeleton/text'; 12 | import { ErrorLog } from '../../../../../../../components/error'; 13 | 14 | export default function Screen() { 15 | const [version, setVersion] = useState(''); 16 | const [files, setFiles] = useState([]); 17 | const [progress, setProgress] = useState(0); 18 | const [progressMessage, setProgressMessage] = useState(''); 19 | const [isConverting, setIsConverting] = useState(false); 20 | const [conversionResult, setConversionResult] = useState(null); 21 | 22 | const [certPath, setCertPath] = useState(null); 23 | const [certPassword, setCertPassword] = useState(''); 24 | const [watermark, setWatermark] = useState(''); 25 | const [signPdf, setSignPdf] = useState(false); 26 | 27 | const [reason, setReason] = useState(''); 28 | const [contactInfo, setContactInfo] = useState(''); 29 | const [companyName, setCompanyName] = useState(''); 30 | const [location, setLocation] = useState(''); 31 | 32 | const fileInputRef = useRef(null); 33 | 34 | // ambil versi LibreOffice 35 | useEffect(() => { 36 | window.electronAPI.getLibreVersion().then(setVersion).catch(console.error); 37 | 38 | // dengarkan progress dari main process 39 | window.electronAPI.onConvertProgress((data) => { 40 | setProgress(data.value); 41 | setProgressMessage(data.message); 42 | }); 43 | }, []); 44 | 45 | const handleUploadCert = async () => { 46 | const result = await window.electronAPI.selectCertFile(); 47 | if (result) setCertPath(result); 48 | }; 49 | 50 | const startConversion = async () => { 51 | setIsConverting(true); 52 | setProgress(0); 53 | setConversionResult(null); 54 | setProgressMessage(''); 55 | 56 | try { 57 | const results = await window.electronAPI.uploadFile({ 58 | action: 'pdf-to-excel', 59 | options: { 60 | watermark, 61 | signPdf, 62 | certPath, 63 | certPassword, 64 | pdf: false 65 | }, 66 | placeholder: { 67 | reason: reason || 'Document verification', 68 | contactInfo: contactInfo || 'info@yourcompany.com', 69 | name: companyName || 'Your Company Name', 70 | location: location || 'Indonesia', 71 | }, 72 | }); 73 | if (!results) return; 74 | 75 | setConversionResult(results); 76 | 77 | ErrorLog({ 78 | message : "Convert Successfully" 79 | }) 80 | } catch (err) { 81 | console.error(err); 82 | ErrorLog({ 83 | message : error.message 84 | }) 85 | } finally { 86 | setIsConverting(false); 87 | setProgress(100); 88 | } 89 | }; 90 | 91 | const resetAll = () => { 92 | setFiles([]); 93 | setProgress(0); 94 | setProgressMessage(''); 95 | setConversionResult(null); 96 | }; 97 | 98 | return ( 99 | <> 100 |
101 |
102 |
103 |
104 | Convert 105 |
106 | 107 |

108 | PDF to EXCEL 109 |

110 | 111 |

112 | Built on the{' '} 113 | 114 | Flamecore Cloud 115 | {' '} 116 | ecosystem — a conversion engine that gives you total freedom. No 117 | file size limits, no upload restrictions, and no internet 118 | dependency. Your documents stay private and secure because 119 | everything runs locally on your device. 120 |

121 | 122 | 128 |
129 |
130 | 131 | {conversionResult && ( 132 |
133 |

Conversion complete

134 | {conversionResult.map((item, index) => { 135 | return ( 136 |
137 |

138 | Input:{' '} 139 | {item.input} 140 |

141 |

142 | Output:{' '} 143 | {item.output} 144 |

145 |
146 | ); 147 | })} 148 | 154 |
155 | )} 156 | 157 | {isConverting && ( 158 |
159 |
160 |
161 | 162 |
163 |

{progressMessage && progressMessage}

164 |
165 |
166 | )} 167 |
168 | 169 | ); 170 | } 171 | --------------------------------------------------------------------------------