├── .env ├── .eslintignore ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public ├── icons │ ├── pwa-192x192.png │ └── pwa-512x512.png ├── netlify.toml ├── robots.txt └── vite.svg ├── script ├── obfuscate.js └── purgecss.js ├── src ├── App.tsx ├── assets │ ├── css │ │ ├── default │ │ │ ├── OverlayScrollbars.css │ │ │ ├── adminlte.css │ │ │ └── custom.css │ │ ├── home │ │ │ └── index.css │ │ ├── index.css │ │ └── minify │ │ │ ├── OverlayScrollbars.min.css │ │ │ ├── adminlte.min.css │ │ │ └── custom.min.css │ ├── img │ │ ├── AdminLTELogo.webp │ │ ├── blog-banner-1.jpg │ │ ├── blog-banner-2.jpg │ │ ├── blog-banner-3.jpg │ │ ├── features-img-1.png │ │ ├── features-img-2.png │ │ ├── hero-banner.png │ │ ├── imgProfile.avif │ │ ├── index.tsx │ │ ├── logo-footer.svg │ │ └── logo.svg │ ├── index.tsx │ └── react.svg ├── components │ ├── atom │ │ ├── Button.tsx │ │ ├── Card.tsx │ │ ├── Col.tsx │ │ ├── HeaderContent.tsx │ │ ├── PanelContent.tsx │ │ ├── Row.tsx │ │ ├── index.ts │ │ ├── modalGlobal.tsx │ │ └── tableMaster.tsx │ ├── index.ts │ └── molekul │ │ ├── content │ │ └── index.tsx │ │ ├── footer │ │ └── index.tsx │ │ ├── header │ │ └── index.tsx │ │ ├── index.ts │ │ └── sidebar │ │ ├── SidebarNavList.tsx │ │ └── index.tsx ├── interface │ ├── helper │ │ ├── index.ts │ │ ├── menu-interface.ts │ │ ├── router-interface.ts │ │ └── tabel-interface.ts │ ├── index.ts │ └── login │ │ └── index.ts ├── main.tsx ├── pages │ ├── dashboard │ │ └── index.tsx │ ├── home │ │ └── index.tsx │ ├── index.tsx │ ├── login │ │ ├── dto │ │ │ └── formLoginDto.ts │ │ ├── index.tsx │ │ └── validate │ │ │ └── index.jsx │ ├── pageNotFound │ │ └── index.tsx │ └── widgets │ │ └── index.tsx ├── reduxStore │ ├── actions │ │ ├── index.ts │ │ ├── theme │ │ │ ├── action.ts │ │ │ ├── index.ts │ │ │ └── type.ts │ │ └── utility │ │ │ ├── action.ts │ │ │ ├── index.ts │ │ │ └── type.ts │ ├── index.ts │ ├── recuers │ │ ├── index.ts │ │ ├── reducerTheme.ts │ │ └── reducerUtility.ts │ ├── rootState │ │ ├── index.ts │ │ ├── rootActions.ts │ │ └── rootReducers.ts │ └── store.ts ├── router │ ├── ProtectedRoute.tsx │ ├── index.ts │ ├── menu.tsx │ └── routes.tsx ├── utils │ ├── helper │ │ ├── Field.tsx │ │ ├── axios.ts │ │ ├── encrypt.tsx │ │ ├── function.tsx │ │ └── index.ts │ ├── index.ts │ └── package │ │ └── index.ts └── vite-env.d.ts ├── ss ├── Screenshot 2023-10-03 at 09.45.09.png ├── Screenshot 2023-10-03 at 15.35.27.png ├── Screenshot 2023-10-03 at 15.35.35.png ├── Screenshot 2023-10-03 at 15.35.45.png └── Screenshot 2023-10-03 at 15.36.14.png ├── tsconfig.json ├── tsconfig.node.json ├── vite.config.ts └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | VITE_APP_KEY=base64:lh9o2e1xp+BvaYNYJ92uJG94ereCF5E5/S4UE2c94Ss= 2 | VITE_APP_BE=http://localhost:8000/api 3 | VITE_APP_SECRETKEY=b3r4sput1h 4 | VITE_APP_MODE=1 -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | dist 4 | dist-ssr 5 | *.local 6 | node_modules/* 7 | .eslintrc.cjs 8 | vite.config.ts 9 | script/* -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | "eslint:recommended", 6 | "plugin:@typescript-eslint/recommended", 7 | "plugin:react-hooks/recommended", 8 | "plugin:prettier/recommended", 9 | ], 10 | ignorePatterns: ["dist", ".eslintrc.cjs"], 11 | parser: "@typescript-eslint/parser", 12 | plugins: ["react-refresh", "@typescript-eslint", "prettier"], 13 | rules: { 14 | "@typescript-eslint/no-explicit-any": "off", 15 | "react-refresh/only-export-components": [ 16 | "off", 17 | { allowConstantExport: true }, 18 | ], 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | build 11 | node_modules 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite + Redux + PWA 2 | 3 | Demo Link https://adminlte-base.netlify.app/ 4 | 5 | #Tampilan Home 6 | Made In By : https://github.com/codewithsadee/landio 7 | 8 | 9 | #Tampilan Login 10 | Made In By : https://github.com/ColorlibHQ/AdminLTE 11 | 12 | 13 | #Tampilan Admin 14 | Made In By : https://github.com/ColorlibHQ/AdminLTE 15 | 16 | 17 | #Hasil Lighthouse 18 | 19 | 20 | #Hasil obfuscate js 21 | 22 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Vite + React + TS 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "adminltets", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build && npm run obfuscate", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview", 11 | "obfuscate": "node ./script/obfuscate.js", 12 | "purgecss": "node ./script/purgecss.js" 13 | }, 14 | "dependencies": { 15 | "ant-table-extensions": "^2.0.0", 16 | "antd": "^5.12.7", 17 | "axios": "^1.6.4", 18 | "crypto-js": "^4.1.1", 19 | "react": "^18.2.0", 20 | "react-bootstrap": "^2.9.2", 21 | "react-dom": "^18.2.0", 22 | "react-number-format": "^5.3.1", 23 | "react-redux": "^8.1.3", 24 | "react-router-dom": "^6.22.1", 25 | "react-select": "^5.8.0", 26 | "react-toastify": "^9.1.3", 27 | "redux": "^4.2.1", 28 | "redux-devtools-extension": "^2.13.9", 29 | "redux-form": "^8.3.10", 30 | "redux-persist": "^6.0.0", 31 | "redux-thunk": "^2.4.2" 32 | }, 33 | "devDependencies": { 34 | "@fortawesome/fontawesome-free": "^6.4.0", 35 | "@types/crypto-js": "^4.1.2", 36 | "@types/node": "^20.7.1", 37 | "@types/react": "^18.2.15", 38 | "@types/react-dom": "^18.2.7", 39 | "@types/react-router-dom": "^5.3.3", 40 | "@types/react-select": "^5.0.1", 41 | "@types/redux": "^3.6.0", 42 | "@types/redux-form": "^8.3.5", 43 | "@types/redux-thunk": "^2.1.0", 44 | "@typescript-eslint/eslint-plugin": "^6.0.0", 45 | "@typescript-eslint/parser": "^6.0.0", 46 | "@vitejs/plugin-react-swc": "^3.5.0", 47 | "eslint": "^8.45.0", 48 | "eslint-config-prettier": "^9.0.0", 49 | "eslint-plugin-prettier": "^5.0.0", 50 | "eslint-plugin-react-hooks": "^4.6.0", 51 | "eslint-plugin-react-refresh": "^0.4.3", 52 | "javascript-obfuscator": "^4.1.0", 53 | "path": "^0.12.7", 54 | "prettier": "^3.0.3", 55 | "purgecss": "^5.0.0", 56 | "react-loading-skeleton": "^3.4.0", 57 | "typescript": "^5.0.2", 58 | "vite": "^5.0.12", 59 | "vite-plugin-compression": "^0.5.1", 60 | "vite-plugin-compression2": "^0.10.5", 61 | "vite-plugin-html": "^3.2.1", 62 | "vite-plugin-pwa": "^0.17.4" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /public/icons/pwa-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samsularifin05/base-admin-lte-react-ts-vitejs/03e690d8f7517b606e7216eb83d85aceedbf19fe/public/icons/pwa-192x192.png -------------------------------------------------------------------------------- /public/icons/pwa-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samsularifin05/base-admin-lte-react-ts-vitejs/03e690d8f7517b606e7216eb83d85aceedbf19fe/public/icons/pwa-512x512.png -------------------------------------------------------------------------------- /public/netlify.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | publish = "dist" 3 | command = "pnpm run build" 4 | 5 | [[redirects]] 6 | from = "/*" 7 | to = "/index.html" 8 | status = 200 9 | 10 | [[headers]] 11 | for = "/manifest.webmanifest" 12 | [headers.values] 13 | Content-Type = "application/manifest+json" 14 | 15 | [[headers]] 16 | for = "/assets/*" 17 | [headers.values] 18 | cache-control = ''' 19 | max-age=31536000, 20 | immutable 21 | ''' -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Allow: / -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /script/obfuscate.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import path from "path"; 3 | import JavaScriptObfuscator from "javascript-obfuscator"; 4 | 5 | const __dirname = path.dirname(new URL(import.meta.url).pathname); 6 | 7 | const settings = { 8 | compact: true, 9 | controlFlowFlattening: true, 10 | controlFlowFlatteningThreshold: 0.75, 11 | numbersToExpressions: true, 12 | simplify: true, 13 | shuffleStringArray: true, 14 | splitStrings: true, 15 | stringArray: true, 16 | // stringArrayEncoding: "base64", 17 | stringArrayThreshold: 0.75, 18 | transformObjectKeys: true, 19 | unicodeEscapeSequence: true, 20 | }; 21 | 22 | function obfuscateDir(dirPath) { 23 | let dirents = fs.readdirSync(dirPath, { 24 | encoding: "utf8", 25 | withFileTypes: true, 26 | }); 27 | for (let i = 0; i < dirents.length; i++) { 28 | let dirent = dirents[i]; 29 | if (dirent.isDirectory()) { 30 | obfuscateDir(path.join(dirPath, dirent.name)); 31 | continue; 32 | } 33 | if (path.extname(dirent.name) !== ".js") continue; 34 | const filePath = path.join(dirPath, dirent.name); 35 | const content = fs.readFileSync(filePath, { encoding: "utf8" }); 36 | const obfuscator = JavaScriptObfuscator.obfuscate(content, settings); 37 | const obfuscatedCode = obfuscator.getObfuscatedCode(); 38 | 39 | fs.writeFileSync(filePath, obfuscatedCode, { 40 | encoding: "utf8", 41 | flag: "w+", 42 | }); 43 | console.log("🤖 🤖 🤖 Done!"); 44 | } 45 | } 46 | 47 | obfuscateDir(path.join(__dirname, "../build")); 48 | -------------------------------------------------------------------------------- /script/purgecss.js: -------------------------------------------------------------------------------- 1 | const { exec } = require("child_process"); 2 | const fs = require("fs"); 3 | const path = require("path"); 4 | 5 | // Function to get file size in kilobytes 6 | function getFilesizeInKiloBytes(filename) { 7 | const stats = fs.statSync(filename); 8 | return (stats.size / 1024).toFixed(2) + "kb"; 9 | } 10 | 11 | // Function to get files with a specific extension from a directory 12 | function getFilesFromPath(dir, extension) { 13 | const files = fs.readdirSync(dir); 14 | return files.filter((e) => path.extname(e).toLowerCase() === extension); 15 | } 16 | 17 | // Define the CSS files and initialize data array 18 | const cssDirectory = "./build/assets/css/"; 19 | const cssFiles = getFilesFromPath(cssDirectory, ".css"); 20 | const data = []; 21 | 22 | if (!cssFiles || cssFiles.length === 0) { 23 | console.log("Cannot find CSS files to purge."); 24 | return; 25 | } 26 | 27 | // Loop through CSS files to collect original sizes 28 | for (const file of cssFiles) { 29 | const originalSize = getFilesizeInKiloBytes(path.join(cssDirectory, file)); 30 | data.push({ file, originalSize, newSize: "" }); 31 | } 32 | 33 | console.log("Running PurgeCSS..."); 34 | 35 | // Execute PurgeCSS 36 | exec( 37 | `./node_modules/purgecss/bin/purgecss.js -css ${cssDirectory}/*.css --content build/index.html build/assets/js/*.js -o ${cssDirectory}`, 38 | function (error, stdout, stderr) { 39 | if (error) { 40 | console.error("Error running PurgeCSS:", error); 41 | return; 42 | } 43 | 44 | console.log("PurgeCSS done"); 45 | 46 | // Update data with new sizes 47 | for (const d of data) { 48 | d.newSize = getFilesizeInKiloBytes(path.join(cssDirectory, d.file)); 49 | } 50 | 51 | console.table(data); 52 | } 53 | ); 54 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Content, Footer, Header, Sidebar } from "./components"; 2 | import { 3 | addWindowClass, 4 | calculateWindowSize, // Assuming this is expensive 5 | removeWindowClass, 6 | useWindowSize, 7 | useDispatch, 8 | useEffect, 9 | LoadingContent, 10 | } from "./utils"; 11 | import { 12 | AppDispatch, 13 | themesActions, 14 | useAppSelector, 15 | utilityActions, 16 | } from "./reduxStore"; 17 | import React, { useMemo } from "react"; 18 | 19 | const App = () => { 20 | const theme = useAppSelector((state) => state.theme); 21 | const utility = useAppSelector((state) => state.utility); 22 | 23 | const dispatch = useDispatch(); 24 | const windowSize = useWindowSize(); 25 | 26 | const memoizedWindowSize = useMemo( 27 | () => calculateWindowSize(windowSize.width), 28 | [windowSize.width] 29 | ); 30 | 31 | const handleToggleMenuSidebar = () => { 32 | dispatch(themesActions.setSidebarToggle(!theme.getSidebarToggle)); 33 | }; 34 | 35 | useEffect(() => { 36 | removeWindowClass("sidebar-closed"); 37 | removeWindowClass("sidebar-collapse"); 38 | removeWindowClass("sidebar-open"); 39 | 40 | // Use the memoized window size 41 | const size = memoizedWindowSize; 42 | 43 | if (utility.getScreenSize !== size) { 44 | dispatch(utilityActions.setScreenSize(size)); 45 | } 46 | 47 | if (theme.getSidebarToggle && utility.getScreenSize === "lg") { 48 | addWindowClass("sidebar-collapse"); 49 | } else if (theme.getSidebarToggle && utility.getScreenSize === "xs") { 50 | addWindowClass("sidebar-open"); 51 | } else if (!theme.getSidebarToggle && utility.getScreenSize !== "lg") { 52 | addWindowClass("sidebar-closed"); 53 | addWindowClass("sidebar-collapse"); 54 | } 55 | }, [ 56 | dispatch, 57 | memoizedWindowSize, 58 | theme.getSidebarToggle, 59 | utility.getScreenSize, 60 | ]); 61 | 62 | return ( 63 | 64 |
65 | {utility.getIsLogin ? ( 66 | <> 67 | {theme.handleSetPageHeader &&
} 68 | {theme.handleSetPageSidebar && } 69 | 70 | {theme.handleSetFooter &&
} 71 | 72 | ) : ( 73 | 74 | )} 75 |
76 | 77 |