@akinon/pz-b2b 1.18.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,26 @@
1
+ 'use client';
2
+
3
+ import { GTMEvent, Product } from '@akinon/next/types';
4
+
5
+ export const pushCartView = (
6
+ products: Pick<Product, 'name' | 'pk' | 'price'>[]
7
+ ) => {
8
+ pushEvent({
9
+ Action: 'Checkout',
10
+ Label: 'Sepet',
11
+ ecommerce: {
12
+ products: products?.map((product) => ({
13
+ name: product.name,
14
+ id: product.pk,
15
+ price: product.price
16
+ }))
17
+ },
18
+ event: 'eeCheckout'
19
+ });
20
+ };
21
+
22
+ export const pushEvent = (event: GTMEvent) => {
23
+ const { Category = 'Enhanced Ecommerce', Value = 0, ...rest } = event;
24
+
25
+ window.dataLayer.push({ Category, Value, ...rest });
26
+ };
@@ -0,0 +1,144 @@
1
+ 'use client';
2
+
3
+ import { useLocalization } from '@akinon/next/hooks';
4
+ import { Image, Price, Input, Icon } from '@akinon/next/components';
5
+ import { useUpdateProductMutation } from '@akinon/next/data/client/b2b';
6
+ import { BasketItemType } from '@akinon/next/types';
7
+ import React, { useState } from 'react';
8
+
9
+ interface Props {
10
+ basketItem: BasketItemType;
11
+ setProductToBeDeleted: (item: BasketItemType) => void;
12
+ setRemoveProduct: (open: boolean) => void;
13
+ }
14
+
15
+ export const BasketItem = (props: Props) => {
16
+ const { t } = useLocalization();
17
+ const { basketItem, setRemoveProduct, setProductToBeDeleted } = props;
18
+ const [updateProduct] = useUpdateProductMutation();
19
+ const [divisionErrors, setDivisionErrors] = useState([]);
20
+
21
+ const handleChange = (divisionID, e, index) => {
22
+ if (parseInt(e.target.value) < 1) {
23
+ setRemoveProduct(true);
24
+ setProductToBeDeleted(basketItem);
25
+ } else {
26
+ updateProduct({
27
+ product_remote_id: basketItem.product_remote_id,
28
+ division: divisionID,
29
+ quantity: parseInt(e.target.value)
30
+ }).then((res) => {
31
+ if ('error' in res && 'data' in res.error) {
32
+ setDivisionErrors((prevErrors) => {
33
+ const newErrors = [...prevErrors];
34
+ newErrors[index] = (res.error as any).data.quantity || [];
35
+ return newErrors;
36
+ });
37
+ } else {
38
+ setDivisionErrors((prevErrors) => {
39
+ const newErrors = [...prevErrors];
40
+ newErrors[index] = [];
41
+ return newErrors;
42
+ });
43
+ }
44
+ });
45
+ }
46
+ };
47
+
48
+ return (
49
+ <>
50
+ <tr>
51
+ <td>
52
+ <div className='h-[183px] w-full flex flex-col justify-between items-center text-xs'>
53
+ <div className='w-full flex items-center justify-center py-3 mb-3 border-b'>
54
+ <Icon name='chevron-down' size={10} />
55
+ </div>
56
+
57
+ <div className='h-full overflow-hidden'
58
+ style={{ writingMode: 'vertical-rl', textOrientation: 'mixed', display: '-webkit-box', lineClamp: 1, WebkitLineClamp: 1, WebkitBoxOrient: 'vertical' }}
59
+ >
60
+ {basketItem.product.name}{basketItem.product.name}{basketItem.product.name}
61
+ </div>
62
+
63
+ <button
64
+ onClick={() => { setProductToBeDeleted(basketItem), setRemoveProduct(true) }}
65
+ className='py-1.5 border-t w-full flex items-center justify-center'
66
+ style={{ writingMode: 'vertical-rl', textOrientation: 'mixed' }}
67
+ >
68
+ {t('basket.b2b.table.delete')}
69
+ </button>
70
+ </div>
71
+ </td>
72
+
73
+ <td className='border text-center'>
74
+ <div className='min-w-[80px] w-full h-full flex items-center justify-center'>
75
+ <Image
76
+ src={basketItem.product.product_image}
77
+ alt={basketItem.product.name}
78
+ width={50}
79
+ height={65}
80
+ />
81
+ </div>
82
+ </td>
83
+
84
+ <td className='min-w-[181px] text-sm border text-[#3f4b5c] leading-normal'>
85
+ <div className='px-4'>
86
+ <div className='break-words'>{basketItem.product.name}</div>
87
+ <div className='break-words'>SKU: {basketItem.product.sku}</div>
88
+ </div>
89
+ </td>
90
+
91
+ <td className='text-sm border text-[#3f4b5c] leading-normal text-right px-3'>
92
+ <Price value={basketItem.price} className='w-max block' />
93
+ </td>
94
+
95
+ <td className='text-sm border text-[#3f4b5c] leading-normal text-center'>
96
+ <div className='min-w-[90px]'>
97
+ {basketItem.quantity}
98
+ </div>
99
+ </td>
100
+
101
+ <td className='border'>
102
+ <div className='min-w-[140px]'>
103
+ {basketItem.divisions.map((division) => (
104
+ <div className='py-5 px-1 border-b last:border-0 text-center flex items-center justify-center gap-x-1' key={division.name}>
105
+ <Icon name='b2b-info' size={18} className='text-[#293245]' />
106
+
107
+ <div className='text-sm underline text-[#3f4b5c] overflow-hidden' style={{ display: '-webkit-box', lineClamp: 1, WebkitLineClamp: 1, WebkitBoxOrient: 'vertical' }}>
108
+ {division.name}
109
+ </div>
110
+ </div>
111
+ ))}
112
+ </div>
113
+ </td>
114
+
115
+ <td className='border'>
116
+ <div className='min-w-[120px]'>
117
+ {basketItem.divisions.map((division, index) => (
118
+ <div className='relative py-5 border-b last:border-0 text-center flex flex-col gap-y-1 items-center justify-center' key={`division-${division.id}`}>
119
+ <Input
120
+ className='appearance-none outline-none w-20 h-6 px-2 py-0 border border-[#c8daec] rounded text-right'
121
+ type="number"
122
+ value={division.quantity}
123
+ onChange={(e) => handleChange(division.id, e, index)}
124
+ />
125
+
126
+ {divisionErrors[index] && (
127
+ <div className="text-xs text-error mb-2 absolute -bottom-1">
128
+ {divisionErrors[index].map((err) => (
129
+ <div key={err}>{err}</div>
130
+ ))}
131
+ </div>
132
+ )}
133
+ </div>
134
+ ))}
135
+ </div>
136
+ </td>
137
+
138
+ <td className='text-sm border text-[#3f4b5c] leading-normal text-right px-6'>
139
+ <Price value={basketItem.total_amount} className='w-max block' />
140
+ </td>
141
+ </tr>
142
+ </>
143
+ );
144
+ };
@@ -0,0 +1,4 @@
1
+ export * from './basket-item';
2
+ export * from './remove-product-modal';
3
+ export * from './request-quote-modal';
4
+ export * from './save-cart-modal';
@@ -0,0 +1,109 @@
1
+ 'use client';
2
+
3
+ import { useDeleteProductMutation } from '@akinon/next/data/client/b2b';
4
+ import { Image } from '@akinon/next/components';
5
+ import { useLocalization } from '@akinon/next/hooks';
6
+ import React, { useEffect } from 'react';
7
+ import { BasketItemType } from '@akinon/next/types';
8
+ import { useAddFavoriteMutation } from '@akinon/next/data/client/wishlist';
9
+ import clsx from 'clsx';
10
+
11
+ export const RemoveProductModal = ({
12
+ open,
13
+ setOpen,
14
+ basketItem
15
+ }: {
16
+ open: boolean;
17
+ setOpen: (open: boolean) => void;
18
+ basketItem: BasketItemType
19
+ }) => {
20
+ const { t } = useLocalization();
21
+ const [addFavorite] = useAddFavoriteMutation();
22
+ const [deleteProduct, { isLoading, isSuccess }] = useDeleteProductMutation();
23
+
24
+ const handleClick = (addToFavorite:boolean) => {
25
+ if(addToFavorite) {
26
+ addFavorite(basketItem.product_remote_id)
27
+ }
28
+
29
+ deleteProduct({ product_remote_id: basketItem.product_remote_id });
30
+ };
31
+
32
+ useEffect(() => {
33
+ if (isSuccess) {
34
+ setOpen(false);
35
+ }
36
+ }, [isSuccess]);
37
+
38
+ if (!open) return null;
39
+
40
+ return (
41
+ <div className="before:w-full before:h-screen before:content-[''] before:bg-black/60 before:z-40 before:fixed before:inset-0">
42
+ <div className="w-11/12 max-w-[344px] md:w-[370px] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 fixed bg-white">
43
+ <header className="relative border-b border-[#d5dadb]">
44
+ <h3 className="text-left px-[30px] py-4 font-medium text-[17px]">
45
+ {t('basket.b2b.remove_product_modal.title')}
46
+ </h3>
47
+
48
+ <button
49
+ className="absolute right-[30px] top-5"
50
+ onClick={() => setOpen(false)}
51
+ >
52
+ <i className="pz-icon-close"></i>
53
+ </button>
54
+ </header>
55
+
56
+ <p className="text-left px-[30px] py-4 text-sm">
57
+ {t('basket.b2b.remove_product_modal.description')}
58
+ </p>
59
+
60
+ <div className="px-[30px] pb-5">
61
+ <div className='flex items-center gap-x-3'>
62
+ <div>
63
+ <Image src={basketItem.product.product_image} width={72} height={93} alt={basketItem.product.name} />
64
+ </div>
65
+ <div className='text-[#3f4b5c] text-sm'>
66
+ <p>{basketItem.product.name}</p>
67
+ <p>SKU: {basketItem.product.sku}</p>
68
+ </div>
69
+ </div>
70
+
71
+ <div className='flex gap-y-3 flex-col items-center'>
72
+ <button
73
+ type="submit"
74
+ className="h-8 bg-black text-white font-medium text-sm w-full"
75
+ disabled={isLoading}
76
+ onClick={() => handleClick(true)}
77
+ >
78
+ {isLoading ? (
79
+ <div className="w-full h-full flex justify-center items-center">
80
+ <div className="w-4 h-4 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
81
+ </div>
82
+ ) : (
83
+ t('basket.b2b.remove_product_modal.add_to_favorites_button')
84
+ )}
85
+ </button>
86
+
87
+ <button
88
+ type="submit"
89
+ className={clsx(
90
+ 'h-auto bg-transparent text-[#181818] border-[#181818] text-xs w-auto',
91
+ !isLoading && 'border-b'
92
+ )}
93
+ disabled={isLoading}
94
+ onClick={() => handleClick(false)}
95
+ >
96
+ {isLoading ? (
97
+ <div className="w-full h-full flex justify-center items-center">
98
+ <div className="w-4 h-4 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
99
+ </div>
100
+ ) : (
101
+ t('basket.b2b.remove_product_modal.delete_product_button')
102
+ )}
103
+ </button>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ );
109
+ }
@@ -0,0 +1,97 @@
1
+ 'use client';
2
+
3
+ import { useCreateQuotationMutation } from '@akinon/next/data/client/b2b';
4
+ import { useLocalization } from '@akinon/next/hooks';
5
+ import React, { useEffect, useState } from 'react';
6
+
7
+ export const RequestQuoteModal = ({
8
+ open,
9
+ setOpen
10
+ }: {
11
+ open: boolean;
12
+ setOpen: (open: boolean) => void;
13
+ }) => {
14
+ const { t } = useLocalization();
15
+ const [createQuotation, { isLoading, isSuccess, error, isError }] = useCreateQuotationMutation();
16
+ const [errors, setErrors] = useState([]);
17
+ const [cartName, setCartName] = useState<string>('');
18
+
19
+ const handleSubmit = (e) => {
20
+ e.preventDefault();
21
+
22
+ if (!cartName) {
23
+ return;
24
+ }
25
+
26
+ createQuotation({ name: cartName }).then((res) => {
27
+ if ('error' in res && 'data' in res.error) {
28
+ setErrors((res.error.data as any).non_field_errors || []);
29
+ }
30
+ })
31
+ };
32
+
33
+ useEffect(() => {
34
+ if (isSuccess) {
35
+ setOpen(false);
36
+ }
37
+ }, [isSuccess]);
38
+
39
+ if (!open) return null;
40
+
41
+ return (
42
+ <div className="before:w-full before:h-screen before:content-[''] before:bg-black/60 before:z-40 before:fixed before:inset-0">
43
+ <div className="w-11/12 max-w-[344px] md:w-[370px] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 fixed bg-white">
44
+ <header className="relative border-b border-[#d5dadb]">
45
+ <h3 className="text-left px-[30px] py-4 font-medium text-lg">
46
+ {t('basket.b2b.request_quote_modal.title')}
47
+ </h3>
48
+
49
+ <button
50
+ className="absolute right-[30px] top-5"
51
+ onClick={() => setOpen(false)}
52
+ >
53
+ <i className="pz-icon-close"></i>
54
+ </button>
55
+ </header>
56
+
57
+ <div className="p-[30px]">
58
+ <form onSubmit={(e) => handleSubmit(e)}>
59
+ <label htmlFor="cart-name-input" className="text-xs block mb-2">
60
+ {t('basket.b2b.request_quote_modal.quotation_name')}
61
+ </label>
62
+
63
+ <input
64
+ id="cart-name-input"
65
+ name="name"
66
+ type="text"
67
+ className="h-8 border border-[#eeeeee] text-sm mb-2.5 outline-none px-2 w-full"
68
+ onChange={(e) => setCartName(e.target.value)}
69
+ />
70
+
71
+ {isError && (
72
+ <div className="text-xs text-error mb-2">
73
+ {errors.map((err) => (
74
+ <div key={err}>{err}</div>
75
+ ))}
76
+ </div>
77
+ )}
78
+
79
+ <button
80
+ type="submit"
81
+ className="h-8 bg-black text-white font-medium text-sm w-full"
82
+ disabled={isLoading}
83
+ >
84
+ {isLoading ? (
85
+ <div className="w-full h-full flex justify-center items-center">
86
+ <div className="w-4 h-4 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
87
+ </div>
88
+ ) : (
89
+ t('basket.b2b.request_quote_modal.button')
90
+ )}
91
+ </button>
92
+ </form>
93
+ </div>
94
+ </div>
95
+ </div>
96
+ );
97
+ }
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+
3
+ import { useSaveBasketMutation } from '@akinon/next/data/client/b2b';
4
+ import { useLocalization } from '@akinon/next/hooks';
5
+ import React, { useEffect, useState } from 'react';
6
+
7
+ export const SaveCartModal = ({
8
+ open,
9
+ setOpen
10
+ }: {
11
+ open: boolean;
12
+ setOpen: (open: boolean) => void;
13
+ }) => {
14
+ const { t } = useLocalization();
15
+ const [cartName, setCartName] = useState<string>('');
16
+ const [saveBasket, { isLoading, isSuccess }] = useSaveBasketMutation();
17
+
18
+ const handleSubmit = (e) => {
19
+ e.preventDefault();
20
+
21
+ if (!cartName) {
22
+ return;
23
+ }
24
+
25
+ saveBasket({ name: cartName });
26
+ };
27
+
28
+ useEffect(() => {
29
+ if (isSuccess) {
30
+ setOpen(false);
31
+ }
32
+ }, [isSuccess]);
33
+
34
+ if (!open) return null;
35
+
36
+ return (
37
+ <div className="before:w-full before:h-screen before:content-[''] before:bg-black/60 before:z-40 before:fixed before:inset-0">
38
+ <div className="w-11/12 max-w-[344px] md:w-[370px] left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 z-50 fixed bg-white">
39
+ <header className="relative border-b border-[#d5dadb]">
40
+ <h3 className="text-left px-[30px] py-4 font-medium text-lg">
41
+ {t('basket.b2b.save_cart_modal.title')}
42
+ </h3>
43
+ <button
44
+ className="absolute right-[30px] top-5"
45
+ onClick={() => setOpen(false)}
46
+ >
47
+ <i className="pz-icon-close"></i>
48
+ </button>
49
+ </header>
50
+ <div className="p-[30px]">
51
+ <form onSubmit={(e) => handleSubmit(e)}>
52
+ <label htmlFor="cart-name-input" className="text-xs block mb-2">
53
+ {t('basket.b2b.save_cart_modal.cart_name')}
54
+ </label>
55
+ <input
56
+ id="cart-name-input"
57
+ name="name"
58
+ type="text"
59
+ className="h-8 border border-[#eeeeee] text-sm mb-2.5 outline-none px-2 w-full"
60
+ onChange={(e) => setCartName(e.target.value)}
61
+ />
62
+ <button
63
+ type="submit"
64
+ className="h-8 bg-black text-white font-medium text-sm w-full"
65
+ disabled={isLoading}
66
+ >
67
+ {isLoading ? (
68
+ <div className="w-full h-full flex justify-center items-center">
69
+ <div className="w-4 h-4 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
70
+ </div>
71
+ ) : (
72
+ t('basket.b2b.save_cart_modal.save_cart_button')
73
+ )}
74
+ </button>
75
+ </form>
76
+ </div>
77
+ </div>
78
+ </div>
79
+ );
80
+ }
@@ -0,0 +1,184 @@
1
+ 'use client';
2
+
3
+ import { GetQuotationsResponse } from '@akinon/next/data/client/account';
4
+ import clsx from 'clsx';
5
+ import React, { Dispatch, SetStateAction, useEffect, useState } from 'react';
6
+ import { twMerge } from 'tailwind-merge';
7
+
8
+ type TabsType = {
9
+ quotations: GetQuotationsResponse | undefined;
10
+ setActiveTab: Dispatch<SetStateAction<string>>;
11
+ activeTab: string;
12
+ };
13
+
14
+ const initialTabCountsState = {
15
+ pendingCount: 0,
16
+ rejectedCount: 0,
17
+ approvedCount: 0,
18
+ sendingApprovalRequestCount: 0
19
+ };
20
+
21
+ export function Tabs(props: TabsType) {
22
+ const quotations = props.quotations?.results;
23
+ const setActiveTab = props.setActiveTab;
24
+ const activeTab = props.activeTab;
25
+ const [tabCounts, setTabCounts] = useState(initialTabCountsState);
26
+
27
+ useEffect(() => {
28
+ setTabCounts(initialTabCountsState);
29
+
30
+ quotations?.forEach((quotation) => {
31
+ if (quotation.status === 'pending') {
32
+ setTabCounts((prevCount) => ({
33
+ ...prevCount,
34
+ pendingCount: prevCount.pendingCount + 1
35
+ }));
36
+ } else if (quotation.status === 'rejected') {
37
+ setTabCounts((prevCount) => ({
38
+ ...prevCount,
39
+ rejectedCount: prevCount.rejectedCount + 1
40
+ }));
41
+ } else if (quotation.status === 'approved') {
42
+ setTabCounts((prevCount) => ({
43
+ ...prevCount,
44
+ approvedCount: prevCount.approvedCount + 1
45
+ }));
46
+ } else if (quotation.status === 'sending_approval_request') {
47
+ setTabCounts((prevCount) => ({
48
+ ...prevCount,
49
+ sendingApprovalRequestCount: prevCount.sendingApprovalRequestCount + 1
50
+ }));
51
+ }
52
+ });
53
+ }, [quotations]);
54
+
55
+ return (
56
+ <div className="mb-9 mt-4 lg:mt-0">
57
+ <div className="relative flex justify-start lg:justify-between space-x-3 lg:space-x-4">
58
+ <div
59
+ className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
60
+ onClick={() => setActiveTab('all')}
61
+ >
62
+ <span
63
+ className={twMerge(
64
+ clsx(
65
+ 'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
66
+ ),
67
+ activeTab === 'all' && 'bg-[#71d200] text-white'
68
+ )}
69
+ >
70
+
71
+ </span>
72
+ <span
73
+ className={clsx(
74
+ 'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
75
+ activeTab === 'all' && 'font-bold'
76
+ )}
77
+ >
78
+ All Quotes (<span>{quotations?.length}</span>)
79
+ </span>
80
+ </div>
81
+
82
+ <div
83
+ className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
84
+ onClick={() => setActiveTab('pending')}
85
+ >
86
+ <span
87
+ className={twMerge(
88
+ clsx(
89
+ 'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
90
+ ),
91
+ activeTab === 'pending' && 'bg-[#71d200] text-white'
92
+ )}
93
+ >
94
+
95
+ </span>
96
+ <span
97
+ className={clsx(
98
+ 'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
99
+ activeTab === 'pending' && 'font-bold'
100
+ )}
101
+ >
102
+ Pending (<span>{tabCounts.pendingCount}</span>)
103
+ </span>
104
+ </div>
105
+
106
+ <div
107
+ className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
108
+ onClick={() => setActiveTab('rejected')}
109
+ >
110
+ <span
111
+ className={twMerge(
112
+ clsx(
113
+ 'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
114
+ ),
115
+ activeTab === 'rejected' && 'bg-[#71d200] text-white'
116
+ )}
117
+ >
118
+
119
+ </span>
120
+ <span
121
+ className={clsx(
122
+ 'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
123
+ activeTab === 'rejected' && 'font-bold'
124
+ )}
125
+ >
126
+ Rejected (<span>{tabCounts.rejectedCount}</span>)
127
+ </span>
128
+ </div>
129
+
130
+ <div
131
+ className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
132
+ onClick={() => setActiveTab('approved')}
133
+ >
134
+ <span
135
+ className={twMerge(
136
+ clsx(
137
+ 'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
138
+ ),
139
+ activeTab === 'approved' && 'bg-[#71d200] text-white'
140
+ )}
141
+ >
142
+
143
+ </span>
144
+ <span
145
+ className={clsx(
146
+ 'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
147
+ activeTab === 'approved' && 'font-bold'
148
+ )}
149
+ >
150
+ Approved (<span>{tabCounts.approvedCount}</span>)
151
+ </span>
152
+ </div>
153
+
154
+ <div
155
+ className="flex flex-col items-center space-y-1.5 transition-all cursor-pointer"
156
+ onClick={() => setActiveTab('sending_approval_request')}
157
+ >
158
+ <span
159
+ className={twMerge(
160
+ clsx(
161
+ 'flex items-center text-xl justify-center w-9 h-9 rounded-full p-1.5 border border-[#eeeded] text-black bg-white'
162
+ ),
163
+ activeTab === 'sending_approval_request' &&
164
+ 'bg-[#71d200] text-white'
165
+ )}
166
+ >
167
+
168
+ </span>
169
+ <span
170
+ className={clsx(
171
+ 'text-xs text-center text-[#363636] w-[50px] lg:w-[110px]',
172
+ activeTab === 'sending_approval_request' && 'font-bold'
173
+ )}
174
+ >
175
+ Requires My Approval (
176
+ <span>{tabCounts.sendingApprovalRequestCount}</span>)
177
+ </span>
178
+ </div>
179
+
180
+ <span className="absolute h-px bg-[#eeeded] top-[18px] left-6 -translate-y-1/2 lg:left-1/2 lg:-translate-x-1/2 z-[-1] w-[250px] lg:w-[calc(100%-110px)]"></span>
181
+ </div>
182
+ </div>
183
+ );
184
+ }