tried fixing infinite loop

This commit is contained in:
Mamalizz
2025-03-27 14:16:53 +03:30
91 changed files with 393 additions and 222 deletions
+23 -5
View File
@@ -41,12 +41,30 @@ jobs:
username: ${{ secrets.SSH_USER }}
password: ${{ secrets.SSH_PASSWORD }}
script: |
cd /root/hshop/
cd /root/hshop/ || { echo "ERROR: دایرکتوری پیدا نشد"; exit 1; }
docker compose down --remove-orphans --timeout 60
docker compose down --remove-orphans --timeout 10
docker compose up --build --detach --remove-orphans
docker image prune -af
docker compose up --build --detach || { echo "ERROR: ارور در بیلد"; exit 1; }
- name: display active containers
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SSH_USER }}
password: ${{ secrets.SSH_PASSWORD }}
script: |
cd /root/hshop/ || { echo "ERROR: دایرکتوری پیدا نشد"; exit 1; }
docker compose ps
- name: clean up server
uses: appleboy/ssh-action@v0.1.6
with:
host: ${{ secrets.SERVER_HOST }}
username: ${{ secrets.SSH_USER }}
password: ${{ secrets.SSH_PASSWORD }}
script: |
docker image prune -af --filter "until=48h" || { echo "ERROR: ارور در پاک کردن images";}
docker builder prune -af --filter "until=48h" || { echo "ERROR: ارور در پاک کردن builder cache";}
-6
View File
@@ -43,10 +43,6 @@ class SendOTPView(APIView):
Code: {otp}"""
sms_api = ghasedak_sms.Ghasedak(api_key="1227eaaddcba72bcb0169b37032cf16ae9ac6ed8b3b7c2768b74e2ee351d1b52gyRe3AGomZRPTNEd")
# response = sms_api.send_single_sms(ghasedak_sms.SendSingleSmsInput(message=message, receptor=phone, line_number='30005006006908', send_date='', client_reference_id=''))
# print(response)
response = sms_api.send_single_sms(
ghasedak_sms.SendSingleSmsInput(
@@ -57,8 +53,6 @@ Code: {otp}"""
)
)
# response = sms_api.send_otp_sms(otp_input)
if response['statusCode'] == 200:
return Response({'detail': f'OTP sent successfully {otp}'}, status=status.HTTP_200_OK)
else:
+5 -5
View File
@@ -241,19 +241,19 @@ AWS_S3_OBJECT_PARAMETERS = {
AZ_IRANIAN_BANK_GATEWAYS = {
"GATEWAYS": {
"ZARINPAL": {
"MERCHANT_CODE": "9cf93a18-dc99-4e6c-8873-d37a8190a027",
"SANDBOX": 0,
"ZIBAL": {
"MERCHANT_CODE": "zibal",
"SANDBOX": True
},
},
"IS_SAMPLE_FORM_ENABLE": True,
"DEFAULT": "ZARINPAL",
"DEFAULT": "ZIBAL",
"CURRENCY": "IRT",
"TRACKING_CODE_QUERY_PARAM": "tc",
"TRACKING_CODE_LENGTH": 16,
"SETTING_VALUE_READER_CLASS": "azbankgateways.readers.DefaultReader",
"BANK_PRIORITIES": [
"ZARINPAL",
"ZIBAL",
],
"IS_SAFE_GET_GATEWAY_PAYMENT": False # better to be True
}
+2 -2
View File
@@ -32,7 +32,7 @@ UNFOLD = {
lambda request: static("rtl.css"),
],
"BORDER_RADIUS": "20px",
"BORDER_RADIUS": "8px",
"SHOW_HISTORY": True,
"SHOW_VIEW_ON_SITE": True,
"ENVIRONMENT": "core.settings.environment_callback",
@@ -47,7 +47,7 @@ UNFOLD = {
"500": "115 115 115",
"600": "82 82 82",
"700": "64 64 64",
"800": "38 38 38",
"800": "42 42 42",
"900": "23 23 23",
"950": "10 10 10"
},
+12 -49
View File
@@ -1,88 +1,51 @@
@layer base {
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Thin.woff2");
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-UltraLight.woff2");
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Light.woff2");
font-family: "Peyda";
src: url("./fonts/peyda/300-PeydaWeb-Light-fanum.woff2");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Regular.woff2");
font-family: "Peyda";
src: url("./fonts/peyda/400-PeydaWeb-Regular-fanum.woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Medium.woff2");
font-family: "Peyda";
src: url("./fonts/peyda/500-PeydaWeb-Medium-fanum.woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-DemiBold.woff2");
font-family: "Peyda";
src: url("./fonts/peyda/600-PeydaWeb-SemiBold-fanum.woff2");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Bold.woff2");
font-family: "Peyda";
src: url("./fonts/peyda/700-PeydaWeb-Bold-fanum.woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-ExtraBold.woff2");
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Black.woff2");
font-family: "Peyda";
src: url("./fonts/peyda/900-PeydaWeb-Black-fanum.woff2");
font-weight: 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-ExtraBlack.woff2");
font-weight: 950;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Heavy.woff2");
font-weight: 1000;
font-style: normal;
font-display: swap;
}
}
@@ -0,0 +1,88 @@
@layer base {
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Thin.woff2");
font-weight: 100;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-UltraLight.woff2");
font-weight: 200;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Light.woff2");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Regular.woff2");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Medium.woff2");
font-weight: 500;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-DemiBold.woff2");
font-weight: 600;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Bold.woff2");
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-ExtraBold.woff2");
font-weight: 800;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Black.woff2");
font-weight: 900;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-ExtraBlack.woff2");
font-weight: 950;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekanXVF";
src: url("./fonts/IranYekanX/IRANYekanX-Heavy.woff2");
font-weight: 1000;
font-style: normal;
font-display: swap;
}
}
+1 -1
View File
@@ -1,3 +1,3 @@
*:not(span){
font-family: 'IRANYekanXVF' !important;
font-family: 'Peyda' !important;
}
+30 -2
View File
@@ -1,6 +1,8 @@
from django.db.models.signals import pre_save
from django.dispatch import receiver
from .models import OrderModel
from account.models import PushSubscription
import ghasedak_sms
@receiver(pre_save, sender=OrderModel)
def order_status_changed(sender, instance, **kwargs):
@@ -9,10 +11,36 @@ def order_status_changed(sender, instance, **kwargs):
if previous.status != instance.status:
send_change_status_notif(instance)
send_change_status_sms(instance)
def send_change_status_notif(order):
pass
def send_change_status_notif(instance):
user_subs = PushSubscription.objects.filter(user=instance.user)
for user_sub in user_subs:
try:
user_sub.send_notif(f'سفارش شما به {instance.get_status_display()} تغییر کرد', f'سفارش شما به {instance.get_status_display()} تغییر کرد', ProductImageModel.objects.all().first().image.url)
except:
print('log later send notif error')
def send_change_status_sms(instance):
sms_api = ghasedak_sms.Ghasedak(api_key="1227eaaddcba72bcb0169b37032cf16ae9ac6ed8b3b7c2768b74e2ee351d1b52gyRe3AGomZRPTNEd")
response = sms_api.send_single_sms(
ghasedak_sms.SendSingleSmsInput(
message=f'سفارش شما به {instance.get_status_display()} تغییر کرد',
receptor=instance.user.phone,
line_number='30005006004095',
client_reference_id=str(instance.user.pk)
)
)
if response['statusCode'] == 200:
print('done log later')
else:
print(f'error: {response}')
def update_cart_price_fields(order):
pass
+1 -2
View File
@@ -202,7 +202,7 @@ class PaymentView(APIView):
factory = bankfactories.BankFactory()
try:
bank = (
factory.create(bank_models.BankType.ZARINPAL)
factory.auto_create()
)
bank.set_request(request)
bank.set_amount(amount)
@@ -220,7 +220,6 @@ class PaymentView(APIView):
except AZBankGatewaysException as e:
print(e)
return Response({'error': str(e)}, status=status.HTTP_400_BAD_REQUEST)
return Response({'gateway_url': bank.redirect_url}, status=status.HTTP_200_OK)
+1 -1
View File
@@ -73,7 +73,7 @@ class ProductVariantSerialzier(serializers.ModelSerializer):
return item['quantity']
return 0
def get_pirce(self, obj):
def get_price(self, obj):
return f'{obj.price:,.0f} تومان'
+1 -1
View File
@@ -35,6 +35,6 @@ const closeModal = () => {
/>
</ToastProvider>
<VueQueryDevtools dir="ltr" buttonPosition="bottom-left" />
<VueQueryDevtools dir="ltr" buttonPosition="bottom-right" />
</div>
</template>
+1 -1
View File
@@ -1,7 +1,7 @@
@layer base {
@font-face {
font-family: "Morabba";
src: url("/fonts/Morabba/IRANYekanX-UltraLight.woff2");
src: url("/fonts/Morabba/Morabba-UltraLight.woff2");
font-weight: 200;
font-style: normal;
font-display: swap;
@@ -109,7 +109,7 @@ watch(
(newValue) => {
if (!isEditing.value) {
addressData.value.phone =
newValue == "بله" ? account.value?.phone : "";
newValue == "بله" ? account.value!.phone : "";
}
},
{
@@ -21,18 +21,17 @@ const discountCode = ref(cart.value?.discount_code?.code || "");
const {
mutateAsync: submitDiscountCode,
isPending: submitDiscountCodeIsPending,
isPending: submitDiscountCodeIsPending
} = useSubmitDiscountCode();
const {
mutateAsync: deleteDiscountCode,
isPending: deleteDiscountCodeIsPending,
isPending: deleteDiscountCodeIsPending
} = useDeleteDiscountCode();
// computed
const nextPage: ComputedRef<{ name: string; label: string } | undefined> =
computed(() => route.meta.nextPage);
const nextPage = computed(() => route.meta.nextPage as { name: string; label: string } | undefined);
const hasSubmittedDiscountCode = computed(() => !!cart.value?.discount_code);
@@ -49,11 +48,11 @@ const handleSubmitDiscountCode = () => {
addToast({
message: "خطایی در ثبت کد تخفیف رخ داد",
options: {
status: "error",
},
status: "error"
}
});
discountCode.value = "";
},
}
}
);
};
@@ -68,11 +67,11 @@ const handleDeleteDiscountCode = () => {
addToast({
message: "خطایی در حذف کد تخفیف رخ داد",
options: {
status: "error",
},
status: "error"
}
});
discountCode.value = "";
},
}
});
};
</script>
@@ -27,7 +27,7 @@ const { isLoading: cartImageIsLoading } = useImage({
v-if="!cartImageIsLoading"
class="size-[3.5rem] shrink-0 rounded-100 border border-gray-300 overflow-hidden"
>
<img :src="image" alt="product" class="object-conver" />
<NuxtImg :src="image" alt="product" class="object-conver" />
</div>
<Skeleton
v-else
@@ -44,7 +44,7 @@ const { data: cart, isLoading: cartIsLoading } = useGetCartOrders();
</div>
<div
v-if="cart?.items.length > 5"
v-if="cart && cart.items.length > 5"
class="h-7 flex-center col-span-full lg:hidden"
>
<button
+1 -1
View File
@@ -121,7 +121,7 @@ watch(
v-if="!cartImageIsLoading"
class="size-[4rem] lg:size-[12rem] aspect-square shrink-0 rounded-xl border border-slate-200 overflow-hidden"
>
<img
<NuxtImg
:src="data.product.image"
class="object-cover size-full"
alt="product"
+4 -3
View File
@@ -6,7 +6,7 @@ import { useImage } from "@vueuse/core";
// types
type Props = {
src: string;
src: string | null | undefined;
alt: string;
};
@@ -18,7 +18,7 @@ const { src } = toRefs(props);
// state
const { isLoading } = useImage({ src: src.value });
const { isLoading } = useImage({ src: src.value ?? '' });
</script>
<template>
@@ -26,11 +26,12 @@ const { isLoading } = useImage({ src: src.value });
class="flex-center size-full select-none rounded-full align-middle overflow-hidden inset-shadow-black/20 inset-shadow-sm"
>
<Skeleton
v-if="isLoading"
v-if="isLoading && !!src"
class="w-full !h-[110%] !rounded-full aspect-square"
/>
<template v-else>
<AvatarImage
v-if="!!src"
class="!size-full rounded-full object-cover"
:src="src"
:alt="alt"
+1 -1
View File
@@ -36,7 +36,7 @@ const remaining = computed(() => items.value.length - max.value);
:class="index < 0 ? '' : ''"
:style="{ width: size, height: size, zIndex: index + 2 }"
>
<img
<NuxtImg
:src="item"
alt="avatar"
class="rounded-full object-cover w-full h-full"
+2 -2
View File
@@ -54,7 +54,7 @@ const createdAt = usePersianTimeAgo(new Date(date.value));
{{ category.name }}
</Tag>
<img
<NuxtImg
:src="image"
class="group-hover:scale-105 transition-transform duration-200 absolute size-full object-cover z-10"
alt=""
@@ -104,7 +104,7 @@ const createdAt = usePersianTimeAgo(new Date(date.value));
</div>
</div>
<img
<NuxtImg
v-if="variant === 'lg'"
:src="image"
class="group-hover:scale-105 transition-transform duration-200 absolute size-full object-cover z-10"
+1 -1
View File
@@ -25,7 +25,7 @@ const { colorObject } = useImageColor(`#category-image-${id.value}`);
<template>
<NuxtLink :to="`/products?category=${id}`">
<div class="group relative rounded-150 overflow-hidden w-full aspect-square bg-white brightness-[97%]">
<img
<NuxtImg
:id="`category-image-${id}`"
class="group-hover:scale-105 transition-transform duration-200 absolute object-contain size-full"
:src="picture"
+2 -2
View File
@@ -37,7 +37,7 @@ const value = ref<OptionChildren>();
watch(
() => value.value,
(newValue: OptionChildren) => {
(newValue) => {
if (!!newValue) {
emit("update:modelValue", newValue.id);
}
@@ -51,7 +51,7 @@ watch(
.flatMap((option) => option.children)
.find((child) => child.id == newValue);
value.value = target || null;
value.value = target || undefined;
},
{ immediate: true }
);
+4 -4
View File
@@ -35,8 +35,8 @@ const fileLimit = 1024 * 1024 * 2;
// methods
const onDrop = (files: File[] | null) => {
if (modelValue.value.length < 3) {
files?.forEach((file, index) => {
if (modelValue.value.length < 3 && files) {
files.forEach((file, index) => {
if (file.size > fileLimit) {
files.splice(index, 1);
addToast({
@@ -51,7 +51,7 @@ const onDrop = (files: File[] | null) => {
emit("update:modelValue", [...files.slice(0, 3)]);
} else {
if (modelValue.value.length + files.length <= 3) {
files?.forEach((item) => {
files.forEach((item) => {
emit("change", item);
resetFileDialog();
});
@@ -110,7 +110,7 @@ const removeAttachment = (id: number) => {
<div class="flex flex-col w-full h-max gap-5 pt-8">
<div
ref="dropZoneRef"
@click="openDialog"
@click="openDialog()"
class="bg-slate-50 relative flex-col-center w-full transition-all text-black/50 gap-3 h-[20rem] border border-dashed rounded-xl cursor-pointer select-none"
:class="{
'border-black': isOverDropZone,
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<div class="bg-black relative overflow-hidden">
<img src="/img/footer-bg.jpg" alt="" class="absolute z-10 object-cover opacity-45" />
<NuxtImg src="/img/footer-bg.jpg" alt="" class="absolute z-10 object-cover opacity-45" />
<div class="flex flex-col gap-4 items-center justify-center relative z-20">
@@ -55,9 +55,9 @@ onMounted(() => {
id="loading-overlay"
class="fixed inset-0 size-full z-9999 flex-center bg-black"
>
<img
<NuxtImg
id="loading-overlay-image"
src="/img/heymlz-loading-1.gif"
src="/img/heymlz/heymlz-loading-1.gif"
class="opacity-0 scale-70 absolute z-20"
alt=""
/>
@@ -11,22 +11,22 @@ type Highlight = {
const highlights = ref<Highlight[]>([
{
icon: "/img/footer-support.svg",
icon: "/img/heymlz/footer-support.svg",
title: "خدمات مشتری",
description: "پشتیبانی استثنایی، راه‌حل‌های پایدار برای شما",
},
{
icon: "/img/footer-send.svg",
icon: "/img/heymlz/footer-send.svg",
title: "ارسال سریع و رایگان",
description: "ارسال رایگان برای سفارش‌های بالای ۱۵۰ دلار",
},
{
icon: "/img/footer-share.svg",
icon: "/img/heymlz/footer-share.svg",
title: "معرفی به دوستان",
description: "ما را به دوستان خود معرفی کنید",
},
{
icon: "/img/footer-security.svg",
icon: "/img/heymlz/footer-security.svg",
title: "پرداخت امن",
description: "پرداخت شما به‌صورت امن پردازش می‌شود",
},
@@ -40,7 +40,7 @@ const highlights = ref<Highlight[]>([
>
<template v-for="(highlight, index) in highlights" :key="index">
<div class="flex flex-col-center gap-[.75rem] px-5">
<img
<NuxtImg
:src="highlight.icon"
class="size-[70px] md:size-[90px]"
alt=""
@@ -54,7 +54,7 @@ const changeSlide = (id: number) => {
class="bg-white brightness-[97%] w-full relative aspect-square overflow-hidden rounded-[12px] md:rounded-200"
>
<Transition name="zoom" mode="out-in">
<img
<NuxtImg
:key="selectedSlideDetail.id"
class="size-full absolute object-contain"
:src="selectedSlideDetail.image"
@@ -84,7 +84,7 @@ const changeSlide = (id: number) => {
"
class="active:scale-95 hover:border-slate-200 transition-all cursor-pointer brightness-[97%] bg-white aspect-square border-2 rounded-[12px] md:rounded-200 w-full overflow-hidden relative"
>
<img
<NuxtImg
class="absolute object-cover size-full"
:src="slide.image"
:alt="String(slide.id)"
@@ -12,12 +12,12 @@ const { selectedVariant } = inject("productVariant") as ProductVariantProvideTyp
<div class="w-full flex flex-col">
<AccordionRoot
class="w-full last:border-b last:border-slate-200"
:default-value="'item' + selectedVariant.details[0].detail_category"
:default-value="'item' + selectedVariant!.details[0].detail_category"
type="single"
:collapsible="true"
>
<AccordionItem
v-for="detailItem in selectedVariant.details"
v-for="detailItem in selectedVariant!.details"
:value="'item' + detailItem.detail_category"
class="overflow-hidden"
>
@@ -36,7 +36,7 @@ const { colorObject } = useImageColor(`#product-image-${id.value}`);
<div
class="group relative size-full aspect-square rounded-xl @[280px]:rounded-2xl bg-white brightness-[98%] overflow-hidden p-6"
>
<img
<NuxtImg
:id="`product-image-${id}`"
:src="picture"
class="group-hover:scale-105 transition-transform duration-200 size-full object-contain absolute inset-0"
@@ -20,7 +20,7 @@ const { picture, price, title, color } = toRefs(props);
<div class="max-w-[500px] w-full h-[116px] flex items-center justify-between py-2 pe-6 ps-2 bg-white rounded-150">
<div class="flex items-center gap-4">
<div class="relative size-[100px] rounded-100 overflow-hidden border-[0.5px] border-slate-200">
<img :src="picture" :alt="title" class="object-cover absolute" />
<NuxtImg :src="picture" :alt="title" class="object-cover absolute" />
</div>
<div class="flex flex-col gap-1.5">
<span class="typo-sub-h-md text-black">{{ title }}</span>
@@ -9,7 +9,7 @@ import { PRODUCT_RANGE } from "~/constants";
// state
const params: GetProductsFilters = inject("params");
const params = inject("params") as GetProductsFilters;
const sort_filter = ref([
{ title: "جدیدترین ها", value: "newest" },
@@ -22,12 +22,12 @@ const sliderValue = ref([
params.price_lte ?? PRODUCT_RANGE.max,
]);
const has_discount = ref(JSON.parse(params.has_discount ?? false));
const in_stock = ref(JSON.parse(params.in_stock ?? false));
const has_discount = ref(Boolean(params.has_discount) ?? false);
const in_stock = ref(Boolean(params.in_stock) ?? false);
const sliderValueDebounced = refDebounced(sliderValue, 1000);
const filtersSuccessMessage = ref<{ title: string; status: string } | null>("");
const filtersSuccessMessage = ref<{ title: string; status: string } | null>(null);
// queries
+20 -8
View File
@@ -25,14 +25,14 @@ const onSwiper = (swiper: SwiperClass) => {
class="flex flex-col justify-center gap-4 bg-black h-[150svh] relative overflow-hidden"
>
<div class="w-full flex justify-center items-center relative z-10">
<span class="text-white typo-h-6 md:typo-h-5 lg:typo-h-4">
دسته بندی ها
</span>
</div>
<!-- <div class="w-full flex justify-center items-center relative z-10">-->
<!-- <span class="text-white typo-h-6 md:typo-h-5 lg:typo-h-4">-->
<!-- دسته بندی ها-->
<!-- </span>-->
<!-- </div>-->
<img
src="/img/gradient-2.png"
<NuxtImg
src="/img/categories-gradient.png"
class="animate-spin [animation-duration:16s] object-cover absolute size-full brightness-45 scale-115 aspect-square"
:style="{
maskImage: 'radial-gradient(black, transparent 50%)'
@@ -41,6 +41,17 @@ const onSwiper = (swiper: SwiperClass) => {
/>
<div class="w-full my-20 relative">
<video
class="aspect-square w-[450px] translate-[-253px] absolute left-1/2 -translate-x-1/2 z-10"
:style="{
filter: 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.4))',
}"
src="/video/heymlz/heymlz-seat-2.webm"
autoplay
playsinline
webkit-playsinline
muted
/>
<Swiper
:loop="true"
:centered-slides="true"
@@ -98,7 +109,8 @@ const onSwiper = (swiper: SwiperClass) => {
<div class="w-full flex justify-center items-center">
<NuxtLink to="/category">
<Button variant="solid" class="invert rounded-full max-xs:typo-label-sm !px-4 xs:!px-8" end-icon="ci:arrow-left">
<Button variant="solid" class="invert rounded-full max-xs:typo-label-sm !px-4 xs:!px-8"
end-icon="ci:arrow-left">
مشاهده همه دسته ها
</Button>
</NuxtLink>
+1 -1
View File
@@ -193,7 +193,7 @@ onUnmounted(() => {
/>
</template>
<img
<NuxtImg
v-else
class="absolute inset-0 size-full object-cover"
:src="slide.image!"
@@ -28,7 +28,7 @@ const onSwiper = (swiper: SwiperClass) => {
<template>
<div class="relative max-h-[700px] flex justify-center items-center h-svh w-full">
<img
<NuxtImg
class="absolute size-full object-cover"
src="/img/hero-bg.jpg"
alt=""
+18 -2
View File
@@ -59,7 +59,7 @@ watch(
class="rounded-200 overflow-hidden h-[70svh] md:h-[80svh] relative"
>
<Transition name="fade">
<img
<NuxtImg
v-if="activeSlideVideo !== 'right'"
:src="homeData!.difreance_section.image1"
class="select-none absolute size-full object-cover brightness-[95%]"
@@ -77,8 +77,9 @@ watch(
</Transition>
<div class="absolute size-full right-0 w-full">
<Transition name="fade">
<img
<NuxtImg
v-if="activeSlideVideo !== 'left'"
:src="homeData!.difreance_section.image2"
class="overlay-image select-none absolute object-cover size-full brightness-[95%]"
@@ -94,12 +95,27 @@ watch(
class="overlay-image select-none absolute object-cover size-full brightness-[95%]"
/>
</Transition>
<video
src="/video/heymlz/heymlz-pulling.webm"
autoplay
muted
playsinline
webkit-playsinline
class="size-[300px] absolute translate-x-[-100px] z-10 top-[32%] -translate-y-1/2"
:style="{
left: `${clipPathPercent}%`,
filter: 'drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.3))'
}"
/>
<div
:style="{
left: `${clipPathPercent}%`,
}"
class="select-none w-2 h-full bg-black absolute left-0 flex items-center justify-center"
>
<div
ref="draggableEl"
class="cursor-grab hover:scale-115 transition-transform rounded-full absolute bg-black size-11 flex items-center justify-center"
@@ -93,7 +93,7 @@ onUnmounted(() => {
class="showcase-slide origin-bottom absolute size-full bg-transparent flex items-center justify-center"
>
<img
<NuxtImg
class="w-[280px] xs:w-[350px] lg:w-[500px] xl:w-[650px] z-20 mb-30 sm:mb-20 lg:mb-30"
:src="slide.image"
:style="{
@@ -29,17 +29,17 @@ const {
isPending: isChatPending,
isFetchingNextPage: isNextChatPagePending,
hasNextPage: hasMoreChat,
fetchNextPage: loadMoreChat,
fetchNextPage: loadMoreChat
} = useGetChat(id, isOpen);
const isCreateMessagePending = useIsMutating({
mutationKey: [MUTATION_KEYS.create_chat],
mutationKey: [MUTATION_KEYS.create_chat]
});
const canLoadMoreChat = ref(false);
const isChatScrollLocked = useScrollLock(chatContainerEl);
const { y: chatContainerScrollY } = useScroll(chatContainerEl, {
behavior: "smooth",
behavior: "smooth"
});
useInfiniteScroll(
@@ -56,7 +56,7 @@ useInfiniteScroll(
distance: 10,
direction: "top",
throttle: 1000,
canLoadMore: () => canLoadMoreChat.value,
canLoadMore: () => canLoadMoreChat.value
}
);
@@ -116,7 +116,7 @@ whenever(
}, 2000);
},
{
once: true,
once: true
}
);
</script>
@@ -125,7 +125,7 @@ whenever(
<Transition name="fade-right-to-left">
<div
v-if="isOpen"
class="fixed right-8 bottom-8 w-[450px] transition-all duration-500 overflow-hidden h-[700px] rounded-250 shadow-2xl shadow-black/30 pt-[40px] bg-white"
class="fixed z-50 right-8 bottom-8 w-[450px] transition-all duration-500 overflow-hidden h-[700px] rounded-250 shadow-2xl shadow-black/30 pt-[40px] bg-white"
>
<CloseButton :disabled="!!isCreateMessagePending" />
@@ -187,6 +187,7 @@ whenever(
class="text-black p-4.5 size-full flex justify-center items-center"
v-else
>
<img class="size-[50px]" src="/img/heymlz/heymlz-idle.gif" alt="" />
Please sign in first
</div>
</div>
@@ -13,18 +13,18 @@ provide("isOpen", {
isOpen,
closeChat,
});
</script>
<template>
<button
v-if="!isOpen"
@click="isOpen = !isOpen"
class="cursor-pointer fixed shadow-xl shadow-black/30 right-8 bottom-8 bg-black size-[70px] flex justify-center items-center rounded-full"
class="cursor-pointer z-50 fixed shadow-xl shadow-black/30 right-8 bottom-8 bg-black size-[70px] flex justify-center items-center rounded-full"
>
<Icon
name="streamline:artificial-intelligence-spark"
class="**:stroke-white"
size="26"
class="**:stroke-white size-[26px]"
/>
</button>
@@ -80,7 +80,7 @@ onMounted(() => {
>
<img
v-if="!reverse"
src="/public/img/hero-bg.jpg"
src="/img/footer-bg.jpg"
class="size-full object-cover absolute"
alt="profile"
/>
@@ -1,8 +1,10 @@
<script lang="ts" setup>
// provide / inject
// import
import type { ProductVariantProvideType } from "~/pages/product";
import type { ProductVariantProvideType } from "~/pages/product/[id].vue";
// provide / inject
const { selectedVariant } = inject("productVariant") as ProductVariantProvideType;
@@ -26,14 +28,14 @@ const { selectedVariant } = inject("productVariant") as ProductVariantProvideTyp
class="w-full grid grid-cols-2 gap-y-[1.5rem] sm:gap-x-[3rem]"
>
<div
v-for="inPackItem in selectedVariant.in_pack_items"
v-for="inPackItem in selectedVariant!.in_pack_items"
class="w-full flex-col-center gap-[.75rem]"
>
<div
class="size-[6.25rem] rounded-full border-slate-200 bg-white flex-center"
>
<div class="size-11 relative">
<img
<NuxtImg
class="size-full absolute object-cover"
:src="inPackItem.cover"
:alt="inPackItem.item_title"
+1 -1
View File
@@ -3,7 +3,7 @@
// import
import useGetProduct from "~/composables/api/product/useGetProduct";
import type { ProductVariantProvideType } from "~/pages/product/types";
import type { ProductVariantProvideType } from "~/pages/product/[id].vue";
// state
@@ -54,7 +54,7 @@ const username = computed(() => {
:class="is_user ? 'rounded-br-none' : 'rounded-bl-none'"
>
<div class="w-2/12 flex items-start justify-start">
<img :src="profile" class="size-16 rounded-full" />
<NuxtImg :src="profile" class="size-16 rounded-full" />
</div>
<div class="w-10/12 flex flex-col items-start pt-2">
@@ -6,7 +6,7 @@ import { usePersianTimeAgo } from "~/composables/global/usePersianTimeAgo";
// types
type Props = {
data: Ticket;
data: Omit<Ticket, "messages">;
};
// props
@@ -2,7 +2,7 @@
import { useQuery } from "@tanstack/vue-query";
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
import { sanitize } from "isomorphic-dompurify";
import sanitizeHtml from 'sanitize-html';
// types
@@ -27,8 +27,8 @@ const useGetArticle = (id: number | string | undefined) => {
select: (article) => {
const copyOfArticle = { ...article };
copyOfArticle.summery = sanitize(copyOfArticle.summery);
copyOfArticle.content = sanitize(copyOfArticle.content);
copyOfArticle.summery = sanitizeHtml(copyOfArticle.summery);
copyOfArticle.content = sanitizeHtml(copyOfArticle.content);
return copyOfArticle;
}
@@ -76,10 +76,9 @@ const useCreateChatMessage = (queryClient: QueryClient) => {
onSuccess: (response) => {
queryClient.setQueryData<InfiniteData<ApiPaginated<Chat>>>(
[QUERY_KEYS.chat],
(oldData) => {
(oldData : any) => {
if (oldData) {
const lastPage =
oldData!.pages[oldData!.pages.length - 1];
const lastPage = oldData!.pages[oldData!.pages.length - 1];
return {
pages: [
@@ -14,6 +14,7 @@ export type GetAllOrdersRequest = {
};
const useGetAllOrders = (params: ComputedRef<GetAllOrdersRequest>) => {
// state
const { $axios: axios } = useNuxtApp();
@@ -2,7 +2,7 @@
import { useQuery } from "@tanstack/vue-query";
import { API_ENDPOINTS, QUERY_KEYS } from "~/constants";
import { sanitize } from "isomorphic-dompurify";
import sanitizeHtml from 'sanitize-html';
// types
@@ -27,7 +27,7 @@ const useGetProduct = (id: string | number | undefined) => {
select: (product) => {
const copyOfProduct = { ...product };
copyOfProduct.description = sanitize(copyOfProduct.description);
copyOfProduct.description = sanitizeHtml(copyOfProduct.description);
copyOfProduct.variants = copyOfProduct.variants.sort((a, b) => b.in_stock - a.in_stock);
+1 -1
View File
@@ -14,7 +14,7 @@ export const useImageColor = (img: string) => {
try {
const color = await fac.getColorAsync(imageEl);
isPending.value = false;
colorObject.value = color;
colorObject.value = color
} catch (e) {
isPending.value = false;
}
+1 -1
View File
@@ -8,7 +8,7 @@ const route = useRoute();
// computed
const pageTitle = computed(() => route.meta.pageTitle);
const prevPage = computed(() => route.meta.prevPage);
const prevPage = computed(() => route.meta.prevPage as { name: string, label: string } | undefined);
// queries
+9
View File
@@ -74,6 +74,7 @@ export default defineNuxtConfig({
"@vueuse/nuxt",
"@formkit/auto-animate/nuxt",
"@vite-pwa/nuxt",
"@nuxt/image",
],
pwa: {
@@ -110,6 +111,14 @@ export default defineNuxtConfig({
},
},
typescript: {
typeCheck: false,
},
image: {
quality: 65,
},
runtimeConfig: {
public: {
API_BASE_URL: process.env.API_BASE_URL,
+10 -3
View File
@@ -6,6 +6,7 @@
"start": "node .output/server/index.mjs",
"build": "nuxt build",
"dev": "nuxt dev",
"lint": "nuxi typecheck",
"dev-network": "nuxi dev --host",
"dev-o": "nuxt dev -- -o",
"test": "vitest",
@@ -16,10 +17,12 @@
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@nuxt/icon": "^1.10.3",
"@nuxt/image": "^1.10.0",
"@nuxtjs/google-fonts": "^3.2.0",
"@tanstack/vue-query": "^5.62.2",
"@tanstack/vue-query-devtools": "^5.62.3",
"@vite-pwa/nuxt": "^0.10.6",
"@vue/language-server": "^2.2.8",
"@vuelidate/core": "^2.0.3",
"@vuelidate/validators": "^2.0.4",
"@vueuse/integrations": "^12.7.0",
@@ -30,10 +33,10 @@
"fast-average-color": "^9.4.0",
"gsap": "^3.12.7",
"highlight.js": "^11.11.1",
"isomorphic-dompurify": "^2.22.0",
"jalali-ts": "^8.0.0",
"nuxt": "^3.15.4",
"reka-ui": "^1.0.0-alpha.6",
"sanitize-html": "^2.15.0",
"swiper": "^11.2.4",
"universal-cookie": "^7.2.2",
"vue": "latest",
@@ -47,9 +50,13 @@
},
"devDependencies": {
"@tailwindcss/postcss": "^4.0.9",
"@types/masonry-layout": "^4.2.8",
"@types/node": "^22.13.11",
"@types/sanitize-html": "^2.13.0",
"@types/web-push": "^3.6.4",
"autoprefixer": "^10.4.20",
"postcss": "^8.5.3",
"tailwindcss": "^4.0.9"
"tailwindcss": "^4.0.9",
"typescript": "^5.8.2",
"vue-tsc": "^2.2.8"
}
}
+2 -2
View File
@@ -28,7 +28,7 @@ if (response.isError) {
<template>
<div class="container">
<div class="w-full h-[80svh] rounded-3xl relative overflow-hidden">
<img class="absolute object-cover size-full" :alt="article!.title" :src="article!.cover_image" />
<NuxtImg class="absolute object-cover size-full" :alt="article!.title" :src="article!.cover_image" />
<div class="absolute bg-linear-to-t from-black/75 to-transparent size-full" />
<div class="absolute pl-10 right-10 bottom-10 flex flex-col gap-6">
<h1 class="typo-h-4 text-white pl-8">
@@ -48,7 +48,7 @@ if (response.isError) {
class="w-fit pr-2 pl-5 h-[50px] rounded-full flex items-center justify-center gap-3 bg-white">
<div
class="relative flex items-center justify-center rounded-full overflow-hidden size-[35px]">
<img
<NuxtImg
class="size-full object-cover absolute"
:src="article!.author.profile_photo"
alt="article-author"
+1 -1
View File
@@ -76,7 +76,7 @@ const selectedGateway = ref<PaymentGateway>(paymentGateways.value[0]);
class="w-full p-5 border rounded-xl flex flex-col gap-4 transition-all cursor-pointer"
>
<div class="aspect-square flex-center">
<img :src="gateway.picture" class="object-cover" />
<NuxtImg :src="gateway.picture" class="object-cover" />
</div>
<span class="typo-label-sm text-black">
{{ gateway.title }}
+1 -1
View File
@@ -133,7 +133,7 @@ const contactWays = ref([
</div>
</div>
<div class="w-4/12 h-full bg-red-300">
<img src="/mlz.jpeg" class="-mt-16" />
<NuxtImg src="/mlz.jpeg" class="-mt-16" />
</div>
</div>
</div>
+2 -1
View File
@@ -15,6 +15,7 @@ const disableLoadingOverlay = useState("disableLoadingOverlay", () => false);
const response = await suspense();
if (response.isError) {
console.log(response);
throw createError({
statusCode: 500,
statusMessage: `Landing error : ${response.error.message}`
@@ -41,7 +42,7 @@ onMounted(() => {
/>
<Categories class="mt-40" />
<Brands />
<MostRecentComments />
<!-- <MostRecentComments />-->
<ClientOnly>
<LatestStories class="mb-20" />
</ClientOnly>
+4 -4
View File
@@ -123,16 +123,16 @@ watch(
</div>
</ul>
<div v-else class="w-full h-max">
<div v-if="!products?.length" class="flex flex-grow w-full">
<div v-if="!products!.length" class="flex flex-grow w-full">
<Placeholder title="محصولی یافت نشد :(" icon="bi:search" />
</div>
<ProductsGrid
:with-header="false"
:products="products"
:products="products!"
class="!p-0"
/>
<div v-if="data?.count > 10" class="w-full flex-center py-10">
<Pagination :items="paginationData" :total="data?.count" />
<div v-if="data && paginationData && data.count > 10" class="w-full flex-center py-10">
<Pagination :items="paginationData" :total="data.count" />
</div>
</div>
</div>
+3 -3
View File
@@ -109,7 +109,7 @@ const formRules = computed(() => {
};
});
const formValidator$ = useVuelidate(formRules, personalData);
const formValidator$ = useVuelidate(formRules, personalData as any);
// methods
@@ -195,7 +195,7 @@ const handleSubmit = (withValidation: boolean) => {
با اولین خریدتون هوش مصنوعی وبسایتمون واستون یک
بایوگرافی درست میکنه :)
</span>
<span
<div
class="flex-center border border-yellow-500 pe-3.5 ps-1 w-max rounded-full"
>
<div class="rounded-full p-2">
@@ -208,7 +208,7 @@ const handleSubmit = (withValidation: boolean) => {
<span class="text-xs text-yellow-500"
>جزو ۳ مشتری برتر</span
>
</span>
</div>
</div>
</div>
@@ -12,7 +12,7 @@ definePageMeta({
// state
const params: GetAllOrdersRequest = useUrlSearchParams("history");
const params = useUrlSearchParams("history") as GetAllOrdersRequest;
const filters = computed(() => {
return {
@@ -91,12 +91,12 @@ const purchases = computed(() => {
return data.value?.results.flat();
});
const hasPurchases = computed(() => data.value?.count > 0);
const hasPurchases = computed(() => data.value && data.value.count > 0);
const hasFilters = computed(() =>
Object.keys(params)
.filter((key) => key != "page")
.some((key) => params[key] != undefined)
.some((key) => (params as any)[key] != undefined)
);
const paginationData = computed(() => {
@@ -246,8 +246,8 @@ const clearFilters = () => {
</template>
</Table>
<div v-if="data?.count > 10" class="w-full flex-center py-10">
<Pagination :items="paginationData" :total="data?.count" />
<div v-if="data && paginationData && data.count > 10" class="w-full flex-center py-10">
<Pagination :items="paginationData" :total="data.count" />
</div>
</div>
</div>
+4 -4
View File
@@ -14,7 +14,7 @@ definePageMeta({
// state
const params: GetAllTicketsRequest = useUrlSearchParams("history");
const params = useUrlSearchParams("history") as GetAllTicketsRequest;
const filters = computed(() => {
return {
@@ -72,12 +72,12 @@ const tickets = computed(() => {
return data.value?.results.flat();
});
const hasTickets = computed(() => data.value?.count > 0);
const hasTickets = computed(() => data.value && data.value.count > 0);
const hasFilters = computed(() =>
Object.keys(params)
.filter((key) => key != "page")
.some((key) => params[key] != undefined)
.some((key) => (params as any)[key] != undefined)
);
const paginationData = computed(() => {
@@ -227,7 +227,7 @@ const clearFilters = () => {
</template>
</Table>
<div v-if="data?.count > 7" class="w-full flex-center py-10">
<div v-if="data && paginationData && data.count > 7" class="w-full flex-center py-10">
<Pagination :items="paginationData" :total="data?.count" />
</div>
</div>
+10 -4
View File
@@ -159,10 +159,16 @@ const resetForm = () => {
}"
/>
<div class="flex items-center justify-center flex-col size-full translate-y-[-80px]">
<img
class="aspect-square w-[300px] translate-y-[100px] animate-fade-in"
src="/img/heymlz-seat.gif"
alt=""
<video
class="aspect-square w-[450px] translate-y-[197px] animate-fade-in"
src="/video/heymlz/heymlz-seat-2.webm"
:style="{
filter: 'drop-shadow(0px 4px 20px rgba(0, 0, 0, 0.15))'
}"
autoplay
playsinline
webkit-playsinline
muted
/>
<div
+26 -16
View File
@@ -3,7 +3,8 @@
// import
import hljs from "highlight.js";
import javascript from "highlight.js/lib/languages/javascript";
import json from "highlight.js/lib/languages/json";
import xml from "highlight.js/lib/languages/xml";
import "highlight.js/styles/atom-one-dark.css";
import LogDate from "~/components/server-logs/LogDate.vue";
import { useQuery } from "@tanstack/vue-query";
@@ -11,7 +12,7 @@ import { useQuery } from "@tanstack/vue-query";
// meta
definePageMeta({
middleware : "check-is-debug",
middleware: "check-is-debug",
layout: "none"
});
@@ -22,7 +23,7 @@ const { $axios: axios } = useNuxtApp();
const { data: serverLogs, isFetching, suspense } = useQuery({
queryKey: ["server-logs"],
queryFn: async () => {
const response = await axios.get("http://localhost:3000/api/server-logger");
const response = await axios.get<AxiosLogType[]>("http://localhost:3000/api/server-logger");
return response.data.reverse();
},
refetchInterval: 5000,
@@ -41,7 +42,9 @@ const logIcon = (status: number) => {
// lifecycle
onMounted(() => {
hljs.registerLanguage("json", javascript);
hljs.registerLanguage("json", json);
hljs.registerLanguage("xml", xml);
hljs.highlightAll();
});
@@ -102,34 +105,41 @@ onMounted(() => {
<details class="text-white">
<summary class="cursor-pointer select-none">Details :</summary>
<div class="flex flex-col gap-2 mt-2 ml-4">
<details class="text-white">
<details
v-if="log.response && typeof log.response === 'string' && (log.response.startsWith('<!DOCTYPE html>') || log.response.startsWith('<html>'))"
class="text-white"
>
<summary class="cursor-pointer select-none">Preview :</summary>
<iframe class="w-full h-[500px] bg-white" :srcdoc="log.response"></iframe>
</details>
<details v-if="log.response" class="text-white">
<summary class="cursor-pointer select-none">Response :</summary>
<pre>
<code class="language-json">
{{ log.response }}
<pre style="tab-size: 2">
<code class="whitespace-pre-wrap">
{{ String(log.response) }}
</code>
</pre>
</details>
<details class="text-white">
<summary class="cursor-pointer select-none">Req Headers :</summary>
<pre class="whitespace-pre-line">
<code class="language-json">
<pre style="tab-size: 2">
<code class="whitespace-pre-line">
{{ log.requestHeaders }}
</code>
</pre>
</details>
<details class="text-white">
<summary class="cursor-pointer select-none">Res Headers :</summary>
<pre>
<code class="language-json">
<pre style="tab-size: 2">
<code class="whitespace-pre-line">
{{ log.responseHeaders }}
</code>
</pre>
</details>
<details v-if="log.payload" class="text-white">
<summary class="cursor-pointer select-none">Payload :</summary>
<pre>
<code class="language-json">
<pre style="tab-size: 2">
<code class="whitespace-pre-line">
{{ log.payload }}
</code>
</pre>
@@ -151,11 +161,11 @@ onMounted(() => {
@keyframes log-fade-in {
from {
opacity : 0;
opacity: 0;
scale: 0.8;
}
to {
opacity : 1;
opacity: 1;
scale: 1;
}
}

Before

Width:  |  Height:  |  Size: 7.9 MiB

After

Width:  |  Height:  |  Size: 7.9 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 MiB

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 79 KiB

Before

Width:  |  Height:  |  Size: 10 MiB

After

Width:  |  Height:  |  Size: 10 MiB

Before

Width:  |  Height:  |  Size: 943 KiB

After

Width:  |  Height:  |  Size: 943 KiB

Before

Width:  |  Height:  |  Size: 1.1 MiB

After

Width:  |  Height:  |  Size: 1.1 MiB

Before

Width:  |  Height:  |  Size: 237 KiB

After

Width:  |  Height:  |  Size: 237 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 366 KiB

Binary file not shown.
Binary file not shown.
+8 -3
View File
@@ -1,4 +1,5 @@
import fs from "fs/promises";
import { ensureFileExists } from "~/utils";
class Logger {
public static async axiosErrorLog(error: any) {
@@ -13,19 +14,23 @@ class Logger {
method: errorJson.config.method,
response: error?.response?.data,
requestHeaders: errorJson.config.headers,
responseHeaders: error.response.headers,
// responseHeaders: error.response.headers,
payload: errorJson.config.data ? JSON.parse(errorJson.config.data) : undefined,
params: errorJson.config.params ?? undefined,
date: nowDate.toString()
};
const logFilePath = ".logs/log.json";
try {
const oldLogs = await fs.readFile(".logs/log.json", "utf-8");
await ensureFileExists(logFilePath, "[]");
const oldLogs = await fs.readFile(logFilePath, "utf-8");
const oldLogsJson = JSON.parse(oldLogs) as Record<any, any>[];
oldLogsJson.push(logData);
await fs.writeFile(".logs/log.json", JSON.stringify(oldLogsJson));
await fs.writeFile(logFilePath, JSON.stringify(oldLogsJson, null, 2));
} catch (e) {
console.error(e);
}
+3 -3
View File
@@ -21,8 +21,8 @@ declare global {
status: number,
code: string,
requestHeaders: Record<any, any>,
responseHeaders: Record<any, any>,
response?: Record<any, any>,
responseHeaders?: Record<any, any>,
response?: any,
payload?: Record<any, any>,
params?: Record<any, any>,
date: string
@@ -35,7 +35,7 @@ declare global {
};
type Account = {
profile_photo: File | null;
profile_photo: string | null;
first_name: string;
last_name: string;
phone: string;
+19
View File
@@ -1,3 +1,6 @@
import fs from "fs/promises";
import path from "path";
export const dateFormatter = (date: string | undefined) => {
const formattedDate = useTimeAgo(date!);
return formattedDate.value;
@@ -77,3 +80,19 @@ export const isImage = (name: string | undefined) => {
}
return false;
};
// Ensure Exist
export const ensureFileExists = async (filePath: string, initialContent = "") => {
try {
await fs.access(filePath);
} catch (error) {
const err = error as any;
if (err.code === "ENOENT") {
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, initialContent, "utf-8");
} else {
throw err;
}
}
};
-7
View File
@@ -1,7 +0,0 @@
import { defineVitestConfig } from "@nuxt/test-utils/config";
export default defineVitestConfig({
test: {
environment: "nuxt",
},
});