1st push
43
.dockerignore
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# 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
|
||||
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
|
||||
npm-debug.log
|
||||
README.md
|
||||
.next
|
||||
.git
|
||||
14
.eslintrc.js
|
|
@ -29,8 +29,11 @@ module.exports = {
|
|||
"sort-imports": [
|
||||
"error",
|
||||
{
|
||||
ignoreCase: true,
|
||||
ignoreDeclarationSort: true,
|
||||
ignoreCase: false,
|
||||
ignoreDeclarationSort: true, // don"t want to sort import lines, use eslint-plugin-import instead
|
||||
ignoreMemberSort: false,
|
||||
memberSyntaxSortOrder: ["none", "all", "multiple", "single"],
|
||||
allowSeparatedGroups: true,
|
||||
},
|
||||
],
|
||||
"tailwindcss/classnames-order": "off",
|
||||
|
|
@ -65,6 +68,13 @@ module.exports = {
|
|||
},
|
||||
],
|
||||
},
|
||||
settings: {
|
||||
"import/resolver": {
|
||||
typescript: {
|
||||
project: "./tsconfig.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
function getDirectoriesToSort() {
|
||||
|
|
|
|||
9
.vscode/settings.json
vendored
|
|
@ -1,5 +1,12 @@
|
|||
{
|
||||
"editor.formatOnSave": false,
|
||||
"eslint.validate": [
|
||||
"typescript"
|
||||
],
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva(?:<[^>]*>)?(([^)]*))", "[\"'`]([^\"'`]*).*?[\"'`]", "(?:twMerge|twJoin)\\(([^\\);]*)[\\);]"]
|
||||
]
|
||||
}
|
||||
}
|
||||
67
Dockerfile
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
FROM node:21-alpine as base
|
||||
|
||||
# Install dependencies only when needed
|
||||
FROM base AS deps
|
||||
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
|
||||
RUN apk add --no-cache libc6-compat
|
||||
WORKDIR /app
|
||||
|
||||
# Install dependencies based on the preferred package manager
|
||||
COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* ./
|
||||
RUN \
|
||||
if [ -f yarn.lock ]; then yarn --frozen-lockfile; \
|
||||
elif [ -f package-lock.json ]; then npm ci; \
|
||||
elif [ -f pnpm-lock.yaml ]; then yarn global add pnpm && pnpm i --frozen-lockfile; \
|
||||
else echo "Lockfile not found." && exit 1; \
|
||||
fi
|
||||
|
||||
|
||||
# Rebuild the source code only when needed
|
||||
FROM base AS builder
|
||||
WORKDIR /app
|
||||
COPY --from=deps /app/node_modules ./node_modules
|
||||
COPY . .
|
||||
|
||||
# Next.js collects completely anonymous telemetry data about general usage.
|
||||
# Learn more here: https://nextjs.org/telemetry
|
||||
# Uncomment the following line in case you want to disable telemetry during the build.
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN yarn build
|
||||
|
||||
# If using npm comment out above and use below instead
|
||||
# RUN npm run build
|
||||
|
||||
# Production image, copy all the files and run next
|
||||
FROM base AS runner
|
||||
WORKDIR /app
|
||||
|
||||
ENV NODE_ENV production
|
||||
# Uncomment the following line in case you want to disable telemetry during runtime.
|
||||
# ENV NEXT_TELEMETRY_DISABLED 1
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs
|
||||
RUN adduser --system --uid 1001 nextjs
|
||||
|
||||
# COPY --from=builder /app/public ./public
|
||||
|
||||
# Set the correct permission for prerender cache
|
||||
RUN mkdir .next
|
||||
RUN chown nextjs:nodejs .next
|
||||
|
||||
# Automatically leverage output traces to reduce image size
|
||||
# https://nextjs.org/docs/advanced-features/output-file-tracing
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
|
||||
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||
|
||||
USER nextjs
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
ENV PORT 3000
|
||||
# set hostname to localhost
|
||||
ENV HOSTNAME "0.0.0.0"
|
||||
|
||||
# server.js is created by next build from the standalone output
|
||||
# https://nextjs.org/docs/pages/api-reference/next-config-js/output
|
||||
CMD ["node", "server.js"]
|
||||
|
|
@ -301,3 +301,10 @@ This project is licensed under the MIT License. For more information, see the [L
|
|||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
||||
|
||||
|
||||
Techniques used
|
||||
CSR - Client side rendering for data
|
||||
useSWR hook for data fetching
|
||||
SSR - from authorization
|
||||
|
||||
16
api/route.js
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// export const dynamic = 'force-dynamic' // defaults to auto
|
||||
// export async function GET(request) {}
|
||||
export default async function handler(req, res) {
|
||||
const session = await getSession(req)
|
||||
|
||||
// Check if the user is authenticated
|
||||
if (!session) {
|
||||
res.status(401).json({
|
||||
error: "User is not authenticated",
|
||||
})
|
||||
return
|
||||
}
|
||||
console.log("_____________________________________________")
|
||||
// Proceed with the route for authorized users
|
||||
// ... implementation of the API Route
|
||||
}
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
export async function GET() {
|
||||
return Response.json({ status: "ok" })
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import "styles/tailwind.css"
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en">
|
||||
<body>{children}</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
65
app/page.tsx
|
|
@ -1,65 +0,0 @@
|
|||
import { Metadata } from "next"
|
||||
import { Button } from "components/Button/Button"
|
||||
import { LP_GRID_ITEMS } from "lp-items"
|
||||
|
||||
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/project-logo.png",
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
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>
|
||||
<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>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,73 +0,0 @@
|
|||
import * as RadixTooltip from "@radix-ui/react-tooltip"
|
||||
import { cva, VariantProps } from "class-variance-authority"
|
||||
import React from "react"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
const tooltipContent = cva([], {
|
||||
variants: {
|
||||
intent: {
|
||||
primary: ["rounded-0.5md", "bg-zinc-700", "font-open-sans", "text-white"],
|
||||
},
|
||||
size: {
|
||||
md: ["px-4", "py-2.5", "text-2xs"],
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
intent: "primary",
|
||||
size: "md",
|
||||
},
|
||||
})
|
||||
|
||||
const tooltipArrow = cva([], {
|
||||
variants: {
|
||||
intent: {
|
||||
primary: ["fill-zinc-700"],
|
||||
},
|
||||
size: {
|
||||
md: ["w-4", "h-2"],
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
intent: "primary",
|
||||
size: "md",
|
||||
},
|
||||
})
|
||||
|
||||
export interface TooltipProps extends VariantProps<typeof tooltipContent>, RadixTooltip.TooltipProps {
|
||||
explainer: React.ReactElement | string
|
||||
children: React.ReactElement
|
||||
className?: string
|
||||
withArrow?: boolean
|
||||
side?: "top" | "right" | "bottom" | "left"
|
||||
}
|
||||
|
||||
export function Tooltip({
|
||||
children,
|
||||
explainer,
|
||||
open,
|
||||
defaultOpen,
|
||||
onOpenChange,
|
||||
intent,
|
||||
size,
|
||||
side = "top",
|
||||
className,
|
||||
withArrow,
|
||||
}: TooltipProps) {
|
||||
return (
|
||||
<RadixTooltip.Provider>
|
||||
<RadixTooltip.Root open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange} delayDuration={200}>
|
||||
<RadixTooltip.Trigger asChild>{children}</RadixTooltip.Trigger>
|
||||
<RadixTooltip.Portal>
|
||||
<RadixTooltip.Content
|
||||
side={side}
|
||||
sideOffset={5}
|
||||
className={twMerge(tooltipContent({ intent, size, className }))}
|
||||
>
|
||||
{explainer}
|
||||
{withArrow ? <RadixTooltip.Arrow className={twMerge(tooltipArrow({ intent, size, className }))} /> : null}
|
||||
</RadixTooltip.Content>
|
||||
</RadixTooltip.Portal>
|
||||
</RadixTooltip.Root>
|
||||
</RadixTooltip.Provider>
|
||||
)
|
||||
}
|
||||
9
components/button.js
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import { Button } from "@chakra-ui/react"
|
||||
|
||||
export default function MyButton(props) {
|
||||
return (
|
||||
<Button href="/about" color="green.500" _hover={{ color: "blue.500" }} align="center">
|
||||
About
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
44
components/formlabelinput.js
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
import { Box, Flex, FormLabel, Input, Spacer } from "@chakra-ui/react"
|
||||
import { Hide, Show } from "@chakra-ui/react"
|
||||
import React, { useEffect, useState } from "react"
|
||||
/**
|
||||
* Component used to generate a form input
|
||||
* props -
|
||||
* label
|
||||
* inputtype
|
||||
* placeholder
|
||||
* onChange = the handler function to get the changed data
|
||||
*/
|
||||
export default function FormLabelInput(props) {
|
||||
const [data, setData] = React.useState("")
|
||||
|
||||
const handlepassChange = (data) => {
|
||||
props.onChange(data)
|
||||
}
|
||||
useEffect(() => {
|
||||
handlepassChange(data)
|
||||
})
|
||||
return (
|
||||
<Flex direction={["column", "column", "row", "row"]} alignItems="center" marginY={5}>
|
||||
<Box flexBasis={0} flexGrow={1}>
|
||||
<Hide below="md">
|
||||
<FormLabel margin={"auto"} whiteSpace={"nowrap"} textAlign={"left"}>
|
||||
{props.label}
|
||||
</FormLabel>
|
||||
</Hide>
|
||||
</Box>
|
||||
|
||||
<Box flexBasis={0} flexGrow={1}>
|
||||
<Input
|
||||
type={props.inputtype}
|
||||
placeholder={props.placeholder}
|
||||
bgColor={"white"}
|
||||
value={data}
|
||||
onChange={(e) => {
|
||||
setData(e.target.value)
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
157
components/headbar.js
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
import { Box, Button, Flex, Link, Stack, Text } from "@chakra-ui/react"
|
||||
import { useDisclosure } from "@chakra-ui/react"
|
||||
import {
|
||||
Drawer,
|
||||
DrawerBody,
|
||||
DrawerCloseButton,
|
||||
DrawerContent,
|
||||
DrawerFooter,
|
||||
DrawerHeader,
|
||||
DrawerOverlay,
|
||||
Input,
|
||||
} from "@chakra-ui/react"
|
||||
import { Icon } from "@radix-ui/react-select"
|
||||
import React from "react"
|
||||
import Logo from "./logo"
|
||||
|
||||
function DrawerExample() {
|
||||
const { isOpen, onOpen, onClose } = useDisclosure()
|
||||
const btnRef = React.useRef()
|
||||
|
||||
return (
|
||||
<>
|
||||
<Button ref={btnRef} colorScheme="teal" onClick={onOpen}>
|
||||
<Icon />
|
||||
</Button>
|
||||
<Drawer isOpen={isOpen} placement="left" onClose={onClose} finalFocusRef={btnRef}>
|
||||
<DrawerOverlay />
|
||||
<DrawerContent>
|
||||
<DrawerCloseButton />
|
||||
<DrawerHeader>Create your account</DrawerHeader>
|
||||
|
||||
<DrawerBody>
|
||||
<Input placeholder="Type here..." />
|
||||
</DrawerBody>
|
||||
|
||||
<DrawerFooter>
|
||||
<Button variant="outline" mr={3} onClick={onClose}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button colorScheme="blue">Save</Button>
|
||||
</DrawerFooter>
|
||||
</DrawerContent>
|
||||
</Drawer>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const NavBar = (props) => {
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
|
||||
const toggle = () => setIsOpen(!isOpen)
|
||||
|
||||
return (
|
||||
<NavBarContainer {...props} h={["10%", "100%", "10%", "100%"]}>
|
||||
<DrawerExample />
|
||||
<LogoItem />
|
||||
<Button
|
||||
size="sm"
|
||||
rounded="md"
|
||||
color={["primary.500", "primary.500", "white", "white"]}
|
||||
bg={["white", "white", "primary.500", "primary.500"]}
|
||||
_hover={{
|
||||
bg: ["primary.100", "primary.100", "primary.600", "primary.600"],
|
||||
}}
|
||||
>
|
||||
Create Account
|
||||
</Button>
|
||||
<MenuToggle toggle={toggle} isOpen={isOpen} />
|
||||
<MenuLinks isOpen={isOpen} />
|
||||
</NavBarContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const CloseIcon = () => (
|
||||
<svg width="24" viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg">
|
||||
<title>Close</title>
|
||||
<path
|
||||
fill="white"
|
||||
d="M9.00023 7.58599L13.9502 2.63599L15.3642 4.04999L10.4142 8.99999L15.3642 13.95L13.9502 15.364L9.00023 10.414L4.05023 15.364L2.63623 13.95L7.58623 8.99999L2.63623 4.04999L4.05023 2.63599L9.00023 7.58599Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
||||
const MenuIcon = () => (
|
||||
<svg width="24px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg" fill="white">
|
||||
<title>Menu</title>
|
||||
<path d="M0 3h20v2H0V3zm0 6h20v2H0V9zm0 6h20v2H0v-2z" />
|
||||
</svg>
|
||||
)
|
||||
|
||||
const MenuToggle = ({ toggle, isOpen }) => {
|
||||
return (
|
||||
<Box display={{ base: "block", md: "none" }} onClick={toggle}>
|
||||
{isOpen ? <CloseIcon /> : <MenuIcon />}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const LogoItem = () => {
|
||||
return (
|
||||
<Link href={"/home"}>
|
||||
<Logo w="100px" color={["white", "white", "primary.500", "primary.500"]}></Logo>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
const MenuItem = ({ children, isLast, to = "/", ...rest }) => {
|
||||
return (
|
||||
<Link href={to}>
|
||||
<Text display="block" {...rest} align={"center"} paddingTop={["15px", "15px", "10px", "10px"]}>
|
||||
{children}
|
||||
</Text>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
const MenuLinks = ({ isOpen }) => {
|
||||
return (
|
||||
<Box display={{ base: isOpen ? "block" : "none", md: "block" }} flexBasis={{ base: "100%", md: "auto" }}>
|
||||
<Stack
|
||||
spacing={[6, 8, 10]}
|
||||
align="center"
|
||||
justify={["flex-end", "flex-end", "center", "center"]}
|
||||
direction={["column", "column", "row", "row"]}
|
||||
pt={[0, 0, 0, 0]}
|
||||
>
|
||||
<MenuItem to="/home">Home</MenuItem>
|
||||
<MenuItem to="/how">How It works </MenuItem>
|
||||
<MenuItem to="/pricing">Pricing </MenuItem>
|
||||
</Stack>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
|
||||
const NavBarContainer = ({ children, ...props }) => {
|
||||
return (
|
||||
<Flex
|
||||
as="nav"
|
||||
align="center"
|
||||
justify="space-between"
|
||||
wrap="wrap"
|
||||
w="100%"
|
||||
mb={8}
|
||||
p={[4, 6, 6, 6]}
|
||||
top={0}
|
||||
position="sticky"
|
||||
zIndex={10}
|
||||
bgGradient="linear(to-b, green.200, green.100)"
|
||||
color={["white", "white", "primary.700", "primary.700"]}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
</Flex>
|
||||
)
|
||||
}
|
||||
|
||||
export default NavBar
|
||||
14
components/layout.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import Head from "next/head"
|
||||
import Header from "../components/headbar"
|
||||
export default function Layout({ children }) {
|
||||
return (
|
||||
<>
|
||||
<Head>
|
||||
<title>Learning bee</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<Header />
|
||||
<main>{children}</main>
|
||||
</>
|
||||
)
|
||||
}
|
||||
252
components/logincomponent.js
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
Input,
|
||||
Link,
|
||||
Stack,
|
||||
} from "@chakra-ui/react"
|
||||
import { getAuth, isSignInWithEmailLink, sendSignInLinkToEmail, signInWithEmailLink } from "firebase/auth"
|
||||
import { useRouter } from "next/router"
|
||||
import React, { Component, useContext, useState } from "react"
|
||||
import FormLabelInput from "./formlabelinput"
|
||||
import { geturlFormdata } from "../constants"
|
||||
import { AuthContext } from "../context"
|
||||
|
||||
// import Login from "../googlelogin";
|
||||
|
||||
export default function Logincomponent(props) {
|
||||
const auth = getAuth()
|
||||
|
||||
//for emaillink auth
|
||||
const [email, setEmail] = useState("")
|
||||
const [linksent, setLinksent] = useState(false)
|
||||
|
||||
//for email password auth
|
||||
const [password, setPassword] = useState("")
|
||||
|
||||
const authContext = useContext(AuthContext)
|
||||
const router = useRouter()
|
||||
|
||||
const [isloaded, setIsloaded] = useState(false)
|
||||
const [emailselect, setEmailselect] = useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!isloaded) {
|
||||
setIsloaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
// function validateForm() {
|
||||
// return email.length > 0 && password.length > 0
|
||||
// }
|
||||
|
||||
// //for email password authentication
|
||||
// async function handleSubmit(event) {
|
||||
// event.preventDefault()
|
||||
|
||||
// var urlForm = geturlFormdata("user", "login")
|
||||
// console.log(email, password)
|
||||
// await getTokens(urlForm.url, email, password).then((value) => {
|
||||
// var idtype = null
|
||||
// if (email.includes("@")) {
|
||||
// idtype = "email"
|
||||
// } else {
|
||||
// idtype = "phone"
|
||||
// }
|
||||
|
||||
// // getuserdata(idtype , email).then((value)=>{
|
||||
// // router.push(`/home`)
|
||||
// // })
|
||||
// })
|
||||
// }
|
||||
|
||||
const onSignInSubmit = () => {}
|
||||
|
||||
const onEmailSubmit = () => {
|
||||
setEmailselect(true)
|
||||
}
|
||||
|
||||
/////////////////////////////
|
||||
//email link auth
|
||||
|
||||
const actionCodeSettings = {
|
||||
// URL you want to redirect back to. The domain (www.example.com) for this
|
||||
// URL must be in the authorized domains list in the Firebase Console.
|
||||
// url: `${siterooturl}login`,
|
||||
|
||||
url: `http://localhost:3000/login`,
|
||||
|
||||
// // This must be true.
|
||||
handleCodeInApp: true,
|
||||
// iOS: {
|
||||
// bundleId: 'com.example.ios'
|
||||
// },
|
||||
// android: {
|
||||
// packageName: 'com.example.android',
|
||||
// installApp: true,
|
||||
// minimumVersion: '12'
|
||||
// },
|
||||
// dynamicLinkDomain: 'example.page.link'
|
||||
}
|
||||
|
||||
const sighinWithCreds = async (email, password) => {
|
||||
const response = await fetch("/api/login", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({ email, password }),
|
||||
})
|
||||
console.log(response.ok)
|
||||
if (response.ok) {
|
||||
router.push("/home")
|
||||
} else {
|
||||
// Handle errors
|
||||
}
|
||||
}
|
||||
|
||||
const signinWithLink = async () => {
|
||||
console.log(auth, email, actionCodeSettings)
|
||||
await sendSignInLinkToEmail(auth, email, actionCodeSettings)
|
||||
.then(() => {
|
||||
// The link was successfully sent. Inform the user.
|
||||
// Save the email locally so you don't need to ask the user for it again
|
||||
// if they open the link on the same device.
|
||||
|
||||
setLinksent(true)
|
||||
|
||||
// ...
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
console.log("error sending link")
|
||||
const errorCode = error.code
|
||||
const errorMessage = error.message
|
||||
// ...
|
||||
})
|
||||
}
|
||||
|
||||
if (isloaded && isSignInWithEmailLink(auth, window.location.href)) {
|
||||
// Additional state parameters can also be passed via URL.
|
||||
// This can be used to continue the user's intended action before triggering
|
||||
// the sign-in operation.
|
||||
// Get the email if available. This should be available if the user completes
|
||||
// the flow on the same device where they started it.
|
||||
|
||||
console.log(email)
|
||||
if (!email) {
|
||||
// User opened the link on a different device. To prevent session fixation
|
||||
// attacks, ask the user to provide the associated email again. For example:
|
||||
// email = window.prompt('Please provide your email for confirmation');
|
||||
}
|
||||
// The client SDK will parse the code from the link for you.
|
||||
signInWithEmailLink(auth, email, window.location.href)
|
||||
.then((result) => {
|
||||
// Clear email from storage.
|
||||
console.log("logged in")
|
||||
|
||||
// result.user.providerData
|
||||
onSuccess(result)
|
||||
// window.localStorage.removeItem('emailForSignIn');
|
||||
|
||||
//router.push('/home')
|
||||
// You can access the new user via result.user
|
||||
|
||||
// Additional user info profile not available via:
|
||||
// result.additionalUserInfo.profile == null
|
||||
// You can check if the user is new or existing:
|
||||
// result.additionalUserInfo.isNewUser
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error)
|
||||
onFailure()
|
||||
// Some error occurred, you can inspect the code: error.code
|
||||
// Common errors could be invalid email and invalid or expired OTPs.
|
||||
})
|
||||
}
|
||||
|
||||
const onSuccess = (res) => {
|
||||
console.log(res)
|
||||
// verifyonServer(res._tokenResponse.idToken, "email", res.user.email, res)
|
||||
}
|
||||
|
||||
const onFailure = () => {
|
||||
alert(`Something went Wrong. Try again `)
|
||||
}
|
||||
const confirmCode = (code) => {
|
||||
// confirmationResult.confirm(code).then((result) => {
|
||||
// // User signed in successfully.
|
||||
// const user = result.user;
|
||||
// // ...
|
||||
// }).catch((error) => {
|
||||
// // User couldn't sign in (bad verification code?)
|
||||
// // ...
|
||||
// });
|
||||
}
|
||||
|
||||
const handleEmailChange = (val) => {
|
||||
setEmail(val)
|
||||
}
|
||||
const handlePasswordChange = (val) => {
|
||||
setPassword(val)
|
||||
}
|
||||
|
||||
console.log(password)
|
||||
return (
|
||||
<Box
|
||||
w={["80%", "60%", "40%"]}
|
||||
align="center"
|
||||
bgGradient="linear(to-b, blue.200, blue.500)"
|
||||
padding={10}
|
||||
borderRadius={10}
|
||||
margin="auto"
|
||||
>
|
||||
{!linksent && (
|
||||
<>
|
||||
<FormLabel margin={"auto"} padding={"auto"} textAlign={"center"}>
|
||||
Log in
|
||||
</FormLabel>
|
||||
<FormControl className={"form-group"}>
|
||||
<FormLabelInput
|
||||
inputtype="email"
|
||||
label="Email*"
|
||||
placeholder="abc@xyz.uvw"
|
||||
onChange={(e) => handleEmailChange(e)}
|
||||
/>
|
||||
|
||||
<FormLabelInput
|
||||
inputtype="password"
|
||||
label="Password"
|
||||
placeholder="type your password"
|
||||
onChange={(e) => handlePasswordChange(e)}
|
||||
/>
|
||||
</FormControl>
|
||||
<Button className="btn" onClick={() => sighinWithCreds(email, password)}>
|
||||
Log in
|
||||
</Button>
|
||||
|
||||
<p>--- or ---</p>
|
||||
<Button className="btn" onClick={signinWithLink}>
|
||||
Continue with signin link
|
||||
</Button>
|
||||
|
||||
{/* <p >Sign in with</p> */}
|
||||
{/* <Login /> */}
|
||||
</>
|
||||
)}
|
||||
|
||||
{linksent && (
|
||||
<>
|
||||
<h4>Signin Link sent</h4>
|
||||
<div className={"form-group"}>
|
||||
<div style={{ marginBlock: "10%" }}>Check your provided email , And Click on the Link to verify </div>
|
||||
<h3>WE are already waiting for you</h3>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
12
components/logo.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { Box, Text } from "@chakra-ui/react"
|
||||
import React from "react"
|
||||
|
||||
export default function Logo(props) {
|
||||
return (
|
||||
<Box {...props}>
|
||||
<Text fontSize="lg" fontWeight="bold">
|
||||
Logo
|
||||
</Text>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
39
components/menuitem.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { Card, CardBody, CardFooter, CardHeader } from "@chakra-ui/react"
|
||||
|
||||
import { Heading, Image, Stack, Text } from "@chakra-ui/react"
|
||||
import { useRouter } from "next/router"
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
|
||||
export default function MenuItem(props) {
|
||||
//define states here
|
||||
const [isloaded, setIsLoaded] = React.useState(false)
|
||||
|
||||
//router
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
if (!isloaded) {
|
||||
console.log("isloaded called")
|
||||
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
//function when clicked
|
||||
|
||||
const handleOnClick = (e) => {
|
||||
router.push(`/learningpath/${props.id}`)
|
||||
}
|
||||
|
||||
return (
|
||||
<Card onClick={handleOnClick}>
|
||||
<CardBody>
|
||||
<Image src={props.image_url} alt={props.image_desc} borderRadius="lg" />
|
||||
<Stack mt="6" spacing="3">
|
||||
<Heading size="md">{props.title}</Heading>
|
||||
<Text>{props.summary}</Text>
|
||||
</Stack>
|
||||
</CardBody>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
125
components/signupcomponent.js
Normal file
|
|
@ -0,0 +1,125 @@
|
|||
import {
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
FormControl,
|
||||
FormErrorMessage,
|
||||
FormHelperText,
|
||||
FormLabel,
|
||||
Input,
|
||||
Link,
|
||||
Stack,
|
||||
} from "@chakra-ui/react"
|
||||
import { useRouter } from "next/router"
|
||||
import React, { useState } from "react"
|
||||
import FormLabelInput from "./formlabelinput"
|
||||
import { geturlFormdata, siterooturl } from "../constants"
|
||||
|
||||
export default function Signupcomponent(props) {
|
||||
const [email, setEmail] = React.useState("")
|
||||
const [phone, setPhone] = React.useState("")
|
||||
const [password, setPassword] = React.useState("")
|
||||
const [confirm_password, setConfirmPassword] = React.useState("")
|
||||
const router = useRouter()
|
||||
|
||||
const [show, setShow] = useState(true)
|
||||
|
||||
const validateForm = () => {
|
||||
return email.length > 0 && (phone.length >= 10 || phone.length == 0) && password.length > 0
|
||||
}
|
||||
|
||||
const handleSubmit = (event) => {
|
||||
console.log(email, password, confirm_password, phone)
|
||||
if (phone.length > 0 && phone.length < 10) {
|
||||
//popup
|
||||
}
|
||||
event.preventDefault()
|
||||
var urlForm
|
||||
if (email.length == 0) {
|
||||
urlForm = geturlFormdata("user", "signup", {}, { phone: phone, password: password })
|
||||
} else if (phone.length == 0) {
|
||||
urlForm = geturlFormdata("user", "signup", {}, { mail: email, password: password })
|
||||
} else {
|
||||
urlForm = geturlFormdata("user", "signup", {}, { mail: email, phone: phone, password: password })
|
||||
}
|
||||
|
||||
if (password !== confirm_password) {
|
||||
setShow(true)
|
||||
return
|
||||
}
|
||||
try {
|
||||
// postsignup(urlForm.url, urlForm.formdata).then(() => {
|
||||
// router.push("/info")
|
||||
// })
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const handleEmailChange = (val) => {
|
||||
setEmail(val)
|
||||
}
|
||||
const handlePhoneChange = (val) => {
|
||||
setPhone(val)
|
||||
}
|
||||
const handlePasswordChange = (val) => {
|
||||
setPassword(val)
|
||||
}
|
||||
const handleConfirmPasswordChange = (val) => {
|
||||
setConfirmPassword(val)
|
||||
}
|
||||
|
||||
return (
|
||||
<Box
|
||||
w="50%"
|
||||
align="center"
|
||||
bgGradient="linear(to-b, blue.200, blue.500)"
|
||||
padding={10}
|
||||
borderRadius={10}
|
||||
margin="auto"
|
||||
>
|
||||
<FormControl onSubmit={handleSubmit} align="center">
|
||||
<FormLabel margin={"auto"} padding={"auto"} textAlign={"center"}>
|
||||
Register
|
||||
</FormLabel>
|
||||
|
||||
<FormLabelInput
|
||||
inputtype="email"
|
||||
label="Email*"
|
||||
placeholder="abc@xyz.uvw"
|
||||
onChange={(e) => handleEmailChange(e)}
|
||||
/>
|
||||
|
||||
<FormLabelInput
|
||||
inputtype="number"
|
||||
label="Phone"
|
||||
placeholder="XXXXXXXXXX"
|
||||
onChange={(e) => handlePhoneChange(e)}
|
||||
/>
|
||||
|
||||
<FormLabelInput
|
||||
inputtype="password"
|
||||
label="Password"
|
||||
placeholder="type your password"
|
||||
onChange={(e) => handlePasswordChange(e)}
|
||||
/>
|
||||
|
||||
<FormLabelInput
|
||||
inputtype="password"
|
||||
label="Confirm Password"
|
||||
placeholder="type your password again"
|
||||
onChange={(e) => handleConfirmPasswordChange(e)}
|
||||
/>
|
||||
|
||||
<Button type="submit" margin="auto" marginY="10px" disabled={!validateForm()} onClick={handleSubmit}>
|
||||
Register
|
||||
</Button>
|
||||
|
||||
<FormLabel margin={"auto"} align="center" textAlign={"center"}>
|
||||
Already registered?{" "}
|
||||
<Link href={`${siterooturl}login`} color="black.500" _hover={{ color: "blue.800", fontSize: "130%" }}>
|
||||
log in
|
||||
</Link>
|
||||
</FormLabel>
|
||||
</FormControl>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
36
constants.js
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
//here we can define all the things that we can keep in the cache database and remove it after some time
|
||||
|
||||
//getting the type of endpoint at one point
|
||||
export const geturlFormdata = (data, reqtype, queryoptions, formdataoptions) => {
|
||||
var rooturl = "https://api.smorentel.com/"
|
||||
var allurl = rooturl + `${data}/${reqtype}?`
|
||||
|
||||
var queryparam = new String()
|
||||
|
||||
for (const property in queryoptions) {
|
||||
queryparam += `${property}=${queryoptions[property]}&`
|
||||
}
|
||||
|
||||
var formdata = new FormData()
|
||||
for (const formproperty in formdataoptions) {
|
||||
formdata.set(formproperty, formdataoptions[formproperty])
|
||||
}
|
||||
|
||||
return { url: allurl + queryparam.slice(0, queryparam.length - 1), formdata: formdata }
|
||||
}
|
||||
|
||||
export const workstatus_dict = { 1: "progress", 2: "created", 3: "done" }
|
||||
|
||||
export const onRefresh = (authContext) => {
|
||||
try {
|
||||
if (typeof window !== "undefined") {
|
||||
if (localStorage.getItem("refresh_token") != undefined) {
|
||||
authContext.login()
|
||||
}
|
||||
} else {
|
||||
authContext.login()
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
}
|
||||
29
context.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import router from "next/router"
|
||||
import { createContext } from "react"
|
||||
import { getlocal, storelocal } from "./localstore"
|
||||
|
||||
export const AuthContext = createContext({
|
||||
isLoggedIn: true,
|
||||
accounttype: true,
|
||||
firebase: null,
|
||||
premium: false,
|
||||
modelopen: false,
|
||||
setModel: () => {},
|
||||
changeaccount: () => {
|
||||
accounttype = !accounttype
|
||||
storelocal("accounttype", accounttype)
|
||||
},
|
||||
checkType: async () => {
|
||||
var at = getlocal("accounttype")
|
||||
accounttype = at
|
||||
},
|
||||
login: () => {
|
||||
isLoggedIn = true
|
||||
},
|
||||
logout: () => {
|
||||
localStorage.removeItem("access_token")
|
||||
localStorage.removeItem("refresh_token")
|
||||
localStorage.removeItem("userdata")
|
||||
isLoggedIn = false
|
||||
},
|
||||
})
|
||||
33
datafetch/datafetch.js
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import useSWR from "swr"
|
||||
|
||||
const fetcher = (...args) => fetch(...args).then((res) => res.json())
|
||||
|
||||
export function menuitemfetcher(url) {
|
||||
const { data, error, isLoading } = useSWR("https://jsonplaceholder.typicode.com/posts", fetcher)
|
||||
|
||||
return {
|
||||
data: data,
|
||||
isLoading,
|
||||
isError: error,
|
||||
}
|
||||
}
|
||||
|
||||
export function learningitemfetcher(url) {
|
||||
const { data, error, isLoading } = useSWR("https://jsonplaceholder.typicode.com/posts", fetcher)
|
||||
|
||||
return {
|
||||
data: data,
|
||||
isLoading,
|
||||
isError: error,
|
||||
}
|
||||
}
|
||||
|
||||
export function learningpodfetcher() {
|
||||
const { data, error, isLoading } = useSWR("https://jsonplaceholder.typicode.com/posts", fetcher)
|
||||
|
||||
return {
|
||||
data: data,
|
||||
isLoading,
|
||||
isError: error,
|
||||
}
|
||||
}
|
||||
28
docker-compose.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
version: '3.8'
|
||||
|
||||
services:
|
||||
nextjs:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.nextjs
|
||||
restart: always
|
||||
|
||||
pm2:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.pm2
|
||||
volumes:
|
||||
- ./pm2.config.js:/pm2/pm2.config.js
|
||||
restart: always
|
||||
depends_on:
|
||||
- nextjs
|
||||
|
||||
nginx:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
restart: always
|
||||
depends_on:
|
||||
- pm2
|
||||
51
localstore.js
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
export const storeobjlocal = (key, value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
}
|
||||
|
||||
export const getobjlocal = (key) => {
|
||||
return JSON.parse(localStorage.getItem(key))
|
||||
}
|
||||
|
||||
export const storelocal = (key, value) => {
|
||||
localStorage.setItem(key, value)
|
||||
}
|
||||
|
||||
export const getlocal = (key) => {
|
||||
return localStorage.getItem(key)
|
||||
}
|
||||
|
||||
export const localkeys = {
|
||||
access_token: "",
|
||||
refresh_token: "",
|
||||
user: { customer: {}, worker: {} },
|
||||
settings: {},
|
||||
}
|
||||
|
||||
export const stateobj = {
|
||||
user: {},
|
||||
}
|
||||
|
||||
/**
|
||||
* what are the filter options -
|
||||
* 1 place ==> filter_place
|
||||
* 2 price range
|
||||
* 3 location
|
||||
* 4 distance
|
||||
* 5 tags
|
||||
* 6 categories
|
||||
* 7 sub categories
|
||||
*/
|
||||
|
||||
/**
|
||||
* user infos -
|
||||
* 1 access_token
|
||||
* 2 refresh_token
|
||||
* 3 user_data -> json -> (user_key) ->
|
||||
*/
|
||||
|
||||
/**
|
||||
* single page data -
|
||||
* items
|
||||
* bookings
|
||||
* bookingreqs
|
||||
*/
|
||||
73
middleware.js
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
import { NextRequest, NextResponse } from "next/server"
|
||||
import { refreshAccessToken } from "./utils/auth"
|
||||
import { jwtVerify } from "jose"
|
||||
const secretKey = new TextEncoder().encode("Swe4g7c?UBm5Nrd96vhsVDtkyJFbqKMTm!TMw5BDRLtaCFAXNvbq?s4rGKQSZnUP")
|
||||
|
||||
const verifyJWT = async (token) => {
|
||||
const { payload, protectedHeader } = await jwtVerify(token, secretKey)
|
||||
return payload
|
||||
}
|
||||
|
||||
export default async function middleware(req, res) {
|
||||
// Extract token from request headers or cookies or wherever it's stored
|
||||
const token = req.cookies.get("token")
|
||||
|
||||
if (!token || token.value == "") {
|
||||
if (req.url == "http://localhost:3000/login") {
|
||||
} else {
|
||||
return NextResponse.redirect(new URL("/login", req.url))
|
||||
}
|
||||
} else {
|
||||
if (req.url == "http://localhost:3000/login") {
|
||||
return NextResponse.redirect("http://localhost:3000/home")
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Verify the token
|
||||
console.log("ASdasdasssssssssssss")
|
||||
console.log(token.value)
|
||||
const payload = await verifyJWT(token.value)
|
||||
|
||||
// Attach the decoded user object to the request
|
||||
// req.user = decoded.user;
|
||||
// return NextResponse.redirect(new URL( req.url, '/home'))
|
||||
// Validate if the user is the one that is present in cookie
|
||||
} catch (error) {
|
||||
if (error.name === "JWTExpired") {
|
||||
console.log(error)
|
||||
//delete the token from cookie
|
||||
console.log("deleting the token")
|
||||
//renew the token
|
||||
// console.log(req.cookies.get('rtoken'));
|
||||
|
||||
const rtoken = req.cookies.get("rtoken")
|
||||
if (rtoken == undefined) {
|
||||
console.log("undefined")
|
||||
}
|
||||
console.log("defined")
|
||||
|
||||
const newtoken = await refreshAccessToken(rtoken)
|
||||
if (newtoken == undefined) {
|
||||
const res = NextResponse.redirect(new URL("/login", req.url)) // creates an actual instance
|
||||
res.cookies.set("token", newtoken)
|
||||
return res
|
||||
}
|
||||
console.log("defined")
|
||||
const res = NextResponse.next() // creates an actual instance
|
||||
res.cookies.set("token", newtoken)
|
||||
return res
|
||||
}
|
||||
console.log(error)
|
||||
// Handle other JWT errors
|
||||
// return Response.json({ message: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
// const res = NextResponse.redirect(new URL( '/login',req.url)) // creates an actual instance
|
||||
// res.cookies.delete('token') // can be called on an instance
|
||||
// return res
|
||||
}
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import { NextResponse } from "next/server"
|
||||
import type { NextRequest } from "next/server"
|
||||
|
||||
export function middleware(request: NextRequest) {
|
||||
// TODO: Feel free to remove this block
|
||||
if (request.headers?.get("host")?.includes("next-enterprise.vercel.app")) {
|
||||
return NextResponse.redirect("https://blazity.com/open-source/nextjs-enterprise-boilerplate", { status: 301 })
|
||||
}
|
||||
}
|
||||
|
||||
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).*)",
|
||||
],
|
||||
}
|
||||
|
|
@ -2,9 +2,12 @@ import withBundleAnalyzer from "@next/bundle-analyzer"
|
|||
import withPlugins from "next-compose-plugins"
|
||||
import { env } from "./env.mjs"
|
||||
|
||||
|
||||
/**
|
||||
* @type {import('next').NextConfig}
|
||||
*/
|
||||
|
||||
|
||||
const config = withPlugins([[withBundleAnalyzer({ enabled: env.ANALYZE })]], {
|
||||
reactStrictMode: true,
|
||||
experimental: { instrumentationHook: true },
|
||||
14
next.config.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// next.config.js
|
||||
module.exports = {
|
||||
// ... rest of the configuration.
|
||||
output: "standalone",
|
||||
async redirects() {
|
||||
return [
|
||||
{
|
||||
source: "/",
|
||||
destination: "/home",
|
||||
permanent: true,
|
||||
},
|
||||
]
|
||||
},
|
||||
}
|
||||
39577
package-lock.json
generated
Normal file
18
package.json
|
|
@ -23,6 +23,12 @@
|
|||
"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": {
|
||||
"@auth/core": "^0.26.2",
|
||||
"@chakra-ui/next-js": "^2.2.0",
|
||||
"@chakra-ui/react": "^2.8.2",
|
||||
"@emotion/react": "^11.11.3",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
"@headlessui/react": "^1.7.18",
|
||||
"@next/bundle-analyzer": "^14.0.3",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
|
|
@ -47,13 +53,25 @@
|
|||
"@semantic-release/release-notes-generator": "^12.1.0",
|
||||
"@t3-oss/env-nextjs": "^0.7.1",
|
||||
"@vercel/otel": "^0.3.0",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"class-variance-authority": "^0.7.0",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"firebase": "^10.8.0",
|
||||
"framer-motion": "^11.0.3",
|
||||
"jose": "^5.2.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"melonjs": "^16.1.1",
|
||||
"next": "^14.0.3",
|
||||
"next-auth": "^4.24.5",
|
||||
"next-compose-plugins": "^2.2.1",
|
||||
"react": "^18.2.0",
|
||||
"react-bootstrap": "^2.10.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"swr": "^2.2.4",
|
||||
"tailwind-merge": "^2.0.0",
|
||||
"webcrypto": "^0.1.1",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
|||
102
pages/_app.js
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
import { initializeApp } from "firebase/app"
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
|
||||
//Auth imports
|
||||
import { AuthContext } from "../context"
|
||||
import "firebase/auth"
|
||||
import { getAuth } from "firebase/auth"
|
||||
|
||||
//server side rendring things
|
||||
import { SSRProvider } from "react-bootstrap"
|
||||
|
||||
import { ChakraProvider } from "@chakra-ui/react"
|
||||
import customTheme from "../utils/themes"
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
/**Initailize the states
|
||||
* Important for keeping data over pages
|
||||
*/
|
||||
const [modelopen, setModelopen] = React.useState(false)
|
||||
const [loggedIn, setLoggedIn] = React.useState(false)
|
||||
const [acctype, setAcctype] = React.useState(false)
|
||||
const [loaded, setLoaded] = React.useState(false)
|
||||
|
||||
///**This place is for firebase Auth */
|
||||
|
||||
//initialize firebase
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyARcLufTeUZbGue0-k9iZJVxmKlp0l0HQU",
|
||||
authDomain: "freebees-24743.firebaseapp.com",
|
||||
projectId: "freebees-24743",
|
||||
storageBucket: "freebees-24743.appspot.com",
|
||||
messagingSenderId: "1084271040061",
|
||||
appId: "1:1084271040061:web:91df1dc11b58f5a4807c54",
|
||||
measurementId: "G-6Z61GFCN05",
|
||||
}
|
||||
|
||||
const app = initializeApp(firebaseConfig)
|
||||
|
||||
const auth = getAuth()
|
||||
|
||||
const setModel = (data) => {
|
||||
setModelopen(data)
|
||||
}
|
||||
|
||||
const login = () => {
|
||||
setLoggedIn(true)
|
||||
}
|
||||
|
||||
const logout = () => {
|
||||
setLoggedIn(false)
|
||||
auth.signOut().then(() => {
|
||||
console.log("successfully signed out")
|
||||
})
|
||||
localStorage.clear()
|
||||
}
|
||||
|
||||
const checkType = () => {
|
||||
console.log("check")
|
||||
if (typeof window != undefined) {
|
||||
}
|
||||
}
|
||||
|
||||
/////////
|
||||
|
||||
/**
|
||||
* useEffect has been uesd to take actions on the first rendering
|
||||
*/
|
||||
useEffect(() => {
|
||||
const app = initializeApp(firebaseConfig)
|
||||
|
||||
if (!loaded) {
|
||||
console.log("1st check", firebaseConfig)
|
||||
|
||||
setLoaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
///// Manual test with console
|
||||
const getLayout = Component.getLayout ?? ((page) => page)
|
||||
return (
|
||||
<SSRProvider>
|
||||
<ChakraProvider theme={customTheme}>
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
isLoggedIn: loggedIn,
|
||||
firebase: app,
|
||||
login: login,
|
||||
logout: logout,
|
||||
accounttype: acctype,
|
||||
modelopen: modelopen,
|
||||
setModel: setModel,
|
||||
checkType: checkType,
|
||||
}}
|
||||
>
|
||||
{getLayout(<Component {...pageProps} />)}
|
||||
</AuthContext.Provider>
|
||||
</ChakraProvider>
|
||||
</SSRProvider>
|
||||
)
|
||||
}
|
||||
|
||||
export default MyApp
|
||||
38
pages/api/login.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
import { createTokens, hashPassword, verifyPassword } from "../../utils/auth"
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method Not Allowed" })
|
||||
}
|
||||
|
||||
const { email, password } = req.body
|
||||
console.log(password)
|
||||
|
||||
// You'd need to fetch user details from your database or any data source
|
||||
//fetch data from postgres database where we store password as hash
|
||||
|
||||
// Here, for demonstration, let's assume a hardcoded user
|
||||
const user = {
|
||||
id: 1,
|
||||
email: "user@example.com",
|
||||
passwordHash: "$2a$10$Zy/TYg6WwOOctCQ6e/KOXebvH3AliSDjdopPxrIUWELVJBf/KE.iq", // hashed password: "password123"
|
||||
}
|
||||
|
||||
const isValid = await verifyPassword(password, user.passwordHash)
|
||||
|
||||
if (!isValid) {
|
||||
return res.status(401).json({ message: "Invalid credentials" })
|
||||
}
|
||||
|
||||
const token = await createTokens(user.id)
|
||||
|
||||
res.setHeader("Set-Cookie", [
|
||||
`token=${token.accessToken}; Path=/; HttpOnly`,
|
||||
`rtoken=${token.refreshToken}; Path=/; HttpOnly`,
|
||||
])
|
||||
|
||||
return res.status(200).json({ token })
|
||||
}
|
||||
|
||||
//user@example.com
|
||||
//password123
|
||||
7
pages/api/logout.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export default async function handler(req, res) {
|
||||
// if (req.method === 'POST') {
|
||||
// cookies().delete('token');
|
||||
// } else {
|
||||
// throw new NotFoundError();
|
||||
// }
|
||||
}
|
||||
35
pages/api/refreshtoken.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { issueAccessToken, verifyRefreshToken } from "../../utils/auth"
|
||||
|
||||
// Example middleware to attach access token to outgoing requests
|
||||
async function attachAccessToken(req, res, next) {
|
||||
const accessToken = req.cookies.accessToken
|
||||
if (!accessToken) {
|
||||
return res.status(401).json({ message: "Access token missing" })
|
||||
}
|
||||
|
||||
// You might want to check token expiration here and refresh if necessary
|
||||
|
||||
req.headers.Authorization = `Bearer ${accessToken}`
|
||||
next()
|
||||
}
|
||||
|
||||
export default async function handler(req, res) {
|
||||
if (req.method !== "POST") {
|
||||
return res.status(405).json({ message: "Method Not Allowed" })
|
||||
}
|
||||
|
||||
const { refreshToken } = req.body
|
||||
|
||||
try {
|
||||
const decoded = await verifyRefreshToken(refreshToken.value)
|
||||
// Check if refresh token is valid, then issue a new access token
|
||||
const accessToken = await issueAccessToken(decoded.userId)
|
||||
console.log("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")
|
||||
res.setHeader("Set-Cookie", [`token=${accessToken}; Path=/; HttpOnly`])
|
||||
|
||||
return res.status(200).json({ accessToken })
|
||||
} catch (error) {
|
||||
console.error("Error refreshing token:", error)
|
||||
return res.status(401).json({ message: "Invalid refresh token" })
|
||||
}
|
||||
}
|
||||
35
pages/content.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import Head from "next/head"
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
import MyButton from "../components/button"
|
||||
import Header from "../components/headbar"
|
||||
import Logincomponent from "../components/logincomponent"
|
||||
import Signupcomponent from "../components/signupcomponent"
|
||||
|
||||
export default function Content(props) {
|
||||
const [isloaded, setIsLoaded] = React.useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isloaded) {
|
||||
console.log("isloaded called")
|
||||
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**This place should be used to get any information from the server side
|
||||
* example : we need to get the session token
|
||||
* or any thing related to the page */
|
||||
export async function getServerSideProps() {
|
||||
const data = await fetch("https://jsonplaceholder.typicode.com/todos").then((response) => {
|
||||
response.json()
|
||||
})
|
||||
|
||||
return { props: { title: "rahul" } }
|
||||
}
|
||||
37
pages/home.js
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
import { useRouter } from "next/router"
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
export default function Home(props) {
|
||||
const [isloaded, setIsLoaded] = React.useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isloaded) {
|
||||
console.log("isloaded called")
|
||||
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
return (
|
||||
<>
|
||||
<button onClick={() => router.push("/menu")}>Click here to read menu</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**This place should be used to get any information from the server side
|
||||
* example : we need to get the session token
|
||||
* or any thing related to the page */
|
||||
export async function getServerSideProps() {
|
||||
const data = await fetch("https://jsonplaceholder.typicode.com/todos").then((response) => {
|
||||
response.json()
|
||||
})
|
||||
|
||||
return { props: { title: "rahul" } }
|
||||
}
|
||||
|
||||
Home.getLayout = function getLayout(page) {
|
||||
return <Layout>{page}</Layout>
|
||||
}
|
||||
30
pages/index.js
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { Box, Center, Flex, Heading, Text } from "@chakra-ui/react"
|
||||
import React, { useEffect } from "react"
|
||||
|
||||
function App() {
|
||||
const [isloaded, setIsLoaded] = React.useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isloaded) {
|
||||
console.log("isloaded called")
|
||||
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<Center bg="gray.200" height="100vh">
|
||||
<Box textAlign="center">
|
||||
<Heading size="4xl" color={"blue.500"}>
|
||||
Coming Soon
|
||||
</Heading>
|
||||
<Text fontSize="2xl" mt="4">
|
||||
⚠ We are working on it! ⚠
|
||||
</Text>
|
||||
<Flex justifyContent="center" mt="8"></Flex>
|
||||
</Box>
|
||||
</Center>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
28
pages/learningpage/[page].js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import * as me from "melonjs"
|
||||
import { useRouter } from "next/router"
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
|
||||
import Layout from "../../components/layout"
|
||||
|
||||
export default function Page(props) {
|
||||
const [isloaded, setIsLoaded] = React.useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isloaded) {
|
||||
console.log("isloaded called")
|
||||
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
const router = useRouter()
|
||||
|
||||
return <></>
|
||||
}
|
||||
|
||||
/**This place should be used to get any information from the server side
|
||||
* example : we need to get the session token
|
||||
* or any thing related to the page */
|
||||
|
||||
Page.getLayout = function getLayout(page) {
|
||||
return <Layout>{page}</Layout>
|
||||
}
|
||||
77
pages/learningpath/[learningpath].js
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { Button, Error, Grid, Spinner } from "@chakra-ui/react"
|
||||
import { useRouter } from "next/router"
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
import Layout from "../../components/layout"
|
||||
|
||||
import { learningitemfetcher } from "../../datafetch/datafetch"
|
||||
|
||||
/**
|
||||
* params -
|
||||
* length of path - plength ( the number of tasks )
|
||||
* structure of the path - DAG
|
||||
* type of path to be formed - pathtype
|
||||
*/
|
||||
|
||||
function Menubox(props) {
|
||||
const { data, isLoading, isError } = learningitemfetcher()
|
||||
const [showedcount, setShowedcount] = React.useState(40)
|
||||
|
||||
const router = useRouter()
|
||||
if (isLoading) return <Spinner />
|
||||
if (isError) return <Error />
|
||||
|
||||
const container = (
|
||||
<>
|
||||
<Grid templateColumns="repeat(10, 1fr)" padding="70" gap={10}>
|
||||
{data.slice(0, showedcount).map((item) => {
|
||||
if ([1, 22, 3, 24, 5, 26].includes(item.id)) {
|
||||
return (
|
||||
<div
|
||||
key={item.id}
|
||||
style={{ backgroundColor: "red", height: 100 }}
|
||||
onClick={() => {
|
||||
router.push(`/learningpage/${item.id}`)
|
||||
}}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
return <div key={100} style={{ backgroundColor: "white", height: 100 }}></div>
|
||||
})}
|
||||
</Grid>
|
||||
</>
|
||||
)
|
||||
return container
|
||||
}
|
||||
|
||||
export default function LearningPath(props) {
|
||||
const [isloaded, setIsLoaded] = React.useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isloaded) {
|
||||
console.log("isloaded called")
|
||||
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
<>
|
||||
<Menubox />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**This place should be used to get any information from the server side
|
||||
* example : we need to get the session token
|
||||
* or any thing related to the page */
|
||||
export async function getServerSideProps() {
|
||||
const data = await fetch("https://jsonplaceholder.typicode.com/todos").then((response) => {
|
||||
response.json()
|
||||
})
|
||||
|
||||
return { props: { title: "rahul" } }
|
||||
}
|
||||
|
||||
LearningPath.getLayout = function getLayout(page) {
|
||||
return <Layout>{page}</Layout>
|
||||
}
|
||||
41
pages/login.js
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
import { useRouter } from "next/router"
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
import Logincomponent from "../components/logincomponent"
|
||||
import Signupcomponent from "../components/signupcomponent"
|
||||
|
||||
export default function Login(props) {
|
||||
const [isloaded, setIsLoaded] = React.useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isloaded) {
|
||||
console.log("isloaded called")
|
||||
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
const router = useRouter()
|
||||
return (
|
||||
<>
|
||||
<Logincomponent />
|
||||
<button onClick={() => router.push("/menu")}>Click here to read menu</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
/**This place should be used to get any information from the server side
|
||||
* example : we need to get the session token
|
||||
* or any thing related to the page */
|
||||
export async function getServerSideProps() {
|
||||
const data = await fetch("https://jsonplaceholder.typicode.com/todos").then((response) => {
|
||||
response.json()
|
||||
})
|
||||
|
||||
return { props: { title: "rahul" } }
|
||||
}
|
||||
|
||||
Login.getLayout = function getLayout(page) {
|
||||
return <Layout>{page}</Layout>
|
||||
}
|
||||
67
pages/menu.js
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { Button, Error, Grid, Spinner } from "@chakra-ui/react"
|
||||
import React, { useContext, useEffect, useState } from "react"
|
||||
import Layout from "../components/layout"
|
||||
|
||||
import MenuItem from "../components/menuitem"
|
||||
|
||||
import { menuitemfetcher } from "../datafetch/datafetch"
|
||||
|
||||
function Menubox(props) {
|
||||
const { data, isLoading, isError } = menuitemfetcher()
|
||||
const [showedcount, setShowedcount] = React.useState(40)
|
||||
|
||||
if (isLoading) return <Spinner />
|
||||
if (isError) return <Error />
|
||||
|
||||
const container = (
|
||||
<>
|
||||
<Grid
|
||||
templateColumns={["repeat(1, 1fr)", "repeat(1, 1fr)", "repeat(2, 1fr)", "repeat(3, 1fr)", "repeat(4, 1fr)"]}
|
||||
padding={["4%", "5%", "6%", "7%"]}
|
||||
gap={6}
|
||||
>
|
||||
{data.slice(1, showedcount).map((item) => (
|
||||
<MenuItem
|
||||
key={item.id}
|
||||
id={item.id}
|
||||
itemid={item.userId}
|
||||
title={item.title}
|
||||
summary={item.body}
|
||||
image_url="https://example.com/image1.jpg"
|
||||
/>
|
||||
))}
|
||||
</Grid>
|
||||
<Button onClick={() => setShowedcount(showedcount + 40)}></Button>
|
||||
</>
|
||||
)
|
||||
return container
|
||||
}
|
||||
|
||||
export default function MenuPage(props) {
|
||||
const [isloaded, setIsLoaded] = React.useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (!isloaded) {
|
||||
console.log("isloaded called")
|
||||
|
||||
setIsLoaded(true)
|
||||
}
|
||||
})
|
||||
|
||||
return <Menubox />
|
||||
}
|
||||
|
||||
/**This place should be used to get any information from the server side
|
||||
* example : we need to get the session token
|
||||
* or any thing related to the page */
|
||||
export async function getServerSideProps() {
|
||||
const data = await fetch("https://jsonplaceholder.typicode.com/todos").then((response) => {
|
||||
response.json()
|
||||
})
|
||||
|
||||
return { props: { title: "rahul" } }
|
||||
}
|
||||
|
||||
MenuPage.getLayout = function getLayout(page) {
|
||||
return <Layout>{page}</Layout>
|
||||
}
|
||||
17
pm2.config.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = {
|
||||
apps: [
|
||||
{
|
||||
name: "nextjs-app",
|
||||
script: "npm",
|
||||
args: "start",
|
||||
instances: "max", // or specify the number of instances you want
|
||||
autorestart: true,
|
||||
watch: false,
|
||||
max_memory_restart: "1G",
|
||||
env: {
|
||||
NODE_ENV: "production",
|
||||
},
|
||||
},
|
||||
// You can add more apps as needed for other services you want to manage with PM2
|
||||
],
|
||||
}
|
||||
BIN
public/SMOR-192.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
public/SMOR-256.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/SMOR-384.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/SMOR-512.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |
62
public/firebase-messaging-sw.js
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
// Give the service worker access to Firebase Messaging.
|
||||
// Note that you can only use Firebase Messaging here. Other Firebase libraries
|
||||
// are not available in the service worker.
|
||||
importScripts("https://www.gstatic.com/firebasejs/9.2.0/firebase-app-compat.js")
|
||||
importScripts("https://www.gstatic.com/firebasejs/9.2.0/firebase-messaging-compat.js")
|
||||
|
||||
// Initialize the Firebase app in the service worker by passing in
|
||||
// your app's Firebase config object.
|
||||
// https://firebase.google.com/docs/web/setup#config-object
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIzaSyARcLufTeUZbGue0-k9iZJVxmKlp0l0HQU",
|
||||
authDomain: "freebees-24743.firebaseapp.com",
|
||||
projectId: "freebees-24743",
|
||||
storageBucket: "freebees-24743.appspot.com",
|
||||
messagingSenderId: "1084271040061",
|
||||
appId: "1:1084271040061:web:91df1dc11b58f5a4807c54",
|
||||
measurementId: "G-6Z61GFCN05",
|
||||
}
|
||||
|
||||
// Retrieve an instance of Firebase Messaging so that it can handle background
|
||||
// messages.
|
||||
|
||||
const app = firebase.initializeApp(firebaseConfig)
|
||||
const messaging = firebase.messaging(app)
|
||||
|
||||
// If you would like to customize notifications that are received in the
|
||||
// background (Web app is closed or not in browser focus) then you should
|
||||
// implement this optional method.
|
||||
// Keep in mind that FCM will still show notification messages automatically
|
||||
// and you should use data messages for custom notifications.
|
||||
// For more info see:
|
||||
// https://firebase.google.com/docs/cloud-messaging/concept-options
|
||||
|
||||
messaging.onBackgroundMessage((payload) => {
|
||||
console.log("[firebase-messaging-sw.js] Received background message ", payload)
|
||||
// Customize notification here
|
||||
const Title = payload.data.title
|
||||
const Options = {
|
||||
body: payload.data.body,
|
||||
icon: "/images/SMOR-192.png",
|
||||
}
|
||||
|
||||
self.registration.showNotification(Title, Options)
|
||||
})
|
||||
|
||||
self.addEventListener("notificationclick", function (event) {
|
||||
event.notification.close()
|
||||
event.waitUntil(
|
||||
clients.matchAll({ type: "window", includeUncontrolled: true }).then(function (clientList) {
|
||||
if (clientList.length > 0) {
|
||||
let client = clientList[0]
|
||||
for (let i = 0; i < clientList.length; i++) {
|
||||
if (clientList[i].focused) {
|
||||
client = clientList[i]
|
||||
}
|
||||
}
|
||||
return client.focus()
|
||||
}
|
||||
return clients.openWindow("/")
|
||||
})
|
||||
)
|
||||
})
|
||||
BIN
public/freebessimagelight.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
public/icon-192x192.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/icon-256x256.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
BIN
public/icon-384x384.png
Normal file
|
After Width: | Height: | Size: 75 KiB |
BIN
public/icon-512x512.png
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
public/images/SMOR-192.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
public/images/SMOR-256.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
public/images/SMOR-384.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
public/images/SMOR-512.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/images/no-image.png
Normal file
|
After Width: | Height: | Size: 2.6 KiB |
4
public/images/vercel.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
30
public/manifest.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"name": "PWA App",
|
||||
"short_name": "App",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any maskable"
|
||||
},
|
||||
{
|
||||
"src": "/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"gcm_sender_id": "1084271040061",
|
||||
"theme_color": "#007",
|
||||
"background_color": "#007",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait"
|
||||
|
||||
}
|
||||
|
||||
BIN
public/marker-icon.png
Normal file
|
After Width: | Height: | Size: 8 KiB |
267
public/sw.js
Normal file
|
|
@ -0,0 +1,267 @@
|
|||
if (!self.define) {
|
||||
let e,
|
||||
s = {}
|
||||
const t = (t, c) => (
|
||||
(t = new URL(t + ".js", c).href),
|
||||
s[t] ||
|
||||
new Promise((s) => {
|
||||
if ("document" in self) {
|
||||
const e = document.createElement("script")
|
||||
;(e.src = t), (e.onload = s), document.head.appendChild(e)
|
||||
} else (e = t), importScripts(t), s()
|
||||
}).then(() => {
|
||||
let e = s[t]
|
||||
if (!e) throw new Error(`Module ${t} didn’t register its module`)
|
||||
return e
|
||||
})
|
||||
)
|
||||
self.define = (c, n) => {
|
||||
const i = e || ("document" in self ? document.currentScript.src : "") || location.href
|
||||
if (s[i]) return
|
||||
let a = {}
|
||||
const l = (e) => t(e, i),
|
||||
u = { module: { uri: i }, exports: a, require: l }
|
||||
s[i] = Promise.all(c.map((e) => u[e] || l(e))).then((e) => (n(...e), a))
|
||||
}
|
||||
}
|
||||
define(["./workbox-1846d813"], function (e) {
|
||||
"use strict"
|
||||
importScripts("worker-WcZyltu4AIEZdlepY3Peo.js"),
|
||||
self.skipWaiting(),
|
||||
e.clientsClaim(),
|
||||
e.precacheAndRoute(
|
||||
[
|
||||
{ url: "/SMOR-192.png", revision: "694a5d68bb01b831f6c9b5c7d43f0c27" },
|
||||
{ url: "/SMOR-256.png", revision: "93baa0d7792e6776ccfbb917ed8fb1e8" },
|
||||
{ url: "/SMOR-384.png", revision: "c0ef024f0903ba73d308e2dee7889bf9" },
|
||||
{ url: "/SMOR-512.png", revision: "243de524262c12c7e00403a4b5dc14c5" },
|
||||
{ url: "/_next/static/WcZyltu4AIEZdlepY3Peo/_buildManifest.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/WcZyltu4AIEZdlepY3Peo/_middlewareManifest.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/WcZyltu4AIEZdlepY3Peo/_ssgManifest.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/0b7b90cd.23a679f790af5ada.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/1120-4b6c01efbb660cb0.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/1443-c9911baf6aa6969d.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/1857-4a754531d0dc3ce7.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/1944-1172cffe798b2d2d.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/1bfc9850-a5e52aab60a2bf09.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/2022-0b1731e5d5e56391.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/252f366e-25667f96c249ca01.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/3516-4d6afd5e3449cb6f.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/3761-100286903250d54e.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/4067.cc92d8e1edb00a0b.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/4321-45e31c436c8cb7a8.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/4355-471e0659b91aaa0f.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/4356-91fe2cc212fb70f3.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/5302-b90e4a54f702b9b8.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/5365-49d12afab2725ba2.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/5575-2326a978162761c3.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/5979-6f2587baf37a9775.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/6046-38951b11790c6f85.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/6220-3632eefa25b908ea.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/6602-15be9cec14ac902e.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/7005-d08d38268c11f695.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/7019-594327e28c484778.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/7164.b91874f80a184dd3.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/7238-8610a6af415f8caf.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/7259-0a5f5ceb4cbcd248.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/7411-111c6e401cb97781.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/8423-fc8e044710f19317.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/8608-e397dfae632fb839.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/9651.5057209db3881ec8.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/framework-428dc724d9086efb.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/main-04e4151d30f71ab2.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/_app-d8b8016d7bea506b.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/_error-0b1a861ffa2edde1.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/address-b7eca4e9b569fd1c.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/bookingreqpage-e8e1a6f68f8bb6c4.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/dashboard-1bdf95290ba665cd.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/home-6ee91d9bf555a563.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/index-ffda4f32514d6aa0.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/info-5a0660f1bea67378.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/itempage-69d6598aef5ec5da.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{
|
||||
url: "/_next/static/chunks/pages/itempage/%5Bitemkey%5D-c2f6ebad6a59057d.js",
|
||||
revision: "WcZyltu4AIEZdlepY3Peo",
|
||||
},
|
||||
{
|
||||
url: "/_next/static/chunks/pages/items/%5Buser_lender%5D-33ac1a4b581b143a.js",
|
||||
revision: "WcZyltu4AIEZdlepY3Peo",
|
||||
},
|
||||
{ url: "/_next/static/chunks/pages/itemswindow-706741772e4ee171.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/login-df75183f3fdaf6f4.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/mappage-95b6b631665d0b5d.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/newItem-76961819cc2fa4f2.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/orders-5637e34fa4aeb00c.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/privacypolicy-fea0a9e36d408ce0.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/profile-d750e0bec23d4699.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/searchpage-a20e60074cffe8c4.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/settings-fdcc39f57cd60718.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/signup-56863616abb5769b.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/splash-4020a016f3a18b60.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/taskpage-c2cea2257c510db0.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/welcome-b00b5fc8b217a709.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/wishlist-d8d72984251367c7.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/pages/workpage-dd025f82110469c5.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/polyfills-5cd94c89d3acac5f.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/chunks/webpack-bde62137e53dadca.js", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/css/4ac61f15fc19c268.css", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/css/6c1a23dd8a183dbb.css", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/media/layers-2x.9859cd12.png", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/media/layers.ef6db872.png", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/_next/static/media/marker-icon.d577052a.png", revision: "WcZyltu4AIEZdlepY3Peo" },
|
||||
{ url: "/favicon.ico", revision: "666586677e60428aa5e58d13871736c7" },
|
||||
{ url: "/firebase-messaging-sw.js", revision: "e387921962396472d67f290af1777d55" },
|
||||
{ url: "/freebessimagelight.png", revision: "7e742e758c9028e8bd0c4345655e93c4" },
|
||||
{ url: "/icon-192x192.png", revision: "ed26a1bbca33bba35e0e25bccec594f1" },
|
||||
{ url: "/icon-256x256.png", revision: "ed2c6547ed3aeea9df44fe44bc30007f" },
|
||||
{ url: "/icon-384x384.png", revision: "22a4a4214cd9cfa1b401091618ca0ac9" },
|
||||
{ url: "/icon-512x512.png", revision: "562865edb386df86d7c72587c9096528" },
|
||||
{ url: "/images/SMOR-192.png", revision: "3023f02d03ea538d8e3c39bdf0de383d" },
|
||||
{ url: "/images/SMOR-512.png", revision: "c446f4acb68301e0d833488005e82085" },
|
||||
{ url: "/images/no-image.png", revision: "12f9ee78f32722610c775ca7375c0c11" },
|
||||
{ url: "/images/vercel.svg", revision: "26bf2d0adaf1028a4d4c6ee77005e819" },
|
||||
{ url: "/manifest.json", revision: "d42be44d1990847c3d9aab15fbefdc00" },
|
||||
{ url: "/marker-icon.png", revision: "3023f02d03ea538d8e3c39bdf0de383d" },
|
||||
{ url: "/vercel.svg", revision: "26bf2d0adaf1028a4d4c6ee77005e819" },
|
||||
],
|
||||
{ ignoreURLParametersMatching: [] }
|
||||
),
|
||||
e.cleanupOutdatedCaches(),
|
||||
e.registerRoute(
|
||||
"/",
|
||||
new e.NetworkFirst({
|
||||
cacheName: "start-url",
|
||||
plugins: [
|
||||
{
|
||||
cacheWillUpdate: async ({ request: e, response: s, event: t, state: c }) =>
|
||||
s && "opaqueredirect" === s.type
|
||||
? new Response(s.body, { status: 200, statusText: "OK", headers: s.headers })
|
||||
: s,
|
||||
},
|
||||
],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,
|
||||
new e.CacheFirst({
|
||||
cacheName: "google-fonts-webfonts",
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 4, maxAgeSeconds: 31536e3 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,
|
||||
new e.StaleWhileRevalidate({
|
||||
cacheName: "google-fonts-stylesheets",
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 4, maxAgeSeconds: 604800 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,
|
||||
new e.StaleWhileRevalidate({
|
||||
cacheName: "static-font-assets",
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 4, maxAgeSeconds: 604800 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,
|
||||
new e.StaleWhileRevalidate({
|
||||
cacheName: "static-image-assets",
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 64, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/\/_next\/image\?url=.+$/i,
|
||||
new e.StaleWhileRevalidate({
|
||||
cacheName: "next-image",
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 64, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/\.(?:mp3|wav|ogg)$/i,
|
||||
new e.CacheFirst({
|
||||
cacheName: "static-audio-assets",
|
||||
plugins: [new e.RangeRequestsPlugin(), new e.ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/\.(?:mp4)$/i,
|
||||
new e.CacheFirst({
|
||||
cacheName: "static-video-assets",
|
||||
plugins: [new e.RangeRequestsPlugin(), new e.ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/\.(?:js)$/i,
|
||||
new e.StaleWhileRevalidate({
|
||||
cacheName: "static-js-assets",
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/\.(?:css|less)$/i,
|
||||
new e.StaleWhileRevalidate({
|
||||
cacheName: "static-style-assets",
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/\/_next\/data\/.+\/.+\.json$/i,
|
||||
new e.StaleWhileRevalidate({
|
||||
cacheName: "next-data",
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
/\.(?:json|xml|csv)$/i,
|
||||
new e.NetworkFirst({
|
||||
cacheName: "static-data-assets",
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
({ url: e }) => {
|
||||
if (!(self.origin === e.origin)) return !1
|
||||
const s = e.pathname
|
||||
return !s.startsWith("/api/auth/") && !!s.startsWith("/api/")
|
||||
},
|
||||
new e.NetworkFirst({
|
||||
cacheName: "apis",
|
||||
networkTimeoutSeconds: 10,
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 16, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
({ url: e }) => {
|
||||
if (!(self.origin === e.origin)) return !1
|
||||
return !e.pathname.startsWith("/api/")
|
||||
},
|
||||
new e.NetworkFirst({
|
||||
cacheName: "others",
|
||||
networkTimeoutSeconds: 10,
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 86400 })],
|
||||
}),
|
||||
"GET"
|
||||
),
|
||||
e.registerRoute(
|
||||
({ url: e }) => !(self.origin === e.origin),
|
||||
new e.NetworkFirst({
|
||||
cacheName: "cross-origin",
|
||||
networkTimeoutSeconds: 10,
|
||||
plugins: [new e.ExpirationPlugin({ maxEntries: 32, maxAgeSeconds: 3600 })],
|
||||
}),
|
||||
"GET"
|
||||
)
|
||||
})
|
||||
4
public/vercel.svg
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1068
public/workbox-1846d813.js
Normal file
0
styles/Home.module.css
Normal file
143
styles/globals.css
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
html,
|
||||
body {
|
||||
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
/* background-color: #1f2833; */
|
||||
|
||||
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
|
||||
}
|
||||
|
||||
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@media all and (min-width: 680px) {
|
||||
.Login {
|
||||
padding: 60px 0;
|
||||
}
|
||||
|
||||
|
||||
.Login form {
|
||||
margin: 0 auto;
|
||||
max-width: 320px;
|
||||
}
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
/* display: flex; */
|
||||
font-weight: 400;
|
||||
font-family: sans-serif;
|
||||
/*background: #12c2e9; /* fallback for old browsers */
|
||||
/*background: -webkit-linear-gradient(to right, #f64f59, #c471ed, #12c2e9); /* Chrome 10-25, Safari 5.1-6 */
|
||||
/*background: linear-gradient(to right, #f64f59, #c471ed, #12c2e9); /* W3C, IE 10+/ Edge, Firefox 16+, Chrome 26+, Opera 12+, Safari 7+ */
|
||||
|
||||
}
|
||||
|
||||
body, html, .App, #root, .outer {
|
||||
width: 100%;
|
||||
min-height: 100%;
|
||||
|
||||
}
|
||||
|
||||
.navbar-light {
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0px 14px 80px rgba(34, 35, 58, 0.2);
|
||||
}
|
||||
|
||||
.outer {
|
||||
|
||||
display: flex;
|
||||
/* justify-content: center; */
|
||||
flex-direction: column;
|
||||
/* margin: auto; */
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
inner {
|
||||
|
||||
|
||||
margin: auto;
|
||||
background: #ffffff;
|
||||
box-shadow: 0px 14px 80px rgba(34, 35, 58, 0.2);
|
||||
padding: 40px 30px 45px 30px;
|
||||
border-radius: 15px;
|
||||
transition: all .3s;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.outer .form-control:focus {
|
||||
border-color: #167bff;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.outer h3 {
|
||||
text-align: center;
|
||||
margin: 0;
|
||||
line-height: 1;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.custom-control-label {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.forgot-password,
|
||||
.forgot-password a {
|
||||
text-align: right;
|
||||
font-size: 13px;
|
||||
padding-top: 10px;
|
||||
color: #7f7d7d;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.forgot-password a {
|
||||
color: #167bff;
|
||||
}
|
||||
|
||||
|
||||
.nav{
|
||||
border: 1 solid #45a29e !important;
|
||||
}
|
||||
.nav-link {
|
||||
color: aqua !important;
|
||||
border-bottom: 1px solid #45a29e !important;
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
color:#1f2833 !important;
|
||||
border-width: 0 !important;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@keyframes spinner {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
.loading-spinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 10px solid #66fcf1; /* Light grey */
|
||||
border-top: 10px solid #45a29e; /* Black */
|
||||
border-radius: 50%;
|
||||
animation: spinner 1.5s linear infinite;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
|
@ -15,6 +15,12 @@ module.exports = {
|
|||
"./src/**/*.{js,ts,jsx,tsx,mdx}",
|
||||
],
|
||||
theme: {
|
||||
screens: {
|
||||
sm: "480px",
|
||||
md: "768px",
|
||||
lg: "976px",
|
||||
xl: "1440px",
|
||||
},
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
|
|
|
|||
252
testdata/items.json
vendored
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
[
|
||||
{
|
||||
"title": "Lorem Ipsum 1",
|
||||
"image_url": "https://example.com/image1.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 2",
|
||||
"image_url": "https://example.com/image2.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 3",
|
||||
"image_url": "https://example.com/image3.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 4",
|
||||
"image_url": "https://example.com/image4.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 5",
|
||||
"image_url": "https://example.com/image5.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 6",
|
||||
"image_url": "https://example.com/image6.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 7",
|
||||
"image_url": "https://example.com/image7.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 8",
|
||||
"image_url": "https://example.com/image8.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 9",
|
||||
"image_url": "https://example.com/image9.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 10",
|
||||
"image_url": "https://example.com/image10.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 11",
|
||||
"image_url": "https://example.com/image11.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 12",
|
||||
"image_url": "https://example.com/image12.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 13",
|
||||
"image_url": "https://example.com/image13.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 14",
|
||||
"image_url": "https://example.com/image14.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 15",
|
||||
"image_url": "https://example.com/image15.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 16",
|
||||
"image_url": "https://example.com/image16.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 17",
|
||||
"image_url": "https://example.com/image17.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 18",
|
||||
"image_url": "https://example.com/image18.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 19",
|
||||
"image_url": "https://example.com/image19.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 20",
|
||||
"image_url": "https://example.com/image20.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 21",
|
||||
"image_url": "https://example.com/image21.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 22",
|
||||
"image_url": "https://example.com/image22.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 23",
|
||||
"image_url": "https://example.com/image23.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 24",
|
||||
"image_url": "https://example.com/image24.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 25",
|
||||
"image_url": "https://example.com/image25.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 26",
|
||||
"image_url": "https://example.com/image26.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 27",
|
||||
"image_url": "https://example.com/image27.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 28",
|
||||
"image_url": "https://example.com/image28.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 29",
|
||||
"image_url": "https://example.com/image29.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 30",
|
||||
"image_url": "https://example.com/image30.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 31",
|
||||
"image_url": "https://example.com/image31.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 32",
|
||||
"image_url": "https://example.com/image32.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 33",
|
||||
"image_url": "https://example.com/image33.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 34",
|
||||
"image_url": "https://example.com/image34.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 35",
|
||||
"image_url": "https://example.com/image35.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 36",
|
||||
"image_url": "https://example.com/image36.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 37",
|
||||
"image_url": "https://example.com/image37.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 38",
|
||||
"image_url": "https://example.com/image38.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 39",
|
||||
"image_url": "https://example.com/image39.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 40",
|
||||
"image_url": "https://example.com/image40.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 41",
|
||||
"image_url": "https://example.com/image41.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 42",
|
||||
"image_url": "https://example.com/image42.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 43",
|
||||
"image_url": "https://example.com/image43.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 44",
|
||||
"image_url": "https://example.com/image44.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 45",
|
||||
"image_url": "https://example.com/image45.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 46",
|
||||
"image_url": "https://example.com/image46.jpg",
|
||||
"summary": "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 47",
|
||||
"image_url": "https://example.com/image47.jpg",
|
||||
"summary": "Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 48",
|
||||
"image_url": "https://example.com/image48.jpg",
|
||||
"summary": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 49",
|
||||
"image_url": "https://example.com/image49.jpg",
|
||||
"summary": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur."
|
||||
},
|
||||
{
|
||||
"title": "Lorem Ipsum 50",
|
||||
"image_url": "https://example.com/image50.jpg",
|
||||
"summary": "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
|
||||
}
|
||||
]
|
||||
39
themes.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// export const CLR_HEAD = '#ffffff' ;
|
||||
|
||||
// export const CLR_RCARD1 = 'orange';
|
||||
|
||||
// export const CLR_RCARD2 = 'orange';
|
||||
|
||||
// export const CLR_RCARD3 = '#ffffff';
|
||||
|
||||
// export const CLR_RCARD4 = '#ffffff';
|
||||
|
||||
// export const CLR_FBAR = '#ffffff';
|
||||
|
||||
////////////////////////////////////////////////////////
|
||||
|
||||
// export const CLR_HEAD = '#1f2833' ;
|
||||
|
||||
// export const CLR_HEAD1 = '#1f2833' ;// 1f2833
|
||||
|
||||
// export const CLR_RCARD1 = '#45a29e'; //1F2833
|
||||
|
||||
// export const CLR_RCARD2 = '#66fcf1';
|
||||
|
||||
// export const CLR_RCARD3 = '#c5c6c7';
|
||||
|
||||
// export const CLR_FBAR = '#0b0c10';
|
||||
|
||||
////////////////////////////////////////
|
||||
|
||||
export const CLR_HEAD = "#1f2833"
|
||||
|
||||
export const CLR_HEAD1 = "#1f2833" // 1f2833
|
||||
|
||||
export const CLR_RCARD1 = "#45a29e" //1F2833
|
||||
|
||||
export const CLR_RCARD2 = "#66fcf1"
|
||||
|
||||
export const CLR_RCARD3 = "#c5c6c7"
|
||||
|
||||
export const CLR_FBAR = "#0b0c10"
|
||||
|
|
@ -25,6 +25,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"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", "next.config.js", "next.config.js"],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
|
|
|||
111
utils/auth.js
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
// utils/auth.js
|
||||
import bcrypt from "bcryptjs"
|
||||
|
||||
import { jwtVerify, SignJWT } from "jose"
|
||||
|
||||
const secret = new TextEncoder().encode("Swe4g7c?UBm5Nrd96vhsVDtkyJFbqKMTm!TMw5BDRLtaCFAXNvbq?s4rGKQSZnUP")
|
||||
|
||||
///
|
||||
|
||||
async function generateToken(payload, expiresIn) {
|
||||
const algo = "HS256"
|
||||
const t = await new SignJWT(payload)
|
||||
.setProtectedHeader({ alg: algo })
|
||||
.setIssuedAt()
|
||||
.setExpirationTime(expiresIn)
|
||||
.sign(secret)
|
||||
|
||||
console.log(t)
|
||||
return t
|
||||
}
|
||||
|
||||
// Function to create both access and refresh tokens
|
||||
export async function createTokens(userId) {
|
||||
const accessTokenPayload = {
|
||||
userId: userId,
|
||||
// exp: Math.floor(Date.now() / 1000) + (15 * 60), // Access token expires in 15 minutes
|
||||
// iss: 'your_issuer_here', // Replace with your actual issuer
|
||||
// aud: 'your_audience_here', // Replace with your actual audience
|
||||
purpose: "access", // Indicate the purpose of this token
|
||||
}
|
||||
|
||||
const refreshTokenPayload = {
|
||||
userId: userId,
|
||||
// exp: Math.floor(Date.now() / 1000) + (7 * 24 * 60 * 60), // Refresh token expires in 7 days
|
||||
// iss: 'your_issuer_here', // Replace with your actual issuer
|
||||
// aud: 'your_audience_here', // Replace with your actual audience
|
||||
purpose: "refresh", // Indicate the purpose of this token
|
||||
}
|
||||
|
||||
const accessToken = await generateToken(accessTokenPayload, "15m") // 15 minutes expiry
|
||||
const refreshToken = await generateToken(refreshTokenPayload, "2d") // 2 days expiry
|
||||
|
||||
return { accessToken, refreshToken }
|
||||
}
|
||||
///
|
||||
|
||||
export const hashPassword = async (password) => {
|
||||
const salt = await bcrypt.genSalt(10)
|
||||
return bcrypt.hash(password, salt)
|
||||
}
|
||||
|
||||
export const verifyPassword = async (password, hashedPassword) => {
|
||||
return bcrypt.compare(password, hashedPassword)
|
||||
}
|
||||
|
||||
export const issueAccessToken = async (userId) => {
|
||||
const algo = "HS256"
|
||||
const accessTokenPayload = {
|
||||
userId: userId,
|
||||
// exp: Math.floor(Date.now() / 1000) + (15 * 60), // Access token expires in 15 minutes
|
||||
// iss: 'your_issuer_here', // Replace with your actual issuer
|
||||
// aud: 'your_audience_here', // Replace with your actual audience
|
||||
purpose: "access", // Indicate the purpose of this token
|
||||
}
|
||||
|
||||
const accessToken = await generateToken(accessTokenPayload, "1m") // 15 minutes expiry
|
||||
|
||||
return accessToken
|
||||
}
|
||||
|
||||
// This function verifies a refresh token using a shared secret key
|
||||
export async function verifyRefreshToken(refreshToken) {
|
||||
try {
|
||||
// Verify the refresh token using the shared secret key
|
||||
|
||||
const { payload, protectedHeader } = await jwtVerify(refreshToken, secret)
|
||||
console.log(payload)
|
||||
// Ensure the token has necessary fields and is not expired
|
||||
if (!payload.userId) {
|
||||
throw new Error("Invalid token format")
|
||||
}
|
||||
|
||||
// Optionally, you can also check if the token is intended for refresh purposes only
|
||||
if (payload.purpose !== "refresh") {
|
||||
throw new Error("Invalid token purpose")
|
||||
}
|
||||
|
||||
// Token is valid, return the decoded token
|
||||
return payload
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw new Error("Invalid refresh token")
|
||||
}
|
||||
}
|
||||
|
||||
export async function refreshAccessToken(refreshToken) {
|
||||
try {
|
||||
const response = await fetch("http://localhost:3000/api/refreshtoken", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
})
|
||||
const data = await response.json()
|
||||
return data.accessToken
|
||||
} catch (error) {
|
||||
console.error("Error refreshing access token:", error)
|
||||
throw new Error("Failed to refresh access token")
|
||||
}
|
||||
}
|
||||
567
utils/categories.js
Normal file
|
|
@ -0,0 +1,567 @@
|
|||
export const categoriesjson = [
|
||||
{
|
||||
category: "Clothes men",
|
||||
subcategories: [
|
||||
{
|
||||
item: "suits",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Cookwares",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Kadhai",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Tawa",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Mixer",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Kitchen Equipments",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Fryer",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Oven",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Minifridge",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Holidays & travel",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Camping",
|
||||
hassub: 1,
|
||||
subs: ["Tents", "Camping Chairs", "Cooking Equipment", "Other Camping Equipments"],
|
||||
},
|
||||
{
|
||||
item: "Luggage",
|
||||
hassub: 1,
|
||||
subs: ["Suitcases", "Travellers Backpacks", "Backpacks", "Other Luggage"],
|
||||
},
|
||||
{
|
||||
item: "Pool/Sea Toys",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Other Holiday & Travel",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Party & events",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Special Effects",
|
||||
hassub: 1,
|
||||
subs: ["Disco Lighting, Lasers & Strobe", "Bubble Machines", "Smoke MAchines", "Other Special Effects"],
|
||||
},
|
||||
{
|
||||
item: "Staging",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Entertainment",
|
||||
hassub: 1,
|
||||
subs: ["Karaoke Equipment", "Photo Booths", "Adult Party Games", "Other Event Entertainment"],
|
||||
},
|
||||
{
|
||||
item: "Catering Equipment",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Display Cabinets / Fridges",
|
||||
"Hot Plates",
|
||||
"Walk-in Fridge Trailers",
|
||||
"Cooking Equipment",
|
||||
"Cutlery, Crockery & Glassware",
|
||||
"Other CAtering Equipment",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Special Effects",
|
||||
hassub: 1,
|
||||
subs: ["Wedding Decorations", "Christmas Decorations", "Centerpieces", "Other Decorations"],
|
||||
},
|
||||
{
|
||||
item: "Portaloos",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Event Furniture",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Seating",
|
||||
"Bars",
|
||||
"Smoke MAchines",
|
||||
"LED furniture",
|
||||
"Tables",
|
||||
"Furniture Accessories",
|
||||
"Other Furniture",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Tents & Marquees",
|
||||
hassub: 1,
|
||||
subs: ["Gazebose", "Marquees", "MArquee Accessories"],
|
||||
},
|
||||
{
|
||||
item: "Event Lighting",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"DJ, Disco & Stage Lights",
|
||||
"Fairy & Festoon Lights",
|
||||
"Light-up Numbers & Letters",
|
||||
"Other Event Lighting",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Party Package Deals",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Other PArty",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Film & Photography",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Cameras",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"DSLR Cameras",
|
||||
"Digital Cinema Cameras",
|
||||
"Camcorders",
|
||||
"GoPros & Headcams",
|
||||
"Film Cameras",
|
||||
"Polaroid",
|
||||
"360 Cameras",
|
||||
"Mirrorless Cameras",
|
||||
"Other Cameras",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Lenses",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Cinema Prime Lenses",
|
||||
"DLSR Lenses",
|
||||
"Medium Format Lenses",
|
||||
"Mirrorless Lenses",
|
||||
"Lens Accessories",
|
||||
"Other Lenses",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Accessories",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Tripods & Heads",
|
||||
"Rigs / Stabiliserws / Gimbals",
|
||||
"Bags and Storage",
|
||||
"Dollies / Sliders",
|
||||
"Grips / Stands / Clamps",
|
||||
"Flashes",
|
||||
"Follow Focus & Lens Support",
|
||||
"Other Accessories",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Lighting",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Continuous Lighting",
|
||||
"Backgrounds & Supports",
|
||||
"Stands , Mounts & Accessories",
|
||||
"Otehr Film & Photography Lighting",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Cameras With Operators",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Photography Studios",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Camera Package Deals / Kits",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Other Film & Photography",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Drones",
|
||||
subcategories: [
|
||||
{
|
||||
item: "DJI Drones",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Spark Series",
|
||||
"Mavic Series",
|
||||
"Phantom Series",
|
||||
"Inspire Series",
|
||||
"Industrial Drones",
|
||||
"Other DJI Drones",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Drones With Operators",
|
||||
hassub: 0,
|
||||
subs: [],
|
||||
},
|
||||
{
|
||||
item: "Drone Accessories",
|
||||
hassub: 1,
|
||||
subs: ["Cases & Bags", "Batteries", "Other Drone Accessories"],
|
||||
},
|
||||
{
|
||||
item: "Drone PAckage Deals",
|
||||
hassub: 0,
|
||||
subs: [],
|
||||
},
|
||||
{
|
||||
item: "Other Drones",
|
||||
hassub: 0,
|
||||
subs: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Sports",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Racket Sports",
|
||||
hassub: 1,
|
||||
subs: ["Squash", "Tennis", "Badminton", "Table Tennis", "Other Racket Sport Equipment"],
|
||||
},
|
||||
{
|
||||
item: "Gym Equipment",
|
||||
hassub: 1,
|
||||
subs: ["Free Weights", "Gym MAchines", "Other Gym Equipments"],
|
||||
},
|
||||
{
|
||||
item: "Water Sports",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Swimming Goggles",
|
||||
"Wetsuits",
|
||||
"Snorkelling Equipment",
|
||||
"Kiteboarding Equipment",
|
||||
"Sailing & Boats",
|
||||
"Kayak & Canoes",
|
||||
"Surfboards",
|
||||
"Windsurfing Equipment",
|
||||
"Jet Skis",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Winter Sports",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Skis",
|
||||
"Snowboards",
|
||||
"Ski Boots",
|
||||
"Snowboard Boots",
|
||||
"Ski & Snowboard Clothing",
|
||||
"Ski & Snowboard Accessories",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Outdoor Sports",
|
||||
hassub: 1,
|
||||
subs: ["Golf", "Rugby", "Cricket", "Football", "Triathalon", "Hiking", "Surfboards", "Other Outdoor Sports"],
|
||||
},
|
||||
{
|
||||
item: "Gym Equipment",
|
||||
hassub: 1,
|
||||
subs: ["Free Weights", "Gym MAchines", "Other Gym Equipments"],
|
||||
},
|
||||
{
|
||||
item: "Country Sports",
|
||||
hassub: 1,
|
||||
subs: ["Fishing Equipment", "Horse Riding Equipment", "Shooting Equipment"],
|
||||
},
|
||||
{
|
||||
item: "Sports Package Deals",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Other Sports",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Kids",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Babies & Toddlers",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Baby Car Seats",
|
||||
"Booster Seats",
|
||||
"Baby Carriers & Backpacks",
|
||||
"Prams & Pushchairs",
|
||||
"Highchairs",
|
||||
"Bouncers / Swings",
|
||||
"Breast Pumps",
|
||||
"Cots",
|
||||
"Baby Monitors",
|
||||
"Play Pens",
|
||||
"Bottle Sterilisers",
|
||||
"Baby Toys",
|
||||
"Baby Toys",
|
||||
"Other Babies & Toddlers",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Kids",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Hoverboards, Scooters & Rollerblades",
|
||||
"Water Toys",
|
||||
"Bikes",
|
||||
"Beanbags",
|
||||
"Other Toys",
|
||||
"Lawn Toys",
|
||||
"Other Kids",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Kids Parties",
|
||||
hassub: 1,
|
||||
subs: ["Bouncy Castles", "Inflatables", "Soft Play", "Other Kids Parties"],
|
||||
},
|
||||
{
|
||||
item: "Kids & Baby Package Deals",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Other Kids & Baby",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Audio Visual Equipment",
|
||||
subcategories: [
|
||||
{
|
||||
item: "PA Systems",
|
||||
hassub: 1,
|
||||
subs: ["Small Venues (<100 people)", "Medium Venues (100-300 people)", "Large Venues (300+ people)"],
|
||||
},
|
||||
{
|
||||
item: "Speakers",
|
||||
hassub: 1,
|
||||
subs: ["Speaker Systems", "Portable Speakers", "Moniter Systems", "Other Speakers"],
|
||||
},
|
||||
{
|
||||
item: "Microphones",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Recording Mics",
|
||||
"Wireless Mics / Systems",
|
||||
"Condenser Mics",
|
||||
"Condenser Mics",
|
||||
"USB Mics",
|
||||
"Standard Mics",
|
||||
"Other Mics",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Recording Equipment",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Recorders",
|
||||
"Studio, Samplers & Sequencers",
|
||||
"Interfaces, Consoles & Controllers",
|
||||
"Headphones",
|
||||
"Sound Blimps",
|
||||
"Other Recording Equipment",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Recording Studios",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Audio Visual PAckage Deals",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Other Audio Visual",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Dj Equipment",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Decks & Turntables",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Mixers",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Controllers",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "DJ Equipment with Operator",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "DJ Equipment Package Deals",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Other DJ Equipment",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Electronics",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Computer",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Laptops",
|
||||
"Ipads & Tablets",
|
||||
"Desktops",
|
||||
"PC Moniters",
|
||||
"Printers & Scanners",
|
||||
"Servers",
|
||||
"Computer Accessories",
|
||||
"Other Computer",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Entertainment & Gaming",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"TVs",
|
||||
"TV Accessories",
|
||||
"Kindles",
|
||||
"Xbox Consoles & Games",
|
||||
"Playstation Consoles & Games",
|
||||
"Nintendo Consoles & Games",
|
||||
"Other Entertainment & Gaming",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Virtual Reality",
|
||||
hassub: 1,
|
||||
subs: ["Headsets", "Games", "Other Virtual Reality"],
|
||||
},
|
||||
{
|
||||
item: "Personal Electronics",
|
||||
hassub: 1,
|
||||
subs: [
|
||||
"Mobile Phones",
|
||||
"Smartwatches",
|
||||
"Headphones",
|
||||
"Fitbits & Fitness Electronics",
|
||||
"Other Personal Electronics",
|
||||
],
|
||||
},
|
||||
{
|
||||
item: "Electronics Package Deals",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Other Electronics",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Musical Instrument",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Guitars",
|
||||
hassub: 1,
|
||||
subs: ["Acoustic", "Bass", "Electric", "Pedals", "Guitar Accessories", "Other Guitars"],
|
||||
},
|
||||
{
|
||||
item: "Amps",
|
||||
hassub: 1,
|
||||
subs: ["Guitar Amps", "Bass Amps", "Piano Amps", "Other Amps"],
|
||||
},
|
||||
{
|
||||
item: "Keyboards & Synthesisers",
|
||||
hassub: 1,
|
||||
subs: ["Electric Keyboards", "Synthesisers", "Pianos", "Other Keyboards"],
|
||||
},
|
||||
{
|
||||
item: "String Instruments",
|
||||
hassub: 1,
|
||||
subs: ["Violins", "Double Bass", "Banjos", "Ouds", "Mandolins", "Ukuele", "Other String Instruments"],
|
||||
},
|
||||
{
|
||||
item: "Brass",
|
||||
hassub: 1,
|
||||
subs: ["Trumpet", "Trombone", "Cornet", "Other Brass"],
|
||||
},
|
||||
{
|
||||
item: "Woodwind",
|
||||
hassub: 1,
|
||||
subs: ["Saxophone", "Fluter", "Carinet", "Recorders", "Other Woodwind"],
|
||||
},
|
||||
{
|
||||
item: "Percussion",
|
||||
hassub: 1,
|
||||
subs: ["Drums", "Cymbals", "Other Percussion"],
|
||||
},
|
||||
{
|
||||
item: "Instrument Accessories",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Musical Instruments Deals",
|
||||
hassub: 0,
|
||||
},
|
||||
{
|
||||
item: "Other Musical Instrument",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
category: "Others",
|
||||
subcategories: [
|
||||
{
|
||||
item: "Other things",
|
||||
hassub: 0,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
28
utils/themes.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
import { extendTheme } from "@chakra-ui/react"
|
||||
|
||||
const colors = {
|
||||
primary: {
|
||||
100: "#B5FCF1",
|
||||
200: "#27EF96",
|
||||
300: "#10DE82",
|
||||
400: "#0EBE6F",
|
||||
500: "#0CA25F",
|
||||
600: "#0A864F",
|
||||
700: "#086F42",
|
||||
800: "#075C37",
|
||||
900: "#064C2E",
|
||||
},
|
||||
}
|
||||
|
||||
const breakpoints = {
|
||||
base: "0em", // 0px
|
||||
sm: "30em", // ~480px. em is a relative unit and is dependant on the font size.
|
||||
md: "48em", // ~768px
|
||||
lg: "62em", // ~992px
|
||||
xl: "80em", // ~1280px
|
||||
"2xl": "96em", // ~1536px
|
||||
}
|
||||
|
||||
const customTheme = extendTheme({ colors, breakpoints })
|
||||
|
||||
export default customTheme
|
||||