diff --git a/.gitignore b/.gitignore index 5ef6a52..f66bd6e 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,7 @@ # misc .DS_Store *.pem - +*.bak # debug npm-debug.log* yarn-debug.log* @@ -39,3 +39,89 @@ yarn-error.log* # typescript *.tsbuildinfo next-env.d.ts +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/**/usage.statistics.xml +.idea/**/dictionaries +.idea/**/shelf +.idea/**/aws.xml +.idea/**/contentModel.xml +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml +.idea/**/dbnavigator.xml +.idea/**/gradle.xml +.idea/**/libraries +cmake-build-*/ +.idea/**/mongoSettings.xml +*.iws +out/ +.idea_modules/ +atlassian-ide-plugin.xml +.idea/replstate.xml +.idea/sonarlint/ +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties +.idea/httpRequests +.idea/caches/build_file_checksums.ser +logs +*.log +lerna-debug.log* +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json +pids +*.pid +*.seed +*.pid.lock +lib-cov +coverage +*.lcov +.nyc_output +.grunt +bower_components +.lock-wscript +build/Release +node_modules/ +jspm_packages/ +web_modules/ +.npm +.eslintcache +.stylelintcache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ +.node_repl_history +*.tgz +.yarn-integrity +.env +.env.development.local +.env.test.local +.env.production.local +.env.local +.cache +.parcel-cache +.next +out +.nuxt +dist +.cache/ +.vuepress/dist +.temp +.docusaurus +.serverless/ +.fusebox/ +.dynamodb/ +.tern-port +.vscode-test +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.js +.env*.local +/messages/*.d.json.ts diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/bewell.iml b/.idea/bewell.iml new file mode 100644 index 0000000..24643cc --- /dev/null +++ b/.idea/bewell.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml new file mode 100644 index 0000000..ccec8e0 --- /dev/null +++ b/.idea/codeStyles/Project.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..79ee123 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000..0a5407a --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,12 @@ + + + + + mysql.8 + true + com.mysql.cj.jdbc.Driver + jdbc:mysql://10.14.88.14:3306 + $ProjectFileDir$ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..001f05c --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/.idea/jsLinters/eslint.xml b/.idea/jsLinters/eslint.xml new file mode 100644 index 0000000..541945b --- /dev/null +++ b/.idea/jsLinters/eslint.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..5c633f7 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/prettier.xml b/.idea/prettier.xml new file mode 100644 index 0000000..0c83ac4 --- /dev/null +++ b/.idea/prettier.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml new file mode 100644 index 0000000..fdf3b0b --- /dev/null +++ b/.idea/sqldialects.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..0cb5926 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,50 @@ +{ + "trailingComma": "none", + "tabWidth": 2, + "useTabs": true, + "semi": false, + "singleQuote": true, + "jsxSingleQuote": true, + "arrowParens": "avoid", + "bracketSpacing": false, + "importOrderSeparation": true, + "importOrderSortSpecifiers": true, + "importOrder": [ + "", + "^@api/(.*)$", + "^@app/(.*)$", + "^@components/(.*)$", + "^@config/(.*)$", + "^@constants/(.*)$", + "^@hooks/(.*)$", + "^@services/(.*)$", + "^@shared/(.*)$", + "^@store/(.*)$", + "^@utils/(.*)$", + "^@ui/(.*)$", + "^../(.*)$", + "^./(.*)$", + "^[./]" + ], + "overrides": [ + { + "files": "*.test.js", + "options": { + "semi": true + } + }, + { + "files": [ + "*.html", + "legacy/**/*.js" + ], + "options": { + "tabWidth": 4 + } + } + ], + "plugins": [ + "@trivago/prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss" + ] +} diff --git a/actions/admin/category.ts b/actions/admin/category.ts new file mode 100644 index 0000000..5f35e27 --- /dev/null +++ b/actions/admin/category.ts @@ -0,0 +1,59 @@ +'use server' + +import {CategoryLocale} from '@prisma/client' +import {z} from 'zod' + +import {getCategoryBySlug} from '@/actions/model/category' +import {i18nLocalesCodes} from '@/i18n-config' +import {STORE_ID} from '@/lib/config/constants' +import {db} from '@/lib/db/prisma/client' +import {createCategoryFormSchema} from '@/lib/schemas/admin/category' +import {cleanEmptyParams, dbErrorHandling, slug as slugger} from '@/lib/utils' + +export const onCategoryCreateAction = async ( + formData: z.infer +) => { + const validatedData = createCategoryFormSchema.parse(formData) + + if (!validatedData) { + return {error: 'Недійсні вхідні дані'} + } + + if (validatedData.locales.length < i18nLocalesCodes.length) { + return {error: 'Заповніть всі мови'} + } + + const locales: CategoryLocale[] = [] + + for (const i in validatedData.locales) { + const locale = validatedData.locales[i] + + const {title, lang} = locale + const slug = slugger(title, lang) + const result = await getCategoryBySlug({slug, lang}) + + if (!result) { + const normalized: any = cleanEmptyParams({slug, ...locale}) + locales.push(normalized) + } else { + return {error: `Категорія ${title} вже існує`} + } + } + + try { + const newCategory = await db.category.create({ + data: { + storeId: STORE_ID, + status: true, + // position: 0, + locales: { + create: locales + } + } + }) + + return {success: JSON.stringify(newCategory, null, 2)} + } catch (error) { + return dbErrorHandling(error) + } +} diff --git a/actions/admin/product.ts b/actions/admin/product.ts new file mode 100644 index 0000000..ac545b8 --- /dev/null +++ b/actions/admin/product.ts @@ -0,0 +1,129 @@ +'use server' + +import {Meta, ProductLocale, ProductToStore} from '@prisma/client' +import sanitizeHtml from 'sanitize-html' +import {z} from 'zod' + +import {i18nLocalesCodes} from '@/i18n-config' +import {STORE_ID} from '@/lib/config/constants' +import {getProductBySlug} from '@/lib/data/models/product' +import {db} from '@/lib/db/prisma/client' +import {createProductFormSchema} from '@/lib/schemas/admin/product' +import { + cleanEmptyParams, + dbErrorHandling, + isEmptyObj, + slug as slugger +} from '@/lib/utils' + +export const onProductCreateAction = async ( + formData: z.infer +) => { + const validatedData = createProductFormSchema.parse(formData) + + if (!validatedData) return {error: 'Недійсні вхідні дані'} + + if (validatedData.locales.length < i18nLocalesCodes.length) { + return {error: 'Заповніть всі мови'} + } + + const {published, image} = validatedData + const price = parseFloat(validatedData.price).toFixed(2) + const price_promotional = parseFloat( + validatedData.pricePromotional as string + ).toFixed(2) + + if (parseFloat(price) < 1) { + return {error: 'Ціна не може бути нижчою за одну гривню'} + } + + const meta: Meta[] = [] + + for (const i in validatedData.meta) { + const normalizedMeta: any = cleanEmptyParams(validatedData.meta[i]) + + //if (!isEmptyObj(normalizedMeta)) {} + meta.push(normalizedMeta) + } + + const locales: ProductLocale[] = [] + + for (const i in validatedData.locales) { + const locale = validatedData.locales[i] + const {title, lang} = locale + const slug = slugger(title, lang) + + const result = await getProductBySlug({slug, lang}) + + if (!result) { + const normalized: any = cleanEmptyParams({slug, ...locale}) + + //locales.push({...normalized, meta: {create: meta[i]}}) + locales.push(normalized) + } else { + return {error: `Продукт з такою назвою ${title} вже існує`} + } + } + + try { + const newProduct = await db.product.create({ + data: { + image, + toStore: { + create: { + published, + price, + pricePromotional: price_promotional, + storeId: STORE_ID + } + }, + locales: { + create: locales + } + } + }) + + return {success: 'JSON.stringify(newProduct, null, 2)'} + } catch (error) { + return dbErrorHandling(error) + } +} + +// const result = sanitizeHtml(description, { +// allowedTags: [ +// 'p', +// 'b', +// 'i', +// 'h1', +// 'h2', +// 'h3', +// 'h4', +// 'h5', +// 'h6', +// 'em', +// 'strong', +// 'a', +// 'blockquote', +// 'div', +// 'li', +// 'ol', +// 'ul', +// 'cite', +// 'code', +// 'small', +// 'sub', +// 'sup' +// ], +// nonBooleanAttributes: [], +// allowedAttributes: { +// a: ['href', 'name', 'target'], +// img: ['src', 'srcset', 'alt', 'title', 'width', 'height', 'loading'], +// selfClosing: ['img', 'hr'] +// }, +// allowedIframeHostnames: [], +// parser: { +// lowerCaseTags: true +// } +// }) +// +// console.log(result) diff --git a/actions/auth/common.ts b/actions/auth/common.ts new file mode 100644 index 0000000..6915515 --- /dev/null +++ b/actions/auth/common.ts @@ -0,0 +1,21 @@ +'use server' + +import {getUserWithAccount} from '@prisma/client/sql' + +import {getAccountByUserId} from '@/data/accout' +import {getUserById} from '@/data/user' +import {db} from '@/lib/db/prisma/client' + +export const exisingUser = async (id: string | number) => { + return await getUserById(id) +} + +export const exisingUserAccount = async (userId: string | number) => { + return await getAccountByUserId(userId) +} + +export const getUserAccountByUserId = async (userId: string | number) => { + const user = await db.$queryRawTyped(getUserWithAccount(userId)) + + return user.length > 0 ? user[0] : null +} diff --git a/actions/auth/google-login.ts b/actions/auth/google-login.ts new file mode 100644 index 0000000..08604cb --- /dev/null +++ b/actions/auth/google-login.ts @@ -0,0 +1,16 @@ +'use server' + +import {AuthError} from 'next-auth' + +import {signIn} from '@/auth' + +export async function googleAuthenticate() { + try { + await signIn('google') + } catch (error) { + if (error instanceof AuthError) { + return 'google log in failed' + } + throw error + } +} diff --git a/actions/auth/login.ts b/actions/auth/login.ts new file mode 100644 index 0000000..2238d81 --- /dev/null +++ b/actions/auth/login.ts @@ -0,0 +1,87 @@ +'use server' + +import {User} from '@prisma/client' +import bcrypt from 'bcryptjs' +import {AuthError} from 'next-auth' +import {getTranslations} from 'next-intl/server' +import * as z from 'zod' + +import {signIn} from '@/auth' +import {db} from '@/lib/db/prisma/client' +import {LoginSchema} from '@/lib/schemas' + +export const login = async (data: z.infer) => { + const t = await getTranslations('Auth') + // Validate the input data + const validatedData = LoginSchema.parse(data) + + // If the data is invalid, return an error + if (!validatedData) { + return {error: t('Invalid input data')} + } + + // Destructure the validated data + const {email, password} = validatedData + + const userExists = await db.user.findFirst({ + where: {email} + }) + + if (!userExists || !userExists.password || !userExists.email) { + return {error: t('Error.user-not-found')} + } + + try { + await signIn('credentials', { + email: userExists.email as string, + password: password as string, + redirectTo: '/' + }) + } catch (error) { + if (error instanceof AuthError) { + switch (error.type) { + case 'CredentialsSignin': + return {error: 'Invalid credentials'} + default: + return {error: error.type} + } + } + + throw error + } + + return {success: 'User successfully logged in!'} +} + +export const authorizeCallback = async ( + credentials: Partial> +): Promise => { + const validatedData = z + .object({ + email: z.string().email(), + password: z.string().min(6) + }) + .safeParse(credentials) + + if (!validatedData.success) return null + + const {email, password} = validatedData.data + + const user = await db.user.findFirst({ + where: {email} + }) + + if (!user || !user.password || !user.email) return null + + try { + if (await bcrypt.compare(password, user.password)) { + return user + } else { + console.log('Invalid credentials', user.email) + return null + } + } catch (err) { + console.log('Verifying password error', err) + } + return null +} diff --git a/actions/auth/register.ts b/actions/auth/register.ts new file mode 100644 index 0000000..06a90ad --- /dev/null +++ b/actions/auth/register.ts @@ -0,0 +1,79 @@ +'use server' + +import bcrypt from 'bcryptjs' +import * as z from 'zod' + +import {db} from '@/lib/db/prisma/client' +import {RegisterSchema} from '@/lib/schemas' +import {hashPassword} from '@/lib/utils' + +// import { generateVerificationToken } from "@/lib/token"; +// import { sendVerificationEmail } from "@/lib/mail"; + +export const register = async (data: z.infer) => { + try { + // Validate the input data + const validatedData = RegisterSchema.parse(data) + + // If the data is invalid, return an error + if (!validatedData) { + return {error: 'Invalid input data'} + } + + // Destructure the validated data + const {email, name, password, passwordConfirmation} = validatedData + + // Check if passwords match + if (password !== passwordConfirmation) { + return {error: 'Passwords do not match'} + } + + // Hash the password + const hashedPassword = await hashPassword(password) + + // Check to see if user already exists + const userExists = await db.user.findFirst({ + where: { + email + } + }) + + // If the user exists, return an error + if (userExists) { + return {error: 'Email already is in use. Please try another one.'} + } + + const lowerCaseEmail = email.toLowerCase() + + // Create the user + const user = await db.user.create({ + data: { + email: lowerCaseEmail, + name, + password: hashedPassword + } + }) + + // Generate Verification Token + // const verificationToken = await generateVerificationToken(email); + + // await sendVerificationEmail(lowerCaseEmail, verificationToken.token); + + return {success: 'Email Verification was sent'} + } catch (error) { + // Handle the error, specifically check for a 503 error + console.error('Database error:', error) + + if ((error as {code: string}).code === 'ETIMEDOUT') { + return { + error: 'Unable to connect to the database. Please try again later.' + } + } else if ((error as {code: string}).code === '503') { + return { + error: 'Service temporarily unavailable. Please try again later.' + } + } else { + return {error: 'An unexpected error occurred. Please try again later.'} + } + } +} diff --git a/actions/execute-action.helper.ts b/actions/execute-action.helper.ts new file mode 100644 index 0000000..2127b4a --- /dev/null +++ b/actions/execute-action.helper.ts @@ -0,0 +1,31 @@ +import {isRedirectError} from 'next/dist/client/components/redirect-error' + +type Options = { + actionFn: () => Promise + successMessage?: string +} + +const executeAction = async ({ + actionFn, + successMessage = 'The actions was successful' +}: Options): Promise<{success: boolean; message: string}> => { + try { + await actionFn() + + return { + success: true, + message: successMessage + } + } catch (error) { + if (isRedirectError(error)) { + throw error + } + + return { + success: false, + message: 'An error has occurred during executing the action' + } + } +} + +export {executeAction} diff --git a/actions/model/category.ts b/actions/model/category.ts new file mode 100644 index 0000000..3d79e22 --- /dev/null +++ b/actions/model/category.ts @@ -0,0 +1,17 @@ +'use server' + +import {CategoryLocale, Lang} from '@prisma/client' + +import {db} from '@/lib/db/prisma/client' + +export const getCategoryBySlug = async (data: { + slug: string + lang: string +}): Promise => { + return db.categoryLocale.findFirst({ + where: { + slug: data.slug, + lang: data.lang as Lang + } + }) +} diff --git a/actions/permission/index.ts b/actions/permission/index.ts new file mode 100644 index 0000000..cf56e13 --- /dev/null +++ b/actions/permission/index.ts @@ -0,0 +1,30 @@ +'use server' + +import {auth} from '@/auth' +import { + Access, + AllRolesPermissions, + PERMISSIONS, + type Permission, + type SingedInSession +} from '@/lib/permission' + +export type CanAccessResponse = {can: boolean; session: SingedInSession | null} +export type CanResponse = boolean | CanAccessResponse + +const can = async (permission: Permission): Promise => { + const session: SingedInSession = (await auth()) as SingedInSession + + if (!session) return false + + const able = + PERMISSIONS[session.user.role as keyof AllRolesPermissions].includes( + permission + ) + + return !Object.values(Access).includes(permission as Access) + ? able + : {can: able, session} +} + +export default can diff --git a/app/(protected)/admin/category/[...slug]/page.tsx b/app/(protected)/admin/category/[...slug]/page.tsx new file mode 100644 index 0000000..2edf96a --- /dev/null +++ b/app/(protected)/admin/category/[...slug]/page.tsx @@ -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 + } + + return
{dump(slug)}
+} diff --git a/app/(protected)/admin/category/page.tsx b/app/(protected)/admin/category/page.tsx new file mode 100644 index 0000000..a372c9c --- /dev/null +++ b/app/(protected)/admin/category/page.tsx @@ -0,0 +1,14 @@ +import Link from 'next/link' + +import AdminPermission from '@/components/(protected)/admin/auth/permission' + +export default function AdminCategoryPage() { + return ( +
+ +

+ Створити +

+
+ ) +} diff --git a/app/(protected)/admin/layout.tsx b/app/(protected)/admin/layout.tsx new file mode 100644 index 0000000..ee2dffa --- /dev/null +++ b/app/(protected)/admin/layout.tsx @@ -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 + + const cookieStore = await cookies() + const defaultOpen = cookieStore.get('sidebar:state')?.value === 'true' + + return ( + + + +
+
+ + + + + + + Building Your Application + + + + + Data Fetching + + + +
+
+
+ {children} +
+
+
+ ) +} diff --git a/app/(protected)/admin/page.tsx b/app/(protected)/admin/page.tsx new file mode 100644 index 0000000..e96eb6d --- /dev/null +++ b/app/(protected)/admin/page.tsx @@ -0,0 +1,5 @@ +import AdminPermission from '@/components/(protected)/admin/auth/permission' + +export default async function AdminPage() { + return +} diff --git a/app/(protected)/admin/product/[...slug]/page.tsx b/app/(protected)/admin/product/[...slug]/page.tsx new file mode 100644 index 0000000..5c8ad0f --- /dev/null +++ b/app/(protected)/admin/product/[...slug]/page.tsx @@ -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 + case 'update': + return + default: + return
{dump(slug)}
+ } +} diff --git a/app/(protected)/admin/product/page.tsx b/app/(protected)/admin/product/page.tsx new file mode 100644 index 0000000..f9b269b --- /dev/null +++ b/app/(protected)/admin/product/page.tsx @@ -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 ( + <> + +

+ Створити +

+ {/*
+ {products + ? products.map((product: Product) => ( +
+ + {product.locales[0].headingTitle || product.locales[0].title} +
+ )) + : null} +
*/} + + ) +} diff --git a/app/[locale]/(auth)/layout.tsx b/app/[locale]/(auth)/layout.tsx new file mode 100644 index 0000000..404c168 --- /dev/null +++ b/app/[locale]/(auth)/layout.tsx @@ -0,0 +1,16 @@ +import React from 'react' + +export default async function AuthLayout({ + children +}: { + children: React.ReactNode +}) { + return ( +
+
+ {/**/} + {children} +
+
+ ) +} diff --git a/app/[locale]/(auth)/login/page.tsx b/app/[locale]/(auth)/login/page.tsx new file mode 100644 index 0000000..88175c7 --- /dev/null +++ b/app/[locale]/(auth)/login/page.tsx @@ -0,0 +1,5 @@ +import LoginForm from '@/components/auth/forms/login-form' + +export default function LoginPage() { + return +} diff --git a/app/[locale]/(auth)/register/page.tsx b/app/[locale]/(auth)/register/page.tsx new file mode 100644 index 0000000..f05c38c --- /dev/null +++ b/app/[locale]/(auth)/register/page.tsx @@ -0,0 +1,5 @@ +import RegisterForm from '@/components/auth/forms/register-form' + +export default function RegisterPage() { + return +} diff --git a/app/[locale]/(root)/(cabinet)/cabinet/[[...slug]]/page.tsx b/app/[locale]/(root)/(cabinet)/cabinet/[[...slug]]/page.tsx new file mode 100644 index 0000000..4b3dd74 --- /dev/null +++ b/app/[locale]/(root)/(cabinet)/cabinet/[[...slug]]/page.tsx @@ -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 ( +
+
+ +
+
+ ) + } else { + const {slug} = await params + + return + } +} diff --git a/app/[locale]/(root)/(shop)/checkout/page.tsx b/app/[locale]/(root)/(shop)/checkout/page.tsx new file mode 100644 index 0000000..31b2ba5 --- /dev/null +++ b/app/[locale]/(root)/(shop)/checkout/page.tsx @@ -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
CheckoutPage
+} diff --git a/app/[locale]/(root)/layout.tsx b/app/[locale]/(root)/layout.tsx new file mode 100644 index 0000000..b6d2c6f --- /dev/null +++ b/app/[locale]/(root)/layout.tsx @@ -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 ( + <> + +
+ {/**/} + {children} +