article

Showing posts with label mongoDB. Show all posts
Showing posts with label mongoDB. Show all posts

Monday, January 29, 2024

Next.js 14 Login Register MongoDB Next-Auth Login Authentication Daisyui Tailwind

Next.js 14 Login Register MongoDB Next-Auth Login Authentication Daisyui Tailwind

Install nextjs npx create-next-app@latest https://nextjs.org/docs/getting-started/installation

Install the following

npm install react-daisyui
https://www.npmjs.com/package/react-daisyui
daisyUI components built with React, Typescript and TailwindCSS

Mongoose
npm install mongoose
https://www.npmjs.com/package/mongoose

NextAuth.js
npm install next-auth
https://www.npmjs.com/package/next-auth/v/4.23.1
NextAuth.js is a complete open source authentication solution for Next.js applications.

bcrypt.js
npm install bcryptjs
https://www.npmjs.com/package/bcryptjs

edit tailwind.config.js Add daisyui to plugins
edit tailwind.config.ts
//edit tailwind.config.ts
import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      backgroundImage: {
        'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
        'gradient-conic':
          'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
      },
    },
  },
  plugins: [require('daisyui')], //https://www.npmjs.com/package/react-daisyui
}
export default config
.env
MONGODB_URI=mongodb://127.0.0.1/nextjs14
NEXTAUTH_SECRET=cairocoders
app\layout.tsx
//app\layout.js
import Navbar from "@/components/Navbar";
import "./globals.css";
import type { Metadata } from "next";
import { Inter } from "next/font/google";

import { getServerSession } from "next-auth";
import SessionProvider from "@/utils/SessionProvider";

const inter = Inter({ subsets: ["latin"] });

export const metadata: Metadata = {
  title: "Next.js 14 Login Register MongoDB Next-Auth Login Authentication Daisyui Tailwind",
  description: "Generated by create next app",
};

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const session = await getServerSession();
  return (
    <html lang="en">
      <body className={inter.className}>
        <SessionProvider session={session}>
          <div className="container mx-auto px-4">
            <Navbar />
            {children}
          </div>
        </SessionProvider>
      </body>
    </html>
  );
}
components\Navbar.tsx
//components\Navbar.jsx
"use client";
import React from "react";
import Link from "next/link";
import { signOut, useSession } from "next-auth/react";

const Navbar = () => {
    const { data: session }: any = useSession();
    return (
        <div className="navbar bg-base-100">
            <div className="flex-1">
                <a className="btn btn-ghost text-xl">Cairocoders</a>
                <Link href="/">
                    Home
                </Link>
            </div>
            <div className="flex-none">
                {!session ? (
                    <>
                        <Link href="/login" className="btn btn-primary">
                            Login
                        </Link>
                        <Link href="/register" className="btn btn-secondary ml-2">
                            Register
                        </Link>
                    </>
                ) : (
                    <>
                        {session.user?.email}
                            <div className="dropdown dropdown-end">
                                <div tabIndex={0} role="button" className="btn btn-ghost btn-circle avatar">
                                    <div className="w-10 rounded-full">
                                        <img alt="Tailwind CSS Navbar component" src="https://daisyui.com/images/stock/photo-1534528741775-53994a69daeb.jpg" />
                                    </div>
                                </div>
                                <ul tabIndex={0} className="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
                                    <li>
                                        <a className="justify-between">
                                            Profile
                                            <span className="badge">New</span>
                                        </a>
                                    </li>
                                    <li><a>Settings</a></li>
                                    <li>
                                        <button
                                            onClick={() => {
                                                signOut();
                                            }}
                                        >
                                            Logout
                                        </button>
                                    </li>
                                </ul>
                            </div>
                    </>
                )}
            </div>
        </div>
    );
};

export default Navbar;
models\User.js
//models\User.js
import mongoose from "mongoose";

const { Schema } = mongoose;

const userSchema = new Schema(
    {
        name: {
            type: String,
            required: true,
        },
        email: {
            type: String,
            unique: true,
            required: true,
        },
        password: {
            type: String,
            required: false,
        },
    },
    { timestamps: true }
);

export default mongoose.models.User || mongoose.model("User", userSchema);
utils\db.js
//utils\db.js
import mongoose from "mongoose";

const connect = async () => {
    if (mongoose.connections[0].readyState) return;

    try {
        await mongoose.connect(process.env.MONGODB_URI, {
            useNewUrlParser: true,
            useUnifiedTopology: true,
        });
        console.log("Mongo Connection successfully established.");
    } catch (error) {
        throw new Error("Error connecting to Mongoose");
    }
};

export default connect;
utils\SessionProvider.tsx
//utils\SessionProvider.tsx
"use client";
import React from "react";
import { SessionProvider } from "next-auth/react"; //https://www.npmjs.com/package/next-auth/v/4.23.1

const AuthProvider = ({ children }: any) => {
    return {children};
};

export default AuthProvider;
app\pag.tsx
//app\pag.tsx
export default function Home() {
  return (
    <main>
      <div className="carousel w-full">
        <div id="item1" className="carousel-item w-full">
          <img src="https://daisyui.com/images/stock/photo-1625726411847-8cbb60cc71e6.jpg" className="w-full" />
        </div>
        <div id="item2" className="carousel-item w-full">
          <img src="https://daisyui.com/images/stock/photo-1609621838510-5ad474b7d25d.jpg" className="w-full" />
        </div>
        <div id="item3" className="carousel-item w-full">
          <img src="https://daisyui.com/images/stock/photo-1414694762283-acccc27bca85.jpg" className="w-full" />
        </div>
        <div id="item4" className="carousel-item w-full">
          <img src="https://daisyui.com/images/stock/photo-1665553365602-b2fb8e5d1707.jpg" className="w-full" />
        </div>
      </div>
      <div className="flex justify-center w-full py-2 gap-2">
        <a href="#item1" className="btn btn-xs">1</a>
        <a href="#item2" className="btn btn-xs">2</a>
        <a href="#item3" className="btn btn-xs">3</a>
        <a href="#item4" className="btn btn-xs">4</a>
      </div>
    </main>
  );
}
app\login\page.tsx
//app\login\page.tsx
"use client";
import React, { useEffect, useState } from "react";
import Link from "next/link";
import { signIn, useSession } from "next-auth/react";
import { useRouter } from "next/navigation";

const Login = () => {
    const router = useRouter();
    const [error, setError] = useState("");
    const { data: session, status: sessionStatus } = useSession();

    useEffect(() => {
        if (sessionStatus === "authenticated") {
            router.replace("/dashboard");
        }
    }, [sessionStatus, router]);

    const isValidEmail = (email: string) => {
        const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
        return emailRegex.test(email);
    };

    const handleSubmit = async (e: any) => {
        e.preventDefault();
        const email = e.target[0].value;
        const password = e.target[1].value;

        if (!isValidEmail(email)) {
            setError("Email is invalid");
            return;
        }

        if (!password || password.length < 8) {
            setError("Password is invalid");
            return;
        }

        const res = await signIn("credentials", {
            redirect: false,
            email,
            password,
        });

        if (res?.error) {
            setError("Invalid email or password");
            if (res?.url) router.replace("/dashboard");
        } else {
            setError("");
        }
    };

    if (sessionStatus === "loading") {
        return <h1>Loading...</h1>;
    }

    return (
        sessionStatus !== "authenticated" && (
        <div className="justify-center mt-16">
        <div className="w-full p-6 m-auto bg-white rounded-md shadow-md lg:max-w-lg">
            <h1 className="text-3xl font-semibold text-center text-purple-700">Login</h1>
            <form onSubmit={handleSubmit} className="space-y-4">
            <div>
                <label className="label">
                <span className="text-base label-text">Email</span>
                </label>
                <input type="text" placeholder="Email Address" required className="w-full input input-bordered input-primary" />
            </div>
            <div>
                <label className="label">
                <span className="text-base label-text">Password</span>
                </label>
                <input type="password" placeholder="Enter Password" required className="w-full input input-bordered input-primary" />
            </div>
            <a href="#" className="text-xs text-gray-600 hover:underline hover:text-blue-600">Forget Password?</a>
            <div>
            <button
                type="submit"
                className="btn btn-primary"
            >
            {" "}
            Sign In
            </button>
            <p className="text-red-600 text-[16px] mb-4">{error && error}</p>
            </div>
            </form>
                <Link
                    className="block text-center text-blue-500 hover:underline mt-2"
                    href="/register"
                 >
                Register Here
                </Link>
            </div>
        </div>
        )
    );
};

export default Login;
app\register\page.tsx
//app\register\page.tsx
"use client";
import React, { useEffect, useState } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { useSession } from "next-auth/react";

const Register = () => {
    const [error, setError] = useState("");
    const router = useRouter();
    const { data: session, status: sessionStatus } = useSession();

    useEffect(() => {
        if (sessionStatus === "authenticated") {
            router.replace("/dashboard");
        }
    }, [sessionStatus, router]);

    const isValidEmail = (email: string) => {
        const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
        return emailRegex.test(email);
    };
    const handleSubmit = async (e: any) => {
        e.preventDefault();
        const name = e.target[0].value;
        const email = e.target[1].value;
        const password = e.target[2].value;

        if (!isValidEmail(email)) {
            setError("Email is invalid");
            return;
        }

        if (!password || password.length < 8) {
            setError("Password is invalid");
            return;
        }

        try {
            const res = await fetch("/api/register", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({
                    name,
                    email,
                    password,
                }),
            });
            if (res.status === 400) {
                setError("This email is already registered");
            }
            if (res.status === 200) {
                setError("");
                router.push("/login");
            }
        } catch (error) {
            setError("Error, try again");
            console.log(error);
        }
    };

    if (sessionStatus === "loading") {
        return <h1>Loading...</h1>;
    }

    return (
        sessionStatus !== "authenticated" && (
            <div className="justify-center mt-16">
                <div className="w-full p-6 m-auto bg-white rounded-md shadow-md lg:max-w-lg">
                    <h1 className="text-3xl font-semibold text-center text-purple-700">Register</h1>
                    <form onSubmit={handleSubmit} className="space-y-4">
                        <div>
                            <label className="label">
                                <span className="text-base label-text">Name</span>
                            </label>
                            <input type="text" placeholder="Name" required className="w-full input input-bordered input-primary" />
                        </div>
                        <div>
                            <label className="label">
                                <span className="text-base label-text">Email</span>
                            </label>
                            <input type="text" placeholder="Email Address" required className="w-full input input-bordered input-primary" />
                        </div>
                        <div>
                            <label className="label">
                                <span className="text-base label-text">Password</span>
                            </label>
                            <input type="password" placeholder="Enter Password" required className="w-full input input-bordered input-primary" />
                        </div>
                        <button
                            type="submit"
                            className="btn btn-primary"
                        >
                            {" "}
                            Register
                        </button>
                        <p className="text-red-600 text-[16px] mb-4">{error && error}</p>
                    </form>
                    <div className="text-center text-gray-500 mt-4">- OR -</div>
                    <Link
                        className="block text-center text-blue-500 hover:underline mt-2"
                        href="/login"
                    >
                        Login with an existing account
                    </Link>
                </div>
            </div>
        )
    );
};

export default Register;
app\api\register\route.ts
//app\api\register\route.ts
import User from "@/models/User";
import connect from "@/utils/db";
import bcrypt from "bcryptjs"; //https://www.npmjs.com/package/bcryptjs npm install bcryptjs
import { NextResponse } from "next/server";

export const POST = async (request: any) => {
    const { name, email, password } = await request.json();

    await connect();

    const existingUser = await User.findOne({ email });

    if (existingUser) {
        return new NextResponse("Email is already in use", { status: 400 });
    }

    const hashedPassword = await bcrypt.hash(password, 5);
    const newUser = new User({
        name,
        email,
        password: hashedPassword,
    });

    try {
        await newUser.save();
        return new NextResponse("user is registered", { status: 200 });
    } catch (err: any) {
        return new NextResponse(err, {
            status: 500,
        });
    }
};
app\api\auth\[...nextauth]\route.ts
//app\api\auth\[...nextauth]\route.ts
import NextAuth from "next-auth";
import { Account, User as AuthUser } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcryptjs";
import User from "@/models/User";
import connect from "@/utils/db";

export const authOptions: any = {
    // Configure one or more authentication providers
    secret: process.env.NEXTAUTH_SECRET,
    providers: [
        CredentialsProvider({
            id: "credentials",
            name: "Credentials",
            credentials: {
                email: { label: "Email", type: "text" },
                password: { label: "Password", type: "password" },
            },
            async authorize(credentials: any) {
                await connect();
                try {
                    const user = await User.findOne({ email: credentials.email });
                    if (user) {
                        const isPasswordCorrect = await bcrypt.compare(
                            credentials.password,
                            user.password
                        );
                        if (isPasswordCorrect) {
                            return user;
                        }
                    }
                } catch (err: any) {
                    throw new Error(err);
                }
            },
        }),
    ],
    callbacks: {
        async signIn({ user, account }: { user: AuthUser; account: Account }) {
            if (account?.provider == "credentials") {
                return true;
            }
        },
    },
};

export const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
run C:\nextjs>npm run dev

Monday, January 22, 2024

Next.js 14 CRUD Create Read Update and Delete - MongoDB Daisyui Tailwind

Next.js 14 CRUD Create Read Update and Delete - MongoDB Daisyui Tailwind

Install nextjs npx create-next-app@latest https://nextjs.org/docs/getting-started/installation

Install the following

npm install react-daisyui
https://www.npmjs.com/package/react-daisyui
daisyUI components built with React, Typescript and TailwindCSS

Mongoose
npm install mongoose
https://www.npmjs.com/package/mongoose

edit tailwind.config.js Add daisyui to plugins
edit tailwind.config.js
//edit tailwind.config.js
import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      backgroundImage: {
        'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
        'gradient-conic':
          'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
      },
    },
  },
  plugins: [require('daisyui')], //https://www.npmjs.com/package/react-daisyui
}
export default config
.env
MONGODB_URI=mongodb://127.0.0.1/nextjs14
app\layout.js
//app\layout.js
import { Inter } from 'next/font/google'
import './globals.css'
import Navbar from "@/components/Navbar";

const inter = Inter({ subsets: ['latin'] })

export const metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
}

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body className={inter.className}>
        <div className="container mx-auto px-4">
          <Navbar />
          <div className="pb-8">{children}</div>
        </div>
      </body>
    </html>
  )
}
components\Navbar.jsx
//components\Navbar.jsx
import Link from "next/link";

export default function Navbar() {
    return (
        <div className="navbar bg-base-100">
        <div className="flex-1">
            <a className="btn btn-ghost text-xl">Cairocoders</a>
        </div>
        <div className="flex-none">
            <div className="dropdown dropdown-end">
            <div tabIndex={0} role="button" className="btn btn-ghost btn-circle">
                <div className="indicator">
                <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4M7 13L5.4 5M7 13l-2.293 2.293c-.63.63-.184 1.707.707 1.707H17m0 0a2 2 0 100 4 2 2 0 000-4zm-8 2a2 2 0 11-4 0 2 2 0 014 0z" /></svg>
                <span className="badge badge-sm indicator-item">8</span>
                </div>
            </div>
            <div tabIndex={0} className="mt-3 z-[1] card card-compact dropdown-content w-52 bg-base-100 shadow">
                <div className="card-body">
                <span className="font-bold text-lg">8 Items</span>
                <span className="text-info">Subtotal: $999</span>
                <div className="card-actions">
                    <button className="btn btn-primary btn-block">View cart</button>
                </div>
                </div>
            </div>
            </div>
            <div className="dropdown dropdown-end">
            <div tabIndex={0} role="button" className="btn btn-ghost btn-circle avatar">
                <div className="w-10 rounded-full">
                <img alt="Tailwind CSS Navbar component" src="https://daisyui.com/images/stock/photo-1534528741775-53994a69daeb.jpg" />
                </div>
            </div>
            <ul tabIndex={0} className="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
                <li>
                <a className="justify-between">
                    Profile
                    <span className="badge">New</span>
                </a>
                </li>
                <li><a>Settings</a></li>
                <li><a>Logout</a></li>
            </ul>
            </div>
        </div>
        </div>
    );
}
.env
MONGODB_URI=mongodb://127.0.0.1/nextjs14
components\EditProductForm.jsx
//components\EditProductForm.jsx
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";

export default function EditProductForm({ id, name, image, price,category }) {
    const [newName, setNewTitle] = useState(name);
    const [newImage, setNewImage] = useState(image);
    const [newPrice, setNewPrice] = useState(price);
    const [newCategory, setNewCategory] = useState(category);

    const router = useRouter();

    const handleSubmit = async (e) => {
        e.preventDefault();

        try {
            const res = await fetch(`http://localhost:3000/api/products/${id}`, {
                method: "PUT",
                headers: {
                    "Content-type": "application/json",
                },
                body: JSON.stringify({ newName, newImage, newPrice, newCategory }),
            });

            if (!res.ok) {
                throw new Error("Failed to update Product");
            }

            router.refresh();
            router.push("/products");
        } catch (error) {
            console.log(error);
        }
    };

    return (
        <>
        <div className="flex justify-between items-center">
            <h1 className="font-bold py-10 text-2xl">Update Product</h1>
        </div>
        <form onSubmit={handleSubmit} className="flex flex-col gap-3">
            <input
                onChange={(e) => setNewTitle(e.target.value)}
                value={newName}
                className="input input-bordered input-accent w-full max-w-xs"
                type="text"
            />

            <input
                onChange={(e) => setNewImage(e.target.value)}
                value={newImage}
                className="input input-bordered input-accent w-full max-w-xs"
                type="text"
            />
            <input
                onChange={(e) => setNewPrice(e.target.value)}
                value={newPrice}
                className="input input-bordered input-accent w-full max-w-xs"
                type="text"
            />
            <input
                onChange={(e) => setNewCategory(e.target.value)}
                value={newCategory}
                className="input input-bordered input-accent w-full max-w-xs"
                type="text"
            />

            <button className="btn btn-primary w-full max-w-xs">
                Update Product
            </button>
        </form>
        </>
    );
}
components\ProductsList.jsx
//components\EditProductForm.jsx
import Link from "next/link";
import RemoveBtn from "./RemoveBtn";
import Image from 'next/image'

const getProducts = async () => {
    try {
        const res = await fetch("http://localhost:3000/api/products", {
            cache: "no-store",
        });

        if (!res.ok) {
            throw new Error("Failed to fetch products");
        }

        return res.json();
    } catch (error) {
        console.log("Error loading products: ", error);
    }
};

export default async function ProductssList() {
    const { products } = await getProducts();

    return (
        <>
            <div className="overflow-x-auto">
                <div className="flex justify-between items-center">
                    <h1 className="font-bold py-10 text-2xl">Next.js 14 CRUD Crate, Read, Update and Delete - MongoDB Daisyui TailwindCSS</h1>
                </div>
                <div className="text-right">
                    <Link className="btn btn-primary" href={"/addProduct"}>
                        Add Product
                    </Link>
                </div>
            <table className="table">
                <thead>
                <tr>
                    <th>
                    <label>
                        <input type="checkbox" className="checkbox" />
                    </label>
                    </th>
                    <th>Name</th>
                    <th>Price</th>
                    <th>Category</th>
                    <th />
                </tr>
                </thead>
                <tbody>
                    {products.map((rs) => (
                    <tr className="hover" key={rs._id}>
                        <th>
                        <label>
                            <input type="checkbox" className="checkbox" />
                        </label>
                        </th>
                        <td>
                            <div className="flex items-center gap-3">
                                <div className="avatar">
                                <div className="mask mask-squircle w-12 h-12">
                                    <Image
                                            src={rs.image}
                                            alt={rs.name}
                                            width={80}
                                            height={80}
                                            className="rounded-lg"
                                    />
                                </div>
                                </div>
                                <div>
                                <div className="font-bold">{rs.name}</div>
                                </div>
                            </div>
                        </td>
                        <td>
                            ${rs.price}
                        </td>
                        <td>{rs.category}</td>
                        <th>
                            <Link href={`/editProduct/${rs._id}`}>
                                <button className="btn btn-primary">Edit</button>
                            </Link>
                            <RemoveBtn id={rs._id} />
                        </th>
                    </tr>
                    ))}                    
                </tbody>
            </table>
            </div>
        </>
    );
}
components\RemoveBtn.jsx
//components\RemoveBtn.jsx
"use client";

import { useRouter } from "next/navigation";

export default function RemoveBtn({ id }) {
    const router = useRouter();
    const removeProduct= async () => {
        const confirmed = confirm("Are you sure?");

        if (confirmed) {
            const res = await fetch(`http://localhost:3000/api/products?id=${id}`, {
                method: "DELETE",
            });

            if (res.ok) {
                router.refresh();
            }
        }
    };

    return (
        <button onClick={removeProduct} className="btn btn-error ml-2">
            Delete
        </button>
    );
}
libs\mongodb.js
//libs\mongodb.js
import mongoose from "mongoose";

const connectMongoDB = async () => {
    try {
        await mongoose.connect(process.env.MONGODB_URI);
        console.log("Connected to MongoDB.");
    } catch (error) {
        console.log(error);
    }
};

export default connectMongoDB;
models\ProductModel.js
//models\ProductModel.js
import mongoose, { Schema } from "mongoose";

const topicSchema = new Schema(
    {
        name: { type: String, required: true },
        category: { type: String, required: true },
        image: { type: String, required: true },
        price: { type: Number, required: true },
    },
    {
        timestamps: true,
    }
);

const ProductModel = mongoose.models.Product || mongoose.model("Product", topicSchema);

export default ProductModel;
app\products\page.js
//app\products\page.js
import Productlist from "@/components/ProductsList";

export default function Home() {
    return ;
}
app\addProduct\page.jsx
//app\addProduct\page.jsx
"use client";

import { useState } from "react";
import { useRouter } from "next/navigation";

export default function AddProduct() {
    const [name, setName] = useState("");
    const [image, setImage] = useState("");
    const [price, setPrice] = useState("");
    const [category, setCategory] = useState("");

    const router = useRouter();

    const handleSubmit = async (e) => {
        e.preventDefault();

        if (!name || !image) {
            alert("Name and image are required.");
            return;
        }

        try {
            const res = await fetch("http://localhost:3000/api/products", {
                method: "POST",
                headers: {
                    "Content-type": "application/json",
                },
                body: JSON.stringify({ name, image, price, category }),
            });

            if (res.ok) {
                router.push("/products");
            } else {
                throw new Error("Failed to create a Product");
            }
        } catch (error) {
            console.log(error);
        }
    };

    return (
        <>
        <div className="flex justify-between items-center">
            <h1 className="font-bold py-10 text-2xl">Add New Product</h1>
        </div>
        <form onSubmit={handleSubmit} className="flex flex-col gap-3">
            <input
                onChange={(e) => setName(e.target.value)}
                value={name}
                    className="input input-bordered input-accent w-full max-w-xs"
                type="text"
                placeholder="Product Name"
            />

            <input
                onChange={(e) => setImage(e.target.value)}
                value={image}
                    className="input input-bordered input-accent w-full max-w-xs"
                type="text"
                placeholder="/images/1.jpg"
                defaultValue="/images/1.jpg"
            />
            <input
                onChange={(e) => setPrice(e.target.value)}
                value={price}
                className="input input-bordered input-accent w-full max-w-xs"
                type="number"
                placeholder="1"
                defaultValue="1"
            />
            <input
                onChange={(e) => setCategory(e.target.value)}
                value={category}
                className="input input-bordered input-accent w-full max-w-xs"
                type="text"
                placeholder="Product Category"
            />

            <button
                type="submit"
                    className="btn btn-primary w-full max-w-xs"
            >
                Add Product
            </button>
        </form>
        </>
    );
}
app\editProduct\[id]\page.js
//app\editProduct\[id]\page.js
import EditProductForm from "@/components/EditProductForm";

const getProductById = async (id) => {
    try {
        const res = await fetch(`http://localhost:3000/api/products/${id}`, {
            cache: "no-store",
        });

        if (!res.ok) {
            throw new Error("Failed to fetch product");
        }

        return res.json();
    } catch (error) {
        console.log(error);
    }
};

export default async function EditProduct({ params }) {
    const { id } = params;
    const { product } = await getProductById(id);
    const { name, image, price,category } = product;

    return <EditProductForm id={id} name={name} image={image} price={price} category={category} />;
}
app\api\products\route.js
//app\api\products\route.js
import connectMongoDB from "@/libs/mongodb";
import Product from "@/models/ProductModel";
import { NextResponse } from "next/server";

export async function GET() {
    await connectMongoDB();
    const products = await Product.find();
    return NextResponse.json({ products });
}

export async function POST(request) {
    const { name, image,price,category } = await request.json();
    await connectMongoDB();
    await Product.create({ name, image, price, category });
    return NextResponse.json({ message: "Product Created" }, { status: 201 });
}

export async function DELETE(request) {
    const id = request.nextUrl.searchParams.get("id");
    await connectMongoDB();
    await Product.findByIdAndDelete(id);
    return NextResponse.json({ message: "Product deleted" }, { status: 200 });
}
app\api\products\[id]\route.js
//app\api\products\[id]\route.js
import connectMongoDB from "@/libs/mongodb";
import Product from "@/models/ProductModel";
import { NextResponse } from "next/server";

export async function PUT(request, { params }) {
    const { id } = params;
    const { newName: name, newImage: image, newPrice: price, newCategory: category } = await request.json();
    await connectMongoDB();
    await Product.findByIdAndUpdate(id, { name, image, price, category});
    return NextResponse.json({ message: "Product updated" }, { status: 200 });
}

export async function GET(request, { params }) {
    const { id } = params;
    await connectMongoDB();
    const product = await Product.findOne({ _id: id });
    return NextResponse.json({ product }, { status: 200 });
}
run C:\nextjs>npm run dev

Wednesday, January 17, 2024

Next.js 14 MongoDB User Registration

Next.js 14 MongoDB User Registration

Install nextjs npx create-next-app@latest https://nextjs.org/docs/getting-started/installation

Install the following

npm install react-daisyui
https://www.npmjs.com/package/react-daisyui
daisyUI components built with React, Typescript and TailwindCSS

Mongoose
npm install mongoose
https://www.npmjs.com/package/mongoose

edit tailwind.config.ts Add daisyui to plugins
edit tailwind.config.ts
//edit tailwind.config.ts
import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      backgroundImage: {
        'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
        'gradient-conic':
          'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
      },
    },
  },
  plugins: [require('daisyui')], //https://www.npmjs.com/package/react-daisyui
}
export default config
.env
MONGODB_URI=mongodb://127.0.0.1/nextjs14
app\layout.tsx
//app\layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Next.js 14 MongoDB User Registration',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}
app\register\page.tsx
//app\register\page.tsx
import { Metadata } from 'next'
import Form from './Form'

export const metadata: Metadata = {
    title: 'Register',
}

export default async function Register() {
    return 
}
app\register\Form.tsx
//app\register\Form.tsx
'use client'

import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'
import { useEffect } from 'react'
import { SubmitHandler, useForm } from 'react-hook-form' //npm install react-hook-form https://www.npmjs.com/package/react-hook-form
import toast from 'react-hot-toast'

type Inputs = {
    name: string
    email: string
    password: string
    confirmPassword: string
}

const Form = () => {
    const params = useSearchParams()
    const router = useRouter()
    let callbackUrl = params.get('callbackUrl') || '/'
    const {
        register,
        handleSubmit,
        getValues,
        formState: { errors, isSubmitting },
    } = useForm<Inputs>({
        defaultValues: {
            name: '',
            email: '',
            password: '',
            confirmPassword: '',
        },
    })

    const formSubmit: SubmitHandler<Inputs> = async (form) => {
        const { name, email, password } = form

        try {
            const res = await fetch('/api/auth/register', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({
                    name,
                    email,
                    password,
                }),
            })
            console.log(res);
            console.log("Success login");
            if (res.ok) {
                return router.push(
                    `/signin?callbackUrl=${callbackUrl}&success=Account has been created`
                )
            } else {
                const data = await res.json()
                throw new Error(data.message)
            }
        } catch (err: any) {
            const error =
                err.message && err.message.indexOf('E11000') === 0
                    ? 'Email is duplicate'
                    : err.message
            toast.error(error || 'error')
        }
    }
    return (
        <div className="mx-auto max-w-2xl lg:max-w-7xl">
            <div className="flex justify-between items-center">
                <h1 className="font-bold py-10 text-2xl">Next.js 14 MongoDB User Registration</h1>
            </div>
        <div className="max-w-sm  mx-auto card bg-base-300 my-4">
            <div className="card-body">
                <h1 className="card-title">Register</h1>
                <form onSubmit={handleSubmit(formSubmit)}>
                    <div className="my-2">
                        <label className="label" htmlFor="name">
                            Name
                        </label>
                        <input
                            type="text"
                            id="name"
                            {...register('name', {
                                required: 'Name is required',
                            })}
                            className="input input-bordered w-full max-w-sm"
                        />
                        {errors.name?.message && (
                            <div className="text-error">{errors.name.message}</div>
                        )}
                    </div>
                    <div className="my-2">
                        <label className="label" htmlFor="email">
                            Email
                        </label>
                        <input
                            type="text"
                            id="email"
                            {...register('email', {
                                required: 'Email is required',
                                pattern: {
                                    value: /[a-z0-9]+@[a-z]+\.[a-z]{2,3}/,
                                    message: 'Email is invalid',
                                },
                            })}
                            className="input input-bordered w-full max-w-sm"
                        />
                        {errors.email?.message && (
                            <div className="text-error"> {errors.email.message}</div>
                        )}
                    </div>
                    <div className="my-2">
                        <label className="label" htmlFor="password">
                            Password
                        </label>
                        <input
                            type="password"
                            id="password"
                            {...register('password', {
                                required: 'Password is required',
                            })}
                            className="input input-bordered w-full max-w-sm"
                        />
                        {errors.password?.message && (
                            <div className="text-error">{errors.password.message}</div>
                        )}
                    </div>
                    <div className="my-2">
                        <label className="label" htmlFor="confirmPassword">
                            Confirm Password
                        </label>
                        <input
                            type="password"
                            id="confirmPassword"
                            {...register('confirmPassword', {
                                required: 'Confirm Password is required',
                                validate: (value) => {
                                    const { password } = getValues()
                                    return password === value || 'Passwords should match!'
                                },
                            })}
                            className="input input-bordered w-full max-w-sm"
                        />
                        {errors.confirmPassword?.message && (
                            <div className="text-error">{errors.confirmPassword.message}</div>
                        )}
                    </div>
                    <div className="my-2">
                        <button
                            type="submit"
                            disabled={isSubmitting}
                            className="btn btn-primary w-full"
                        >
                            {isSubmitting && (
                                <span className="loading loading-spinner"></span>
                            )}
                            Register
                        </button>
                    </div>
                </form>

                <div className="divider"> </div>
                <div>
                    Already have an account?{' '}
                    <Link className="link" href={`/signin?callbackUrl=${callbackUrl}`}>
                        Login
                    </Link>
                </div>
            </div>
        </div>
        </div>
    )
}

export default Form
app\signin\page.tsx
//app\signin\page.tsx
import { Metadata } from 'next'
import Form from './Form'

export const metadata: Metadata = {
    title: 'Sign in',
}

export default async function Signin() {
    return <Form />
}
app\signin\Form.tsx
//app\signin\Form.tsx
'use client'

import Link from 'next/link'

const Form = () => {
    return (
        <div className="max-w-sm  mx-auto card bg-base-300">
            <div className="card-body">
                <h1 className="card-title">Sign in</h1>
                <form>
                    <div className="my-2">
                        <label className="label" htmlFor="email">
                            Email
                        </label>
                        <input
                            type="text"
                            id="email"
                            className="input input-bordered w-full max-w-sm"
                        />
                    </div>
                    <div className="my-2">
                        <label className="label" htmlFor="password">
                            Password
                        </label>
                        <input
                            type="password"
                            id="password"
                            className="input input-bordered w-full max-w-sm"
                        />
                    </div>
                    <div className="my-4">
                        <button
                            type="submit"
                            className="btn btn-primary w-full"
                        >
                            Sign in
                        </button>
                    </div>
                </form>
                <div>
                    <Link className="link" href={'/register'}>
                        Register
                    </Link>
                </div>
            </div>
        </div>
    )
}
export default Form
app\api\auth\register\route.ts
//app\api\auth\register\Form.ts
import { NextRequest } from 'next/server'
import bcrypt from 'bcryptjs' //npm i bcryptjs https://www.npmjs.com/package/bcryptjs
import dbConnect from '@/lib/dbConnect'
import UserModel from '@/lib/models/UserModel'

export const POST = async (request: NextRequest) => {
    const { name, email, password } = await request.json()
    console.log(name);
    await dbConnect()
    const hashedPassword = await bcrypt.hash(password, 5)
    const newUser = new UserModel({
        name,
        email,
        password: hashedPassword,
    })
    try {
        await newUser.save()
        return Response.json(
            { message: 'User has been created' },
            {
                status: 201,
            }
        )
    } catch (err: any) {
        return Response.json(
            { message: err.message },
            {
                status: 500,
            }
        )
    }
}
lib\dbConnect.ts
//lib\dbConnect.ts
import mongoose from 'mongoose'

async function dbConnect() {
    try {
        await mongoose.connect(process.env.MONGODB_URI!)
    } catch (error) {
        throw new Error('Connection failed!')
    }
}

export default dbConnect
lib\models\UserModel.ts
//lib\models\UserModel.ts
import mongoose from 'mongoose'

export type User = {
    _id: string
    name: string
    email: string
    isAdmin: boolean
}

const UserSchema = new mongoose.Schema(
    {
        name: {
            type: String,
            required: true,
        },
        email: {
            type: String,
            required: true,
            unique: true,
        },
        password: {
            type: String,
            required: true,
        },
        isAdmin: { type: Boolean, required: true, default: false },
    },
    { timestamps: true }
)

const UserModel = mongoose.models?.User || mongoose.model('User', UserSchema)

export default UserModel
run C:\nextjs>npm run dev

Tuesday, January 16, 2024

Next.js 14 Pagination MongoDB

Next.js 14 Pagination MongoDB

Install nextjs npx create-next-app@latest https://nextjs.org/docs/getting-started/installation

Install the following

npm install react-daisyui
https://www.npmjs.com/package/react-daisyui
daisyUI components built with React, Typescript and TailwindCSS

Mongoose
npm install mongoose
https://www.npmjs.com/package/mongoose

edit tailwind.config.ts Add daisyui to plugins
edit tailwind.config.ts
//edit tailwind.config.ts
import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      backgroundImage: {
        'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
        'gradient-conic':
          'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
      },
    },
  },
  plugins: [require('daisyui')], //https://www.npmjs.com/package/react-daisyui
}
export default config
.env
MONGODB_URI=mongodb://127.0.0.1/
app\page.tsx
//
import { dbConnect } from "@/lib/connectMongo";
import Link from "next/link";
import Image from 'next/image'

async function getData(perPage, page) {
    try {
        // DB Connect
        const client = await dbConnect();
        const db = client.db("nextjs14");

        // DB Query
        const items = await db
            .collection("products")
            .find({})
            .skip(perPage * (page - 1))
            .limit(perPage)
            .toArray();

        const itemCount = await db.collection("products").countDocuments({});

        const respnse = { items, itemCount };
        //console.log(items);
        console.log(itemCount);
        return respnse;
    } catch (error) {
        throw new Error("Failed to fetch data. Please try again later.");
    }
}

export default async function Page({ searchParams }) {
    let page = parseInt(searchParams.page, 10);
    page = !page || page < 1 ? 1 : page;
    const perPage = 4;
    const data = await getData(perPage, page);

    const totalPages = Math.ceil(data.itemCount / perPage);

    const prevPage = page - 1 > 0 ? page - 1 : 1;
    const nextPage = page + 1;
    const isPageOutOfRange = page > totalPages;

    const pageNumbers = [];
    const offsetNumber = 3;
    for (let i = page - offsetNumber; i <= page + offsetNumber; i++) {
        if (i >= 1 && i <= totalPages) {
            pageNumbers.push(i);
        }
    }

    return (
        <>
            <div className="container mx-auto">
                <div className="flex justify-between items-center">
                    <h1 className="font-bold py-10 text-2xl">Next.js 14 Pagination MongoDB</h1>
                </div>
                <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
                    <thead className="bg-gray-100 dark:bg-gray-700">
                        <tr>
                            <th scope="col" className="p-4">
                                <div className="flex items-center">
                                    <input id="checkbox-all" type="checkbox" className="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
                                    <label htmlFor="checkbox-all" className="sr-only">checkbox</label>
                                </div>
                            </th>
                            <th className="py-3 text-left">Image</th>
                            <th className="py-3 text-left">Product Name</th>
                            <th className="py-3 text-left">Price</th>
                            <th className="py-3 text-left">Category</th>
                            <th className="py-3 text-left">Actions</th>
                        </tr>
                    </thead>
                    <tbody className="bg-white divide-y divide-gray-200 dark:bg-gray-800 dark:divide-gray-700">
                        {data.items.map((item) => (
                            <tr key={item._id} className="hover:bg-gray-100 dark:hover:bg-gray-700">
                                    <td className="p-4 w-4">
                                        <div className="flex items-center">
                                            <input id="checkbox-table-1" type="checkbox" className="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
                                            <label htmlFor="checkbox-table-1" className="sr-only">checkbox</label>
                                        </div>
                                    </td>
                                    <td>
                                        <Image
                                            src={item.image}
                                            alt={item.name}
                                            width={80}
                                            height={80}
                                            className="rounded-lg"
                                        />
                                    </td>
                                    <td>{item.name}</td>
                                    <td>${item.price}</td>
                                    <td>{item.category}</td>
                                    <td>
                                        Delete
                                    </td>
                                </tr>
                        ))}
                    </tbody>
                </table>

                {isPageOutOfRange ? (
                    <div>No more pages...</div>
                ) : (

                    <div className="flex justify-center items-center mt-16">

                        <div className="flex border-[1px] gap-4 rounded-[10px] border-light-green p-4">
                            {page === 1 ? (
                                <div className="opacity-60 py-2 px-5" aria-disabled="true">
                                    Previous
                                </div>
                            ) : (
                                <Link href={`?page=${prevPage}`} className="py-2 px-5" aria-label="Previous Page">
                                    Previous
                                </Link>
                            )}
                                
                            {pageNumbers.map((pageNumber, index) => (
                                <Link
                                    key={index}
                                    className={
                                        page === pageNumber
                                            ? "bg-blue-500 font-bold py-2 px-5 rounded text-white"
                                            : "bg-gray-500 hover:bg-gray-400 font-bold py-2 px-5 rounded text-white"
                                    }
                                    href={`?page=${pageNumber}`}
                                >
                                    {pageNumber}
                                </Link>
                            ))}

                            {page === totalPages ? (
                                <div className="opacity-60 py-2 px-5" aria-disabled="true">
                                    Next
                                </div>
                            ) : (
                                <Link href={`?page=${nextPage}`} className="py-2 px-5" aria-label="Next Page">
                                    Next
                                </Link>
                            )}
                        </div>
                    </div>

                )}

            </div>
        </>
    );
}
app\layout.tsx
//app\layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Next.js 14 MongoDB Pagination',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}
lib\connectMongo.ts
//lib\connectMongo.ts
import { MongoClient } from "mongodb";
const MONGODB_URI = process.env.MONGODB_URI;
let client = null;

export async function dbConnect() {
    if (client) {
        return client;
    }

    if (!MONGODB_URI) {
        console.log("MongoDb URI not found.");
    }

    try {
        client = await MongoClient.connect(MONGODB_URI);
        console.log("Connected to MongoDb successfully.");
        return client;
    } catch (error) {
        console.error("Error connecting to the database:", error);
    }
}
run C:\nextjs>npm run dev

Monday, January 8, 2024

Next.js 14 Server Actions MongoDB - List all data, Create and Delete

Next.js 14 Server Actions MongoDB - List all data, Create and Delete

Install nextjs npx create-next-app@latest https://nextjs.org/docs/getting-started/installation

Install the following
npm i mongoose-zod
https://github.com/andreww2012/mongoose-zod
A library which allows to author mongoose ("a MongoDB object modeling tool") schemas using zod ("a TypeScript-first schema declaration and validation library").

npm install react-daisyui
https://www.npmjs.com/package/react-daisyui
daisyUI components built with React, Typescript and TailwindCSS

npm install react-hot-toast
https://www.npmjs.com/package/react-hot-toast
Add beautiful notifications to your React app with react-hot-toast.

edit tailwind.config.ts Add daisyui to plugins
edit tailwind.config.ts
//edit tailwind.config.ts
import type { Config } from 'tailwindcss'

const config: Config = {
  content: [
    './pages/**/*.{js,ts,jsx,tsx,mdx}',
    './components/**/*.{js,ts,jsx,tsx,mdx}',
    './app/**/*.{js,ts,jsx,tsx,mdx}',
  ],
  theme: {
    extend: {
      backgroundImage: {
        'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))',
        'gradient-conic':
          'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))',
      },
    },
  },
  plugins: [require('daisyui')], //https://www.npmjs.com/package/react-daisyui
}
export default config
.env
MONGODB_URI=mongodb://127.0.0.1/nextjs14
app\page.tsx
//app\page.tsx
import Image from 'next/image' 
//https://www.npmjs.com/package/react-hot-toast
//https://www.npmjs.com/package/react-daisyui
//https://github.com/andreww2012/mongoose-zod
//https://www.npmjs.com/package/mongoose
//npm i mongoose zod daisyui react-hot-toast 
import { Toaster } from 'react-hot-toast'
import CreateForm from './create-form'
import dbConnect from '@/lib/db-connect'
import ProductModel, { Product } from '@/lib/product-model'
import DeleteForm from './delete-form'

export default async function Home() {
  await dbConnect()
  const products = (await ProductModel.find({}).sort({
    _id: -1,
  })) as Product[]
  return (
    <div className="mx-auto max-w-2xl lg:max-w-7xl">
      <div className="flex justify-between items-center">
        <h1 className="font-bold py-10 text-2xl">Next.js 14 Server Actions MongoDB - List all data, Create Product and Delete</h1>
        <Toaster />
        <CreateForm />
      </div>
      <div className="inline-block min-w-full align-middle">
      <table className="min-w-full divide-y divide-gray-200 table-fixed dark:divide-gray-700">
        <thead className="bg-gray-100 dark:bg-gray-700">
            <tr>
              <th scope="col" className="p-4">
                <div className="flex items-center">
                  <input id="checkbox-all" type="checkbox" className="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
                  <label htmlFor="checkbox-all" className="sr-only">checkbox</label>
                </div>
              </th>
              <th className="py-3 text-left">Image</th>
              <th className="py-3 text-left">Product Name</th>
              <th className="py-3 text-left">Price</th>
              <th className="py-3 text-left">Category</th>
              <th className="py-3 text-left">Actions</th>
            </tr>
        </thead>
        <tbody className="bg-white divide-y divide-gray-200 dark:bg-gray-800 dark:divide-gray-700">
          {products.length === 0 ? (
            <tr>
              <td colSpan={5}>No product found</td>
            </tr>
          ) : (
            products.map((product: Product) => (
              <tr key={product._id} className="hover:bg-gray-100 dark:hover:bg-gray-700"> 
                <td className="p-4 w-4">
                  <div className="flex items-center">
                    <input id="checkbox-table-1" type="checkbox" className="w-4 h-4 text-blue-600 bg-gray-100 rounded border-gray-300 focus:ring-blue-500 dark:focus:ring-blue-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600" />
                    <label htmlFor="checkbox-table-1" className="sr-only">checkbox</label>
                  </div>
                </td>
                <td>
                  <Image
                    src={product.image}
                    alt={product.name}
                    width={80}
                    height={80}
                    className="rounded-lg"
                  />
                </td>
                <td>{product.name}</td>
                <td>${product.price}</td>
                <td>{product.category}</td>
                <td>
                  <DeleteForm
                    _id={product._id.toString()}
                    name={product.name}
                  />
                </td>
              </tr>
            ))
          )}
        </tbody>
      </table>
    </div>
    </div>
  )
}
app\layout.tsx
//app\layout.tsx
import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
  title: 'Next.js 14 Server Actions MongoDB - List all data, Create and Delete',
  description: 'Generated by create next app',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body className={inter.className}>{children}</body>
    </html>
  )
}
app\create-form.tsx
//
'use client'

import { useFormState, useFormStatus } from 'react-dom'
import { createProduct } from '@/lib/actions'
import { useEffect, useRef } from 'react'
import toast from 'react-hot-toast'

export default function CreateForm() {
    const [state, formAction] = useFormState(createProduct, {
        message: '',
    })
    const { pending } = useFormStatus()
    const ref = useRef<HTMLFormElement>(null)
    useEffect(() => {
        if (state.message.indexOf('Created product') === 0) {
            ; (document.getElementById('my_modal') as any)!.close()
            ref.current?.reset()
            toast(state.message)
        } else if (state.message) {
            toast(state.message)
        }
    }, [state.message])

    return (
        <div>
            <button
                className="btn btn-primary"
                onClick={() =>
                    (document.getElementById('my_modal')! as any).showModal()
                }
            >
                Create Product
            </button>
            <dialog id="my_modal" className="modal">
                <div className="modal-box">
                    <h2 className="tex-2xl font-bold pm-4">Create Product</h2>
                    <form ref={ref} action={formAction}>
                        <div className="form-control w-full max-w-xs py-4">
                            <label htmlFor="name">Name</label>
                            <input
                                type="text"
                                id="name"
                                name="name"
                                className="input input-bordered w-full max-w-xs"
                                required
                            />
                        </div>
                        <div className="form-control w-full max-w-xs py-4">
                            <label htmlFor="image">Image</label>
                            <input
                                type="text"
                                id="image"
                                name="image"
                                className="input input-bordered w-full max-w-xs"
                                required
                                defaultValue="/images/1.jpg"
                            />
                        </div>
                        <div className="form-control w-full max-w-xs py-4">
                            <label htmlFor="price">Price</label>
                            <input
                                type="number"
                                id="price"
                                name="price"
                                className="input input-bordered w-full max-w-xs"
                                required
                                defaultValue="1"
                            />
                        </div>
                        <div className="form-control w-full max-w-xs py-4">
                            <label htmlFor="name">Category</label>
                            <input
                                type="text"
                                id="category"
                                name="category"
                                className="input input-bordered w-full max-w-xs"
                                required
                            />
                        </div>
                        <button
                            className="btn btn-primary mr-3"
                            type="submit"
                            disabled={pending}
                        >
                            Create
                        </button>
                        <button
                            type="button"
                            className="btn btn-ghost"
                            onClick={() =>
                                (document.getElementById('my_modal') as any).close()
                            }
                        >
                            Back
                        </button>
                    </form>
                </div>
            </dialog>
        </div>
    )
}
app\delete-form.tsx
//app\delete-form.tsx
'use client'

import { deleteProduct } from '@/lib/actions'
import { useFormStatus } from 'react-dom'
import toast from 'react-hot-toast'

export default function DeleteForm({
    _id,
    name,
}: {
    _id: string
    name: string
}) {
    const { pending } = useFormStatus()
    return (
        <form
            action={async (formData) => {
                const res = await deleteProduct(formData)
                toast(res.message)
            }}
        >
            <input type="hidden" name="_id" value={_id} />
            <input type="hidden" name="name" value={name} />
            <button type="submit" disabled={pending} className="btn btn-ghost">
                Delete
            </button>
        </form>
    )
}
lib\db-connect.ts
//lib\db-connect.ts
import mongoose from 'mongoose'

export default async function dbConnect() {
    try {
        await mongoose.connect(process.env.MONGODB_URI!)
        console.log("Success Connection");
    } catch (error) {
        throw new Error('Connection failed!')
    }
}
lib\product-model.ts
//lib\product-model.ts
import mongoose from 'mongoose'

export type Product = {
    _id: string
    name: string
    image: string
    price: number
    category: string
}

const productSchema = new mongoose.Schema(
    {
        name: { type: String, required: true, unique: true },
        image: { type: String, required: true },
        price: { type: Number, required: true },
        category: { type: String, required: true },
    },
    {
        timestamps: true,
    }
)

const ProductModel =
    mongoose.models.Product || mongoose.model('Product', productSchema)
export default ProductModel
lib\actions.ts
//lib\actions.ts
'use server'
import { revalidatePath } from 'next/cache'
import ProductModel from './product-model'
import dbConnect from './db-connect'
import { z } from 'zod'

export async function createProduct(prevState: any, formData: FormData) {
    const schema = z.object({
        name: z.string().min(3),
        image: z.string().min(1),
        price: z.number().min(1),
        category: z.string().min(1),
    })
    const parse = schema.safeParse({
        name: formData.get('name'),
        image: formData.get('image'),
        price: Number(formData.get('price')),
        category: formData.get('category'),
    })
    if (!parse.success) {
        console.log(parse.error)
        return { message: 'Form data is not valid' }
    }
    const data = parse.data
    try {
        await dbConnect()
        const product = new ProductModel(data)
        await product.save()
        revalidatePath('/')
        return { message: `Created product ${data.name}` }
    } catch (e) {
        return { message: 'Failed to create product' }
    }
}

export async function deleteProduct(formData: FormData) {
    const schema = z.object({
        _id: z.string().min(1),
        name: z.string().min(1),
    })
    const data = schema.parse({
        _id: formData.get('_id'),
        name: formData.get('name'),
    })

    try {
        await dbConnect()
        await ProductModel.findOneAndDelete({ _id: data._id })
        revalidatePath('/')
        console.log({ message: `Deleted product ${data.name}` })
        return { message: `Deleted product ${data.name}` }
    } catch (e) {
        return { message: 'Failed to delete product' }
    }
}
run C:\nextjs>npm run dev

Saturday, November 14, 2020

Live Username Available using Python Flask MongoDB and Jquery Ajax


Live Username Available using Python Flask MongoDB and Jquery Ajax

if you start registration it will ask your username if no record found in the database Username not available if username available the message display Username Available and the registration button is disable 
app.py
#app.py
from flask import Flask, render_template, flash, redirect, url_for, request, json
from flask_mongoengine import MongoEngine #ModuleNotFoundError: No module named 'flask_mongoengine' = (venv) C:\flaskmyproject>pip install flask-mongoengine  
 
app = Flask(__name__)

app.config['MONGODB_SETTINGS'] = {
    'db': 'dbmongocrud',
    'host': 'localhost',
    'port': 27017
}
db = MongoEngine()
db.init_app(app)	
  
class User(db.Document):
    user_name = db.StringField()
    email = db.StringField()
    password = db.StringField()
	
@app.route('/',methods=['POST','GET'])
def index():
    message = ''
    if request.method == 'POST':
        # Get Form Fields
        _username = request.form['username']
        txtemail = request.form['txtemail']
        txtpass = request.form['txtpass']
        usersave = User(user_name=_username, email=txtemail, password=txtpass)
        usersave.save()
        message = 'Succefully Register'
    return render_template('checkusername.html', message=message)
 
@app.route('/check_username', methods=['POST'])
def check_username():
    if request.method == 'POST':
        username = request.form['username']
        total = User.objects(user_name=username).count()
        print(username)
        if total > 0:
            msg =  '{ "html":"Ok"}'
            msghtml = json.loads(msg)
            return msghtml["html"] 
        else:	
            msg =  '{ "html":"No found"}'
            msghtml = json.loads(msg)
            return msghtml["html"] 
	
if __name__ == '__main__':
    app.run(debug=True)
templates/checkusername.html
//templates/checkusername.html
<html>  
 <head>  
  <title>Live Username Available using Python Flask MongoDB and Jquery Ajax</title>  
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.0/jquery.min.js"></script>
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />
  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>  
  <style>  
  body  
  {  
   margin:0;  
   padding:0;  
   background-color:#f1f1f1;  
  }  
  .box  
  {  
   width:800px;  
   border:1px solid #ccc;  
   background-color:#fff;  
   border-radius:5px;
   margin-top:36px;  
  }  
  </style>  
 <script>  
$(function() {
    $('#username').blur(function(){
		var username = $(this).val();
        $.ajax({
            url: '/check_username',
            data: $('form').serialize(),
            type: 'POST',
            success: function(response) {
                if(response == 'Ok'){
					$('#availability').html('<span class="text-success">Username Available</span>');
					$('#register').attr("disabled", true);
                }else{
					$('#availability').html('<span class="text-danger">Username not available</span>');
					$('#register').attr("disabled", false);
				}
            },
            error: function(error) {
                console.log(error);
            }
        });
    });
}); 
</script> 
 </head>  
 <body>  
  <div class="container box">  
   <div class="form-group">  
    <h3 align="center">Live Username Available using Python Flask MongoDB and Jquery Ajax</h3><br />  
	{% if message %}
          <div class="alert alert-success">{{message}}</div>
    {% endif %}
    <form class="form-signin" action="/" method="post">
	<label>Enter Username</label>  
    <input type="text" name="username" id="username" class="form-control" />
    <span id="availability"></span>
    <br />
	<label>Enter Email</label>  
    <input type="text" name="txtemail" id="txtemail" class="form-control" />
	<label>Enter Password</label>  
    <input type="text" name="txtpass" id="txtpass" class="form-control" />
	<br />
    <button type="submit" name="register" class="btn btn-info" id="register" disabled>Register</button>
	</form>
    <br />
   </div>  
   <br />  
   <br />  
  </div>  
 </body>  
</html>  

Thursday, November 12, 2020

Python Flask MongoDB Upload File and validate before save to Database


Python Flask MongoDB Upload File and validate before save to Database


app.py
 
#app.py
from flask import Flask, render_template, flash, redirect, url_for, request
from flask_mongoengine import MongoEngine #ModuleNotFoundError: No module named 'flask_mongoengine' = (venv) C:\flaskmyproject>pip install flask-mongoengine  
from werkzeug.utils import secure_filename
import os
#import magic
import urllib.request
 
app = Flask(__name__)
app.secret_key = "caircocoders-ednalan-2020"
 
app.config['MONGODB_SETTINGS'] = {
    'db': 'dbmongocrud',
    'host': 'localhost',
    'port': 27017
}
db = MongoEngine()
db.init_app(app)	
 
UPLOAD_FOLDER = 'static/img'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
  
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
  
def allowed_file(filename):
 return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
  
class User(db.Document):
    name = db.StringField()
    email = db.StringField()
    profile_pic = db.StringField()
	
@app.route('/')
def index():
    return render_template('upload.html')
 
@app.route('/upload', methods=['POST'])
def upload():
    file = request.files['inputFile']
    rs_username = request.form['txtusername']
    inputEmail = request.form['inputEmail']
    filename = secure_filename(file.filename)
  
    if file and allowed_file(file.filename):
       file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
       usersave = User(name=rs_username, email=inputEmail, profile_pic=file.filename)
       usersave.save()
       flash('File successfully uploaded ' + file.filename + ' to the database!')
       return redirect('/')
    else:
       flash('Invalid Uplaod only txt, pdf, png, jpg, jpeg, gif') 
    return redirect('/')    
  
if __name__ == '__main__':
    app.run(debug=True)
templates/upload.html
//templates/upload.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <title>Python Flask MongoDB Upload File and validate before save to Database</title>
  </head>
  <body>
  <h2>Python Flask MongoDB Upload File and validate before save to Database </h2>
  <h3>Select a file to upload</h3>
        <p>
         {% with messages = get_flashed_messages() %}
           {% if messages %}
          {% for message in messages %}
            <div class="alert alert-primary">
             <strong>{{ message }}</strong>
           </div>
          {% endfor %}
           {% endif %}
         {% endwith %}
        </p>
<form method="post" action="/upload" enctype="multipart/form-data">
 <p>Name : <input type="text" name="txtusername" value="" placeholder="User Name" required> </p>
 <p>Email : <input type="email" name="inputEmail" placeholder="Email address" required></p>
  <p>
   <input type="file" name="inputFile" autocomplete="off" required onchange="loadFile(event)">
  </p>
    <p>
  <input type="submit" value="Submit">
 </p>
</form>
<img id="output" width="500"/>
<script>
  var loadFile = function(event) {
    var output = document.getElementById('output');
    output.src = URL.createObjectURL(event.target.files[0]);
    output.onload = function() {
      URL.revokeObjectURL(output.src) 
    }
  };
</script>

<style>
.alert-primary {
    color: #004085;
    background-color: #cce5ff;
    border-color: #b8daff;
}
.alert {
    position: relative;
    padding: .75rem 1.25rem;
    margin-bottom: 1rem;
    border: 1px solid transparent;
    border-radius: .25rem;
}
</style>
    </body>
</html>

Wednesday, November 11, 2020

Python Flask MongoDB Login Register logout with Jquery ajax and password hash

Python Flask MongoDB Login Register logout with Jquery ajax and password hash

MongoDB Compass Download https://www.mongodb.com/try/download/compass

create a file called app.py

ModuleNotFoundError: No module named 'flask_mongoengine'

install module flask_mongoengine
(venv) C:\flaskmyproject>pip install flask-mongoengine

create database table
table name user
name = StringField
email = StringField
password = StringField
reg_date = DateTimeField

app.py
#app.py
from flask import Flask, render_template, request, json, redirect, session
from flask_mongoengine import MongoEngine #ModuleNotFoundError: No module named 'flask_mongoengine' = (venv) C:\flaskmyproject>pip install flask-mongoengine
from werkzeug.security import generate_password_hash, check_password_hash
from datetime import datetime

app = Flask(__name__)
app.secret_key = "caircocoders-ednalan-2020"

app.config['MONGODB_SETTINGS'] = {
    'db': 'dbmongocrud',
    'host': 'localhost',
    'port': 27017
}
db = MongoEngine()
db.init_app(app)

class User(db.Document):
    name = db.StringField()
    email = db.StringField()
    password = db.StringField()
    reg_date = db.DateTimeField(datetime.now)
	
@app.route('/')
def main():
    return render_template('index.html')
	
@app.route('/signUp',methods=['POST','GET'])
def signUp():	
    today = datetime.today()	
    #print(today)
    if request.method == 'POST':
        _name = request.form['inputName']
        _email = request.form['inputEmail']
        _password = request.form['inputPassword']
        # validate the received values
        if _name and _email and _password:
            _hashed_password = generate_password_hash(_password)
            users = User.objects(email=_email).first()
            if not users:
                usersave = User(name=_name, email=_email, password=_hashed_password, reg_date=today)
                usersave.save()
                msg =  '{ "html":"ok"}'
                msghtml = json.loads(msg)
                return msghtml["html"]
            else:
                msg =  '{ "html":"

A user with this email address already exists

"}' msghtml = json.loads(msg) return msghtml["html"] else: msg = '{ "html":"

Enter the required fields

"}' msghtml = json.loads(msg) return msghtml["html"] else: return render_template("signup.html") @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': # Get Form Fields _username = request.form['inputEmail'] _password = request.form['inputPassword'] # Get user by username users = User.objects(email=_username).count() print(users) # result 1 if users > 0: # Get stored hash user_rs = User.objects(email=_username).first() password = user_rs['password'] print(password) # Compare Passwords if check_password_hash(password, _password): # Passed session['sessionusername'] = _username return redirect('/userHome') else: error = 'Invalid login' return render_template('signin.html', error=error) else: error = 'Username not found' return render_template('signin.html', error=error) return render_template('signin.html') @app.route('/userHome') def userHome(): print(session.get('sessionusername')) if session.get('sessionusername'): return render_template('userHome.html') else: return render_template('error.html',error = 'Unauthorized Access') @app.route('/logout') def logout(): session.pop('sessionusername', None) return redirect('/') if __name__ == '__main__': app.run(debug=True)
templates/signup.html
//templates/signup.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Python Flask MongoDB Login Register logout with Jquery ajax and password hash</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />  
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>  
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="/static/js/signUp.js"></script>
</head>
<body>
    <div class="container">
        <div class="header">
            <nav>
                <ul class="nav nav-pills pull-right">
                    <li role="presentation" class="active"><a href="/">Home</a></li>
                    <li role="presentation"><a href="/login">Sign In</a></li>
                    <li role="presentation"><a href="/signUp">Sign Up</a></li>
                </ul>
            </nav>
            <img src="/static/images/Flask_Icon.png" alt="Flask_Icon.png"/ > 
        </div>
      <div class="jumbotron">
        <h2>Python Flask MongoDB Login Register logout with Jquery ajax and password hash</h2>
        <div id="message"></div>
        <form class="form-signin">
        <p><label for="inputName" class="sr-only">Name</label>
        <input type="name" name="inputName" id="inputName" class="form-control" placeholder="Name" required autofocus></p>
        <p><label for="inputEmail" class="sr-only">Email address</label>
        <input type="email" name="inputEmail" id="inputEmail" class="form-control" placeholder="Email address" required autofocus></p>
        <p><label for="inputPassword" class="sr-only">Password</label>
        <input type="password" name="inputPassword" id="inputPassword" class="form-control" placeholder="Password" required></p>
        <p><button id="btnSignUp" class="btn btn-lg btn-primary btn-block" type="button">Sign up</button></p>
      </form>
      </div>
      <footer class="footer">
         <p>©tutorial101.blogspot.com</p>
      </footer>
    </div>
<style>
#message h3 {
	border:1px #a81b2f solid;padding:5px;
}
</style>	
  </body>
</html>
templates/signin.html
//templates/signin.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Python Flask MongoDB Login Register logout with Jquery ajax and password hash</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />  
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>  
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
    <div class="container">
        <div class="header">
            <nav>
                <ul class="nav nav-pills pull-right">
                    <li role="presentation" class="active"><a href="/">Home</a></li>
                    <li role="presentation"><a href="/login">Sign In</a></li>
                    <li role="presentation"><a href="/signUp">Sign Up</a></li>
                </ul>
            </nav>
            <img src="/static/images/Flask_Icon.png" alt="Flask_Icon.png"/ > 
        </div>
      <div class="jumbotron">
        <h2>Python Flask MongoDB Login Register logout with Jquery ajax and password hash</h2>
        {% if error %}
          <div class="alert alert-danger">{{error}}</div>
        {% endif %}
        <form class="form-signin" action="/login" method="post">
        <p><label for="inputEmail" class="sr-only">Email address</label>
        <input type="email" name="inputEmail" id="inputEmail" class="form-control" placeholder="Email address" required autofocus></p>
        <p><label for="inputPassword" class="sr-only">Password</label>
        <input type="password" name="inputPassword" id="inputPassword" class="form-control" placeholder="Password" required></p>
         
        <p><button id="btnSignIn" class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button></p>
      </form>
      </div>
        <footer class="footer">
            <p>©tutorial101.blogspot.com</p>
        </footer>
    </div>
  </body>
</html>
templates/userHome.html
//templates/userHome.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Login Register logout with Jquery ajax, json and MySQLdb and password hash</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />  
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>  
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
    <div class="container">
        <div class="header">
            <nav>
                <ul class="nav nav-pills pull-right">
                    <li role="presentation" class="active"><a href="/userHome">Dashboard</a></li>
                    <li role="presentation"><a href="/logout">Log Out</a></li>
                </ul>
            </nav>
            <img src="/static/images/Flask_Icon.png" alt="Flask_Icon.png"/ > 
        </div>
      <div class="jumbotron">
        <h1>Dashboard <small> Welcome {{session.sessionusername}}</small></h1>
      </div>      
        <footer class="footer">
            <p>©tutorial101.blogspot.com</p>
        </footer>
    </div>
</body>
</html>

templates/index.html
//templates/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<title>Login Register logout with Jquery ajax, json and MySQLdb and password hash</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" />  
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>  
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
</head>
<body>
    <div class="container">
        <div class="header">
            <nav>
                <ul class="nav nav-pills pull-right">
                    <li role="presentation" class="active"><a href="/">Home</a></li>
                    <li role="presentation"><a href="/login">Sign In</a></li>
                    <li role="presentation"><a href="/signUp">Sign Up</a></li>
                </ul>
            </nav>
            <img src="/static/images/Flask_Icon.png" alt="Flask_Icon.png"/ > 
        </div>
        <div class="jumbotron">
            <h2>Login Register logout with Jquery ajax, json and MySQLdb and password hash</h2>
            <p class="lead"></p>
            <p><a class="btn btn-lg btn-success" href="signUp" role="button">Sign Up</a>
            </p>
        </div>
        <div class="row marketing">
            <div class="col-lg-6">
                <h4>Blog 1</h4>
                <p>Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit</p>
            </div>
            <hr>
            <div class="col-lg-6">
                <h4>Blog 1</h4>
                <p>Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit</p>
            </div>
        </div>
        <footer class="footer">
            <p>©tutorial101.blogspot.com</p>
        </footer>
    </div>
</body>
</html>
static/js/signUp.js
//static/js/signUp.js
$(function() {
    $('#btnSignUp').click(function() {
        $.ajax({
            url: '/signUp',
            data: $('form').serialize(),
            type: 'POST',
            success: function(response) {
                console.log(response); alert(response)
				$('#message').html(response);
				if(response == 'ok'){
					window.location.href = '/login';
				}
            },
            error: function(error) {
                console.log(error);
				$('#message').html(error);
            }
        });
    });
});

Related Post