From 8138da6b1d56ec6ca8cb7e83c3af68e643927915 Mon Sep 17 00:00:00 2001 From: Yevhen Odynets Date: Wed, 5 Feb 2025 08:01:14 +0200 Subject: [PATCH] added tons of features --- .gitignore | 88 +- .idea/.gitignore | 8 + .idea/bewell.iml | 12 + .idea/codeStyles/Project.xml | 99 + .idea/codeStyles/codeStyleConfig.xml | 5 + .idea/dataSources.xml | 12 + .idea/inspectionProfiles/Project_Default.xml | 8 + .idea/jsLinters/eslint.xml | 6 + .idea/modules.xml | 8 + .idea/prettier.xml | 7 + .idea/sqldialects.xml | 8 + .idea/vcs.xml | 6 + .prettierrc.json | 50 + actions/admin/category.ts | 59 + actions/admin/product.ts | 129 + actions/auth/common.ts | 21 + actions/auth/google-login.ts | 16 + actions/auth/login.ts | 87 + actions/auth/register.ts | 79 + actions/execute-action.helper.ts | 31 + actions/model/category.ts | 17 + actions/permission/index.ts | 30 + .../admin/category/[...slug]/page.tsx | 19 + app/(protected)/admin/category/page.tsx | 14 + app/(protected)/admin/layout.tsx | 65 + app/(protected)/admin/page.tsx | 5 + .../admin/product/[...slug]/page.tsx | 30 + app/(protected)/admin/product/page.tsx | 34 + app/[locale]/(auth)/layout.tsx | 16 + app/[locale]/(auth)/login/page.tsx | 5 + app/[locale]/(auth)/register/page.tsx | 5 + .../(cabinet)/cabinet/[[...slug]]/page.tsx | 26 + app/[locale]/(root)/(shop)/checkout/page.tsx | 12 + app/[locale]/(root)/layout.tsx | 17 + app/[locale]/(root)/page.tsx | 69 + app/[locale]/error.tsx | 34 + app/[locale]/layout.tsx | 33 + app/api/auth/[...nextauth]/route.ts | 1 + app/api/uploads/[...filestore]/route.ts | 0 app/favicon.ico | Bin 25931 -> 11267 bytes app/globals.css | 215 +- app/layout.tsx | 58 +- app/page.tsx | 101 - auth.config.ts | 20 + auth.ts | 101 + components.json | 21 + .../(protected)/admin/auth/permission.tsx | 26 + .../admin/category/create-form.tsx | 171 + components/(protected)/admin/footer.tsx | 47 + components/(protected)/admin/header.tsx | 41 + .../admin/product/create-edit-form.tsx | 442 +++ .../admin/product/create-form.tsx.back | 169 + components/(protected)/admin/sidebar.tsx | 106 + components/auth/auth-header.tsx | 18 + components/auth/back-button.tsx | 16 + components/auth/card-wrapper.tsx | 37 + components/auth/form-error.tsx | 17 + components/auth/form-success.tsx | 15 + components/auth/forms/login-form.tsx | 104 + components/auth/forms/register-form.tsx | 133 + components/auth/forms/sign-out-button.tsx | 14 + components/auth/google-login.tsx | 29 + components/cabinet/index.tsx | 60 + .../shared/above/css/fig-one.module.css | 19 + components/shared/above/fig-one.tsx | 115 + components/shared/above/fig-three.tsx | 3 + components/shared/above/fig-two.tsx | 3 + components/shared/above/index.tsx | 47 + components/shared/app-sidebar.tsx~ | 175 + components/shared/editor/jodit.tsx | 56 + components/shared/footer.tsx | 28 + components/shared/header/cabinet-button.tsx | 33 + components/shared/header/controls.tsx | 34 + components/shared/header/index.tsx | 54 + components/shared/home/feature-cards.tsx | 44 + components/shared/home/home-carousel.tsx | 74 + components/shared/home/logo.tsx | 29 + components/shared/icons/google.tsx | 34 + components/shared/icons/props.ts | 5 + components/shared/icons/whatsapp.tsx | 22 + components/shared/locale-switcher.tsx | 41 + components/shared/navbar/index.tsx | 9 + components/shared/navbar/navbar-menu.tsx | 44 + components/shared/search/form.tsx | 33 + .../shared/sidebar/app-catalog-render.tsx | 55 + components/shared/sidebar/app-catalog.tsx | 16 + components/shared/social-media-panel.tsx | 82 + components/shared/store/product-card.tsx | 30 + components/temp-component.tsx | 31 + components/ui/avatar.tsx | 50 + components/ui/breadcrumb.tsx | 115 + components/ui/button.tsx | 59 + components/ui/card.tsx | 76 + components/ui/carousel.tsx | 266 ++ components/ui/collapsible.tsx | 11 + components/ui/dialog.tsx | 122 + components/ui/dropdown-menu.tsx | 201 + components/ui/form.tsx | 178 + components/ui/input.tsx | 23 + components/ui/label.tsx | 26 + components/ui/select.tsx | 159 + components/ui/separator.tsx | 31 + components/ui/sheet.tsx | 140 + components/ui/sidebar.tsx | 763 ++++ components/ui/skeleton.tsx | 15 + components/ui/switch.tsx | 29 + components/ui/tabs.tsx | 55 + components/ui/toast.tsx | 135 + components/ui/toaster.tsx | 35 + components/ui/tooltip.tsx | 32 + data/accout.ts | 17 + data/user.ts | 21 + eslint.config.mjs | 67 +- global.d.ts | 11 + hooks/use-mobile.tsx | 19 + hooks/use-toast.ts | 191 + i18n-config.ts | 14 + i18n/request.ts | 42 + i18n/routing.ts | 23 + lib/config/constants.ts | 1 + lib/config/dayjs.ts | 10 + lib/config/editor.ts | 60 + lib/config/http.ts | 3 + lib/config/routes.ts | 5 + lib/constants.ts | 14 + lib/data.ts | 152 + lib/data/mapResponseToDTO.ts | 27 + lib/data/models/product.ts | 50 + lib/db/prisma/client.ts | 64 + lib/db/prisma/schema/category.prisma | 40 + lib/db/prisma/schema/enum.prisma | 28 + lib/db/prisma/schema/index.prisma | 26 + lib/db/prisma/schema/meta.prisma | 49 + lib/db/prisma/schema/product.prisma | 141 + lib/db/prisma/schema/store.prisma | 38 + lib/db/prisma/schema/user.prisma | 92 + lib/db/prisma/seed/main.ts | 28 + lib/db/prisma/seed/store.ts | 55 + lib/db/prisma/seed/user.ts | 24 + lib/db/prisma/sql/getUserWithAccount.sql | 14 + lib/db/redis/client.ts | 8 + lib/db/redis/ttl.ts | 1 + lib/module/image.ts | 30 + lib/permission/index.ts | 52 + lib/permission/roles/index.ts | 1 + lib/permission/roles/supervisor.ts | 7 + lib/schemas/admin/category.ts | 26 + lib/schemas/admin/product.ts | 44 + lib/schemas/index.ts | 40 + lib/schemas/meta.ts | 8 + lib/styles/jodit.css | 5 + lib/utils.ts | 144 + lib/validator.ts | 65 + messages/ru.json | 65 + messages/uk.json | 63 + middleware.ts | 18 + middlewares/chain.ts | 36 + middlewares/withAuthMiddleware.ts | 18 + middlewares/withI18nMiddleware.ts | 25 + next.config.ts | 36 +- package-lock.json | 3456 +++++++++++++++-- package.json | 98 +- public/file.svg | 1 - public/globe.svg | 1 - public/images/947-vistabon_mobi_1.jpg | Bin 0 -> 380487 bytes public/images/above/fig-1.svg | 85 + public/images/above/fig-2.svg | 61 + public/images/above/fig-3.svg | 104 + public/images/logo.svg | 59 + public/images/main-1.jpg | Bin 0 -> 753751 bytes .../og-abxbm8wqowl65w86drm8my4xr6aia1km.png | Bin 0 -> 1417181 bytes public/img/icon/google.svg | 1 + public/img/icon/uk-flower-24.png | Bin 0 -> 2553 bytes public/img/icon/квіточка.png | Bin 0 -> 2711 bytes public/img/logo-be.svg | 12 + public/img/logo-icon.svg | 24 + public/img/logo-moto.svg | 23 + public/img/logo-no-motto.svg | 34 + public/img/logo-sign.png | Bin 0 -> 19789 bytes public/img/logo-sign.svg | 24 + public/img/logo-well.svg | 14 + public/img/logo.svg | 59 + public/next.svg | 1 - ...stacare_osteostrong_tablets_box_livo_1.png | Bin 0 -> 1302735 bytes .../189238-192172-orig-1500-1500-d76a.jpg | Bin 0 -> 258219 bytes ...aa7b-06ecb5f905b0-w1000-h1000-wm-frame.jpg | Bin 0 -> 47060 bytes public/uploads/637393-1500x1500-ea2f.jpg | Bin 0 -> 124905 bytes .../79282077-e324-4248-9f5d-184242ec4dd4.webp | Bin 0 -> 44480 bytes public/uploads/products/IMG_6572.jpg | Bin 0 -> 389923 bytes public/vercel.svg | 1 - public/window.svg | 1 - tailwind.config.ts | 163 +- tsconfig.json | 3 + types/auth.ts | 13 + types/index.ts | 9 + 195 files changed, 12619 insertions(+), 415 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/bewell.iml create mode 100644 .idea/codeStyles/Project.xml create mode 100644 .idea/codeStyles/codeStyleConfig.xml create mode 100644 .idea/dataSources.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/jsLinters/eslint.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/prettier.xml create mode 100644 .idea/sqldialects.xml create mode 100644 .idea/vcs.xml create mode 100644 .prettierrc.json create mode 100644 actions/admin/category.ts create mode 100644 actions/admin/product.ts create mode 100644 actions/auth/common.ts create mode 100644 actions/auth/google-login.ts create mode 100644 actions/auth/login.ts create mode 100644 actions/auth/register.ts create mode 100644 actions/execute-action.helper.ts create mode 100644 actions/model/category.ts create mode 100644 actions/permission/index.ts create mode 100644 app/(protected)/admin/category/[...slug]/page.tsx create mode 100644 app/(protected)/admin/category/page.tsx create mode 100644 app/(protected)/admin/layout.tsx create mode 100644 app/(protected)/admin/page.tsx create mode 100644 app/(protected)/admin/product/[...slug]/page.tsx create mode 100644 app/(protected)/admin/product/page.tsx create mode 100644 app/[locale]/(auth)/layout.tsx create mode 100644 app/[locale]/(auth)/login/page.tsx create mode 100644 app/[locale]/(auth)/register/page.tsx create mode 100644 app/[locale]/(root)/(cabinet)/cabinet/[[...slug]]/page.tsx create mode 100644 app/[locale]/(root)/(shop)/checkout/page.tsx create mode 100644 app/[locale]/(root)/layout.tsx create mode 100644 app/[locale]/(root)/page.tsx create mode 100644 app/[locale]/error.tsx create mode 100644 app/[locale]/layout.tsx create mode 100644 app/api/auth/[...nextauth]/route.ts create mode 100644 app/api/uploads/[...filestore]/route.ts delete mode 100644 app/page.tsx create mode 100644 auth.config.ts create mode 100644 auth.ts create mode 100644 components.json create mode 100644 components/(protected)/admin/auth/permission.tsx create mode 100644 components/(protected)/admin/category/create-form.tsx create mode 100644 components/(protected)/admin/footer.tsx create mode 100644 components/(protected)/admin/header.tsx create mode 100644 components/(protected)/admin/product/create-edit-form.tsx create mode 100644 components/(protected)/admin/product/create-form.tsx.back create mode 100644 components/(protected)/admin/sidebar.tsx create mode 100644 components/auth/auth-header.tsx create mode 100644 components/auth/back-button.tsx create mode 100644 components/auth/card-wrapper.tsx create mode 100644 components/auth/form-error.tsx create mode 100644 components/auth/form-success.tsx create mode 100644 components/auth/forms/login-form.tsx create mode 100644 components/auth/forms/register-form.tsx create mode 100644 components/auth/forms/sign-out-button.tsx create mode 100644 components/auth/google-login.tsx create mode 100644 components/cabinet/index.tsx create mode 100644 components/shared/above/css/fig-one.module.css create mode 100644 components/shared/above/fig-one.tsx create mode 100644 components/shared/above/fig-three.tsx create mode 100644 components/shared/above/fig-two.tsx create mode 100644 components/shared/above/index.tsx create mode 100644 components/shared/app-sidebar.tsx~ create mode 100644 components/shared/editor/jodit.tsx create mode 100644 components/shared/footer.tsx create mode 100644 components/shared/header/cabinet-button.tsx create mode 100644 components/shared/header/controls.tsx create mode 100644 components/shared/header/index.tsx create mode 100644 components/shared/home/feature-cards.tsx create mode 100644 components/shared/home/home-carousel.tsx create mode 100644 components/shared/home/logo.tsx create mode 100644 components/shared/icons/google.tsx create mode 100644 components/shared/icons/props.ts create mode 100644 components/shared/icons/whatsapp.tsx create mode 100644 components/shared/locale-switcher.tsx create mode 100644 components/shared/navbar/index.tsx create mode 100644 components/shared/navbar/navbar-menu.tsx create mode 100644 components/shared/search/form.tsx create mode 100644 components/shared/sidebar/app-catalog-render.tsx create mode 100644 components/shared/sidebar/app-catalog.tsx create mode 100644 components/shared/social-media-panel.tsx create mode 100644 components/shared/store/product-card.tsx create mode 100644 components/temp-component.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/breadcrumb.tsx create mode 100644 components/ui/button.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/carousel.tsx create mode 100644 components/ui/collapsible.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/dropdown-menu.tsx create mode 100644 components/ui/form.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/select.tsx create mode 100644 components/ui/separator.tsx create mode 100644 components/ui/sheet.tsx create mode 100644 components/ui/sidebar.tsx create mode 100644 components/ui/skeleton.tsx create mode 100644 components/ui/switch.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/toaster.tsx create mode 100644 components/ui/tooltip.tsx create mode 100644 data/accout.ts create mode 100644 data/user.ts create mode 100644 global.d.ts create mode 100644 hooks/use-mobile.tsx create mode 100644 hooks/use-toast.ts create mode 100644 i18n-config.ts create mode 100644 i18n/request.ts create mode 100644 i18n/routing.ts create mode 100644 lib/config/constants.ts create mode 100644 lib/config/dayjs.ts create mode 100644 lib/config/editor.ts create mode 100644 lib/config/http.ts create mode 100644 lib/config/routes.ts create mode 100644 lib/constants.ts create mode 100644 lib/data.ts create mode 100644 lib/data/mapResponseToDTO.ts create mode 100644 lib/data/models/product.ts create mode 100644 lib/db/prisma/client.ts create mode 100644 lib/db/prisma/schema/category.prisma create mode 100644 lib/db/prisma/schema/enum.prisma create mode 100644 lib/db/prisma/schema/index.prisma create mode 100644 lib/db/prisma/schema/meta.prisma create mode 100644 lib/db/prisma/schema/product.prisma create mode 100644 lib/db/prisma/schema/store.prisma create mode 100644 lib/db/prisma/schema/user.prisma create mode 100644 lib/db/prisma/seed/main.ts create mode 100644 lib/db/prisma/seed/store.ts create mode 100644 lib/db/prisma/seed/user.ts create mode 100644 lib/db/prisma/sql/getUserWithAccount.sql create mode 100644 lib/db/redis/client.ts create mode 100644 lib/db/redis/ttl.ts create mode 100644 lib/module/image.ts create mode 100644 lib/permission/index.ts create mode 100644 lib/permission/roles/index.ts create mode 100644 lib/permission/roles/supervisor.ts create mode 100644 lib/schemas/admin/category.ts create mode 100644 lib/schemas/admin/product.ts create mode 100644 lib/schemas/index.ts create mode 100644 lib/schemas/meta.ts create mode 100644 lib/styles/jodit.css create mode 100644 lib/utils.ts create mode 100644 lib/validator.ts create mode 100644 messages/ru.json create mode 100644 messages/uk.json create mode 100644 middleware.ts create mode 100644 middlewares/chain.ts create mode 100644 middlewares/withAuthMiddleware.ts create mode 100644 middlewares/withI18nMiddleware.ts delete mode 100644 public/file.svg delete mode 100644 public/globe.svg create mode 100644 public/images/947-vistabon_mobi_1.jpg create mode 100644 public/images/above/fig-1.svg create mode 100644 public/images/above/fig-2.svg create mode 100644 public/images/above/fig-3.svg create mode 100644 public/images/logo.svg create mode 100644 public/images/main-1.jpg create mode 100644 public/images/og-abxbm8wqowl65w86drm8my4xr6aia1km.png create mode 100644 public/img/icon/google.svg create mode 100644 public/img/icon/uk-flower-24.png create mode 100644 public/img/icon/квіточка.png create mode 100644 public/img/logo-be.svg create mode 100644 public/img/logo-icon.svg create mode 100644 public/img/logo-moto.svg create mode 100644 public/img/logo-no-motto.svg create mode 100644 public/img/logo-sign.png create mode 100644 public/img/logo-sign.svg create mode 100644 public/img/logo-well.svg create mode 100644 public/img/logo.svg delete mode 100644 public/next.svg create mode 100644 public/uploads/1256-vistacare_osteostrong_tablets_box_livo_1.png create mode 100644 public/uploads/189238-192172-orig-1500-1500-d76a.jpg create mode 100644 public/uploads/1ca3d021-a55d-4c0a-aa7b-06ecb5f905b0-w1000-h1000-wm-frame.jpg create mode 100644 public/uploads/637393-1500x1500-ea2f.jpg create mode 100644 public/uploads/79282077-e324-4248-9f5d-184242ec4dd4.webp create mode 100644 public/uploads/products/IMG_6572.jpg delete mode 100644 public/vercel.svg delete mode 100644 public/window.svg create mode 100644 types/auth.ts create mode 100644 types/index.ts 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} +