diff --git a/.env b/.env new file mode 100644 index 0000000..71e3f0c --- /dev/null +++ b/.env @@ -0,0 +1,16 @@ +DB_USER= +DB_HOST= +DB_NAME= +DB_PASSWORD= +DB_PORT= + +YOOKASSA_SHOP_ID= +YOOKASSA_SECRET_KEY= + +APP_URL= + + +# SMTP Configuration for Yandex +SMTP_USER=levishub@yandex.com +SMTP_PASS=avhpihoudpyvibtx +DEFAULT_TO_EMAIL=miduway@yandex.ru diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..d9b57ee --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +DB_USER= +DB_HOST= +DB_NAME= +DB_PASSWORD= +DB_PORT= + +YOOKASSA_SHOP_ID= +YOOKASSA_SECRET_KEY= + +APP_URL= + + + + +# SMTP Configuration for Yandex +SMTP_USER=levishub@yandex.ru +SMTP_PASS=avhpihoudpyvibtx +DEFAULT_TO_EMAIL=miduway@yandex.ru diff --git a/README.md b/README.md index 25b5821..b915854 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Make sure to install dependencies: ```bash # npm + npm install # pnpm diff --git a/components/EmailForm.vue b/components/EmailForm.vue new file mode 100644 index 0000000..502d732 --- /dev/null +++ b/components/EmailForm.vue @@ -0,0 +1,78 @@ + + + diff --git a/components/UiButton/UiButton.vue b/components/UiButton/UiButton.vue index a2a4bee..7d0392d 100644 --- a/components/UiButton/UiButton.vue +++ b/components/UiButton/UiButton.vue @@ -2,6 +2,8 @@ diff --git a/frontend.env.example b/frontend.env.example deleted file mode 100644 index a57c961..0000000 --- a/frontend.env.example +++ /dev/null @@ -1,2 +0,0 @@ -VITE_MODE=DEVELOP -VITE_API_BASE_PATH=http://localhost:5173/ diff --git a/nuxt.config.ts b/nuxt.config.ts index f277112..f45a9b3 100644 --- a/nuxt.config.ts +++ b/nuxt.config.ts @@ -25,11 +25,13 @@ export default defineNuxtConfig({ css: ['@/assets/css/tailwind.css'], ...config, + htmlValidator: { usePrettier: true, logLevel: 'error', failOnError: false, }, + app: { head: { title: 'Vino Galante', @@ -39,4 +41,25 @@ export default defineNuxtConfig({ ...head, }, }, + + modules: [ + ...config.modules, + [ + 'nuxt-mail', + { + smtp: { + host: 'smtp.yandex.ru', + port: 465, + secure: true, + auth: { + user: process.env.SMTP_USER || 'levishub@yandex.com', + pass: process.env.SMTP_PASS || 'avhpihoudpyvibtx', + }, + }, + message: { + to: process.env.DEFAULT_TO_EMAIL || 'default@example.com', + }, + }, + ], + ], }) diff --git a/package.json b/package.json index dc3f5f2..8595d67 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "format": "node_modules/.bin/prettier --write ./" }, "dependencies": { + "@a2seven/yoo-checkout": "^1.1.4", "@nuxt/content": "^3.6.0", "@nuxt/fonts": "^0.11.4", "@nuxt/icon": "^1.13.0", @@ -23,9 +24,15 @@ "@pinia/nuxt": "^0.5.5", "@tailwindcss/postcss": "^4.1.10", "better-sqlite3": "^11.10.0", + "crypto-js": "^4.2.0", + "date-fns": "^4.1.0", "husky": "^9.1.7", + "nodemailer": "^7.0.3", "nuxt": "^3.17.5", + "nuxt-mail": "^6.0.2", "nuxt-schema-org": "^5.0.5", + "pg": "^8.16.2", + "postgres": "^3.4.7", "swiper": "^11.2.8", "vue": "^3.5.16" }, @@ -34,6 +41,7 @@ "@nuxt/test-utils": "^3.11.3", "@nuxtjs/tailwindcss": "^6.11.4", "@types/node": "^22.0.0", + "@types/pg": "^8.15.4", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.5.0", "autoprefixer": "^10.4.18", diff --git a/pages/books/[slug].vue b/pages/books/[slug].vue index 37221d2..318eea7 100644 --- a/pages/books/[slug].vue +++ b/pages/books/[slug].vue @@ -33,7 +33,7 @@ -
+
@@ -41,7 +41,12 @@ ruble
- + {{ book.buttonText }} @@ -153,10 +158,11 @@ diff --git a/pages/email.vue b/pages/email.vue new file mode 100644 index 0000000..94bb38a --- /dev/null +++ b/pages/email.vue @@ -0,0 +1,19 @@ + + + diff --git a/pages/index/_ui/heroBanner/_ui/buyContent.vue b/pages/index/_ui/heroBanner/_ui/buyContent.vue index 10544b2..32de97f 100644 --- a/pages/index/_ui/heroBanner/_ui/buyContent.vue +++ b/pages/index/_ui/heroBanner/_ui/buyContent.vue @@ -1,12 +1,14 @@ import UiParagraph from '@/components/Typography/UiParagraph.vue'; @@ -23,12 +25,14 @@ const content = reactive({ '💡 Узнай, как думает мужчина, что его действительно цепляет, и что делает женщину незабываемой.', button: 'КУПИТЬ КНИГУ I', botContent: 'PDF + EPUB сразу после оплаты', + path: '/books/1', }, { topContent: '💡 Продолжение для тех, кто готов перейти от флирта к глубокому контакту. Как строить притяжение, не теряя себя.', button: 'КУПИТЬ КНИГУ II', botContent: 'PDF + EPUB сразу после оплаты', + path: '/books/2', }, ], }) diff --git a/assets/img/png/book1.png b/public/img/books/book1.png similarity index 100% rename from assets/img/png/book1.png rename to public/img/books/book1.png diff --git a/assets/img/png/book2.png b/public/img/books/book2.png similarity index 100% rename from assets/img/png/book2.png rename to public/img/books/book2.png diff --git a/store/useSelectedBook.ts b/store/useSelectedBook.ts new file mode 100644 index 0000000..cb4c33a --- /dev/null +++ b/store/useSelectedBook.ts @@ -0,0 +1,58 @@ +import { defineStore } from 'pinia' + +interface CartItem { + id: number + quantity: number + summary?: number +} + +export const useSelectedBook = defineStore('selectedBook', { + state: () => ({ + cartQuantities: [] as CartItem[], + }), + actions: { + increment(id: number) { + const item = this.cartQuantities.find((i) => i.id === id) + if (item && item.quantity < 1) { + item.quantity++ + } + }, + decrement(id: number) { + const item = this.cartQuantities.find((i) => i.id === id) + if (item) { + item.quantity-- + } + }, + reset(id: number) { + const idx = this.cartQuantities.findIndex((i) => i.id === id) + if (idx !== -1) { + this.cartQuantities.splice(idx, 1) + } + }, + addToCart(id: number) { + if (!this.cartQuantities.find((i) => i.id === id)) { + this.cartQuantities.push({ id, quantity: 1 }) + } + }, + getQuantity(id: number) { + return this.cartQuantities.find((i) => i.id === id)?.quantity || 0 + }, + }, + getters: { + getTotalPrice: (state) => (books: { id: number; price: number }[]) => { + const selected = state.cartQuantities.filter((i) => i.quantity > 0) + if (selected.length === 2) { + // Проверяем, что это две разные книги + const ids = selected.map((i) => i.id) + if (ids[0] !== ids[1]) { + return 936 + } + } + // Обычная сумма + return selected.reduce((sum, item) => { + const book = books.find((b) => b.id === item.id) + return sum + (book ? book.price * item.quantity : 0) + }, 0) + }, + }, +})