hey-pharmacist-ecommerce 1.1.13 → 1.1.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +2 -4
- package/dist/index.d.ts +2 -4
- package/dist/index.js +1039 -857
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1039 -856
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/components/AccountAddressesTab.tsx +209 -0
- package/src/components/AccountOrdersTab.tsx +151 -0
- package/src/components/AccountOverviewTab.tsx +209 -0
- package/src/components/AccountPaymentTab.tsx +116 -0
- package/src/components/AccountSavedItemsTab.tsx +76 -0
- package/src/components/AccountSettingsTab.tsx +116 -0
- package/src/components/AddressFormModal.tsx +23 -10
- package/src/components/CartItem.tsx +60 -56
- package/src/components/Header.tsx +69 -16
- package/src/components/Notification.tsx +148 -0
- package/src/components/ProductCard.tsx +215 -178
- package/src/components/QuickViewModal.tsx +314 -0
- package/src/components/TabNavigation.tsx +48 -0
- package/src/components/ui/Button.tsx +1 -1
- package/src/components/ui/ConfirmModal.tsx +84 -0
- package/src/hooks/usePaymentMethods.ts +58 -0
- package/src/index.ts +0 -1
- package/src/providers/CartProvider.tsx +22 -6
- package/src/providers/EcommerceProvider.tsx +8 -7
- package/src/providers/FavoritesProvider.tsx +10 -3
- package/src/providers/NotificationProvider.tsx +79 -0
- package/src/providers/WishlistProvider.tsx +34 -9
- package/src/screens/AddressesScreen.tsx +72 -61
- package/src/screens/CartScreen.tsx +48 -32
- package/src/screens/ChangePasswordScreen.tsx +155 -0
- package/src/screens/CheckoutScreen.tsx +162 -125
- package/src/screens/EditProfileScreen.tsx +165 -0
- package/src/screens/LoginScreen.tsx +59 -72
- package/src/screens/NewAddressScreen.tsx +16 -10
- package/src/screens/ProductDetailScreen.tsx +334 -234
- package/src/screens/ProfileScreen.tsx +190 -200
- package/src/screens/RegisterScreen.tsx +51 -70
- package/src/screens/SearchResultsScreen.tsx +2 -1
- package/src/screens/ShopScreen.tsx +260 -384
- package/src/screens/WishlistScreen.tsx +226 -224
- package/src/styles/globals.css +9 -0
- package/src/screens/CategoriesScreen.tsx +0 -122
- package/src/screens/HomeScreen.tsx +0 -211
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
|
|
3
|
-
import
|
|
3
|
+
import {
|
|
4
4
|
useCallback,
|
|
5
5
|
useEffect,
|
|
6
6
|
useMemo,
|
|
@@ -31,7 +31,6 @@ import {
|
|
|
31
31
|
Tag,
|
|
32
32
|
DollarSign,
|
|
33
33
|
} from 'lucide-react';
|
|
34
|
-
import Image from 'next/image';
|
|
35
34
|
import { useRouter } from 'next/navigation';
|
|
36
35
|
import { useBasePath } from '@/providers/BasePathProvider';
|
|
37
36
|
import { ProductCard } from '@/components/ProductCard';
|
|
@@ -42,6 +41,8 @@ import { Input } from '@/components/ui/Input';
|
|
|
42
41
|
import { useProducts, useCategories } from '@/hooks/useProducts';
|
|
43
42
|
import { ProductFilters } from '@/lib/types';
|
|
44
43
|
import { formatPrice } from '@/lib/utils/format';
|
|
44
|
+
import Image from 'next/image';
|
|
45
|
+
import React from 'react';
|
|
45
46
|
|
|
46
47
|
type SortOption = 'featured' | 'price-low-high' | 'price-high-low' | 'newest';
|
|
47
48
|
type ViewMode = 'grid' | 'list';
|
|
@@ -655,268 +656,200 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
|
|
|
655
656
|
const renderFiltersPanel = () => (
|
|
656
657
|
<>
|
|
657
658
|
<div className="space-y-6">
|
|
658
|
-
{/* Filters Title */}
|
|
659
|
-
<h3 className="text-lg font-bold text-slate-900">Filters</h3>
|
|
660
|
-
|
|
661
|
-
{/* Search Products */}
|
|
662
|
-
<div className="space-y-2">
|
|
663
|
-
<label className="text-sm font-medium text-slate-600">Search Products</label>
|
|
664
|
-
<div className="relative">
|
|
665
|
-
<Search className="absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-slate-400" />
|
|
666
|
-
<input
|
|
667
|
-
type="text"
|
|
668
|
-
placeholder="Search..."
|
|
669
|
-
value={searchQuery}
|
|
670
|
-
onChange={handleInputChange}
|
|
671
|
-
className="w-full rounded-lg border border-slate-200 bg-white pl-10 pr-4 py-2 text-sm text-slate-900 placeholder-slate-400 focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20"
|
|
672
|
-
/>
|
|
673
|
-
</div>
|
|
674
|
-
</div>
|
|
675
659
|
|
|
676
|
-
{
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
660
|
+
<div className={`lg:w-72 ${showFilters ? 'block rounded-full' : 'hidden lg:block'}`}>
|
|
661
|
+
<div className="bg-white rounded-[24px] p-6 border-2 border-gray-100 sticky top-24">
|
|
662
|
+
<h3 className="font-['Poppins',sans-serif] font-semibold text-secondary">
|
|
663
|
+
Filters
|
|
664
|
+
</h3>
|
|
665
|
+
{/* Search */}
|
|
666
|
+
<div className="mb-6">
|
|
667
|
+
<label className="font-['Poppins',sans-serif] text-[12px] text-muted mb-2 block font-medium">
|
|
668
|
+
Search Products
|
|
669
|
+
</label>
|
|
670
|
+
<div className="relative">
|
|
671
|
+
<Search className="absolute left-3 top-1/2 -translate-y-1/2 size-4 text-muted" />
|
|
672
|
+
<input
|
|
673
|
+
type="text"
|
|
674
|
+
placeholder="Search..."
|
|
675
|
+
value={searchQuery}
|
|
676
|
+
onChange={handleInputChange}
|
|
677
|
+
className="w-full pl-10 pr-4 py-2.5 rounded-xl border-2 border-gray-200 focus:border-primary focus:outline-none font-['Poppins',sans-serif] text-[13px] text-secondary"
|
|
678
|
+
/>
|
|
679
|
+
</div>
|
|
680
|
+
</div>
|
|
681
|
+
|
|
682
|
+
{/* Category Filter */}
|
|
683
|
+
<div className="mb-6">
|
|
694
684
|
<button
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
className={`flex w-full items-center gap-3 rounded-lg px-3 py-2.5 text-sm font-medium transition ${
|
|
698
|
-
!categoryFilter
|
|
699
|
-
? 'bg-primary-600 text-white'
|
|
700
|
-
: 'bg-white text-slate-700 border border-slate-200 hover:border-primary-300'
|
|
701
|
-
}`}
|
|
685
|
+
onClick={() => toggleFilterSection('category')}
|
|
686
|
+
className="w-full flex items-center justify-between mb-3"
|
|
702
687
|
>
|
|
703
|
-
<
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
688
|
+
<label className="font-['Poppins',sans-serif] text-[12px] text-muted font-medium cursor-pointer">
|
|
689
|
+
Category
|
|
690
|
+
</label>
|
|
691
|
+
{expandedFilterSections.category ? (
|
|
692
|
+
<ChevronDown className={`size-4 text-muted transition-transform ${expandedFilterSections.category ? 'rotate-180' : ''}`} />
|
|
693
|
+
) : (
|
|
694
|
+
<ChevronDown className={`size-4 text-muted transition-transform ${expandedFilterSections.category ? 'rotate-180' : ''}`} />
|
|
695
|
+
)}
|
|
711
696
|
</button>
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
<button
|
|
738
|
-
type="button"
|
|
739
|
-
onClick={(e) => {
|
|
740
|
-
e.stopPropagation();
|
|
741
|
-
toggleCategoryExpand(category.id ?? '');
|
|
742
|
-
}}
|
|
743
|
-
className="p-1"
|
|
744
|
-
>
|
|
745
|
-
<ChevronDown className={`h-4 w-4 transition-transform ${isExpanded ? 'rotate-180' : ''} ${isCategoryActive ? 'text-white' : 'text-slate-400'}`} />
|
|
746
|
-
</button>
|
|
747
|
-
)}
|
|
748
|
-
</button>
|
|
749
|
-
{isExpanded && Array.isArray(category.categorySubCategories) && category.categorySubCategories.length > 0 && (
|
|
750
|
-
<div className="ml-9 space-y-1">
|
|
751
|
-
{category.categorySubCategories.map((sub) => {
|
|
752
|
-
const isSubActive = subCategoryFilter === sub.id;
|
|
753
|
-
return (
|
|
754
|
-
<button
|
|
755
|
-
key={sub.id}
|
|
756
|
-
type="button"
|
|
757
|
-
onClick={() => handleSubCategoryChange(category.id ?? '', sub.id)}
|
|
758
|
-
className={`block w-full rounded-lg px-3 py-2 text-sm text-left transition ${
|
|
759
|
-
isSubActive
|
|
760
|
-
? 'bg-primary-50 text-primary-700 font-medium'
|
|
761
|
-
: 'text-slate-600 hover:bg-slate-50'
|
|
762
|
-
}`}
|
|
763
|
-
>
|
|
764
|
-
{sub.name}
|
|
765
|
-
</button>
|
|
766
|
-
);
|
|
767
|
-
})}
|
|
768
|
-
</div>
|
|
769
|
-
)}
|
|
770
|
-
</div>
|
|
771
|
-
);
|
|
772
|
-
})}
|
|
697
|
+
{expandedFilterSections.category && (
|
|
698
|
+
<div className="space-y-2">
|
|
699
|
+
{sortedCategories.map((category) => {
|
|
700
|
+
const isCategoryActive = categoryFilter === category.id;
|
|
701
|
+
const isExpanded = !!expandedCategories[category.id as string];
|
|
702
|
+
const Icon = getCategoryIconForFilter(category.name ?? '');
|
|
703
|
+
return (
|
|
704
|
+
<button
|
|
705
|
+
key={category.id}
|
|
706
|
+
onClick={() => {
|
|
707
|
+
if (!isExpanded) toggleCategoryExpand(category.id ?? '');
|
|
708
|
+
handleCategoryChange(category.id ?? '');
|
|
709
|
+
}}
|
|
710
|
+
className={`w-full text-left px-4 py-3 rounded-xl font-['Poppins',sans-serif] text-[13px] transition-all flex items-center gap-3 ${isCategoryActive
|
|
711
|
+
? 'bg-primary text-white shadow-lg'
|
|
712
|
+
: 'text-secondary hover:bg-gray-50 border-2 border-gray-100'
|
|
713
|
+
}`}
|
|
714
|
+
>
|
|
715
|
+
<Icon className="size-4" />
|
|
716
|
+
{category.name}
|
|
717
|
+
</button>
|
|
718
|
+
);
|
|
719
|
+
})}
|
|
720
|
+
</div>
|
|
721
|
+
)}
|
|
773
722
|
</div>
|
|
774
|
-
)}
|
|
775
|
-
</div>
|
|
776
723
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
<span className="truncate">{brand}</span>
|
|
810
|
-
{isSelected && <Check className="h-3 w-3 text-primary-600 flex-shrink-0 ml-2" />}
|
|
811
|
-
</button>
|
|
812
|
-
);
|
|
813
|
-
})
|
|
724
|
+
{/* Brand Filter */}
|
|
725
|
+
<div className="mb-6">
|
|
726
|
+
<button
|
|
727
|
+
onClick={() => toggleFilterSection('brand')}
|
|
728
|
+
className="w-full flex items-center justify-between mb-3"
|
|
729
|
+
>
|
|
730
|
+
<label className="font-['Poppins',sans-serif] text-[12px] text-muted font-medium cursor-pointer">
|
|
731
|
+
Brand
|
|
732
|
+
</label>
|
|
733
|
+
<ChevronDown className={`size-4 text-muted transition-transform ${expandedFilterSections.brand ? 'rotate-180' : ''}`} />
|
|
734
|
+
</button>
|
|
735
|
+
{expandedFilterSections.brand && (
|
|
736
|
+
<div className="space-y-2">
|
|
737
|
+
{availableBrands.length === 0 ? (
|
|
738
|
+
<div className="text-xs text-slate-500">No brands available</div>
|
|
739
|
+
) : (availableBrands.map((brand) => {
|
|
740
|
+
const isSelected = brandFilter === brand;
|
|
741
|
+
return (
|
|
742
|
+
<button
|
|
743
|
+
key={brand}
|
|
744
|
+
onClick={() => handleBrandChange(brand)}
|
|
745
|
+
className={`w-full text-left px-4 py-3 rounded-xl font-['Poppins',sans-serif] text-[13px] transition-all ${isSelected
|
|
746
|
+
? 'bg-primary text-white shadow-lg'
|
|
747
|
+
: 'text-secondary hover:bg-gray-50 border-2 border-gray-100'
|
|
748
|
+
}`}
|
|
749
|
+
>
|
|
750
|
+
{brand}
|
|
751
|
+
</button>
|
|
752
|
+
)
|
|
753
|
+
})
|
|
754
|
+
)}
|
|
755
|
+
</div>
|
|
814
756
|
)}
|
|
757
|
+
|
|
815
758
|
</div>
|
|
816
|
-
)}
|
|
817
|
-
</div>
|
|
818
759
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
<button
|
|
822
|
-
type="button"
|
|
823
|
-
onClick={() => toggleFilterSection('availability')}
|
|
824
|
-
className="flex w-full items-center justify-between text-sm font-medium text-slate-600"
|
|
825
|
-
>
|
|
826
|
-
<span>Availability</span>
|
|
827
|
-
{expandedFilterSections.availability ? (
|
|
828
|
-
<ChevronUp className="h-4 w-4" />
|
|
829
|
-
) : (
|
|
830
|
-
<ChevronDown className="h-4 w-4" />
|
|
831
|
-
)}
|
|
832
|
-
</button>
|
|
833
|
-
{expandedFilterSections.availability && (
|
|
834
|
-
<div className="space-y-2">
|
|
760
|
+
{/* In Stock Filter */}
|
|
761
|
+
<div className="mb-6">
|
|
835
762
|
<button
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
className={`flex w-full items-center justify-between rounded-lg border px-3 py-2.5 text-sm font-medium transition ${
|
|
839
|
-
inStockOnly
|
|
840
|
-
? 'border-primary-500 bg-primary-50 text-primary-700'
|
|
841
|
-
: 'border-slate-200 bg-white text-slate-600 hover:border-primary-300'
|
|
842
|
-
}`}
|
|
763
|
+
onClick={() => toggleFilterSection('availability')}
|
|
764
|
+
className="w-full flex items-center justify-between mb-3"
|
|
843
765
|
>
|
|
844
|
-
<
|
|
845
|
-
|
|
766
|
+
<label className="font-['Poppins',sans-serif] text-[12px] text-muted font-medium cursor-pointer">
|
|
767
|
+
Availability
|
|
768
|
+
</label>
|
|
769
|
+
<ChevronDown className={`size-4 text-muted transition-transform ${expandedFilterSections.availability ? 'rotate-180' : ''}`} />
|
|
846
770
|
</button>
|
|
771
|
+
{expandedFilterSections.availability && (
|
|
772
|
+
<div className="space-y-2">
|
|
773
|
+
<button
|
|
774
|
+
onClick={handleToggleStock}
|
|
775
|
+
className={`w-full flex items-center justify-between px-4 py-3 rounded-xl font-['Poppins',sans-serif] text-[13px] transition-all border-2 ${inStockOnly
|
|
776
|
+
? 'bg-primary text-white shadow-lg'
|
|
777
|
+
: 'text-secondary hover:bg-gray-50 border-2 border-gray-100'
|
|
778
|
+
}`}
|
|
779
|
+
>
|
|
780
|
+
In Stock Only
|
|
781
|
+
</button>
|
|
782
|
+
</div>
|
|
783
|
+
)}
|
|
847
784
|
</div>
|
|
848
|
-
)}
|
|
849
|
-
</div>
|
|
850
785
|
|
|
851
|
-
|
|
852
|
-
<div className="space-y-3">
|
|
853
|
-
<button
|
|
854
|
-
type="button"
|
|
855
|
-
onClick={() => toggleFilterSection('price')}
|
|
856
|
-
className="flex w-full items-center justify-between text-sm font-medium text-slate-600"
|
|
857
|
-
>
|
|
858
|
-
<span>Price Range</span>
|
|
859
|
-
{expandedFilterSections.price ? (
|
|
860
|
-
<ChevronUp className="h-4 w-4" />
|
|
861
|
-
) : (
|
|
862
|
-
<ChevronDown className="h-4 w-4" />
|
|
863
|
-
)}
|
|
864
|
-
</button>
|
|
865
|
-
{expandedFilterSections.price && (
|
|
866
|
-
<div className="space-y-3">
|
|
867
|
-
<div className="flex flex-wrap gap-2">
|
|
868
|
-
{priceRanges.map((range) => {
|
|
869
|
-
const isActive = selectedPriceRange === range.value;
|
|
870
|
-
return (
|
|
871
|
-
<button
|
|
872
|
-
type="button"
|
|
873
|
-
key={range.value}
|
|
874
|
-
onClick={() => handlePriceRangeSelect(range.value)}
|
|
875
|
-
className={`rounded-lg border px-3 py-1.5 text-sm font-medium transition ${
|
|
876
|
-
isActive
|
|
877
|
-
? 'border-primary-600 bg-primary-600 text-white'
|
|
878
|
-
: 'border-slate-200 bg-white text-slate-600 hover:border-primary-300'
|
|
879
|
-
}`}
|
|
880
|
-
>
|
|
881
|
-
{range.label}
|
|
882
|
-
</button>
|
|
883
|
-
);
|
|
884
|
-
})}
|
|
885
|
-
</div>
|
|
886
|
-
<div className="grid grid-cols-2 gap-2">
|
|
887
|
-
<Input
|
|
888
|
-
type="number"
|
|
889
|
-
min="0"
|
|
890
|
-
placeholder="Min"
|
|
891
|
-
value={customPrice.min}
|
|
892
|
-
onChange={(event) =>
|
|
893
|
-
setCustomPrice((current) => ({ ...current, min: event.target.value }))
|
|
894
|
-
}
|
|
895
|
-
className="text-sm"
|
|
896
|
-
/>
|
|
897
|
-
<Input
|
|
898
|
-
type="number"
|
|
899
|
-
min="0"
|
|
900
|
-
placeholder="Max"
|
|
901
|
-
value={customPrice.max}
|
|
902
|
-
onChange={(event) =>
|
|
903
|
-
setCustomPrice((current) => ({ ...current, max: event.target.value }))
|
|
904
|
-
}
|
|
905
|
-
className="text-sm"
|
|
906
|
-
/>
|
|
907
|
-
</div>
|
|
786
|
+
<div className="mb-6">
|
|
908
787
|
<button
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
disabled={!isCustomPriceDirty}
|
|
912
|
-
className="w-full rounded-lg border border-primary-500 bg-primary-500/10 px-4 py-2 text-sm font-medium text-primary-700 transition hover:bg-primary-500/20 disabled:cursor-not-allowed disabled:border-slate-200 disabled:text-slate-400"
|
|
788
|
+
onClick={() => toggleFilterSection('price')}
|
|
789
|
+
className="w-full flex items-center justify-between mb-3"
|
|
913
790
|
>
|
|
914
|
-
|
|
791
|
+
<label className="font-['Poppins',sans-serif] text-[12px] text-muted font-medium cursor-pointer">
|
|
792
|
+
Price Range
|
|
793
|
+
</label>
|
|
794
|
+
<ChevronDown className={`size-4 text-muted transition-transform ${expandedFilterSections.price ? 'rotate-180' : ''}`} />
|
|
915
795
|
</button>
|
|
796
|
+
{expandedFilterSections.price && (
|
|
797
|
+
<div className="space-y-2">
|
|
798
|
+
<div className="flex flex-wrap gap-2">
|
|
799
|
+
{priceRanges.map((range) => {
|
|
800
|
+
const isActive = selectedPriceRange === range.value;
|
|
801
|
+
return (
|
|
802
|
+
<button
|
|
803
|
+
type="button"
|
|
804
|
+
key={range.value}
|
|
805
|
+
onClick={() => handlePriceRangeSelect(range.value)}
|
|
806
|
+
className={`w-full flex items-center justify-between px-4 py-3 rounded-xl font-['Poppins',sans-serif] text-[13px] transition-all border-2 ${isActive
|
|
807
|
+
? 'bg-primary text-white shadow-lg'
|
|
808
|
+
: 'text-secondary hover:bg-gray-50 border-2 border-gray-100'
|
|
809
|
+
}`}
|
|
810
|
+
>
|
|
811
|
+
{range.label}
|
|
812
|
+
</button>
|
|
813
|
+
);
|
|
814
|
+
})}
|
|
815
|
+
</div>
|
|
816
|
+
<div className="grid grid-cols-2 gap-2">
|
|
817
|
+
<Input
|
|
818
|
+
type="number"
|
|
819
|
+
min="0"
|
|
820
|
+
placeholder="Min"
|
|
821
|
+
value={customPrice.min}
|
|
822
|
+
onChange={(event) =>
|
|
823
|
+
setCustomPrice((current) => ({ ...current, min: event.target.value }))
|
|
824
|
+
}
|
|
825
|
+
className="w-1/2 px-4 py-2.5 rounded-xl border-2 border-gray-200 focus:border-primary focus:outline-none font-['Poppins',sans-serif] text-[13px] text-secondary"
|
|
826
|
+
/>
|
|
827
|
+
<Input
|
|
828
|
+
type="number"
|
|
829
|
+
min="0"
|
|
830
|
+
placeholder="Max"
|
|
831
|
+
value={customPrice.max}
|
|
832
|
+
onChange={(event) =>
|
|
833
|
+
setCustomPrice((current) => ({ ...current, max: event.target.value }))
|
|
834
|
+
}
|
|
835
|
+
className="w-1/2 px-4 py-2.5 rounded-xl border-2 border-gray-200 focus:border-primary focus:outline-none font-['Poppins',sans-serif] text-[13px] text-secondary"
|
|
836
|
+
/>
|
|
837
|
+
</div>
|
|
838
|
+
<button
|
|
839
|
+
type="button"
|
|
840
|
+
onClick={applyCustomPrice}
|
|
841
|
+
disabled={!isCustomPriceDirty}
|
|
842
|
+
className="w-full rounded-lg border border-primary bg-primary/10 px-4 py-2 text-sm font-medium text-primary transition hover:bg-primary/20 disabled:cursor-not-allowed disabled:border-slate-200 disabled:text-slate-400"
|
|
843
|
+
>
|
|
844
|
+
Apply
|
|
845
|
+
</button>
|
|
846
|
+
</div>
|
|
847
|
+
)}
|
|
916
848
|
</div>
|
|
917
|
-
|
|
849
|
+
</div>
|
|
918
850
|
</div>
|
|
919
851
|
|
|
852
|
+
|
|
920
853
|
</div>
|
|
921
854
|
</>
|
|
922
855
|
);
|
|
@@ -939,7 +872,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
|
|
|
939
872
|
return (
|
|
940
873
|
<div className="min-h-screen bg-[#F9FAFB]">
|
|
941
874
|
{/* Header Section */}
|
|
942
|
-
<section className="relative overflow-hidden bg-
|
|
875
|
+
<section className="relative overflow-hidden bg-primary-bg py-16 md:py-24">
|
|
943
876
|
<div className="container mx-auto px-4">
|
|
944
877
|
<motion.div
|
|
945
878
|
initial={{ opacity: 0, y: 24 }}
|
|
@@ -987,92 +920,88 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
|
|
|
987
920
|
</section>
|
|
988
921
|
|
|
989
922
|
{/* Shop by Category Section */}
|
|
990
|
-
<section className="
|
|
991
|
-
<div className="
|
|
992
|
-
<h2 className="text-2xl md:text-3xl font-
|
|
923
|
+
<section className="py-8 bg-white">
|
|
924
|
+
<div className="max-w-[1400px] mx-auto px-8 md:px-12">
|
|
925
|
+
<h2 className="text-2xl md:text-3xl font-['Poppins',sans-serif] font-semibold text-secondary mb-6">
|
|
993
926
|
Shop by Category
|
|
994
927
|
</h2>
|
|
995
|
-
<div className="grid grid-cols-
|
|
996
|
-
{
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
928
|
+
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4">
|
|
929
|
+
{displayCategories.map((category, index) => {
|
|
930
|
+
const Icon = getCategoryIcon(category.name ?? '');
|
|
931
|
+
const isSelected = categoryFilter === category.id;
|
|
932
|
+
|
|
933
|
+
return (
|
|
934
|
+
<>
|
|
935
|
+
|
|
936
|
+
<motion.button
|
|
937
|
+
onClick={handleClearCategory}
|
|
938
|
+
initial={{ opacity: 0, y: 20 }}
|
|
939
|
+
animate={{ opacity: 1, y: 0 }}
|
|
940
|
+
transition={{ delay: index * 0.1 }}
|
|
941
|
+
className={`group relative overflow-hidden rounded-[24px] p-6 transition-all duration-300 ${!categoryFilter ? 'bg-gradient-to-br from-primary to-secondary text-white shadow-xl scale-105'
|
|
942
|
+
: 'bg-gradient-to-br from-gray-50 to-white hover:shadow-lg border-2 border-gray-100 hover:border-primary'
|
|
943
|
+
}`}
|
|
1003
944
|
>
|
|
1004
|
-
<
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
<
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
{/* Category Cards */}
|
|
1033
|
-
{displayCategories.map((category, index) => {
|
|
1034
|
-
const Icon = getCategoryIcon(category.name ?? '');
|
|
1035
|
-
const isSelected = categoryFilter === category.id;
|
|
1036
|
-
return (
|
|
1037
|
-
<motion.button
|
|
1038
|
-
key={category.id}
|
|
1039
|
-
initial={{ opacity: 0, y: 20 }}
|
|
1040
|
-
animate={{ opacity: 1, y: 0 }}
|
|
1041
|
-
transition={{ delay: index * 0.1 }}
|
|
1042
|
-
onClick={() => handleCategoryChange(category.id ?? '')}
|
|
1043
|
-
className={`group rounded-xl p-8 text-left transition ${
|
|
1044
|
-
isSelected
|
|
1045
|
-
? 'bg-gradient-to-b from-primary-500 to-primary-300 text-white shadow-lg'
|
|
1046
|
-
: 'border border-slate-200 bg-white hover:border-primary-300 hover:shadow-md'
|
|
945
|
+
<div className="relative">
|
|
946
|
+
<div className={`size-12 rounded-full mb-3 mx-auto flex items-center justify-center transition-all ${!categoryFilter
|
|
947
|
+
? 'bg-white/20'
|
|
948
|
+
: 'bg-gradient-to-br from-primary/10 to-secondary/10 group-hover:scale-110'
|
|
949
|
+
}`}>
|
|
950
|
+
<Icon className={`size-6 ${!categoryFilter ? 'text-white' : 'text-primary'
|
|
951
|
+
}`} />
|
|
952
|
+
</div>
|
|
953
|
+
<h3 className={`font-['Poppins',sans-serif] font-semibold text-[14px] mb-1.5 ${!categoryFilter ? 'text-white' : 'text-secondary'
|
|
954
|
+
}`}>
|
|
955
|
+
All Products
|
|
956
|
+
</h3>
|
|
957
|
+
<p className={`font-['Poppins',sans-serif] text-[11px] ${!categoryFilter ? 'text-white/80' : 'text-muted'
|
|
958
|
+
}`}>
|
|
959
|
+
Browse Everything
|
|
960
|
+
</p>
|
|
961
|
+
</div>
|
|
962
|
+
</motion.button>
|
|
963
|
+
|
|
964
|
+
|
|
965
|
+
<motion.button
|
|
966
|
+
key={category.id}
|
|
967
|
+
initial={{ opacity: 0, y: 20 }}
|
|
968
|
+
animate={{ opacity: 1, y: 0 }}
|
|
969
|
+
transition={{ delay: index * 0.1 }}
|
|
970
|
+
onClick={() => handleCategoryChange(category.id ?? '')}
|
|
971
|
+
className={`group relative overflow-hidden rounded-[24px] p-6 transition-all duration-300 ${isSelected ? 'bg-gradient-to-br from-primary to-secondary text-white shadow-xl scale-105'
|
|
972
|
+
: 'bg-gradient-to-br from-gray-50 to-white hover:shadow-lg border-2 border-gray-100 hover:border-primary'
|
|
1047
973
|
}`}
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
<
|
|
1051
|
-
|
|
1052
|
-
|
|
974
|
+
>
|
|
975
|
+
<div className="relative">
|
|
976
|
+
<div className={`size-12 rounded-full mb-3 mx-auto flex items-center justify-center transition-all ${isSelected
|
|
977
|
+
? 'bg-white/20'
|
|
978
|
+
: 'bg-gradient-to-br from-primary/10 to-secondary/10 group-hover:scale-110'
|
|
979
|
+
}`}>
|
|
980
|
+
<Icon className={`size-6 ${isSelected ? 'text-white' : 'text-primary'
|
|
981
|
+
}`} />
|
|
982
|
+
</div>
|
|
983
|
+
<h3 className={`font-['Poppins',sans-serif] font-semibold text-[14px] mb-1.5 ${isSelected ? 'text-white' : 'text-secondary'
|
|
984
|
+
}`}>
|
|
1053
985
|
{category.name}
|
|
1054
986
|
</h3>
|
|
1055
|
-
<p className={`text-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
category.name?.toLowerCase().includes('medicine') ? 'OTC medications' :
|
|
1059
|
-
category.name?.toLowerCase().includes('care') ? 'Daily essentials' :
|
|
1060
|
-
'Shop now'}
|
|
987
|
+
<p className={`font-['Poppins',sans-serif] text-[11px] ${isSelected ? 'text-white/80' : 'text-muted'
|
|
988
|
+
}`}>
|
|
989
|
+
{category.description}
|
|
1061
990
|
</p>
|
|
1062
|
-
</
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
)}
|
|
991
|
+
</div>
|
|
992
|
+
</motion.button>
|
|
993
|
+
</>
|
|
994
|
+
);
|
|
995
|
+
})}
|
|
1067
996
|
</div>
|
|
1068
997
|
</div>
|
|
1069
998
|
</section>
|
|
1070
999
|
|
|
1071
|
-
<div className="relative
|
|
1000
|
+
<div className="relative pb-16 mt-8">
|
|
1072
1001
|
<div className="container mx-auto px-4">
|
|
1073
1002
|
<div className="flex flex-col gap-8 lg:flex-row">
|
|
1074
1003
|
<aside className="hidden w-72 flex-shrink-0 lg:block">
|
|
1075
|
-
<div className="sticky top-24 rounded-lg bg-white
|
|
1004
|
+
<div className="sticky top-24 rounded-lg bg-white">
|
|
1076
1005
|
{renderFiltersPanel()}
|
|
1077
1006
|
</div>
|
|
1078
1007
|
</aside>
|
|
@@ -1084,7 +1013,8 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
|
|
|
1084
1013
|
<h2 className="text-base font-medium text-gray-700">
|
|
1085
1014
|
{isLoading
|
|
1086
1015
|
? 'Loading products...'
|
|
1087
|
-
:
|
|
1016
|
+
: `${displayedProducts.length} products found`}
|
|
1017
|
+
|
|
1088
1018
|
</h2>
|
|
1089
1019
|
</div>
|
|
1090
1020
|
<div className="flex flex-col gap-3 md:flex-row md:items-center">
|
|
@@ -1095,7 +1025,7 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
|
|
|
1095
1025
|
onChange={(event) => {
|
|
1096
1026
|
setSortOption(event.target.value as SortOption);
|
|
1097
1027
|
}}
|
|
1098
|
-
className="appearance-none rounded-
|
|
1028
|
+
className="appearance-none rounded-full border border-gray-200 bg-white py-2.5 pl-10 pr-9 text-sm font-medium text-gray-700 shadow-sm transition focus:outline-none focus:ring-1 focus:ring-secondary focus:border-primary"
|
|
1099
1029
|
>
|
|
1100
1030
|
<option value="featured">Featured products</option>
|
|
1101
1031
|
<option value="price-low-high">Price: low to high</option>
|
|
@@ -1104,51 +1034,33 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
|
|
|
1104
1034
|
</select>
|
|
1105
1035
|
<ChevronDown className="pointer-events-none absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-gray-400" />
|
|
1106
1036
|
</div>
|
|
1107
|
-
<div className="flex items-center rounded-
|
|
1037
|
+
<div className="flex items-center rounded-full border border-gray-200 bg-gray-100 shadow-sm p-1">
|
|
1108
1038
|
<button
|
|
1109
1039
|
type="button"
|
|
1110
1040
|
onClick={() => setViewMode('grid')}
|
|
1111
|
-
className={`flex items-center gap-2 rounded-
|
|
1112
|
-
|
|
1113
|
-
|
|
1041
|
+
className={`flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium transition ${viewMode === 'grid'
|
|
1042
|
+
? 'bg-white text-primary shadow-md'
|
|
1043
|
+
: 'text-gray-500 hover:text-gray-700'
|
|
1114
1044
|
}`}
|
|
1115
1045
|
aria-pressed={viewMode === 'grid'}
|
|
1116
1046
|
>
|
|
1117
1047
|
<LayoutGrid className="h-4 w-4" />
|
|
1118
|
-
Grid
|
|
1119
1048
|
</button>
|
|
1120
1049
|
<button
|
|
1121
1050
|
type="button"
|
|
1122
1051
|
onClick={() => setViewMode('list')}
|
|
1123
|
-
className={`flex items-center gap-2 rounded-
|
|
1124
|
-
|
|
1125
|
-
|
|
1052
|
+
className={`flex items-center gap-2 rounded-full px-4 py-2 text-sm font-medium transition ${viewMode === 'list'
|
|
1053
|
+
? 'bg-white text-primary shadow-md'
|
|
1054
|
+
: 'text-gray-500 hover:text-gray-700'
|
|
1126
1055
|
}`}
|
|
1127
1056
|
aria-pressed={viewMode === 'list'}
|
|
1128
1057
|
>
|
|
1129
1058
|
<LayoutList className="h-4 w-4" />
|
|
1130
|
-
List
|
|
1131
1059
|
</button>
|
|
1132
1060
|
</div>
|
|
1133
1061
|
</div>
|
|
1134
1062
|
</div>
|
|
1135
1063
|
|
|
1136
|
-
<div className="mt-4 md:hidden">
|
|
1137
|
-
<Button
|
|
1138
|
-
variant="outline"
|
|
1139
|
-
className="w-full"
|
|
1140
|
-
onClick={() => setShowFilters(true)}
|
|
1141
|
-
>
|
|
1142
|
-
<SlidersHorizontal className="h-5 w-5" />
|
|
1143
|
-
Filters
|
|
1144
|
-
{hasActiveFilters && (
|
|
1145
|
-
<span className="ml-2 rounded-full bg-primary-600 px-2 py-0.5 text-xs font-semibold text-white">
|
|
1146
|
-
{activeFilterChips.length}
|
|
1147
|
-
</span>
|
|
1148
|
-
)}
|
|
1149
|
-
</Button>
|
|
1150
|
-
</div>
|
|
1151
|
-
|
|
1152
1064
|
{hasActiveFilters && (
|
|
1153
1065
|
<div className="mt-6 flex flex-wrap items-center gap-2 border-t border-gray-100 pt-4">
|
|
1154
1066
|
{activeFilterChips.map((chip) => (
|
|
@@ -1243,11 +1155,6 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
|
|
|
1243
1155
|
<h3 className="text-xl font-semibold text-gray-900">
|
|
1244
1156
|
{product.name}
|
|
1245
1157
|
</h3>
|
|
1246
|
-
{/* {product.description && (
|
|
1247
|
-
<p className="text-sm leading-relaxed text-gray-600 line-clamp-2">
|
|
1248
|
-
{product.description}
|
|
1249
|
-
</p>
|
|
1250
|
-
)} */}
|
|
1251
1158
|
<div className="flex flex-wrap items-center gap-4 text-sm text-gray-500">
|
|
1252
1159
|
<span className="inline-flex items-center gap-2 font-medium text-primary-600">
|
|
1253
1160
|
<ShieldCheck className="h-4 w-4" />
|
|
@@ -1265,21 +1172,21 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
|
|
|
1265
1172
|
<p className="text-3xl font-semibold text-gray-900">
|
|
1266
1173
|
{formatPrice(product.finalPrice)}
|
|
1267
1174
|
</p>
|
|
1268
|
-
{product.
|
|
1175
|
+
{product.isDiscounted && (
|
|
1269
1176
|
<p className="text-sm text-gray-400 line-through">
|
|
1270
1177
|
{formatPrice(product.priceBeforeDiscount)}
|
|
1271
1178
|
</p>
|
|
1272
1179
|
)}
|
|
1273
1180
|
</div>
|
|
1274
|
-
<
|
|
1275
|
-
size="sm"
|
|
1181
|
+
<button
|
|
1276
1182
|
onClick={(event) => {
|
|
1277
1183
|
event.stopPropagation();
|
|
1278
1184
|
router.push(buildPath(`/products/${product._id}`));
|
|
1279
1185
|
}}
|
|
1186
|
+
className="w-full font-['Poppins',sans-serif] font-medium text-sm px-3 py-2 rounded-xl bg-secondary text-white hover:opacity-80 hover:shadow-lg transition-all duration-300 flex items-center justify-center gap-1.5 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
1280
1187
|
>
|
|
1281
1188
|
View product
|
|
1282
|
-
</
|
|
1189
|
+
</button>
|
|
1283
1190
|
</div>
|
|
1284
1191
|
</motion.div>
|
|
1285
1192
|
);
|
|
@@ -1325,37 +1232,6 @@ export function ShopScreen({ initialFilters = {}, categoryName }: ShopScreenProp
|
|
|
1325
1232
|
</div>
|
|
1326
1233
|
</div>
|
|
1327
1234
|
</div>
|
|
1328
|
-
|
|
1329
|
-
<AnimatePresence>
|
|
1330
|
-
{showFilters && (
|
|
1331
|
-
<motion.div
|
|
1332
|
-
initial={{ opacity: 0 }}
|
|
1333
|
-
animate={{ opacity: 1 }}
|
|
1334
|
-
exit={{ opacity: 0 }}
|
|
1335
|
-
className="fixed inset-0 z-50 bg-black/40 backdrop-blur-sm lg:hidden"
|
|
1336
|
-
>
|
|
1337
|
-
<motion.div
|
|
1338
|
-
initial={{ y: '100%' }}
|
|
1339
|
-
animate={{ y: 0 }}
|
|
1340
|
-
exit={{ y: '100%' }}
|
|
1341
|
-
transition={{ type: 'spring', stiffness: 260, damping: 26 }}
|
|
1342
|
-
className="absolute inset-x-0 bottom-0 max-h-[85vh] overflow-y-auto rounded-t-3xl bg-white p-6 shadow-2xl"
|
|
1343
|
-
>
|
|
1344
|
-
<div className="mb-6 flex items-center justify-between">
|
|
1345
|
-
<h3 className="text-lg font-semibold text-gray-900">Filters</h3>
|
|
1346
|
-
<button
|
|
1347
|
-
type="button"
|
|
1348
|
-
onClick={() => setShowFilters(false)}
|
|
1349
|
-
className="rounded-full border border-gray-200 p-2 text-gray-500 hover:text-gray-700"
|
|
1350
|
-
>
|
|
1351
|
-
<X className="h-4 w-4" />
|
|
1352
|
-
</button>
|
|
1353
|
-
</div>
|
|
1354
|
-
{renderFiltersPanel()}
|
|
1355
|
-
</motion.div>
|
|
1356
|
-
</motion.div>
|
|
1357
|
-
)}
|
|
1358
|
-
</AnimatePresence>
|
|
1359
1235
|
</div>
|
|
1360
1236
|
);
|
|
1361
1237
|
}
|