Skip to content

Commit

Permalink
feat!: checkout receivers (#267)
Browse files Browse the repository at this point in the history
* chore: html attr,  robots.txt

* chore: aria-label

* feat!: checkout receivers
  • Loading branch information
hmbanan666 authored Oct 29, 2024
1 parent 42e7500 commit dd70607
Show file tree
Hide file tree
Showing 11 changed files with 208 additions and 11 deletions.
25 changes: 25 additions & 0 deletions apps/email/server/public/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"version": "0.6.1",
"title": "@next-orders/email",
"description": "Easy way to send an email through a prepared service.",
"routes": [
{
"path": "/send",
"method": "POST",
"body": {
"to": {
"type": "string",
"required": true
},
"subject": {
"type": "string",
"required": true
},
"html": {
"type": "string",
"required": true
}
}
}
]
}
File renamed without changes.
1 change: 1 addition & 0 deletions apps/food/app/components/Cart/Drawer.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
<button
class="z-30 fixed left-0 right-0 -top-20 -bottom-20 appearance-none bg-neutral-700/50 opacity-0 data-[active=true]:opacity-100 translate-x-full data-[active=true]:-translate-x-0 transition-opacity"
:data-active="isCartDrawerOpened"
aria-label="Close"
@click="isCartDrawerOpened = !isCartDrawerOpened"
/>
<div
Expand Down
3 changes: 2 additions & 1 deletion apps/food/app/composables/useChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ export async function useChannel() {
const categoriesWithProducts = computed(() => {
return categories.value.filter((category) => category.products?.length > 0)
})
const products = computed(() => categoriesWithProducts.value?.flatMap((category) => category.products) || [])
const allProducts = computed(() => categoriesWithProducts.value?.flatMap((category) => category.products) || [])
const products = computed(() => allProducts.value?.filter((product) => product.variants?.length > 0))

return { channel: data, menus, activeMenu, categories, categoriesWithProducts, products, refresh }
}
10 changes: 10 additions & 0 deletions apps/food/app/layouts/checkout.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,13 @@
</div>
</main>
</template>

<script setup lang="ts">
const { locale } = useI18n()
useHead({
htmlAttrs: {
lang: locale,
},
})
</script>
10 changes: 10 additions & 0 deletions apps/food/app/layouts/default.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@
</div>
</aside>
</template>

<script setup lang="ts">
const { locale } = useI18n()
useHead({
htmlAttrs: {
lang: locale,
},
})
</script>
14 changes: 14 additions & 0 deletions apps/food/prisma/migrations/20241029135704_receiver/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
-- CreateTable
CREATE TABLE "checkout_receiver" (
"id" TEXT NOT NULL,
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"type" TEXT NOT NULL,
"data" JSONB,
"channel_id" TEXT NOT NULL,

CONSTRAINT "checkout_receiver_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "checkout_receiver" ADD CONSTRAINT "checkout_receiver_channel_id_fkey" FOREIGN KEY ("channel_id") REFERENCES "channel"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
33 changes: 23 additions & 10 deletions apps/food/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,27 @@ datasource db {
}

model Channel {
id String @id
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @map("updated_at")
id String @id
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @map("updated_at")
slug String
name String
description String?
conditions String?
phone String?
countryCode String @map("country_code")
currencyCode String @map("currency_code")
timeZone String @map("time_zone")
isActive Boolean @default(true) @map("is_active")
isDeliveryAvailable Boolean @default(true) @map("is_delivery_available")
isPickupAvailable Boolean @default(true) @map("is_pickup_available")
minAmountForDelivery Int? @map("min_amount_for_delivery")
countryCode String @map("country_code")
currencyCode String @map("currency_code")
timeZone String @map("time_zone")
isActive Boolean @default(true) @map("is_active")
isDeliveryAvailable Boolean @default(true) @map("is_delivery_available")
isPickupAvailable Boolean @default(true) @map("is_pickup_available")
minAmountForDelivery Int? @map("min_amount_for_delivery")
paymentMethods PaymentMethod[]
warehouses Warehouse[]
menus Menu[]
products Product[]
checkouts Checkout[]
checkoutReceivers CheckoutReceiver[]
users User[]
workingDays WorkingDay[]
Expand Down Expand Up @@ -184,6 +185,18 @@ model CheckoutLine {
@@map("checkout_line")
}

model CheckoutReceiver {
id String @id
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @default(now()) @map("updated_at")
type String
data Json?
channelId String @map("channel_id")
channel Channel @relation(fields: [channelId], references: [id])
@@map("checkout_receiver")
}

model User {
id String @id
createdAt DateTime @default(now()) @map("created_at")
Expand Down
4 changes: 4 additions & 0 deletions apps/food/public/robots.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Allow: /
Disallow: /checkout
Disallow: /finish
Disallow: /command-center
Disallow: /welcome
100 changes: 100 additions & 0 deletions apps/food/server/api/checkout/index.patch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ export default defineEventHandler(async (event) => {
},
})

await sendToReceivers(checkout.id)

const session = await getUserSession(event)
await replaceUserSession(event, {
...session,
Expand All @@ -84,3 +86,101 @@ export default defineEventHandler(async (event) => {
throw errorResolver(error)
}
})

async function sendToReceivers(checkoutId: string) {
const checkout = await prisma.checkout.findFirst({
where: { id: checkoutId },
include: {
lines: {
include: {
variant: true,
},
},
},
})
if (!checkout?.id) {
return
}

const channel = await prisma.channel.findFirst({
where: { id: checkout?.channelId },
include: {
warehouses: true,
paymentMethods: true,
},
})
if (!channel?.id) {
return
}

const receivers = await prisma.checkoutReceiver.findMany({
where: { channelId: checkout?.channelId },
})

for (const receiver of receivers as CheckoutReceiver[]) {
if (receiver.type === 'EMAIL') {
const html = prepareEmailHtml(checkout as Checkout & { lines: (CheckoutLine & { variant: ProductVariant })[] }, channel as Channel & { warehouses: Warehouse[], paymentMethods: PaymentMethod[] })

await sendEmail(receiver, html)
}
}
}

async function sendEmail(receiver: CheckoutReceiverTypeEmail, html: string) {
const logger = useLogger('sendEmail')

try {
await $fetch(receiver.data.url, {
method: receiver.data.method,
body: {
to: receiver.data.to,
subject: receiver.data.subject,
html,
},
})

return true
} catch (error) {
logger.error(error)

return false
}
}

function prepareEmailHtml(checkout: Checkout & { lines: (CheckoutLine & { variant: ProductVariant })[] }, channel: Channel & { warehouses: Warehouse[], paymentMethods: PaymentMethod[] }) {
return `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Новая заявка!</h1>
<h2>${checkout?.deliveryMethod === 'WAREHOUSE' ? 'Самовывоз' : 'Доставка'}}</h2>
<p>Клиент: ${checkout.name}, ${checkout.phone}</p>
<p>Время: ${checkout.time}</p>
${checkout.warehouseId ? `<p>Адрес склада-кухни: ${channel.warehouses.find((w) => w.id === checkout.warehouseId)?.address}</p>` : ''}
${checkout.street ? `<p>Адрес: ${checkout.street} ${checkout.flat}, домофон ${checkout.doorphone}, подъезд ${checkout.entrance}, этаж ${checkout.floor}. ${checkout.addressNote}</p>` : ''}
<p>Метод оплаты: ${channel.paymentMethods.find((p) => p.id === checkout.paymentMethodId)?.name}</p>
${checkout.change ? `<p>Нужна сдача с: ${checkout.change}</p>` : ''}
<p>Комментарий: ${checkout.note}</p>
<h3>Заказанные товары:</h3>
${checkout.lines
.map((line) => {
return `
<p>${line.variant.name} - ${line.quantity} шт. - ${line.variant.gross}</p>
`
})
.join('')}
<p>Итого: ${checkout.totalPrice}</p>
</body>
</html>
`
}
19 changes: 19 additions & 0 deletions apps/food/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,25 @@ declare global {
isGift: boolean
}

type CheckoutReceiver = {
id: string
createdAt: Date
updatedAt: Date
channelId: string
} & CheckoutReceiverTypes

type CheckoutReceiverTypes = CheckoutReceiverTypeEmail

interface CheckoutReceiverTypeEmail {
type: 'EMAIL'
data: {
url: string
method: 'POST'
to: string
subject: string
}
}

interface User {
id: string
createdAt: Date
Expand Down

0 comments on commit dd70607

Please sign in to comment.