This commit is contained in:
marzban-dev
2025-04-22 19:04:28 +03:30
30 changed files with 567 additions and 203 deletions
+13 -1
View File
@@ -28,4 +28,16 @@ SMS_API_KEY = ''
VAPID_PRIVATE_KEY = 'NajogmGTsGsZ_dfURrjUpgsm5fui-s5AzruBQgMh_I4' VAPID_PRIVATE_KEY = 'NajogmGTsGsZ_dfURrjUpgsm5fui-s5AzruBQgMh_I4'
OPENAI_API_KEY = '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' OPENAI_API_KEY = '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'
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql',
# 'NAME': 'hshop',
# 'USER': 'byeto',
# 'PASSWORD': 'vuhbyq-cypMu0-sirbon',
# 'HOST': '185.110.189.208',
# 'PORT': '5434',
# }
# }
+11
View File
@@ -239,6 +239,12 @@ UNFOLD = {
"badge": "utils.admin.new_ticket_count", "badge": "utils.admin.new_ticket_count",
}, },
{
"title": _("ارتباط با ما"),
"icon": "perm_phone_msg",
"link": reverse_lazy("admin:ticket_contactusmodel_changelist"),
"badge": "utils.admin.new_contact_us_count",
},
], ],
}, },
{ {
@@ -262,6 +268,11 @@ UNFOLD = {
"icon": "photo_prints", "icon": "photo_prints",
"link": reverse_lazy("admin:product_productvariant_changelist"), "link": reverse_lazy("admin:product_productvariant_changelist"),
}, },
{
"title": _("بخش جزيیات محصول"),
"icon": "subject",
"link": reverse_lazy("admin:product_productdetailmodel_changelist"),
},
], ],
}, },
+3 -1
View File
@@ -8,7 +8,7 @@ from django.utils.translation import gettext_lazy as _
from django.views.generic import RedirectView, TemplateView from django.views.generic import RedirectView, TemplateView
from unfold.views import UnfoldModelAdminViewMixin from unfold.views import UnfoldModelAdminViewMixin
from order.models import OrderModel from order.models import OrderModel
from ticket.models import Ticket from ticket.models import Ticket, ContactUsModel
from account.models import SecurityBreachAttemptModel from account.models import SecurityBreachAttemptModel
import json import json
@@ -19,9 +19,11 @@ def dashboard_callback(request, context):
pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').count() pending_count = OrderModel.objects.filter(status='ADMIN_PENDING').count()
open_tickets_count = Ticket.objects.filter(status__in=['open', 'in_progress']).count() open_tickets_count = Ticket.objects.filter(status__in=['open', 'in_progress']).count()
open_contact_us_count = ContactUsModel.objects.filter(is_reviewed=False).count()
context.update(random_data()) context.update(random_data())
context.update({'pending_count': pending_count}) context.update({'pending_count': pending_count})
context.update({'open_tickets_count': open_tickets_count}) context.update({'open_tickets_count': open_tickets_count})
context.update({'open_contact_us_count': open_contact_us_count})
return context return context
+3 -3
View File
@@ -1,4 +1,5 @@
from django.contrib import admin, messages from django.contrib import admin, messages
# from product.tasks import update_prices
from .models import * from .models import *
from unfold.admin import TabularInline, StackedInline from unfold.admin import TabularInline, StackedInline
from home.models import LearnVideoModel from home.models import LearnVideoModel
@@ -122,7 +123,7 @@ class DetailModelAdmin(ModelAdmin, ImportExportModelAdmin):
class ProductDetailModel1Admin(ModelAdmin, ImportExportModelAdmin): class ProductDetailModel1Admin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm import_form_class = ImportForm
export_form_class = ExportForm export_form_class = ExportForm
search_fields = ['detail_category__title'] search_fields = ['detail_category__title', 'name']
compressed_fields = True compressed_fields = True
warn_unsaved_form = True warn_unsaved_form = True
@@ -216,8 +217,7 @@ class ProductModelAdmin(ModelAdmin, ImportExportModelAdmin):
@action(description=f"اپدیت قیمت ها") @action(description=f"اپدیت قیمت ها")
def update_products_price(self, request): def update_products_price(self, request):
print('from the button') # update_prices()
ProductVariant.update_all_prices()
messages.success(request, f"قیمت {ProductVariant.objects.all().count()} تنوع محصول اپدیت شد") messages.success(request, f"قیمت {ProductVariant.objects.all().count()} تنوع محصول اپدیت شد")
return redirect("admin:product_productmodel_changelist") return redirect("admin:product_productmodel_changelist")
@@ -0,0 +1,20 @@
# Generated by Django 5.1.2 on 2025-04-21 23:30
import django.db.models.deletion
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0035_alter_attributetype_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='productdetailmodel',
name='detail_category',
field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='product.productdetailcategory', verbose_name='دسته بندی جزيات'),
preserve_default=False,
),
]
@@ -0,0 +1,19 @@
# Generated by Django 5.1.2 on 2025-04-21 23:37
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('product', '0036_alter_productdetailmodel_detail_category'),
]
operations = [
migrations.AddField(
model_name='productdetailmodel',
name='name',
field=models.CharField(default=1, help_text='این متن فقط برای راحتی در استفاده از پنل ادمین میباشد', max_length=50, verbose_name='نام جزيیات'),
preserve_default=False,
),
]
+5 -21
View File
@@ -4,6 +4,7 @@ from account.models import User
from django.urls import reverse from django.urls import reverse
import requests import requests
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.core.exceptions import ValidationError
class MainCategoryModel(models.Model): class MainCategoryModel(models.Model):
name = models.CharField(max_length=50, verbose_name='نام دسته بندی') name = models.CharField(max_length=50, verbose_name='نام دسته بندی')
@@ -212,14 +213,15 @@ class ProductImageModel(models.Model):
class ProductDetailModel(models.Model): class ProductDetailModel(models.Model):
detail_category = models.ForeignKey(ProductDetailCategory, on_delete=models.CASCADE, verbose_name='دسته بندی جزيات', blank=True, null=True) name = models.CharField(max_length=50, verbose_name='نام جزيیات', help_text='این متن فقط برای راحتی در استفاده از پنل ادمین میباشد')
detail_category = models.ForeignKey(ProductDetailCategory, on_delete=models.CASCADE, verbose_name='دسته بندی جزيات')
detail = models.ManyToManyField(DetailModel, verbose_name='جزيات ها') detail = models.ManyToManyField(DetailModel, verbose_name='جزيات ها')
class Meta: class Meta:
verbose_name = 'جزیات محصول' verbose_name = 'جزیات محصول'
verbose_name_plural = 'جزیات محصول ها' verbose_name_plural = 'جزیات محصول ها'
# def __str__(self): def __str__(self):
# return f'جزيیات محصول {self.product}' return f'جزيیات محصول {self.detail_category.title} - {self.name}'
class ProductVariant(models.Model): class ProductVariant(models.Model):
product = models.ForeignKey(ProductModel, on_delete=models.CASCADE, related_name='variants', verbose_name='محصول') product = models.ForeignKey(ProductModel, on_delete=models.CASCADE, related_name='variants', verbose_name='محصول')
@@ -272,21 +274,3 @@ class ProductVariant(models.Model):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
self.set_or_update_price() self.set_or_update_price()
super().save(*args, **kwargs) super().save(*args, **kwargs)
def get_toman_price_after_discount(self):
return self.price * ((100 - self.discount) / 100)
@classmethod
def update_all_prices(cls):
print('calling the update all prices ')
dollor_object, _ = DollorModel.objects.get_or_create(unique_filed='unique')
print(dollor_object.price)
dollor_object.update_price()
dollor_object.save()
dollor_price = dollor_object.price
print(dollor_object.price)
print('classmethod dollor price update ')
products = cls.objects.all()
for product in products:
product.set_or_update_price(dollor_price=dollor_price)
product.save()
+22 -1
View File
@@ -39,4 +39,25 @@
{% endcomponent %} {% endcomponent %}
</div> </div>
</div> </div>
{% endif %} {% endif %}
{% if open_contact_us_count%}
<div dir='rtl' class="bg-base-50 border border-base-200 border-dashed flex flex-col gap-4 p-4 rounded dark:bg-white/[.02] dark:border-base-700 lg:flex-row lg:justify-between w-full shrink-0 lg:items-center" style="justify-content: space-between;">
<div class="flex flex-col lg:flex-row lg:items-center">
<h2 class="font-semibold text-font-important-light text-base dark:text-font-important-dark flex items-center">
<span class="material-symbols-outlined md-18 mr-3 w-4.5 align-middle">confirmation_number</span>
<span class="align-middle" style="margin-right: 8px;">ارتباط با ما جدید داری</span>
<span class="text-white bg-primary-800 py-1 px-2 rounded" style="margin-right: 8px;">{{ open_contact_us_count }} </span>
</h2>
</div>
<div class="flex lg:flex-row lg:items-center">
{% component "unfold/components/flex.html" with class="flex-col gap-4 lg:flex-row" %}
{% component "unfold/components/button.html" with href="/secret-admin/ticket/contactusmodel/" %}
نمایش ارتباط با ما ها
{% endcomponent %}
{% endcomponent %}
</div>
</div>
{% endif %}
+9
View File
@@ -18,6 +18,15 @@ class MessageInline(TabularInline):
model = Message model = Message
extra = 1 extra = 1
@admin.register(ContactUsModel)
class ContactUsAdmin(ModelAdmin):
list_filter = ['type', 'is_reviewed']
list_display = ['full_name', 'phone', 'email', 'message', 'is_reviewed']
compressed_fields = True
warn_unsaved_form = True
readonly_fields = ['full_name', 'email', 'phone', 'type', 'message', ]
@admin.register(Ticket) @admin.register(Ticket)
class TicketAdmin(ModelAdmin, ImportExportModelAdmin): class TicketAdmin(ModelAdmin, ImportExportModelAdmin):
import_form_class = ImportForm import_form_class = ImportForm
@@ -0,0 +1,24 @@
# Generated by Django 5.1.2 on 2025-04-21 22:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0019_alter_attachment_options'),
]
operations = [
migrations.CreateModel(
name='ContactUsModel',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('full_name', models.CharField(max_length=50, verbose_name='نام و نام خانوادگی')),
('email', models.EmailField(max_length=254, verbose_name='ایمیل')),
('phone', models.CharField(max_length=30)),
('type', models.CharField(choices=[('ORDER', 'پیگیری سفارش'), ('SUGGESTION', 'پیشنهادات'), ('COMPLAINT', 'انتقادات')], max_length=20, verbose_name='نوع')),
('message', models.TextField(verbose_name='پیام')),
],
),
]
@@ -0,0 +1,18 @@
# Generated by Django 5.1.2 on 2025-04-21 22:53
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0020_contactusmodel'),
]
operations = [
migrations.AlterField(
model_name='contactusmodel',
name='type',
field=models.CharField(choices=[('ORDER_FOLLOW_UP', 'پیگیری سفارش'), ('SUGGESTION', 'پیشنهادات'), ('COMPLAINT', 'انتقادات')], max_length=20, verbose_name='نوع'),
),
]
@@ -0,0 +1,22 @@
# Generated by Django 5.1.2 on 2025-04-21 22:57
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0021_alter_contactusmodel_type'),
]
operations = [
migrations.AlterModelOptions(
name='contactusmodel',
options={'verbose_name': 'ارتباط با ما', 'verbose_name_plural': 'ارتباط با ما ها '},
),
migrations.AddField(
model_name='contactusmodel',
name='is_reviewed',
field=models.BooleanField(default=False),
),
]
@@ -0,0 +1,23 @@
# Generated by Django 5.1.2 on 2025-04-21 23:30
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('ticket', '0022_alter_contactusmodel_options_and_more'),
]
operations = [
migrations.AlterField(
model_name='contactusmodel',
name='is_reviewed',
field=models.BooleanField(default=False, verbose_name='بررسی شده'),
),
migrations.AlterField(
model_name='contactusmodel',
name='phone',
field=models.CharField(max_length=30, verbose_name='شماره تماس'),
),
]
+23 -1
View File
@@ -73,4 +73,26 @@ class Message(models.Model):
class Meta: class Meta:
verbose_name = 'پیام تیکت' verbose_name = 'پیام تیکت'
verbose_name_plural = 'پیام های تیکت' verbose_name_plural = 'پیام های تیکت'
class ContactUsModel(models.Model):
full_name = models.CharField(max_length=50, verbose_name='نام و نام خانوادگی')
email = models.EmailField(max_length=254, verbose_name='ایمیل')
phone = models.CharField(max_length=30, verbose_name='شماره تماس')
FEEDBACK_TYPES = [
('ORDER_FOLLOW_UP', 'پیگیری سفارش'),
('SUGGESTION', 'پیشنهادات'),
('COMPLAINT', 'انتقادات'),
]
type = models.CharField(max_length=20, choices=FEEDBACK_TYPES, verbose_name='نوع')
message = models.TextField(verbose_name='پیام')
is_reviewed = models.BooleanField(default=False, verbose_name='بررسی شده')
def __str__(self):
return f'{self.full_name} - {self.message[:15]}...'
class Meta:
verbose_name = 'ارتباط با ما'
verbose_name_plural = 'ارتباط با ما ها '
+8 -2
View File
@@ -1,5 +1,5 @@
from rest_framework import serializers from rest_framework import serializers
from .models import Ticket, Message, Attachment from .models import Ticket, Message, Attachment, ContactUsModel
from django.utils.timezone import localtime from django.utils.timezone import localtime
from order.serializers import OrderListSerializer from order.serializers import OrderListSerializer
from order.serializers import OrderModel from order.serializers import OrderModel
@@ -88,4 +88,10 @@ class TicketListSerializer(serializers.ModelSerializer):
return obj.get_status_display() return obj.get_status_display()
def get_ticket_category(self, obj): def get_ticket_category(self, obj):
return obj.get_ticket_category_display() return obj.get_ticket_category_display()
class ContactUsSerializer(serializers.ModelSerializer):
class Meta:
model = ContactUsModel
fields = ['full_name', 'email', 'phone', 'type', 'message']
+1
View File
@@ -6,6 +6,7 @@ urlpatterns = [
path('', views.TicketListView.as_view(), name='ticket-list'), path('', views.TicketListView.as_view(), name='ticket-list'),
path('<int:pk>', views.TicketDetailView.as_view(), name='ticket-detail'), path('<int:pk>', views.TicketDetailView.as_view(), name='ticket-detail'),
path('message/create', views.MessageCreateView.as_view(), name='message-create'), path('message/create', views.MessageCreateView.as_view(), name='message-create'),
path('contact-us/create', views.CreateContactUsView.as_view(), name='contact-us-create'),
path('attachment/create', views.AttachmentUploadView.as_view(), name='attachment-upload'), path('attachment/create', views.AttachmentUploadView.as_view(), name='attachment-upload'),
path('attachment/delete/<int:pk>', views.AttachmentDeleteView.as_view(), name='attachment-upload'), path('attachment/delete/<int:pk>', views.AttachmentDeleteView.as_view(), name='attachment-upload'),
] ]
+14 -3
View File
@@ -2,7 +2,7 @@ from rest_framework import generics, permissions
from rest_framework.response import Response from rest_framework.response import Response
from rest_framework.views import APIView from rest_framework.views import APIView
from .models import Ticket, Message, Attachment from .models import Ticket, Message, Attachment
from .serializers import TicketListSerializer, MessageSerializer, TicketSerializer, AttachmentSerializer from .serializers import TicketListSerializer, MessageSerializer, TicketSerializer, AttachmentSerializer, ContactUsSerializer
from utils.pagination import StructurePagination from utils.pagination import StructurePagination
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes
from rest_framework.permissions import IsAuthenticated from rest_framework.permissions import IsAuthenticated
@@ -56,7 +56,6 @@ class TicketCreateView(APIView):
permission_classes = [permissions.IsAuthenticated] permission_classes = [permissions.IsAuthenticated]
def post(self, request): def post(self, request):
new_ticket_ser = self.serializer_class(data=request.data, context={'request': request}) new_ticket_ser = self.serializer_class(data=request.data, context={'request': request})
message = request.data.get('message', None)
if new_ticket_ser.is_valid(): if new_ticket_ser.is_valid():
new_ticket_ser.save(customer=request.user) new_ticket_ser.save(customer=request.user)
return Response(new_ticket_ser.data, status=status.HTTP_201_CREATED) return Response(new_ticket_ser.data, status=status.HTTP_201_CREATED)
@@ -159,4 +158,16 @@ class UpdateTicketStatusView(APIView):
return Response({"error": "Invalid status"}, status=400) return Response({"error": "Invalid status"}, status=400)
ticket.status = new_status ticket.status = new_status
ticket.save() ticket.save()
return Response({"message": "Ticket status updated successfully"}) return Response({"message": "Ticket status updated successfully"})
class CreateContactUsView(APIView):
serializer_class = ContactUsSerializer
permission_classes = [permissions.AllowAny]
def post(self, request):
contact_us_ser = self.serializer_class(data=request.data, context={'request': request})
if contact_us_ser.is_valid():
contact_us_ser.save()
return Response(contact_us_ser.data, status=status.HTTP_201_CREATED)
else:
return Response(contact_us_ser.errors, status=status.HTTP_400_BAD_REQUEST)
+4 -1
View File
@@ -1,6 +1,6 @@
from order.models import OrderModel from order.models import OrderModel
from product.models import DollorModel, CommentModel from product.models import DollorModel, CommentModel
from ticket.models import Ticket from ticket.models import Ticket, ContactUsModel
from home.models import LearnVideoModel from home.models import LearnVideoModel
from account.models import SecurityBreachAttemptModel from account.models import SecurityBreachAttemptModel
@@ -24,6 +24,9 @@ def new_learn_video_count(request):
def new_attck_count(request): def new_attck_count(request):
return SecurityBreachAttemptModel.objects.filter(viewd=False).count() return SecurityBreachAttemptModel.objects.filter(viewd=False).count()
def new_contact_us_count(request):
return ContactUsModel.objects.filter(is_reviewed=False).count()
from django.contrib import admin, messages from django.contrib import admin, messages
from unfold.admin import ModelAdmin from unfold.admin import ModelAdmin
from home.models import LearnVideoModel from home.models import LearnVideoModel
+5 -2
View File
@@ -150,9 +150,12 @@ watch(
</div> </div>
</div> </div>
<span class="font-semibold typo-sub-h-sm lg:typo-sub-h-xl text-black"> <NuxtLink
:to="`product/${data.product.id}`"
class="font-semibold typo-sub-h-sm lg:typo-sub-h-xl text-black underline underline-offset-2"
>
{{ data.product.title }} {{ data.product.title }}
</span> </NuxtLink>
<div class="flex items-center justify-start gap-1.5"> <div class="flex items-center justify-start gap-1.5">
<div <div
+106 -30
View File
@@ -12,11 +12,9 @@ const {} = toRefs(props);
</script> </script>
<template> <template>
<div class="relative w-full flex flex-col justify-center min-h-[450px] h-svh"> <div class="relative w-full flex flex-col justify-center py-32 lg:py-48">
<div class="flex-col-center gap-6 mb-24 sm:mb-32 container"> <div class="flex-col-center gap-6 mb-24 sm:mb-32 container">
<span class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4 text-black"> <span class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4 text-black"> مجله در ستون و سطرآنچ </span>
مجله در ستون و سطرآنچ
</span>
<p class="text-slate-500 text-center max-w-[750px] typo-p-sm md:typo-p-lg xl:typo-p-xl"> <p class="text-slate-500 text-center max-w-[750px] typo-p-sm md:typo-p-lg xl:typo-p-xl">
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با استفاده از طراحان گرافیک است. چاپگرها و
متون بلکه روزنامه و مجله در ستون و سطرآنچنان که متون بلکه روزنامه و مجله در ستون و سطرآنچنان که
@@ -30,13 +28,19 @@ const {} = toRefs(props);
<div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85"> <div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85">
HEYMLZ HEYMLZ
</div> </div>
<NuxtImg src="/img/heymlz/heymlz-logo.png" class="h-[25px] sm:h-[45px] invert opacity-85" /> <NuxtImg
src="/img/heymlz/heymlz-logo.png"
class="h-[25px] sm:h-[45px] invert opacity-85"
/>
</template> </template>
<template v-for="i in 10"> <template v-for="i in 10">
<div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85"> <div class="text-[30px] lg:text-[40px] text-white whitespace-nowrap font-semibold opacity-85">
HEYMLZ HEYMLZ
</div> </div>
<NuxtImg src="/img/heymlz/heymlz-logo.png" class="h-[25px] sm:h-[45px] invert opacity-85" /> <NuxtImg
src="/img/heymlz/heymlz-logo.png"
class="h-[25px] sm:h-[45px] invert opacity-85"
/>
</template> </template>
</div> </div>
</div> </div>
@@ -46,32 +50,104 @@ const {} = toRefs(props);
class="bg-slate-100/70 flex items-center pr-20 gap-12 sm:gap-20 w-max animate-marquee h-[90px] sm:h-[140px]" class="bg-slate-100/70 flex items-center pr-20 gap-12 sm:gap-20 w-max animate-marquee h-[90px] sm:h-[140px]"
> >
<template v-for="i in 1"> <template v-for="i in 1">
<NuxtImg src="/img/brands/brand-1.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-2.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-1.png"
<NuxtImg src="/img/brands/brand-3.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-4.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg src="/img/brands/brand-5.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-6.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-2.png"
<NuxtImg src="/img/brands/brand-1.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-2.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg src="/img/brands/brand-3.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-4.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-3.png"
<NuxtImg src="/img/brands/brand-5.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-6.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg
src="/img/brands/brand-4.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-5.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-6.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-1.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-2.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-3.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-4.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-5.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-6.png"
class="h-[25px] sm:h-[45px]"
/>
</template> </template>
<template v-for="i in 1"> <template v-for="i in 1">
<NuxtImg src="/img/brands/brand-1.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-2.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-1.png"
<NuxtImg src="/img/brands/brand-3.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-4.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg src="/img/brands/brand-5.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-6.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-2.png"
<NuxtImg src="/img/brands/brand-1.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-2.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg src="/img/brands/brand-3.png" class="h-[25px] sm:h-[45px]" /> <NuxtImg
<NuxtImg src="/img/brands/brand-4.png" class="h-[25px] sm:h-[45px]" /> src="/img/brands/brand-3.png"
<NuxtImg src="/img/brands/brand-5.png" class="h-[25px] sm:h-[45px]" /> class="h-[25px] sm:h-[45px]"
<NuxtImg src="/img/brands/brand-6.png" class="h-[25px] sm:h-[45px]" /> />
<NuxtImg
src="/img/brands/brand-4.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-5.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-6.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-1.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-2.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-3.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-4.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-5.png"
class="h-[25px] sm:h-[45px]"
/>
<NuxtImg
src="/img/brands/brand-6.png"
class="h-[25px] sm:h-[45px]"
/>
</template> </template>
</div> </div>
</div> </div>
+10 -4
View File
@@ -18,10 +18,16 @@ withDefaults(defineProps<Props>(), {
<template> <template>
<div class="w-full flex flex-col gap-2"> <div class="w-full flex flex-col gap-2">
<div class="flex items-center gap-1 ps-2"> <div class="flex items-center gap-1 ps-2">
<label :for="id" class="typo-label-xs lg:typo-label-sm">{{ <label
label :for="id"
}}</label> class="typo-label-xs lg:typo-label-sm"
<span v-if="!!required && required" class="text-danger-600">*</span> >{{ label }}</label
>
<span
v-if="!!required && required"
class="text-danger-600"
>*</span
>
</div> </div>
<slot /> <slot />
<div <div
+125 -48
View File
@@ -20,6 +20,8 @@ defineProps<Props>();
const { token } = useAuth(); const { token } = useAuth();
const { data: account } = useGetAccount(); const { data: account } = useGetAccount();
const route = useRoute();
// emit // emit
const emit = defineEmits(["update:modelValue"]); const emit = defineEmits(["update:modelValue"]);
@@ -29,13 +31,22 @@ const emit = defineEmits(["update:modelValue"]);
const closeSideDrawer = () => { const closeSideDrawer = () => {
emit("update:modelValue", false); emit("update:modelValue", false);
}; };
// watch
watch(
() => route.fullPath,
() => {
closeSideDrawer();
}
);
</script> </script>
<template> <template>
<Transition name="fade"> <Transition name="fade">
<div <div
v-if="modelValue" v-if="modelValue"
class="md:hidden fixed inset-0 h-svh z-999 size-full bg-black/50 cursor-pointer" class="md:hidden fixed inset-0 min-h-svh z-1001 size-full bg-black/50 cursor-pointer"
@click="closeSideDrawer" @click="closeSideDrawer"
/> />
</Transition> </Transition>
@@ -43,61 +54,127 @@ const closeSideDrawer = () => {
<div <div
@click.stop @click.stop
:class="modelValue ? 'translate-x-0' : 'translate-x-[100%]'" :class="modelValue ? 'translate-x-0' : 'translate-x-[100%]'"
class="md:hidden cursor-default flex top-0 right-0 fixed z-999 transition-all duration-500 rounded-e-xl flex-col bg-white w-[300px] h-full gap-8 pt-12" class="md:hidden cursor-default flex top-0 right-0 fixed z-1002 transition-all duration-500 flex-col bg-white w-[300px] h-full px-4"
> >
<div class="flex items-center flex-col justify-end gap-[1.5rem]"> <div class="flex items-center flex-col justify-end gap-2 border-b border-slate-200 py-5">
<Tooltip v-if="!!account && !!token" title="حساب کاربری"> <NuxtLink
<NuxtLink v-if="!!account && !!token"
:to="{ name: 'profile' }" :to="{ name: 'profile' }"
class="flex items-center justify-center" class="w-full flex items-center justify-between gap-3 p-2 transition-all"
> active-class="bg-black rounded-md text-white **:stroke-white"
<Avatar >
class="!size-7" <div class="flex items-center gap-3">
:src="account.profile_photo" <div class="size-5 flex-center">
:alt=" <Avatar
account.first_name && account.last_name class="!size-5"
? `${account.first_name.charAt( :src="account.profile_photo"
0 :alt="
)} ${account.last_name.charAt(0)}` account.first_name && account.last_name
: 'بدون نام کاربری' ? `${account.first_name.charAt(0)} ${account.last_name.charAt(0)}`
" : 'بدون نام کاربری'
/> "
</NuxtLink> />
</Tooltip> </div>
<Tooltip v-else title="ورود"> <span class="text-xs"> {{ account.first_name }} {{ account.last_name }} </span>
<NuxtLink to="/signin" class="flex-center"> </div>
<Icon <Icon
name="ci:profile" name="bi:chevron-left"
size="24px" size="12"
class="**:stroke-black" class="**:stroke-black/50 opacity-70"
/> />
</NuxtLink> </NuxtLink>
</Tooltip>
<Tooltip title="محصولات"> <NuxtLink
<NuxtLink to="/products" class="flex-center"> v-else
<Icon to="/signin"
name="ci:search" class="w-full flex items-center justify-between gap-4 p-2 transition-all"
size="21px" active-class="bg-black rounded-md text-white **:stroke-white"
class="**:stroke-black" >
/> <div class="flex items-center gap-3">
</NuxtLink> <div class="size-5 flex-center">
</Tooltip> <Icon
<Tooltip title="سبد خرید"> name="ci:profile"
<NuxtLink to="/cart" class="flex-center"> size="18"
<Icon name="ci:cart" size="24px" class="**:stroke-black" /> class="**:stroke-black"
</NuxtLink> />
</Tooltip> </div>
<span class="text-xs"> ورود به حساب </span>
</div>
<Icon
name="bi:chevron-left"
size="12"
class="**:stroke-black/50 opacity-70"
/>
</NuxtLink>
<!--
<NuxtLink
to="/products"
class="w-full flex items-center justify-between gap-4 p-2"
active-class="bg-black rounded-md text-white **:stroke-white"
>
<div class="flex items-center gap-3">
<div class="size-5 flex-center">
<Icon
name="ci:search"
size="18"
class="**:stroke-black"
/>
</div>
<span class="text-xs"> جست و جو </span>
</div>
<Icon
name="bi:chevron-left"
size="12"
class="**:stroke-black/50 opacity-70"
/>
</NuxtLink> -->
<NuxtLink
to="/cart"
class="w-full flex items-center justify-between gap-4 p-2 transition-all"
active-class="bg-black rounded-md text-white **:stroke-white"
>
<div class="flex items-center gap-3">
<div class="size-5 flex-center">
<Icon
name="ci:cart"
size="19"
class="**:stroke-black"
/>
</div>
<span class="text-xs"> سبد خرید </span>
</div>
<Icon
name="bi:chevron-left"
size="12"
class="**:stroke-black/50 opacity-70"
/>
</NuxtLink>
</div> </div>
<nav <nav class="flex-center flex-col gap-2 typo-label-sm font-light text-black/80 py-5">
class="flex-center flex-col gap-[2.5rem] typo-label-sm font-light text-black/80"
>
<NuxtLink <NuxtLink
v-for="(link, index) in NAV_LINKS" v-for="(link, index) in NAV_LINKS"
:key="index" :key="index"
:to="link.path" :to="link.path"
class="w-full flex items-center justify-between gap-3 p-2 transition-all"
active-class="bg-black rounded-md text-white **:stroke-white"
> >
{{ link.title }} <div class="flex items-center gap-3">
<div class="size-5 flex-center">
<Icon
:name="link.icon"
size="18"
class="**:stroke-black"
/>
</div>
<span class="text-xs"> {{ link.title }}</span>
</div>
<Icon
name="bi:chevron-left"
size="12"
class="**:stroke-black/50 opacity-70"
/>
</NuxtLink> </NuxtLink>
</nav> </nav>
</div> </div>
+4 -4
View File
@@ -20,13 +20,13 @@ const onSwiper = (swiper: SwiperClass) => {
<template> <template>
<section <section
ref="sectionTarget" ref="sectionTarget"
class="flex flex-col justify-center gap-4 bg-black h-[110svh] sm:h-[150svh] relative overflow-hidden" class="flex flex-col justify-center gap-4 bg-black sm:min-h-[110svh] relative overflow-hidden shrink-0 py-24 lg:py-32"
> >
<div class="w-full relative translate-y-[-55px] sm:translate-y-[-130px] flex-center z-10 container"> <div class="w-full relative flex-center z-10 container">
<span class="text-white typo-h-6 md:typo-h-5 lg:typo-h-4"> دسته بندی ها </span> <span class="text-white typo-h-6 md:typo-h-5 lg:typo-h-4"> دسته بندی ها </span>
</div> </div>
<div class="w-full my-20 relative"> <div class="w-full mt-44 lg:mt-64 relative">
<NuxtImg <NuxtImg
class="aspect-square w-[240px] md:w-[300px] lg:w-[350px] translate-y-[-164px] md:translate-y-[-206px] lg:translate-y-[-240px] absolute left-1/2 -translate-x-1/2 z-10" class="aspect-square w-[240px] md:w-[300px] lg:w-[350px] translate-y-[-164px] md:translate-y-[-206px] lg:translate-y-[-240px] absolute left-1/2 -translate-x-1/2 z-10"
:style="{ :style="{
@@ -89,7 +89,7 @@ const onSwiper = (swiper: SwiperClass) => {
</div> </div>
</div> </div>
<div class="w-full flex justify-center items-center"> <div class="w-full flex justify-center items-center mt-14">
<NuxtLink to="/category"> <NuxtLink to="/category">
<Button <Button
variant="primary" variant="primary"
+11 -10
View File
@@ -1,5 +1,4 @@
<script setup lang="ts"> <script setup lang="ts">
// state // state
import useGetArticles from "~/composables/api/blog/useGetArticles"; import useGetArticles from "~/composables/api/blog/useGetArticles";
@@ -10,22 +9,24 @@ const { data: articles, suspense } = useGetArticles(page);
// ssr // ssr
await suspense(); await suspense();
</script> </script>
<template> <template>
<section class="mt-20 container"> <section class="container">
<div class="flex items-center justify-between mb-12 md:mb-20"> <div class="flex items-center justify-between mb-12 md:mb-20">
<span class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4 text-black"> <span class="typo-h-6 max-sm:text-xl md:typo-h-5 lg:typo-h-4 text-black"> مقالات اخیر سایت </span>
مقالات اخیر سایت
</span>
<NuxtLink to="/articles"> <NuxtLink to="/articles">
<Button variant="primary" class="rounded-full max-sm:typo-label-sm max-sm:py-2" <Button
end-icon="ci:arrow-left"> variant="primary"
class="rounded-full max-sm:typo-label-sm max-sm:py-2"
end-icon="ci:arrow-left"
>
نمایش همه نمایش همه
</Button> </Button>
</NuxtLink> </NuxtLink>
</div> </div>
<ArticlesList :articles="[...articles!.results,...articles!.results,...articles!.results,...articles!.results,...articles!.results,...articles!.results]" /> <ArticlesList
:articles="[...articles!.results,...articles!.results,...articles!.results,...articles!.results,...articles!.results,...articles!.results]"
/>
</section> </section>
</template> </template>
+1 -1
View File
@@ -72,7 +72,7 @@ watch(
</script> </script>
<template> <template>
<div class="container mb-40 lg:mb-80 mt-20"> <div class="container mb-40 lg:mb-40 max-lg:mt-20 lg:-mt-32">
<div> <div>
<div class="flex flex-col items-center gap-3 mb-10 lg:mb-16"> <div class="flex flex-col items-center gap-3 mb-10 lg:mb-16">
<span class="typo-p-sm md:typo-p-md text-slate-500"> مقایسه محصولات </span> <span class="typo-p-sm md:typo-p-md text-slate-500"> مقایسه محصولات </span>
+10 -11
View File
@@ -85,14 +85,14 @@ onUnmounted(() => {
<template> <template>
<section <section
id="products-showcase-container" id="products-showcase-container"
class="perspective-midrange relative z-[99999]" class="perspective-midrange relative z-[999]"
> >
<div class="w-full h-[102svh] bg-black"> <div class="w-full min-h-[120svh] lg:min-h-[102svh] bg-black">
<NuxtLink <NuxtLink
v-for="slide in homeData!.show_case_slider" v-for="slide in homeData!.show_case_slider"
:key="slide.id" :key="slide.id"
:to="slide.link" :to="slide.link"
class="showcase-slide origin-bottom absolute size-full bg-transparent flex items-center justify-center" class="showcase-slide origin-bottom absolute size-full bg-transparent flex items-center justify-center max-lg:-mt-16 lg:mt-5"
> >
<NuxtImg <NuxtImg
class="w-[280px] xs:w-[350px] lg:w-[500px] xl:w-[650px] z-20 mb-30 sm:mb-20 lg:mb-30" class="w-[280px] xs:w-[350px] lg:w-[500px] xl:w-[650px] z-20 mb-30 sm:mb-20 lg:mb-30"
@@ -102,12 +102,8 @@ onUnmounted(() => {
}" }"
alt="" alt=""
/> />
<div <div class="flex flex-col items-center justify-center gap-6 text-center absolute z-20 mt-20">
class="flex flex-col items-center justify-center gap-6 text-center absolute z-20 mt-20" <span class="text-white typo-h-6 sm:typo-h-5 lg:typo-h-4 xl:typo-h-3">
>
<span
class="text-white typo-h-6 sm:typo-h-5 lg:typo-h-4 xl:typo-h-3"
>
{{ slide.title }} {{ slide.title }}
</span> </span>
<p <p
@@ -115,10 +111,13 @@ onUnmounted(() => {
> >
{{ slide.description }} {{ slide.description }}
</p> </p>
<NuxtLink :to="slide.link" class="relative"> <NuxtLink
:to="slide.link"
class="relative"
>
<NuxtImg <NuxtImg
src="/img/heymlz/heymlz-falling.gif" src="/img/heymlz/heymlz-falling.gif"
class="absolute top-[106px] sm:top-[105px] lg:top-[117px] left-1/2 -translate-1/2 w-[250px] drop-shadow-md" class="absolute top-[101px] sm:top-[100px] lg:top-[117px] left-1/2 -translate-1/2 w-[200px] lg:w-[250px] drop-shadow-md"
/> />
<Button <Button
variant="primary" variant="primary"
+4
View File
@@ -94,17 +94,21 @@ export const NAV_LINKS = [
{ {
title: "خانه", title: "خانه",
path: "/", path: "/",
icon: "ci:home",
}, },
{ {
title: "محصولات", title: "محصولات",
path: "/products", path: "/products",
icon: "ci:airdrop",
}, },
{ {
title: "دسته بندی ها", title: "دسته بندی ها",
path: "/category", path: "/category",
icon: "ci:delivery-boxes",
}, },
{ {
title: "ارتباط با ما", title: "ارتباط با ما",
path: "/contact-us", path: "/contact-us",
icon: "ci:call",
}, },
]; ];
+2 -2
View File
@@ -33,12 +33,12 @@ onMounted(() => {
<LoadingOverlay /> <LoadingOverlay />
<Hero class="mb-20 max-md:mt-[80px]" /> <Hero class="mb-20 max-md:mt-[80px]" />
<Preview /> <Preview />
<ProductsShowcase class="mb-40" /> <ProductsShowcase class="lg:mb-12" />
<ProductsGrid <ProductsGrid
title="محصولات پرفروش" title="محصولات پرفروش"
:products="[...homeData!.products,...homeData!.products]" :products="[...homeData!.products,...homeData!.products]"
/> />
<Categories class="mt-40" /> <Categories class="mt-12" />
<Brands /> <Brands />
<LatestStories class="mb-20" /> <LatestStories class="mb-20" />
</div> </div>
+27 -23
View File
@@ -1,9 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
// import // import
import useGetProducts, { import useGetProducts, { type GetProductsFilters } from "~/composables/api/products/useGetProducts";
type GetProductsFilters,
} from "~/composables/api/products/useGetProducts";
import { PRODUCT_RANGE } from "~/constants"; import { PRODUCT_RANGE } from "~/constants";
// state // state
@@ -59,12 +57,8 @@ watch(
<template> <template>
<div class="w-full container flex flex-col"> <div class="w-full container flex flex-col">
<div <div class="w-full flex flex-col lg:flex-row justify-end items-end py-[3.5rem] lg:py-[5rem] gap-10 lg:gap-5">
class="w-full flex flex-col lg:flex-row justify-end items-end py-[3.5rem] lg:py-[5rem] gap-10 lg:gap-5" <div class="flex flex-col items-center lg:items-start gap-[1rem] lg:gap-[1.5rem] text-black w-full">
>
<div
class="flex flex-col items-center lg:items-start gap-[1rem] lg:gap-[1.5rem] text-black w-full"
>
<!-- <div class="flex-center gap-[.75rem]"> <!-- <div class="flex-center gap-[.75rem]">
<span class="text-xs lg:text-sm">خانه</span> <span class="text-xs lg:text-sm">خانه</span>
<span class="text-xs lg:text-sm">/</span> <span class="text-xs lg:text-sm">/</span>
@@ -75,9 +69,7 @@ watch(
<h1 class="typo-h-5 lg:typo-h-4">لیست محصولات</h1> <h1 class="typo-h-5 lg:typo-h-4">لیست محصولات</h1>
</div> </div>
<div <div class="w-full flex items-center justify-between lg:justify-end gap-4">
class="w-full flex items-center justify-between lg:justify-end gap-4"
>
<Input <Input
placeholder="جست و جو محصول ..." placeholder="جست و جو محصول ..."
v-model="search" v-model="search"
@@ -96,9 +88,7 @@ watch(
<Suspense> <Suspense>
<FilterButton /> <FilterButton />
<template #fallback> <template #fallback>
<Skeleton <Skeleton class="!size-11 lg:!w-[10.35rem] lg:!h-[3.35rem] shrink-0 !rounded-xl" />
class="!size-11 lg:!w-[10.35rem] lg:!h-[3.35rem] shrink-0 !rounded-xl"
/>
</template> </template>
</Suspense> </Suspense>
</div> </div>
@@ -107,24 +97,35 @@ watch(
v-if="productsIsLoading" v-if="productsIsLoading"
class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-8 gap-5 sm:gap-8 w-full" class="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-y-8 gap-5 sm:gap-8 w-full"
> >
<div class="w-full flex flex-col gap-3" v-for="i in 8" :key="i"> <div
class="w-full flex flex-col gap-3"
v-for="i in 8"
:key="i"
>
<Skeleton <Skeleton
v-for="i in 3" v-for="i in 3"
:key="i" :key="i"
class="w-full" class="w-full"
:class="{ :class="{
'!h-[11.75rem] lg:!h-[22.5rem] !rounded-2xl': i == 1, '!h-[11.75rem] lg:!h-[22.5rem] !rounded-2xl': i == 1,
'!h-[1.35rem] lg:!h-[1.5rem] !rounded-sm lg:!hidden': [ '!h-[1.35rem] lg:!h-[1.5rem] !rounded-sm lg:!hidden': [2, 3].includes(i),
2, 3,
].includes(i),
'!w-1/2': i == 2, '!w-1/2': i == 2,
}" }"
/> />
</div> </div>
</ul> </ul>
<div v-else class="w-full h-max"> <div
<div v-if="!products!.length" class="flex flex-grow w-full"> v-else
<Placeholder title="محصولی یافت نشد :(" icon="bi:search" /> class="w-full h-max"
>
<div
v-if="!products!.length"
class="flex flex-grow w-full"
>
<Placeholder
title="محصولی یافت نشد :("
icon="bi:search"
/>
</div> </div>
<ProductsGrid <ProductsGrid
:with-header="false" :with-header="false"
@@ -135,7 +136,10 @@ watch(
v-if="data && paginationData && data.count > 10" v-if="data && paginationData && data.count > 10"
class="w-full flex-center py-10" class="w-full flex-center py-10"
> >
<Pagination :items="paginationData" :total="data.count" /> <Pagination
:items="paginationData"
:total="data.count"
/>
</div> </div>
</div> </div>
</div> </div>
+20 -34
View File
@@ -39,10 +39,7 @@ const formRules = computed(() => {
return { return {
phone: { phone: {
required: helpers.withMessage("Phone is required", required), required: helpers.withMessage("Phone is required", required),
phoneValidator: helpers.withMessage( phoneValidator: helpers.withMessage("شماره تلفن وارد شده معتبر نمی باشد", helpers.regex(/^[1-9][0-9]{9}$/)),
"شماره تلفن وارد شده معتبر نمی باشد",
helpers.regex(/^[1-9][0-9]{9}$/)
),
}, },
}; };
}); });
@@ -63,11 +60,7 @@ const {
}); });
const { mutateAsync: sendOtp, isPending: sendOtpIsPending } = useOtp(); const { mutateAsync: sendOtp, isPending: sendOtpIsPending } = useOtp();
const { const { mutateAsync: signIn, isPending: signInIsPending, status: signInStatus } = useSignIn();
mutateAsync: signIn,
isPending: signInIsPending,
status: signInStatus,
} = useSignIn();
// computed // computed
@@ -152,9 +145,7 @@ const resetForm = () => {
<template> <template>
<div class="w-full flex h-svh items-center relative container"> <div class="w-full flex h-svh items-center relative container">
<div class="pattern -z-10 size-full fixed inset-0" /> <div class="pattern -z-10 size-full fixed inset-0" />
<div <div class="flex items-center justify-center flex-col size-full translate-y-[-100px]">
class="flex items-center justify-center flex-col size-full translate-y-[-100px]"
>
<img <img
class="aspect-square w-[250px] sm:w-[325px] translate-y-[90px] sm:translate-y-[120px] animate-fade-in" class="aspect-square w-[250px] sm:w-[325px] translate-y-[90px] sm:translate-y-[120px] animate-fade-in"
src="/img/heymlz/heymlz-signin.gif" src="/img/heymlz/heymlz-signin.gif"
@@ -166,11 +157,12 @@ const resetForm = () => {
<div <div
class="max-w-[600px] w-full p-6 h-[350px] sm:h-[400px] flex flex-col items-center bg-white border shadow-black/10 justify-center border-slate-300 rounded-3xl" class="max-w-[600px] w-full p-6 h-[350px] sm:h-[400px] flex flex-col items-center bg-white border shadow-black/10 justify-center border-slate-300 rounded-3xl"
> >
<h1 class="typo-h-6 sm:typo-h-5 mt-8"> <h1 class="typo-h-6 sm:typo-h-5 mt-8">شماره خود را وارد کنید</h1>
شماره خود را وارد کنید
</h1>
<form @submit.prevent class="max-w-[500px] w-full mt-12"> <form
@submit.prevent
class="max-w-[500px] w-full mt-12"
>
<Input <Input
v-if="!showOtp" v-if="!showOtp"
class="w-full tracking-[3px] persian-number" class="w-full tracking-[3px] persian-number"
@@ -186,9 +178,7 @@ const resetForm = () => {
name="twemoji:flag-iran" name="twemoji:flag-iran"
size="24" size="24"
/> />
<span class="text-slate-500 typo-label-sm"> <span class="text-slate-500 typo-label-sm"> +۹۸ </span>
+۹۸
</span>
</div> </div>
</template> </template>
</Input> </Input>
@@ -196,13 +186,7 @@ const resetForm = () => {
<OtpInput <OtpInput
v-else v-else
v-model="otpCode" v-model="otpCode"
:status=" :status="signInStatus === 'success' ? 'success' : signInStatus === 'error' ? 'error' : 'idle'"
signInStatus === 'success'
? 'success'
: signInStatus === 'error'
? 'error'
: 'idle'
"
:disabled="signInIsPending || sendOtpIsPending" :disabled="signInIsPending || sendOtpIsPending"
:autofocus="true" :autofocus="true"
@complete="handleLogin" @complete="handleLogin"
@@ -220,7 +204,10 @@ const resetForm = () => {
ارسال کد ارسال کد
</Button> </Button>
<div v-else class="flex items-center w-full gap-4 mt-4"> <div
v-else
class="flex items-center w-full gap-4 mt-4"
>
<Button <Button
class="rounded-full w-full mt-4 max-sm:h-[45px]" class="rounded-full w-full mt-4 max-sm:h-[45px]"
type="button" type="button"
@@ -235,11 +222,7 @@ const resetForm = () => {
type="submit" type="submit"
@click="resendOtp" @click="resendOtp"
:loading="signInIsPending || sendOtpIsPending" :loading="signInIsPending || sendOtpIsPending"
:disabled=" :disabled="signInIsPending || isResendOtpBlocked || sendOtpIsPending"
signInIsPending ||
isResendOtpBlocked ||
sendOtpIsPending
"
> >
ارسال مجدد کد ارسال مجدد کد
{{ isResendOtpBlocked ? otpBlockerTimePassed : "" }} {{ isResendOtpBlocked ? otpBlockerTimePassed : "" }}
@@ -250,8 +233,11 @@ const resetForm = () => {
to="/" to="/"
class="flex items-center gap-2 justify-center mt-6" class="flex items-center gap-2 justify-center mt-6"
> >
<span> بازگشت به فروشگاه </span> <Icon
<Icon name="ci:left-rotation" size="24" /> name="ci:left-rotation"
class="lg:text-xl"
/>
<span class="text-xs lg:text-sm"> بازگشت به فروشگاه </span>
</NuxtLink> </NuxtLink>
</form> </form>
</div> </div>