diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..0643ee0 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,9 @@ +{ + "extends": [ + "next/core-web-vitals", + "plugin:security/recommended-legacy" + ], + "plugins": [ + "security" + ] +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..05a93e5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,18 @@ +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "weekly" + open-pull-requests-limit: 5 + groups: + minor-and-patch: + update-types: ["minor", "patch"] + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 0000000..3709458 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,62 @@ +name: CI + +on: + pull_request: + branches: [main] + push: + branches: [main] + +permissions: + contents: read + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + quality: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - name: Install Dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Type Check + run: npm run typecheck + continue-on-error: true + + - name: Write Format + run: npm run format:write + + - name: Check Format + run: npm run format:check + + - name: Build + run: npx vercel build --yes + env: + VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + MONGODB_URI: ${{ secrets.MONGODB_URI }} + NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} + NEXT_PUBLIC_SALESFAM_API_KEY: ${{ secrets.NEXT_PUBLIC_SALESFAM_API_KEY }} + RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }} + + secrets-scan: + runs-on: ubuntu-latest + container: zricethezav/gitleaks:latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Escanear secretos + run: gitleaks detect --source=. --verbose --redact \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0523389 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.env +.env*.local +node_modules/ +.next/ +out/ +coverage/ +.vercel +*.tsbuildinfo +npm-debug.log* +.DS_Store diff --git a/SECURITY_DEBT.md b/SECURITY_DEBT.md new file mode 100644 index 0000000..a23db08 --- /dev/null +++ b/SECURITY_DEBT.md @@ -0,0 +1,24 @@ +# Known Security Debt + +## 🔴 Pending — Requires Developer Fix + +### 1. Regex Injection / ReDoS in Company Search +- **File:** `app/api/company/route.js:120` +- **Risk:** `companyName` is received unsanitized from a query parameter and concatenated into a `RegExp`. This allows for ReDoS (server hang) and exact match bypass. +- **Suggested Fix:** Escape special regex characters before building the `RegExp`, or use an exact Mongo filter instead of `$regex`. +- **Detected by:** `eslint-plugin-security` (`detect-non-literal-regexp`), does not block CI (severity: warning). + +### 2. List Elements Without `key` (5 instances) +- **Files:** `app/settings/manage-contracts/AddContract.tsx:144`, `EditContract.tsx:156`, `components/ContractTable.jsx:99`, `components/datatable.tsx:363`, `components/datatableSeller1.tsx:275` +- **Risk:** Not a security issue — it is a React bug that can cause incorrect rendering when reordering/updating lists. +- **Suggested Fix:** Add `key={unique-id}` to each iterated element. +- **Note:** Temporarily downgraded to a warning via `eslint-disable-next-line` to avoid blocking the pipeline — see comments in each file. + +## 🟢 Reviewed — False Positive, No Action Required + +- `components/ResetPassword.tsx:29`, `components/SetPassword.tsx:34` — Comparison between two fields of the same form, not between a secret and a stored value. No remote attacker can measure timing here. +- `components/TabComponent.tsx:23` — The index used never comes from user input, only from the `tabs` array itself rendered by the component. + +## Recommended run 'npm run lint' to see other needs + +- `grep -rn "eslint-disable" --include="*.tsx" --include="*.jsx" --include="*.js" .` run it eventually to check not documented bypassed lines. \ No newline at end of file diff --git a/app/add-project/AddProjectClient.tsx b/app/add-project/AddProjectClient.tsx new file mode 100644 index 0000000..78dcf6b --- /dev/null +++ b/app/add-project/AddProjectClient.tsx @@ -0,0 +1,640 @@ +"use client" + +import { useEffect, useState } from "react" +import * as React from "react" +import Link from "next/link" +import { useRouter } from "next/navigation" +import { format } from "date-fns" +import { + ArrowLeftIcon, + Calendar as CalendarIcon, + Check, + ChevronsUpDown, +} from "lucide-react" +import { useSession } from "next-auth/react" + +import { fetchCompanies } from "@/lib/company/company" +import { fetchClients } from "@/lib/fetchClients" +import { getUser } from "@/lib/getUser" +import { + sendAdminNotification, + sendUserNotification, +} from "@/lib/notification/sendNotification" +import { cn } from "@/lib/utils" +import { Button } from "@/components/ui/button" +import { Calendar } from "@/components/ui/calendar" +import { Checkbox } from "@/components/ui/checkbox" +import { + Command, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, +} from "@/components/ui/command" +import { + Popover, + PopoverContent, + PopoverTrigger, +} from "@/components/ui/popover" +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" +import { Textarea } from "@/components/ui/textarea" +import { useToast } from "@/components/ui/use-toast" +import { Icons } from "@/components/icons" + +export default function AddProject() { + const AdminEmail = process.env.NEXT_PUBLIC_ADMIN_EMAIL + const [open, setOpen] = useState(false) + const [openClient, setOpenClient] = useState(false) + const [value, setValue] = useState("") + const [valueClient, setValueClient] = useState("") + + const { toast } = useToast() + const router = useRouter() + + const { data: session } = useSession() + + const [isLoading, setLoading] = useState(false) + const [projectName, setprojectName] = useState("") + const [salesPerson, setUserId] = useState("") + const [salesPersonEmail, setSalesPersonEmail] = useState("") + const [salesId, setsalesId] = useState() + const [clientId, setClientId] = useState("") + const [upSellerId, setupSellerId] = useState() + const [projectDetails, setprojectDetails] = useState("") + const [budget, setbudget] = useState("") + const [companyName, setcompanyName] = useState("image appeal") + let [dateSigned, setdateSigned] = useState("") + const [clientName, setclientName] = useState("") + const [email, setemail] = useState("") + const [phone, setphone] = useState("") + const [address, setaddress] = useState("") + const [Users, setUsers] = useState([]) + const [clients, setClients] = useState([]) + const [Allcompanies, setAllcompanies] = useState() + const [contracts, setContracts] = useState() + const [currentUser, setcurrentUser] = useState() + const [isClientCall, setIsClientCall] = useState(false) + const [isClientEmail, setIsClientEmail] = useState(false) + const [hasClient, setHasClient] = useState(false) + const role = session?.user?.role + const name = session?.user?.name + const id = session?.user?.id + const salesPersonsEmail = session?.user?.email + console.log(salesPersonsEmail) + + useEffect(() => { + fetch("/api/user") + .then((response) => response.json()) + .then((data) => { + const main = data.users + const users = main.filter((item) => item.role != "SuperAdmin") + setUsers(users) + console.log(users) + }) + .catch((error) => { + console.error("Error:", error) + }) + + if (role != "SuperAdmin") { + setUserId(name) + + + } + + fetchCompanies().then((data) => { + setAllcompanies(data) + }) + + if (session) { + const userId = session?.user?.id + const role = session?.user?.role + + fetchClients(role).then((data) => { + if (role !== "SuperAdmin" && role !== "Admin-IA") { + setClients(data) + const filterPerson = data.filter((item) => userId == item.sellerId) + setClients(filterPerson) + } else { + setClients(data) + } + }) + + getUser(userId).then((salesPerson) => { + console.log(salesPerson) + setcurrentUser(salesPerson) + setContracts(salesPerson.contracts) + + if (role != "SuperAdmin" && role == "Sales1") { + setsalesId(salesPerson._id) + setupSellerId(salesPerson.upSellerId) + } + + if (role != "SuperAdmin" && role == "Sales2") { + setsalesId(salesPerson._id) + setupSellerId(salesPerson.upSellerId) + } + if (role != "SuperAdmin" && role == "Admin-IA") { + setsalesId(salesPerson._id) + setupSellerId(salesPerson.upSellerId) + } + }) + } + }, [session, salesPerson, role]) + + //=====================handle has client + const handleClient = () => { + setHasClient(!hasClient) + if (!hasClient) { + setClientId("") + } + } + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setLoading(true) + + function formatDate(dateString) { + const options = { year: "numeric", month: "long", day: "numeric" } + return new Intl.DateTimeFormat("en-US", options).format( + new Date(dateString) + ) + } + + if (dateSigned) { + dateSigned = formatDate(dateSigned) + } + if (clientId) { + if ( + !projectName || + !projectDetails || + !budget || + !salesPerson || + !salesId || + // !upSellerId || + !companyName || + !dateSigned + ) { + toast({ + variant: "destructive", + title: "All fields required with client.", + }) + setLoading(false) + return + } + } else { + if ( + !projectName || + !projectDetails || + !budget || + !salesPerson || + !salesId || + !upSellerId || + !companyName || + !dateSigned || + !clientName || + !email || + !phone || + !address + ) { + toast({ + variant: "destructive", + title: "All fields required.", + }) + setLoading(false) + return + } + } + + try { + const res = await fetch("api/project", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + projectName, + projectDetails, + budget, + salesPerson, + salesId, + upSellerId, + companyName: companyName ? companyName : null, + dateSigned, + clientName, + email: email ? email : null, + phone: phone ? phone : null, + address: address ? address : null, + clientId: clientId ? clientId : null, + callClient: isClientCall, + emailClient: isClientEmail, + }), + }) + + if (res.ok) { + setLoading(false) + await sendUserNotification(projectName, salesPerson, salesPersonEmail?salesPersonEmail:salesPersonsEmail) + await sendAdminNotification(projectName, salesPerson, AdminEmail) + router.push("/dashboard") + + } else { + if (res.status == 409) { + toast({ + variant: "destructive", + title: "Client email already exist!", + }) + } else if (res.status == 408) { + toast({ + variant: "destructive", + title: "Client name already exist!", + }) + } else { + toast({ + variant: "destructive", + title: "project submit failed!", + }) + } + setLoading(false) + } + } catch (error) { + console.log("Error during project submit:", error) + toast({ + variant: "destructive", + title: `Error during project submit:", ${error}`, + }) + setLoading(false) + } + } + + return ( +
+
+
+

+ +
+ +
+ + Add New Project +

+
+
+
+ + setprojectName(e.target.value.trim())} + placeholder="Project Name" + /> +
+
+ + + {(Users && role == "SuperAdmin") || + (Users && role == "Admin-IA") ? ( +
+ + + + + + + + No sales person found. + + {Users?.map((user) => ( + + { + setValue( + currentValue == value ? "" : currentValue + ) + setsalesId(user?._id) + setupSellerId(user?.upSellerId) + setUserId( + currentValue == value ? "" : currentValue + ) + setSalesPersonEmail(user.email) + setOpen(false) + }} + > + + + {user?.name} + + ))} + + + + +
+ ) : ( + <> + setUserId(e.target.value.trim())} + defaultValue={name} + /> + + )} +
+
+
+
+ + +
+
+ + + + + + + + + +
+
+ + setbudget(e.target.value)} + placeholder="Project Budget" + /> +
+
+ + + + )} +
+ {!hasClient && ( +
+ setIsClientCall(!isClientCall)} + /> + + setIsClientEmail(!isClientEmail)} + /> + +
+ )} +
+ +
+
+
+
+ ) +} diff --git a/app/add-project/page.tsx b/app/add-project/page.tsx index 78dcf6b..aae52db 100644 --- a/app/add-project/page.tsx +++ b/app/add-project/page.tsx @@ -1,640 +1,8 @@ -"use client" +// app/add-project/page.tsx +import AddProjectClient from "./AddProjectClient" -import { useEffect, useState } from "react" -import * as React from "react" -import Link from "next/link" -import { useRouter } from "next/navigation" -import { format } from "date-fns" -import { - ArrowLeftIcon, - Calendar as CalendarIcon, - Check, - ChevronsUpDown, -} from "lucide-react" -import { useSession } from "next-auth/react" +export const dynamic = 'force-dynamic'; -import { fetchCompanies } from "@/lib/company/company" -import { fetchClients } from "@/lib/fetchClients" -import { getUser } from "@/lib/getUser" -import { - sendAdminNotification, - sendUserNotification, -} from "@/lib/notification/sendNotification" -import { cn } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { Calendar } from "@/components/ui/calendar" -import { Checkbox } from "@/components/ui/checkbox" -import { - Command, - CommandEmpty, - CommandGroup, - CommandInput, - CommandItem, -} from "@/components/ui/command" -import { - Popover, - PopoverContent, - PopoverTrigger, -} from "@/components/ui/popover" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" -import { Textarea } from "@/components/ui/textarea" -import { useToast } from "@/components/ui/use-toast" -import { Icons } from "@/components/icons" - -export default function AddProject() { - const AdminEmail = process.env.NEXT_PUBLIC_ADMIN_EMAIL - const [open, setOpen] = useState(false) - const [openClient, setOpenClient] = useState(false) - const [value, setValue] = useState("") - const [valueClient, setValueClient] = useState("") - - const { toast } = useToast() - const router = useRouter() - - const { data: session } = useSession() - - const [isLoading, setLoading] = useState(false) - const [projectName, setprojectName] = useState("") - const [salesPerson, setUserId] = useState("") - const [salesPersonEmail, setSalesPersonEmail] = useState("") - const [salesId, setsalesId] = useState() - const [clientId, setClientId] = useState("") - const [upSellerId, setupSellerId] = useState() - const [projectDetails, setprojectDetails] = useState("") - const [budget, setbudget] = useState("") - const [companyName, setcompanyName] = useState("image appeal") - let [dateSigned, setdateSigned] = useState("") - const [clientName, setclientName] = useState("") - const [email, setemail] = useState("") - const [phone, setphone] = useState("") - const [address, setaddress] = useState("") - const [Users, setUsers] = useState([]) - const [clients, setClients] = useState([]) - const [Allcompanies, setAllcompanies] = useState() - const [contracts, setContracts] = useState() - const [currentUser, setcurrentUser] = useState() - const [isClientCall, setIsClientCall] = useState(false) - const [isClientEmail, setIsClientEmail] = useState(false) - const [hasClient, setHasClient] = useState(false) - const role = session?.user?.role - const name = session?.user?.name - const id = session?.user?.id - const salesPersonsEmail = session?.user?.email - console.log(salesPersonsEmail) - - useEffect(() => { - fetch("/api/user") - .then((response) => response.json()) - .then((data) => { - const main = data.users - const users = main.filter((item) => item.role != "SuperAdmin") - setUsers(users) - console.log(users) - }) - .catch((error) => { - console.error("Error:", error) - }) - - if (role != "SuperAdmin") { - setUserId(name) - - - } - - fetchCompanies().then((data) => { - setAllcompanies(data) - }) - - if (session) { - const userId = session?.user?.id - const role = session?.user?.role - - fetchClients(role).then((data) => { - if (role !== "SuperAdmin" && role !== "Admin-IA") { - setClients(data) - const filterPerson = data.filter((item) => userId == item.sellerId) - setClients(filterPerson) - } else { - setClients(data) - } - }) - - getUser(userId).then((salesPerson) => { - console.log(salesPerson) - setcurrentUser(salesPerson) - setContracts(salesPerson.contracts) - - if (role != "SuperAdmin" && role == "Sales1") { - setsalesId(salesPerson._id) - setupSellerId(salesPerson.upSellerId) - } - - if (role != "SuperAdmin" && role == "Sales2") { - setsalesId(salesPerson._id) - setupSellerId(salesPerson.upSellerId) - } - if (role != "SuperAdmin" && role == "Admin-IA") { - setsalesId(salesPerson._id) - setupSellerId(salesPerson.upSellerId) - } - }) - } - }, [session, salesPerson, role]) - - //=====================handle has client - const handleClient = () => { - setHasClient(!hasClient) - if (!hasClient) { - setClientId("") - } - } - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault() - setLoading(true) - - function formatDate(dateString) { - const options = { year: "numeric", month: "long", day: "numeric" } - return new Intl.DateTimeFormat("en-US", options).format( - new Date(dateString) - ) - } - - if (dateSigned) { - dateSigned = formatDate(dateSigned) - } - if (clientId) { - if ( - !projectName || - !projectDetails || - !budget || - !salesPerson || - !salesId || - // !upSellerId || - !companyName || - !dateSigned - ) { - toast({ - variant: "destructive", - title: "All fields required with client.", - }) - setLoading(false) - return - } - } else { - if ( - !projectName || - !projectDetails || - !budget || - !salesPerson || - !salesId || - !upSellerId || - !companyName || - !dateSigned || - !clientName || - !email || - !phone || - !address - ) { - toast({ - variant: "destructive", - title: "All fields required.", - }) - setLoading(false) - return - } - } - - try { - const res = await fetch("api/project", { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - projectName, - projectDetails, - budget, - salesPerson, - salesId, - upSellerId, - companyName: companyName ? companyName : null, - dateSigned, - clientName, - email: email ? email : null, - phone: phone ? phone : null, - address: address ? address : null, - clientId: clientId ? clientId : null, - callClient: isClientCall, - emailClient: isClientEmail, - }), - }) - - if (res.ok) { - setLoading(false) - await sendUserNotification(projectName, salesPerson, salesPersonEmail?salesPersonEmail:salesPersonsEmail) - await sendAdminNotification(projectName, salesPerson, AdminEmail) - router.push("/dashboard") - - } else { - if (res.status == 409) { - toast({ - variant: "destructive", - title: "Client email already exist!", - }) - } else if (res.status == 408) { - toast({ - variant: "destructive", - title: "Client name already exist!", - }) - } else { - toast({ - variant: "destructive", - title: "project submit failed!", - }) - } - setLoading(false) - } - } catch (error) { - console.log("Error during project submit:", error) - toast({ - variant: "destructive", - title: `Error during project submit:", ${error}`, - }) - setLoading(false) - } - } - - return ( -
-
-
-

- -
- -
- - Add New Project -

-
-
-
- - setprojectName(e.target.value.trim())} - placeholder="Project Name" - /> -
-
- - - {(Users && role == "SuperAdmin") || - (Users && role == "Admin-IA") ? ( -
- - - - - - - - No sales person found. - - {Users?.map((user) => ( - - { - setValue( - currentValue == value ? "" : currentValue - ) - setsalesId(user?._id) - setupSellerId(user?.upSellerId) - setUserId( - currentValue == value ? "" : currentValue - ) - setSalesPersonEmail(user.email) - setOpen(false) - }} - > - - - {user?.name} - - ))} - - - - -
- ) : ( - <> - setUserId(e.target.value.trim())} - defaultValue={name} - /> - - )} -
-
-
-
- - -
-
- - - - - - - - - -
-
- - setbudget(e.target.value)} - placeholder="Project Budget" - /> -
-
- - - - )} -
- {!hasClient && ( -
- setIsClientCall(!isClientCall)} - /> - - setIsClientEmail(!isClientEmail)} - /> - -
- )} -
- -
-
-
-
- ) -} +export default function AddProjectPage() { + return +} \ No newline at end of file diff --git a/app/api/company/route.js b/app/api/company/route.js index 51214c4..a405ca7 100644 --- a/app/api/company/route.js +++ b/app/api/company/route.js @@ -116,6 +116,7 @@ export async function GET(req) { if (companyName) { try { await connectMongoDB() + // SECURITY DEBT: ver SECURITY_DEBT.md #1 — companyName no saneado antes de construir el RegExp (ReDoS + regex injection) const company = await Company.findOne({ companyName: { $regex: new RegExp("^" + companyName + "$", "i") }, }).select() diff --git a/app/contracts/page.tsx b/app/contracts/page.tsx index f6f3faa..7ac23e9 100644 --- a/app/contracts/page.tsx +++ b/app/contracts/page.tsx @@ -13,6 +13,8 @@ import SignContract from "@/components/company/SignContract" import ContactsSkeleton from "./ContactsSkelaton" +export const dynamic = 'force-dynamic'; + function replaceSpaceAndLowerCase(inputString) { const result = inputString.replace(" ", "-").toLowerCase() return result diff --git a/app/dashboard/DashboardClient.tsx b/app/dashboard/DashboardClient.tsx new file mode 100644 index 0000000..7b56d59 --- /dev/null +++ b/app/dashboard/DashboardClient.tsx @@ -0,0 +1,89 @@ +"use client" +import { useEffect } from "react" +import { useSession } from "next-auth/react" +import { fetchInvoices } from "@/lib/fetchInvoices" +import { fetchProjects } from "@/lib/fetchProjects" + +import AdminIA from "./AdminIA" +import SalesOne from "./SalesOne" +import SalesTwo from "./SalesTwo" +import SuperAdmin from "./SuperAdmin" + + +export default function DashboardPage() { + const { data: session } = useSession() + const role = session?.user?.role + const username = session?.user?.name + + useEffect(() => { + if (session && role == "Sales2") { + fetchProjects() + .then((apiData) => { + if (apiData) { + const pendingProjects = apiData.filter( + (item) => item.status == "Pending" && item.salesPerson == username + ) + const filteredData = apiData.filter( + (item) => + item.status !== "Pending" && item.salesPerson == username + ) + + const completedproject = apiData.filter( + (item) => + item.status == "Complete" && item.salesPerson == username + ) + + const uniqueClientNames = [] + + filteredData.forEach((item) => { + if (!uniqueClientNames.includes(item.clientName)) { + uniqueClientNames.push(item.clientName) + } + }) + + setcompletedproject(completedproject) + setclientName(uniqueClientNames) + setpendingProject(pendingProjects) + setData(filteredData) + setLoading(false) + } + }) + .catch((error) => { + console.error("Error in component:", error) + }) + + fetchInvoices() + .then((invoiceData) => { + if (invoiceData) { + const earning = invoiceData.filter( + (item) => + item.status == "Paid" && + item.commission_paid == "Yes" && + item.userId == session?.user?.id + ) + + const total = earning.reduce( + (total, item) => total + (item.amount / 100) * item.rate, + 0 + ) + + settotalEarn(total) + } + }) + .catch((error) => { + console.error("Error in component:", error) + }) + } + }, [session, role, username]) + + return ( + <> +
+ {role == "SuperAdmin" && } + {role == "Admin-IA" && } + {role == "Sales1" && } + {role == "Sales2" && } +
+ + ) +} diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 0eaa000..5c43473 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,88 +1,8 @@ -"use client" -import { useEffect } from "react" -import { useSession } from "next-auth/react" -import { fetchInvoices } from "@/lib/fetchInvoices" -import { fetchProjects } from "@/lib/fetchProjects" +// app/dashboard/page.tsx +import DashboardClient from "./DashboardClient" -import AdminIA from "./AdminIA" -import SalesOne from "./SalesOne" -import SalesTwo from "./SalesTwo" -import SuperAdmin from "./SuperAdmin" +export const dynamic = 'force-dynamic'; export default function DashboardPage() { - const { data: session } = useSession() - const role = session?.user?.role - const username = session?.user?.name - - useEffect(() => { - if (session && role == "Sales2") { - fetchProjects() - .then((apiData) => { - if (apiData) { - const pendingProjects = apiData.filter( - (item) => item.status == "Pending" && item.salesPerson == username - ) - const filteredData = apiData.filter( - (item) => - item.status !== "Pending" && item.salesPerson == username - ) - - const completedproject = apiData.filter( - (item) => - item.status == "Complete" && item.salesPerson == username - ) - - const uniqueClientNames = [] - - filteredData.forEach((item) => { - if (!uniqueClientNames.includes(item.clientName)) { - uniqueClientNames.push(item.clientName) - } - }) - - setcompletedproject(completedproject) - setclientName(uniqueClientNames) - setpendingProject(pendingProjects) - setData(filteredData) - setLoading(false) - } - }) - .catch((error) => { - console.error("Error in component:", error) - }) - - fetchInvoices() - .then((invoiceData) => { - if (invoiceData) { - const earning = invoiceData.filter( - (item) => - item.status == "Paid" && - item.commission_paid == "Yes" && - item.userId == session?.user?.id - ) - - const total = earning.reduce( - (total, item) => total + (item.amount / 100) * item.rate, - 0 - ) - - settotalEarn(total) - } - }) - .catch((error) => { - console.error("Error in component:", error) - }) - } - }, [session, role, username]) - - return ( - <> -
- {role == "SuperAdmin" && } - {role == "Admin-IA" && } - {role == "Sales1" && } - {role == "Sales2" && } -
- - ) -} + return +} \ No newline at end of file diff --git a/app/settings/manage-contracts/AddContract.tsx b/app/settings/manage-contracts/AddContract.tsx index 8f302ab..f62ea9c 100644 --- a/app/settings/manage-contracts/AddContract.tsx +++ b/app/settings/manage-contracts/AddContract.tsx @@ -141,6 +141,7 @@ export default function AddContract(props) { offerCompany.map( (singleCompany, index) => !activeCompany.includes(singleCompany.companyName) && ( + // eslint-disable-next-line react/jsx-key -- deuda conocida, ver SECURITY_DEBT.md #2, pendiente de fix !activeCompany.includes(singleCompany.companyName) && ( + // eslint-disable-next-line react/jsx-key -- deuda conocida, ver SECURITY_DEBT.md #2, pendiente de fix { {singleUser.contracts.map((contract, index) => ( + // eslint-disable-next-line react/jsx-key -- deuda conocida, ver SECURITY_DEBT.md #2, pendiente de fix
{ variant: "destructive", title: "All fields are required", }) + // eslint-disable-next-line security/detect-possible-timing-attacks -- comparación de dos campos del mismo formulario, no de un secreto contra un valor almacenado } else if (password !== confirmPassword) { toast({ variant: "destructive", diff --git a/components/SetPassword.tsx b/components/SetPassword.tsx index 18713ef..32a355b 100644 --- a/components/SetPassword.tsx +++ b/components/SetPassword.tsx @@ -31,6 +31,7 @@ export default function SetPassword(props) { return } + // eslint-disable-next-line security/detect-possible-timing-attacks -- comparación de dos campos del mismo formulario, no de un secreto contra un valor almacenado if (password != verifyPassword) { console.log("The password you entered does not match") toast({ diff --git a/components/TabComponent.tsx b/components/TabComponent.tsx index 64c45c2..abcb8ca 100644 --- a/components/TabComponent.tsx +++ b/components/TabComponent.tsx @@ -20,6 +20,7 @@ const TabComponent = ({ tabs }) => {
))} + {/* eslint-disable-next-line security/detect-object-injection -- activeTab solo puede ser un índice generado por el propio .map() sobre tabs, nunca input externo */}
{tabs[activeTab].content}