added tons of features

This commit is contained in:
2025-02-05 08:01:14 +02:00
parent 4ae0d8c545
commit 8138da6b1d
195 changed files with 12619 additions and 415 deletions

View File

@@ -0,0 +1,19 @@
import {auth} from '@/auth'
import {CreateForm} from '@/components/(protected)/admin/category/create-form'
import {dump} from '@/lib/utils'
export default async function Page({
params
}: {
params: Promise<{slug?: string[]}>
}) {
const session = await auth()
const {slug} = await params
switch ((slug || [])[0]) {
case 'create':
return <CreateForm />
}
return <div>{dump(slug)}</div>
}

View File

@@ -0,0 +1,14 @@
import Link from 'next/link'
import AdminPermission from '@/components/(protected)/admin/auth/permission'
export default function AdminCategoryPage() {
return (
<div>
<AdminPermission />
<p>
<Link href='/admin/category/create'>Створити</Link>
</p>
</div>
)
}

View File

@@ -0,0 +1,65 @@
import {cookies} from 'next/headers'
import {ReactNode} from 'react'
import {auth} from '@/auth'
import AdminPermission from '@/components/(protected)/admin/auth/permission'
import {AdminSidebar} from '@/components/(protected)/admin/sidebar'
import {
SidebarInset,
SidebarProvider,
SidebarTrigger
} from '@/components/ui/sidebar'
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator
} from '@/ui/breadcrumb'
import {Separator} from '@/ui/separator'
export default async function AdminLayout({children}: {children: ReactNode}) {
//const session = await auth()
if (!(await auth())) return <AdminPermission />
const cookieStore = await cookies()
const defaultOpen = cookieStore.get('sidebar:state')?.value === 'true'
return (
<SidebarProvider
defaultOpen={defaultOpen}
style={{
// @ts-ignore
'--sidebar-width': '16rem',
'--sidebar-width-mobile': '18rem'
}}
>
<AdminSidebar />
<SidebarInset>
<header className='flex h-16 shrink-0 items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12'>
<div className='flex items-center gap-2 px-4'>
<SidebarTrigger className='-ml-1' />
<Separator orientation='vertical' className='mr-2 h-4' />
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem className='hidden md:block'>
<BreadcrumbLink href='#'>
Building Your Application
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator className='hidden md:block' />
<BreadcrumbItem>
<BreadcrumbPage>Data Fetching</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</div>
</header>
<main id='admin-bw-panel' className='container'>
{children}
</main>
</SidebarInset>
</SidebarProvider>
)
}

View File

@@ -0,0 +1,5 @@
import AdminPermission from '@/components/(protected)/admin/auth/permission'
export default async function AdminPage() {
return <AdminPermission />
}

View File

@@ -0,0 +1,30 @@
import ProductCreateEditForm from '@/components/(protected)/admin/product/create-edit-form'
import {getProductById} from '@/lib/data/models/product'
import {dump} from '@/lib/utils'
export default async function Page({
params
}: {
params: Promise<{slug?: string[]}>
}) {
const {slug} = await params
const [method, id] = slug || []
let data = null
if (id) {
data = await getProductById(parseInt(id))
if (data) {
data = JSON.parse(JSON.stringify(data))
}
}
switch (method) {
case 'create':
return <ProductCreateEditForm />
case 'update':
return <ProductCreateEditForm data={data} />
default:
return <div>{dump(slug)}</div>
}
}

View File

@@ -0,0 +1,34 @@
import {Product} from '@prisma/client'
import {LayoutList} from 'lucide-react'
import Link from 'next/link'
import AdminPermission from '@/components/(protected)/admin/auth/permission'
import dayjs from '@/lib/config/dayjs'
import {getProducts} from '@/lib/data/models/product'
import {dump} from '@/lib/utils'
//const products = await getProducts()
export default async function AdminProductPage() {
return (
<>
<AdminPermission />
<p>
<Link href='/admin/product/create'>Створити</Link>
</p>
{/*<section className={'mt-12'}>
{products
? products.map((product: Product) => (
<article
key={product.id}
className={'flex flex-row items-center justify-evenly'}
>
<LayoutList />
{product.locales[0].headingTitle || product.locales[0].title}
</article>
))
: null}
</section>*/}
</>
)
}

View File

@@ -0,0 +1,16 @@
import React from 'react'
export default async function AuthLayout({
children
}: {
children: React.ReactNode
}) {
return (
<section className='relative w-full'>
<div className='flex h-screen items-center justify-center bg-[radial-gradient(ellipse_at_top,_var(--tw-gradient-stops))] from-brand-violet-400 to-brand-yellow-200'>
{/**/}
{children}
</div>
</section>
)
}

View File

@@ -0,0 +1,5 @@
import LoginForm from '@/components/auth/forms/login-form'
export default function LoginPage() {
return <LoginForm />
}

View File

@@ -0,0 +1,5 @@
import RegisterForm from '@/components/auth/forms/register-form'
export default function RegisterPage() {
return <RegisterForm />
}

View File

@@ -0,0 +1,26 @@
import can, {CanAccessResponse} from '@/actions/permission'
import LoginForm from '@/components/auth/forms/login-form'
import CabinetIndex from '@/components/cabinet'
import {Access} from '@/lib/permission'
export default async function CabinetPage({
params
}: {
params: Promise<{slug?: string[]}>
}) {
const user = (await can(Access.Cabinet)) as CanAccessResponse
if (!user.can || !user.session) {
return (
<div className='my-8'>
<div className='container flex flex-col sm:flex-row'>
<LoginForm />
</div>
</div>
)
} else {
const {slug} = await params
return <CabinetIndex slug={slug} session={user.session} />
}
}

View File

@@ -0,0 +1,12 @@
import {Metadata} from 'next'
export const metadata: Metadata = {
title: 'Checkout'
}
export default function CheckoutPage() {
//throw new Error('NOT IMPLEMENTED')
//const session = await auth()
return <div>CheckoutPage</div>
}

View File

@@ -0,0 +1,17 @@
import {ReactNode} from 'react'
import Above from '@/components/shared/above'
import Footer from '@/components/shared/footer'
import Header from '@/components/shared/header'
export default async function HomeLayout({children}: {children: ReactNode}) {
return (
<>
<Above />
<Header />
{/*<Above />*/}
{children}
<Footer />
</>
)
}

View File

@@ -0,0 +1,69 @@
import Image from 'next/image'
import FeatureCards from '@/components/shared/home/feature-cards'
import {HomeCarousel} from '@/components/shared/home/home-carousel'
import AppCatalog from '@/components/shared/sidebar/app-catalog'
import {carousels} from '@/lib/data'
import {db} from '@/lib/db/prisma/client'
import {dump} from '@/lib/utils'
import image from '@/public/uploads/products/IMG_6572.jpg'
// const storeModel = async (id: any) => {
// return db.store.findFirst({
// where: {id},
// include: {
// storeLocale: {
// include: {
// meta: {
// include: {
// openGraph: true
// }
// }
// }
// }
// }
// })
// }
export default async function HomePage() {
return (
<>
<div className='mt-1'>
<div className='container flex flex-col sm:flex-row'>
<section className='bw-layout-col-left pt-3'>
<AppCatalog />
</section>
<div className='bw-layout-col-right pt-3'>
{/*<pre>{dump(await storeModel(1))}</pre>*/}
<section className='w-full'>
<HomeCarousel items={carousels}></HomeCarousel>
</section>
</div>
</div>
</div>
{/*<pre>{JSON.stringify(session)}</pre>*/}
<section className='relative mx-auto mt-8 h-[640px] w-[840px] bg-brand-violet-200'>
<Image
src={'/uploads/products/IMG_6572.jpg'}
//fill
//sizes='(min-width: 808px) 50vw, 100vw'
width={1280}
height={1280}
alt=''
title=''
priority
style={{
objectFit: 'contain' // cover, contain, none
}}
/>
</section>
<section className='mb-4 mt-[128px]'>
<div className='container'>
<FeatureCards />
</div>
</section>
</>
)
}

34
app/[locale]/error.tsx Normal file
View File

@@ -0,0 +1,34 @@
'use client'
import {useTranslations} from 'next-intl'
import React from 'react'
import {Button} from '@/components/ui/button'
export default function ErrorPage({
error,
reset
}: {
error: Error
reset: () => void
}) {
const t = useTranslations('Error')
return (
<div className='flex min-h-screen flex-col items-center justify-center'>
<div className='w-1/3 rounded-lg p-6 text-center shadow-md'>
<h1 className='mb-4 text-3xl font-bold'>{t('title')}</h1>
<p className='text-destructive'>{error.message}</p>
<Button variant='outline' className='mt-4' onClick={() => reset()}>
{t('try-again')}
</Button>
<Button
variant='outline'
className='ml-2 mt-4'
onClick={() => (window.location.href = '/')}
>
{t('back-to-home')}
</Button>
</div>
</div>
)
}

33
app/[locale]/layout.tsx Normal file
View File

@@ -0,0 +1,33 @@
import {NextIntlClientProvider} from 'next-intl'
import {getMessages} from 'next-intl/server'
import {notFound} from 'next/navigation'
import {ReactNode} from 'react'
import {routing} from '@/i18n/routing'
import {TIMEZONE} from '@/lib/constants'
export default async function RootLayout({
children,
params
}: Readonly<{
children: ReactNode
params: Promise<{locale: string}>
}>) {
const {locale} = await params
if (!routing.locales.includes(locale as any)) {
notFound()
}
const messages = await getMessages()
//const queryClient = new QueryClient()
return (
<NextIntlClientProvider
messages={messages}
timeZone={TIMEZONE}
now={new Date()}
>
{children}
</NextIntlClientProvider>
)
}

View File

@@ -0,0 +1 @@
export {GET, POST} from '@/auth'

View File

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@@ -2,20 +2,215 @@
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
body {
margin: 0;
padding: 0;
overflow-y: scroll;
}
@media (prefers-color-scheme: dark) {
@layer base {
:root {
--background: #0a0a0a;
--foreground: #ededed;
--background: 0 0% 100%;
--foreground: 0 4.615% 12.75%;
--card: 0 0% 100%;
--card-foreground: 20 14.3% 4.1%;
--popover: 0 0% 100%;
--popover-foreground: 20 14.3% 4.1%;
--primary: 47.9 95.8% 53.1%;
--primary-foreground: 26 83.3% 14.1%;
--secondary: 60 4.8% 95.9%;
--secondary-foreground: 24 9.8% 10%;
--muted: 60 4.8% 95.9%;
--muted-foreground: 25 5.3% 44.7%;
--accent: 60 4.8% 95.9%;
--accent-foreground: 24 9.8% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 20 5.9% 90%;
--input: 20 5.9% 90%;
--ring: 20 14.3% 4.1%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--sidebar-background: 0 0% 98%;
--sidebar-foreground: 240 5.3% 26.1%;
--sidebar-primary: 240 5.9% 10%;
--sidebar-primary-foreground: 0 0% 98%;
--sidebar-accent: 240 4.8% 95.9%;
--sidebar-accent-foreground: 240 5.9% 10%;
--sidebar-border: 220 13% 91%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 60 9.1% 97.8%;
--card: 20 14.3% 4.1%;
--card-foreground: 60 9.1% 97.8%;
--popover: 20 14.3% 4.1%;
--popover-foreground: 60 9.1% 97.8%;
--primary: 47.9 95.8% 53.1%;
--primary-foreground: 26 83.3% 14.1%;
--secondary: 12 6.5% 15.1%;
--secondary-foreground: 60 9.1% 97.8%;
--muted: 12 6.5% 15.1%;
--muted-foreground: 24 5.4% 63.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 60 9.1% 97.8%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 60 9.1% 97.8%;
--border: 12 6.5% 15.1%;
--input: 12 6.5% 15.1%;
--ring: 35.5 91.7% 32.9%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
--sidebar-background: 240 5.9% 10%;
--sidebar-foreground: 240 4.8% 95.9%;
--sidebar-primary: 224.3 76.3% 48%;
--sidebar-primary-foreground: 0 0% 100%;
--sidebar-accent: 240 3.7% 15.9%;
--sidebar-accent-foreground: 240 4.8% 95.9%;
--sidebar-border: 240 3.7% 15.9%;
--sidebar-ring: 217.2 91.2% 59.8%;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground;
}
}
@layer utilities {
/* .no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none; !* IE and Edge *!
scrollbar-width: none; !* Firefox *!
}*/
.bw-app-catalog-collapse {
@apply absolute scale-0 z-20
}
.header-button {
@apply cursor-pointer p-1 border border-transparent rounded-[2px];
}
.h1-bold {
@apply font-bold text-2xl lg:text-3xl;
}
.bw-layout-col-left {
@apply flex-1 sm:w-7/12 md:w-5/12 xl:w-4/12 lg:flex-col
}
.bw-layout-col-right {
@apply sm:w-5/12 md:w-7/12 xl:w-8/12 flex-1 sm:flex-auto sm:pl-4 md:pl-7 xl:pl-9
}
.bw-header-col-left {
@apply w-[9/12] flex-auto
}
.bw-header-col-right {
@apply flex-grow-0 flex-shrink-0 md:basis-[272px]
}
.bw-border-color {
@apply border-brand-violet-100;
}
.bw-separator-color {
@apply bg-brand-violet-100;
}
}
.bw-dd-menu {
/* since nested groupes are not supported we have to use
regular css for the nested dropdowns
*/
li>ul { transform: translatex(100%) scale(0) }
li:hover>ul { transform: translatex(101%) scale(1) }
li > button svg { transform: rotate(-90deg) }
li:hover > button svg { transform: rotate(-270deg) }
/* Below styles fake what can be achieved with the tailwind config
you need to add the group-hover variant to scale and define your custom
min width style.
See https://codesandbox.io/s/tailwindcss-multilevel-dropdown-y91j7?file=/index.html
for implementation with config file
*/
.group:hover .group-hover\:scale-100 { transform: scale(1) }
.group:hover .group-hover\:-rotate-180 { transform: rotate(180deg) }
}
.jodit-wysiwyg > * {
all: revert;
color: #262626 !important;
}
.jodit-wysiwyg {
padding: 0 16px !important;
/*font-family: Consolas, Monaco, sans-serif;*/
/*p {
font-size: 17px !important;
}*/
}
#admin-bw-panel form {
input {
@apply bg-white outline-0 text-[16px] leading-none;
}
[role="tablist"] {
@apply bg-brand-violet ;
[data-state=active] {
@apply bg-brand-yellow text-brand-violet;
}
[data-state=inactive] {
@apply text-brand-yellow bg-brand-violet;
}
/*@apply bg-brand-yellow-100;*/
/*[id$='trigger-uk' i][data-state=active] {
@apply bg-brand-yellow-100;
}
[id$='trigger-ru' i][data-state=active] {
@apply bg-brand-violet-100;
}*/
}
label{
@apply uppercase text-brand-violet-950 flex items-center justify-between leading-none ml-1 mt-4;
}
#form-tab-uk label {
&:after {
content: ' ';
display: inline-block;
width: 18px;
height: 0;
border-top: solid #0066cc 6px;
border-bottom: solid #ffcc00 6px;
margin-right: 4px;
}
}
}

View File

@@ -1,34 +1,32 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import type {Metadata} from 'next'
import {headers} from 'next/headers'
import {ReactNode} from 'react'
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
import './globals.css'
import {Toaster} from '@/components/ui/toaster'
import {routing} from '@/i18n/routing'
import {APP_DESCRIPTION, APP_NAME, APP_SLOGAN} from '@/lib/constants'
export const metadata: Metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
{children}
</body>
</html>
);
title: {
template: `%s | ${APP_NAME}`,
default: `${APP_NAME}. ${APP_SLOGAN}`
},
description: APP_DESCRIPTION
}
export default async function RootLayout({
children
}: Readonly<{children: ReactNode}>) {
const headersList = await headers()
const locale = headersList.get('x-site-locale') ?? routing.defaultLocale
return (
<html lang={locale} suppressHydrationWarning>
<body className='min-h-screen antialiased'>
{children}
<Toaster />
</body>
</html>
)
}

View File

@@ -1,101 +0,0 @@
import Image from "next/image";
export default function Home() {
return (
<div className="grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20 font-[family-name:var(--font-geist-sans)]">
<main className="flex flex-col gap-8 row-start-2 items-center sm:items-start">
<Image
className="dark:invert"
src="/next.svg"
alt="Next.js logo"
width={180}
height={38}
priority
/>
<ol className="list-inside list-decimal text-sm text-center sm:text-left font-[family-name:var(--font-geist-mono)]">
<li className="mb-2">
Get started by editing{" "}
<code className="bg-black/[.05] dark:bg-white/[.06] px-1 py-0.5 rounded font-semibold">
app/page.tsx
</code>
.
</li>
<li>Save and see your changes instantly.</li>
</ol>
<div className="flex gap-4 items-center flex-col sm:flex-row">
<a
className="rounded-full border border-solid border-transparent transition-colors flex items-center justify-center bg-foreground text-background gap-2 hover:bg-[#383838] dark:hover:bg-[#ccc] text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5"
href="https://vercel.com/new?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
className="dark:invert"
src="/vercel.svg"
alt="Vercel logomark"
width={20}
height={20}
/>
Deploy now
</a>
<a
className="rounded-full border border-solid border-black/[.08] dark:border-white/[.145] transition-colors flex items-center justify-center hover:bg-[#f2f2f2] dark:hover:bg-[#1a1a1a] hover:border-transparent text-sm sm:text-base h-10 sm:h-12 px-4 sm:px-5 sm:min-w-44"
href="https://nextjs.org/docs?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Read our docs
</a>
</div>
</main>
<footer className="row-start-3 flex gap-6 flex-wrap items-center justify-center">
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org/learn?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/file.svg"
alt="File icon"
width={16}
height={16}
/>
Learn
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://vercel.com/templates?framework=next.js&utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/window.svg"
alt="Window icon"
width={16}
height={16}
/>
Examples
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
href="https://nextjs.org?utm_source=create-next-app&utm_medium=appdir-template-tw&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
<Image
aria-hidden
src="/globe.svg"
alt="Globe icon"
width={16}
height={16}
/>
Go to nextjs.org
</a>
</footer>
</div>
);
}