Compare commits

..

No commits in common. "b7d144bb66d86bb1cbbabe1660fbb82407a202a3" and "f047a9ec1c100b12476930ef7a16475abc03fc54" have entirely different histories.

14 changed files with 58 additions and 1049 deletions

View File

@ -1,3 +0,0 @@
NEXT_PUBLIC_CHINT_TOKEN=lIywwAMdrOdsRxuWvRoekdxrPtmIPkxA
DATABASE_URL="postgresql://postgres:root@localhost:5432/rooftop?schema=public"
JWT_SECRET="secret_key"

2
.gitignore vendored
View File

@ -34,5 +34,3 @@ yarn-error.log*
# typescript # typescript
*.tsbuildinfo *.tsbuildinfo
next-env.d.ts next-env.d.ts
.env

View File

@ -64,6 +64,34 @@ const InverterViewPage = (props: Props) => {
</button> </button>
)} )}
</Tab> </Tab>
<Tab as={Fragment}>
{({ selected }) => (
<button className={`${selected ? 'border-b !border-primary text-primary !outline-none' : ''} -mb-[1px] flex items-center border-transparent p-5 py-3 before:inline-block hover:border-b hover:!border-primary hover:text-primary`} >
Event
</button>
)}
</Tab>
<Tab as={Fragment}>
{({ selected }) => (
<button className={`${selected ? 'border-b !border-primary text-primary !outline-none' : ''} -mb-[1px] flex items-center border-transparent p-5 py-3 before:inline-block hover:border-b hover:!border-primary hover:text-primary`} >
Settings
</button>
)}
</Tab>
<Tab as={Fragment}>
{({ selected }) => (
<button className={`${selected ? 'border-b !border-primary text-primary !outline-none' : ''} -mb-[1px] flex items-center border-transparent p-5 py-3 before:inline-block hover:border-b hover:!border-primary hover:text-primary`} >
Firmware
</button>
)}
</Tab>
<Tab as={Fragment}>
{({ selected }) => (
<button className={`${selected ? 'border-b !border-primary text-primary !outline-none' : ''} -mb-[1px] flex items-center border-transparent p-5 py-3 before:inline-block hover:border-b hover:!border-primary hover:text-primary`} >
Data
</button>
)}
</Tab>
</Tab.List> </Tab.List>
<Tab.Panels> <Tab.Panels>
<Tab.Panel> <Tab.Panel>
@ -90,8 +118,9 @@ const InverterViewPage = (props: Props) => {
</div> </div>
</div> </div>
</Tab.Panel> </Tab.Panel>
<Tab.Panel>Chart</Tab.Panel>
</Tab.Panels> </Tab.Panels>
</Tab.Group> </Tab.Group>
)} )}

View File

@ -100,30 +100,12 @@ const Sidebar = () => {
</li> </li>
<li className="menu nav-item"> <li className="menu nav-item">
<button type="button" className={`${currentMenu === 'sungrow' ? 'active' : ''} nav-link group w-full`} onClick={() => toggleMenu('sungrow')}> <Link href="#" className="nav-link group">
<div className="flex items-center"> <div className="flex items-center">
<IconMenuComponents className="shrink-0 group-hover:!text-primary" /> <IconMenuComponents className="shrink-0 group-hover:!text-primary" />
<span className="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">Sungrow</span> <span className="text-black ltr:pl-3 rtl:pr-3 dark:text-[#506690] dark:group-hover:text-white-dark">Sungrow</span>
</div> </div>
</Link>
<div className={currentMenu !== 'component' ? '-rotate-90 rtl:rotate-90' : ''}>
<IconCaretDown />
</div>
</button>
<AnimateHeight duration={300} height={currentMenu === 'sungrow' ? 'auto' : 0}>
<ul className="sub-menu text-gray-500">
<li>
<Link href="/sungrow/plant">Plant</Link>
</li>
<li>
<Link href="/sungrow/device">Device</Link>
</li>
<li>
<Link href="/sungrow/maintenance">Maintenance</Link>
</li>
</ul>
</AnimateHeight>
</li> </li>
<li className="menu nav-item"> <li className="menu nav-item">

View File

@ -1,20 +0,0 @@
GET http://localhost:3005/api/auth/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI2YmM2MGIzMC1hNzcyLTRiM2MtOTYwOC1jMjgyNmNhMjA2NjEiLCJlbWFpbCI6ImFzZEBhc2QuY29tIiwiaWF0IjoxNzQwNTUyMjE4LCJleHAiOjE3NDA1NTU4MTh9.n3rFlFbqKY-kg8YF6cuRkT_Kc6hsCqQQ87TJbkS8DAg
###
POST http://localhost:3005/api/register
Content-Type: application/json
{
"email": "asd@asd.com",
"password":"123456"
}
###
POST http://localhost:3005/api/login
Content-Type: application/json
{
"email": "asd@asd.com",
"password":"123456"
}

View File

@ -1,19 +0,0 @@
import { NextRequest, NextResponse } from "next/server";
import jwt from "jsonwebtoken";
const SECRET_KEY = process.env.JWT_SECRET as string;
export function middleware(req: NextRequest) {
const token = req.cookies.get("token")?.value;
if (!token) return NextResponse.redirect(new URL("/login", req.url));
try {
jwt.verify(token, SECRET_KEY);
return NextResponse.next();
} catch (error) {
return NextResponse.redirect(new URL("/login", req.url));
}
}
export const config = { matcher: ["/dashboard", "/profile"] };

893
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -11,7 +11,6 @@
"dependencies": { "dependencies": {
"@emotion/react": "^11.10.6", "@emotion/react": "^11.10.6",
"@headlessui/react": "^1.7.8", "@headlessui/react": "^1.7.8",
"@prisma/client": "^6.4.1",
"@reduxjs/toolkit": "^1.9.1", "@reduxjs/toolkit": "^1.9.1",
"@tippyjs/react": "^4.2.6", "@tippyjs/react": "^4.2.6",
"@types/node": "18.11.18", "@types/node": "18.11.18",
@ -19,12 +18,9 @@
"@types/react-dom": "18.0.10", "@types/react-dom": "18.0.10",
"apexcharts": "^4.5.0", "apexcharts": "^4.5.0",
"axios": "^1.7.9", "axios": "^1.7.9",
"bcrypt": "^5.1.1",
"cookie": "^1.0.2",
"eslint": "8.32.0", "eslint": "8.32.0",
"eslint-config-next": "13.1.2", "eslint-config-next": "13.1.2",
"i18next": "^22.4.10", "i18next": "^22.4.10",
"jsonwebtoken": "^9.0.2",
"next": "14.0.3", "next": "14.0.3",
"ni18n": "^1.0.5", "ni18n": "^1.0.5",
"react": "18.2.0", "react": "18.2.0",
@ -35,6 +31,7 @@
"react-perfect-scrollbar": "^1.5.8", "react-perfect-scrollbar": "^1.5.8",
"react-popper": "^2.3.0", "react-popper": "^2.3.0",
"react-redux": "^8.1.3", "react-redux": "^8.1.3",
"typescript": "4.9.4",
"universal-cookie": "^6.1.1", "universal-cookie": "^6.1.1",
"yup": "^0.32.11" "yup": "^0.32.11"
}, },
@ -47,7 +44,6 @@
"postcss": "^8.4.35", "postcss": "^8.4.35",
"prettier": "^2.8.0", "prettier": "^2.8.0",
"prettier-plugin-tailwindcss": "^0.2.0", "prettier-plugin-tailwindcss": "^0.2.0",
"tailwindcss": "^3.4.1", "tailwindcss": "^3.4.1"
"typescript": "^5.7.3"
} }
} }

View File

@ -1,27 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import jwt from "jsonwebtoken";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
const SECRET_KEY = process.env.JWT_SECRET as string;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ message: "Unauthorized" });
}
const token = authHeader.split(" ")[1]; // Extract token
try {
const decoded: any = jwt.verify(token, SECRET_KEY);
const user = await prisma.user.findUnique({ where: { id: decoded.userId } });
if (!user) return res.status(401).json({ message: "User not found" });
res.json({ user });
} catch (error) {
res.status(401).json({ message: "Invalid token" });
}
}

View File

@ -1,24 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import { PrismaClient } from "@prisma/client";
import bcrypt from "bcrypt";
import jwt from "jsonwebtoken";
const prisma = new PrismaClient();
const SECRET_KEY = process.env.JWT_SECRET as string;
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") return res.status(405).json({ message: "Method not allowed" });
const { email, password } = req.body;
const user = await prisma.user.findUnique({ where: { email } });
if (!user) return res.status(401).json({ message: "Invalid credentials" });
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) return res.status(401).json({ message: "Invalid credentials" });
const token = jwt.sign({ userId: user.id, email: user.email }, SECRET_KEY, { expiresIn: "1h" });
res.setHeader("Set-Cookie", `token=${token}; HttpOnly; Path=/; Secure`);
res.json({ token });
}

View File

@ -1,21 +0,0 @@
import { NextApiRequest, NextApiResponse } from "next";
import { PrismaClient } from "@prisma/client";
import bcrypt from "bcrypt";
const prisma = new PrismaClient();
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") return res.status(405).json({ message: "Method not allowed" });
const { email, password } = req.body;
const existingUser = await prisma.user.findUnique({ where: { email } });
if (existingUser) return res.status(400).json({ message: "User already exists" });
const hashedPassword = await bcrypt.hash(password, 10);
const user = await prisma.user.create({
data: { email, password: hashedPassword },
});
res.status(201).json({ message: "User registered", user });
}

View File

@ -1,12 +0,0 @@
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"password" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

View File

@ -1,3 +0,0 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View File

@ -1,22 +0,0 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(uuid())
email String @unique
password String
createdAt DateTime @default(now())
}