diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c6f9149 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url +NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key diff --git a/.gitignore b/.gitignore index 8f322f0..00bba9b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ /node_modules /.pnp .pnp.js +.yarn/install-state.gz # testing /coverage @@ -26,6 +27,7 @@ yarn-error.log* # local env files .env*.local +.env # vercel .vercel diff --git a/README.md b/README.md index e5f733e..e60ec27 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,46 @@ -This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). +# Invoice & Inventory Manager -## Getting Started +A modern invoice generator and inventory management system built with Next.js 15 and Supabase. -First, run the development server: +## Features -```bash -npm run dev -# or -yarn dev -# or -pnpm dev -``` +- Create and manage invoices +- Track inventory with real-time stock updates +- Generate professional PDF invoices +- View invoice history +- Multi-category product management with GST support -Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. +## Setup -You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. +1. Clone the repo +2. Install dependencies: + ```bash + yarn install + ``` -This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. +3. Add your Supabase credentials to `.env.local`: + ``` + NEXT_PUBLIC_SUPABASE_URL=your_supabase_url + NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key + ``` -## Learn More +4. Run the dev server: + ```bash + yarn dev + ``` -To learn more about Next.js, take a look at the following resources: +Open [http://localhost:3000](http://localhost:3000) -- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. -- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. +## Database Schema -You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! +You'll need these Supabase tables: +- `inventory` - Products (id, name, price, stock, type, gst) +- `types` - Product categories (name, cgst, sgst, gst) +- `history` - Invoice records -## Deploy on Vercel +## Stack -The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. - -Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. +- Next.js 15 +- Supabase +- Redux Toolkit +- Tailwind CSS (via globals.css) diff --git a/next.config.js b/next.config.js index 3b56536..8342e46 100644 --- a/next.config.js +++ b/next.config.js @@ -1,8 +1,6 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - experimental: { - serverActions: true, - }, + // Server Actions are now stable in Next.js 15 }; module.exports = nextConfig; diff --git a/package.json b/package.json index 7ef8b44..ff3c816 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "bill-gen-next", - "version": "0.1.0", + "name": "invoice-inventory-app", + "version": "1.0.0", "private": true, "scripts": { "dev": "next dev", @@ -10,14 +10,14 @@ "lint": "next lint" }, "dependencies": { - "@headlessui/react": "^1.7.14", - "@reduxjs/toolkit": "^1.9.5", - "@supabase/auth-helpers-nextjs": "^0.7.1", - "@supabase/supabase-js": "^2.24.0", - "next": "^13.4.1", + "@headlessui/react": "^2.2.0", + "@reduxjs/toolkit": "^2.3.0", + "@supabase/ssr": "^0.5.2", + "@supabase/supabase-js": "^2.46.1", + "next": "^15.0.3", "num-words": "^1.2.3", - "react": "18.2.0", - "react-dom": "18.2.0", - "react-redux": "^8.0.7" + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-redux": "^9.1.2" } } diff --git a/src/app/dashboard/add.jsx b/src/app/dashboard/add.jsx index 49dc2c3..7d29004 100644 --- a/src/app/dashboard/add.jsx +++ b/src/app/dashboard/add.jsx @@ -1,7 +1,7 @@ "use client"; import { useEffect, useState } from "react"; import styles from "@/styles/page.module.css"; -import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; +import { createClient } from "@/utils/supabase/client"; import { Dialog } from "@headlessui/react"; import { useRouter } from "next/navigation"; export const revalidate = 0; @@ -10,7 +10,7 @@ const add = () => { const [newdata, setnewData] = useState(); const [type, setType] = useState(); const [isOpen, setIsOpen] = useState(false); - const supabase = createClientComponentClient(); + const supabase = createClient(); const router = useRouter(); const handleAdd = async (e) => { diff --git a/src/app/dashboard/buttons.jsx b/src/app/dashboard/buttons.jsx index 111e045..7a442ca 100644 --- a/src/app/dashboard/buttons.jsx +++ b/src/app/dashboard/buttons.jsx @@ -1,14 +1,13 @@ "use client"; import { useEffect, useState } from "react"; import styles from "@/styles/page.module.css"; - -import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; +import { createClient } from "@/utils/supabase/client"; import { useRouter } from "next/navigation"; const buttons = ({ itemdata }) => { const router = useRouter(); const [data, setData] = useState(itemdata); - const supabase = createClientComponentClient(); + const supabase = createClient(); const update = async () => { await supabase diff --git a/src/app/dashboard/page.jsx b/src/app/dashboard/page.jsx index 25c54dd..1555525 100644 --- a/src/app/dashboard/page.jsx +++ b/src/app/dashboard/page.jsx @@ -1,19 +1,15 @@ import styles from "@/styles/page.module.css"; - -import { cookies } from "next/headers"; -import { createServerComponentClient } from "@supabase/auth-helpers-nextjs"; +import { createClient } from "@/utils/supabase/server"; import Add from "./add"; import Buttons from "./buttons"; import Price from "./price"; import Type from "./type"; -// optimize with redux -// import { store } from "@/redux/store"; export const revalidate = 0; const dashboard = async () => { - const supabase = createServerComponentClient({ cookies }); + const supabase = await createClient(); const { data, error } = await supabase .from("inventory") .select("*") diff --git a/src/app/dashboard/price.jsx b/src/app/dashboard/price.jsx index bf05a56..f421dbc 100644 --- a/src/app/dashboard/price.jsx +++ b/src/app/dashboard/price.jsx @@ -1,11 +1,10 @@ "use client"; import styles from "@/styles/page.module.css"; - -import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; +import { createClient } from "@/utils/supabase/client"; import { useEffect, useState } from "react"; const price = ({ price, id }) => { - const supabase = createClientComponentClient(); + const supabase = createClient(); const [data, setData] = useState(price); const handleInput = (e) => { diff --git a/src/app/dashboard/type.jsx b/src/app/dashboard/type.jsx index ebc1ca0..91e94e2 100644 --- a/src/app/dashboard/type.jsx +++ b/src/app/dashboard/type.jsx @@ -1,7 +1,7 @@ "use client"; import { useState } from "react"; import styles from "@/styles/page.module.css"; -import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; +import { createClient } from "@/utils/supabase/client"; import { Dialog } from "@headlessui/react"; import { useRouter } from "next/navigation"; export const revalidate = 0; @@ -9,7 +9,7 @@ export const revalidate = 0; const type = () => { const [newdata, setnewData] = useState(); const [isOpen, setIsOpen] = useState(false); - const supabase = createClientComponentClient(); + const supabase = createClient(); const router = useRouter(); const handleAdd = async (e) => { diff --git a/src/app/history/page.jsx b/src/app/history/page.jsx index 7a8ace0..e4b6635 100644 --- a/src/app/history/page.jsx +++ b/src/app/history/page.jsx @@ -1,7 +1,6 @@ "use client"; import styles from "@/styles/page.module.css"; - -import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; +import { createClient } from "@/utils/supabase/client"; import { useEffect, useState } from "react"; const history = () => { @@ -10,7 +9,7 @@ const history = () => { from: "", to: "", }); - const supabase = createClientComponentClient(); + const supabase = createClient(); const getHistory = async () => { if (date.from === "" || date.to === "") { diff --git a/src/app/layout.jsx b/src/app/layout.jsx index 1c6f250..2cd3784 100644 --- a/src/app/layout.jsx +++ b/src/app/layout.jsx @@ -10,8 +10,8 @@ const inter = Inter({ display: "swap", }); export const metadata = { - title: "Bill Generator", - description: "Generate bills for your business", + title: "Invoice & Inventory Manager", + description: "Generate invoices and manage inventory", }; export default function RootLayout({ children }) { diff --git a/src/app/login/page.jsx b/src/app/login/page.jsx index 89812ba..be0b393 100644 --- a/src/app/login/page.jsx +++ b/src/app/login/page.jsx @@ -1,22 +1,18 @@ "use client"; import { useState } from "react"; import styles from "@/styles/page.module.css"; -import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; import { useRouter } from "next/navigation"; function App() { const [user, setUser] = useState(""); const [password, setPassword] = useState(""); - const supabase = createClientComponentClient(); const router = useRouter(); + const handleSubmit = async (e) => { e.preventDefault(); - const { data, error } = await supabase.auth.signInWithPassword({ - email: user, - password: password, - }); - if (error) console.log(error); - else router.push("/"); + // Auth is disabled - login page kept for reference only + alert("Authentication is disabled. This app is now public."); + router.push("/"); }; return (
diff --git a/src/app/preview/page.jsx b/src/app/preview/page.jsx index 0058785..669589d 100644 --- a/src/app/preview/page.jsx +++ b/src/app/preview/page.jsx @@ -5,11 +5,9 @@ import { useEffect, useState } from "react"; import Total from "@/components/total"; import numWords from "num-words"; import { Dialog } from "@headlessui/react"; - -import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; +import { createClient } from "@/utils/supabase/client"; import Link from "next/link"; import { useRouter } from "next/navigation"; -import Image from "next/image"; import { useDispatch, useSelector } from "react-redux"; import { empty } from "@/redux/formSlice"; @@ -22,7 +20,7 @@ const preview = () => { const tax = useSelector((state) => state.data.tax); const router = useRouter(); - const supabase = createClientComponentClient(); + const supabase = createClient(); const [isOpen, setIsOpen] = useState(false); const [invoiceno, setInvoiceno] = useState(""); @@ -70,8 +68,7 @@ const preview = () => { if (error) console.log(error); else { dispatch(empty()); - // refetch todo - document.title = "Bill Generator"; + document.title = "Invoice Generator"; router.push("/"); } }; @@ -103,14 +100,14 @@ const preview = () => {
- SARAVANAN TRADERS + YOUR COMPANY NAME
- No.2/32, Kakkan Nagar, 2nd Cross Street,
- Adambakkam, Chennai - 600088, Tamilnadu. + Your Company Address
+ City, State - PIN Code
- GSTIN: 33BAZPS2766P1ZI
Email: saravanantraderss@gmail.com + GSTIN: Your GSTIN
Email: your@email.com
{/* */} @@ -119,7 +116,7 @@ const preview = () => { Date: {new Date().toLocaleDateString("en-IN")}
- Invoice No: ST/{invoiceno}/23-24 + Invoice No: INV/{invoiceno}
Payment Method: {formData.paymed} @@ -256,13 +253,13 @@ const preview = () => {
- Bank Name: Karur Vysya Bank + Bank Name: Your Bank Name
- Account Number: 1104135000009692 + Account Number: Your Account Number
- Branch/IFSC Code: Alandur/KVBL00001104 + Branch/IFSC Code: Your Branch/IFSC
@@ -278,14 +275,8 @@ const preview = () => {
- For SARAVANAN TRADERS + For YOUR COMPANY NAME
- Signature Authorised Signatory
diff --git a/src/app/supabase.jsx b/src/app/supabase.jsx deleted file mode 100644 index a65e958..0000000 --- a/src/app/supabase.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import { createClient } from "@supabase/supabase-js"; - -export const supabase = createClient( - process.env.NEXT_PUBLIC_SUPABASE_URL, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, - { - auth: { - persistSession: false, - }, - } -); diff --git a/src/components/nav.jsx b/src/components/nav.jsx index b5244a8..9664ce2 100644 --- a/src/components/nav.jsx +++ b/src/components/nav.jsx @@ -1,32 +1,21 @@ "use client"; import Link from "next/link"; import styles from "../styles/page.module.css"; - -import { createClientComponentClient } from "@supabase/auth-helpers-nextjs"; -import { usePathname, useRouter } from "next/navigation"; +import { usePathname } from "next/navigation"; const nav = () => { const pathname = usePathname(); - const router = useRouter(); - const supabase = createClientComponentClient(); - const handleLogout = async () => { - const { error } = await supabase.auth.signOut(); - router.push("/login"); - if (error) { - console.log(error); - } - }; + return (
- {"<"}SARAVANAN TRADERS{"/>"} + Invoice & Inventory {pathname !== "/login" && (
Dashboard History -
)}
diff --git a/src/middleware.js b/src/middleware.js index 6a218a4..019af71 100644 --- a/src/middleware.js +++ b/src/middleware.js @@ -1,24 +1,6 @@ -import { createMiddlewareClient } from "@supabase/auth-helpers-nextjs"; import { NextResponse } from "next/server"; +// Auth middleware disabled - app now works without authentication export async function middleware(req) { - const res = NextResponse.next(); - const pathname = req.nextUrl.pathname; - const supabase = createMiddlewareClient({ req, res }); - const { - data: { session }, - } = await supabase.auth.getSession(); - if ( - !session && - (pathname === "/" || - pathname === "/details" || - pathname === "/preview" || - pathname === "/history") - ) { - return NextResponse.redirect(new URL("/login", req.url)); - } - // if (!session && pathname !== "/login") { - // return NextResponse.redirect("localhost:3000/login"); - // } - return res; + return NextResponse.next(); } diff --git a/src/redux/dataSlice.js b/src/redux/dataSlice.js index cd9f7ec..bf9afde 100644 --- a/src/redux/dataSlice.js +++ b/src/redux/dataSlice.js @@ -1,7 +1,8 @@ -import { supabase } from "@/app/supabase"; +import { createClient } from "@/utils/supabase/client"; import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; export const fetchData = createAsyncThunk("fetchData", async () => { + const supabase = createClient(); let { data, error } = await supabase .from("inventory") .select("*") diff --git a/src/utils/supabase/client.js b/src/utils/supabase/client.js new file mode 100644 index 0000000..90241c8 --- /dev/null +++ b/src/utils/supabase/client.js @@ -0,0 +1,8 @@ +import { createBrowserClient } from "@supabase/ssr"; + +export function createClient() { + return createBrowserClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY + ); +} diff --git a/src/utils/supabase/server.js b/src/utils/supabase/server.js new file mode 100644 index 0000000..354fc1b --- /dev/null +++ b/src/utils/supabase/server.js @@ -0,0 +1,29 @@ +import { createServerClient } from "@supabase/ssr"; +import { cookies } from "next/headers"; + +export async function createClient() { + const cookieStore = await cookies(); + + return createServerClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, + { + cookies: { + getAll() { + return cookieStore.getAll(); + }, + setAll(cookiesToSet) { + try { + cookiesToSet.forEach(({ name, value, options }) => + cookieStore.set(name, value, options) + ); + } catch { + // The `setAll` method was called from a Server Component. + // This can be ignored if you have middleware refreshing + // user sessions. + } + }, + }, + } + ); +}