article

Wednesday, September 10, 2025

Laravel 12 Quiz React | React Starter Kit

Laravel 12 Quiz React | React Starter Kit

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12DB
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

php artisan make:controller HomeController change it with the following codes:
app\Http\Controllers\HomeController.php

//app\Http\Controllers\HomeController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Question;

class HomeController extends Controller
{
    public function get_home_data()
    {
        //$question = Question::latest()->get();
        $question = Question::where('category', "Programming")->latest()->get();
        return Inertia::render('quiz', [
            'questionsitems' => $question,
        ]);

        #return Inertia::render('quiz');
    }
}
Create tables Question Model
php artisan make:model Question -m myapp>php artisan make:model Question -m
Open new Questions migrations yourproject/database/migrations laravelproject\database\migrations\_create_questions_table.php
//laravelproject\database\migrations\_create_questions_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('questions', function (Blueprint $table) {
            $table->id();
            $table->string('question');
            $table->json('options');
            $table->string('answer');
            $table->string('category');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('questions');
    }
};
myapp>php artisan migrate
Migration table created successfully.
check database table

update Question Model
app/models/Question.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Question extends Model
{
    protected $fillable = [
        'question',
        'options',
        'answer',
        'category',
    ];

    protected $casts = [
        'options' => 'array', // Automatically casts to/from array/JSON
    ];
}
Frontend with React and InertiaJS
File: pages\quiz.tsx
reactjs\resources\js\pages\quiz.tsx
//reactjs\resources\js\pages\quiz.tsx
'use client';

import { Head } from '@inertiajs/react';
import { useState } from 'react';
import QuestionCard  from "@/components/QuestionCard"
import Confetti from "react-confetti";

interface QuestionItem {
    id: number;
    question: string;
    options: string;
    answer: string;
    category: string;
}
 
interface Props {
    questionsitems: QuestionItem[];
}

export default function quiz({ questionsitems }: Props) {
    //console.log(questionsitems)
    const [currentQuestion, setCurrentQuestion] = useState(0);
    const [selectedAnswer, setSelectedAnswer] = useState(null);
    const [score, setScore] = useState(0);
    const [isFinished, setIsFinished] = useState(false);
    const [showFeedback, setShowFeedback] = useState(false);

    const handleAnswer = (option) => {
        if (showFeedback) return;

        setSelectedAnswer(option);
        setShowFeedback(true);

        if (option === questionsitems[currentQuestion].answer) {
        setScore(score + 1);
        }
    };
    
    const goToNext = () => {
        if (currentQuestion + 1 < questionsitems.length) {
        setCurrentQuestion(currentQuestion + 1);
        setSelectedAnswer(null);
        setShowFeedback(false);
        } else {
        setIsFinished(true);
        }
    };

    const restartQuiz = () => {
        setCurrentQuestion(0);
        setScore(0);
        setSelectedAnswer(null);
        setShowFeedback(false);
        setIsFinished(false);
    };

    const calculateProgress = () => {
        if (isFinished) return 100;
        const baseProgress = (currentQuestion / questionsitems.length) * 100;
        const questionProgress = selectedAnswer ? (1 / questionsitems.length) * 100 : 0;
        return baseProgress + questionProgress;
    };

    const percentage = (score / questionsitems.length) * 100;
    const showConfetti = isFinished && percentage > 50;

    return (
        <div className="min-h-screen">
            <Head title="Quiz"></Head>
            <div className="min-h-screen bg-gray-900 text-white flex flex-col items-center justify-center p-4 ">
            {showConfetti && <Confetti />}    
            <div className="text-center mb-8">
                <h1 className="text-4xl font-bold text-purple-600 mb-2">Laravel 12 Quiz React | React Starter Kit</h1>
                <p className="text-gray-400">Test your knowledge</p>
            </div>
 
            <div className="w-full max-w-xl mb-6">
                <div className="bg-gray-700 h-3 rounded-full overflow-hidden">
                <div
                    className="h-full bg-gradient-to-r from-indigo-500 to-purple-600 duration-500 ease-out transition-all"
                    style={{ width: `${calculateProgress()}%` }}
                ></div>
                </div>
            </div>
           
            {!isFinished ? (
                <>
                <QuestionCard
                    showFeedback={showFeedback}
                    onAnswer={handleAnswer}
                    data={questionsitems[currentQuestion]}
                    current={currentQuestion}
                    total={questionsitems.length}
                    selected={selectedAnswer}
                />
                <div className="mt-6 min-h-[60px]">
                    {showFeedback && (
                    <button
                        className="bg-gradient-to-r from-indigo-600 to-purple-600 py-3 px-6 rounded-lg font-medium shadow-lg cursor-pointer"
                        onClick={goToNext}
                    >
                        {currentQuestion + 1 < questionsitems.length
                        ? "Continue"
                        : "See Results"}
                    </button>
                    )}
                </div>
                </>
            ) : (
                <div className="text-center">
                <h2 className="text-3xl font-bold mb-4">Quiz Completed!</h2>
                <p className="text-xl mb-6">
                    You scored <span>{score}</span> out of{" "}
                    <span className="font-bold">{questionsitems.length}</span> and it is{" "}
                    {Math.round((score / questionsitems.length) * 100)}%
                </p>
                <button
                    className="bg-gradient-to-r from-indigo-600 to-purple-600 py-3 px-6 rounded-lg font-medium shadow-lg cursor-pointer"
                    onClick={restartQuiz}
                >
                    Restart Quiz
                </button>
                </div>
            )}

            </div>
        </div>
    );
}
File: js\components\QuestionCard.tsx
reactjs\resources\js\components\QuestionCard.tsx
//reactjs\resources\js\components\QuestionCard.tsx
const QuestionCard = ({
    data,
    onAnswer,
    showFeedback,
    selected,
    current,
    total,
}) => {
    console.log(data)
    const { question, options, answer } = data;

    const getButtonStyle = (option) => {
        if (!showFeedback) {
        return "bg-indigo-700 hover:bg-indigo-600 hover:scale-[1.01]";
        }
        if (option === answer) return "bg-emerald-600";
        if (option === selected) return "bg-rose-600";
        return "bg-gray-600";
    };

    return (
        <div className="bg-gray-800 p-6 rounded-2xl shadow-lg w-full max-w-xl border border-gray-700">
        <div className="flex justify-between items-center mb-4">
            <h2 className="text-lg font-medium text-gray-300">
            Question {current + 1} of {total}
            </h2>
            <span className="text-sm bg-gray-700 px-3 py-1 rounded-full">
            {selected
                ? Math.round(((current + 1) / total) * 100) + "% complete"
                : Math.round((current / total) * 100) + "% complete"}
            </span>
        </div>
        <p className="text-xl font-medium mb-6">{question}</p>
        <div className="grid gap-3">
            {options.map((option, index) => (
            <button
                className={`${getButtonStyle(
                option
                )} text-left px-4 py-3 cursor-pointer rounded-lg text-white `}
                key={index}
                onClick={() => onAnswer(option)}
                disabled={showFeedback}
            >
                {option}
            </button>
            ))}
        </div>
        </div>
    );
};

export default QuestionCard;
//routes/web.php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\HomeController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::get('/quiz', [HomeController::class, 'get_home_data'])->name('quiz');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');
    
});
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Monday, September 8, 2025

Laravel 12 Multiple Upload Fle React | React Starter Kit

Laravel 12 Multiple Upload Fle React | React Starter Kit

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12DB
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

php artisan make:controller ProductController change it with the following codes:
app\Http\Controllers\ProductController.php

//app\Http\Controllers\ProductController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Product;
use Illuminate\Support\Str;

class ProductController extends Controller
{
    public function create()
    {
        return Inertia::render('admin/products/create');
    }

    public function store(Request $request)
    {
        $request->validate([
            'name' => 'string|required|max:255',
            'description' => 'string|nullable',
            'images' => 'nullable|array',
            'images.*' => 'image|max:2048',
        ]);

        //   Slug
        $slug = Str::slug($request->name);
        //   Image
        $images = [];
        if ($request->hasFile('images')) {
            foreach ($request->file('images') as $image) {
                $images[] = $image->store('products/images', 'public');
            }
        }

        $new_product = [
            'name' => $request->name,
            'slug' => $slug,
            'description' => $request->description,
            'price' => $request->price,
            'images' => $images,
        ];

        $prod = Product::create($new_product);
        //dd($prod);
        return redirect()->route('dashboard.products.index')->with('success', 'Product created successfully!');
    }
}
Create tables Product Model
php artisan make:model Product -m myapp>php artisan make:model Product -m
Open new Products migrations yourproject/database/migrations laravelproject\database\migrations\_create_products_table.php
//laravelproject\database\migrations\_create_products_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('slug')->unique();
            $table->decimal('price', 10, 2)->default(0);
            $table->text('description')->nullable();
            $table->json('images')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};
myapp>php artisan migrate
Migration table created successfully.
check database table

update Product Model
app/models/Product.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class Product extends Model
{
    protected $fillable = [
        'name',
        'slug',
        'price',
        'description',
        'images',
    ];

    protected $casts = [
        'images' => 'array',
        'price' => 'decimal:2',
    ];
}
Frontend with React and InertiaJS
Add sidebar menu product mainNavItems from reactjs\rescourses\js\components\app-sidebar.tsx
File: pages\admin\products\create.tsx
reactjs\resources\js\pages\users\create.tsx
//reactjs\resources\js\pages\admin\products\create.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { Textarea } from "@/components/ui/textarea"
import { Head, router, useForm } from '@inertiajs/react';
import { CompactFileInput } from '@/components/ImageUploadInput';
import { useState } from 'react';
import { LoaderCircle } from 'lucide-react';
import InputError from '@/components/input-error';
import { CreateProductItem } from '@/types/products';

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Create Product',
        href: '/dashboard/products/create',
    },
];

export default function Dashboard() {
    const [images, setImages] = useState<File[]>([]);
    const { data, setData, processing, errors, reset } = useForm<Required<CreateProductItem>>({
        name: '',
        slug: '',
        image: null,
        description: '',
        price: 0,
        images: null,
    });

    const handleSubmit: React.FormEventHandler = (e) => {
        e.preventDefault();
        data.images = images;
        console.log(data.images);
        router.post('/dashboard/products', data, {
            onFinish: () => {
                reset();
                console.log("success");
            },
        });
    };

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Create Product" />
            <div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
                <div className="flex justify-end">Create New Product</div>
                <div className="p-10 border-sidebar-border/70 dark:border-sidebar-border relative min-h-[100vh] flex-1 overflow-hidden rounded-xl border md:min-h-min">
                <form onSubmit={handleSubmit} className="space-y-4">
                    <div className="grid gap-6">
                        <div className="grid gap-2">
                            <Label htmlFor="title">Product Name</Label>
                            <Input id="name" value={data.name} onChange={(e) => setData('name', e.target.value)} />
                            <InputError message={errors.name} className="mt-2" />
                        </div>
                    </div>
                    <div className="grid gap-6">
                        <div className="grid gap-2">
                            <Label htmlFor="price">Price</Label>
                            <Input id="price" value={data.price} onChange={(e) => setData('price', Number(e.target.value))} />
                            <InputError message={errors.price} className="mt-2" />
                        </div>
                    </div>

                    <div className="grid gap-2">
                        <div className="flex items-center">
                            <Label htmlFor="message">Product Description</Label>
                        </div>
                        <Textarea
                            value={data.description}
                            onChange={(e) => setData('description', e.target.value)}
                            placeholder="Type your description here."
                            id="message"
                        />
                        <InputError message={errors.description} />
                    </div>
 
                    <div className="grid gap-2">
                    <h2 className="mb-3 text-lg font-semibold">Upload product Images</h2>
                        <div className="rounded border p-4">
                            <CompactFileInput multiple={true} maxSizeMB={1} onChange={setImages} />
                        </div>
                    </div>
 
                    <Button type="submit" className="mt-4 w-full" disabled={processing}>
                        {processing && <LoaderCircle className="h-4 w-4 animate-spin" />}
                        Add Product
                    </Button>   
                </form>

                </div>
            </div>
        </AppLayout>
    );
}
File: js\components\ImageUploadInput.tsx
reactjs\resources\js\components\ImageUploadInput.tsx
//reactjs\resources\js\components\ImageUploadInput.tsx
'use client';
import React, { useState } from 'react';

// Shared types
type ImageFile = {
    id: string;
    file: File;
    preview: string; 
};

type FileInputProps = {
    multiple?: boolean;
    maxSizeMB?: number;
    onChange?: (files: File[]) => void;
    acceptedFileTypes?: string;
};

// Compact File Input Component
export const CompactFileInput: React.FC<FileInputProps> = ({ multiple = false, maxSizeMB = 1, onChange, acceptedFileTypes = 'image/*' }) => {
    const [images, setImages] = useState<ImageFile[]>([]);
    const [error, setError] = useState<string | null>(null);
    const fileInputRef = React.useRef<HTMLInputElement>(null);

    const maxSizeBytes = maxSizeMB * 1024 * 1024; // Convert MB to bytes

    const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const selectedFiles = Array.from(e.target.files || []);
        console.log(selectedFiles);
        // Reset error
        setError(null);

        // Validate file size
        const oversizedFiles = selectedFiles.filter((file) => file.size > maxSizeBytes);
        if (oversizedFiles.length > 0) {
            setError(`File(s) exceed the ${maxSizeMB}MB limit`);
            return;
        }

        // Handle single file mode
        if (!multiple && selectedFiles.length > 0) {
            // Remove previous image and URL
            images.forEach((img) => URL.revokeObjectURL(img.preview));

            const file = selectedFiles[0];
            const newImage = {
                id: Math.random().toString(36).substr(2, 9),
                file,
                preview: URL.createObjectURL(file),
            };

            setImages([newImage]);
            onChange?.([file]);
            return;
        }

        // Handle multiple files
        const newImages = selectedFiles.map((file) => ({
            id: Math.random().toString(36).substr(2, 9),
            file,
            preview: URL.createObjectURL(file),
        }));

        setImages((prev) => [...prev, ...newImages]);
        onChange?.(selectedFiles);
    };

    const removeImage = (id: string) => {
        setImages((prev) => {
            const updatedImages = prev.filter((img) => {
                if (img.id === id) {
                    URL.revokeObjectURL(img.preview);
                    return false;
                }
                return true;
            });

            // Notify parent component
            onChange?.(updatedImages.map((img) => img.file));

            return updatedImages;
        });
    };

    const handleBrowseClick = () => {
        fileInputRef.current?.click();
    };

    return (
        <div className="compact-file-input">
            <div className="input-container flex items-center gap-2 rounded border border-gray-300 p-2">
                <button type="button" onClick={handleBrowseClick} className="rounded bg-blue-500 px-3 py-1 text-sm text-white hover:bg-blue-600">
                    Browse
                </button>
                <span className="text-sm text-gray-500">{multiple ? 'Choose images' : 'Choose an image'}</span>
                <input ref={fileInputRef} type="file" accept={acceptedFileTypes} multiple={multiple} onChange={handleFileChange} className="hidden" />
            </div>

            {error && <div className="mt-1 text-xs text-red-500">{error}</div>}

            {images.length > 0 && (
                <div className="image-previews mt-2 flex flex-wrap gap-2">
                    {images.map((img) => (
                        <div key={img.id} className="preview-container relative h-16 w-16 overflow-hidden rounded border">
                            <img src={img.preview} alt="Preview" className="h-full w-full object-cover" />
                            <button
                                type="button"
                                onClick={() => removeImage(img.id)}
                                className="absolute top-0 right-0 flex h-5 w-5 items-center justify-center rounded-full bg-red-500 text-xs text-white"
                            >
                                ×
                            </button>
                        </div>
                    ))}
                </div>
            )}
        </div>
    );
};
File: js\components\ui\textarea.tsx
reactjs\resources\js\components\ui\textarea.tsx
//reactjs\resources\js\components\ui\textarea.tsx
import * as React from "react"

import { cn } from "@/lib/utils"

function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
  return (
    <textarea
      data-slot="textarea"
      className={cn(
        "border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
        className
      )}
      {...props}
    />
  )
}

export { Textarea }
File: js\lib\utils.ts
reactjs\resources\js\lib\utils.ts
import { type ClassValue, clsx } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
    return twMerge(clsx(inputs));
}
File: js\types\products.ts
reactjs\resources\js\types\products.ts
export interface ProductItem {
    id: number;
    name: string;
    slug: string;
    price: number;
    description: string;
    images: string | null;
}
//routes/web.php
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\ProductController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');

    Route::get('/dashboard/products/create', [ProductController::class, 'create'])->name('dashboard.products.create');
    Route::post('/dashboard/products', [ProductController::class, 'store'])->name('dashboard.products.store');
});

require __DIR__.'/settings.php';
require __DIR__.'/auth.php';
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Tuesday, August 5, 2025

React + Vite How to Create Portfolios No coding using Lovable AI

React + Vite How to Create Portfolios No coding using Lovable AI

Create react project
Run the following command in your terminal: vite dev/guide/
npm create vite@latest

Update to latest tailwindcss
npm install tailwindcss@latest

Sunday, August 3, 2025

React Vite How to Setup Path Aliases | from @/components/Navigation

React Vite How to Setup Path Aliases | from @/components/Navigation

Create react project
Run the following command in your terminal: vite dev/guide/
npm create vite@latest
Install react-router-dom

my-app\src\App.tsx my-app\src\App.tsx
//src\App.tsx
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Index from "./pages/Index";
import NotFound from "./pages/NotFound";

const App = () => (
  <BrowserRouter>
    <Routes>
        <Route path="/" element={<Index />} />
        <Route path="*" element={<NotFound />} />
    </Routes>
  </BrowserRouter>
);

export default App;
my-app\src\pages\Index.tsx
//pages\Index.tsx
import { Navigation } from "@/components/Navigation";

const Index = () => {
  return (
    <div className="min-h-screen">
      <Navigation />
      <main>
        <div id="home">
          Heror
        </div>
        <h1>About</h1>
        <h1>Skills</h1>
        <h1>Projects</h1>
        <h1>Contact</h1>
      </main>
      Footer
    </div>
  );
};

export default NotFound;
my-app\src\pages\NotFound.tsx
//pages\NotFound.tsx
import { useLocation } from "react-router-dom";
import { useEffect } from "react";

const NotFound = () => {
  const location = useLocation();

  useEffect(() => {
    console.error(
      "404 Error: User attempted to access non-existent route:",
      location.pathname
    );
  }, [location.pathname]);

  return (
    <div className="min-h-screen flex items-center justify-center bg-gray-100">
      <div className="text-center">
        <h1 className="text-4xl font-bold mb-4">404</h1>
        <p className="text-xl text-gray-600 mb-4">Oops! Page not found</p>
        <a href="/" className="text-blue-500 hover:text-blue-700 underline">
          Return to Home
        </a>
      </div>
    </div>
  );
};

export default Navigation;
my-app\src\pages\Navigation.tsx
//pages\Navigation.tsx
import { useState, useEffect } from "react";
import logo from "@/assets/react.svg";

const navLinks = [
  { label: "Home", href: "#home" },
  { label: "About", href: "#about" },
  { label: "Skills", href: "#skills" },
  { label: "Portfolios", href: "#portfolios" },
  { label: "Contact", href: "#contact" },
];

export function Navigation() {
  const [scrolled, setScrolled] = useState(false);

  useEffect(() => {
    const handleScroll = () => {
      setScrolled(window.scrollY > 50);
    };
    
    window.addEventListener("scroll", handleScroll);
    return () => window.removeEventListener("scroll", handleScroll);
  }, []);

  const scrollToSection = (href: string) => {
    console.log(`Navigating to ${href}`);
  };

  return (
    <nav className={`fixed top-0 left-0 right-0 z-50 transition-smooth ${
      scrolled ? "bg-background/90 backdrop-blur-md shadow-card" : "bg-transparent"
    }`}>
      <div className="container mx-auto max-w-6xl px-4">
        <div className="flex items-center justify-between h-16">
          {/* Logo */}
          <div className="text-2xl font-bold">
              <img 
                src={logo} 
                alt="logo" 
                className="h-[50px]"
              />
          </div>

          {/* Desktop Navigation */}
          <div className="hidden md:flex items-center space-x-8">
            {navLinks.map((link) => (
              <button
                key={link.label}
                onClick={() => scrollToSection(link.href)}
                className="text-muted-foreground hover:text-primary transition-smooth font-medium"
              >
                {link.label}
              </button>
            ))}
            <button>
              Hire Me
            </button>
          </div>
        </div>
      </div>
    </nav>
  );
}
Update vite.config.ts
my-app\vite.config.ts
//vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  resolve: {
    alias: {
      '@/components': '/src/components',
      '@/assets': '/src/assets',
      '@/lib': '/src/lib',
    },
  },
});
Update tsconfig.json
my-app\tsconfig.json
//tsconfig.json
{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ],
  "compilerOptions": {
    "baseUrl": "./",
    "paths": {
        "@components/*": ["src/components/*"],
        "@assets/*": ["src/assets/*"],
        "@lib/*": ["src/lib/*"]
    }
  }
}
Run
myapp\my-app> npm run dev

Monday, July 28, 2025

python tkinter chatbot google API Gemini

python tkinter chatbot google API Gemini chatbot.py
 
import tkinter as tk
from tkinter import scrolledtext, Entry, Button
import google.generativeai as genai

class SimpleChatbot:
    def __init__(self, root):
        self.root = root
        self.root.geometry("700x480") #Initial size and position
        self.root.title("Generative AI Chatbot")

        # Configure the Generative AI API with your API key
        genai.configure(api_key='AIzaSyA8ZbJrTvnMnnDOlN3HDT3cnwsZ906321654654WhQY')  # Replace with your actual API key

        # Create a model instance (adjust parameters as per documentation)
        self.model = genai.GenerativeModel(model_name="gemini-2.0-flash")  # Example model name

        # Create a scrolled text area for displaying the conversation
        self.conversation_area = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=90, height=30)
        self.conversation_area.grid(column=0, row=0, padx=10, pady=10, columnspan=2)
        self.conversation_area.insert(tk.INSERT, "Chatbot: Hello! How can I help you today?\n")

        # Create an entry widget for the user to type their message
        self.user_input = Entry(root, width=60)
        self.user_input.grid(column=0, row=1, padx=10, pady=10)

        # Create a button to send the message
        self.send_button = Button(root, text="Send", command=self.send_message)
        self.send_button.grid(column=1, row=1, padx=10, pady=10)

    def send_message(self):
        user_message = self.user_input.get()
        self.conversation_area.insert(tk.INSERT, f"You: {user_message}\n")
        self.user_input.delete(0, tk.END)

        # Call the Generative AI API to get a response
        response = self.model.generate_content(user_message)  # Hypothetical method
        bot_response = f"Chatbot: {response.text}\n"
        self.conversation_area.insert(tk.INSERT, bot_response)

if __name__ == "__main__":
    root = tk.Tk()
    chatbot = SimpleChatbot(root)
    root.mainloop()

Wednesday, June 4, 2025

Laravel 12 Simple Inertia React Pagination Components | React Starter Kit

Laravel 12 Simple Inertia React Pagination Components | React Starter Kit

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

php artisan make:controller UsersController change it with the following codes:
app\Http\Controllers\UsersController.php

//app\Http\Controllers\UsersController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;

class UsersController extends Controller
{
    public function home()
    {
        $query = User::select('id', 'name', 'email', 'created_at')->latest();

        // Handle search
        if (request()->has('search')) {
            $search = request('search');
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                    ->orWhere('email', 'like', "%{$search}%");
            });
        }

        $users = $query->paginate(10);

        return Inertia::render('users/index', [
            'users' => $users,
            'filters' => [
                'search' => request('search', ''),
            ]
        ]);
    }
}
Frontend with React and InertiaJS
Add sidebar menu product mainNavItems from reactjs\rescourses\js\components\app-sidebar.tsx
File: pages\users\index.tsx
reactjs\resources\js\pages\users\index.tsx
//reactjs\resources\js\pages\users\index.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router } from '@inertiajs/react';
import { Search } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import Pagination from '@/components/Pagination'; 
import { useState } from 'react';

interface User {
    id: number;
    name: string;
    email: string;
    created_at: string;
}

interface Props {
    users: {
        data: User[];
        current_page: number;
        last_page: number;
        per_page: number;
        total: number;
        from: number;
        to: number;
    };
    filters: {
        search: string;
        filter: string;
    };
}

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Laravel 12 Simple Inertia React Pagination Components | React Starter Kit',
        href: '/admin/users',
    },
];

export default function UsersIndex({ users, filters }: Props) {
    //console.log(users); 
    const [searchTerm, setSearchTerm] = useState(filters.search);

    const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        router.get(route('users.home'), {
            search: searchTerm,
        }, {
            preserveState: true,
            preserveScroll: true,
        });
    };
    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Users" />
            <div className="flex h-full flex-1 flex-col gap-6 rounded-xl p-6 bg-gradient-to-br from-background to-muted/20">
                <div className="flex gap-4 mb-4">
                    <form onSubmit={handleSearch} className="relative flex-1">
                        <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
                        <Input placeholder="Search name or email..."
                            value={searchTerm}
                            onChange={(e) => setSearchTerm(e.target.value)}
                            className="pl-10 bg-gray-200  text-black"
                        />
                    </form>                                     
                </div>
                <div className="rounded-md border">
                    <div className="relative w-full overflow-auto">
                        <table className="w-full caption-bottom text-sm  bg-white rounded-lg">
                            <thead>
                                <tr className="bg-gray-200 text-black">
                                    <th className="py-2 px-4 text-left border-b">ID</th>
                                    <th className="py-2 px-4 text-left border-b">Name</th>
                                    <th className="py-2 px-4 text-left border-b">Email</th>
                                    <th className="py-2 px-4 text-left border-b">Created At</th>                                   
                                    <th className="py-2 px-4 text-right border-b">Actions</th>                                
                                </tr>                            
                            </thead>   
                            <tbody>
                                {users.data.map((user) => (
                                    <tr key={user.id} className="hover:bg-gray-50 text-black">
                                        <td className="p-4 align-middle font-medium border-b">{user.id}</td>
                                        <td className="p-4 align-middle font-medium border-b">{user.name}</td>
                                        <td className="p-4 align-middle font-medium border-b">{user.email}</td>
                                        <td className="p-4 align-middle font-medium border-b">{new Date(user.created_at).toLocaleDateString()}</td>
                                        <td className="p-4 align-middle text-right border-b">
                                            <div className="flex justify-end gap-2">
                                                <Button variant="ghost"
                                                    className="w-full sm:w-auto bg-blue-600 text-white rounded-lg px-4 py-2 hover:bg-blue-700"
                                                >
                                                    Edit
                                                </Button>                                                
                                                <Button variant="ghost"
                                                    className="w-full sm:w-auto bg-red-600 text-white rounded-lg px-4 py-2 hover:bg-red-700 transition transition ml-2"
                                                >
                                                    Delete
                                                </Button>                                            
                                            </div>
                                        </td>
                                    </tr>
                                ))}
                                {users.total === 0 && (
                                    <tr>
                                        <td colSpan={6} className="p-4 text-center text-muted-foreground">
                                            No users Found                                        
                                        </td>
                                    </tr>
                                )}
                            </tbody> 
                        </table>              
                    </div>                
                </div>
                {/* Pagination */}
                <div className="flex items-center justify-between px-2">
                    <div className="text-sm text-muted-foreground">
                        Showing {users.from} to {users.to} of {users.total} results  
                    </div>                    
                    <div className="flex items-center space-x-2">
                    <Pagination links={users.links}/>                  
                    </div>                
                </div> 
            </div>
        </AppLayout>
    );
}
File: js\components\Pagination.tsx
reactjs\resources\js\components\Pagination.tsx
//reactjs\resources\js\components\Pagination.tsx
import { Link } from '@inertiajs/react'

export default function Pagination({ links }) {
    console.log(links);
    return (
        <div className="flex flex-wrap items-center space-x-1 mt-4">
            {links.map((link, index) => (
                <Link 
                    key={index}    
                    href={link.url ?? '#'}
                    dangerouslySetInnerHTML={{ __html: link.label}}
                    className={`text-lg rounded-lg px-4 py-2 border gap-2
                        ${link.active ? 'border-t-blue-600 text-white  bg-blue-600' : 'bg-white text-gray-700'}
                        ${!link.url ? 'opacity-50 pointer-events-none' : 'hover:bg-gray-100'}`}
                />
            ))}
        </div>
    )
}
php artisan tinker
User::factory()->count(1000)->create()
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\UsersController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');

    Route::get('/admin/users-pagination-search', [UsersController::class, 'home'])->name('users.home');
});

require __DIR__.'/settings.php';
require __DIR__.'/auth.php';
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Friday, May 30, 2025

Laravel 12 React Starter kit Schedule Calendar | Fullcalendar CRUD

Laravel 12 React Starter kit Schedule Calendar | Fullcalendar CRUD

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

Create tables Schedule Model php artisan make:model Schedule -m myapp>php artisan make:model Schedule -m Open new Schedule migrations yourproject/database/migrations laravelproject\database\migrations\_create_schedule_table.php
//laravelproject\database\migrations\_create_schedule_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('schedules', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->dateTime('start');
            $table->dateTime('end');
            $table->string('color')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('schedules');
    }
};
run
myapp>php artisan migrate
update Schedule Model
app/models/Schedule.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Schedule extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'start', 
        'end',
        'color',
    ];
}
Create ScheduleController
php artisan make:controller ScheduleController
change it with the following codes:
app\Http\Controllers\ScheduleController.php

//app\Http\Controllers\ScheduleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\Schedule;

class ScheduleController extends Controller
{
    public function index()
    {
        $query = Schedule::select('id', 'title', 'start', 'end', 'color', 'created_at')->get();

        return Inertia::render('schedule/index', [
            'schedules' => $query,
            'flash' => [
                'success' => session('success'),
                'error' => session('error')
            ]
        ]);
    }

    public function store(Request $request)
    {
        Schedule::create([
            'title' => $request->title,
            'start' => $request->startdate,
            'end' => $request->enddate,
            'color' => $request->color,
        ]);

        return redirect()->route('schedule.index')->with('success', 'Schedule created successfully!');
    }

    public function destroy($id)
    {
        Schedule::findOrFail($id)->delete();
        return redirect()->route('schedule.index')->with('success', 'Schedule Deleted successfully!');
    }

    public function update(Request $request, $id)
    {
        $schedule = Schedule::findOrFail($id);
        $schedule->update($request->only('start', 'end'));

        return redirect()->route('schedule.index')->with('success', 'Schedule Updated successfully!');
    }
}
Frontend with React and InertiaJS
Add sidebar menu product mainNavItems from reactjs\rescourses\js\components\app-sidebar.tsx
Index.tsx File: pages\schedule\index.tsx
reactjs\resources\js\pages\schedule\index.tsx
//reactjs\resources\js\pages\schedule\index.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router } from '@inertiajs/react';
import FullCalendar from '@fullcalendar/react'
import dayGridPlugin from '@fullcalendar/daygrid' //npm install --save @fullcalendar/react @fullcalendar/core @fullcalendar/daygrid @fullcalendar/interaction
import interactionPlugin from "@fullcalendar/interaction"; // needed for dayClick
import {
  DateSelectArg,
  formatDate,
} from "@fullcalendar/core"; //   npm install @fullcalendar/interaction https://fullcalendar.io/docs/react
import { useState, useEffect } from 'react'; 
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
import { DialogDescription } from '@radix-ui/react-dialog';
import { Label } from '@/components/ui/label';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { CheckCircle2, XCircle } from 'lucide-react';

interface Schedule {
    id: number;
    title: string;
    start: Date;
    end: Date;
    color: string;
}
  
interface Props {
    schedules: Schedule[];
    flash?: {
        success?: string;
        error?: string;
    };
}

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Schedule',
        href: '/admin/schedule',
    },
];

export default function Dashboard({ schedules, flash }: Props) {
    //console.log(schedules);
    const [isDialogOpen, setIsDialogOpen] = useState<boolean>(false);
    const [title, setTitle] = useState<string>(""); 
    const [color, setColor] = useState<string>("#000000");
    const [startdate, setStart] = useState<DateSelectArg | null>(null);
    const [enddate, setEnd] = useState<DateSelectArg | null>(null);
    const [toastMessage, setToastMessage] = useState('');
    const [toastType, setToastType] = useState<'success' | 'error'>('success');
    const [showToast, setShowToast] = useState(false);
    //console.log(flash);
    useEffect(() => { 
      if (flash?.success) { 
          setToastMessage(flash.success);
          setToastType('success');
          setShowToast(true); 
      } else if (flash?.error) { 
          setToastMessage(flash.error);
          setToastType('error');
          setShowToast(true);
      }
    }, [flash]);

    useEffect(() => {
        if (showToast) { 
            const timer = setTimeout(() => {
                setShowToast(false);
            }, 3000);
            return () => clearTimeout(timer);
        }
    }, [showToast]);

    const handleDateClick = (selected: DateSelectArg) => {
        const start = selected.startStr
        const end = selected.endStr
        //console.log(start)
        setStart(start)
        setEnd(end)
        setIsDialogOpen(true);
    };

    function handleSubmit(e: React.FormEvent) {
        e.preventDefault();
        //console.log(color);
        router.post(route('schedule.store'), {
            _method: 'post',
            title,
            startdate,
            enddate,
            color,
        });
        handleCloseDialog();
    };

    const handleCloseDialog = () => {
        setIsDialogOpen(false);
    };

    function formatEvents() {
        return schedules.map(schedule => {
            const {id, title, start, end, color} = schedule
      
            let startTime = new Date(start)
            let endTime = new Date(end)
      
            return {
              id, 
              title, 
              start: startTime,
              end: endTime, 
              color,
              }
        })
    }

    function handleEventDrop(info) {
        if(window.confirm("Are you sure you want to change the event date?")){
            //console.log('change confirmed')
            //console.log(info.event.start)
            const start = (new Date(info.event.start)).toISOString().slice(0, 10)
            const end = (new Date(info.event.end)).toISOString().slice(0, 10)
  
            router.put(`/admin/schedule/${info.event.id}`, {
              _method: 'put',
              start,
              end,
            });
        } else {
            console.log('change aborted')
        }
    }

    function handleEventClick(data) {
        console.log(data);
        console.log(data.event.id);
        if (confirm(`Are you sure you want to delete the event '${data.event.title}'`)) {
            router.delete(`/admin/schedule/${data.event.id}`, {
                onSuccess: () => {
                    router.reload();
                },
                onError: () => {
                    console.error("Failed to delete post.");
                },
            });
            data.event.remove()
        }
    }

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Schedule" />
            <div className="flex h-full flex-1 flex-col gap-4 rounded-xl p-4">
                {showToast && (
                    <div className={`fixed top-4 right-4 z-50 flex items-center gap-2 rounded-lg p-4 shadow-lg ${toastType === 'success' ? 'bg-green-500' : 'bg-red-500'
                        } text-white animate-in fade-in slide-in-from-top-5`}>
                        {toastType === 'success' ? (
                            <CheckCircle2 className="h-5 w-5" />
                        ) : (
                            <XCircle className="h-5 w-5" />
                        )}
                        <span>{toastMessage}</span>                    
                </div>)}

                <h1>Laravel 12 React Starter kit Schedule Calendar | Fullcalendar</h1>
                <FullCalendar
                    plugins={[dayGridPlugin, interactionPlugin]} // Initialize calendar with required plugins.
                    headerToolbar = {{
                        left: 'prev,next' ,
                        center: 'title',
                        right: 'dayGridMonth,timeGridWeek,timeGridDay'
                    }}
                    initialView="dayGridMonth"
                    editable={true}
                    selectable={true} // Allow dates to be selectable.
                    selectMirror={true} // Mirror selections visually.
                    select={handleDateClick} // Handle date selection to create new events.
                    events={formatEvents()}
                    eventDrop={handleEventDrop}
                    eventClick={handleEventClick}
                    //events={[
                    //    {title : 'Event 1', start: '2025-05-19', end: '2025-05-21'},
                    //]}
                />
            </div>
            <div className="flex justify-between items-center">                
                    <Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>                     
                        <DialogContent>
                            <DialogHeader>
                                <DialogTitle>Add New Event Details</DialogTitle>   
                                <DialogDescription>
                                    Make changes to your category here. Click update when you're done.
                                </DialogDescription>                        
                            </DialogHeader>   
                            <form onSubmit={handleSubmit} className="space-y-4">
                                <div className="space-y-2">
                                    <Label htmlFor="title">Title</Label>                                    
                                    <Input id="title"
                                    type="text"
                                    value={title}
                                    onChange={(e) => setTitle(e.target.value)}
                                    />
                                </div>                               
                                <div className="space-y-2">
                                    <Label htmlFor="color">Color</Label>                                    
                                    <Input type="color" id="color" name="color"
                                    value={color}
                                    onChange={(e) => setColor(e.target.value)}
                                    />
                                </div>                               
                                <Button type="submit">
                                    Create
                                </Button>                            
                            </form>                
                        </DialogContent>                    
                    </Dialog>                
            </div>
        </AppLayout>
    );
}
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\ScheduleController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');

    Route::get('/admin/schedule', [ScheduleController::class, 'index'])->name('schedule.index');
    Route::post('/admin/schedule', [ScheduleController::class, 'store'])->name('schedule.store');
    Route::delete('/admin/schedule/{id}', [ScheduleController::class, 'destroy'])->name('schedule.destroy');
    Route::put('/admin/schedule/{id}', [ScheduleController::class, 'update'])->name('users.update');
});

require __DIR__.'/settings.php';
require __DIR__.'/auth.php';
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Wednesday, May 28, 2025

Laravel 12 Schedule Calendar | Fullcalendar

Laravel 12 Schedule Calendar | Fullcalendar

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

Create tables Schedule Model php artisan make:model Schedule -m myapp>php artisan make:model Schedule -m Open new Schedule migrations yourproject/database/migrations laravelproject\database\migrations\_create_schedule_table.php
//laravelproject\database\migrations\_create_schedule_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('schedules', function (Blueprint $table) {
            $table->id();
            $table->string('title');
            $table->dateTime('start');
            $table->dateTime('end');
            $table->string('color')->nullable();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('schedules');
    }
};
run
myapp>php artisan migrate
update Schedule Model
app/models/Schedule.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;

class Schedule extends Model
{
    use HasFactory;

    protected $fillable = [
        'title',
        'start', 
        'end',
        'color',
    ];
}
Create ScheduleController
php artisan make:controller ScheduleController
change it with the following codes:
app\Http\Controllers\ScheduleController.php

//app\Http\Controllers\ScheduleController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Schedule;
use Carbon\Carbon;

class ScheduleController extends Controller
{
    public function index()
    {
        return view('schedule.index');
    }

    public function store(Request $request)
    {
        $item = new Schedule();
        $item->title = $request->title;
        $item->start = $request->start;
        $item->end = $request->end;
        $item->color = $request->color;
        $item->save();

        return redirect()->route("schedule.index");
    }

    public function getEvents()
    {
        $schedules = Schedule::all();
        return response()->json($schedules);
    }

    public function update(Request $request, $id)
    {
        $schedule = Schedule::findOrFail($id);

        $schedule->update([
            'start' => Carbon::parse($request->input('start_date'))->setTimezone('UTC'),
            'end' => Carbon::parse($request->input('end_date'))->setTimezone('UTC'),
        ]);

        return response()->json(['message' => 'Event moved successfully']);
    }

    public function deleteEvent($id)
    {
        $schedule = Schedule::findOrFail($id);
        $schedule->delete();

        return response()->json(['message' => 'Event deleted successfully']);
    }

    public function search(Request $request)
    {
        $searchKeywords = $request->input('title');
        $matchingEvents = Schedule::where('title', 'like', '%' . $searchKeywords . '%')->get();

        return response()->json($matchingEvents);
    }
}
Create AuthController
php artisan make:controller AuthController
change it with the following codes:
app\Http\Controllers\AuthController.php

//app\Http\Controllers\AuthController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    public function login()
    {
        $pass = Hash::make("123456789");
        return view("login")->with('password', $pass);
    }

    public function loginAction(Request $request)
    {
        $validated = $request->validate([
            'email' => 'required|email',
            'password' => 'required'
        ]);

        if (!Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {
            throw ValidationException::withMessages([
                'email' => trans('auth.failed')
            ]);
        }

        $request->session()->regenerate();
        return redirect()->route("products.index")->with("success", "Login successfully");
    }

    public function logout(Request $request)
    {
        Auth::guard('web')->logout();

        $request->session()->invalidate();

        return redirect('/login');
    }
}
Create View File resources/views/layouts/app.blade.php
//resources/views/layouts/app.blade.php
<!DOCTYPE html>
<html>

<head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0" />
    <title>Schedule Fullcalendar</title>
    <meta name="csrf-token" content="{{ csrf_token() }}">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.6/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-4Q6Gf2aSP4eDXB8Miphtr37CMZZQ5oXLH2yaXMJ2w8e2ZtHTl7GptT4jmndRuHDT" crossorigin="anonymous">
</head>

<body>
    <div class="layout-wrapper layout-content-navbar">
        <div class="layout-container">
            <div class="layout-page">
                <div class="content-wrapper">
                    @yield('content')
                </div>
            </div>
        </div>
    </div>
</body>

</html>
Home resources/views/schedule/index.blade.php
//resources/views/schedule/index.blade.php
@extends('layouts.app')
@section('content')
<div class="container mt-5">
    <div class="row">
        <h1>Laravel 12 Schedule Calendar | Fullcalendar</h1>
        <div class="col-md-6">
            <div class="input-group mb-3">
                <input type="text" id="searchInput" class="form-control" placeholder="Search events">
                <div class="input-group-append">
                    <button id="searchButton" class="btn btn-primary">Search</button>
                </div>
            </div>
        </div>
        <div class="col-md-6">
            Color : <input type="color" id="myColor" class='form-control' name="colorpicker" onchange="myFunction()" />
            <p id="demo"></p>
        </div>
    </div>

    @if (Session::has('success'))
    <span class="alert alert-success p-2">{{ Session::get('success')}}</span>
    @endif
    @if (Session::has('error'))
    <span>{{ Session::get('error')}}</span>
    @endif

    <div class="card">
        <div class="card-body">
            <div id="calendar" style="width: 100%;height:100vh"></div>
        </div>
    </div>
</div>
<script
    src="https://code.jquery.com/jquery-3.7.1.js"
    integrity="sha256-eKhayi8LEQwp4NKxN+CfCh+3qOVUtJn3QNZ0TciWLP4="
    crossorigin="anonymous"></script>
<script src='https://cdn.jsdelivr.net/npm/fullcalendar@6.1.17/index.global.min.js'></script> <!--fullcalendar.io/docs/initialize-globals-->

<script type="text/javascript">
    $.ajaxSetup({
        headers: {
            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
        }
    });

    var calendarEl = document.getElementById('calendar');
    var events = [];
    var calendar = new FullCalendar.Calendar(calendarEl, {
        headerToolbar: {
            left: 'prev,next today',
            center: 'title',
            right: 'dayGridMonth,timeGridWeek,timeGridDay'
        },
        initialView: 'dayGridMonth',
        events: '/events',
        editable: true,
        selectable: true,
        select: function(info) {
            var title = prompt('Event Title:');
            var color = prompt("Color", "#0d6efd");
            if (title && color) {
                //alert('selected ' + info.startStr + ' to ' + info.endStr);
                $.ajax({
                    url: "/create-schedule",
                    data: 'title=' + title + '&start=' + info.startStr + '&end=' + info.endStr + '&color=' + color + '&_token=' + "{{ csrf_token() }}",
                    type: "post",
                    success: function(data) {
                        alert("Added Successfully");
                        calendar.refetchEvents(); // Refresh events
                    }
                });

            }
        },
        // Drag And Drop
        eventDrop: function(info) {
            var eventId = info.event.id;
            var newStartDate = info.event.start;
            var newEndDate = info.event.end || newStartDate;
            var newStartDateUTC = newStartDate.toISOString().slice(0, 10);
            var newEndDateUTC = newEndDate.toISOString().slice(0, 10);

            $.ajax({
                method: 'post',
                url: `/schedule/${eventId}`,
                data: {
                    '_token': "{{ csrf_token() }}",
                    start_date: newStartDateUTC,
                    end_date: newEndDateUTC,
                },
                success: function(response) {
                    alert(response.message);
                    console.log('Event moved successfully.');
                },
                error: function(error) {
                    console.error('Error moving event:', error);
                }
            });
        },
        // Deleting The Event
        eventContent: function(info) {
            var eventTitle = info.event.title;
            var eventElement = document.createElement('div');
            eventElement.innerHTML = '<span style="cursor: pointer;">❌</span> ' + eventTitle;

            eventElement.querySelector('span').addEventListener('click', function() {
                if (confirm("Are you sure you want to delete this event?")) {
                    var eventId = info.event.id;
                    $.ajax({
                        method: 'get',
                        url: '/schedule/delete/' + eventId,
                        headers: {
                            'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
                        },
                        success: function(response) {
                            alert(response.message);
                            console.log('Event deleted successfully.');
                            calendar.refetchEvents(); // Refresh events after deletion
                        },
                        error: function(error) {
                            console.error('Error deleting event:', error);
                        }
                    });
                }
            });
            return {
                domNodes: [eventElement]
            };
        },
    });
    calendar.render();

    document.getElementById('searchButton').addEventListener('click', function() {
        var searchKeywords = document.getElementById('searchInput').value.toLowerCase();
        filterAndDisplayEvents(searchKeywords);
    });

    function filterAndDisplayEvents(searchKeywords) {
        $.ajax({
            method: 'GET',
            url: `/events/search?title=${searchKeywords}`,
            success: function(response) {
                calendar.removeAllEvents();
                calendar.addEventSource(response);
            },
            error: function(error) {
                console.error('Error searching events:', error);
            }
        });
    }

    function myFunction() {
        var x = document.getElementById("myColor").value;
        document.getElementById("demo").innerHTML = x;
    }
</script>
@endsection
resources/views/login.blade.php
//resources/views/login.blade.php
@extends('layouts.app')
@section('content')
<div class="container mt-5">
    <div class="card">
        <div class="card-header">
            Login
        </div>
        <div class="card-body">
            <form action="{{ route('login.action') }}" method="POST" class="user">
                @csrf
                @if ($errors->any())
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
                @endif
                <div class="mb-3">
                    <label class="form-label">Email</label>
                    <input name="email" type="email" class="form-control form-control-user" id="exampleInputEmail" aria-describedby="emailHelp" placeholder="Enter Email Address...">
                </div>
                <div class="mb-3">
                    <label class="form-label">Password</label>
                    <input name="password" type="password" class="form-control form-control-user" id="exampleInputPassword" placeholder="Password">
                    {{ $password }}
                </div>
                <div class="mb-3">
                    <div class="custom-control custom-checkbox small">
                        <input name="remember" type="checkbox" class="custom-control-input" id="customCheck">
                        <label class="custom-control-label" for="customCheck">Remember
                            Me</label>
                    </div>
                </div>
                <button type="submit" class="btn btn-primary btn-block btn-user">Login</button>
            </form>
        </div>
    </div>

</div>

@endsection
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ScheduleController; //php artisan make:controller ScheduleController
use App\Http\Controllers\AuthController;

Route::get('/', function () {
    return view('welcome');
});

Route::middleware('auth')->group(function () {
    Route::controller(ScheduleController::class)->group(function () {
        Route::get('/fullcalender', 'index')->name('schedule.index');
        Route::post('/create-schedule', 'store')->name('schedule.store');
        Route::get('/events', 'getEvents')->name('schedule.events');
        Route::post('/schedule/{id}', 'update')->name('schedule.update');
        Route::get('/schedule/delete/{id}', 'deleteEvent')->name('schedule.deleteEvent');
        Route::get('/events/search', 'search')->name('schedule.search');
    });
});

Route::controller(AuthController::class)->group(function () {
    Route::get('login', 'login')->name('login');
    Route::post('login', 'loginAction')->name('login.action');
    Route::get('logout', 'logout')->middleware('auth')->name('logout');
});

Wednesday, May 14, 2025

Laravel 12 React Pagination and Search | React Starter kit

Laravel 12 React Pagination and Search | React Starter kit

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

php artisan make:controller UsersController change it with the following codes:
app\Http\Controllers\UsersController.php

//app\Http\Controllers\UsersController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Inertia\Inertia;
use App\Models\User;

class UsersController extends Controller
{
    public function home()
    {
        $query = User::select('id', 'name', 'email', 'created_at')->latest();

        // Handle search
        if (request()->has('search')) {
            $search = request('search');
            $query->where(function ($q) use ($search) {
                $q->where('name', 'like', "%{$search}%")
                    ->orWhere('email', 'like', "%{$search}%");
            });
        }

        $users = $query->paginate(10);

        return Inertia::render('users/index', [
            'users' => $users,
            'filters' => [
                'search' => request('search', ''),
            ]
        ]);
    }
}
Frontend with React and InertiaJS
Add sidebar menu product mainNavItems from reactjs\rescourses\js\components\app-sidebar.tsx
File: pages\users\index.tsx
reactjs\resources\js\pages\users\index.tsx
//reactjs\resources\js\pages\users\index.tsx
import AppLayout from '@/layouts/app-layout';
import { type BreadcrumbItem } from '@/types';
import { Head, router } from '@inertiajs/react';
import { Button } from '@/components/ui/button';
import { Search, ChevronLeft, ChevronRight } from 'lucide-react';
import { Input } from '@/components/ui/input';
import { useState } from 'react';

interface User {
    id: number;
    name: string;
    email: string;
    created_at: string;
}

interface Props {
    users: {
        data: User[];
        current_page: number;
        last_page: number;
        per_page: number;
        total: number;
        from: number;
        to: number;
    };
    filters: {
        search: string;
        filter: string;
    };
}

const breadcrumbs: BreadcrumbItem[] = [
    {
        title: 'Users',
        href: '/admin/users',
    },
];

export const generatePaginationnumber = (currentPage, totalPages) => {

    if (totalPages <= 7) {
       return Array.from({ length: totalPages }, (_, i) => i + 1); 
    }
      
    if (currentPage <= 3) {
        return [1, 2, 3 , "...", totalPages - 1, totalPages];
    }
     
    if (currentPage >= totalPages - 3) {
       console.log("2");
       return [1, 2, 3, "...", totalPages - 2, totalPages - 1, totalPages];
    }
     
    return [
      1,
      "...",
      currentPage - 1,
      currentPage,
      currentPage + 1,
      "...",
      totalPages,
    ];
};

export default function UsersIndex({ users, filters }: Props) {
    //console.log(users); 

    const params = new URLSearchParams(window.location.search);
    const currentPage = Number(params.get("page")) || 1;
    //console.log(currentPage); 

    const totalPages = Math.ceil(Number(users.total) / Number(users.per_page));
    //const totalPages = users.total;
    console.log(totalPages);

    const allPages = generatePaginationnumber(currentPage, totalPages);

    const [searchTerm, setSearchTerm] = useState(filters.search);

    const handleSearch = (e: React.FormEvent<HTMLFormElement>) => {
        e.preventDefault();
        router.get(route('users.home'), {
            search: searchTerm,
        }, {
            preserveState: true,
            preserveScroll: true,
        });
    };
    
    const handlePageChange = (page: number) => {
        router.get(route('users.home'), {
            page,
        }, {
            preserveState: true,
            preserveScroll: true,
        });
    };

    return (
        <AppLayout breadcrumbs={breadcrumbs}>
            <Head title="Users" />
            <div className="flex h-full flex-1 flex-col gap-6 rounded-xl p-6 bg-gradient-to-br from-background to-muted/20">
                <div className="flex gap-4 mb-4">
                    <form onSubmit={handleSearch} className="relative flex-1">
                        <Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-muted-foreground" />
                        <Input placeholder="Search name or email..."
                            value={searchTerm}
                            onChange={(e) => setSearchTerm(e.target.value)}
                            className="pl-10 bg-gray-200  text-black"
                        />
                    </form>                                    
                </div>
                <div className="rounded-md border">
                    <div className="relative w-full overflow-auto">
                        <table className="w-full caption-bottom text-sm  bg-white rounded-lg">
                            <thead>
                                <tr className="bg-gray-200 text-black">
                                    <th className="py-2 px-4 text-left border-b">ID</th>
                                    <th className="py-2 px-4 text-left border-b">Name</th>
                                    <th className="py-2 px-4 text-left border-b">Email</th>
                                    <th className="py-2 px-4 text-left border-b">Created At</th>                                   
                                    <th className="py-2 px-4 text-right border-b">Actions</th>                                
                                </tr>                            
                            </thead>     
                            <tbody>
                                {users.data.map((user) => (
                                    <tr key={user.id} className="hover:bg-gray-50 text-black">
                                        <td className="p-4 align-middle font-medium border-b">{user.id}</td>
                                        <td className="p-4 align-middle font-medium border-b">{user.name}</td>
                                        <td className="p-4 align-middle font-medium border-b">{user.email}</td>
                                        <td className="p-4 align-middle font-medium border-b">{new Date(user.created_at).toLocaleDateString()}</td>
                                        <td className="p-4 align-middle text-right border-b">
                                            <div className="flex justify-end gap-2">
                                                <Button variant="ghost"
                                                    className="w-full sm:w-auto bg-blue-600 text-white rounded-lg px-4 py-2 hover:bg-blue-700"
                                                >
                                                    Edit
                                                </Button>                                                
                                                <Button variant="ghost"
                                                    className="w-full sm:w-auto bg-red-600 text-white rounded-lg px-4 py-2 hover:bg-red-700 transition transition ml-2"
                                                >
                                                    Delete
                                                </Button>                                            
                                            </div>
                                        </td>
                                    </tr>
                                ))}
                                {users.total === 0 && (
                                    <tr>
                                        <td colSpan={6} className="p-4 text-center text-muted-foreground">
                                            No users Found                                        
                                        </td>
                                    </tr>
                                )}
                            </tbody> 
                        </table>                    
                    </div>                
                </div>

                {/* Pagination */}
                <div className="flex items-center justify-between px-2">
                    <div className="text-sm text-muted-foreground">
                        Showing {users.from} to {users.to} of {users.total} results  
                    </div>                    
                    <div className="flex items-center space-x-2">
                        <Button variant="outline"
                            size="icon"
                            onClick={() => handlePageChange(users.current_page - 1)}
                            disabled={users.current_page === 1}
                        >
                            <ChevronLeft className="h-4 w-4" />
                        </Button>   
                        <div className="flex items-center space-x-1">
                        {allPages.map((page, index) => (

                            <Button key={index}
                            variant={page === users.current_page ? "default" : "outline"}
                            size="icon"
                            onClick={() => handlePageChange(page)}
                            >
                            {page}
                            </Button> 
                           
                        ))}
                        </div>                        
                        <Button variant="outline"
                            size="icon"
                            onClick={() => handlePageChange(users.current_page + 1)}
                            disabled={users.current_page === users.last_page}
                        >
                            <ChevronRight className="h-4 w-4" />
                        </Button>                    
                    </div>                
                </div> 
            </div>
        </AppLayout>
    );
}
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use App\Http\Controllers\UsersController;

Route::get('/', function () {
    return Inertia::render('welcome');
})->name('home');

Route::middleware(['auth', 'verified'])->group(function () {
    Route::get('dashboard', function () {
        return Inertia::render('dashboard');
    })->name('dashboard');

    Route::get('/admin/users-pagination-search', [UsersController::class, 'home'])->name('users.home');
});

require __DIR__.'/settings.php';
require __DIR__.'/auth.php';
Run php artisan serve and npm run dev myapp>composer run dev
Starting Laravel development server: http://127.0.0.1:8000

Sunday, May 11, 2025

Laravel 12 CRUD Image Upload, Search, Soft Deletes Bootstrap and Vite

Laravel 12 CRUD Image Upload, Search, Soft Deletes Bootstrap and Vite e

Download Laravel App

https://laravel.com/docs/12.x/installation

Connecting our Database

open .env file root directory.

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=8889
DB_DATABASE=laravel12dev
DB_USERNAME=root
DB_PASSWORD=root

Database Migration
php artisan migrate

myapp>php artisan migrate
Migration table created successfully.
check database table

Create tables Product Model php artisan make:model Product -m myapp>php artisan make:model Product -m Open new Products migrations yourproject/database/migrations laravelproject\database\migrations\_create_products_table.php
//laravelproject\database\migrations\_create_products_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('products', function (Blueprint $table) {
            $table->id();
            $table->string('name');
            $table->string('description')->nullable();
            $table->integer('quantity');
            $table->float('price');
            $table->string('image')->nullable();
            $table->enum('status', ['active', 'in-active'])->default('active');
            $table->softDeletes();
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('products');
    }
};
run
myapp>php artisan migrate
update Product Model
app/models/Product.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;

class Product extends Model
{
    use SoftDeletes, HasFactory;

    protected $fillable = [
        "name",
        "description",
        "quantity",
        "price",
        "status",
        "image",
    ];
}
Create PostController
php artisan make:controller PostController
change it with the following codes:
app\Http\Controllers\PostController.php

//app\Http\Controllers\ProductController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Models\Product;
use Illuminate\Support\Facades\Storage;

class ProductController extends Controller
{
    public function index(Request $request)
    {
        // $products = Product::all();
        $query = Product::query();
        if (request()->has("search") && $request->search) {
            $query = $query->where("name", "like", "%" . $request->search . "%")
                ->orWhere('description', 'like', "%" . $request->search . "%");
        }
        $products = $query->latest()->paginate(3);

        return view("product.index", compact("products"));
    }

    public function create()
    {
        return view("product.create");
    }

    public function store(Request $request)
    {
        $validated = $request->validate([
            "name" => "required|string",
            "description" => "nullable|string",
            "price" => "required|numeric",
            "quantity" => "required|numeric",
            "status" => "required",
            "image" => "nullable|image|mimes:jpg,png",
        ]);

        if ($request->hasFile("image")) { //php artisan storage:link
            $validated["image"] = $request->file("image")->store("products", "public");
        }
        Product::create($validated);

        return redirect()->route("products.index")->with("success", "product added successfully");
    }

    public function show($id)
    {
        $product = Product::find($id);
        return view("product.show", compact("product"));
    }

    public function edit($id)
    {
        $product = Product::find($id);
        return view("product.edit", compact("product"));
    }

    public function update(Request $request, $id)
    {
        $validated = $request->validate([
            "name" => "required|string",
            "description" => "nullable|string",
            "price" => "required|numeric",
            "quantity" => "required|numeric",
            "status" => "required",
            "image" => "nullable|image|mimes:jpg,png",
        ]);

        if ($request->hasFile("image")) {
            if ($request->image && Storage::disk("public")->exists($request->image)) {
                Storage::disk("public")->delete($request->image);
            }
            $validated["image"] = $request->file("image")->store("products", "public");
        }

        Product::find($id)->update($validated);

        return redirect()->route("products.index")->with("success", "product updated successfully!");
    }

    public function destroy($id)
    {
        Product::find($id)->delete();
        return redirect()->route("products.index")->with("success", "product deleted successfully!");
    }

    public function trashedProducts(Request $request)
    {
        $query = Product::query()->onlyTrashed();
        $products = $query->paginate(3);
        return view("product.deleted-products", compact("products"));
    }

    public function showTrashed($id)
    {
        $product = Product::onlyTrashed()->findOrFail($id);
        return view("product.show", compact("product"));
    }

    public function restoreProduct($id)
    {
        $product = Product::onlyTrashed()->findOrFail($id);
        $product->restore();
        return redirect()->route("products.index")->with("success", "product restored successfully");
    }

    public function destroyProduct($id)
    {
        $product = Product::onlyTrashed()->findOrFail($id);
        if ($product->image && Storage::exists($product->image)) {
            Storage::delete($product->image);
        }
        $product->forceDelete();

        return redirect()->route("products.index")->with("success", "product was force deleted successfully!");
    }
}
Create AuthController
php artisan make:controller AuthController
change it with the following codes:
app\Http\Controllers\AuthController.php

//app\Http\Controllers\AuthController.php
<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;

class AuthController extends Controller
{
    public function login()
    {
        $pass = Hash::make("123456789");
        return view("login")->with('password', $pass);
    }

    public function loginAction(Request $request)
    {
        $validated = $request->validate([
            'email' => 'required|email',
            'password' => 'required'
        ]);

        if (!Auth::attempt($request->only('email', 'password'), $request->boolean('remember'))) {
            throw ValidationException::withMessages([
                'email' => trans('auth.failed')
            ]);
        }

        $request->session()->regenerate();
        return redirect()->route("products.index")->with("success", "Login successfully");
    }

    public function logout(Request $request)
    {
        Auth::guard('web')->logout();

        $request->session()->invalidate();

        return redirect('/login');
    }
}
Create View File resources/views/layouts/app.blade.php
//resources/views/layouts/app.blade.php
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Laravel 12 CRUD</title>
    @vite(['resources/sass/app.scss', 'resources/js/app.js'])
</head>

<body>
    <nav class="py-2 bg-body-tertiary border-bottom">
        <div class="container d-flex flex-wrap">
            <ul class="nav me-auto">
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2 active" aria-current="page">Home</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">Features</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">Pricing</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">FAQs</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">About</a></li>
            </ul>
            <ul class="nav">
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">Login</a></li>
                <li class="nav-item"><a href="#" class="nav-link link-body-emphasis px-2">Sign up</a></li>
            </ul>
        </div>
    </nav>
    <header class="py-3 mb-4 border-bottom">
        <div class="container d-flex flex-wrap justify-content-center"> <a href="/" class="d-flex align-items-center mb-3 mb-lg-0 me-lg-auto link-body-emphasis text-decoration-none">
                <svg class="bi me-2" width="40" height="32" aria-hidden="true">
                    <use xlink:href="#bootstrap"></use>
                </svg> <span class="fs-4">Cairocoders</span> </a>
        </div>
    </header>
    @yield('content')
</body>

</html>
Home resources/views/product/index.blade.php
//resources/views/product/index.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            <div class="row">
                <div class="col">
                    <h2>Product List</h2>
                </div>
                <div class="col">
                    <div class="row">
                        <div class="col-md-12">
                            <div class="row">
                                <div class="col">
                                    <div><a href="{{ route('products.trashed')}}" class="float-end btn btn-danger" style="margin-left:10px;">Deleted</a></div>
                                    <div><a href="{{ route('products.create')}}" class="float-end btn btn-primary">Add New</a></div>
                                </div>
                            </div>
                        </div>
                        <div class="col-md-12" style="padding-top:20px;">
                            <form class="d-flex" role="search" action="{{ route('products.index')}}" method="GET">
                                @csrf
                                <input class="form-control me-2" name="search" type="search" placeholder="Search"
                                    aria-label="Search">
                                <button class="btn btn-outline-success" type="submit">Search</button>
                            </form>
                        </div>
                    </div>

                </div>
            </div>
        </div>
        @if (Session::has('success'))
        <span class="alert alert-success p-2">{{ Session::get('success')}}</span>
        @endif
        @if (Session::has('error'))
        <span>{{ Session::get('error')}}</span>
        @endif
        <div class="card-body">
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">Photo</th>
                        <th scope="col">Product Name</th>
                        <th scope="col">Quantity</th>
                        <th scope="col">Price</th>
                        <th scope="col">Status</th>
                        <th scope="col">Description</th>
                        <th scope="col">Action</th>
                    </tr>
                </thead>
                <tbody>
                    @if (count($products) > 0)
                    @foreach ($products as $product)
                    <tr>
                        <th scope="row">{{ $loop->iteration }}</th>
                        <td><img src="{{ asset('storage/' . $product->image) }}" width="100"></td>
                        <td>{{ str($product->name)->words(2)}}</td>
                        <td>{{ $product->quantity}}</td>
                        <td>{{ $product->price}}</td>
                        <td>{{ $product->status}}</td>
                        <td>{{ str($product->description)->words(5)}}</td>
                        <td>
                            <a href="{{ route('products.show', $product->id) }} " class="btn btn-success btn">show</a>
                            <a href="{{ route('products.edit', $product->id) }} " class="btn btn-primary btn">edit</a>
                            <form action="{{ route('products.destroy', $product->id) }}" method="POST"
                                style="display:inline-block">
                                @csrf @method('DELETE')
                                <button onclick="return confirm('Are you sure?')"
                                    class="btn btn btn-danger">Delete</button>
                            </form>
                        </td>
                    </tr>
                    @endforeach
                    @else
                    <tr>
                        <td colspan="8" class="text-center">No Data Found!</td>
                    </tr>
                    @endif

                </tbody>
            </table>
            {{ $products->links('pagination::bootstrap-5') }}
        </div>
    </div>
</div>
@endsection
resources/views/product/create.blade.php
//resources/views/product/create.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            Add New Product
        </div>
        <div class="card-body">
            <form action="{{ route('products.store')}}" method="post" enctype="multipart/form-data">
                @csrf
                @include('product.form')

                <button type="submit" class="btn btn-primary">Save</button>
                <a href="{{ route('products.index')}}" class="btn btn-secondary">Cancel</a>
            </form>
        </div>
    </div>

</div>

@endsection
resources/views/product/form.blade.php
//resources/views/product/form.blade.php
<div class="mb-3">
    <label class="form-label">Product Name *</label>
    <input type="text" name="name" class="form-control" value="{{ old('name', $product->name ?? '') }}">
    @error('name')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Description</label>
    <textarea name="description" class="form-control">{{ old('description', $product->description ?? '') }}</textarea>
    @error('description')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Price *</label>
    <input type="number" name="price" step="0.01" class="form-control"
        value="{{ old('price', $product->price ?? '') }}">
    @error('price')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Quantity *</label>
    <input type="number" name="quantity" class="form-control" value="{{ old('quantity', $product->quantity ?? '') }}">
    @error('quantity')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Status *</label>
    <select name="status" class="form-select">
        <option value="active" {{ (old('status', $product->status ?? '') == 'active') ? 'selected' : '' }}>Active</option>
        <option value="in-active" {{ (old('status', $product->status ?? '') == 'in-active') ? 'selected' : '' }}>Inactive
        </option>
    </select>
    @error('status')
    <span class="text-danger">{{ $message }}</span>
    @enderror
</div>

<div class="mb-3">
    <label class="form-label">Product Image</label>
    <input type="file" name="image" class="form-control">
    @error('image')
    <span class="text-danger">{{ $message }}</span>
    @enderror
    @if (!empty($product->image))
    <img src="{{ asset('storage/' . $product->image) }}" class="mt-2" width="300">
    @endif
</div>
resources/views/product/edit.blade.php
//resources/views/product/edit.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            Edit Product
        </div>
        <div class="card-body">
            <form action="{{ route('products.update', $product->id) }}" method="POST" enctype="multipart/form-data">
                @csrf
                @method('PUT')
                @include('product.form')

                <button type="submit" class="btn btn-primary">Update</button>
                <a href="{{ route('products.index')}}" class="btn btn-secondary">Cancel</a>
            </form>
        </div>
    </div>

</div>

@endsection
resources/views/product/show.blade.php
//resources/views/product/show.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <h2>Product Details</h2>

    <div class="card">
        <div class="card-body">
            @if($product->image)
            <img src="{{ asset('storage/' . $product->image) }}" width="250" class="mb-3">
            @endif

            <p><strong>Name:</strong> {{ $product->name }}</p>
            <p><strong>Description:</strong> {{ $product->description }}</p>
            <p><strong>Price:</strong> ${{ number_format($product->price, 2) }}</p>
            <p><strong>Quantity:</strong> {{ $product->quantity }}</p>
            <p><strong>Status:</strong>
                <span class="badge bg-{{ $product->status === 'active' ? 'success' : 'secondary' }}">
                    {{ ucfirst($product->status) }}
                </span>
            </p>

            <a href="{{ route('products.edit', $product->id) }}" class="btn btn-warning">Edit</a>
            <a href="{{ route('products.index') }}" class="btn btn-secondary">Back</a>
        </div>
    </div>
</div>

@endsection
resources/views/product/deleted-products.blade.php
//resources/views/product/deleted-products.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            <div class="row">
                <div class="col">
                    <h2>Product List</h2>
                </div>
                <div class="col">
                    <div class="row">
                        <div class="col-md-12">
                            <a href="{{ route('products.index')}}" class="float-end btn btn-warning">View All
                                Products</a>
                        </div>
                        <div class="col-md-12" style="padding-top:20px;">
                            <form class="d-flex" role="search" action="{{ route('products.trashed')}}" method="GET">
                                @csrf
                                <input class="form-control me-2" name="search" type="search" placeholder="Search"
                                    aria-label="Search">
                                <button class="btn btn-outline-success" type="submit">Search</button>
                            </form>
                        </div>
                    </div>

                </div>
            </div>
        </div>
        @if (Session::has('success'))
        <span class="alert alert-success p-2">{{ Session::get('success')}}</span>
        @endif
        @if (Session::has('error'))
        <span>{{ Session::get('error')}}</span>
        @endif
        <div class="card-body">
            <table class="table table-striped">
                <thead>
                    <tr>
                        <th scope="col">#</th>
                        <th scope="col">Photo</th>
                        <th scope="col">Product Name</th>
                        <th scope="col">Quantity</th>
                        <th scope="col">Price</th>
                        <th scope="col">Status</th>
                        <th scope="col">Description</th>
                        <th scope="col">Action</th>
                    </tr>
                </thead>
                <tbody>
                    @if (count($products) > 0)
                    @foreach ($products as $product)
                    <tr>
                        <th scope="row">{{ $loop->iteration }}</th>
                        <td><img src="{{ asset('storage/' . $product->image) }}" width="100"></td>
                        <td>{{ $product->name}}</td>
                        <td>{{ $product->quantity}}</td>
                        <td>{{ $product->price}}</td>
                        <td>{{ $product->status}}</td>
                        <td>{{ $product->description}}</td>
                        <td>
                            <a href="{{ route('trashed.show', $product->id) }}" class="btn btn-success btn">show</a>
                            <form action="{{ route('trashed.restore', $product->id) }}" method="POST"
                                style="display:inline-block">
                                @csrf
                                @method('PUT')
                                <button onclick="return confirm('Are you sure?')"
                                    class="btn btn btn-info">Restore</button>
                            </form>
                            <form action="{{ route('trashed.delete', $product->id) }}" method="POST"
                                style="display:inline-block">
                                @csrf
                                @method('DELETE')
                                <button onclick="return confirm('Are you sure?')"
                                    class="btn btn btn-danger">Delete</button>
                            </form>
                        </td>
                    </tr>
                    @endforeach
                    @else
                    <tr>
                        <td colspan="8" class="text-center">No Data Found!</td>
                    </tr>
                    @endif

                </tbody>
            </table>
            {{ $products->links() }}
        </div>
    </div>
</div>
@endsection
resources/views/login.blade.php
//resources/views/login.blade.php
@extends('layouts.layout')
@section('content')
<div class="container">
    <div class="card">
        <div class="card-header">
            Login
        </div>
        <div class="card-body">
            <form action="{{ route('login.action') }}" method="POST" class="user">
                @csrf
                @if ($errors->any())
                <div class="alert alert-danger">
                    <ul>
                        @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                        @endforeach
                    </ul>
                </div>
                @endif
                <div class="mb-3">
                    <label class="form-label">Email</label>
                    <input name="email" type="email" class="form-control form-control-user" id="exampleInputEmail" aria-describedby="emailHelp" placeholder="Enter Email Address...">
                </div>
                <div class="mb-3">
                    <label class="form-label">Password</label>
                    <input name="password" type="password" class="form-control form-control-user" id="exampleInputPassword" placeholder="Password">
                    {{ $password }}
                </div>
                <div class="mb-3">
                    <div class="custom-control custom-checkbox small">
                        <input name="remember" type="checkbox" class="custom-control-input" id="customCheck">
                        <label class="custom-control-label" for="customCheck">Remember
                            Me</label>
                    </div>
                </div>
                <button type="submit" class="btn btn-primary btn-block btn-user">Login</button>
            </form>
        </div>
    </div>

</div>

@endsection
Routes
routes/web.php
//routes/web.php
<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\ProductController;
use App\Http\Controllers\AuthController;

Route::get('/', function () {
    return view('welcome');
});

Route::middleware('auth')->group(function () {
    //Route::get('dashboard', function () {
    //    return view('dashboard');
    //})->name('dashboard');

    Route::controller(ProductController::class)->group(function () {
        Route::get('/admin/products', 'index')->name('products.index');
        Route::get('/admin/products/create', 'create')->name('products.create');
        Route::post('/admin/products', 'store')->name('products.store');
        Route::get('/admin/products/show/{product}', 'show')->name('products.show');
        Route::get('/admin/products/{product}/edit', 'edit')->name('products.edit');
        Route::put('/admin/products/{product}', 'update')->name('products.update');
        Route::delete('/admin/products/{product}', 'destroy')->name('products.destroy');

        Route::get('/admin/deleted-products', 'trashedProducts')->name('products.trashed');
        Route::get('/admin/show-trashed-product/{id}', 'showTrashed')->name('trashed.show');
        Route::put('/admin/restore-product/{id}', 'restoreProduct')->name('trashed.restore');
        Route::delete('/admin/delete-product/{id}', 'destroyProduct')->name('trashed.delete');
    });
});

Route::controller(AuthController::class)->group(function () {
    Route::get('login', 'login')->name('login');
    Route::post('login', 'loginAction')->name('login.action');
    Route::get('logout', 'logout')->middleware('auth')->name('logout');
});

Related Post