Compare commits

..

13 Commits

Author SHA1 Message Date
Константин Уколов 085253d77d Merge remote-tracking branch 'origin/main' into main
continuous-integration/drone/push Build is passing
# Conflicts:
#	src/entities/product/ui/ProductImage.vue
2024-09-02 02:51:08 +03:00
Константин Уколов eeb2386efc feat: remove dark mode 2024-09-02 02:14:08 +03:00
Константин Уколов 80df33672b feat: redirect from init page 2024-09-02 02:12:29 +03:00
Константин Уколов 9a64f023b9 fix: icon size 2024-09-02 02:07:56 +03:00
Константин Уколов 5868f6af53 feat: add localStorage 2024-09-02 01:18:24 +03:00
Константин Уколов 0130b9aa0d feat: new price count 2024-09-02 00:27:30 +03:00
Константин Уколов 63d31fbe53 feat: add description 2024-08-31 14:04:49 +03:00
user-penguin 3e6fe8dada crm -> api
continuous-integration/drone/push Build is passing
2024-08-24 19:48:21 +07:00
user-penguin e29f4ce4e6 fix
continuous-integration/drone/push Build is passing
2024-08-24 19:39:17 +07:00
user-penguin fbb366dfba lojahjsdf
continuous-integration/drone/push Build is failing
2024-08-24 19:36:36 +07:00
user-penguin b44a742d7f switch to generate
continuous-integration/drone/push Build is passing
2024-08-24 19:26:34 +07:00
user-penguin f6d7bb7b5e ssr false
continuous-integration/drone/push Build is passing
2024-08-24 19:21:25 +07:00
user-penguin 57769eb9a3 add drone
continuous-integration/drone/push Build is passing
2024-08-24 17:43:19 +07:00
18 changed files with 9958 additions and 72 deletions
+34
View File
@@ -0,0 +1,34 @@
kind: pipeline
type: docker
name: stage-deploy
trigger:
event:
- push
steps:
- name: build
image: node:20.9
commands:
- node -v
- npm -v
- yarn --version
- yarn install
- yarn generate
- name: scp
image: appleboy/drone-scp
settings:
host:
from_secret: server_ip
username:
from_secret: ssh_user
key:
from_secret: ssh_key
port:
from_secret: ssh_port
target:
- deploys/sweet_crm
source:
- .output/public
rm: true
+5 -1
View File
@@ -1,11 +1,15 @@
// https://nuxt.com/docs/api/configuration/nuxt-config // https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({ export default defineNuxtConfig({
devtools: { enabled: true }, ssr: false,
devtools: { enabled: false },
eslint: { eslint: {
config: { config: {
standalone: false, standalone: false,
}, },
}, },
modules: ['@nuxt/ui', '@unocss/nuxt', '@nuxt/eslint'], modules: ['@nuxt/ui', '@unocss/nuxt', '@nuxt/eslint'],
colorMode: {
preference: 'light',
},
compatibilityDate: '2024-07-30', compatibilityDate: '2024-07-30',
}) })
+1 -1
View File
@@ -7,7 +7,7 @@ export default defineConfig({
target: 'src/shared/api', target: 'src/shared/api',
schemas: 'src/shared/model', schemas: 'src/shared/model',
client: 'vue-query', client: 'vue-query',
baseUrl: 'https://cake-crm.3crabs.ru', baseUrl: 'https://cake-api.3crabs.ru',
// mock: true, // mock: true,
}, },
input: './cakes.json', input: './cakes.json',
+13
View File
@@ -0,0 +1,13 @@
<script setup lang="ts">
navigateTo({
path: '/catalog',
})
</script>
<template>
<div />
</template>
<style scoped>
</style>
+3 -1
View File
@@ -1 +1,3 @@
export { ProductCard, ProductImage } from './ui' export { ProductCard, ProductImage, ProductPrice } from './ui'
export * as productModel from './model'
+1
View File
@@ -0,0 +1 @@
export { useProductPrice } from './use-product-price'
@@ -0,0 +1,75 @@
import { useStorage } from '@vueuse/core'
import type { CrmVariant } from '~/src/shared/model'
import { useCRMGetCart } from '~/src/shared/api/crm/crm'
export function useProductPrice(variants: CrmVariant[], id: string) {
const amount = ref(0)
const currentUnitPrice = ref(0)
const prevUnitPrice = ref()
const { mutateAsync, data } = useCRMGetCart()
const addedItems = useStorage<{
id: string
count: number
}[]>('added-items', [])
const storageProduct = computed(() => addedItems.value.find(item => item.id === id))
const setInitCurrentPrice = () => {
currentUnitPrice.value = Number(variants[0].price) / 100
}
watch(amount, async (value, oldValue) => {
await mutateAsync({
data: [
{
count: String(value),
productId: id,
},
],
})
const newCurrent = Number(data.value?.data.amount) / 100 || 0
const newPrev = Number(data.value?.data.amountOld) / 100 || 0
currentUnitPrice.value = newCurrent
if (newCurrent !== newPrev) {
prevUnitPrice.value = newPrev
}
else {
prevUnitPrice.value = undefined
}
if (value === 0) {
addedItems.value = addedItems.value.filter(item => item.id !== id)
setInitCurrentPrice()
return
}
if (storageProduct.value) {
storageProduct.value.count = value
return
}
addedItems.value.push({
id,
count: value,
})
})
if (storageProduct.value) {
amount.value = storageProduct.value.count
}
else {
setInitCurrentPrice()
}
return {
amount,
currentUnitPrice,
prevUnitPrice,
}
}
+5 -43
View File
@@ -1,4 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { useProductPrice } from '../model'
import ProductImage from './ProductImage.vue' import ProductImage from './ProductImage.vue'
import { InputNumber } from '~/src/shared/ui' import { InputNumber } from '~/src/shared/ui'
import type { CrmProduct } from '~/src/shared/model' import type { CrmProduct } from '~/src/shared/model'
@@ -7,52 +8,13 @@ const props = defineProps<{
product: CrmProduct product: CrmProduct
}>() }>()
const amount = ref(0) const { amount, currentUnitPrice, prevUnitPrice } = useProductPrice(props.product.variants || [], props.product.id!)
const currentUnitPrice = ref(0)
const prevUnitPrice = ref()
function onImageClick() { function onImageClick() {
navigateTo( navigateTo(
`/products/${props.product.id}`, `/products/${props.product.id}`,
) )
} }
function updatePrices() {
if (!props.product.variants?.length) {
return
}
for (let i = 0; i < props.product.variants.length; i++) {
const variant = props.product.variants[i]
let max = Number.POSITIVE_INFINITY
let min = 0
variant.properties?.forEach((property) => {
if (property.name === 'min') {
min = Number(property.value)
}
if (property.name === 'max')
max = Number(property.value)
})
if (amount.value >= min && amount.value <= max) {
currentUnitPrice.value = Number(variant.price) / 100
if (i !== 0) {
prevUnitPrice.value = Number(props.product.variants[i - 1].price) / 100
}
else {
prevUnitPrice.value = undefined
}
return
}
}
}
updatePrices()
</script> </script>
<template> <template>
@@ -67,10 +29,10 @@ updatePrices()
<div>{{ product.name }}</div> <div>{{ product.name }}</div>
<div class="flex items-center gap-3"> <div class="flex items-center gap-3">
<div class="text-2xl font-bold" :class="{ 'text-pink-800': prevUnitPrice }"> <div class="text-2xl font-bold" :class="{ 'text-pink-800': prevUnitPrice }">
{{ currentUnitPrice * (amount || 1) }} {{ currentUnitPrice }}
</div> </div>
<div v-if="prevUnitPrice" class="line-through text-slate-400"> <div v-if="prevUnitPrice" class="line-through text-slate-400">
{{ prevUnitPrice * amount }} {{ prevUnitPrice }}
</div> </div>
</div> </div>
@@ -78,7 +40,7 @@ updatePrices()
<UButton v-if="!amount" class="w-full justify-center" size="xl" @click="amount = 1"> <UButton v-if="!amount" class="w-full justify-center" size="xl" @click="amount = 1">
В корзину В корзину
</UButton> </UButton>
<InputNumber v-else v-model="amount" @update:model-value="updatePrices" /> <InputNumber v-else v-model="amount" />
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -9,7 +9,7 @@ defineProps<{
<template> <template>
<div class="relative flex flex-col justify-between overflow-hidden"> <div class="relative flex flex-col justify-between overflow-hidden">
<img alt="product_img" :src="`https://cake-crm.3crabs.ru${path}`"> <img class="w-full h-full" alt="product_img" :src="`https://cake-api.3crabs.ru${path}`">
<div class="flex gap-1 absolute top-3 left-3"> <div class="flex gap-1 absolute top-3 left-3">
<UBadge v-for="(label, index) in labels" :key="index" :ui="{ rounded: 'rounded-full' }" size="md"> <UBadge v-for="(label, index) in labels" :key="index" :ui="{ rounded: 'rounded-full' }" size="md">
+31
View File
@@ -0,0 +1,31 @@
<script setup lang="ts">
import { useProductPrice } from '../model'
import { InputNumber } from '~/src/shared/ui'
import type { CrmVariant } from '~/src/shared/model'
const props = defineProps<{
variants: CrmVariant[]
productId: string
}>()
const { amount, currentUnitPrice, prevUnitPrice } = useProductPrice(props.variants, props.productId)
</script>
<template>
<div class="bg-gray-50 rounded-2xl border border-slate-200 flex gap-5 justify-between p-5 items-center">
<div class="flex flex-col gap-1 flex-1">
<div class="text-2xl font-bold" :class="{ 'text-pink-800': prevUnitPrice }">
{{ currentUnitPrice }}
</div>
<div v-if="prevUnitPrice" class="line-through text-slate-400">
{{ prevUnitPrice }}
</div>
</div>
<div class="flex-1">
<UButton v-if="!amount" class="w-full justify-center" size="xl" @click="amount = 1">
В корзину
</UButton>
<InputNumber v-else v-model="amount" />
</div>
</div>
</template>
+1
View File
@@ -1,2 +1,3 @@
export { default as ProductCard } from './ProductCard.vue' export { default as ProductCard } from './ProductCard.vue'
export { default as ProductImage } from './ProductImage.vue' export { default as ProductImage } from './ProductImage.vue'
export { default as ProductPrice } from './ProductPrice.vue'
+1
View File
@@ -0,0 +1 @@
export { CartButton } from './ui'
+28
View File
@@ -0,0 +1,28 @@
<script setup lang="ts">
import { useStorage } from '@vueuse/core'
const addedItems = useStorage<{
id: string
count: number
}[]>('added-items', [])
const addedItemsAmount = computed(() => addedItems.value.length)
</script>
<template>
<UButton
variant="soft"
size="xl"
class="border text-black border-pink-500"
icon="heroicons-shopping-cart"
:trailing-icon="addedItemsAmount ? 'badge' : undefined"
>
Корзина
<template #trailing>
<UBadge
v-if="addedItemsAmount"
size="xs" :label="addedItemsAmount"
/>
</template>
</UButton>
</template>
+1
View File
@@ -0,0 +1 @@
export { default as CartButton } from './CartButton.vue'
+12 -3
View File
@@ -1,5 +1,5 @@
<script setup lang="ts"> <script setup lang="ts">
import { ProductImage } from '~/src/entities/product' import { ProductImage, ProductPrice } from '~/src/entities/product'
import { useCRMGetProduct } from '~/src/shared/api/crm/crm' import { useCRMGetProduct } from '~/src/shared/api/crm/crm'
import { Header } from '~/src/widgets/header' import { Header } from '~/src/widgets/header'
import { StandardLayout } from '~/src/shared/ui' import { StandardLayout } from '~/src/shared/ui'
@@ -16,15 +16,24 @@ const { data } = useCRMGetProduct(route.params.id as string)
<Header /> <Header />
</template> </template>
<div class="flex gap-12"> <div class="flex gap-12">
<ProductImage :path="data?.data.product?.images?.[0]" class="w-128 h-128 rounded-[20px]" :labels="data?.data.product?.labels" /> <ProductImage :path="data?.data.product?.images?.[0]" class="w-125 h-125 rounded-[20px]" :labels="data?.data.product?.labels" />
<div class="flex flex-col gap-6"> <div class="flex flex-col gap-6">
<div class="font-bold text-4xl"> <div class="font-bold text-4xl">
{{ data?.data.product?.name }} {{ data?.data.product?.name }}
</div> </div>
<div class="flex flex-col gap-4"> <div class="flex flex-col gap-4">
<div>Надо заполнить на бэке - тяжело тестить</div> <div v-for="characteristic in data?.data.product?.characteristics" :key="characteristic.name" class="flex gap-2 items-end">
<span class="font-bold">{{ characteristic.name }}:</span>
<span class="text-sm font-medium">{{ characteristic.value }}</span>
</div>
</div> </div>
<ProductPrice
v-if="data?.data.product?.id && data.data.product.variants"
:product-id="data.data.product.id"
:variants="data.data.product.variants"
/>
</div> </div>
</div> </div>
<template #footer> <template #footer>
+14 -14
View File
@@ -51,7 +51,7 @@ export const cRMGetCart = (
): Promise<AxiosResponse<CrmCartRsp>> => { ): Promise<AxiosResponse<CrmCartRsp>> => {
crmOrderItem = unref(crmOrderItem); crmOrderItem = unref(crmOrderItem);
return axios.post( return axios.post(
`https://cake-crm.3crabs.ru/cart`, `https://cake-api.3crabs.ru/cart`,
crmOrderItem,options crmOrderItem,options
); );
} }
@@ -99,13 +99,13 @@ const {mutation: mutationOptions, axios: axiosOptions} = options ?? {};
): Promise<AxiosResponse<CrmCatalogRsp>> => { ): Promise<AxiosResponse<CrmCatalogRsp>> => {
return axios.get( return axios.get(
`https://cake-crm.3crabs.ru/catalog`,options `https://cake-api.3crabs.ru/catalog`,options
); );
} }
export const getCRMGetCatalogQueryKey = () => { export const getCRMGetCatalogQueryKey = () => {
return ['https:','cake-crm.3crabs.ru','catalog'] as const; return ['https:','cake-api.3crabs.ru','catalog'] as const;
} }
@@ -151,13 +151,13 @@ export const cRMGetImage = (
): Promise<AxiosResponse<ApiHttpBody>> => { ): Promise<AxiosResponse<ApiHttpBody>> => {
name = unref(name); name = unref(name);
return axios.get( return axios.get(
`https://cake-crm.3crabs.ru/images/${name}`,options `https://cake-api.3crabs.ru/images/${name}`,options
); );
} }
export const getCRMGetImageQueryKey = (name: MaybeRef<string>,) => { export const getCRMGetImageQueryKey = (name: MaybeRef<string>,) => {
return ['https:','cake-crm.3crabs.ru','images',name] as const; return ['https:','cake-api.3crabs.ru','images',name] as const;
} }
@@ -203,7 +203,7 @@ export const cRMOrder = (
): Promise<AxiosResponse<CrmOrderRsp>> => { ): Promise<AxiosResponse<CrmOrderRsp>> => {
crabscrmOrder = unref(crabscrmOrder); crabscrmOrder = unref(crabscrmOrder);
return axios.post( return axios.post(
`https://cake-crm.3crabs.ru/orders`, `https://cake-api.3crabs.ru/orders`,
crabscrmOrder,options crabscrmOrder,options
); );
} }
@@ -251,13 +251,13 @@ const {mutation: mutationOptions, axios: axiosOptions} = options ?? {};
): Promise<AxiosResponse<CrmPositionsRsp>> => { ): Promise<AxiosResponse<CrmPositionsRsp>> => {
id = unref(id); id = unref(id);
return axios.get( return axios.get(
`https://cake-crm.3crabs.ru/positions/${id}`,options `https://cake-api.3crabs.ru/positions/${id}`,options
); );
} }
export const getCRMGetPositionsQueryKey = (id: MaybeRef<string>,) => { export const getCRMGetPositionsQueryKey = (id: MaybeRef<string>,) => {
return ['https:','cake-crm.3crabs.ru','positions',id] as const; return ['https:','cake-api.3crabs.ru','positions',id] as const;
} }
@@ -303,13 +303,13 @@ export const cRMGetProduct = (
): Promise<AxiosResponse<CrmProductRsp>> => { ): Promise<AxiosResponse<CrmProductRsp>> => {
id = unref(id); id = unref(id);
return axios.get( return axios.get(
`https://cake-crm.3crabs.ru/products/${id}`,options `https://cake-api.3crabs.ru/products/${id}`,options
); );
} }
export const getCRMGetProductQueryKey = (id: MaybeRef<string>,) => { export const getCRMGetProductQueryKey = (id: MaybeRef<string>,) => {
return ['https:','cake-crm.3crabs.ru','products',id] as const; return ['https:','cake-api.3crabs.ru','products',id] as const;
} }
@@ -355,13 +355,13 @@ export const cRMGetBreadcrumbs = (
): Promise<AxiosResponse<CrmBreadcrumbsRsp>> => { ): Promise<AxiosResponse<CrmBreadcrumbsRsp>> => {
id = unref(id); id = unref(id);
return axios.get( return axios.get(
`https://cake-crm.3crabs.ru/products/${id}/breadcrumbs`,options `https://cake-api.3crabs.ru/products/${id}/breadcrumbs`,options
); );
} }
export const getCRMGetBreadcrumbsQueryKey = (id: MaybeRef<string>,) => { export const getCRMGetBreadcrumbsQueryKey = (id: MaybeRef<string>,) => {
return ['https:','cake-crm.3crabs.ru','products',id,'breadcrumbs'] as const; return ['https:','cake-api.3crabs.ru','products',id,'breadcrumbs'] as const;
} }
@@ -407,7 +407,7 @@ export const cRMSearch = (
): Promise<AxiosResponse<CrmPositionsRsp>> => { ): Promise<AxiosResponse<CrmPositionsRsp>> => {
params = unref(params); params = unref(params);
return axios.get( return axios.get(
`https://cake-crm.3crabs.ru/search`,{ `https://cake-api.3crabs.ru/search`,{
...options, ...options,
params: {...unref(params), ...options?.params},} params: {...unref(params), ...options?.params},}
); );
@@ -415,7 +415,7 @@ export const cRMSearch = (
export const getCRMSearchQueryKey = (params?: MaybeRef<CRMSearchParams>,) => { export const getCRMSearchQueryKey = (params?: MaybeRef<CRMSearchParams>,) => {
return ['https:','cake-crm.3crabs.ru','search', ...(params ? [params]: [])] as const; return ['https:','cake-api.3crabs.ru','search', ...(params ? [params]: [])] as const;
} }
+3 -8
View File
@@ -1,4 +1,6 @@
<script setup lang="ts"> <script setup lang="ts">
import { CartButton } from '~/src/features/cart'
function goToCatalogPage() { function goToCatalogPage() {
navigateTo({ navigateTo({
path: '/catalog', path: '/catalog',
@@ -17,14 +19,7 @@ function goToCatalogPage() {
Каталог Каталог
</UButton> </UButton>
<UInputMenu placeholder="Поиск по каталогу" size="xl" class="w-full" leading-icon="heroicons-magnifying-glass" /> <UInputMenu placeholder="Поиск по каталогу" size="xl" class="w-full" leading-icon="heroicons-magnifying-glass" />
<UButton variant="soft" size="xl" class="border text-black border-pink-500" icon="heroicons-shopping-cart" trailing-icon="badge"> <CartButton />
Корзина
<template #trailing>
<UBadge
size="xs" label="2"
/>
</template>
</UButton>
</div> </div>
</div> </div>
</template> </template>
+9729
View File
File diff suppressed because it is too large Load Diff