├── src ├── vite-env.d.ts ├── index.css ├── types │ ├── accountType.ts │ └── userType.ts ├── page.tsx ├── main.tsx ├── components │ ├── Logo.tsx │ ├── SearchUser.tsx │ ├── Footer.tsx │ ├── Navbar.tsx │ ├── ToogleNightMode.tsx │ ├── EditUser.tsx │ ├── FilterUser.tsx │ ├── AddUser.tsx │ └── UserList.tsx ├── middleware │ └── ProtectedRoute.tsx ├── controllers │ ├── AccountController.ts │ └── UserControllers.ts ├── hooks │ └── useTheme.tsx ├── Layout.tsx ├── assets │ └── react.svg └── pages │ ├── SettingsProfile.tsx │ └── Login.tsx ├── bun.lockb ├── postcss.config.js ├── tsconfig.json ├── vite.config.ts ├── tailwind.config.js ├── .gitignore ├── index.html ├── tsconfig.node.json ├── tsconfig.app.json ├── eslint.config.js ├── package.json ├── public └── vite.svg └── README.md /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rendy278/RenLiblary/HEAD/bun.lockb -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/types/accountType.ts: -------------------------------------------------------------------------------- 1 | export interface Account { 2 | id: number; 3 | username: string; 4 | email: string; 5 | password: string; 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [], 3 | "references": [ 4 | { "path": "./tsconfig.app.json" }, 5 | { "path": "./tsconfig.node.json" } 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/types/userType.ts: -------------------------------------------------------------------------------- 1 | export interface User { 2 | id: number; 3 | name: string; 4 | address: string; 5 | phone: number; 6 | bookTitle: string; 7 | loadDate: string; 8 | returnDate: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/page.tsx: -------------------------------------------------------------------------------- 1 | import UserList from "./components/UserList"; 2 | 3 | const App = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | }; 10 | 11 | export default App; 12 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | export default { 3 | content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], 4 | darkMode: "class", 5 | theme: { 6 | extend: {}, 7 | }, 8 | plugins: [], 9 | }; 10 | -------------------------------------------------------------------------------- /src/main.tsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | import "./index.css"; 4 | import Layout from "./Layout.tsx"; // Layout utama yang mengatur navigasi 5 | 6 | createRoot(document.getElementById("root")!).render( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /.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 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /src/components/Logo.tsx: -------------------------------------------------------------------------------- 1 | import { BiBook } from "react-icons/bi"; 2 | import { Link } from "react-router-dom"; 3 | 4 | const Logo = () => { 5 | return ( 6 | 7 | 8 |

RenLiblary

9 | 10 | ); 11 | }; 12 | 13 | export default Logo; 14 | -------------------------------------------------------------------------------- /src/middleware/ProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import { Navigate } from "react-router-dom"; 3 | 4 | const ProtectedRoute = ({ children }: { children: ReactNode }) => { 5 | const isLoggedIn = localStorage.getItem("isLoggedIn") === "true"; 6 | return isLoggedIn ? children : ; 7 | }; 8 | 9 | export default ProtectedRoute; 10 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Crud Local Storange Test Intern 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/components/SearchUser.tsx: -------------------------------------------------------------------------------- 1 | interface SearchUserProps { 2 | query: string; 3 | onSearch: (e: React.ChangeEvent) => void; 4 | } 5 | 6 | const SearchUser: React.FC = ({ query, onSearch }) => { 7 | return ( 8 |
9 | 16 |
17 | ); 18 | }; 19 | 20 | export default SearchUser; 21 | -------------------------------------------------------------------------------- /tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", 4 | "target": "ES2022", 5 | "lib": ["ES2023"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | 9 | /* Bundler mode */ 10 | "moduleResolution": "bundler", 11 | "allowImportingTsExtensions": true, 12 | "isolatedModules": true, 13 | "moduleDetection": "force", 14 | "noEmit": true, 15 | 16 | /* Linting */ 17 | "strict": true, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": true, 20 | "noFallthroughCasesInSwitch": true, 21 | "noUncheckedSideEffectImports": true 22 | }, 23 | "include": ["vite.config.ts"] 24 | } 25 | -------------------------------------------------------------------------------- /src/controllers/AccountController.ts: -------------------------------------------------------------------------------- 1 | import { Account } from "../types/accountType"; 2 | 3 | const defaultAccount: Account = { 4 | id: 1, 5 | username: "Rendy Yoshizawa", 6 | email: "rendyyoshizawa@gmail.com", 7 | password: "rendy123", 8 | }; 9 | 10 | export const getAccount = (): Account => { 11 | const account = localStorage.getItem("account"); 12 | return account ? JSON.parse(account) : defaultAccount; 13 | }; 14 | 15 | const saveAccount = (account: Account): void => { 16 | localStorage.setItem("account", JSON.stringify(account)); 17 | }; 18 | 19 | export const editAccount = (updatedAccount: Partial): void => { 20 | const account = getAccount(); 21 | const newAccount = { ...account, ...updatedAccount }; 22 | saveAccount(newAccount); 23 | }; 24 | -------------------------------------------------------------------------------- /tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", 4 | "target": "ES2020", 5 | "useDefineForClassFields": true, 6 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 7 | "module": "ESNext", 8 | "skipLibCheck": true, 9 | 10 | /* Bundler mode */ 11 | "moduleResolution": "bundler", 12 | "allowImportingTsExtensions": true, 13 | "isolatedModules": true, 14 | "moduleDetection": "force", 15 | "noEmit": true, 16 | "jsx": "react-jsx", 17 | 18 | /* Linting */ 19 | "strict": true, 20 | "noUnusedLocals": true, 21 | "noUnusedParameters": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "noUncheckedSideEffectImports": true 24 | }, 25 | "include": ["src"] 26 | } 27 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import reactHooks from 'eslint-plugin-react-hooks' 4 | import reactRefresh from 'eslint-plugin-react-refresh' 5 | import tseslint from 'typescript-eslint' 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ) 29 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Logo from "./Logo"; 3 | 4 | const Footer: React.FC = () => { 5 | const currentYear = new Date().getFullYear(); 6 | 7 | return ( 8 |
9 |
10 |
11 | 12 |

rendyyoshizawa@gmail.com

13 |
14 |
15 |

16 | © {currentYear} Rendev. All rights reserved. 17 |

18 |
19 |
20 |
21 | ); 22 | }; 23 | 24 | export default Footer; 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "testinternpaid", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc -b && vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "react": "^18.3.1", 14 | "react-dom": "^18.3.1", 15 | "react-icons": "^5.4.0", 16 | "react-router-dom": "^7.1.1" 17 | }, 18 | "devDependencies": { 19 | "@eslint/js": "^9.17.0", 20 | "@types/react": "^18.3.18", 21 | "@types/react-dom": "^18.3.5", 22 | "@vitejs/plugin-react": "^4.3.4", 23 | "autoprefixer": "^10.4.20", 24 | "eslint": "^9.17.0", 25 | "eslint-plugin-react-hooks": "^5.0.0", 26 | "eslint-plugin-react-refresh": "^0.4.16", 27 | "globals": "^15.14.0", 28 | "postcss": "^8.4.49", 29 | "tailwindcss": "^3.4.17", 30 | "typescript": "~5.6.2", 31 | "typescript-eslint": "^8.18.2", 32 | "vite": "^6.0.5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/hooks/useTheme.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createContext, 3 | useState, 4 | useEffect, 5 | useContext, 6 | ReactNode, 7 | } from "react"; 8 | 9 | interface ThemeContextProps { 10 | isNightMode: boolean; 11 | toggleNightMode: () => void; 12 | } 13 | 14 | const ThemeContext = createContext(undefined); 15 | 16 | export const useTheme = () => { 17 | const context = useContext(ThemeContext); 18 | if (!context) { 19 | throw new Error("useTheme must be used within a ThemeProvider"); 20 | } 21 | return context; 22 | }; 23 | 24 | interface ThemeProviderProps { 25 | children: ReactNode; 26 | } 27 | 28 | export const ThemeProvider = ({ children }: ThemeProviderProps) => { 29 | const [isNightMode, setIsNightMode] = useState(() => { 30 | const storedValue = localStorage.getItem("isNightMode"); 31 | return storedValue ? JSON.parse(storedValue) : false; 32 | }); 33 | 34 | useEffect(() => { 35 | localStorage.setItem("isNightMode", JSON.stringify(isNightMode)); 36 | document.documentElement.classList.toggle("dark", isNightMode); 37 | }, [isNightMode]); 38 | 39 | const toggleNightMode = () => { 40 | setIsNightMode((prevMode) => !prevMode); 41 | }; 42 | 43 | return ( 44 | 45 | {children} 46 | 47 | ); 48 | }; 49 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/controllers/UserControllers.ts: -------------------------------------------------------------------------------- 1 | import { User } from "../types/userType"; 2 | 3 | const getUsers = (): User[] => { 4 | const users = localStorage.getItem("users"); 5 | return users ? JSON.parse(users) : []; 6 | }; 7 | 8 | export const addUser = (user: User): void => { 9 | const users = getUsers(); 10 | users.push(user); 11 | localStorage.setItem("users", JSON.stringify(users)); 12 | }; 13 | 14 | export const editUser = (updatedUser: User): void => { 15 | const users = getUsers().map((user) => 16 | user.id === updatedUser.id ? updatedUser : user 17 | ); 18 | localStorage.setItem("users", JSON.stringify(users)); 19 | }; 20 | 21 | export const deleteUser = (id: number): void => { 22 | const users = getUsers().filter((user) => user.id !== id); 23 | localStorage.setItem("users", JSON.stringify(users)); 24 | }; 25 | 26 | export const searchUsers = (query: string): User[] => { 27 | const users = getUsers(); 28 | return users.filter( 29 | (user) => 30 | user.name.toLowerCase().includes(query.toLowerCase()) || 31 | user.address.toLowerCase().includes(query.toLowerCase()) || 32 | user.bookTitle.toLowerCase().includes(query.toLowerCase()) 33 | ); 34 | }; 35 | 36 | export const filterUsersByDate = ( 37 | startDate: string, 38 | endDate: string 39 | ): User[] => { 40 | const users = getUsers(); 41 | const start = startDate ? new Date(startDate) : null; 42 | const end = endDate ? new Date(endDate) : null; 43 | 44 | return users.filter((user) => { 45 | const loadDate = new Date(user.loadDate); 46 | const returnDate = new Date(user.returnDate); 47 | 48 | const isInRange = 49 | (start ? loadDate >= start : true) && (end ? returnDate <= end : true); 50 | return isInRange; 51 | }); 52 | }; 53 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default tseslint.config({ 18 | languageOptions: { 19 | // other options... 20 | parserOptions: { 21 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }) 26 | ``` 27 | 28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` 29 | - Optionally add `...tseslint.configs.stylisticTypeChecked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: 31 | 32 | ```js 33 | // eslint.config.js 34 | import react from 'eslint-plugin-react' 35 | 36 | export default tseslint.config({ 37 | // Set the react version 38 | settings: { react: { version: '18.3' } }, 39 | plugins: { 40 | // Add the react plugin 41 | react, 42 | }, 43 | rules: { 44 | // other rules... 45 | // Enable its recommended rules 46 | ...react.configs.recommended.rules, 47 | ...react.configs['jsx-runtime'].rules, 48 | }, 49 | }) 50 | ``` 51 | -------------------------------------------------------------------------------- /src/Layout.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import Footer from "./components/Footer"; 3 | import Navbar from "./components/Navbar"; 4 | import { 5 | createBrowserRouter, 6 | createRoutesFromElements, 7 | Route, 8 | RouterProvider, 9 | Navigate, 10 | Outlet, 11 | } from "react-router-dom"; 12 | import Login from "./pages/Login"; 13 | import App from "./page"; 14 | import ProtectedRoute from "./middleware/ProtectedRoute"; 15 | import { ThemeProvider } from "./hooks/useTheme"; 16 | import SettingsProfile from "./pages/SettingsProfile"; 17 | 18 | const MainLayout = ({ handleLogout }: { handleLogout: () => void }) => ( 19 | <> 20 | 21 | 22 |