feat: init project SelfChckout

This commit is contained in:
Luis Felipe Cadavid 2024-09-19 19:22:43 -05:00
parent 765cc0c7cf
commit 9d31ea245c
62 changed files with 8869 additions and 7149 deletions

View file

@ -3,9 +3,7 @@
"projectOwner": "Blazity",
"repoType": "github",
"repoHost": "https://github.com",
"files": [
"README.md"
],
"files": ["README.md"],
"imageSize": 100,
"commit": true,
"commitConvention": "angular",
@ -15,27 +13,21 @@
"name": "Bart Stefanski",
"avatar_url": "https://avatars.githubusercontent.com/u/28964599?v=4",
"profile": "https://bstefanski.com/",
"contributions": [
"code"
]
"contributions": ["code"]
},
{
"login": "jjablonski-it",
"name": "Jakub Jabłoński",
"avatar_url": "https://avatars.githubusercontent.com/u/51968772?v=4",
"profile": "https://github.com/jjablonski-it",
"contributions": [
"infra"
]
"contributions": ["infra"]
},
{
"login": "neg4n",
"name": "Igor Klepacki",
"avatar_url": "https://avatars.githubusercontent.com/u/57688858?v=4",
"profile": "https://neg4n.dev/",
"contributions": [
"doc"
]
"contributions": ["doc"]
}
],
"contributorsPerLine": 7,

View file

@ -1,2 +1,2 @@
.next
.next
node_modules

68
.gitignore vendored
View file

@ -1,35 +1,35 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
.pnp
.pnp.js
# testing
coverage
# next.js
.next/
out/
build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
/test-results/
/playwright-report/
/playwright/.cache/
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
node_modules
.pnp
.pnp.js
# testing
coverage
# next.js
.next/
out/
build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env.local
.env.development.local
.env.test.local
.env.production.local
/test-results/
/playwright-report/
/playwright/.cache/
/.npm-only-allow

View file

@ -1,2 +1,2 @@
.next
.next
node_modules

View file

@ -1,12 +1,12 @@
{
branches: ["main"],
"plugins":
[
["@semantic-release/npm", { "npmPublish": false }],
"@semantic-release/release-notes-generator",
"@semantic-release/github",
"@semantic-release/commit-analyzer",
"@semantic-release/git",
"@semantic-release/changelog",
],
}
{
branches: ["main"],
"plugins":
[
["@semantic-release/npm", { "npmPublish": false }],
"@semantic-release/release-notes-generator",
"@semantic-release/github",
"@semantic-release/commit-analyzer",
"@semantic-release/git",
"@semantic-release/changelog",
],
}

42
LICENSE
View file

@ -1,21 +1,21 @@
MIT License
Copyright (c) 2023 Blazity
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License
Copyright (c) 2023 Blazity
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,11 +1,10 @@
<img align="right" width="360px" height="410px" src="./.github/assets/project-logo-vertical.png">
# Next.js Enterprise Boilerplate </br> [![GitHub Actions Workflow Status][check-workflow-badge]][check-workflow-badge-link] [![GitHub License][github-license-badge]][github-license-badge-link] [![GitHub contributors][github-contributors-badge]][github-contributors-badge-link] [![Discord][discord-badge]][discord-badge-link] [![Blazity][made-by-blazity-badge]][made-by-blazity-badge-link]
# Next.js Enterprise Boilerplate </br> [![GitHub Actions Workflow Status][check-workflow-badge]][check-workflow-badge-link] [![GitHub License][github-license-badge]][github-license-badge-link] [![GitHub contributors][github-contributors-badge]][github-contributors-badge-link] [![Discord][discord-badge]][discord-badge-link] [![Blazity][made-by-blazity-badge]][made-by-blazity-badge-link]
Welcome to the _Next.js Enterprise Boilerplate_, an open-source template for enterprise projects! It's loaded with features that'll help you build a high-performance, maintainable, and enjoyable app. We've done all the heavy lifting for you, so sit back, relax, and get ready to conquer the world with your incredible app! 🌍
> [!NOTE]
> **Blazity** is a group of Next.js/Headless experts. Contact us at [contact@blazity.com](https://blazity.com) if you’d like to talk about your project or just to have a chat with us
> [!NOTE] > **Blazity** is a group of Next.js/Headless experts. Contact us at [contact@blazity.com](https://blazity.com) if you’d like to talk about your project or just to have a chat with us
## Features
@ -273,7 +272,6 @@ If you're looking for help or simply want to share your thoughts about the proje
</a>
<br />
## 📜 License
This project is licensed under the MIT License. For more information, see the [LICENSE](./LICENSE) file.
@ -314,7 +312,6 @@ This project is licensed under the MIT License. For more information, see the [L
[github-contributors-badge]: https://img.shields.io/github/contributors/blazity/next-enterprise?link=https%3A%2F%2Fgithub.com%2FBlazity%2Fnext-enterprise%2Fgraphs%2Fcontributors
[discord-badge]: https://img.shields.io/discord/1111676875782234175?color=7b8dcd&link=https%3A%2F%2Fblazity.com%2Fdiscord
[made-by-blazity-badge]: https://img.shields.io/badge/made_by-Blazity-blue?color=FF782B&link=https://blazity.com/
[check-workflow-badge-link]: https://github.com/Blazity/next-enterprise/actions/workflows/check.yml
[github-license-badge-link]: https://github.com/Blazity/next-enterprise/blob/main/LICENSE
[github-contributors-badge-link]: https://github.com/Blazity/next-enterprise/graphs/contributors

View file

@ -0,0 +1,28 @@
import { BASE_URL_AUTHENTICATION } from "@package/config/ApiPath"
import { FetchData } from "@package/config/FetchData"
async function auth(ae_user: string, ae_password: string): Promise<string> {
const options: RequestInit = {
method: "POST",
// mode: 'cors',
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: ae_user,
password: ae_password,
}),
}
const data = await FetchData<{ token: string }>(BASE_URL_AUTHENTICATION, "login", options)
console.log(data)
if (data.correct) {
return data.object.token
} else {
console.error("Error en la autenticación:", data.message)
return "error en la autenticación"
}
}
export { auth }

View file

@ -0,0 +1,26 @@
"use client"
import LoginForm from "@organisms/LoginForm"
import React from "react"
const LoginPage: React.FC = () => {
return (
<div className="flex min-h-screen items-center justify-center bg-gradient-to-r from-blue-900 to-blue-700">
<div className="w-full max-w-md rounded-lg bg-white p-10 shadow-lg">
<h2 className="mb-8 text-center text-4xl font-bold text-blue-900">Iniciar Sesión</h2>
<LoginForm />
<div className="mt-4 text-center">
<button
onClick={() => alert("Recuperación de contraseña")} //TODO: Implement password recovery
className="text-sm text-blue-600 hover:underline"
>
¿Olvidaste tu contraseña?
</button>
</div>
</div>
</div>
)
}
export default LoginPage

View file

@ -1,3 +1,5 @@
export async function GET() {
return Response.json({ status: "ok" })
return new Response(JSON.stringify({ status: "ok" }), {
headers: { "Content-Type": "application/json" },
})
}

14
app/home/page.tsx Normal file
View file

@ -0,0 +1,14 @@
import CheckoutScreen from "@app/selfcheckout/CheckoutScreen"
import LoyaltyProgramScreen from "@app/selfcheckout/LoyaltyProgramScreen"
import PreCheckoutScreen from "@app/selfcheckout/PreCheckoutScreen"
import React from "react"
const HomeAfterLogin: React.FC = () => {
return (
<div className="min-h-screen bg-gray-100 p-6">
<LoyaltyProgramScreen />
</div>
)
}
export default HomeAfterLogin

View file

@ -1,9 +1,15 @@
import "styles/tailwind.css"
import "@package/styles/tailwind.css";
import Navbar from "@organisms/Navbar";
import React from "react";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
<body>
<Navbar />
<main>{children}</main>
</body>
</html>
)
);
}

View file

@ -1,65 +1,68 @@
import { Metadata } from "next"
import { Button } from "components/Button/Button"
import { LP_GRID_ITEMS } from "lp-items"
"use client"
import Grid from "@mui/material/Grid2"
import Image from "next/image"
import { useRouter } from "next/navigation"
import Button from "@package/components/atoms/Button"
export const metadata: Metadata = {
title: "Next.js Enterprise Boilerplate",
twitter: {
card: "summary_large_image",
},
openGraph: {
url: "https://next-enterprise.vercel.app/",
images: [
{
width: 1200,
height: 630,
url: "https://raw.githubusercontent.com/Blazity/next-enterprise/main/.github/assets/project-logo.png",
},
],
},
}
const HomePage: React.FC = () => {
const router = useRouter()
const handleLoginClick = () => {
router.push("/account/login")
}
const handleFeaturesClick = () => {
router.push("/features")
}
export default function Web() {
return (
<>
<section className="bg-white dark:bg-gray-900">
<div className="mx-auto grid max-w-screen-xl px-4 py-8 text-center lg:py-16">
<div className="mx-auto place-self-center">
<h1 className="mb-4 max-w-2xl text-4xl font-extrabold leading-none tracking-tight dark:text-white md:text-5xl xl:text-6xl">
Next.js Enterprise Boilerplate
</h1>
<p className="mb-6 max-w-2xl font-light text-gray-500 dark:text-gray-400 md:text-lg lg:mb-8 lg:text-xl">
Jumpstart your enterprise project with our feature-packed, high-performance Next.js boilerplate!
Experience rapid UI development, AI-powered code reviews, and an extensive suite of tools for a smooth and
enjoyable development process.
</p>
<Button href="https://github.com/Blazity/next-enterprise" className="mr-3">
Get started
</Button>
<Grid
container
className="min-h-screen"
alignItems="center"
justifyContent="center"
sx={{
background: "linear-gradient(to right, black, #2c3e50, #0064dc)",
alignItems: "center",
justifyContent: "center",
}}
>
<Grid size={{ xs: 12, md: 6 }} display="flex" justifyContent={{ xs: "center", md: "flex-end" }}>
<Image
src="/img/selfcheckout.jpg"
alt="Checkout"
width={500}
height={400}
style={{ width: 'auto', height: 'auto' }}
className="rounded-lg shadow-lg"
priority
/>
</Grid>
<Grid size={{ xs: 12, md: 6 }} textAlign={{ xs: "center", md: "left" }} padding={{ md: "0 24px" }}>
<h1 className="mb-4 text-5xl font-bold text-white md:text-6xl">SELF CHECKOUT</h1>
<p className="mb-6 text-xl text-gray-200 md:text-2xl">Mejorando la experiencia en tienda</p>
<Grid container justifyContent={{ xs: "center", md: "flex-start" }} spacing={2}>
<Grid size={{ xs: 6, md: 6 }}>
<Button
href="https://vercel.com/new/git/external?repository-url=https://github.com/Blazity/next-enterprise"
intent="secondary"
>
Deploy Now
</Button>
</div>
</div>
</section>
<section className="bg-white dark:bg-gray-900">
<div className="mx-auto max-w-screen-xl px-4 py-8 sm:py-16 lg:px-6">
<div className="justify-center space-y-8 md:grid md:grid-cols-2 md:gap-12 md:space-y-0 lg:grid-cols-3">
{LP_GRID_ITEMS.map((singleItem) => (
<div key={singleItem.title} className="flex flex-col items-center justify-center text-center">
<div className="mb-4 flex h-10 w-10 items-center justify-center rounded-full bg-primary-100 p-1.5 text-blue-700 dark:bg-primary-900 lg:h-12 lg:w-12">
{singleItem.icon}
</div>
<h3 className="mb-2 text-xl font-bold dark:text-white">{singleItem.title}</h3>
<p className="text-gray-500 dark:text-gray-400">{singleItem.description}</p>
</div>
))}
</div>
</div>
</section>
</>
label="Iniciar Sesión"
onClick={handleLoginClick}
className="rounded-lg bg-blue-600 px-6 py-3 text-lg text-white transition hover:bg-blue-700"
/>
</Grid>
<Grid size={{ xs: 6, md: 6 }}>
<Button
label="Ver Funcionalidades"
onClick={handleFeaturesClick}
className="rounded-lg bg-gray-200 px-6 py-3 text-lg text-blue-600 transition hover:bg-gray-300"
/>
</Grid>
</Grid>
</Grid>
</Grid>
)
}
export default HomePage

View file

@ -0,0 +1,34 @@
"use client"
import { Box, Container } from "@mui/material"
import Grid from "@mui/material/Grid2" // Usando Grid2
import ButtonPanel from "@organisms/ButtonPanel"
import Header from "@organisms/Header"
import MainPanel from "@organisms/MainPanel"
import ProductList from "@organisms/ProductList"
import React from "react"
const CheckoutScreen: React.FC = () => {
return (
<Container maxWidth="xl" sx={{ minHeight: "100vh", bgcolor: "background.default", padding: 0 }}>
<Header sx={{ minHeight: "64px", marginBottom: 1 }} />
<Box sx={{ flexGrow: 1 }}>
<Grid container spacing={2} alignItems="stretch" sx={{ height: "calc(100vh - 64px)" }}>
<Grid size={{ xs: 12, md: 4 }} sx={{ height: "100%" }}>
<ProductList />
</Grid>
<Grid size={{ xs: 12, md: 4 }} sx={{ height: "100%" }}>
<ButtonPanel />
</Grid>
<Grid size={{ xs: 12, md: 4 }} sx={{ height: "100%" }}>
<MainPanel />
</Grid>
</Grid>
</Box>
</Container>
)
}
export default CheckoutScreen

View file

@ -0,0 +1,99 @@
"use client"
import CheckoutScreen from "@app/selfcheckout/CheckoutScreen"
import AskForAffiliation from "@layouts/AskForAffiliation"
import IdentityDocumentNumber from "@layouts/IdentityDocumentNumber"
import TypeIdentityDocument from "@layouts/TypeIdentityDocument"
import { Box, Card, CardContent, Container } from "@mui/material"
import Grid from "@mui/material/Grid2"
import Header from "@organisms/Header"
import { getDocumentsByCountry } from "@package/utils/getDocumentsByCountry"
import { useEffect, useState } from "react"
const LoyaltyProgramScreen: React.FC = () => {
const [isAffiliated, setIsAffiliated] = useState<boolean | null>(null)
const [documentType, setDocumentType] = useState<string | null>(null)
const [documentNumber, setDocumentNumber] = useState<string | null>(null)
const [goToCheckout, setGoToCheckout] = useState<boolean>(false)
const [countryDocumentTypes, setCountryDocumentTypes] = useState<string[]>([])
useEffect(() => {
const documents = getDocumentsByCountry()
setCountryDocumentTypes(documents)
}, [])
const handleAffiliationSelect = (isAffiliated: boolean) => {
setIsAffiliated(isAffiliated)
if (!isAffiliated) {
setDocumentType("13")
setDocumentNumber("222222222222")
setGoToCheckout(true)
}
}
const handleDocumentSelect = (selectedDocumentType: string) => {
setDocumentType(selectedDocumentType)
}
const handleDocumentNumberSubmit = (enteredDocumentNumber: string) => {
setDocumentNumber(enteredDocumentNumber)
setGoToCheckout(true)
}
if (goToCheckout) {
return <CheckoutScreen />
}
return (
<Container
maxWidth={false}
sx={{
height: "100hv",
display: "flex",
justifyContent: "center",
alignItems: "center",
padding: 0,
}}
>
<Card sx={{ width: "100%", p: 2 }}>
<Header sx={{ minHeight: "32px", marginBottom: 1 }} />
<CardContent sx={{ padding: "4px", height: "100%" }}>
<Grid container spacing={2} sx={{ height: "100%" }} alignItems="center">
<Grid size={{ xs: 12, md: 6 }}>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "100%",
bgcolor: "#f5f5f5",
p: 5,
borderRadius: 5,
}}
>
<Box
component="img"
src="/img/logo.png"
alt="Rhiscom Logo"
sx={{ width: "600px", height: "auto", mb: 15 }}
/>
</Box>
</Grid>
<Grid size={{ xs: 12, md: 6 }}>
{isAffiliated === null && <AskForAffiliation onAffiliationSelect={handleAffiliationSelect} />}
{isAffiliated && !documentType && (
<TypeIdentityDocument typesDocument={countryDocumentTypes} onDocumentSelect={handleDocumentSelect} />
)}
{isAffiliated && documentType && (
<IdentityDocumentNumber documentType={documentType} onSubmit={handleDocumentNumberSubmit} />
)}
</Grid>
</Grid>
</CardContent>
</Card>
</Container>
)
}
export default LoyaltyProgramScreen

View file

@ -0,0 +1,36 @@
"use client"
import Title from "@atoms/Title"
import { Tooltip } from "@atoms/Tooltip"
import { Box, Button, Container } from "@mui/material"
import React from "react"
const PreCheckoutScreen: React.FC = () => {
const handleStartCheckout = () => {
console.log("Comenzar checkout")
}
return (
<Container maxWidth="md" sx={{ textAlign: "center", marginTop: "20vh" }}>
<Box sx={{ backgroundColor: "#f1f1f1", padding: "40px", borderRadius: "8px", boxShadow: 3 }}>
<Title text="Esta caja está" size="h4" />
<Title text="Abierta" size="h2" />
<Tooltip explainer="Escanee un producto o pulse el botón para comenzar" withArrow side="top">
<Title text="Escanee un producto o pulse aquí para comenzar" size="h6" />
</Tooltip>
<Button
variant="contained"
color="success"
size="large"
sx={{ marginTop: "40px" }}
onClick={handleStartCheckout}
>
Comenzar
</Button>
</Box>
</Container>
)
}
export default PreCheckoutScreen

View file

@ -1,31 +0,0 @@
import type { Meta, StoryObj } from "@storybook/react"
import { Button } from "./Button"
const meta: Meta<typeof Button> = {
title: "Button",
component: Button,
args: {
intent: "primary",
underline: false,
children: "Button",
size: "lg",
},
argTypes: {
intent: {
options: ["primary", "secondary"],
control: { type: "select" },
},
size: {
options: ["sm", "lg"],
control: { type: "select" },
},
},
}
type Story = StoryObj<typeof Button>
export const Default: Story = {
render: (args) => <Button {...args} />,
}
export default meta

View file

@ -1,47 +0,0 @@
import { cva, type VariantProps } from "class-variance-authority"
import { twMerge } from "tailwind-merge"
const button = cva(
[
"justify-center",
"inline-flex",
"items-center",
"rounded-xl",
"text-center",
"border",
"border-blue-400",
"transition-colors",
"delay-50",
],
{
variants: {
intent: {
primary: ["bg-blue-400", "text-white", "hover:enabled:bg-blue-700"],
secondary: ["bg-transparent", "text-blue-400", "hover:enabled:bg-blue-400", "hover:enabled:text-white"],
},
size: {
sm: ["min-w-20", "h-full", "min-h-10", "text-sm", "py-1.5", "px-4"],
lg: ["min-w-32", "h-full", "min-h-12", "text-lg", "py-2.5", "px-6"],
},
underline: { true: ["underline"], false: [] },
},
defaultVariants: {
intent: "primary",
size: "lg",
},
}
)
export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLAnchorElement>, VariantProps<typeof button> {
underline?: boolean
href: string
}
export function Button({ className, intent, size, underline, ...props }: ButtonProps) {
return (
<a className={twMerge(button({ intent, size, className, underline }))} {...props}>
{props.children}
</a>
)
}

View file

@ -8,8 +8,11 @@ export const env = createEnv({
.optional()
.transform((value) => value === "true"),
},
client: {},
client: {
NEXT_PUBLIC_API_URL: z.string().url(),
},
runtimeEnv: {
ANALYZE: process.env.ANALYZE,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
},
})

View file

@ -1,26 +1,26 @@
---
convention:
commitTypes:
- feat
- fix
- perf
- refactor
- style
- test
- build
- ops
- docs
- chore
- merge
- revert
- feat
- fix
- perf
- refactor
- style
- test
- build
- ops
- docs
- chore
- merge
- revert
commitScopes: []
releaseTagGlobPattern: v[0-9]*.[0-9]*.[0-9]*
changelog:
commitTypes:
- feat
- fix
- perf
- merge
- feat
- fix
- perf
- merge
includeInvalidCommits: true
commitIgnoreRegexPattern: "^WIP "
headlines:

170
graph.svg
View file

@ -1,85 +1,85 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 8.0.2 (20230410.1723)
-->
<!-- Title: G Pages: 1 -->
<svg width="677pt" height="230pt"
viewBox="0.00 0.00 677.20 230.20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(21.6 208.6)">
<title>G</title>
<polygon fill="#111111" stroke="none" points="-21.6,21.6 -21.6,-208.6 655.6,-208.6 655.6,21.6 -21.6,21.6"/>
<!-- components/Button/Button.stories.tsx -->
<g id="node1" class="node">
<title>components/Button/Button.stories.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M233.33,-64C233.33,-64 7.67,-64 7.67,-64 3.83,-64 0,-60.17 0,-56.33 0,-56.33 0,-48.67 0,-48.67 0,-44.83 3.83,-41 7.67,-41 7.67,-41 233.33,-41 233.33,-41 237.17,-41 241,-44.83 241,-48.67 241,-48.67 241,-56.33 241,-56.33 241,-60.17 237.17,-64 233.33,-64"/>
<text text-anchor="middle" x="120.5" y="-48.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">components/Button/Button.stories.tsx</text>
</g>
<!-- components/Button/Button.tsx -->
<g id="node2" class="node">
<title>components/Button/Button.tsx</title>
<path fill="none" stroke="#cfffac" d="M466.33,-43C466.33,-43 284.67,-43 284.67,-43 280.83,-43 277,-39.17 277,-35.33 277,-35.33 277,-27.67 277,-27.67 277,-23.83 280.83,-20 284.67,-20 284.67,-20 466.33,-20 466.33,-20 470.17,-20 474,-23.83 474,-27.67 474,-27.67 474,-35.33 474,-35.33 474,-39.17 470.17,-43 466.33,-43"/>
<text text-anchor="middle" x="375.5" y="-27.8" font-family="Arial" font-size="14.00" fill="#cfffac">components/Button/Button.tsx</text>
</g>
<!-- components/Button/Button.stories.tsx&#45;&gt;components/Button/Button.tsx -->
<g id="edge1" class="edge">
<title>components/Button/Button.stories.tsx&#45;&gt;components/Button/Button.tsx</title>
<path fill="none" stroke="#757575" d="M241.33,-42.55C249.59,-41.87 257.89,-41.18 266.08,-40.5"/>
<polygon fill="#757575" stroke="#757575" points="266.1,-43.93 275.78,-39.61 265.53,-36.95 266.1,-43.93"/>
</g>
<!-- components/Tooltip/Tooltip.tsx -->
<g id="node3" class="node">
<title>components/Tooltip/Tooltip.tsx</title>
<path fill="none" stroke="#cfffac" d="M211.33,-105C211.33,-105 29.67,-105 29.67,-105 25.83,-105 22,-101.17 22,-97.33 22,-97.33 22,-89.67 22,-89.67 22,-85.83 25.83,-82 29.67,-82 29.67,-82 211.33,-82 211.33,-82 215.17,-82 219,-85.83 219,-89.67 219,-89.67 219,-97.33 219,-97.33 219,-101.17 215.17,-105 211.33,-105"/>
<text text-anchor="middle" x="120.5" y="-89.8" font-family="Arial" font-size="14.00" fill="#cfffac">components/Tooltip/Tooltip.tsx</text>
</g>
<!-- pages/_app.test.tsx -->
<g id="node4" class="node">
<title>pages/_app.test.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M179.83,-146C179.83,-146 61.17,-146 61.17,-146 57.33,-146 53.5,-142.17 53.5,-138.33 53.5,-138.33 53.5,-130.67 53.5,-130.67 53.5,-126.83 57.33,-123 61.17,-123 61.17,-123 179.83,-123 179.83,-123 183.67,-123 187.5,-126.83 187.5,-130.67 187.5,-130.67 187.5,-138.33 187.5,-138.33 187.5,-142.17 183.67,-146 179.83,-146"/>
<text text-anchor="middle" x="120.5" y="-130.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">pages/_app.test.tsx</text>
</g>
<!-- pages/_app.tsx -->
<g id="node5" class="node">
<title>pages/_app.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M421.83,-146C421.83,-146 329.17,-146 329.17,-146 325.33,-146 321.5,-142.17 321.5,-138.33 321.5,-138.33 321.5,-130.67 321.5,-130.67 321.5,-126.83 325.33,-123 329.17,-123 329.17,-123 421.83,-123 421.83,-123 425.67,-123 429.5,-126.83 429.5,-130.67 429.5,-130.67 429.5,-138.33 429.5,-138.33 429.5,-142.17 425.67,-146 421.83,-146"/>
<text text-anchor="middle" x="375.5" y="-130.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">pages/_app.tsx</text>
</g>
<!-- pages/_app.test.tsx&#45;&gt;pages/_app.tsx -->
<g id="edge2" class="edge">
<title>pages/_app.test.tsx&#45;&gt;pages/_app.tsx</title>
<path fill="none" stroke="#757575" d="M187.97,-134.5C225.82,-134.5 272.99,-134.5 310.43,-134.5"/>
<polygon fill="#757575" stroke="#757575" points="310.21,-138 320.21,-134.5 310.21,-131 310.21,-138"/>
</g>
<!-- styles/tailwind.css -->
<g id="node6" class="node">
<title>styles/tailwind.css</title>
<path fill="none" stroke="#cfffac" d="M626.33,-146C626.33,-146 517.67,-146 517.67,-146 513.83,-146 510,-142.17 510,-138.33 510,-138.33 510,-130.67 510,-130.67 510,-126.83 513.83,-123 517.67,-123 517.67,-123 626.33,-123 626.33,-123 630.17,-123 634,-126.83 634,-130.67 634,-130.67 634,-138.33 634,-138.33 634,-142.17 630.17,-146 626.33,-146"/>
<text text-anchor="middle" x="572" y="-130.8" font-family="Arial" font-size="14.00" fill="#cfffac">styles/tailwind.css</text>
</g>
<!-- pages/_app.tsx&#45;&gt;styles/tailwind.css -->
<g id="edge3" class="edge">
<title>pages/_app.tsx&#45;&gt;styles/tailwind.css</title>
<path fill="none" stroke="#757575" d="M429.91,-134.5C451.26,-134.5 476.19,-134.5 499.1,-134.5"/>
<polygon fill="#757575" stroke="#757575" points="498.81,-138 508.81,-134.5 498.81,-131 498.81,-138"/>
</g>
<!-- pages/api/health.ts -->
<g id="node7" class="node">
<title>pages/api/health.ts</title>
<path fill="none" stroke="#cfffac" d="M177.83,-187C177.83,-187 63.17,-187 63.17,-187 59.33,-187 55.5,-183.17 55.5,-179.33 55.5,-179.33 55.5,-171.67 55.5,-171.67 55.5,-167.83 59.33,-164 63.17,-164 63.17,-164 177.83,-164 177.83,-164 181.67,-164 185.5,-167.83 185.5,-171.67 185.5,-171.67 185.5,-179.33 185.5,-179.33 185.5,-183.17 181.67,-187 177.83,-187"/>
<text text-anchor="middle" x="120.5" y="-171.8" font-family="Arial" font-size="14.00" fill="#cfffac">pages/api/health.ts</text>
</g>
<!-- pages/index.tsx -->
<g id="node8" class="node">
<title>pages/index.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M167.83,-23C167.83,-23 73.17,-23 73.17,-23 69.33,-23 65.5,-19.17 65.5,-15.33 65.5,-15.33 65.5,-7.67 65.5,-7.67 65.5,-3.83 69.33,0 73.17,0 73.17,0 167.83,0 167.83,0 171.67,0 175.5,-3.83 175.5,-7.67 175.5,-7.67 175.5,-15.33 175.5,-15.33 175.5,-19.17 171.67,-23 167.83,-23"/>
<text text-anchor="middle" x="120.5" y="-7.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">pages/index.tsx</text>
</g>
<!-- pages/index.tsx&#45;&gt;components/Button/Button.tsx -->
<g id="edge4" class="edge">
<title>pages/index.tsx&#45;&gt;components/Button/Button.tsx</title>
<path fill="none" stroke="#757575" d="M175.77,-15.79C202.08,-17.87 234.64,-20.44 265.75,-22.9"/>
<polygon fill="#757575" stroke="#757575" points="265.43,-26.47 275.67,-23.77 265.98,-19.49 265.43,-26.47"/>
</g>
</g>
</svg>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 8.0.2 (20230410.1723)
-->
<!-- Title: G Pages: 1 -->
<svg width="677pt" height="230pt"
viewBox="0.00 0.00 677.20 230.20" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(21.6 208.6)">
<title>G</title>
<polygon fill="#111111" stroke="none" points="-21.6,21.6 -21.6,-208.6 655.6,-208.6 655.6,21.6 -21.6,21.6"/>
<!-- components/Button/Button.stories.tsx -->
<g id="node1" class="node">
<title>components/Button/Button.stories.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M233.33,-64C233.33,-64 7.67,-64 7.67,-64 3.83,-64 0,-60.17 0,-56.33 0,-56.33 0,-48.67 0,-48.67 0,-44.83 3.83,-41 7.67,-41 7.67,-41 233.33,-41 233.33,-41 237.17,-41 241,-44.83 241,-48.67 241,-48.67 241,-56.33 241,-56.33 241,-60.17 237.17,-64 233.33,-64"/>
<text text-anchor="middle" x="120.5" y="-48.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">components/Button/Button.stories.tsx</text>
</g>
<!-- components/Button/Button.tsx -->
<g id="node2" class="node">
<title>components/Button/Button.tsx</title>
<path fill="none" stroke="#cfffac" d="M466.33,-43C466.33,-43 284.67,-43 284.67,-43 280.83,-43 277,-39.17 277,-35.33 277,-35.33 277,-27.67 277,-27.67 277,-23.83 280.83,-20 284.67,-20 284.67,-20 466.33,-20 466.33,-20 470.17,-20 474,-23.83 474,-27.67 474,-27.67 474,-35.33 474,-35.33 474,-39.17 470.17,-43 466.33,-43"/>
<text text-anchor="middle" x="375.5" y="-27.8" font-family="Arial" font-size="14.00" fill="#cfffac">components/Button/Button.tsx</text>
</g>
<!-- components/Button/Button.stories.tsx&#45;&gt;components/Button/Button.tsx -->
<g id="edge1" class="edge">
<title>components/Button/Button.stories.tsx&#45;&gt;components/Button/Button.tsx</title>
<path fill="none" stroke="#757575" d="M241.33,-42.55C249.59,-41.87 257.89,-41.18 266.08,-40.5"/>
<polygon fill="#757575" stroke="#757575" points="266.1,-43.93 275.78,-39.61 265.53,-36.95 266.1,-43.93"/>
</g>
<!-- components/Tooltip/Tooltip.tsx -->
<g id="node3" class="node">
<title>components/Tooltip/Tooltip.tsx</title>
<path fill="none" stroke="#cfffac" d="M211.33,-105C211.33,-105 29.67,-105 29.67,-105 25.83,-105 22,-101.17 22,-97.33 22,-97.33 22,-89.67 22,-89.67 22,-85.83 25.83,-82 29.67,-82 29.67,-82 211.33,-82 211.33,-82 215.17,-82 219,-85.83 219,-89.67 219,-89.67 219,-97.33 219,-97.33 219,-101.17 215.17,-105 211.33,-105"/>
<text text-anchor="middle" x="120.5" y="-89.8" font-family="Arial" font-size="14.00" fill="#cfffac">components/Tooltip/Tooltip.tsx</text>
</g>
<!-- pages/_app.test.tsx -->
<g id="node4" class="node">
<title>pages/_app.test.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M179.83,-146C179.83,-146 61.17,-146 61.17,-146 57.33,-146 53.5,-142.17 53.5,-138.33 53.5,-138.33 53.5,-130.67 53.5,-130.67 53.5,-126.83 57.33,-123 61.17,-123 61.17,-123 179.83,-123 179.83,-123 183.67,-123 187.5,-126.83 187.5,-130.67 187.5,-130.67 187.5,-138.33 187.5,-138.33 187.5,-142.17 183.67,-146 179.83,-146"/>
<text text-anchor="middle" x="120.5" y="-130.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">pages/_app.test.tsx</text>
</g>
<!-- pages/_app.tsx -->
<g id="node5" class="node">
<title>pages/_app.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M421.83,-146C421.83,-146 329.17,-146 329.17,-146 325.33,-146 321.5,-142.17 321.5,-138.33 321.5,-138.33 321.5,-130.67 321.5,-130.67 321.5,-126.83 325.33,-123 329.17,-123 329.17,-123 421.83,-123 421.83,-123 425.67,-123 429.5,-126.83 429.5,-130.67 429.5,-130.67 429.5,-138.33 429.5,-138.33 429.5,-142.17 425.67,-146 421.83,-146"/>
<text text-anchor="middle" x="375.5" y="-130.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">pages/_app.tsx</text>
</g>
<!-- pages/_app.test.tsx&#45;&gt;pages/_app.tsx -->
<g id="edge2" class="edge">
<title>pages/_app.test.tsx&#45;&gt;pages/_app.tsx</title>
<path fill="none" stroke="#757575" d="M187.97,-134.5C225.82,-134.5 272.99,-134.5 310.43,-134.5"/>
<polygon fill="#757575" stroke="#757575" points="310.21,-138 320.21,-134.5 310.21,-131 310.21,-138"/>
</g>
<!-- styles/tailwind.css -->
<g id="node6" class="node">
<title>styles/tailwind.css</title>
<path fill="none" stroke="#cfffac" d="M626.33,-146C626.33,-146 517.67,-146 517.67,-146 513.83,-146 510,-142.17 510,-138.33 510,-138.33 510,-130.67 510,-130.67 510,-126.83 513.83,-123 517.67,-123 517.67,-123 626.33,-123 626.33,-123 630.17,-123 634,-126.83 634,-130.67 634,-130.67 634,-138.33 634,-138.33 634,-142.17 630.17,-146 626.33,-146"/>
<text text-anchor="middle" x="572" y="-130.8" font-family="Arial" font-size="14.00" fill="#cfffac">styles/tailwind.css</text>
</g>
<!-- pages/_app.tsx&#45;&gt;styles/tailwind.css -->
<g id="edge3" class="edge">
<title>pages/_app.tsx&#45;&gt;styles/tailwind.css</title>
<path fill="none" stroke="#757575" d="M429.91,-134.5C451.26,-134.5 476.19,-134.5 499.1,-134.5"/>
<polygon fill="#757575" stroke="#757575" points="498.81,-138 508.81,-134.5 498.81,-131 498.81,-138"/>
</g>
<!-- pages/api/health.ts -->
<g id="node7" class="node">
<title>pages/api/health.ts</title>
<path fill="none" stroke="#cfffac" d="M177.83,-187C177.83,-187 63.17,-187 63.17,-187 59.33,-187 55.5,-183.17 55.5,-179.33 55.5,-179.33 55.5,-171.67 55.5,-171.67 55.5,-167.83 59.33,-164 63.17,-164 63.17,-164 177.83,-164 177.83,-164 181.67,-164 185.5,-167.83 185.5,-171.67 185.5,-171.67 185.5,-179.33 185.5,-179.33 185.5,-183.17 181.67,-187 177.83,-187"/>
<text text-anchor="middle" x="120.5" y="-171.8" font-family="Arial" font-size="14.00" fill="#cfffac">pages/api/health.ts</text>
</g>
<!-- pages/index.tsx -->
<g id="node8" class="node">
<title>pages/index.tsx</title>
<path fill="none" stroke="#c6c5fe" d="M167.83,-23C167.83,-23 73.17,-23 73.17,-23 69.33,-23 65.5,-19.17 65.5,-15.33 65.5,-15.33 65.5,-7.67 65.5,-7.67 65.5,-3.83 69.33,0 73.17,0 73.17,0 167.83,0 167.83,0 171.67,0 175.5,-3.83 175.5,-7.67 175.5,-7.67 175.5,-15.33 175.5,-15.33 175.5,-19.17 171.67,-23 167.83,-23"/>
<text text-anchor="middle" x="120.5" y="-7.8" font-family="Arial" font-size="14.00" fill="#c6c5fe">pages/index.tsx</text>
</g>
<!-- pages/index.tsx&#45;&gt;components/Button/Button.tsx -->
<g id="edge4" class="edge">
<title>pages/index.tsx&#45;&gt;components/Button/Button.tsx</title>
<path fill="none" stroke="#757575" d="M175.77,-15.79C202.08,-17.87 234.64,-20.44 265.75,-22.9"/>
<polygon fill="#757575" stroke="#757575" points="265.43,-26.47 275.67,-23.77 265.98,-19.49 265.43,-26.47"/>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.4 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Before After
Before After

View file

@ -1,5 +1,42 @@
import { context, diag, DiagConsoleLogger, DiagLogLevel, propagation, trace } from "@opentelemetry/api"
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto"
import { Resource } from "@opentelemetry/resources"
import { BatchSpanProcessor } from "@opentelemetry/sdk-trace-base"
import { NodeTracerProvider } from "@opentelemetry/sdk-trace-node"
import { registerOTel } from "@vercel/otel"
export function register() {
registerOTel("next-app")
// Configuración del logger de diagnóstico
diag.setLogger(new DiagConsoleLogger(), DiagLogLevel.ERROR)
// Verificación de registro previo de OpenTelemetry
function isAlreadyRegistered() {
return trace.getTracerProvider() || context.active() || propagation.setGlobalPropagator
}
// Inicialización de OpenTelemetry
function initializeOpenTelemetry() {
const resource = new Resource({
"service.name": "next-app", // Cambia el nombre del servicio según tu proyecto
})
const provider = new NodeTracerProvider({
resource,
})
const traceExporter = new OTLPTraceExporter({
url: "http://localhost:4318/v1/traces", // Asegúrate de que el servicio OTLP esté corriendo
})
provider.addSpanProcessor(new BatchSpanProcessor(traceExporter))
provider.register()
}
// Registrar OpenTelemetry si no está registrado
if (!isAlreadyRegistered()) {
initializeOpenTelemetry()
}
// Registrar la configuración de OpenTelemetry
export function register() {
registerOTel("next-app") // Mismo nombre de servicio
}

View file

@ -9,7 +9,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -29,7 +29,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -49,7 +49,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg>
@ -65,7 +65,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -85,7 +85,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -105,7 +105,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -125,7 +125,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -145,7 +145,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -165,7 +165,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -185,7 +185,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -205,7 +205,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -226,7 +226,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -246,7 +246,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -266,7 +266,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -286,7 +286,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -306,7 +306,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -326,7 +326,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -346,7 +346,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path strokeLinecap="round" strokeLinejoin="round" d="M10.5 6a7.5 7.5 0 107.5 7.5h-7.5V6z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M13.5 10.5H21A7.5 7.5 0 0013.5 3v7.5z" />
@ -363,7 +363,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -383,7 +383,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"
@ -403,7 +403,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24"
strokeWidth={1.5}
stroke="currentColor"
className="h-6 w-6"
className="size-6"
>
<path
strokeLinecap="round"

View file

@ -1,22 +1,24 @@
import { trace } from "@opentelemetry/api"
import { NextResponse } from "next/server"
import type { NextRequest } from "next/server"
export function middleware(request: NextRequest) {
// Iniciar una nueva traza para el middleware
const tracer = trace.getTracer("nextjs-middleware")
const span = tracer.startSpan("middleware-span")
// TODO: Feel free to remove this block
if (request.headers?.get("host")?.includes("next-enterprise.vercel.app")) {
span.setAttribute("redirect", true)
span.end()
return NextResponse.redirect("https://blazity.com/open-source/nextjs-enterprise-boilerplate", { status: 301 })
}
// Finalizar la traza
span.end()
return NextResponse.next()
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - api (API routes)
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
"/((?!api|_next/static|_next/image|favicon.ico).*)",
],
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
}

2
next-env.d.ts vendored
View file

@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.

View file

@ -7,7 +7,11 @@ import { env } from "./env.mjs"
*/
const config = withPlugins([[withBundleAnalyzer({ enabled: env.ANALYZE })]], {
reactStrictMode: true,
experimental: { instrumentationHook: true },
experimental: { instrumentationHook: false },
env: {
REACT_APP_API_URL: process.env.REACT_APP_API_URL, // Para que esté disponible en el cliente
},
rewrites() {
return [
{ source: "/healthz", destination: "/api/health" },

View file

@ -23,7 +23,14 @@
"coupling-graph": "npx madge --extensions js,jsx,ts,tsx,css,md,mdx ./ --exclude '.next|tailwind.config.js|reset.d.ts|prettier.config.js|postcss.config.js|playwright.config.ts|next.config.js|next-env.d.ts|instrumentation.ts|e2e/|README.md|.storybook/|.eslintrc.js' --image graph.svg"
},
"dependencies": {
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/icons-material": "^6.1.0",
"@mui/material": "^6.1.0",
"@mui/system": "^6.1.0",
"@next/bundle-analyzer": "^14.0.3",
"@opentelemetry/exporter-trace-otlp-http": "^0.53.0",
"@opentelemetry/exporter-trace-otlp-proto": "^0.53.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
@ -39,6 +46,7 @@
"@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/themes": "^3.1.3",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^11.1.0",
"@semantic-release/git": "^10.0.1",
@ -46,13 +54,16 @@
"@semantic-release/npm": "^11.0.1",
"@semantic-release/release-notes-generator": "^12.1.0",
"@t3-oss/env-nextjs": "^0.7.1",
"@vercel/otel": "^0.3.0",
"@vercel/otel": "^1.10.0",
"class-variance-authority": "^0.7.0",
"i18next": "^23.15.1",
"jaeger-client": "^3.19.0",
"lodash": "^4.17.21",
"next": "^14.0.3",
"next-compose-plugins": "^2.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-i18next": "^15.0.1",
"tailwind-merge": "^2.0.0",
"zod": "^3.22.4"
},
@ -62,11 +73,11 @@
"@babel/plugin-transform-optional-chaining": "^7.23.4",
"@babel/plugin-transform-react-jsx": "^7.23.4",
"@jest/globals": "^29.7.0",
"@opentelemetry/api": "1.7.0",
"@opentelemetry/resources": "1.18.1",
"@opentelemetry/sdk-node": "0.45.1",
"@opentelemetry/sdk-trace-node": "1.18.1",
"@opentelemetry/semantic-conventions": "1.18.1",
"@opentelemetry/api": "^1.9.0",
"@opentelemetry/resources": "^1.26.0",
"@opentelemetry/sdk-node": "^0.53.0",
"@opentelemetry/sdk-trace-node": "^1.26.0",
"@opentelemetry/semantic-conventions": "^1.27.0",
"@playwright/test": "^1.40.0",
"@storybook/addon-essentials": "^7.5.3",
"@storybook/addon-interactions": "^7.5.3",
@ -74,7 +85,7 @@
"@storybook/blocks": "^7.5.3",
"@storybook/nextjs": "^7.5.3",
"@storybook/react": "^7.5.3",
"@storybook/test-runner": "^0.15.2",
"@storybook/test-runner": "^0.19.1",
"@storybook/testing-library": "^0.2.2",
"@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.1.2",
@ -96,7 +107,7 @@
"eslint-plugin-react": "7.33.2",
"eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-tailwindcss": "^3.13.0",
"fetch-mock": "^9.11.0",
"fetch-mock": "^11.1.3",
"gzip-size": "6",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
@ -109,11 +120,11 @@
"prettier-plugin-tailwindcss": "^0.5.7",
"semantic-release": "^22.0.8",
"storybook": "^7.5.3",
"tailwindcss": "^3.3.5",
"tailwindcss": "^3.4.12",
"ts-jest": "^29.1.1",
"tsc": "^2.0.4",
"typescript": "5.3.2",
"webpack": "5.89.0"
"webpack": "^5.94.0"
},
"engines": {
"node": ">=18.17.0"

View file

@ -0,0 +1,45 @@
"use client"
import { Button as MUIButton } from "@mui/material"
import React from "react"
interface ButtonProps {
label: string
variant?: "primary" | "secondary" | "success" | "danger" | "warning" | "info"
onClick?: () => void
disabled?: boolean
className?: string
startIcon?: React.ReactNode
}
const Button: React.FC<ButtonProps> = ({
label,
variant = "primary",
onClick,
disabled,
className = "",
startIcon,
}) => {
const colorMap: Record<string, "primary" | "secondary" | "error" | "warning" | "info" | "success"> = {
primary: "primary",
secondary: "secondary",
success: "success",
danger: "error",
warning: "warning",
info: "info",
}
return (
<MUIButton
onClick={onClick}
disabled={disabled}
color={colorMap[variant ?? "primary"]}
className={className}
variant="contained"
startIcon={startIcon}
>
{label}
</MUIButton>
)
}
export default Button

View file

@ -0,0 +1,27 @@
"use client"
import { TextField } from "@mui/material"
import React from "react"
interface InputProps {
type: string
placeholder?: string
value: string
onChange: (e: React.ChangeEvent<HTMLInputElement>) => void
autocomplete?: string
}
const Input: React.FC<InputProps> = ({ type, placeholder, value, onChange, autocomplete }) => {
return (
<TextField
type={type}
placeholder={placeholder}
value={value}
onChange={onChange}
autoComplete={autocomplete}
fullWidth
variant="outlined"
/>
)
}
export default Input

View file

@ -0,0 +1,18 @@
"use client"
import { Typography } from "@mui/material"
import React from "react"
interface LabelProps {
htmlFor: string
text: string
}
const Label: React.FC<LabelProps> = ({ htmlFor, text }) => {
return (
<Typography variant="subtitle1" component="label" htmlFor={htmlFor} className="mb-2 block">
{text}
</Typography>
)
}
export default Label

View file

@ -0,0 +1,18 @@
"use client"
import { Typography } from "@mui/material"
import React from "react"
interface TitleProps {
text: string
size?: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
}
const Title: React.FC<TitleProps> = ({ text, size = "h1" }) => {
return (
<Typography variant={size} className="font-bold">
{text}
</Typography>
)
}
export default Title

View file

@ -0,0 +1,50 @@
"use client"
import { Box, Card, CardContent } from "@mui/material"
import Grid from "@mui/material/Grid2"
import Button from "@atoms/Button"
import Label from "@atoms/Label"
import Title from "@atoms/Title"
interface AskForAffiliationProps {
onAffiliationSelect: (isAffiliated: boolean) => void
}
const AskForAffiliation: React.FC<AskForAffiliationProps> = ({ onAffiliationSelect }) => {
const handleYesClick = () => {
onAffiliationSelect(true)
}
const handleNoClick = () => {
onAffiliationSelect(false)
}
return (
<Card sx={{ width: "100%", maxWidth: "100%" }}>
<CardContent>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "100%",
p: 2,
}}
>
<Title text="Se requiere Información" size="h3" />
<Label htmlFor="loyalty" text="¿Está registrado como afiliado?" />
<Box sx={{ mt: 3, display: "flex", gap: 3 }}>
<Grid size={{ xs: 6 }} sx={{ minWidth: "200px" }}>
<Button label="Sí" onClick={handleYesClick} variant="primary" className="h-16 w-full" />
</Grid>
<Grid size={{ xs: 6 }} sx={{ minWidth: "200px" }}>
<Button label="No" onClick={handleNoClick} variant="primary" className="h-16 w-full" />
</Grid>
</Box>
</Box>
</CardContent>
</Card>
)
}
export default AskForAffiliation

View file

@ -0,0 +1,110 @@
"use client"
import { Box, Card, CardContent } from "@mui/material"
import Grid from "@mui/material/Grid2"
import React, { useEffect, useState } from "react"
import Button from "@atoms/Button"
import Input from "@atoms/Input"
import Title from "@atoms/Title"
interface IdentityDocumentNumberProps {
documentType: string
onSubmit: (documentNumber: string) => void
}
const IdentityDocumentNumber: React.FC<IdentityDocumentNumberProps> = ({ documentType, onSubmit }) => {
const [inputValue, setInputValue] = useState<string>("")
const [keyboardMode, setKeyboardMode] = useState<"numeric" | "alpha">("numeric")
const [isAlphaEnabled, setIsAlphaEnabled] = useState<boolean>(false)
useEffect(() => {
const alphaDocuments = ["PASAPORTE", "CURP", "RFC", "NIE", "RUT"] // Documentos que contienen letras
setIsAlphaEnabled(alphaDocuments.includes(documentType.toUpperCase()))
setKeyboardMode(isAlphaEnabled ? "alpha" : "numeric")
}, [documentType, isAlphaEnabled])
const handleKeyPress = (key: string) => {
setInputValue((prev) => prev + key)
}
const handleDelete = () => {
setInputValue((prev) => prev.slice(0, -1))
}
// Renderiza el teclado basado en el modo seleccionado
const renderKeyboard = () => {
if (keyboardMode === "numeric") {
return (
<>
{["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"].map((num) => (
<Grid key={num} size={{ xs: 4 }}>
<Button label={num} onClick={() => handleKeyPress(num)} variant="primary" className="h-16 w-full" />
</Grid>
))}
</>
)
} else {
return (
<>
{"ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").map((letter) => (
<Grid key={letter} size={{ xs: 2 }}>
<Button label={letter} onClick={() => handleKeyPress(letter)} variant="primary" className="h-16 w-full" />
</Grid>
))}
</>
)
}
}
return (
<Card sx={{ width: "100%", maxWidth: "100%" }}>
<CardContent>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "100%",
p: 2,
}}
>
{/* Título */}
<Title text={`Ingrese el número de ${documentType}`} size="h4" />
{/* Input que muestra el valor actual */}
<Input
type="text"
placeholder="Ingrese el número de documento"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
<Grid container spacing={2} justifyContent="center" alignItems="center" sx={{ mt: 3 }}>
<Grid size={{ xs: 4 }}>
<Button label="Borrar" onClick={handleDelete} variant="danger" className="h-16 w-full" />
</Grid>
<Grid size={{ xs: 4 }}>
<Button label="Cancelar" onClick={() => setInputValue("")} variant="danger" className="h-16 w-full" />
</Grid>
{renderKeyboard()}
<Grid size={{ xs: 4 }}>
<Button label="Ingresar" onClick={() => onSubmit(inputValue)} variant="success" className="h-16 w-full" />
</Grid>
{isAlphaEnabled && (
<Grid size={{ xs: 4 }}>
<Button
label={`Cambiar a ${keyboardMode === "numeric" ? "Alfabético" : "Numérico"}`}
onClick={() => setKeyboardMode(keyboardMode === "numeric" ? "alpha" : "numeric")}
variant="secondary"
className="h-16 w-full"
/>
</Grid>
)}
</Grid>
</Box>
</CardContent>
</Card>
)
}
export default IdentityDocumentNumber

View file

@ -0,0 +1,52 @@
"use client"
import { Box, Card, CardContent } from "@mui/material"
import Grid from "@mui/material/Grid2"
import Button from "@atoms/Button"
import Label from "@atoms/Label"
import Title from "@atoms/Title"
interface TypesDocumentProps {
typesDocument: string[]
onDocumentSelect: (documentType: string) => void
}
const TypeIdentityDocument: React.FC<TypesDocumentProps> = ({ typesDocument, onDocumentSelect }) => {
const handleDocumentClick = (documentType: string) => {
onDocumentSelect(documentType)
}
return (
<Card sx={{ width: "100%", maxWidth: "100%" }}>
<CardContent>
<Box
sx={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "100%",
p: 2,
}}
>
<Title text="Se requiere Información" size="h4" />
<Label htmlFor="loyalty" text="Seleccione el tipo de documento de identidad" />
<Grid container spacing={2} justifyContent="center" sx={{ mt: 3 }}>
{typesDocument.map((documentType, index) => (
<Grid key={index} size={{ xs: 6, md: 4 }} sx={{ minWidth: "200px" }}>
<Button
label={documentType}
onClick={() => handleDocumentClick(documentType)}
variant="primary"
className="h-16 w-full"
/>
</Grid>
))}
</Grid>
</Box>
</CardContent>
</Card>
)
}
export default TypeIdentityDocument

View file

@ -0,0 +1,39 @@
import { Box, FormControl } from "@mui/material"
import React from "react"
import Input from "@atoms/Input"
import Label from "@atoms/Label"
interface FormFieldProps {
label: string
inputType: string
inputValue: string
onInputChange: (e: React.ChangeEvent<HTMLInputElement>) => void
placeholder?: string
autocomplete?: string
}
const FormField: React.FC<FormFieldProps> = ({
label,
inputType,
inputValue,
onInputChange,
placeholder,
autocomplete,
}) => {
return (
<Box mb={2}>
<FormControl fullWidth>
<Label htmlFor={label} text={label} />
<Input
type={inputType}
value={inputValue}
onChange={onInputChange}
placeholder={placeholder}
autocomplete={autocomplete}
/>
</FormControl>
</Box>
)
}
export default FormField

View file

@ -0,0 +1,20 @@
"use client"
import Grid2 from "@mui/material/Grid2"
import React from "react"
import Button from "@atoms/Button"
interface NumericKeyProps {
label: string | number
onClick: () => void
className?: string
}
const NumericKey: React.FC<NumericKeyProps> = ({ label, onClick, className }) => {
return (
<Grid2 container justifyContent="center" component="div">
<Button label={label.toString()} onClick={onClick} className={`h-16 w-full ${className}`} />
</Grid2>
)
}
export default NumericKey

View file

@ -0,0 +1,69 @@
"use client"
import Grid from "@mui/material/Grid2"
import React from "react"
import NumericKey from "@molecules/NumericKey"
const NumericPad: React.FC = () => {
const handleKeyClick = (key: number) => {
console.log(`Key pressed: ${key}`)
}
return (
<Grid container spacing={0.4} justifyContent="center" alignItems="center">
<Grid size={{ xs: 6 }}>
<NumericKey
label="Cancel"
onClick={() => console.log("Cancel pressed")}
className="h-12 w-full bg-danger-light text-white hover:bg-danger-dark"
/>
</Grid>
<Grid size={{ xs: 6 }}>
<NumericKey label="clear" onClick={() => console.log("Clear pressed")} className="size-12 bg-secondary-light" />
</Grid>
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
<NumericKey
label="0"
onClick={() => console.log("0")}
className="size-24 bg-primary-500 text-white hover:bg-primary-600"
/>
</Grid>
{[...Array(9).keys()].map((i) => (
<Grid key={i + 1} size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
{" "}
<NumericKey
label={i + 1}
onClick={() => handleKeyClick(i + 1)}
className="size-24 bg-primary-500 text-white hover:bg-primary-600"
/>
</Grid>
))}
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
<NumericKey
label="."
onClick={() => console.log(".")}
className="size-24 bg-primary-500 text-white hover:bg-primary-600"
/>
</Grid>
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
<NumericKey label="/" onClick={() => console.log("Divide pressed")} className="size-12 bg-secondary-light" />
</Grid>
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
<NumericKey label="QTY" onClick={() => console.log("QTY pressed")} className="size-12 bg-secondary-light" />
</Grid>
<Grid size={{ xs: 3 }} sx={{ minWidth: "100px" }}>
<NumericKey label="Price" onClick={() => console.log("Price pressed")} className="size-12 bg-secondary-light" />
</Grid>
<Grid size={{ xs: 6 }}>
<NumericKey
label="Enter"
onClick={() => console.log("Enter pressed")}
className="h-12 w-full bg-success-light text-white hover:bg-success-dark"
/>
</Grid>
</Grid>
)
}
export default NumericPad

View file

@ -0,0 +1,28 @@
"use client"
import { Box } from "@mui/material"
import React, { useState } from "react"
import { useTranslation } from "react-i18next"
import Input from "@atoms/Input"
const PluEntry: React.FC = () => {
const [plu, setPlu] = useState("")
const { t } = useTranslation()
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setPlu(e.target.value)
}
return (
<Box sx={{ p: 1, backgroundColor: "#f5f5f5", borderRadius: 2 }}>
<Input
type="text"
value={plu}
onChange={handleInputChange}
placeholder={t("lbl_enter_PLU")}
autocomplete="off"
/>
</Box>
)
}
export default PluEntry

View file

@ -0,0 +1,13 @@
"use client"
import { Box, Typography } from "@mui/material"
import React from "react"
const ProductMatch: React.FC = () => {
return (
<Box sx={{ p: 2, backgroundColor: "#f5f5f5", borderRadius: 1 }}>
<Typography variant="h6">Coincidencia de productos:</Typography> {/* TODO: Aquí irán los productos que coincidan con el PLU */}
</Box>
)
}
export default ProductMatch

View file

@ -0,0 +1,15 @@
"use client"
import { Box, Typography } from "@mui/material"
import React from "react"
const Screen: React.FC = () => {
return (
<Box sx={{ p: 2, backgroundColor: "#f5f5f5", borderRadius: 1, textAlign: "center" }}>{/* TODO: Logica para valores */}
<Typography variant="h6">Item Subtotal: $0.00</Typography>
<Typography variant="body1">Total Savings: $0.00</Typography>
<Typography variant="body1">Tax: $0.00</Typography>
</Box>
)
}
export default Screen

View file

@ -0,0 +1,310 @@
import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet"
import BackspaceIcon from "@mui/icons-material/Backspace"
import CreditCardIcon from "@mui/icons-material/CreditCard"
import DeleteIcon from "@mui/icons-material/Delete"
import DiscountIcon from "@mui/icons-material/Discount"
import LockIcon from "@mui/icons-material/Lock"
import PercentIcon from "@mui/icons-material/Percent"
import PlayArrowIcon from "@mui/icons-material/PlayArrow"
import ReceiptIcon from "@mui/icons-material/Receipt"
import RedeemIcon from "@mui/icons-material/Redeem"
import ReplayIcon from "@mui/icons-material/Replay"
import SearchIcon from "@mui/icons-material/Search"
import SettingsIcon from "@mui/icons-material/Settings"
import ShoppingBagIcon from "@mui/icons-material/ShoppingBag"
import ShoppingBasketIcon from "@mui/icons-material/ShoppingBasket"
import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"
import StorefrontIcon from "@mui/icons-material/Storefront"
import TransferWithinAStationIcon from "@mui/icons-material/TransferWithinAStation"
import { Box, Card, CardContent } from "@mui/material"
import Grid from "@mui/material/Grid2"
import React from "react"
import { useTranslation } from "react-i18next"
import Button from "@atoms/Button"
import { Tooltip } from "@atoms/Tooltip"
import ThemeToggle from "@styles/ThemeToggle"
const ButtonPanel: React.FC = () => {
const { t } = useTranslation()
return (
<Card sx={{ height: "100%" }}>
<CardContent>
<ThemeToggle />
<Grid container spacing={1}>
<Grid size={{ xs: 4 }}>
<Tooltip explainer="Anula la orden actual" side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label="Void Order"
onClick={() => {}}
variant="danger"
className="full-size-button"
startIcon={<ShoppingCartIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer="Void Last" side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label="Void Last"
onClick={() => {}}
variant="danger"
className="full-size-button"
startIcon={<BackspaceIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_resume_previously_suspended_order")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label="Resume"
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<PlayArrowIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_transfer_Order_another_station")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_transfer_order")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<TransferWithinAStationIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_generate_resale")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_resale")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<StorefrontIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_generate_refund")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_returns")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<ReplayIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_add_receipt")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_add_receipt")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<ReceiptIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_manage_coupons")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_coupons")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<RedeemIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_manage_discount_percentage")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_discount_percentage")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<PercentIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_manage_amount_discount")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_amount_discount")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<DiscountIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("lbl_bag_fee")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("msj_bag_fee")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<ShoppingBagIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_bag_credit_management")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_bag_credit")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<ShoppingBasketIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_bottle_refund_management")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_bottle_refund")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<DeleteIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_tax_exemption_management")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_tax_exempt")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<CreditCardIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_delete_tax_exception")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_clear_exempt")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<CreditCardIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 4 }}>
<Tooltip explainer={t("msj_query_search_article")} side="top" withArrow>
<Box sx={{ width: "100%" }}>
<Button
label={t("lbl_item_inquiry")}
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<SearchIcon />}
/>
</Box>
</Tooltip>
</Grid>
<Grid size={{ xs: 12 }} container spacing={1} justifyContent="center">
<Grid size={{ xs: 4 }}>
<Box sx={{ width: "100%" }}>
<Button
label=""
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<SettingsIcon />}
/>
</Box>
</Grid>
<Grid size={{ xs: 4 }}>
<Box sx={{ width: "100%" }}>
<Button
label=""
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<AccountBalanceWalletIcon />}
/>
</Box>
</Grid>
<Grid size={{ xs: 4 }}>
<Box sx={{ width: "100%" }}>
<Button
label=""
onClick={() => {}}
variant="primary"
className="full-size-button"
startIcon={<SearchIcon />}
/>
</Box>
</Grid>
</Grid>
<Grid size={{ xs: 12 }}>
<Box sx={{ width: "100%" }}>
<Button
label=""
onClick={() => {}}
variant="warning"
className="full-size-button"
startIcon={<LockIcon />}
/>
</Box>
</Grid>
</Grid>
</CardContent>
</Card>
)
}
export default ButtonPanel

View file

@ -0,0 +1,62 @@
import MoreVertIcon from "@mui/icons-material/MoreVert"
import PersonIcon from "@mui/icons-material/Person"
import { Card, CardContent, CardHeader, IconButton, Typography } from "@mui/material"
import Grid from "@mui/material/Grid2"
import React from "react"
import { useTranslation } from "react-i18next"
interface HeaderProps {
sx?: object
}
const Header: React.FC<HeaderProps> = ({ sx }) => {
const { t } = useTranslation()
return (
<Card sx={{ ...sx, p: 0.1 }}>
<CardHeader
title="Generic userd"
subheader="Add Member"
titleTypographyProps={{ color: "text.primary", fontWeight: "bold", fontSize: "1rem" }}
subheaderTypographyProps={{ color: "text.secondary", fontSize: "0.75rem" }}
sx={{ paddingBottom: 0 }}
/>
<CardContent sx={{ paddingTop: 0.2 }}>
<Grid container alignItems="center" justifyContent="space-between">
<Grid>
<Typography variant="body2" color="text.secondary">
Fri, 13 Sept
</Typography>
</Grid>
<Grid>
<Typography variant="body2" color="text.secondary">
9:36 am
</Typography>
</Grid>
<Grid>
<Typography variant="body2" color="text.secondary">
{t('lbl_Store')}: <strong>8004</strong>
</Typography>
</Grid>
<Grid>
<Typography variant="body2" color="text.secondary">
{t('lbl_register')}: <strong>317</strong>
</Typography>
</Grid>
<Grid container alignItems="center">
<PersonIcon sx={{ color: "text.secondary", fontSize: "18px", mr: 0.9 }} /> {/* Icono más pequeño */}
<Typography variant="body2" color="text.secondary">
{t('lbl_associate')} <strong>2</strong>
</Typography>
</Grid>
<Grid>
<IconButton size="small">
<MoreVertIcon color="action" sx={{ fontSize: "18px" }} />
</IconButton>
</Grid>
</Grid>
</CardContent>
</Card>
)
}
export default Header

View file

@ -0,0 +1,65 @@
"use client"
import { Box, Typography } from "@mui/material"
import { useRouter } from "next/navigation"
import React, { useState } from "react"
import { useTranslation } from "react-i18next"
import { auth } from "@app/account/controller/AuthController"
import Button from "@atoms/Button"
import Input from "@atoms/Input"
import Label from "@atoms/Label"
const LoginForm: React.FC = () => {
const [username, setUsername] = useState("")
const [password, setPassword] = useState("")
const [error, setError] = useState<string | null>(null)
const router = useRouter()
const { t } = useTranslation()
const handleLogin = async () => {
const token = await auth(username, password)
if (token) {
router.push("/home")
} else {
setError( t("msj_Incorrect_credentials_try_again."))
}
}
return (
<Box
component="form"
onSubmit={(e) => {
e.preventDefault()
handleLogin()
}}
sx={{ display: "flex", flexDirection: "column", gap: 3, width: "100%" }}
>
<Box>
<Label htmlFor={t("lbl_username")} text={t("lbl_username")} />
<Input
type="text"
placeholder={t("msj_enter_your_username")}
value={username}
onChange={(e) => setUsername(e.target.value)}
autocomplete="username"
/>
</Box>
<Box>
<Label htmlFor={t("lbl_password")} text={t("lbl_password")} />
<Input
type="password"
placeholder={t("msj_enter_your_password")}
value={password}
onChange={(e) => setPassword(e.target.value)}
autocomplete="current-password"
/>
</Box>
{error && <Typography color="error">{error}</Typography>}
<Button label={t("lbl_login")} onClick={handleLogin} variant="primary" />
</Box>
)
}
export default LoginForm

View file

@ -0,0 +1,33 @@
"use client"
import { Card, CardContent } from "@mui/material"
import Grid from "@mui/material/Grid2"
import React from "react"
import NumericPad from "@molecules/NumericPad"
import PluEntry from "@molecules/PluEntry"
import ProductMatch from "@molecules/ProductMatch"
import Screen from "@molecules/Screen"
const MainPanel: React.FC = () => {
return (
<Card sx={{ height: "100%" }}>
<CardContent sx={{ padding: "8px" }}>
<Grid container spacing={1} justifyContent="center" alignItems="stretch">
<Grid size={{ xs: 12 }}>
<Screen />
</Grid>
<Grid size={{ xs: 12 }}>
<ProductMatch />
</Grid>
<Grid size={{ xs: 12 }}>
<PluEntry />
</Grid>
<Grid size={{ xs: 12 }}>
<NumericPad />
</Grid>
</Grid>
</CardContent>
</Card>
)
}
export default MainPanel

View file

@ -0,0 +1,47 @@
"use client";
import Grid from '@mui/material/Grid2';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import React, { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import i18n from '@package/utils/language/i18n';
const Navbar: React.FC = () => {
const { t } = useTranslation();
const [language, setLanguage] = useState(i18n.language || 'en');
const changeLanguage = (lng: string) => {
i18n.changeLanguage(lng);
setLanguage(lng);
};
useEffect(() => {
setLanguage(i18n.language);
}, []);
return (
<Grid
container
justifyContent="space-between"
alignItems="center"
className="bg-blue-600 text-white p-4"
>
<Grid>
<h1 className="text-white font-bold">{t('lbl_wellcome')}</h1>
</Grid>
<Grid>
<Select
value={language}
onChange={(e) => changeLanguage(e.target.value as string)}
variant="outlined"
className="bg-white text-black"
>
<MenuItem value="en">English</MenuItem>
<MenuItem value="es">Español</MenuItem>
</Select>
</Grid>
</Grid>
);
};
export default Navbar;

View file

@ -0,0 +1,28 @@
"use client"
import { Card, CardContent, Typography } from "@mui/material"
import React from "react"
const ProductList: React.FC = () => {
return (
<Card sx={{ height: "100%" }}>
{" "}
{/* Ajustar la altura de la Card */}
<CardContent>
<Typography variant="h6" color="text.primary">
Lista de productos
</Typography>
<Typography variant="body1" color="text.secondary">
Producto 1
</Typography>
<Typography variant="body1" color="text.secondary">
Producto 2
</Typography>
<Typography variant="body1" color="text.secondary">
Producto 3
</Typography>
</CardContent>
</Card>
)
}
export default ProductList

View file

@ -0,0 +1 @@
export const BASE_URL_AUTHENTICATION = "/AUTHENTICATION-SERVICE/rhi-bpc/"

View file

@ -0,0 +1,32 @@
import { ReturnService } from "@package/config/ReturnService"
import i18n from "@package/utils/language/i18n"
const BASE_URL = process.env.NEXT_PUBLIC_API_URL
async function FetchData<T>(
BASE_URL_MODULE: string,
endpoint: string,
options: RequestInit = {}
): Promise<ReturnService<T>> {
const url = `${BASE_URL}${BASE_URL_MODULE}${endpoint}`
const text = i18n.t("errorProcessingAPI") as string
try {
const response = await fetch(url, options)
if (response.status === 403) {
const text = i18n.t("inCorrectAccess") as string
return new ReturnService<T>(null as unknown as T, false, text, 1)
}
if (!response.ok) {
return new ReturnService<T>(null as unknown as T, false, text, 1)
}
const data = await response.json()
return new ReturnService<T>(data as T)
} catch (error: any) {
return new ReturnService<T>(null as unknown as T, false, text || error.message, error.errorCode || 1)
}
}
export { FetchData }

View file

@ -0,0 +1,13 @@
export class ReturnService<T> {
correct: boolean
message: string
errorCode: number
object: T
constructor(object: T, correct = true, message = "", errorCode = 0) {
this.correct = correct
this.message = message
this.errorCode = errorCode
this.object = object
}
}

View file

@ -0,0 +1,29 @@
"use client"
import React, { useEffect, useState } from "react"
const ThemeToggle: React.FC = () => {
const [theme, setTheme] = useState("light")
useEffect(() => {
const savedTheme = localStorage.getItem("theme")
if (savedTheme) {
setTheme(savedTheme)
document.documentElement.classList.toggle("dark", savedTheme === "dark")
}
}, [])
const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light"
setTheme(newTheme)
localStorage.setItem("theme", newTheme)
document.documentElement.classList.toggle("dark", newTheme === "dark")
}
return (
<button onClick={toggleTheme} className="bg-primary rounded-lg p-2 text-white transition hover:bg-primary-600">
{theme === "light" ? "Modo Oscuro" : "Modo Claro"}
</button>
)
}
export default ThemeToggle

View file

@ -0,0 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
.full-size-button {
width: 100%;
height: 60px;
font-size: 14px;
}

View file

@ -0,0 +1,63 @@
export enum ColombiaDocuments {
CEDULA_CIUDADANIA = "Cédula de Ciudadanía",
TARJETA_IDENTIDAD = "Tarjeta de Identidad",
PASAPORTE = "Pasaporte",
CEDULA_EXTRANJERIA = "Cédula de Extranjería",
}
export enum EcuadorDocuments {
CEDULA_IDENTIDAD = "Cédula de Identidad",
PASAPORTE = "Pasaporte",
RUC = "Registro Único de Contribuyentes (RUC)",
}
export enum CostaRicaDocuments {
CEDULA_NACIONAL = "Cédula Nacional",
DIMEX = "Documento de Identificación de Migrantes Extranjeros (DIMEX)",
PASAPORTE = "Pasaporte",
}
export enum ChileDocuments {
CEDULA_IDENTIDAD = "Cédula de Identidad",
PASAPORTE = "Pasaporte",
RUT = "Rol Único Tributario (RUT)",
}
export enum MexicoDocuments {
CREDENCIAL_ELECTOR = "Credencial de Elector (INE)",
PASAPORTE = "Pasaporte",
CURP = "Clave Única de Registro de Población (CURP)",
RFC = "Registro Federal de Contribuyentes (RFC)",
}
export enum USADocuments {
DRIVER_LICENSE = "Driver's License",
PASSPORT = "Passport",
SSN = "Social Security Number (SSN)",
STATE_ID = "State ID",
}
export enum PeruDocuments {
DNI = "Documento Nacional de Identidad (DNI)",
PASAPORTE = "Pasaporte",
CARNET_EXTRANJERIA = "Carnet de Extranjería",
}
export enum ArgentinaDocuments {
DNI = "Documento Nacional de Identidad (DNI)",
PASAPORTE = "Pasaporte",
CUIL = "Código Único de Identificación Laboral (CUIL)",
}
export enum BrazilDocuments {
CPF = "Cadastro de Pessoas Físicas (CPF)",
RG = "Registro Geral (RG)",
CNH = "Carteira Nacional de Habilitação (CNH)",
PASAPORTE = "Pasaporte",
}
export enum SpainDocuments {
DNI = "Documento Nacional de Identidad (DNI)",
NIE = "Número de Identificación de Extranjeros (NIE)",
PASAPORTE = "Pasaporte",
}

View file

@ -0,0 +1,41 @@
import {
ArgentinaDocuments,
BrazilDocuments,
ChileDocuments,
ColombiaDocuments,
CostaRicaDocuments,
EcuadorDocuments,
MexicoDocuments,
PeruDocuments,
SpainDocuments,
USADocuments,
} from "@package/utils/enums/TypesDocumentsByCountry"
const country = process.env.NEXT_PUBLIC_STORE_COUNTRY
export const getDocumentsByCountry = (): string[] => {
switch (country?.toLowerCase()) {
case "colombia":
return Object.values(ColombiaDocuments)
case "ecuador":
return Object.values(EcuadorDocuments)
case "costarica":
return Object.values(CostaRicaDocuments)
case "chile":
return Object.values(ChileDocuments)
case "mexico":
return Object.values(MexicoDocuments)
case "usa":
return Object.values(USADocuments)
case "peru":
return Object.values(PeruDocuments)
case "argentina":
return Object.values(ArgentinaDocuments)
case "brazil":
return Object.values(BrazilDocuments)
case "spain":
return Object.values(SpainDocuments)
default:
return []
}
}

View file

@ -0,0 +1,96 @@
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
i18n
.use(initReactI18next)
.init({
fallbackLng: 'en', // Default language
lng: 'en', // Specify the default language to use
resources: {
en: {
translation: {
lbl_wellcome: "Welcome",
lbl_login: "Login",
lbl_username: "Username",
lbl_password: "Password",
lbl_enter_PLU: "Enter PLU",
lbl_transfer_order: "Transfer Order",
lbl_resale: "Resale",
lbl_add_receipt: "Add receipt",
lbl_coupons : "Coupons",
lbl_discount_percentage: "Discount percentage",
lbl_amount_discount: "Amount discount",
lbl_bag_credit: "Bag credit",
lbl_bottle_refund: "Bottle refund",
lbl_tax_exempt: "Tax exempt",
lbl_item_inquiry: "Item Inquiry",
lbl_Store: "Store",
lbl_register: "Register",
lbl_associate: "Associate",
msj_resume_previously_suspended_order: "Resume a previously suspended order",
msj_transfer_Order_another_station: "Transfer an order to another station",
msj_generate_resale: "Generate a resale",
msj_generate_refund: "Generate a refund",
msj_add_receipt: "Add receipt",
msj_manage_coupons: "Manage coupons",
msj_manage_discount_percentage: "Manage discount percentage",
msj_manage_amount_discount: "Manage amount discount",
msj_bag_credit_management: "Credit bag management",
msj_bottle_refund_management: "Bottle refund management",
msj_tax_exemption_management: "Tax exemption management",
msj_query_search_article: "Query or search for article",
msj_enter_your_username: "Enter your username",
msj_enter_your_password: "Enter your password",
msj_Incorrect_credentials_try_again: "Incorrect credentials, try again",
},
},
es: {
translation: {
lbl_wellcome: "Bienvenido",
lbl_login: "Iniciar sesión",
lbl_username: "Nombre de usuario",
lbl_password: "Contraseña",
lbl_enter_PLU: "Ingrese PLU",
lbl_transfer_order: "Transferir orden",
lbl_resale: "Reventa",
lbl_add_receipt: "Agregar recibo",
lbl_coupons : "Cupones",
lbl_discount_percentage: "Porcentaje de descuento",
lbl_amount_discount: "Importe de descuento",
lbl_bag_credit: "Bolsa de crédito",
lbl_bottle_refund: "Devolución de botellas",
lbl_tax_exempt: "Exento de impuestos",
lbl_item_inquiry: "Consulta de artículo",
lbl_Store: "Tienda",
lbl_register: "Registro",
lbl_associate: "Asociado",
msj_resume_previously_suspended_order: "Reanuda una orden previamente suspendida",
msj_transfer_Order_another_station: "Transfiere la orden a otra estación",
msj_generate_resale: "Generar una reventa",
msj_generate_refund: "Generar una devolución",
msj_add_receipt: "Agregar recibo",
msj_manage_coupons: "Administrar cupones",
msj_manage_discount_percentage: "Administrar porcentaje de descuento",
msj_manage_amount_discount: "Administrar importe de descuento",
msj_bag_credit_management: "Gestión de bolsa de crédito",
msj_bottle_refund_management: "Gestión de devolución de botellas",
msj_tax_exemption_management: "Gestión de exención de impuestos",
msj_query_search_article: "Consulta o búsqueda de artículo",
msj_enter_your_username: "Ingrese su nombre de usuario",
msj_enter_your_password: "Ingrese su contraseña",
msj_Incorrect_credentials_try_again: "Credenciales incorrectas, inténtelo de nuevo",
},
},
},
interpolation: {
escapeValue: false, // Not needed for React as it escapes by default
},
});
export default i18n;

BIN
public/img/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
public/img/selfcheckout.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View file

@ -1,8 +1,6 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:base"
],
"extends": ["config:base"],
"packageRules": [
{
"enabled": false,

View file

@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -12,7 +12,7 @@ module.exports = {
"./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/**/*.{js,ts,jsx,tsx,mdx}",
"./package/**/*.{js,ts,jsx,tsx}",
],
theme: {
extend: {
@ -29,62 +29,35 @@ module.exports = {
800: "#1e40af",
900: "#1e3a8a",
},
secondary: {
light: "#6b7280",
dark: "#374151",
},
success: {
light: "#10b981",
dark: "#047857",
},
danger: {
light: "#ef4444",
dark: "#b91c1c",
},
warning: {
light: "#f59e0b",
dark: "#b45309",
},
info: {
light: "#0ea5e9",
dark: "#0369a1",
},
background: {
light: "#ffffff",
dark: "#111827",
},
},
fontFamily: {
body: [
"Inter",
"ui-sans-serif",
"system-ui",
"-apple-system",
"system-ui",
"Segoe UI",
"Roboto",
"Helvetica Neue",
"Arial",
"Noto Sans",
"sans-serif",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji",
],
sans: [
"Inter",
"ui-sans-serif",
"system-ui",
"-apple-system",
"system-ui",
"Segoe UI",
"Roboto",
"Helvetica Neue",
"Arial",
"Noto Sans",
"sans-serif",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol",
"Noto Color Emoji",
],
},
borderWidth: {
DEFAULT: "1px",
0: "0",
2: "2px",
3: "3px",
4: "4px",
6: "6px",
8: "8px",
},
minHeight: {
...defaultTheme.height,
},
minWidth: {
...defaultTheme.width,
sans: ["Inter", "ui-sans-serif", "system-ui", "-apple-system", "Segoe UI", "Roboto", "Helvetica Neue", "Arial"],
},
},
},
plugins: [],
future: {
hoverOnlyWhenSupported: true,
},
}

View file

@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/tsconfig",
"display": "Next.js",
"compilerOptions": {
"target": "es5",
"target": "ES6",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
@ -17,14 +17,32 @@
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"types": ["node", "jest", "@testing-library/jest-dom"],
"plugins": [
{
"name": "next"
}
]
],
"baseUrl": ".",
"paths": {
"@atoms/*": ["./package/components/atoms/*"],
"@molecules/*": ["./package/components/molecules/*"],
"@organisms/*": ["./package/components/organisms/*"],
"@layouts/*": ["./package/components/layouts/*"],
"@styles/*": ["./package/styles/*"],
"@package/*": ["./package/*"],
"@app/*": ["./app/*"],
"@public/*": ["./public/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.mjs", "jest.config.js", ".next/types/**/*.ts"],
"include": [
"next-env.d.ts",
"**/*.ts",
"**/*.tsx",
"**/*.mjs",
"jest.config.js",
".next/types/**/*.ts",
"package/config/FetchData.tsx"
],
"exclude": ["node_modules"]
}

13609
yarn.lock

File diff suppressed because it is too large Load diff