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

View file

@ -1,11 +1,10 @@
<img align="right" width="360px" height="410px" src="./.github/assets/project-logo-vertical.png"> <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! 🌍 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] > [!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
> **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 ## Features
@ -273,7 +272,6 @@ If you're looking for help or simply want to share your thoughts about the proje
</a> </a>
<br /> <br />
## 📜 License ## 📜 License
This project is licensed under the MIT License. For more information, see the [LICENSE](./LICENSE) file. 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 [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 [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/ [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 [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-license-badge-link]: https://github.com/Blazity/next-enterprise/blob/main/LICENSE
[github-contributors-badge-link]: https://github.com/Blazity/next-enterprise/graphs/contributors [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() { 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 }) { export default function RootLayout({ children }: { children: React.ReactNode }) {
return ( return (
<html lang="en"> <html lang="en">
<body>{children}</body> <body>
<Navbar />
<main>{children}</main>
</body>
</html> </html>
) );
} }

View file

@ -1,65 +1,68 @@
import { Metadata } from "next" "use client"
import { Button } from "components/Button/Button" import Grid from "@mui/material/Grid2"
import { LP_GRID_ITEMS } from "lp-items" import Image from "next/image"
import { useRouter } from "next/navigation"
import Button from "@package/components/atoms/Button"
export const metadata: Metadata = { const HomePage: React.FC = () => {
title: "Next.js Enterprise Boilerplate", const router = useRouter()
twitter: {
card: "summary_large_image", const handleLoginClick = () => {
}, router.push("/account/login")
openGraph: { }
url: "https://next-enterprise.vercel.app/",
images: [ const handleFeaturesClick = () => {
{ router.push("/features")
width: 1200, }
height: 630,
url: "https://raw.githubusercontent.com/Blazity/next-enterprise/main/.github/assets/project-logo.png",
},
],
},
}
export default function Web() {
return ( return (
<> <Grid
<section className="bg-white dark:bg-gray-900"> container
<div className="mx-auto grid max-w-screen-xl px-4 py-8 text-center lg:py-16"> className="min-h-screen"
<div className="mx-auto place-self-center"> alignItems="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"> justifyContent="center"
Next.js Enterprise Boilerplate sx={{
</h1> background: "linear-gradient(to right, black, #2c3e50, #0064dc)",
<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"> alignItems: "center",
Jumpstart your enterprise project with our feature-packed, high-performance Next.js boilerplate! justifyContent: "center",
Experience rapid UI development, AI-powered code reviews, and an extensive suite of tools for a smooth and }}
enjoyable development process. >
</p> <Grid size={{ xs: 12, md: 6 }} display="flex" justifyContent={{ xs: "center", md: "flex-end" }}>
<Button href="https://github.com/Blazity/next-enterprise" className="mr-3"> <Image
Get started src="/img/selfcheckout.jpg"
</Button> 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 <Button
href="https://vercel.com/new/git/external?repository-url=https://github.com/Blazity/next-enterprise" label="Iniciar Sesión"
intent="secondary" onClick={handleLoginClick}
> className="rounded-lg bg-blue-600 px-6 py-3 text-lg text-white transition hover:bg-blue-700"
Deploy Now />
</Button> </Grid>
</div> <Grid size={{ xs: 6, md: 6 }}>
</div> <Button
</section> label="Ver Funcionalidades"
<section className="bg-white dark:bg-gray-900"> onClick={handleFeaturesClick}
<div className="mx-auto max-w-screen-xl px-4 py-8 sm:py-16 lg:px-6"> className="rounded-lg bg-gray-200 px-6 py-3 text-lg text-blue-600 transition hover:bg-gray-300"
<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) => ( </Grid>
<div key={singleItem.title} className="flex flex-col items-center justify-center text-center"> </Grid>
<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"> </Grid>
{singleItem.icon} </Grid>
</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>
</>
) )
} }
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() .optional()
.transform((value) => value === "true"), .transform((value) => value === "true"),
}, },
client: {}, client: {
NEXT_PUBLIC_API_URL: z.string().url(),
},
runtimeEnv: { runtimeEnv: {
ANALYZE: process.env.ANALYZE, ANALYZE: process.env.ANALYZE,
NEXT_PUBLIC_API_URL: process.env.NEXT_PUBLIC_API_URL,
}, },
}) })

View file

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

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" import { registerOTel } from "@vercel/otel"
export function register() { // Configuración del logger de diagnóstico
registerOTel("next-app") 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" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -29,7 +29,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -49,7 +49,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" /> <path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
</svg> </svg>
@ -65,7 +65,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -85,7 +85,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -105,7 +105,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -125,7 +125,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -145,7 +145,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -165,7 +165,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -185,7 +185,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -205,7 +205,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -226,7 +226,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -246,7 +246,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -266,7 +266,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -286,7 +286,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -306,7 +306,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -326,7 +326,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -346,7 +346,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" 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="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" /> <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" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -383,7 +383,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"
@ -403,7 +403,7 @@ export const LP_GRID_ITEMS = [
viewBox="0 0 24 24" viewBox="0 0 24 24"
strokeWidth={1.5} strokeWidth={1.5}
stroke="currentColor" stroke="currentColor"
className="h-6 w-6" className="size-6"
> >
<path <path
strokeLinecap="round" strokeLinecap="round"

View file

@ -1,22 +1,24 @@
import { trace } from "@opentelemetry/api"
import { NextResponse } from "next/server" import { NextResponse } from "next/server"
import type { NextRequest } from "next/server" import type { NextRequest } from "next/server"
export function middleware(request: NextRequest) { 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 // TODO: Feel free to remove this block
if (request.headers?.get("host")?.includes("next-enterprise.vercel.app")) { 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 }) return NextResponse.redirect("https://blazity.com/open-source/nextjs-enterprise-boilerplate", { status: 301 })
} }
// Finalizar la traza
span.end()
return NextResponse.next()
} }
export const config = { export const config = {
matcher: [ matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
/*
* 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).*)",
],
} }

2
next-env.d.ts vendored
View file

@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" /> /// <reference types="next/image-types/global" />
// NOTE: This file should not be edited // 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 })]], { const config = withPlugins([[withBundleAnalyzer({ enabled: env.ANALYZE })]], {
reactStrictMode: true, 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() { rewrites() {
return [ return [
{ source: "/healthz", destination: "/api/health" }, { 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" "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": { "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", "@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-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4", "@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5",
@ -39,6 +46,7 @@
"@radix-ui/react-tabs": "^1.0.4", "@radix-ui/react-tabs": "^1.0.4",
"@radix-ui/react-toggle-group": "^1.0.4", "@radix-ui/react-toggle-group": "^1.0.4",
"@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-tooltip": "^1.0.7",
"@radix-ui/themes": "^3.1.3",
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^11.1.0", "@semantic-release/commit-analyzer": "^11.1.0",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
@ -46,13 +54,16 @@
"@semantic-release/npm": "^11.0.1", "@semantic-release/npm": "^11.0.1",
"@semantic-release/release-notes-generator": "^12.1.0", "@semantic-release/release-notes-generator": "^12.1.0",
"@t3-oss/env-nextjs": "^0.7.1", "@t3-oss/env-nextjs": "^0.7.1",
"@vercel/otel": "^0.3.0", "@vercel/otel": "^1.10.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"i18next": "^23.15.1",
"jaeger-client": "^3.19.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"next": "^14.0.3", "next": "^14.0.3",
"next-compose-plugins": "^2.2.1", "next-compose-plugins": "^2.2.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-i18next": "^15.0.1",
"tailwind-merge": "^2.0.0", "tailwind-merge": "^2.0.0",
"zod": "^3.22.4" "zod": "^3.22.4"
}, },
@ -62,11 +73,11 @@
"@babel/plugin-transform-optional-chaining": "^7.23.4", "@babel/plugin-transform-optional-chaining": "^7.23.4",
"@babel/plugin-transform-react-jsx": "^7.23.4", "@babel/plugin-transform-react-jsx": "^7.23.4",
"@jest/globals": "^29.7.0", "@jest/globals": "^29.7.0",
"@opentelemetry/api": "1.7.0", "@opentelemetry/api": "^1.9.0",
"@opentelemetry/resources": "1.18.1", "@opentelemetry/resources": "^1.26.0",
"@opentelemetry/sdk-node": "0.45.1", "@opentelemetry/sdk-node": "^0.53.0",
"@opentelemetry/sdk-trace-node": "1.18.1", "@opentelemetry/sdk-trace-node": "^1.26.0",
"@opentelemetry/semantic-conventions": "1.18.1", "@opentelemetry/semantic-conventions": "^1.27.0",
"@playwright/test": "^1.40.0", "@playwright/test": "^1.40.0",
"@storybook/addon-essentials": "^7.5.3", "@storybook/addon-essentials": "^7.5.3",
"@storybook/addon-interactions": "^7.5.3", "@storybook/addon-interactions": "^7.5.3",
@ -74,7 +85,7 @@
"@storybook/blocks": "^7.5.3", "@storybook/blocks": "^7.5.3",
"@storybook/nextjs": "^7.5.3", "@storybook/nextjs": "^7.5.3",
"@storybook/react": "^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", "@storybook/testing-library": "^0.2.2",
"@testing-library/jest-dom": "^6.1.4", "@testing-library/jest-dom": "^6.1.4",
"@testing-library/react": "^14.1.2", "@testing-library/react": "^14.1.2",
@ -96,7 +107,7 @@
"eslint-plugin-react": "7.33.2", "eslint-plugin-react": "7.33.2",
"eslint-plugin-storybook": "^0.6.15", "eslint-plugin-storybook": "^0.6.15",
"eslint-plugin-tailwindcss": "^3.13.0", "eslint-plugin-tailwindcss": "^3.13.0",
"fetch-mock": "^9.11.0", "fetch-mock": "^11.1.3",
"gzip-size": "6", "gzip-size": "6",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^29.7.0",
@ -109,11 +120,11 @@
"prettier-plugin-tailwindcss": "^0.5.7", "prettier-plugin-tailwindcss": "^0.5.7",
"semantic-release": "^22.0.8", "semantic-release": "^22.0.8",
"storybook": "^7.5.3", "storybook": "^7.5.3",
"tailwindcss": "^3.3.5", "tailwindcss": "^3.4.12",
"ts-jest": "^29.1.1", "ts-jest": "^29.1.1",
"tsc": "^2.0.4", "tsc": "^2.0.4",
"typescript": "5.3.2", "typescript": "5.3.2",
"webpack": "5.89.0" "webpack": "^5.94.0"
}, },
"engines": { "engines": {
"node": ">=18.17.0" "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", "$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [ "extends": ["config:base"],
"config:base"
],
"packageRules": [ "packageRules": [
{ {
"enabled": false, "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}", "./app/**/*.{js,ts,jsx,tsx,mdx}",
"./pages/**/*.{js,ts,jsx,tsx,mdx}", "./pages/**/*.{js,ts,jsx,tsx,mdx}",
"./components/**/*.{js,ts,jsx,tsx,mdx}", "./components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/**/*.{js,ts,jsx,tsx,mdx}", "./package/**/*.{js,ts,jsx,tsx}",
], ],
theme: { theme: {
extend: { extend: {
@ -29,62 +29,35 @@ module.exports = {
800: "#1e40af", 800: "#1e40af",
900: "#1e3a8a", 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: { fontFamily: {
body: [ sans: ["Inter", "ui-sans-serif", "system-ui", "-apple-system", "Segoe UI", "Roboto", "Helvetica Neue", "Arial"],
"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,
}, },
}, },
}, },
plugins: [], plugins: [],
future: {
hoverOnlyWhenSupported: true,
},
} }

View file

@ -2,7 +2,7 @@
"$schema": "https://json.schemastore.org/tsconfig", "$schema": "https://json.schemastore.org/tsconfig",
"display": "Next.js", "display": "Next.js",
"compilerOptions": { "compilerOptions": {
"target": "es5", "target": "ES6",
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": true,
"skipLibCheck": true, "skipLibCheck": true,
@ -17,14 +17,32 @@
"isolatedModules": true, "isolatedModules": true,
"jsx": "preserve", "jsx": "preserve",
"incremental": true, "incremental": true,
"baseUrl": ".",
"types": ["node", "jest", "@testing-library/jest-dom"], "types": ["node", "jest", "@testing-library/jest-dom"],
"plugins": [ "plugins": [
{ {
"name": "next" "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"] "exclude": ["node_modules"]
} }

13609
yarn.lock

File diff suppressed because it is too large Load diff