How to seamlessly Roll Auth into your React App using Firebase Authentication
Lets Comprehensively walk-through Firebase SDK setup, user registration, and storing data in Firestore.We'll Enhance your app's security and user experience with highlights on best practices.
Introduction
Wrestling with authentication complexity? Want to ship your app in a weekend?
Worry no more because Firebase BaaS (Backend as a Service) has you covered.
Think about it: no more worrying about password hashing, token management, or setting up authentication servers. Instead of spending time building the main app functionality, you can leverage Firebase Authentication for a production-ready solution in minutes.
Why Firebase Authentication?
- Security Out of the Box: Firebase handles sensitive operations like password hashing, token management, and secure session handling.
- Simple Integration: With just a few lines of code, you get a production-ready authentication system.
- Scalability: Built on Google’s infrastructure, it scales automatically with your user base.
Prerequisites
- Basic React knowledge
- Node.js installed
- A Firebase account (free)
- Familiarity with async/await and promises
- Basic understanding of authentication concepts
What We'll Build
We'll create a complete authentication system featuring:
- Email/password sign-up and sign-in
- Password reset functionality
- User authentication credentials stored in Firestore
- Persistent login sessions and Authentication State
Try Kodaschool for free
Click below to sign up and get access to free web, android and iOs challenges.
Initial Setup and Configuration
Create a Firebase Project
- Navigate to the Firebase page and create a project with a name of your choice.
- For this project, let’s disable Google Analytics.
- Allow a few minutes for the project to be created.
Configure Dashboard Services
- On the side panel, under the Build dropdown, select:
- Authentication
- Firestore Database
Configure Authentication
Click on the Get Started button and enable the providers you need for authentication.
For our setup, we’ll only use the Email Provider.After selecting your provider, you’ll be navigated to the Sign-in Method page, which shows a list of your active authentication providers.
After selecting your provider, you’ll be navigated to the Sign-in Method page, which shows a list of your active authentication providers.
Configure Firestore Database
- Select Firestore Database from the side panel.
- Click on the Create Database button.
- Let’s create a collection of users to store user information with an auto-generated ID.
Ensure the database is in Test-mode (configures basic firestore rules):
This Should be the Final Output:
Firestore Security Rules
// security rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if request.auth != null && request.auth.uid == userId;
allow write: if request.auth != null && request.auth.uid == userId;
}
}
}
service cloud.firestore { ... }
Defines service being as => Firestore.
match /databases/{database}/documents { ... }
Matches all documents in Firestore db.
match /users/{userId} { ... }
Specifically matches documents within the users collection, where {userId} its representing any user ID.
allow read: if request.auth != null && request.auth.uid == userId;
Allows read access to a user document only if the request is authenticated (request.auth != null) and the authenticated user's ID matches the document ID (request.auth.uid == userId).
allow write: if request.auth != null && request.auth.uid == userId;
Allows write access to a user document under the same conditions as read access:
Register our Web-app in Firebase Project
Provide a name of your choice to your project.
Copy the code in a code/text editor of your choice:
Key Concepts in Authentication
Firebase provides a variety of functions to enable a custom and simplified authentication flow:
- Creating New Users
Functionality for registering users with an email and password. - Signing In
Enables user login to access restricted features. - Password Reset
Allows users to reset their password securely if forgotten. - Signing Out
Lets users log out from their account and end the session. - Auth State Changes
A way to listen to real-time authentication state changes, like login or logout, across the app.
Step 1. Creating New users
import { createUserWithEmailAndPassword } from 'firebase/auth';
const handleSignup=()=>{
try {
// Create auth account
const userCredential = await createUserWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// Store additional user data in Firestore
return user;
} catch (error) {
console.error("Error in signup process:", error);
throw error; // Re-throw to handle in UI
}
}
// Common Error Codes:
// auth/email-already-in-use
// auth/invalid-email
// auth/operation-not-allowed
// auth/weak-password
The createUserWithEmailAndPassword async function handles user registration in Firebase Auth by taking user credentials (auth, email, password) as arguments.
It creates a new user account in your Firebase project, sets up their initial password, and returns a Promise with the user's credentials. The auth object represents your initialized Firebase auth instance from the 'firebase/auth' , while email and password come from user input fields
step 2. Signing in Users
import { signInWithEmailAndPassword } from 'firebase/auth';
const handlesignin=()=>{
try {
// Create auth account
const userCredential = awaitsignInWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// Store additional user data in Firestore
return user;
} catch (error) {
console.error("Error in signin process:", error);
throw error; // Re-throw to handle in UI
}
}
// Common Error Codes:
// auth/invalid-email
// auth/user-disabled
// auth/user-not-found
// auth/wrong-password
The signInWithEmailAndPassword async function handles user authentication in Firebase by validating provided client side credentials (auth, email, password). It checks these against existing Firebase user accounts, and if matched, returns a Promise containing the user's credential object.
3.Password Reset
import { sendPasswordResetEmail } from 'firebase/auth';
const handleResetPassword = async () => {
try {
await sendPasswordResetEmail(auth, email);
//email successfully sent to your inbox
} catch (error) {
const errorCode = error.code;
const errorMessage = error.message;
}
};
The sendPasswordResetEmail async function manages Firebase's password recovery process. When triggered with your auth instance and the user's email, it automatically sends a reset link to that email address. This link gives users a secure way to create a new password. The function returns a Promise - if successful, the user gets a recovery email; if it fails (like with an invalid email), it throws an error that your code can catch and handle appropriately.
4.sign out
import { signOut } from 'firebase/auth';
const logout = () => {
if (window.confirm("Are you sure you want to log out?")) {
signOut(auth)
.then(() => {
console.log("User signed out");
//navigate to get started page
})
.catch((error) => {
console.error("Logout error:", error.message);
});
}
};
The signOut function terminates the user's current session, clearing all authentication state. When invoked, it removes the user's tokens and returns a Promise that resolves once they're successfully logged out.
5.AuthState Changes(users state change)
import { onAuthStateChanged } from 'firebase/auth';
// Set up listener
onAuthStateChanged(auth, (user) => {
if (user) {
// User object !== null
} else {
// User is signed out
}
});
The onAuthStateChanged real-time listener keeps track of user authentication status . It watches for any changes to the user's sign-in state (logging in, logging out, or token expiry) and triggers a callback function that lets you update your app accordingly.This helps maintain proper UI state -eg showing different content for logged-in versus logged-out users.
Firestore Concepts
Database Structure
- Collections: Groups of documents, e.g., a
'users'
collection organized like folders. - Documents: Individual data records that contain user data with unique IDs. Documents can also have subcollections.
Core Database Operations
- Reading Data
getDoc
: Reads a single document.getDocs
: Reads multiple documents.- Real-time listeners: Listens to changes in real-time to reflect updates immediately.
- Writing Data
setDoc
: Creates or overwrites documents.addDoc
: Adds new documents.updateDoc
: Updates existing documents.
Other Operations
- DocumentSnapshot: Represents the state of a single document.
- QuerySnapshot: Represents the state of a collection or query result, often as an array of DocumentSnapshot objects.
- DocumentReference: A reference to a specific document in Firestore, used to perform operations like reading, writing, or deleting data.
Lets Ship something :-)
Lets initialize our project using vite
npm init vite@latest
Select React and Javascript option
Ensure your src folder resembles this;
frontend/
├── src/
├── auth/ # Auth components
│ ├── resetpassword.jsx
│ ├── signin.jsx
│ ├── signinbtn.jsx
│ └── signup.jsx
│
├── context/
│ └── authctx.jsx
│
├── navbar/
│ ├── index.css
│ ├── index.jsx
│ └── RightMenu.jsx
│
├── styles/
│ ├── input.css
│ └── output.css
│
├── App.css
├── App.jsx
├── error.jsx # Fallback ui for non-existent routes
├── errorboundary.jsx # Error boundary for React errors
├── firebase.js
├── home.jsx
├── index.css
└── main.jsx
Global Files
Create a src/firebase.js file
import { initializeApp } from 'firebase/app';
import { getAuth } from 'firebase/auth';
import { getFirestore, collection, addDoc,doc,getDoc } from 'firebase/firestore';
const firebaseConfig = {
apiKey: "AIzaSy..........A",
authDomain: "ivanp....o-..............",
projectId: "ivan........,
storageBucket: "iva.......com",
messagingSenderId: "789..........3710",
appId: "1:78977............9df"
};
export const app = initializeApp(firebaseConfig);
export const auth =getAuth(app);
export const db =getFirestore(app);
export { collection, addDoc ,doc,getDoc};
Create a context for global statemanagement
authctx.jsx
import React, { createContext, useContext, useState, useEffect } from 'react';
import { auth } from '../firebase';
const AuthContext = createContext();
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [isAuthLoading, setIsAuthLoading] = useState(true);
useEffect(() => {
// Add an observer for changes to the user's sign-in state
const unsubscribe = auth.onAuthStateChanged((user) => {
setUser(user);
setIsAuthLoading(false);
});
return unsubscribe;
}, []);
return (
<AuthContext.Provider value={{ user, setUser, isAuthLoading }}>
{children}
</AuthContext.Provider>
);
};
export const useAuth = () => useContext(AuthContext);
Parse the provider to our root file main.jsx
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import App from './App.jsx'
import './styles/output.css'
import { AuthProvider } from './context/authctx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<AuthProvider>
<App />
</AuthProvider>
</StrictMode>,
)
ErrorBounary.jsx file fallback-ui for our react-errors
import { Component } from 'react';
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.log(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1 className='p-10 m-3 flex justify-center align-baseline'>Something went wrong.</h1>;
}
// eslint-disable-next-line react/prop-types
return this.props.children;
}
}
export default ErrorBoundary;
Error.jsx file for unmatched routes
import React from 'react'
const Error = () => {
return (
<>
<div className="text-center py-12 mb-12 px-6">
<h1 className="text-3xl lg:text-4xl mb-6 font-display text-black leading-tight">Sorry, this page doesn't exist</h1>
<p className="max-w-lg mx-auto">We are sorry, but the page you are looking for cannot be found.</p>
</div></>
)
}
export default Error
Create an App.jsx for our Routes configuration:
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Navbar from './navbar/index';
import Error from './error';
import { Home } from './home';
import { SignUpForm } from './auth/signup';
import { SignIn } from './auth/signin';
import { ResetPassword } from './auth/resetpassword';
import ErrorBoundary from './errorboundary';
// Create a new component for the container
const Container = ({ children }) => (
<div className="container mx-auto px-4 py-8">{children}</div>
);
// Create a new component for the Navbar container
const NavbarContainer = () => (
<div className="sticky top-0 z-50">
<Navbar />
</div>
);
function App() {
return (
<ErrorBoundary>
<Router>
<NavbarContainer />
<Container>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/signup" element={<SignUpForm />} />
<Route path="/signin" element={<SignIn />} />
<Route path="/resetpassword" element={<ResetPassword />} />
<Route path="*" element={<Error />} />
</Routes>
</Container>
</Router>
</ErrorBoundary>
);
}
export default App;
Lets create home.jsx
import { useAuth } from './context//authctx';
export const Home = () => {
const { user } = useAuth();
return (
<>
<section className="bg-gray-100 py-24">
<div className="grid max-w-7xl mx-auto grid-cols-1 md:grid-cols-2 items-center">
{user ? <p> `Welsome {user.email} to our Homepage ` </p>: <p>Loggin to view this page</p>}
</div>
</section>
</>
);
};
When the user is authenticated, this is the homepage one will see
When the user is not logged in below will be his ui;
Auth components in the Auth folder
Lets create the resetpassword.jsx
The resetPassword component
1. uses Firebase authentication for handling the password reset process
2. manages three pieces of state(email sent, messages state , error )
handleResetPassword:
- Calls Firebase's sendPasswordResetEmail function thus shows error or 200
messsage
import React, { useState } from 'react';
import { Typography, Button } from '@material-tailwind/react';
import { sendPasswordResetEmail } from 'firebase/auth';
import { auth } from '../firebase';
export const ResetPassword = () => {
const [email, setEmail] = useState('');
const [message, setMessage] = useState('');
const [error, setError] = useState('');
const handleEmailChange = (e) => {
setEmail(e.target.value);
};
const handleResetPassword = async () => {
try {
await sendPasswordResetEmail(auth, email);
setMessage('Password reset email sent. Check your inbox.');
setError('');
} catch (error) {
setError(`Error sending password reset email: ${error.message}`);
setMessage('');
}
};
return (
<div className="fixed z-10 inset-0 flex items-center justify-center overflow-y-auto" role="dialog" aria-modal="true">
<div className="inline-block bg-white rounded-lg px-4 pt-5 pb-2 text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full sm:p-6">
<div>
<h3 className="text-lg leading-6 font-medium text-gray-900" id="modal-title">
Reset Password
</h3>
<div className="mt-2">
<input
id="email"
type="email"
className="shadow-sm focus:ring-indigo-500 focus:border-indigo-500 block w-full sm:text-sm border-gray-300 rounded-md"
placeholder="Email address"
value={email}
onChange={handleEmailChange}
/>
</div>
</div>
<div className="mt-5 sm:mt-6">
<Button
variant="gradient"
fullWidth
onClick={handleResetPassword}
className="bg-indigo-600 text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 sm:text-sm"
>
Send reset link
</Button>
</div>
{message && (
<Typography variant="small" color="green" className="mt-4">
{message}
</Typography>
)}
{error && (
<Typography variant="small" color="red" className="mt-4">
{error}
</Typography>
)}
</div>
</div>
);
};
Lets create a signin.jsx
Signin component
State Management.:
- Tracks email, password, errors, loading state
- Shows/hides password visibility
Validation:
- Email validation using regex
- Password minimum length (8 characters)
Authentication:
- Uses Firebase's signInWithEmailAndPassword
- Shows loading spinner during authentication
- Handles success/error states
import React, { useState } from "react";
import {
Card,
CardHeader,
CardBody,
CardFooter,
Typography,
Input,
Checkbox,
Button,
} from "@material-tailwind/react";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "../firebase";
import { useNavigate,Link } from "react-router-dom";
import { useAuth } from "../context/authctx";
import { FaEye, FaEyeSlash } from "react-icons/fa";
import { PuffLoader } from "react-spinners";
export const SignIn = () => {
const [password, setPassword] = useState("");
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false)
const navigate = useNavigate();
const { setUser } = useAuth();
const MIN_PASSWORD_LENGTH = 8;
const validateEmail = (email) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
};
const togglePasswordVisibility = () => {
setShowPassword(!showPassword);
};
const handleForgotPassword = () => {
// Add your forgot password logic here
console.log("Forgot password clicked");
navigate("/resetpassword");
};
const handlePasswordChange = (e) => {
const value = e.target.value.trim();
setPassword(value);
setPasswordError(
value.length >= MIN_PASSWORD_LENGTH
? ""
: `Password must be at least ${MIN_PASSWORD_LENGTH} characters long`
);
};
const handleEmailChange = (e) => {
const value = e.target.value;
setEmail(value);
setEmailError(validateEmail(value) ? "" : "Invalid email format");
};
const handleLogin = () => {
if (!validateEmail(email) || password.length < MIN_PASSWORD_LENGTH) {
setErrorMessage(
"Please enter a valid email and ensure the password is at least 8 characters long"
);
return;
}
setIsLoading(true);
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
console.log("User logged in:", user);
alert("You have successfully loggined in as ", user);
setUser(user);
navigate("/");
})
.catch((error) => {
console.log("Login error:", error.message);
setErrorMessage("Login failed: invalid credentials ");
}).
finally(() => {
setIsLoading(false);
});
};
return (
<>
<Card className="mx-auto w-full max-w-[24rem]">
<CardHeader
variant="gradient"
color="gray"
className="mb-4 grid h-28 place-items-center"
>
<Typography variant="h3" color="white">
Sign In
</Typography>
</CardHeader>
<CardBody className="flex flex-col gap-4">
<Typography className="-mb-2" variant="h6">
Email
</Typography>
<Input
label="Email"
size="lg"
value={email}
onChange={handleEmailChange}
error={emailError}
/>
<Typography className="-mb-2" variant="h6">
Password
</Typography>
<Input
label="Password"
size="lg"
type={showPassword ? "text" : "password"}
value={password}
onChange={handlePasswordChange}
icon={
showPassword ? (
<FaEyeSlash onClick={togglePasswordVisibility} />
) : (
<FaEye onClick={togglePasswordVisibility} />
)
}
/>
{errorMessage && (
<Typography variant="small" color="red">
{errorMessage}
</Typography>
)}
<div className="-ml-2.5">
<Checkbox label="Remember Me" />
</div>
</CardBody>
<CardFooter className="pt-0">
<Button variant="gradient" onClick={handleLogin} fullWidth>
{isLoading ? (
<PuffLoader color="#EBF0EF" />
) : (
'Sign In'
)}
</Button>
<Typography
variant="small"
color="blue-gray"
className="mt-2 cursor-pointer"
onClick={handleForgotPassword}
>
Forgot password?
</Typography>
<Typography variant="small" className="mt-6 flex justify-center">
Don't have an account?
<Link
to="/signup"
variant="small"
color="blue-gray"
className="ml-1 font-bold"
>
Sign up
</Link>
</Typography>
</CardFooter>
</Card>
</>
);
}
Here are Signup.jsx
Signup component
State Management:
- Tracks name, email, password, confirm password
- Similar password visibility toggle
- Loading state for form submission
Enhanced Validation:
- Email validation
- Password matching confirmation and length check
- Real-time error messages
Authentication + Database:
- Creates user with Firebase createUserWithEmailAndPassword
- Stores additional user data in Firestore database
- Handles success/error states
import React, { useState } from "react";
import {
Card,
CardHeader,
CardBody,
CardFooter,
Typography,
Input,
Checkbox,
Button,
} from "@material-tailwind/react";
import { signInWithEmailAndPassword } from "firebase/auth";
import { auth } from "../firebase";
import { useNavigate,Link } from "react-router-dom";
import { useAuth } from "../context/authctx";
import { FaEye, FaEyeSlash } from "react-icons/fa";
import { PuffLoader } from "react-spinners";
export const SignIn = () => {
const [password, setPassword] = useState("");
const [email, setEmail] = useState('');
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [errorMessage, setErrorMessage] = useState("");
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false)
const navigate = useNavigate();
const { setUser } = useAuth();
const MIN_PASSWORD_LENGTH = 8;
const validateEmail = (email) => {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
};
const togglePasswordVisibility = () => {
setShowPassword(!showPassword);
};
const handleForgotPassword = () => {
// Add your forgot password logic here
console.log("Forgot password clicked");
navigate("/resetpassword");
};
const handlePasswordChange = (e) => {
const value = e.target.value.trim();
setPassword(value);
setPasswordError(
value.length >= MIN_PASSWORD_LENGTH
? ""
: `Password must be at least ${MIN_PASSWORD_LENGTH} characters long`
);
};
const handleEmailChange = (e) => {
const value = e.target.value;
setEmail(value);
setEmailError(validateEmail(value) ? "" : "Invalid email format");
};
const handleLogin = () => {
if (!validateEmail(email) || password.length < MIN_PASSWORD_LENGTH) {
setErrorMessage(
"Please enter a valid email and ensure the password is at least 8 characters long"
);
return;
}
setIsLoading(true);
signInWithEmailAndPassword(auth, email, password)
.then((userCredential) => {
const user = userCredential.user;
console.log("User logged in:", user);
alert("You have successfully loggined in as ", user);
setUser(user);
navigate("/");
})
.catch((error) => {
console.log("Login error:", error.message);
setErrorMessage("Login failed: invalid credentials ");
}).
finally(() => {
setIsLoading(false);
});
};
return (
<>
<Card className="mx-auto w-full max-w-[24rem]">
<CardHeader
variant="gradient"
color="gray"
className="mb-4 grid h-28 place-items-center"
>
<Typography variant="h3" color="white">
Sign In
</Typography>
</CardHeader>
<CardBody className="flex flex-col gap-4">
<Typography className="-mb-2" variant="h6">
Email
</Typography>
<Input
label="Email"
size="lg"
value={email}
onChange={handleEmailChange}
error={emailError}
/>
<Typography className="-mb-2" variant="h6">
Password
</Typography>
<Input
label="Password"
size="lg"
type={showPassword ? "text" : "password"}
value={password}
onChange={handlePasswordChange}
icon={
showPassword ? (
<FaEyeSlash onClick={togglePasswordVisibility} />
) : (
<FaEye onClick={togglePasswordVisibility} />
)
}
/>
{errorMessage && (
<Typography variant="small" color="red">
{errorMessage}
</Typography>
)}
<div className="-ml-2.5">
<Checkbox label="Remember Me" />
</div>
</CardBody>
<CardFooter className="pt-0">
<Button variant="gradient" onClick={handleLogin} fullWidth>
{isLoading ? (
<PuffLoader color="#EBF0EF" />
) : (
'Sign In'
)}
</Button>
<Typography
variant="small"
color="blue-gray"
className="mt-2 cursor-pointer"
onClick={handleForgotPassword}
>
Forgot password?
</Typography>
<Typography variant="small" className="mt-6 flex justify-center">
Don't have an account?
<Link
to="/signup"
variant="small"
color="blue-gray"
className="ml-1 font-bold"
>
Sign up
</Link>
</Typography>
</CardFooter>
</Card>
</>
);
}
Lets create th reusable button
import React from 'react'
import { useNavigate } from "react-router-dom";
import { Button } from "@material-tailwind/react";
export const SignInBtn = () => {
const navigate = useNavigate();
const handleSignIn = () => {
navigate("/signin");
};
return <Button onClick={handleSignIn}>Sign In</Button>;
}
Lets create our navbar components rom this repo
Ensure your tailwind config.js resembles this
import withMT from '@material-tailwind/react/utils/withMT'
export default withMT({
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {
fontFamily: {
play: ["Play", "sans-serif"],
},
},
},
daisyui: {
themes: ["light"],
},
plugins: [require("daisyui")],
})
Ensure that your package.json resemble this :
{
"name": "frontend",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview",
"watch:tailwind": "tailwindcss -i ./src/styles/input.css -o ./src/styles/output.css --watch"
},
"dependencies": {
"@ant-design/icons": "^5.2.6",
"@material-tailwind/react": "^2.1.9",
"ant": "^0.2.0",
"antd": "^5.9.2",
"axios": "^1.6.2",
"firebase": "^9.23.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-icons": "^4.12.0",
"react-router-dom": "^6.22.2",
"react-spinners": "^0.13.8"
},
"devDependencies": {
"@eslint/js": "^9.11.1",
"@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0",
"@vitejs/plugin-react": "^4.3.2",
"autoprefixer": "^10.4.20",
"daisyui": "^4.12.13",
"eslint": "^9.11.1",
"eslint-plugin-react": "^7.37.0",
"eslint-plugin-react-hooks": "^5.1.0-rc.0",
"eslint-plugin-react-refresh": "^0.4.12",
"globals": "^15.9.0",
"postcss": "^8.4.47",
"tailwindcss": "^3.4.14",
"vite": "^5.4.8"
}
}
Now lets coss our fingers and pray
npm run watch:tailwind
Lets start our server
npm run dev
And it works feel free to share any contributions and tweaking with the ui here. Happy coding :- )