matarial-init 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # matarial-init
2
+
3
+ An NPM package that automatically installs the full `src` folder (featuring React Context dynamic workflow, public placeholder API integration, Library Management system, and interactive Dashboard) into any React project.
4
+
5
+ ---
6
+
7
+ ## 🚀 Quick Start
8
+
9
+ ### Option 1: Automatic installation via `npm install`
10
+ Run inside your React project:
11
+ ```bash
12
+ npm install matarial-init
13
+ ```
14
+ During installation, the `postinstall` script will automatically copy the full `src/` directory into your project root.
15
+
16
+ ---
17
+
18
+ ### Option 2: Run via `npx`
19
+ You can also run the initialization script directly anytime:
20
+ ```bash
21
+ npx matarial-init
22
+ ```
23
+
24
+ ---
25
+
26
+ ## 📦 What gets installed in `src/`
27
+
28
+ - **`src/context/`**:
29
+ - `dataContext.js`: `DataContext` declaration & default public placeholder API fetch handler (`https://jsonplaceholder.typicode.com`).
30
+ - `DataContext.jsx`: `DataProvider` component wrapping your React app.
31
+ - `useData.jsx`: Dynamic workflow hook supporting fetching, caching, loading/error states, and mutations (`addData`, `updateData`, `deleteData`, `refresh`).
32
+ - **`src/pages/`**:
33
+ - `DashBoard.jsx`: Dynamic Dashboard with live public placeholder API integration, resource tabs (Posts, Todos, Users), metrics cards, search filter, pagination, record modals, and deletion.
34
+ - `Books.jsx`, `Members.jsx`, `MyBooks.jsx`, `Penalty.jsx`, `Home.jsx`, `Signin.jsx`, `SignUp.jsx`.
35
+ - **`src/components/`**:
36
+ - `Table.jsx`, `Modal.jsx`, `Button.jsx`, `Input.jsx`, `Header.jsx`, `Sidebar.jsx`, `Navbar.jsx`.
37
+ - **`src/layout/`**: `DashboardLayout.jsx`, `PublicLayout.jsx`.
38
+ - **`src/routes/`**: `ProtectedRoute.jsx`, `PublicRoute.jsx`.
39
+ - **`src/validation/`**: Yup validation schemas.
40
+ - **`src/constants/`**: Permissions and storage keys.
41
+ - **`src/uttils/`**: Local persistence helpers.
42
+
43
+ ---
44
+
45
+ ## ⚙️ Peer Dependencies
46
+ Ensure your project has the required dependencies:
47
+ ```bash
48
+ npm install react react-dom react-router-dom formik yup react-toastify
49
+ ```
50
+ And include Tailwind CSS or styling support (e.g. in your `index.html` or CSS):
51
+ ```html
52
+ <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
53
+ ```
54
+
55
+ ---
56
+
57
+ ## 🚢 Publishing to NPM
58
+
59
+ To publish this package to NPM:
60
+ ```bash
61
+ npm login
62
+ npm publish --access public
63
+ ```
package/bin/cli.js ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { installSrc } from "./copy-src.js"
4
+
5
+ console.log("\x1b[36m🚀 Initializing matarial-init...\x1b[0m")
6
+ const targetDir = process.cwd()
7
+ installSrc(targetDir)
@@ -0,0 +1,86 @@
1
+ import fs from "fs"
2
+ import path from "path"
3
+ import { fileURLToPath } from "url"
4
+
5
+ const __filename = fileURLToPath(import.meta.url)
6
+ const __dirname = path.dirname(__filename)
7
+
8
+ /**
9
+ * Recursively copies all files and directories from source to destination.
10
+ */
11
+ export function copyFolderSync(from, to) {
12
+ if (!fs.existsSync(to)) {
13
+ fs.mkdirSync(to, { recursive: true })
14
+ }
15
+
16
+ const entries = fs.readdirSync(from, { withFileTypes: true })
17
+
18
+ for (const entry of entries) {
19
+ const srcPath = path.join(from, entry.name)
20
+ const destPath = path.join(to, entry.name)
21
+
22
+ if (entry.isDirectory()) {
23
+ copyFolderSync(srcPath, destPath)
24
+ } else {
25
+ fs.copyFileSync(srcPath, destPath)
26
+ }
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Detects the root directory of the consumer project.
32
+ */
33
+ export function detectConsumerDir() {
34
+ const packageDir = path.resolve(__dirname, "..")
35
+
36
+ // Case 1: If installed inside node_modules, find the parent project root
37
+ const parts = packageDir.split(path.sep)
38
+ const nmIndex = parts.lastIndexOf("node_modules")
39
+ if (nmIndex > 0) {
40
+ const consumerRoot = parts.slice(0, nmIndex).join(path.sep)
41
+ if (consumerRoot && fs.existsSync(consumerRoot)) {
42
+ return consumerRoot
43
+ }
44
+ }
45
+
46
+ // Case 2: If invoked via npm install, npm sets INIT_CWD to the invoking directory
47
+ if (process.env.INIT_CWD) {
48
+ const initCwd = path.resolve(process.env.INIT_CWD)
49
+ if (initCwd !== packageDir) {
50
+ return initCwd
51
+ }
52
+ }
53
+
54
+ // Case 3: Direct CLI execution (e.g. npx matarial-init)
55
+ return process.cwd()
56
+ }
57
+
58
+ /**
59
+ * Installs the package's src directory into the target project directory.
60
+ */
61
+ export function installSrc(targetBaseDir = null) {
62
+ const packageDir = path.resolve(__dirname, "..")
63
+ const resolvedTarget = path.resolve(targetBaseDir || detectConsumerDir())
64
+
65
+ // Skip if running within the package's own root during local development or build
66
+ if (resolvedTarget === packageDir) {
67
+ return
68
+ }
69
+
70
+ const sourceSrc = path.join(packageDir, "src")
71
+ const targetSrc = path.join(resolvedTarget, "src")
72
+
73
+ if (!fs.existsSync(sourceSrc)) {
74
+ console.error(`\x1b[31m[matarial-init] Error: Source 'src' directory not found at: ${sourceSrc}\x1b[0m`)
75
+ return
76
+ }
77
+
78
+ try {
79
+ console.log(`\x1b[36m[matarial-init] Installing 'src' folder into: ${targetSrc}...\x1b[0m`)
80
+ copyFolderSync(sourceSrc, targetSrc)
81
+ console.log(`\x1b[32m✔ [matarial-init] Successfully installed 'src' folder into your project!\x1b[0m`)
82
+ console.log(`\x1b[35m[matarial-init] Included: DataContext, useData, Dashboard, Components, Routes, Layout, and Utils.\x1b[0m`)
83
+ } catch (err) {
84
+ console.error(`\x1b[31m[matarial-init] Failed to copy 'src' folder: ${err.message}\x1b[0m`)
85
+ }
86
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { installSrc } from "./copy-src.js"
4
+
5
+ // Automatically detect consumer project root and install the src folder
6
+ installSrc()
package/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { DataContext, defaultSource } from "./src/context/dataContext.js"
2
+ export { DataProvider } from "./src/context/DataContext.jsx"
3
+ export { useData } from "./src/context/useData.jsx"
4
+ export { default as Dashboard } from "./src/pages/DashBoard.jsx"
5
+ export { default as Books } from "./src/pages/Books.jsx"
6
+ export { default as Members } from "./src/pages/Members.jsx"
7
+ export { default as MyBooks } from "./src/pages/MyBooks.jsx"
8
+ export { default as SignIn } from "./src/pages/Signin.jsx"
9
+ export { default as SignUp } from "./src/pages/SignUp.jsx"
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "matarial-init",
3
+ "version": "1.0.0",
4
+ "description": "Automatically installs the complete src folder with React Context dynamic workflow, components, and Dashboard into your project.",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "matarial-init": "./bin/cli.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "bin",
13
+ "index.js",
14
+ "README.md"
15
+ ],
16
+ "scripts": {
17
+ "postinstall": "node ./bin/postinstall.js",
18
+ "dev": "vite",
19
+ "build": "vite build",
20
+ "lint": "eslint .",
21
+ "preview": "vite preview"
22
+ },
23
+ "keywords": [
24
+ "matarial-init",
25
+ "react",
26
+ "context",
27
+ "dashboard",
28
+ "dynamic-workflow"
29
+ ],
30
+ "dependencies": {
31
+ "formik": "^2.4.9",
32
+ "react": "^19.2.7",
33
+ "react-dom": "^19.2.7",
34
+ "react-router-dom": "^7.18.0",
35
+ "react-toastify": "^11.1.0",
36
+ "yup": "^1.7.1"
37
+ },
38
+ "devDependencies": {
39
+ "@eslint/js": "^10.0.1",
40
+ "@types/react": "^19.2.17",
41
+ "@types/react-dom": "^19.2.3",
42
+ "@vitejs/plugin-react": "^6.0.2",
43
+ "eslint": "^10.5.0",
44
+ "eslint-plugin-react-hooks": "^7.1.1",
45
+ "eslint-plugin-react-refresh": "^0.5.3",
46
+ "globals": "^17.6.0",
47
+ "vite": "^8.1.0"
48
+ }
49
+ }
package/src/App.css ADDED
@@ -0,0 +1,24 @@
1
+ .navbar {
2
+ display: flex;
3
+ justify-content: space-between;
4
+ height: 40px;
5
+ background: gray;
6
+ text-align: center;
7
+ align-items: center;
8
+ padding-inline: 10px;
9
+ gap: 5px;
10
+ }
11
+
12
+ .logo {
13
+ font-size: 20px;
14
+ font-weight: 600;
15
+ color: #fff;
16
+ cursor: pointer;
17
+ }
18
+
19
+ .input {
20
+ border: solid 1px;
21
+ border-radius: 8px;
22
+ padding: 5px;
23
+ color: black;
24
+ }
package/src/App.jsx ADDED
@@ -0,0 +1,51 @@
1
+ import { Route, Routes } from "react-router-dom"
2
+ import Home from "./pages/Home"
3
+ import SignIn from "./pages/Signin"
4
+ import SignUp from "./pages/SignUp"
5
+ import Dashboard from "./pages/DashBoard"
6
+ import { ToastContainer } from "react-toastify"
7
+ import "react-toastify/dist/ReactToastify.css";
8
+ import { ProtectedRoute } from "./routes/ProtectedRoute"
9
+ import { PublicRoute } from "./routes/PublicRoute"
10
+ import { PublicLayout } from "./layout/PublicLayout"
11
+ import { DashboardLayout } from "./layout/DashboardLayout"
12
+ import Books from "./pages/Books"
13
+ import Members from "./pages/Members"
14
+ import Penalty from "./pages/Penalty"
15
+ import MyBooks from "./pages/MyBooks"
16
+ import { DataProvider } from "./context/DataContext.jsx"
17
+
18
+ function App() {
19
+ return (
20
+ <DataProvider>
21
+ <ToastContainer
22
+ position="top-right"
23
+ autoClose={3000}
24
+ theme="colored"
25
+ />
26
+ <Routes>
27
+ <Route element={
28
+ <PublicRoute>
29
+ <PublicLayout />
30
+ </PublicRoute>}>
31
+ <Route path="/" element={<Home />} />
32
+ <Route path="/signin" element={<SignIn />} />
33
+ <Route path="/signup" element={<SignUp />} />
34
+ </Route>
35
+
36
+ <Route element={
37
+ <ProtectedRoute>
38
+ <DashboardLayout />
39
+ </ProtectedRoute>}>
40
+ <Route path="/dashboard" element={<Dashboard />} />
41
+ <Route path="/books" element={<Books />} />
42
+ <Route path="/members" element={<Members />} />
43
+ <Route path="/penalty" element={<Penalty />} />
44
+ <Route path="/mybooks" element={<MyBooks />} />
45
+ </Route>
46
+ </Routes>
47
+ </DataProvider>
48
+ )
49
+ }
50
+
51
+ export default App
@@ -0,0 +1,21 @@
1
+ const Button = ({ className = "", text, type = "button", onClick, disabled = false }) => {
2
+ return (
3
+ <button
4
+ className={`
5
+ rounded-md
6
+ px-3
7
+ py-2
8
+ cursor-pointer
9
+ transition
10
+ ${disabled ? "opacity-50 cursor-not-allowed" : ""}
11
+ ${className}
12
+ `}
13
+ type={type}
14
+ onClick={onClick}
15
+ disabled={disabled}
16
+ >
17
+ {text}
18
+ </button>
19
+ )
20
+ }
21
+ export default Button
@@ -0,0 +1,29 @@
1
+ import { useState } from "react"
2
+ import { clearData } from "../uttils/persistence"
3
+ import Button from "./Button"
4
+ import { Modal } from "./Modal"
5
+ import { STORAGE_KEYS } from "../constants/storageKeys"
6
+ import { useNavigate } from "react-router-dom"
7
+
8
+ export const Header = () => {
9
+ const navigate = useNavigate()
10
+ const [open, setOpen] = useState(false)
11
+ const handleLogout = () => {
12
+ clearData(STORAGE_KEYS.CURRENT_USER)
13
+ navigate("/signin")
14
+ setOpen(false)
15
+ }
16
+ return (
17
+ <>
18
+ <header className="w-full flex justify-between items-center p-4">
19
+ <h1>My Header</h1>
20
+ <Button className="bg-gray-400 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded" text="Logout" onClick={() => setOpen(true)} />
21
+ </header>
22
+ <Modal isOpen={open} onClose={() => setOpen(false)} title="Confirm Logout" submit={handleLogout}>
23
+ <p className="text-gray-600 mb-4">
24
+ Are you sure you want to logout?
25
+ </p>
26
+ </Modal>
27
+ </>
28
+ )
29
+ }
@@ -0,0 +1,33 @@
1
+ const Input = ({ type, name, value, onChange, onBlur, placeholder, options, min, max }) => {
2
+ if (type === "select") {
3
+ return (
4
+ <select
5
+ className="input w-full"
6
+ name={name}
7
+ value={value}
8
+ onChange={onChange}
9
+ onBlur={onBlur}
10
+ placeholder={placeholder}
11
+ >
12
+ {options?.map((item, i) =>
13
+ <option key={i} value={item.value}>
14
+ {item.label}
15
+ </option>)}
16
+ </select>
17
+ )
18
+ }
19
+ return (
20
+ <input
21
+ className="input w-full"
22
+ type={type}
23
+ name={name}
24
+ value={value}
25
+ onChange={onChange}
26
+ onBlur={onBlur}
27
+ placeholder={placeholder}
28
+ min={min}
29
+ max={max}
30
+ />
31
+ )
32
+ }
33
+ export default Input
@@ -0,0 +1,43 @@
1
+ import Button from "./Button";
2
+
3
+ export const Modal = ({
4
+ isOpen,
5
+ onClose,
6
+ title,
7
+ children,
8
+ submit,
9
+ }) => {
10
+
11
+ if (!isOpen) return null;
12
+
13
+ return (
14
+ <div className="fixed inset-0 bg-black/50 flex justify-center items-center">
15
+
16
+ <div className="bg-white rounded-md p-5 min-w-[400px]">
17
+
18
+ {title &&
19
+ <h2 className="text-xl text-gray-800 font-bold mb-4 border-b pb-2">
20
+ {title}
21
+ </h2>
22
+ }
23
+
24
+ {children}
25
+
26
+ <div className="flex justify-end gap-2">
27
+
28
+ <Button
29
+ className="bg-red-400 hover:bg-red-700 text-white font-bold py-2 px-4 rounded"
30
+ text="Close"
31
+ onClick={onClose}
32
+ />
33
+
34
+ <Button
35
+ className="bg-gray-400 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded"
36
+ text="Submit"
37
+ onClick={submit}
38
+ />
39
+ </div>
40
+ </div>
41
+ </div>
42
+ );
43
+ };
@@ -0,0 +1,19 @@
1
+ import { Link, useNavigate } from "react-router-dom"
2
+ import Button from "./Button"
3
+
4
+ const Navbar = () => {
5
+ const navigate = useNavigate()
6
+ return (
7
+ <nav className="navbar">
8
+ <div className="logo" onClick={() => navigate("/")}>
9
+ Library
10
+ </div>
11
+ <div className="flex gap-2">
12
+ <Button text="Sign In" onClick={() => navigate("/signin")}/>
13
+ <Button text="Sign Up" onClick={() => navigate("/signup")} />
14
+ </div>
15
+ </nav>
16
+ )
17
+ }
18
+
19
+ export default Navbar
@@ -0,0 +1,30 @@
1
+ import { useLocation, useNavigate } from "react-router-dom"
2
+ import Button from "./Button"
3
+ import { SIDEBAR_MENU } from "../constants/storageKeys"
4
+ import { usePermission } from "../hooks/usePermission"
5
+
6
+ export const Sidebar = () => {
7
+ const navigate = useNavigate()
8
+ const location = useLocation()
9
+ const { hashPermission } = usePermission()
10
+ return (
11
+ <div>
12
+ <div className="flex justify-start items-center h-16 px-4 text-black font-bold border-b">LOGO</div>
13
+ <div className="flex flex-col gap-2 p-2">
14
+ {SIDEBAR_MENU.filter((menu) => hashPermission(menu.permission)).map((item) => (
15
+ <Button
16
+ key={item.id}
17
+ text={item.title}
18
+ onClick={() => navigate(item.path)}
19
+ className={
20
+ location.pathname === item.path
21
+ ? "bg-gray-400 text-white w-full"
22
+ : "w-full bg-gray-200 hover:bg-gray-300"
23
+ }
24
+ />
25
+
26
+ ))}
27
+ </div>
28
+ </div>
29
+ )
30
+ }
@@ -0,0 +1,56 @@
1
+ import Button from "./Button"
2
+
3
+ export const Table = ({ data, column, action = [] }) => {
4
+ if (!data.length) {
5
+ return (
6
+ <div>
7
+ <h1>No records Found.</h1>
8
+ </div>
9
+ )
10
+ }
11
+ return (
12
+ <table className="table-auto border-collapse w-full">
13
+ <thead>
14
+ <tr>
15
+ {column?.map((column) => (
16
+ <th key={column.key} className="bg-gray-400 text-white px-4 py-2 text-left">
17
+ {column.title.toUpperCase()}
18
+ </th>
19
+ ))}
20
+
21
+ {action.length > 0 &&
22
+ <th className="bg-gray-400 text-white px-4 py-2 text-left">
23
+ Action
24
+ </th>
25
+ }
26
+
27
+ </tr>
28
+ </thead>
29
+ <tbody>
30
+ {data.map((item, index) => (
31
+ <tr key={index}>
32
+ {column?.map((column, idx) => (
33
+ <td key={column.key} className="px-4 py-2">
34
+ {item[column.key]}
35
+ </td>
36
+ ))}
37
+ {action.length > 0 && (
38
+ <td className="px-4 py-2">
39
+ <div className="flex gap-2">
40
+ {action.map((actionItem, i) => (
41
+ <Button
42
+ key={i}
43
+ text={actionItem.label}
44
+ className={actionItem.className}
45
+ onClick={() => actionItem.onClick(item)}
46
+ />
47
+ ))}
48
+ </div>
49
+ </td>
50
+ )}
51
+ </tr>
52
+ ))}
53
+ </tbody>
54
+ </table>
55
+ )
56
+ }
@@ -0,0 +1,27 @@
1
+ import { Role } from "./storageKeys";
2
+
3
+ export const PERMISSIONS = {
4
+ VIEW_DASHBOARD:[
5
+ Role.ADMIN,
6
+ Role.LIBRARIAN,
7
+ Role.Member
8
+ ],
9
+ VIEW_BOOKS:[
10
+ Role.ADMIN,
11
+ Role.LIBRARIAN,
12
+ Role.Member
13
+ ],
14
+ VIEW_MEMBER:[
15
+ Role.ADMIN,
16
+ Role.LIBRARIAN,
17
+ ],
18
+ VIEW_PANALTY:[
19
+ Role.ADMIN,
20
+ Role.LIBRARIAN,
21
+ Role.Member
22
+ ],
23
+ VIEW_MyBOOKS:[
24
+ Role.Member
25
+ ],
26
+
27
+ }