Browse Source
feat: implement subscription management features, including active subscriptions, plans, and history sections
master
feat: implement subscription management features, including active subscriptions, plans, and history sections
master
sina_sajjadi
4 weeks ago
14 changed files with 571 additions and 25 deletions
-
1.env
-
50public/image/Frame 1000005757.svg
-
BINpublic/image/Frame 1000005757.webp
-
8public/image/payments/🦆 icon _PayPal_.svg
-
3public/image/payments/🦆 icon _Stripe_.svg
-
2public/locales/en/common.json
-
3src/data/client/api-endpoints.ts
-
6src/data/subscription.ts
-
24src/pages/subscriptions.tsx
-
69src/pages/subscriptions/active-section.tsx
-
93src/pages/subscriptions/history-section.tsx
-
32src/pages/subscriptions/index.tsx
-
69src/pages/subscriptions/modal.tsx
-
236src/pages/subscriptions/plans-section.tsx
50
public/image/Frame 1000005757.svg
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -0,0 +1,8 @@ |
|||
<svg width="92" height="25" viewBox="0 0 92 25" fill="none" xmlns="http://www.w3.org/2000/svg"> |
|||
<path d="M15.9294 1.76469C14.7886 1.00258 13.3 0.620605 11.4636 0.620605H4.35384C3.79086 0.620605 3.47983 0.894219 3.42075 1.44092L0.532503 19.0593C0.502487 19.2323 0.547036 19.3906 0.665744 19.5344C0.783773 19.6786 0.932226 19.7503 1.11002 19.7503H4.48708C5.0794 19.7503 5.40496 19.4773 5.46473 18.9299L6.26444 14.1802C6.29364 13.95 6.39782 13.7628 6.57561 13.6187C6.75327 13.4748 6.97547 13.3807 7.24209 13.3375C7.5087 13.2948 7.76011 13.2733 7.99753 13.2733C8.23426 13.2733 8.51555 13.2878 8.84193 13.3164C9.16749 13.3452 9.37503 13.3592 9.46399 13.3592C12.0115 13.3592 14.011 12.6616 15.4628 11.265C16.9139 9.86898 17.6403 7.93322 17.6403 5.45697C17.6403 3.75866 17.0696 2.52799 15.9294 1.76469ZM12.2635 7.05483C12.115 8.06257 11.7303 8.72437 11.1082 9.04114C10.486 9.35832 9.59737 9.51604 8.44221 9.51604L6.97574 9.5592L7.73132 4.93864C7.79026 4.62227 7.98272 4.46388 8.30883 4.46388H9.15337C10.3377 4.46388 11.1973 4.62979 11.7306 4.96029C12.2635 5.29158 12.4413 5.98994 12.2635 7.05483Z" fill="#003087"/> |
|||
<path d="M90.9608 0.620605H87.6728C87.3462 0.620605 87.1539 0.778994 87.0952 1.09577L84.2067 19.0594L84.1621 19.1457C84.1621 19.2904 84.2215 19.4265 84.3402 19.5561C84.4581 19.6854 84.6068 19.7503 84.7843 19.7503H87.7174C88.2794 19.7503 88.5906 19.4773 88.6506 18.9299L91.5389 1.26801V1.22512C91.5387 0.822154 91.3456 0.620605 90.9608 0.620605Z" fill="#009CDE"/> |
|||
<path d="M51.233 7.57131C51.233 7.4277 51.1733 7.2907 51.0556 7.16135C50.9367 7.03187 50.8032 6.9668 50.6556 6.9668H47.234C46.9075 6.9668 46.641 7.11146 46.4341 7.39853L41.724 14.1353L39.7688 7.65789C39.6201 7.19765 39.2945 6.9668 38.7913 6.9668H35.4581C35.3096 6.9668 35.1764 7.03174 35.0587 7.16135C34.9398 7.2907 34.8809 7.42784 34.8809 7.57131C34.8809 7.62938 35.1698 8.47821 35.7473 10.1191C36.3248 11.7604 36.9468 13.5309 37.6135 15.431C38.2801 17.3308 38.6279 18.3389 38.6576 18.4533C36.2283 21.6778 35.0141 23.4052 35.0141 23.6351C35.0141 24.0097 35.2064 24.1966 35.5918 24.1966H39.0134C39.3391 24.1966 39.6055 24.053 39.8132 23.765L51.1444 7.87356C51.2033 7.81641 51.233 7.7161 51.233 7.57131Z" fill="#003087"/> |
|||
<path d="M83.0939 6.97023H79.7166C79.3017 6.97023 79.0505 7.44539 78.9617 8.39545C78.1906 7.24437 76.7844 6.66797 74.74 6.66797C72.607 6.66797 70.7922 7.44539 69.2966 8.99996C67.8004 10.5545 67.0527 12.383 67.0527 14.4843C67.0527 16.1831 67.5636 17.536 68.5855 18.5432C69.6075 19.5515 70.9776 20.0546 72.6963 20.0546C73.5551 20.0546 74.4289 19.8816 75.3176 19.5364C76.2064 19.191 76.902 18.7306 77.4064 18.1546C77.4064 18.1835 77.3763 18.3129 77.3174 18.5431C77.2577 18.7738 77.2285 18.9468 77.2285 19.0613C77.2285 19.5224 77.4205 19.7521 77.8063 19.7521H80.8726C81.4346 19.7521 81.761 19.4791 81.8497 18.9318L83.6717 7.66119C83.7009 7.48842 83.6565 7.33029 83.5383 7.18616C83.4195 7.04242 83.2717 6.97023 83.0939 6.97023ZM77.2952 15.4774C76.5397 16.1971 75.6289 16.5568 74.5624 16.5568C73.703 16.5568 73.0074 16.3268 72.4739 15.8659C71.9405 15.4061 71.6739 14.7727 71.6739 13.9659C71.6739 12.9014 72.0442 12.0011 72.785 11.2671C73.5248 10.533 74.4439 10.166 75.5401 10.166C76.3687 10.166 77.0576 10.4035 77.6062 10.8783C78.1538 11.3535 78.4285 12.0085 78.4285 12.8435C78.4283 13.8797 78.0505 14.758 77.2952 15.4774Z" fill="#009CDE"/> |
|||
<path d="M32.6158 6.97023H29.2386C28.8233 6.97023 28.572 7.44539 28.4831 8.39545C27.6832 7.24437 26.276 6.66797 24.2616 6.66797C22.1286 6.66797 20.3139 7.44539 18.8181 8.99996C17.3219 10.5545 16.5742 12.383 16.5742 14.4843C16.5742 16.1831 17.0853 17.536 18.1074 18.5432C19.1294 19.5515 20.4992 20.0546 22.2176 20.0546C23.0467 20.0546 23.9063 19.8816 24.7948 19.5364C25.6835 19.191 26.3945 18.7306 26.9278 18.1546C26.8089 18.5 26.75 18.8024 26.75 19.0613C26.75 19.5224 26.9424 19.7521 27.3276 19.7521H30.3937C30.9561 19.7521 31.2823 19.4791 31.3713 18.9318L33.1931 7.66119C33.2223 7.48842 33.178 7.33029 33.0598 7.18616C32.9413 7.04242 32.7932 6.97023 32.6158 6.97023ZM26.8168 15.4987C26.0612 16.2049 25.1351 16.5568 24.0396 16.5568C23.18 16.5568 22.4913 16.3268 21.9733 15.8659C21.4546 15.4061 21.1955 14.7727 21.1955 13.9659C21.1955 12.9014 21.5657 12.0011 22.3066 11.2671C23.0467 10.533 23.9651 10.166 25.0616 10.166C25.8906 10.166 26.5794 10.4035 27.128 10.8783C27.6756 11.3535 27.95 12.0085 27.95 12.8435C27.95 13.9087 27.5722 14.7942 26.8168 15.4987Z" fill="#003087"/> |
|||
<path d="M66.4079 1.76469C65.2673 1.00258 63.7789 0.620605 61.9422 0.620605H54.8769C54.2841 0.620605 53.9581 0.894219 53.8993 1.44092L51.011 19.0593C50.981 19.2323 51.0254 19.3906 51.1443 19.5344C51.2619 19.6786 51.4108 19.7503 51.5885 19.7503H55.2321C55.5875 19.7503 55.8244 19.5634 55.9431 19.189L56.7431 14.1802C56.7724 13.95 56.8762 13.7628 57.0541 13.6187C57.2319 13.4748 57.4539 13.3807 57.7208 13.3375C57.9872 13.2948 58.2386 13.2733 58.4762 13.2733C58.7129 13.2733 58.9942 13.2878 59.3202 13.3164C59.646 13.3452 59.854 13.3592 59.9423 13.3592C62.4901 13.3592 64.4894 12.6616 65.9412 11.265C67.3927 9.86898 68.1186 7.93322 68.1186 5.45697C68.1187 3.75866 67.5481 2.52799 66.4079 1.76469ZM61.8533 8.86837C61.2015 9.30011 60.2238 9.51591 58.9206 9.51591L57.4985 9.55907L58.2541 4.93851C58.3128 4.62213 58.5053 4.46375 58.8316 4.46375H59.6312C60.2828 4.46375 60.8012 4.49252 61.1869 4.54994C61.5714 4.60788 61.9422 4.78739 62.2977 5.08964C62.6534 5.3919 62.8309 5.83116 62.8309 6.40676C62.8309 7.61605 62.5047 8.43624 61.8533 8.86837Z" fill="#009CDE"/> |
|||
</svg> |
@ -0,0 +1,3 @@ |
|||
<svg width="60" height="26" viewBox="0 0 60 26" fill="none" xmlns="http://www.w3.org/2000/svg"> |
|||
<path d="M4.26747 10.3539C4.26747 9.7045 4.80031 9.45473 5.68283 9.45473C6.94833 9.45473 8.54685 9.83771 9.81235 10.5204V6.60736C8.43029 6.05787 7.06489 5.8414 5.68283 5.8414C2.30261 5.8414 0.0546875 7.60644 0.0546875 10.5537C0.0546875 15.1495 6.38218 14.4168 6.38218 16.3983C6.38218 17.1643 5.71613 17.4141 4.78366 17.4141C3.4016 17.4141 1.63656 16.8479 0.237852 16.082V20.045C1.78642 20.711 3.35164 20.9941 4.78366 20.9941C8.24713 20.9941 10.6283 19.279 10.6283 16.2984C10.6116 11.3363 4.26747 12.2188 4.26747 10.3539ZM15.5237 2.51114L11.4608 3.37701L11.4442 16.7147C11.4442 19.1791 13.2925 20.9941 15.7569 20.9941C17.1223 20.9941 18.1213 20.7443 18.6708 20.4446V17.0644C18.138 17.2808 15.5071 18.0468 15.5071 15.5824V9.6712H18.6708V6.12447H15.5071L15.5237 2.51114ZM23.8494 7.35667L23.583 6.12447H19.9863V20.6944H24.1491V10.8201C25.1315 9.53799 26.7967 9.77111 27.3129 9.95427V6.12447C26.78 5.92466 24.8318 5.55833 23.8494 7.35667ZM28.3286 6.12447H32.5081V20.6944H28.3286V6.12447ZM28.3286 4.85897L32.5081 3.9598V0.57959L28.3286 1.46211V4.85897ZM41.2001 5.8414C39.5682 5.8414 38.5192 6.60736 37.9364 7.1402L37.7199 6.10782H34.0566V25.5232L38.2195 24.6407L38.2361 19.9284C38.8356 20.3613 39.7181 20.9774 41.1834 20.9774C44.164 20.9774 46.8781 18.5796 46.8781 13.3012C46.8615 8.47231 44.114 5.8414 41.2001 5.8414ZM40.201 17.3141C39.2185 17.3141 38.6358 16.9645 38.2361 16.5315L38.2195 10.3539C38.6524 9.87102 39.2519 9.53799 40.201 9.53799C41.7162 9.53799 42.7653 11.2364 42.7653 13.4177C42.7653 15.649 41.7329 17.3141 40.201 17.3141ZM59.9994 13.4677C59.9994 9.20496 57.9346 5.8414 53.9883 5.8414C50.0252 5.8414 47.6275 9.20496 47.6275 13.4344C47.6275 18.4464 50.4582 20.9774 54.5211 20.9774C56.5026 20.9774 58.0012 20.5278 59.1335 19.8951V16.5648C58.0012 17.131 56.7024 17.4807 55.0539 17.4807C53.4388 17.4807 52.0067 16.9145 51.8236 14.9497H59.9661C59.9661 14.7332 59.9994 13.8673 59.9994 13.4677ZM51.7736 11.8858C51.7736 10.0042 52.9226 9.22161 53.9716 9.22161C54.9873 9.22161 56.0697 10.0042 56.0697 11.8858H51.7736Z" fill="#6772E5"/> |
|||
</svg> |
@ -1,24 +0,0 @@ |
|||
import React from 'react'; |
|||
import { useTranslation } from 'react-i18next'; |
|||
import Layout from '@/components/layouts/admin'; |
|||
|
|||
const Subscription: React.FC = () => { |
|||
const { t } = useTranslation(); |
|||
|
|||
return ( |
|||
<div> |
|||
<h1>{t('subscription.title')}</h1> |
|||
<p>{t('subscription.description')}</p> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default Subscription; |
|||
|
|||
Subscription.Layout = Layout; |
|||
|
|||
// export const getStaticProps = async ({ locale }: any) => ({
|
|||
// props: {
|
|||
// ...(await serverSideTranslations(locale, ['table', 'common', 'form'])),
|
|||
// },
|
|||
// });
|
@ -0,0 +1,69 @@ |
|||
import React from 'react'; |
|||
import { useGetActiveSubscription } from '@/data/subscription'; |
|||
import { FaStar } from 'react-icons/fa'; |
|||
import Image from 'next/image'; |
|||
import background from '../../../public/image/Frame 1000005757.webp'; |
|||
|
|||
const ActiveSubscriptionSection: React.FC = () => { |
|||
const { data: activeData, isLoading: isActiveLoading } = |
|||
useGetActiveSubscription(); |
|||
|
|||
const formatDate = (dateString: string): string => { |
|||
if (!dateString) return ''; |
|||
|
|||
const date = new Date(dateString); |
|||
if (isNaN(date.getTime())) return dateString; // Return original string if invalid
|
|||
|
|||
return date.toLocaleDateString('en-US', { |
|||
year: 'numeric', |
|||
month: 'long', |
|||
day: 'numeric', |
|||
}); |
|||
}; |
|||
|
|||
if (isActiveLoading) { |
|||
return <div>Loading...</div>; |
|||
} |
|||
|
|||
return ( |
|||
<div className="flex gap-7"> |
|||
<div className="bg-white w-1/3 p-8 rounded-xl"> |
|||
<div className="mb-8 border p-2 rounded-lg flex items-center gap-2 max-w-fit"> |
|||
<FaStar |
|||
size={20} |
|||
className={`${ |
|||
activeData?.subscription?.duration === '7D' |
|||
? 'text-gray-200' |
|||
: 'text-[#F0BE18]' |
|||
}`}
|
|||
/> |
|||
<h1>{activeData?.subscription?.title}</h1> |
|||
</div> |
|||
<div> |
|||
<Image src={background} alt="subscription type" /> |
|||
</div> |
|||
</div> |
|||
<div className="bg-white w-4/6 p-8 rounded-xl gap-y-6 gap-x-8"> |
|||
<div className="flex flex-col border w-full h-full rounded-lg justify-between p-4 text-sm font-light"> |
|||
<div className="flex justify-between"> |
|||
<p className="text-gray-500">Start Your Plan</p> |
|||
<p>{formatDate(activeData?.start_date)}</p> |
|||
</div> |
|||
<div className="flex justify-between"> |
|||
<p className="text-gray-500">Expiration Date</p> |
|||
<p>{formatDate(activeData?.end_date)}</p> |
|||
</div> |
|||
<div className="flex justify-between"> |
|||
<p className="text-gray-500">Reserve Subscription</p> |
|||
</div> |
|||
<div className="flex justify-between"> |
|||
<p className="text-gray-500">Remaining Days</p> |
|||
<p>{activeData?.days_left}</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default ActiveSubscriptionSection; |
@ -0,0 +1,93 @@ |
|||
import React from 'react'; |
|||
import { |
|||
useGetActiveSubscription, |
|||
useGetAllSubscriptions, |
|||
useGetSubscriptionsHistory, |
|||
} from '@/data/subscription'; |
|||
|
|||
const HistorySection: React.FC = () => { |
|||
const { data: historyData, isLoading: isHistoryLoading } = |
|||
useGetSubscriptionsHistory(); |
|||
const formatDate = (dateString: string): string => { |
|||
if (!dateString) return ''; |
|||
|
|||
const date = new Date(dateString); |
|||
if (isNaN(date.getTime())) return dateString; // Return original string if invalid
|
|||
|
|||
return date.toLocaleDateString('en-US', { |
|||
year: 'numeric', |
|||
month: 'long', |
|||
day: 'numeric', |
|||
}); |
|||
}; |
|||
|
|||
// Helper function to format price strings
|
|||
const formatPrice = (priceString: string): string => { |
|||
if (!priceString) return ''; |
|||
|
|||
const priceNumber = parseFloat(priceString); |
|||
if (isNaN(priceNumber)) return priceString; // Return original string if invalid
|
|||
|
|||
return priceNumber.toFixed(0); // Removes decimals
|
|||
}; |
|||
if (isHistoryLoading) { |
|||
return <p>Loading...</p>; |
|||
} |
|||
console.log(historyData); |
|||
|
|||
|
|||
return ( |
|||
<div className="flex flex-col bg-white p-10 rounded-xl gap-y-6 gap-x-8"> |
|||
<h1 className="relative mt-1 bg-light text-lg font-semibold text-heading before:absolute before:-top-px before:h-7 before:w-1 before:rounded-tr-md before:rounded-br-md before:bg-accent ltr:before:-left-6 rtl:before:-right-6 md:before:-top-0.5 md:ltr:before:-left-7 md:rtl:before:-right-7 lg:before:h-8"> |
|||
History Plans |
|||
</h1> |
|||
{historyData?.results.length > 0 ? ( |
|||
<div className="overflow-x-auto"> |
|||
<table className="min-w-full divide-y divide-gray-200"> |
|||
<thead> |
|||
<tr className="bg-[rgba(31,41,55,0.03)] rounded-lg"> |
|||
<th className="px-6 py-3 text-left font-semibold text-base tracking-wider"> |
|||
Subscription Type |
|||
</th> |
|||
<th className="px-6 py-3 text-left font-semibold text-base tracking-wider"> |
|||
Costs |
|||
</th> |
|||
<th className="px-6 py-3 text-left font-semibold text-base tracking-wider"> |
|||
Start Date |
|||
</th> |
|||
<th className="px-6 py-3 text-left font-semibold text-base tracking-wider"> |
|||
End Date |
|||
</th> |
|||
<th className="px-6 py-3 text-left font-semibold text-base tracking-wider"> |
|||
Tracking Number |
|||
</th> |
|||
</tr> |
|||
</thead> |
|||
<tbody className="bg-white divide-y divide-gray-200"> |
|||
{historyData?.results.map((subscription) => ( |
|||
<tr key={subscription.id}> |
|||
<td className="px-6 py-4 whitespace-nowrap"> |
|||
{subscription.subscription.title} |
|||
</td> |
|||
<td className="px-6 py-4 whitespace-nowrap"> |
|||
{formatPrice(subscription.subscription.price)}$ |
|||
</td> |
|||
<td className="px-6 py-4 whitespace-nowrap"> |
|||
{formatDate(subscription.start_date)} |
|||
</td> |
|||
<td className="px-6 py-4 whitespace-nowrap"> |
|||
{formatDate(subscription.end_date)} |
|||
</td> |
|||
</tr> |
|||
))} |
|||
</tbody> |
|||
</table> |
|||
</div> |
|||
) : ( |
|||
<p className="text-gray-500">No historical subscriptions found.</p> |
|||
)} |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default HistorySection; |
@ -0,0 +1,32 @@ |
|||
import React from 'react'; |
|||
import Layout from '@/components/layouts/admin'; |
|||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'; |
|||
import { GetStaticProps } from 'next'; |
|||
import ActiveSubscriptionSection from './active-section'; |
|||
import PlansSection from './plans-section'; |
|||
import HistorySection from './history-section'; |
|||
|
|||
export const getStaticProps: GetStaticProps = async ({ locale }) => ({ |
|||
props: { |
|||
...(await serverSideTranslations(locale!, ['common', 'form'])), |
|||
}, |
|||
}); |
|||
|
|||
const Subscription: React.FC = () => { |
|||
return ( |
|||
<div className="flex flex-col gap-10"> |
|||
{/* Active Subscription Section */} |
|||
<ActiveSubscriptionSection /> |
|||
|
|||
{/* Plans Section */} |
|||
<PlansSection /> |
|||
|
|||
{/* History Plans Section */} |
|||
<HistorySection /> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default Subscription; |
|||
|
|||
Subscription.Layout = Layout; |
@ -0,0 +1,69 @@ |
|||
// components/ui/Modal.tsx
|
|||
|
|||
import React, { useEffect } from 'react'; |
|||
import { FaTimes } from 'react-icons/fa'; |
|||
|
|||
interface ModalProps { |
|||
display: boolean; |
|||
setDisplay: (display: boolean) => void; |
|||
children: React.ReactNode; |
|||
} |
|||
|
|||
const Modal: React.FC<ModalProps> = ({ display, setDisplay, children }) => { |
|||
// Close the modal when the Escape key is pressed
|
|||
useEffect(() => { |
|||
const handleEscape = (event: KeyboardEvent) => { |
|||
if (event.key === 'Escape') { |
|||
setDisplay(false); |
|||
} |
|||
}; |
|||
if (display) { |
|||
document.addEventListener('keydown', handleEscape); |
|||
} else { |
|||
document.removeEventListener('keydown', handleEscape); |
|||
} |
|||
return () => { |
|||
document.removeEventListener('keydown', handleEscape); |
|||
}; |
|||
}, [display, setDisplay]); |
|||
|
|||
// Prevent scrolling when modal is open
|
|||
useEffect(() => { |
|||
if (display) { |
|||
document.body.style.overflow = 'hidden'; |
|||
} else { |
|||
document.body.style.overflow = 'auto'; |
|||
} |
|||
return () => { |
|||
document.body.style.overflow = 'auto'; |
|||
}; |
|||
}, [display]); |
|||
|
|||
if (!display) return null; |
|||
|
|||
return ( |
|||
<div |
|||
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50" |
|||
onClick={() => setDisplay(false)} // Close when clicking on the backdrop
|
|||
> |
|||
<div |
|||
className="bg-white rounded-lg shadow-lg w-full max-w-lg p-6 relative" |
|||
onClick={(e) => e.stopPropagation()} // Prevent closing when clicking inside the modal
|
|||
> |
|||
{/* Close Button */} |
|||
<button |
|||
className="absolute top-4 right-4 text-gray-500 hover:text-gray-700" |
|||
onClick={() => setDisplay(false)} |
|||
aria-label="Close Modal" |
|||
> |
|||
<FaTimes size={20} /> |
|||
</button> |
|||
|
|||
{/* Modal Content */} |
|||
<div>{children}</div> |
|||
</div> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default Modal; |
@ -0,0 +1,236 @@ |
|||
import React from 'react'; |
|||
import { useGetAllSubscriptions } from '@/data/subscription'; |
|||
import { RiHeadphoneLine } from 'react-icons/ri'; |
|||
import { FaRegCheckCircle } from 'react-icons/fa'; |
|||
import Button from '@/components/ui/button'; |
|||
import { useState } from 'react'; |
|||
import Modal from '@/components/ui/modal/modal'; |
|||
import { IoMdClose } from 'react-icons/io'; |
|||
import payPal from '../../../public/image/payments/🦆 icon _PayPal_.svg'; |
|||
import Stripe from '../../../public/image/payments/🦆 icon _Stripe_.svg'; |
|||
import Image from 'next/image'; |
|||
const PlansSection: React.FC = () => { |
|||
const [display, setDisplay] = useState(false); |
|||
const [selected, setSelected] = useState({}); |
|||
const { data: allSubscriptions, isLoading: isAllLoading } = |
|||
useGetAllSubscriptions(); |
|||
|
|||
const formatPrice = (priceString: string): string => { |
|||
if (!priceString) return ''; |
|||
|
|||
const priceNumber = parseFloat(priceString); |
|||
if (isNaN(priceNumber)) return priceString; // Return original string if invalid
|
|||
|
|||
return priceNumber.toFixed(0); // Removes decimals
|
|||
}; |
|||
|
|||
const formatTitle = (text: string) => { |
|||
if (!text) { |
|||
return; |
|||
} |
|||
const splitedText = text.split(''); |
|||
const number = splitedText[0]; |
|||
const duration = () => { |
|||
const char = splitedText[1]; |
|||
if (char === 'D') { |
|||
return 'Day'; |
|||
} |
|||
if (char === 'M') { |
|||
return 'Month'; |
|||
} |
|||
if (char === 'Y') { |
|||
return 'Year'; |
|||
} |
|||
return ''; |
|||
}; |
|||
return ( |
|||
<div> |
|||
<span className="text-3xl font-semibold ">{number} </span> |
|||
<span className="text-2xl font-medium text-[#666666] font-sans"> |
|||
{duration()} |
|||
</span> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
if (isAllLoading) { |
|||
return <div>Loading...</div>; |
|||
} |
|||
|
|||
return ( |
|||
<div className="flex flex-col bg-white p-10 rounded-xl gap-y-6 gap-x-8"> |
|||
<div className="flex justify-between"> |
|||
<div> |
|||
<h1 className="text-2xl font-medium mb-4">Plans</h1> |
|||
<p className="text-[#666666] text-lg font-light"> |
|||
Change your current workspace plan |
|||
</p> |
|||
</div> |
|||
<div className="bg-[rgba(31,41,55,0.05)] flex items-center self-end p-2 rounded-lg gap-2"> |
|||
<p>Support</p> |
|||
<RiHeadphoneLine /> |
|||
</div> |
|||
</div> |
|||
<div className="grid grid-cols-4 gap-8"> |
|||
{allSubscriptions?.results.map((item) => ( |
|||
<div className="border p-6 rounded-lg" key={item.id}> |
|||
<div className="border bg-[#F3F4F6] p-2 rounded-xl flex gap-10 items-end mb-6"> |
|||
<p>{formatTitle(item.duration)}</p> |
|||
<p className="text-sm text-[#666666]">User Per Month</p> |
|||
</div> |
|||
<div className="flex justify-between"> |
|||
<div> |
|||
<span className="text-xl font-semibold"> |
|||
{formatPrice(item.final_price)}$ |
|||
</span> |
|||
<span className="text-sm text-[#666666]">User Per Month</span> |
|||
</div> |
|||
{!!formatPrice(item.discount_percentage) && ( |
|||
<div className="bg-green-100 p-1 rounded-md pt-[7px]"> |
|||
<p className="text-[#1DCE1D] text-xs font-semibold"> |
|||
Save {item.discount_percentage}% |
|||
</p> |
|||
</div> |
|||
)} |
|||
</div> |
|||
<Button |
|||
onClick={() => { |
|||
setDisplay(true); |
|||
setSelected(item); |
|||
}} |
|||
className="w-full mt-6" |
|||
> |
|||
Purchase Subscription |
|||
</Button> |
|||
</div> |
|||
))} |
|||
</div> |
|||
<div className=""> |
|||
<h1 className="text-2xl font-medium mb-4"> |
|||
Features of All Subscriptions |
|||
</h1> |
|||
<div className="border p-8 grid grid-cols-2 gap-8 text-[#666666] text-sm rounded-lg"> |
|||
<div className="flex items-start gap-2"> |
|||
<div className="mt-1"> |
|||
<FaRegCheckCircle color="#1DCE1D" size={15} /> |
|||
</div> |
|||
<p> |
|||
Customizable Storefront: Merchants can personalize their |
|||
storefront with custom branding, logos, and banners to create a |
|||
unique shopping experience. |
|||
</p> |
|||
</div> |
|||
<div className="flex items-start gap-2"> |
|||
<div className="mt-1"> |
|||
<FaRegCheckCircle color="#1DCE1D" size={15} /> |
|||
</div> |
|||
<p> |
|||
Advanced Analytics Dashboard: Access detailed insights into sales, |
|||
traffic, and customer behavior to optimize listings and marketing |
|||
strategies. |
|||
</p> |
|||
</div> |
|||
<div className="flex items-start gap-2"> |
|||
<div className="mt-1"> |
|||
<FaRegCheckCircle color="#1DCE1D" size={15} /> |
|||
</div> |
|||
<p> |
|||
Bulk Listing Management: Easily upload and manage multiple |
|||
gemstone listings at once with bulk editing features. |
|||
</p> |
|||
</div> |
|||
<div className="flex items-start gap-2"> |
|||
<div className="mt-1"> |
|||
<FaRegCheckCircle color="#1DCE1D" size={15} /> |
|||
</div> |
|||
<p> |
|||
Real-Time Inventory Tracking: Merchants can track stock levels in |
|||
real-time, ensuring they never over-sell or run out of inventory. |
|||
</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<Modal |
|||
open={!!Object.keys(selected).length} |
|||
onClose={() => { |
|||
setSelected({}); |
|||
}} |
|||
> |
|||
<div className="bg-white py-10 px-12 w-[609px] rounded-2xl "> |
|||
<div className="flex items-center justify-between"> |
|||
<h1 className="text-xl font-semibold mb-2"> |
|||
One-year subscription |
|||
</h1> |
|||
<button onClick={() => setSelected({})}> |
|||
<IoMdClose className="text-gray-500" size={25} /> |
|||
</button> |
|||
</div> |
|||
<p className="text-sm text-[#666666] mb-8"> |
|||
Customers like the functionality and ease of setup of the product. |
|||
They mention it works great, is easy to mount, and hook up through |
|||
your home internet. |
|||
</p> |
|||
<div className="border w-80 p-6 rounded-lg mb-8"> |
|||
<div className="border bg-[#F3F4F6] p-2 rounded-xl flex gap-10 items-end mb-6"> |
|||
<p>{formatTitle(selected.duration)}</p> |
|||
<p className="text-sm text-[#666666]">User Per Month</p> |
|||
</div> |
|||
<div className="flex justify-between"> |
|||
<div> |
|||
<span className="text-xl font-semibold"> |
|||
{formatPrice(selected.final_price)}$ |
|||
</span> |
|||
<span className="text-sm text-[#666666]">User Per Month</span> |
|||
</div> |
|||
{!!formatPrice(selected.discount_percentage) && ( |
|||
<div className="bg-green-100 p-1 rounded-md pt-[7px]"> |
|||
<p className="text-[#1DCE1D] text-xs font-semibold"> |
|||
Save {selected.discount_percentage}% |
|||
</p> |
|||
</div> |
|||
)} |
|||
</div> |
|||
</div> |
|||
<div className="border p-8 rounded-lg"> |
|||
<h1 className="text-lg font-semibold mb-6"> |
|||
Features Subscription |
|||
</h1> |
|||
<div className="text-[#666666] text-sm"> |
|||
<div className="flex items-center gap-2 mb-4"> |
|||
<div> |
|||
<FaRegCheckCircle color="#1DCE1D" size={15} /> |
|||
</div> |
|||
<p>Joining Live Streams</p> |
|||
</div>{' '} |
|||
<div className="flex items-center gap-2 mb-4"> |
|||
<div> |
|||
<FaRegCheckCircle color="#1DCE1D" size={15} /> |
|||
</div> |
|||
<p>Joining Live Streams</p> |
|||
</div>{' '} |
|||
<div className="flex items-center gap-2"> |
|||
<div> |
|||
<FaRegCheckCircle color="#1DCE1D" size={15} /> |
|||
</div> |
|||
<p>Creating a Custom Profile</p> |
|||
</div> |
|||
</div> |
|||
</div> |
|||
<div className='flex gap-6 my-8'> |
|||
<button className="border py-6 px-16 w-full rounded-lg"> |
|||
<Image src={payPal} alt="payPal" /> |
|||
</button> |
|||
<button className="border py-6 px-16 w-full rounded-lg"> |
|||
<Image src={Stripe} alt="Stripe" /> |
|||
</button> |
|||
</div> |
|||
<Button className='w-full'> |
|||
Purchase Subscription |
|||
</Button> |
|||
</div> |
|||
</Modal> |
|||
</div> |
|||
); |
|||
}; |
|||
|
|||
export default PlansSection; |
Write
Preview
Loading…
Cancel
Save
Reference in new issue