create-next-structure 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.
Files changed (40) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +51 -0
  3. package/bin/index.js +90 -0
  4. package/package.json +44 -0
  5. package/templates/.env.template +36 -0
  6. package/templates/app/(dashboard)/dashboard/page.jsx +40 -0
  7. package/templates/app/(dashboard)/layout.jsx +24 -0
  8. package/templates/app/(dashboard)/users/page.jsx +64 -0
  9. package/templates/app/globals.css +14 -0
  10. package/templates/app/layout.jsx +25 -0
  11. package/templates/app/login/page.jsx +69 -0
  12. package/templates/app/page.jsx +10 -0
  13. package/templates/components/auth/withAuth.jsx +50 -0
  14. package/templates/components/auth/withPublic.jsx +50 -0
  15. package/templates/components/layout/Header.jsx +29 -0
  16. package/templates/components/layout/Sidebar.jsx +35 -0
  17. package/templates/components/ui/Button.jsx +36 -0
  18. package/templates/components/ui/ErrorDisplay.jsx +19 -0
  19. package/templates/components/ui/Input.jsx +30 -0
  20. package/templates/components/ui/Loading.jsx +16 -0
  21. package/templates/components/ui/Modal.jsx +33 -0
  22. package/templates/components/ui/index.js +10 -0
  23. package/templates/contexts/AuthContext.jsx +112 -0
  24. package/templates/docs/README.md +128 -0
  25. package/templates/hooks/useAsync.js +38 -0
  26. package/templates/hooks/useAuth.js +93 -0
  27. package/templates/hooks/useForm.js +67 -0
  28. package/templates/jsconfig.json +27 -0
  29. package/templates/lib/api/apiClient.js +105 -0
  30. package/templates/lib/api/auth.api.js +36 -0
  31. package/templates/lib/api/user.api.js +43 -0
  32. package/templates/next.config.js +18 -0
  33. package/templates/package.json +23 -0
  34. package/templates/store/ReduxProvider.jsx +34 -0
  35. package/templates/store/api/apiSlice.js +108 -0
  36. package/templates/store/api/authApi.js +155 -0
  37. package/templates/store/api/exampleApi.js +108 -0
  38. package/templates/store/api/userApi.js +114 -0
  39. package/templates/store/slices/authSlice.js +86 -0
  40. package/templates/store/store.js +27 -0
package/LICENSE ADDED
@@ -0,0 +1,7 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 MD. Sifat Islam
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,51 @@
1
+
2
+ # create-next-structure
3
+
4
+ CLI tool to scaffold a **Next.js + Redux Toolkit** project structure with auth, RTK Query, and a ready-to-use template.
5
+
6
+ ## ✅ Features
7
+
8
+ - Next.js App Router layout
9
+ - Redux Toolkit + RTK Query pre-configured
10
+ - Auth flow helpers and protected routes
11
+ - Reusable UI components and layout shell
12
+ - Template documentation included
13
+
14
+ ## 📦 Install
15
+
16
+ ```bash
17
+ npm install -g create-next-structure
18
+ ```
19
+
20
+ ## 🚀 Usage
21
+
22
+ ```bash
23
+ create-next-structure my-app
24
+ ```
25
+
26
+ Then:
27
+
28
+ ```bash
29
+ cd my-app
30
+ npm install
31
+ npm run dev
32
+ ```
33
+
34
+ ## 🧩 Template docs
35
+
36
+ After generation, read:
37
+
38
+ - `docs/README.md` (template usage overview)
39
+ - `docs/PROJECT_STRUCTURE.md`
40
+ - `docs/API_INTEGRATION.md`
41
+ - `docs/ARCHITECTURE.md`
42
+ - `docs/EXAMPLES.md`
43
+
44
+ ## 🔧 Notes
45
+
46
+ - Make sure you create `.env.local` using `.env.template` and set `NEXT_PUBLIC_API_URL`.
47
+ - Protected routes live under `app/(dashboard)`.
48
+
49
+ ## 📄 License
50
+
51
+ ISC
package/bin/index.js ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs-extra";
4
+ import path from "path";
5
+ import { fileURLToPath } from "url";
6
+ import chalk from "chalk";
7
+ import ora from "ora";
8
+ // Update notifier
9
+ import updateNotifier from "update-notifier";
10
+ import pkg from "../package.json" assert { type: "json" };
11
+
12
+ const notifier = updateNotifier({
13
+ pkg,
14
+ updateCheckInterval: 1000 * 60 * 60, // 1 hour
15
+ });
16
+
17
+ notifier.notify();
18
+
19
+ if (notifier.update) {
20
+ console.log(
21
+ chalk.yellow(`Update available ${pkg.version} → ${notifier.update.latest}`),
22
+ );
23
+ console.log(chalk.blue(`Run npm install -g ${pkg.name}@latest to update.\n`));
24
+ }
25
+
26
+ const __filename = fileURLToPath(import.meta.url);
27
+ const __dirname = path.dirname(__filename);
28
+
29
+ // project name --> from user arg
30
+ const projectName = (process.argv[2] || "").trim();
31
+
32
+ // check if project name is provided
33
+ if (!projectName) {
34
+ console.log(chalk.red("Please provide a project name"));
35
+ console.log(chalk.blue("Usage: create-next-structure <project-name>"));
36
+ process.exit(1);
37
+ }
38
+
39
+ // basic validation for folder name
40
+ const invalidNameChars = /[<>:"/\\|?*\x00-\x1F]/;
41
+ if (invalidNameChars.test(projectName)) {
42
+ console.log(chalk.red("Invalid project name. Avoid special characters."));
43
+ process.exit(1);
44
+ }
45
+
46
+ // Dirs
47
+ const targetDir = path.join(process.cwd(), projectName);
48
+ const templateDir = path.join(__dirname, "../templates");
49
+
50
+ // Create project function
51
+ async function createProject() {
52
+ const spinner = ora("Creating project structure...").start();
53
+
54
+ try {
55
+ // Check template dir exists
56
+ const templateExists = await fs.pathExists(templateDir);
57
+ if (!templateExists) {
58
+ spinner.fail("Template folder not found");
59
+ console.error(chalk.red(`Missing template at: ${templateDir}`));
60
+ process.exit(1);
61
+ }
62
+
63
+ // Prevent overwrite
64
+ const targetExists = await fs.pathExists(targetDir);
65
+ if (targetExists) {
66
+ spinner.fail("Target folder already exists");
67
+ console.error(chalk.red(`Folder already exists: ${targetDir}`));
68
+ process.exit(1);
69
+ }
70
+
71
+ // copy template files to target dir
72
+ await fs.copy(templateDir, targetDir);
73
+
74
+ spinner.succeed("Project structure created successfully!");
75
+ console.log(
76
+ chalk.green(`Your Next.js project "${projectName}" has been created!`),
77
+ );
78
+ console.log(
79
+ chalk.blue(`Navigate to the project directory: cd ${projectName}`),
80
+ );
81
+ console.log(chalk.blue("Install dependencies: npm install"));
82
+ console.log(chalk.blue("Start development server: npm run dev"));
83
+ } catch (err) {
84
+ spinner.fail("Failed to create project structure");
85
+ console.error(chalk.red(err));
86
+ process.exit(1);
87
+ }
88
+ }
89
+
90
+ createProject();
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "create-next-structure",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "bin": {
6
+ "create-next-structure": "bin/index.js"
7
+ },
8
+ "type": "module",
9
+ "description": "Scaffold a Next.js + Redux Toolkit project structure with auth and RTK Query.",
10
+ "keywords": [
11
+ "nextjs",
12
+ "redux",
13
+ "rtk-query",
14
+ "template",
15
+ "scaffold",
16
+ "cli",
17
+ "starter"
18
+ ],
19
+ "author": "MD. Sifat Islam",
20
+ "license": "ISC",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "git+https://github.com/stackmatrixdev/create-next-structure.git"
24
+ },
25
+ "homepage": "https://github.com/stackmatrixdev/create-next-structure#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/stackmatrixdev/create-next-structure/issues"
28
+ },
29
+ "files": [
30
+ "bin",
31
+ "templates",
32
+ "README.md"
33
+ ],
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "dependencies": {
38
+ "chalk": "^5.3.0",
39
+ "fs-extra": "^11.2.0",
40
+ "inquirer": "^9.2.0",
41
+ "ora": "^7.0.1",
42
+ "update-notifier": "^7.3.1"
43
+ }
44
+ }
@@ -0,0 +1,36 @@
1
+ # Environment Variables Template
2
+
3
+ # Copy this file to .env.local and fill in your values
4
+ # .env.local is ignored by git for security
5
+
6
+ # ===========================================
7
+ # API Configuration
8
+ # ===========================================
9
+
10
+ # Backend API Base URL (REQUIRED)
11
+ # Development: http://localhost:5000/api/v1
12
+ # Production: https://your-api.com/api/v1
13
+ NEXT_PUBLIC_API_URL=http://localhost:5000/api/v1
14
+
15
+ # ===========================================
16
+ # Optional Configuration
17
+ # ===========================================
18
+
19
+ # App Name
20
+ NEXT_PUBLIC_APP_NAME="My App"
21
+
22
+ # App Version
23
+ NEXT_PUBLIC_APP_VERSION="1.0.0"
24
+
25
+ # Enable Debug Mode (show console logs)
26
+ NEXT_PUBLIC_DEBUG_MODE=false
27
+
28
+
29
+ # ===========================================
30
+ # Notes
31
+ # ===========================================
32
+
33
+ # 1. NEXT_PUBLIC_ prefix makes variables available in browser
34
+ # 2. Variables without prefix are server-side only
35
+ # 3. Never commit .env.local to version control
36
+ # 4. Update values based on your environment
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Dashboard Page (Redux Version)
3
+ * Responsibility: Main dashboard view with Redux state
4
+ *
5
+ * FEATURES:
6
+ * - Uses Redux to access user data
7
+ * - Shows current user information
8
+ */
9
+
10
+ "use client";
11
+
12
+ import { useSelector } from "react-redux";
13
+ import { selectCurrentUser } from "@/store/slices/authSlice";
14
+
15
+ export default function DashboardPage() {
16
+ const user = useSelector(selectCurrentUser);
17
+
18
+ return (
19
+ <div className="dashboard">
20
+ <h1>Dashboard</h1>
21
+ <p>Welcome, {user?.email || user?.name || "User"}</p>
22
+ <div className="user-info">
23
+ {user && (
24
+ <>
25
+ <p>
26
+ <strong>Email:</strong> {user.email}
27
+ </p>
28
+ <p>
29
+ <strong>Role:</strong> {user.role || "N/A"}
30
+ </p>
31
+ <p>
32
+ <strong>ID:</strong> {user.id || user._id}
33
+ </p>
34
+ </>
35
+ )}
36
+ </div>
37
+ {/* Add dashboard content here */}
38
+ </div>
39
+ );
40
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Dashboard Layout
3
+ * Responsibility: Protected layout with header and sidebar
4
+ */
5
+
6
+ "use client";
7
+
8
+ import { Header } from "@/components/layout/Header";
9
+ import { Sidebar } from "@/components/layout/Sidebar";
10
+ import { withAuth } from "@/components/auth/withAuth";
11
+
12
+ function DashboardLayout({ children }) {
13
+ return (
14
+ <div className="app-layout">
15
+ <Header />
16
+ <div className="layout-content">
17
+ <Sidebar />
18
+ <main className="main-content">{children}</main>
19
+ </div>
20
+ </div>
21
+ );
22
+ }
23
+
24
+ export default withAuth(DashboardLayout);
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Users Page (Redux Version with RTK Query)
3
+ * Responsibility: Display and manage users using RTK Query
4
+ *
5
+ * FEATURES:
6
+ * - Automatic data fetching with RTK Query
7
+ * - Built-in caching and refetching
8
+ * - Loading and error states handled automatically
9
+ * - No manual useState or useEffect needed!
10
+ *
11
+ * USAGE EXAMPLE:
12
+ * - Data is automatically fetched when component mounts
13
+ * - Refetch manually: refetch()
14
+ * - Access loading state: isLoading
15
+ * - Access error state: error
16
+ */
17
+
18
+ "use client";
19
+
20
+ import { useGetUsersQuery } from "@/store/api/userApi";
21
+ import { Loading, ErrorDisplay } from "@/components/ui";
22
+
23
+ export default function UsersPage() {
24
+ // RTK Query automatically handles fetching, caching, and state
25
+ const { data, isLoading, error, refetch } = useGetUsersQuery({
26
+ page: 1,
27
+ limit: 10,
28
+ });
29
+
30
+ if (isLoading) return <Loading />;
31
+ if (error)
32
+ return (
33
+ <ErrorDisplay
34
+ error={error.data?.message || "Failed to load users"}
35
+ onRetry={refetch}
36
+ />
37
+ );
38
+
39
+ const users = data?.users || data?.data || [];
40
+
41
+ return (
42
+ <div className="users-page">
43
+ <div className="users-header">
44
+ <h1>Users</h1>
45
+ <button onClick={refetch}>Refresh</button>
46
+ </div>
47
+ <div className="users-list">
48
+ {users.length === 0 ? (
49
+ <p>No users found</p>
50
+ ) : (
51
+ users.map((user) => (
52
+ <div key={user._id || user.id} className="user-card">
53
+ <p>
54
+ <strong>{user.name || user.email}</strong>
55
+ </p>
56
+ <p>{user.email}</p>
57
+ <span>{user.role || "User"}</span>
58
+ </div>
59
+ ))
60
+ )}
61
+ </div>
62
+ </div>
63
+ );
64
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Global Styles
3
+ * Add your global CSS/styling framework here
4
+ */
5
+
6
+ body {
7
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
8
+ 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
9
+ sans-serif;
10
+ -webkit-font-smoothing: antialiased;
11
+ -moz-osx-font-smoothing: grayscale;
12
+ }
13
+
14
+ /* Add your component styles or import CSS framework */
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Root Layout
3
+ * Responsibility: Main application layout wrapper with Redux Provider
4
+ */
5
+
6
+ import { Inter } from "next/font/google";
7
+ import { ReduxProvider } from "@/store/ReduxProvider";
8
+ import "./globals.css";
9
+
10
+ const inter = Inter({ subsets: ["latin"] });
11
+
12
+ export const metadata = {
13
+ title: "App Template - Redux",
14
+ description: "Next.js starter template with Redux Toolkit & RTK Query",
15
+ };
16
+
17
+ export default function RootLayout({ children }) {
18
+ return (
19
+ <html lang="en">
20
+ <body className={inter.className}>
21
+ <ReduxProvider>{children}</ReduxProvider>
22
+ </body>
23
+ </html>
24
+ );
25
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Login Page (Redux Version)
3
+ * Responsibility: User login form with Redux & RTK Query
4
+ *
5
+ * FEATURES:
6
+ * - Uses Redux for state management
7
+ * - Uses RTK Query for API calls
8
+ * - Auto-redirects on successful login
9
+ * - Protected with withPublic HOC
10
+ */
11
+
12
+ "use client";
13
+
14
+ import { useState } from "react";
15
+ import { useAuth } from "@/hooks/useAuth";
16
+ import { withPublic } from "@/components/auth/withPublic";
17
+ import { Button, Input } from "@/components/ui";
18
+
19
+ function LoginPage() {
20
+ const { login, isLoading } = useAuth();
21
+ const [formData, setFormData] = useState({ email: "", password: "" });
22
+ const [error, setError] = useState("");
23
+
24
+ const handleSubmit = async (e) => {
25
+ e.preventDefault();
26
+ setError("");
27
+
28
+ try {
29
+ await login(formData);
30
+ // Auto-redirects to /dashboard on success
31
+ } catch (err) {
32
+ setError(err?.data?.message || "Login failed. Please try again.");
33
+ }
34
+ };
35
+
36
+ return (
37
+ <div className="auth-page">
38
+ <div className="auth-card">
39
+ <h1>Login</h1>
40
+ <form onSubmit={handleSubmit}>
41
+ <Input
42
+ label="Email"
43
+ type="email"
44
+ value={formData.email}
45
+ onChange={(e) =>
46
+ setFormData({ ...formData, email: e.target.value })
47
+ }
48
+ required
49
+ />
50
+ <Input
51
+ label="Password"
52
+ type="password"
53
+ value={formData.password}
54
+ onChange={(e) =>
55
+ setFormData({ ...formData, password: e.target.value })
56
+ }
57
+ required
58
+ />
59
+ {error && <div className="error">{error}</div>}
60
+ <Button type="submit" loading={isLoading}>
61
+ Login
62
+ </Button>
63
+ </form>
64
+ </div>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ export default withPublic(LoginPage);
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Home Page
3
+ * Responsibility: Landing page with redirect to dashboard
4
+ */
5
+
6
+ import { redirect } from "next/navigation";
7
+
8
+ export default function Home() {
9
+ redirect("/dashboard");
10
+ }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Protected Route HOC (Redux Version)
3
+ * Responsibility: Restrict access to authenticated users only
4
+ *
5
+ * USAGE GUIDE:
6
+ * - Wrap any component that requires authentication
7
+ * - Automatically redirects to login if user is not authenticated
8
+ *
9
+ * EXAMPLE:
10
+ * ```jsx
11
+ * import { withAuth } from '@/components/auth/withAuth';
12
+ *
13
+ * function DashboardPage() {
14
+ * return <div>Protected Dashboard</div>;
15
+ * }
16
+ *
17
+ * export default withAuth(DashboardPage);
18
+ * ```
19
+ */
20
+
21
+ "use client";
22
+
23
+ import { useEffect } from "react";
24
+ import { useRouter } from "next/navigation";
25
+ import { useSelector } from "react-redux";
26
+ import { selectIsAuthenticated, selectToken } from "@/store/slices/authSlice";
27
+ import Loading from "@/components/ui/Loading";
28
+
29
+ /**
30
+ * Higher-order component that protects routes requiring authentication
31
+ */
32
+ export const withAuth = (Component) => {
33
+ return function ProtectedRoute(props) {
34
+ const router = useRouter();
35
+ const isAuthenticated = useSelector(selectIsAuthenticated);
36
+ const token = useSelector(selectToken);
37
+
38
+ useEffect(() => {
39
+ if (!isAuthenticated && !token) {
40
+ router.push("/login");
41
+ }
42
+ }, [isAuthenticated, token, router]);
43
+
44
+ if (!isAuthenticated || !token) {
45
+ return <Loading />;
46
+ }
47
+
48
+ return <Component {...props} />;
49
+ };
50
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Public Route HOC (Redux Version)
3
+ * Responsibility: Restrict access to non-authenticated users only
4
+ *
5
+ * USAGE GUIDE:
6
+ * - Wrap any component that should only be accessible to non-authenticated users
7
+ * - Automatically redirects to dashboard if user is authenticated
8
+ * - Perfect for login, register, forgot password pages
9
+ *
10
+ * EXAMPLE:
11
+ * ```jsx
12
+ * import { withPublic } from '@/components/auth/withPublic';
13
+ *
14
+ * function LoginPage() {
15
+ * return <div>Login Form</div>;
16
+ * }
17
+ *
18
+ * export default withPublic(LoginPage);
19
+ * ```
20
+ */
21
+
22
+ "use client";
23
+
24
+ import { useEffect } from "react";
25
+ import { useRouter } from "next/navigation";
26
+ import { useSelector } from "react-redux";
27
+ import { selectIsAuthenticated } from "@/store/slices/authSlice";
28
+
29
+ /**
30
+ * Higher-order component that protects routes for non-authenticated users
31
+ * Redirects to dashboard if user is already logged in
32
+ */
33
+ export const withPublic = (Component, redirectTo = "/dashboard") => {
34
+ return function PublicRoute(props) {
35
+ const router = useRouter();
36
+ const isAuthenticated = useSelector(selectIsAuthenticated);
37
+
38
+ useEffect(() => {
39
+ if (isAuthenticated) {
40
+ router.push(redirectTo);
41
+ }
42
+ }, [isAuthenticated, router]);
43
+
44
+ if (isAuthenticated) {
45
+ return null;
46
+ }
47
+
48
+ return <Component {...props} />;
49
+ };
50
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Header Component
3
+ * Responsibility: App header with user menu
4
+ */
5
+
6
+ "use client";
7
+
8
+ import { useAuth } from "@/contexts/AuthContext";
9
+
10
+ export const Header = () => {
11
+ const { user, logout } = useAuth();
12
+
13
+ return (
14
+ <header className="app-header">
15
+ <div className="header-content">
16
+ <h1 className="app-title">App Name</h1>
17
+
18
+ <nav className="header-nav">
19
+ {user && (
20
+ <div className="user-menu">
21
+ <span>{user.email}</span>
22
+ <button onClick={logout}>Logout</button>
23
+ </div>
24
+ )}
25
+ </nav>
26
+ </div>
27
+ </header>
28
+ );
29
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Sidebar Component
3
+ * Responsibility: Navigation sidebar
4
+ */
5
+
6
+ "use client";
7
+
8
+ import Link from "next/link";
9
+ import { usePathname } from "next/navigation";
10
+
11
+ export const Sidebar = () => {
12
+ const pathname = usePathname();
13
+
14
+ const navItems = [
15
+ { path: "/dashboard", label: "Dashboard" },
16
+ { path: "/users", label: "Users" },
17
+ // Add more navigation items here
18
+ ];
19
+
20
+ return (
21
+ <aside className="sidebar">
22
+ <nav className="sidebar-nav">
23
+ {navItems.map((item) => (
24
+ <Link
25
+ key={item.path}
26
+ href={item.path}
27
+ className={`nav-item ${pathname === item.path ? "active" : ""}`}
28
+ >
29
+ {item.label}
30
+ </Link>
31
+ ))}
32
+ </nav>
33
+ </aside>
34
+ );
35
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Button Component
3
+ * Responsibility: Reusable button with consistent styling and variants
4
+ */
5
+
6
+ /**
7
+ * Base button component
8
+ * @param {string} variant - Button style variant: 'primary', 'secondary', 'danger'
9
+ * @param {string} size - Button size: 'sm', 'md', 'lg'
10
+ * @param {boolean} disabled - Disabled state
11
+ * @param {boolean} loading - Loading state
12
+ * @param {function} onClick - Click handler
13
+ */
14
+ export const Button = ({
15
+ children,
16
+ variant = "primary",
17
+ size = "md",
18
+ disabled = false,
19
+ loading = false,
20
+ onClick,
21
+ type = "button",
22
+ className = "",
23
+ ...props
24
+ }) => {
25
+ return (
26
+ <button
27
+ type={type}
28
+ onClick={onClick}
29
+ disabled={disabled || loading}
30
+ className={`btn btn-${variant} btn-${size} ${className}`}
31
+ {...props}
32
+ >
33
+ {loading ? "Loading..." : children}
34
+ </button>
35
+ );
36
+ };