merge
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
name: Deploy to Server
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Copy files to server
|
||||
uses: appleboy/scp-action@v0.1.6
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SSH_USER }}
|
||||
password: ${{ secrets.SSH_PASSWORD }}
|
||||
source: "."
|
||||
target: "/root/hshop/"
|
||||
|
||||
- name: SSH command to build and start Docker
|
||||
uses: appleboy/ssh-action@v0.1.6
|
||||
with:
|
||||
host: ${{ secrets.SERVER_HOST }}
|
||||
username: ${{ secrets.SSH_USER }}
|
||||
password: ${{ secrets.SSH_PASSWORD }}
|
||||
script: |
|
||||
cd /root/hshop/
|
||||
docker compose down
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
+7
-7
@@ -2,11 +2,11 @@
|
||||
|
||||
DEBUG = True # keep debug true to set the database to sqlite
|
||||
# postgres database info
|
||||
DB_NAME = ''
|
||||
DB_USER = ''
|
||||
DB_PASSWORD = ''
|
||||
DB_HOST = ''
|
||||
DB_PORT = ''
|
||||
DB_NAME = 'hshop'
|
||||
DB_USER = 'byeto'
|
||||
DB_PASSWORD = 'vuhbyq-cypMu0-sirbon'
|
||||
DB_HOST = 'db'
|
||||
DB_PORT = 5434
|
||||
SECRET_KEY = '2h&gmi54wqauwqht48l-9c)r6_67_(oe_$ll%(+xz%u#)+of@d'
|
||||
# email stuff
|
||||
EMAIL_BACKEND = ''
|
||||
@@ -17,9 +17,9 @@ DEFAULT_FROM_EMAIL = ''
|
||||
# telegram bot toekn
|
||||
TELEGRAM_BOT_TOKEN = ''
|
||||
# domain for allowed host and allowed cors
|
||||
DOMAIN = ''
|
||||
DOMAIN = '38.60.202.91'
|
||||
# domain for api (the domain that django will use)
|
||||
API_DOMAIN = ''
|
||||
API_DOMAIN = '38.60.202.91'
|
||||
SITE_TITLE = ''
|
||||
SITE_HEADER = ''
|
||||
# jwt token configs
|
||||
|
||||
@@ -9,6 +9,7 @@ from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||
from rest_framework_simplejwt.views import TokenObtainPairView
|
||||
from django.shortcuts import get_object_or_404
|
||||
from rest_framework_simplejwt.tokens import RefreshToken
|
||||
import ghasedak_sms
|
||||
class SendOTPView(APIView):
|
||||
permission_classes = [AllowAny]
|
||||
@extend_schema(
|
||||
@@ -24,18 +25,45 @@ class SendOTPView(APIView):
|
||||
)
|
||||
def post(self, request):
|
||||
phone = request.data.get('phone')
|
||||
if not phone:
|
||||
return Response({'detail': 'Phone number is required'}, status=status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
try:
|
||||
user, created = User.objects.get_or_create(phone=phone)
|
||||
print(created)
|
||||
print(user.phone)
|
||||
otp = user.set_otp()
|
||||
otp = user.set_otp()
|
||||
message = f"کد یک بار مصرف : {otp}"
|
||||
print(message)
|
||||
# send otp
|
||||
return Response({'detail': 'OTP sent successfully'}, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
sms_api = ghasedak_sms.Ghasedak(api_key="4dc844abd4409fe247ec73831aed2498ad3749c1945660cc252654371756b966vafe5d9LGgMbnfGn")
|
||||
|
||||
# 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(
|
||||
message=message,
|
||||
receptor=phone,
|
||||
line_number='90002930',
|
||||
send_date='',
|
||||
client_reference_id=''
|
||||
)
|
||||
)
|
||||
|
||||
# response = sms_api.send_otp_sms(otp_input)
|
||||
|
||||
if response['statusCode'] == 200:
|
||||
return Response({'detail': 'OTP sent successfully'}, status=status.HTTP_200_OK)
|
||||
else:
|
||||
print('remmber to remove #TODO')
|
||||
return Response({'detail': f'OTP sent successfully {otp}'}, status=status.HTTP_200_OK)
|
||||
# return Response({'detail': response, 'otp_code': otp}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
except User.DoesNotExist:
|
||||
return Response({'detail': 'User not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
return Response({'detail': 'user not found'}, status=status.HTTP_404_NOT_FOUND)
|
||||
except Exception as e:
|
||||
return Response({'detail': f'An error occurred: {response}'}, status=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
class CustomTokenObtainPairView(TokenObtainPairView):
|
||||
|
||||
@@ -27,7 +27,7 @@ EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD")
|
||||
DEFAULT_FROM_EMAIL = os.getenv("SECRET_KEY")
|
||||
|
||||
SECRET_KEY = os.getenv("SECRET_KEY")
|
||||
DEBUG = os.getenv("DEBUG")
|
||||
DEBUG = False
|
||||
# in production lists of allowed hosts and allowed orgins will genrate
|
||||
# in development every host and orgin will be true
|
||||
# in prodcution it will use the postgres info you enterd in .env.local
|
||||
@@ -39,18 +39,18 @@ if not DEBUG:
|
||||
f"https://{DOMAIN}",
|
||||
f"http://{DOMAIN}",
|
||||
]
|
||||
CORS_ALLOWED_ORIGINS = [f"https://{API_DOMAIN}", f"http://{API_DOMAIN}",
|
||||
f"http://{DOMAIN}", f"https://{DOMAIN}", ]
|
||||
|
||||
# CORS_ALLOWED_ORIGINS = [f"https://{API_DOMAIN}", f"http://{API_DOMAIN}",
|
||||
# f"http://{DOMAIN}", f"https://{DOMAIN}", ]
|
||||
CORS_ALLOW_ALL_ORIGINS = True
|
||||
# database postgres
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.postgresql',
|
||||
'NAME': os.getenv("DB_NAME"),
|
||||
'USER': os.getenv("DB_USER"),
|
||||
'PASSWORD': os.getenv("DB_PASSWORD"),
|
||||
'HOST': os.getenv("DB_HOST"),
|
||||
'PORT': os.getenv("DB_PORT"),
|
||||
'NAME': "hshop",
|
||||
'USER': "byeto",
|
||||
'PASSWORD': "vuhbyq-cypMu0-sirbon",
|
||||
'HOST': 'db',
|
||||
'PORT': 5432,
|
||||
}
|
||||
}
|
||||
else:
|
||||
|
||||
+35
-1
@@ -13,4 +13,38 @@ sms_api = Ghasedak(api_key=os.getenv("SMS_API_KEY"))
|
||||
|
||||
### ارسال پیام otp
|
||||
|
||||
#response = sms_api.send_otp_sms(receptor='09359****', message='OTP message')
|
||||
#response = sms_api.send_otp_sms(receptor='09359****', message='OTP message')
|
||||
|
||||
|
||||
|
||||
import ghasedak_sms
|
||||
|
||||
# Initialize the Ghasedak API client with your API key
|
||||
sms_api = ghasedak_sms.Ghasedak(api_key="Your_API_KEY")
|
||||
|
||||
# Define the OTP code and the recipient's phone number
|
||||
otp_code = "123456" # Replace with the generated OTP code
|
||||
phone_number = "09xxxxxxxxx" # Replace with the recipient's phone number
|
||||
|
||||
# Create the OTP input object
|
||||
otp_input = ghasedak_sms.SendOtpInput(
|
||||
send_date=None, # Immediate send; use a specific datetime for scheduled send
|
||||
receptors=[
|
||||
ghasedak_sms.SendOtpReceptorDto(
|
||||
mobile=phone_number,
|
||||
# client_reference_id='optional_client_ref_id' # Optional: Add if you have a client reference ID
|
||||
)
|
||||
],
|
||||
template_name="YourTemplateName", # Replace with your OTP template name
|
||||
inputs=[
|
||||
ghasedak_sms.SendOtpInput.OtpInput(param="Code", value=otp_code),
|
||||
# Add more parameters if your template requires them
|
||||
],
|
||||
udh=False # Set to True if you need User Data Header; typically False for standard SMS
|
||||
)
|
||||
|
||||
# Send the OTP SMS
|
||||
response = sms_api.send_otp_sms(otp_input)
|
||||
|
||||
# Print the response to check the result
|
||||
print(response)
|
||||
+19
-11
@@ -1,11 +1,13 @@
|
||||
services:
|
||||
# frontend:
|
||||
# build:
|
||||
# context: ./frontend
|
||||
# ports:
|
||||
# - "80:3000"
|
||||
# depends_on:
|
||||
# - django
|
||||
frontend:
|
||||
build:
|
||||
context: ./frontend
|
||||
ports:
|
||||
- "80:3000"
|
||||
depends_on:
|
||||
- django
|
||||
networks:
|
||||
- default
|
||||
|
||||
django:
|
||||
build:
|
||||
@@ -18,22 +20,28 @@ services:
|
||||
- ./backend:/app
|
||||
- media_data:/app/media
|
||||
command: ["sh", "-c", "python manage.py migrate && python manage.py runserver 0.0.0.0:8000"]
|
||||
|
||||
networks:
|
||||
- default
|
||||
|
||||
db:
|
||||
image: postgres:16
|
||||
environment:
|
||||
POSTGRES_DB: shop_db
|
||||
POSTGRES_DB: hshop
|
||||
POSTGRES_USER: byeto
|
||||
POSTGRES_PASSWORD: vuhbyq-cypMu0-sirbon
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "5434:5432"
|
||||
- "5434:5432"
|
||||
networks:
|
||||
- default
|
||||
|
||||
|
||||
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
media_data:
|
||||
|
||||
|
||||
networks:
|
||||
default:
|
||||
@@ -120,15 +120,20 @@
|
||||
--breakpoint-xs: 480px;
|
||||
|
||||
/* ANIMATIONS */
|
||||
<<<<<<< HEAD
|
||||
--animate-marquee: marquee 3s linear infinite;
|
||||
--animate-slide-down: slideDown 300ms ease-out;
|
||||
--animate-slide-up: slideUp 300ms ease-out;
|
||||
--animate-overlay-show: overlayShow 150ms ease-in;
|
||||
--animate-content-show: contentShow 150ms ease-in;
|
||||
=======
|
||||
--animate-marquee: marquee 25s linear infinite;
|
||||
--animate-marquee-reverse: marquee 25s linear infinite reverse;
|
||||
>>>>>>> be4fa509843c81855f5ffc118150196c94a7b17b
|
||||
|
||||
@keyframes marquee {
|
||||
to {
|
||||
transform: translateY(-50%);
|
||||
transform: translateX(50%);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
tag: string;
|
||||
date: string;
|
||||
comments: number;
|
||||
title: string;
|
||||
description: string;
|
||||
link: string;
|
||||
variant?: "sm" | "lg";
|
||||
}
|
||||
|
||||
// props
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
variant: "lg"
|
||||
});
|
||||
const {} = toRefs(props);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
:class="variant === 'lg' ? 'rounded-150 overflow-hidden' : ''"
|
||||
class="max-h-[700px] h-[700px] relative"
|
||||
>
|
||||
|
||||
<Tag
|
||||
v-if="variant === 'lg'"
|
||||
class="bg-success-500 absolute left-10 top-10 z-20"
|
||||
>
|
||||
اسپیکر
|
||||
</Tag>
|
||||
|
||||
<div
|
||||
v-if="variant === 'sm'"
|
||||
class="h-[350px] rounded-150 overflow-hidden relative"
|
||||
>
|
||||
<Tag
|
||||
class="bg-success-500 absolute z-20 left-6 top-6"
|
||||
>
|
||||
اسپیکر
|
||||
</Tag>
|
||||
|
||||
<img
|
||||
src="/img/hero-bg.jpg"
|
||||
class="absolute size-full object-cover z-10"
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
:class="variant === 'lg' ? 'absolute px-10' : 'invert mt-8'"
|
||||
class="bottom-10 flex flex-col gap-6 z-20"
|
||||
>
|
||||
|
||||
<div class="flex items-center gap-6">
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon
|
||||
name="ci:comment"
|
||||
size="18"
|
||||
class="**:stroke-white"
|
||||
/>
|
||||
<span class="typo-p-sm text-white">
|
||||
۰ نظر
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<Icon
|
||||
name="ci:calendar"
|
||||
size="18"
|
||||
class="**:stroke-white"
|
||||
/>
|
||||
<span class="typo-p-sm text-white">
|
||||
۳۱ مهر ۱۴۰۳
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-4 flex-col">
|
||||
<span
|
||||
:class="variant === 'lg' ? 'typo-h-4' : 'typo-h-6'"
|
||||
class="text-white"
|
||||
>
|
||||
برسی آیفون ۱۶ پرومکس
|
||||
</span>
|
||||
<p
|
||||
:class="variant === 'lg' ? 'typo-h-4' : 'typo-h-6 text-slate-500'"
|
||||
class="typo-p-md text-white text-justify"
|
||||
>
|
||||
نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد.
|
||||
نیاز و کاربردهای متنوع با هدف بهبود ابزارهای کاربردی می باشد.
|
||||
کتابهای زیادی در شصت و سه درصد گذشته.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<span class="underline text-white typo-p-md">
|
||||
بیشتر بخوانید...
|
||||
</span>
|
||||
|
||||
</div>
|
||||
|
||||
<img
|
||||
v-if="variant === 'lg'"
|
||||
src="/img/hero-bg.jpg"
|
||||
class="absolute size-full object-cover z-10"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<div
|
||||
v-if="variant === 'lg'"
|
||||
class="w-full h-full bg-linear-to-t from-black to-transparent absolute inset-0 z-15"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,73 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
// brands: string[];
|
||||
}
|
||||
|
||||
// props
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const {} = toRefs(props);
|
||||
|
||||
// state
|
||||
|
||||
const { $gsap: gsap } = useNuxtApp();
|
||||
|
||||
// lifecycle
|
||||
|
||||
onMounted(() => {
|
||||
gsap.to("#marquee-text-container", {
|
||||
xPercent: -50, // Adjust based on content width
|
||||
duration: 10, // Adjust for desired speed
|
||||
ease: "none",
|
||||
repeat: -1
|
||||
});
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative w-full flex flex-col justify-center h-[350px]">
|
||||
|
||||
<div class="-rotate-z-2 z-20">
|
||||
<div
|
||||
class="bg-warning-500 flex pr-20 gap-20 py-2 w-max animate-marquee-reverse"
|
||||
>
|
||||
<span
|
||||
v-for="i in 10"
|
||||
class="text-[50px] text-white whitespace-nowrap font-semibold"
|
||||
>
|
||||
TEST {{ i }}
|
||||
</span>
|
||||
<span
|
||||
v-for="i in 10"
|
||||
class="text-[50px] text-white whitespace-nowrap font-semibold"
|
||||
>
|
||||
TEST {{ i }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="rotate-z-2 z-10">
|
||||
<div
|
||||
class="bg-slate-50 flex pr-20 gap-20 py-2 w-max animate-marquee"
|
||||
>
|
||||
<span
|
||||
v-for="i in 10"
|
||||
class="text-[50px] text-slate-300 whitespace-nowrap font-semibold"
|
||||
>
|
||||
TEST {{ i }}
|
||||
</span>
|
||||
<span
|
||||
v-for="i in 10"
|
||||
class="text-[50px] text-slate-300 whitespace-nowrap font-semibold"
|
||||
>
|
||||
TEST {{ i }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,47 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
category: string;
|
||||
count: number;
|
||||
description: string;
|
||||
picture: string;
|
||||
}
|
||||
|
||||
// props
|
||||
|
||||
defineProps<Props>();
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative rounded-150 overflow-hidden w-full h-[500px]">
|
||||
<img
|
||||
class="absolute object-cover size-full"
|
||||
:src="picture"
|
||||
alt=""
|
||||
/>
|
||||
<div class="bg-linear-to-t from-black/80 to-transparent absolute z-10 size-full" />
|
||||
|
||||
<div class="absolute z-20 bottom-0 p-6 flex items-end justify-between w-full">
|
||||
|
||||
<div class="flex flex-col gap-2 text-white">
|
||||
<div class="typo-s-h-md">
|
||||
تمام دسته ها
|
||||
<span class="typo-p-xs -translate-y-1 inline-block mr-1">
|
||||
24
|
||||
</span>
|
||||
</div>
|
||||
<span class="typo-p-md">محصولات ما را مشاهده کنید</span>
|
||||
</div>
|
||||
|
||||
<Icon
|
||||
size="24"
|
||||
name="ci:arrow-left"
|
||||
class="**:stroke-white mb-1"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
+2
-1
@@ -3,6 +3,7 @@
|
||||
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import type { SwiperClass } from "swiper/react";
|
||||
|
||||
// types
|
||||
|
||||
type Props = {
|
||||
@@ -22,6 +23,7 @@ const swiper_instance = ref<SwiperClass | null>(null);
|
||||
const onSwiper = (swiper: SwiperClass) => {
|
||||
swiper_instance.value = swiper;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -86,7 +88,6 @@ const onSwiper = (swiper: SwiperClass) => {
|
||||
tag="New"
|
||||
/>
|
||||
</SwiperSlide>
|
||||
...
|
||||
</Swiper>
|
||||
</div>
|
||||
</section>
|
||||
@@ -0,0 +1,96 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// types
|
||||
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import type { SwiperClass } from "swiper/react";
|
||||
|
||||
type Props = {}
|
||||
|
||||
// props
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const {} = toRefs(props);
|
||||
|
||||
// state
|
||||
|
||||
const swiper_instance = ref<SwiperClass | null>(null);
|
||||
|
||||
const slides = [
|
||||
{
|
||||
id: 0,
|
||||
title: "TEST"
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "TEST"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "TEST"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "TEST"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "TEST"
|
||||
}
|
||||
];
|
||||
|
||||
// methods
|
||||
|
||||
const onSwiper = (swiper: SwiperClass) => {
|
||||
swiper_instance.value = swiper;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full my-20 relative">
|
||||
<Swiper
|
||||
:slides-per-view="3.65"
|
||||
:space-between="20"
|
||||
:slides-offset-after="125"
|
||||
:slides-offset-before="125"
|
||||
@swiper="onSwiper"
|
||||
>
|
||||
<SwiperSlide
|
||||
v-for="slide in slides"
|
||||
:key="slide.id"
|
||||
>
|
||||
<CategoryCard
|
||||
category="یک دسته بندی تست"
|
||||
picture="/img/product-1.jpg"
|
||||
:count="20"
|
||||
description="یک دسته بندی تستasdasd"
|
||||
/>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
|
||||
<div
|
||||
v-if="!swiper_instance?.isBeginning"
|
||||
@click="swiper_instance?.slidePrev()"
|
||||
class="absolute z-20 right-20 shadow-lg cursor-pointer shadow-black/25 bottom-[50%] bg-white rounded-full size-11.5 flex justify-center items-center"
|
||||
>
|
||||
<Icon
|
||||
name="ci:arrow-right"
|
||||
class="**:stroke-black"
|
||||
size="24"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!swiper_instance?.isEnd"
|
||||
@click="swiper_instance?.slideNext()"
|
||||
class="absolute z-20 left-20 shadow-lg cursor-pointer shadow-black/25 bottom-[50%] bg-white rounded-full size-11.5 flex justify-center items-center"
|
||||
>
|
||||
<Icon
|
||||
name="ci:arrow-left"
|
||||
class="**:stroke-black"
|
||||
size="24"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,121 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// import
|
||||
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import type { SwiperClass } from "swiper/react";
|
||||
|
||||
// state
|
||||
|
||||
const swiper_instance = ref<SwiperClass | null>(null);
|
||||
|
||||
const slides = [
|
||||
{
|
||||
id: 0,
|
||||
title: "TEST"
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
title: "TEST"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "TEST"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "TEST"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: "TEST"
|
||||
}
|
||||
];
|
||||
|
||||
// methods
|
||||
|
||||
const onSwiper = (swiper: SwiperClass) => {
|
||||
swiper_instance.value = swiper;
|
||||
};
|
||||
|
||||
const onChange = (swiper: SwiperClass) => {
|
||||
console.log(swiper.activeIndex, swiper.realIndex, swiper.snapIndex);
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full mb-20">
|
||||
<div
|
||||
class="relative"
|
||||
>
|
||||
<Swiper
|
||||
:slides-per-view="1.2"
|
||||
:loop="true"
|
||||
:space-between="40"
|
||||
:centered-slides="true"
|
||||
@swiper="onSwiper"
|
||||
@slide-change="onChange"
|
||||
>
|
||||
<SwiperSlide
|
||||
v-for="slide in slides"
|
||||
:key="slide.id"
|
||||
>
|
||||
<div class="relative w-full rounded-200 h-[80svh] overflow-hidden">
|
||||
<img
|
||||
class="absolute inset-0 size-full object-cover"
|
||||
src="/img/hero-bg.jpg"
|
||||
alt=""
|
||||
/>
|
||||
<div class="size-full absolute z-10 bg-linear-to-t from-black to-transparent" />
|
||||
<div class="px-20 absolute z-10 w-full bottom-36">
|
||||
<div class="border-b border-white/10 pb-6">
|
||||
<h3 class="typo-hero-1 text-white">
|
||||
Samsung {{ slide.id }}
|
||||
</h3>
|
||||
<div class="flex justify-between items-end">
|
||||
<span class="typo-p-lg text-white">
|
||||
توضیحات درمورد این محصول خاص
|
||||
</span>
|
||||
<Button class="invert rounded-full hover:bg-transparent">
|
||||
خرید Samsung
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
<div class="absolute w-full bottom-20 left-[50%] translate-x-[-50%] z-100">
|
||||
<div class="container h-full">
|
||||
<div class="h-full flex items-center justify-between px-20">
|
||||
<button @click="swiper_instance?.slidePrev()">
|
||||
<Icon
|
||||
class="**:stroke-white cursor-pointer"
|
||||
name="ci:arrow-right"
|
||||
size="24"
|
||||
/>
|
||||
</button>
|
||||
<div class="flex items-center justify-center gap-3 text-white">
|
||||
<div
|
||||
v-for="(slide, index) in slides"
|
||||
:class="swiper_instance?.realIndex === index ? 'bg-white' : 'bg-transparent'"
|
||||
class="border border-white size-3 rounded-full transition-all duration-200"
|
||||
@click="swiper_instance?.slideTo(index)"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<button>
|
||||
<Icon
|
||||
@click="swiper_instance?.slideNext()"
|
||||
class="**:stroke-white cursor-pointer"
|
||||
name="ci:arrow-left"
|
||||
size="24"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// types
|
||||
|
||||
type Props = {}
|
||||
|
||||
// props
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const {} = toRefs(props);
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="h-[100svh] mt-20 px-20">
|
||||
<div class="flex items-center justify-between mb-20">
|
||||
<span class="typo-h-4 text-black">
|
||||
مقالات اخیر سایت
|
||||
</span>
|
||||
<Button variant="outlined" class="rounded-full" start-icon="ci:paper">
|
||||
نمایش همه
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex gap-12">
|
||||
<div class="flex-1 flex flex-col gap-12">
|
||||
<BlogPost
|
||||
description="aaasd"
|
||||
title="asd"
|
||||
:comments="2"
|
||||
link="#"
|
||||
date="2020-06-10"
|
||||
tag="asdsa"
|
||||
/>
|
||||
<BlogPost
|
||||
description="aaasd"
|
||||
title="asd"
|
||||
:comments="2"
|
||||
link="#"
|
||||
date="2020-06-10"
|
||||
tag="asdsa"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex-[0.8] flex flex-col">
|
||||
<BlogPost
|
||||
description="aaasd"
|
||||
title="asd"
|
||||
:comments="2"
|
||||
link="#"
|
||||
date="2020-06-10"
|
||||
tag="asdsa"
|
||||
variant="sm"
|
||||
/>
|
||||
<BlogPost
|
||||
description="aaasd"
|
||||
title="asd"
|
||||
:comments="2"
|
||||
link="#"
|
||||
date="2020-06-10"
|
||||
tag="asdsa"
|
||||
variant="sm"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// import
|
||||
|
||||
import { Swiper, SwiperSlide } from "swiper/vue";
|
||||
import type { SwiperClass } from "swiper/react";
|
||||
|
||||
// types
|
||||
|
||||
type Props = {}
|
||||
|
||||
// props
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const {} = toRefs(props);
|
||||
|
||||
// state
|
||||
|
||||
const swiper_instance = ref<SwiperClass | null>(null);
|
||||
|
||||
// methods
|
||||
|
||||
const onSwiper = (swiper: SwiperClass) => {
|
||||
swiper_instance.value = swiper;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="relative max-h-[700px] flex justify-center items-center h-svh w-full">
|
||||
<img
|
||||
class="absolute size-full object-cover"
|
||||
src="/img/hero-bg.jpg"
|
||||
alt=""
|
||||
/>
|
||||
<div class="absolute bg-black/60 size-full z-10" />
|
||||
|
||||
<div class="w-full relative z-10">
|
||||
<Swiper
|
||||
:slides-per-view="1"
|
||||
:loop="true"
|
||||
:space-between="40"
|
||||
:centered-slides="true"
|
||||
:grab-cursor="true"
|
||||
@swiper="onSwiper"
|
||||
>
|
||||
<SwiperSlide
|
||||
v-for="i in 6"
|
||||
:key="i"
|
||||
>
|
||||
<div class="flex justify-center items-center">
|
||||
<div class="max-w-[900px] px-4 text-white flex flex-col items-center gap-4">
|
||||
<Icon name="ci:instagram" size="28" class="**:stroke-white" />
|
||||
<p class="typo-h-5 leading-[150%] text-center">
|
||||
لورم ایپسوم متن ساختگی با تولید سادگی نامفهوم از صنعت چاپ و با
|
||||
استفاده از طراحان گرافیک است. چاپگرها و متون بلکه روزنامه و مجله
|
||||
در ستون و سطرآنچنان که لازم.
|
||||
</p>
|
||||
<span class="typo-p-xl text-center">
|
||||
- منصور مرزبان
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</SwiperSlide>
|
||||
</Swiper>
|
||||
|
||||
<div class="flex items-center justify-center gap-3 text-white mt-6 relative z-10">
|
||||
<div
|
||||
v-for="(i, index) in 6"
|
||||
:class="swiper_instance?.realIndex === index ? 'bg-white' : 'bg-transparent'"
|
||||
class="border border-white size-2 rounded-full transition-all duration-200"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,93 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// import
|
||||
|
||||
import { useDraggable } from "@vueuse/core";
|
||||
|
||||
// state
|
||||
|
||||
const clipPathPercent = ref(49);
|
||||
|
||||
const draggableEl = ref<HTMLElement | null>(null);
|
||||
const previewContainerEl = ref<HTMLElement | null>(null);
|
||||
|
||||
const { x: dragAxisX } = useDraggable(draggableEl, {
|
||||
initialValue: { x: 0, y: 0 },
|
||||
axis: "x"
|
||||
});
|
||||
|
||||
// watch
|
||||
|
||||
watch(() => dragAxisX.value, (newValue) => {
|
||||
const clientRect = previewContainerEl.value?.getBoundingClientRect()!;
|
||||
const percent = clientRect.width / 100;
|
||||
const clipPercent = (newValue - clientRect.x - 8) / percent;
|
||||
if (clipPercent >= 5 && clipPercent <= 95) {
|
||||
clipPathPercent.value = clipPercent;
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="flex flex-col items-center gap-3 mb-16">
|
||||
<span class="typo-p-md text-slate-500">یک متن تست لورم</span>
|
||||
<span class="typo-h-3 text-black">تفاوت محصول را ببینید در اینجا</span>
|
||||
</div>
|
||||
<div ref="previewContainerEl" class="rounded-200 overflow-hidden h-[90svh] relative">
|
||||
|
||||
<img
|
||||
src="/img/hero-bg.jpg"
|
||||
class="select-none absolute size-full object-cover"
|
||||
alt=""
|
||||
/>
|
||||
|
||||
<div
|
||||
class="absolute size-full right-0 w-full"
|
||||
>
|
||||
<img
|
||||
src="/img/hero-bg.jpg"
|
||||
class="overlay-image select-none absolute object-cover size-full hue-rotate-200 brightness-35"
|
||||
alt=""
|
||||
/>
|
||||
<div
|
||||
:style="{
|
||||
left: `${clipPathPercent}%`
|
||||
}"
|
||||
ref="draggableEl"
|
||||
class="select-none w-2 h-full bg-white absolute left-0 flex items-center justify-center"
|
||||
>
|
||||
<div
|
||||
class="cursor-grab hover:scale-115 transition-transform rounded-full absolute bg-white size-11 flex items-center justify-center"
|
||||
>
|
||||
<Icon name="ci:arrows" size="24" class="**:stroke-black" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute bottom-0 p-10 w-full flex justify-between items-end bg-linear-to-t from-black/55 to-transparent">
|
||||
<div class="flex flex-col gap-2 text-white">
|
||||
<span class="typo-p-md">رنگ محصول</span>
|
||||
<span class="typo-h-3">نارنجی</span>
|
||||
</div>
|
||||
<div class="flex flex-col justify-start gap-2 text-white">
|
||||
<span class="typo-p-md">رنگ محصول</span>
|
||||
<span class="typo-h-3">سفید</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.overlay-image {
|
||||
clip-path: polygon(
|
||||
v-bind('clipPathPercent + "%"') 0,
|
||||
100% 0,
|
||||
100% 100%,
|
||||
v-bind('clipPathPercent + "%"') 100%
|
||||
);
|
||||
}
|
||||
</style>
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
// types
|
||||
import Tooltip from "~/components/Tooltip.vue";
|
||||
import Tooltip from "~/components/ui/Tooltip.vue";
|
||||
|
||||
type Props = {
|
||||
variant?: "solid" | "outlined";
|
||||
@@ -0,0 +1,13 @@
|
||||
FROM node:20-alpine as build-stage
|
||||
WORKDIR /app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM node:20-alpine as production-stage
|
||||
WORKDIR /app
|
||||
COPY --from=build-stage /app /app
|
||||
EXPOSE 3000
|
||||
ENV NODE_ENV=production
|
||||
CMD ["npm", "run", "start"]
|
||||
+11
-19
@@ -1,26 +1,18 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import Categories from "~/components/home/Categories.vue";
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full h-screen p-8 flex gap-8 justify-start items-start container">
|
||||
<Product
|
||||
brand="Samsung"
|
||||
title="Galaxy S20 Ultra"
|
||||
picture="/assets/img/product-1.jpg"
|
||||
:colors="['#0000ff', '#00ff00','red']"
|
||||
:price="599"
|
||||
:rate="2.4"
|
||||
tag="New"
|
||||
/>
|
||||
<Product
|
||||
brand="Samsung"
|
||||
title="Galaxy S20 Ultra"
|
||||
picture="/assets/img/product-1.jpg"
|
||||
:colors="['#0000ff', '#00ff00','red']"
|
||||
:price="599"
|
||||
:rate="2.4"
|
||||
tag="New"
|
||||
/>
|
||||
<div class="w-full">
|
||||
<Hero />
|
||||
<Preview />
|
||||
<Categories />
|
||||
<ProductsSlider title="یک عنوان تستی" />
|
||||
<Brands />
|
||||
<ProductHero />
|
||||
<MostRecentComments />
|
||||
<LatestStories />
|
||||
</div>
|
||||
</template>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 5.2 MiB |
Reference in New Issue
Block a user