├── .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 |