hey-pharmacist-ecommerce 1.0.0
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/README.md +269 -0
- package/dist/index.d.mts +564 -0
- package/dist/index.d.ts +564 -0
- package/dist/index.js +7541 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +7485 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +70 -0
- package/src/components/CartItem.tsx +103 -0
- package/src/components/EmptyState.tsx +27 -0
- package/src/components/Footer.tsx +147 -0
- package/src/components/Header.tsx +151 -0
- package/src/components/OrderCard.tsx +98 -0
- package/src/components/ProductCard.tsx +122 -0
- package/src/components/ui/Badge.tsx +31 -0
- package/src/components/ui/Button.tsx +61 -0
- package/src/components/ui/Input.tsx +45 -0
- package/src/components/ui/Modal.tsx +79 -0
- package/src/components/ui/Skeleton.tsx +46 -0
- package/src/hooks/useOrders.ts +98 -0
- package/src/hooks/useProducts.ts +125 -0
- package/src/index.ts +71 -0
- package/src/lib/Apis/api.ts +46 -0
- package/src/lib/Apis/apis/addresses-api.ts +1461 -0
- package/src/lib/Apis/apis/auth-api.ts +945 -0
- package/src/lib/Apis/apis/blogs-api.ts +582 -0
- package/src/lib/Apis/apis/cart-api.ts +456 -0
- package/src/lib/Apis/apis/categories-api.ts +725 -0
- package/src/lib/Apis/apis/chats-api.ts +1101 -0
- package/src/lib/Apis/apis/contact-us-api.ts +394 -0
- package/src/lib/Apis/apis/discounts-api.ts +763 -0
- package/src/lib/Apis/apis/drafts-api.ts +448 -0
- package/src/lib/Apis/apis/events-api.ts +1311 -0
- package/src/lib/Apis/apis/file-proccesor-api.ts +293 -0
- package/src/lib/Apis/apis/health-api.ts +119 -0
- package/src/lib/Apis/apis/images-api.ts +271 -0
- package/src/lib/Apis/apis/inventory-api.ts +375 -0
- package/src/lib/Apis/apis/marketing-api.ts +3099 -0
- package/src/lib/Apis/apis/notifications-api.ts +843 -0
- package/src/lib/Apis/apis/open-aiapi.ts +513 -0
- package/src/lib/Apis/apis/orders-api.ts +1343 -0
- package/src/lib/Apis/apis/payment-methods-api.ts +411 -0
- package/src/lib/Apis/apis/payments-api.ts +469 -0
- package/src/lib/Apis/apis/product-attributes-api.ts +538 -0
- package/src/lib/Apis/apis/product-favorite-list-api.ts +321 -0
- package/src/lib/Apis/apis/product-variants-api.ts +648 -0
- package/src/lib/Apis/apis/products-api.ts +1442 -0
- package/src/lib/Apis/apis/review-api.ts +1383 -0
- package/src/lib/Apis/apis/roles-api.ts +614 -0
- package/src/lib/Apis/apis/shipping-api.ts +703 -0
- package/src/lib/Apis/apis/statistics-api.ts +234 -0
- package/src/lib/Apis/apis/stores-api.ts +1519 -0
- package/src/lib/Apis/apis/sub-categories-api.ts +1208 -0
- package/src/lib/Apis/apis/user-groups-api.ts +1198 -0
- package/src/lib/Apis/apis/users-api.ts +1403 -0
- package/src/lib/Apis/apis/web-hooks-api.ts +198 -0
- package/src/lib/Apis/base.ts +70 -0
- package/src/lib/Apis/configuration.ts +75 -0
- package/src/lib/Apis/index.ts +17 -0
- package/src/lib/Apis/models/add-contact-to-list-dto.ts +33 -0
- package/src/lib/Apis/models/add-message-dto.ts +56 -0
- package/src/lib/Apis/models/address-created-request.ts +134 -0
- package/src/lib/Apis/models/address.ts +164 -0
- package/src/lib/Apis/models/allow-user-credit-dto.ts +27 -0
- package/src/lib/Apis/models/appointment.ts +75 -0
- package/src/lib/Apis/models/available-dates-dto.ts +33 -0
- package/src/lib/Apis/models/available-suggested-dates-dto.ts +33 -0
- package/src/lib/Apis/models/blog.ts +75 -0
- package/src/lib/Apis/models/browser-stats-response-dto.ts +40 -0
- package/src/lib/Apis/models/bulk-move-subcategories-dto.ts +33 -0
- package/src/lib/Apis/models/bulk-unassign-subcategories-dto.ts +27 -0
- package/src/lib/Apis/models/campaign-content-response-dto.ts +40 -0
- package/src/lib/Apis/models/campaign-draft-dto.ts +175 -0
- package/src/lib/Apis/models/campaign-draft-response-dto.ts +40 -0
- package/src/lib/Apis/models/campaign-draft-schedule-dto.ts +49 -0
- package/src/lib/Apis/models/campaign-draft-schedule-response-dto.ts +40 -0
- package/src/lib/Apis/models/campaign-draft-sending-dto.ts +43 -0
- package/src/lib/Apis/models/campaign-draft-sending-response-dto.ts +40 -0
- package/src/lib/Apis/models/cart-body-dto.ts +40 -0
- package/src/lib/Apis/models/cart-body-populated.ts +47 -0
- package/src/lib/Apis/models/cart-item-populated.ts +41 -0
- package/src/lib/Apis/models/cart-item.ts +33 -0
- package/src/lib/Apis/models/cart-response-dto.ts +70 -0
- package/src/lib/Apis/models/categories-paginated-response-dto.ts +52 -0
- package/src/lib/Apis/models/category-filters.ts +40 -0
- package/src/lib/Apis/models/category-populated.ts +106 -0
- package/src/lib/Apis/models/category-sub-category-populated.ts +51 -0
- package/src/lib/Apis/models/category.ts +99 -0
- package/src/lib/Apis/models/categorys-headlines-response-dto.ts +40 -0
- package/src/lib/Apis/models/change-user-email-dto.ts +27 -0
- package/src/lib/Apis/models/chat.ts +33 -0
- package/src/lib/Apis/models/check-notifications-response-dto.ts +33 -0
- package/src/lib/Apis/models/contact-aggregated-stats-response-dto.ts +40 -0
- package/src/lib/Apis/models/contact-full-dto.ts +93 -0
- package/src/lib/Apis/models/contact-full-response-dto.ts +40 -0
- package/src/lib/Apis/models/contact-list-stats-response-dto.ts +40 -0
- package/src/lib/Apis/models/contact-lists-response-dto.ts +40 -0
- package/src/lib/Apis/models/contact-us.ts +81 -0
- package/src/lib/Apis/models/country-stats-response-dto.ts +40 -0
- package/src/lib/Apis/models/create-address-dto.ts +134 -0
- package/src/lib/Apis/models/create-blog-dto.ts +45 -0
- package/src/lib/Apis/models/create-category-dto.ts +45 -0
- package/src/lib/Apis/models/create-chat-dto.ts +39 -0
- package/src/lib/Apis/models/create-contact-dto.ts +39 -0
- package/src/lib/Apis/models/create-contact-list-dto.ts +27 -0
- package/src/lib/Apis/models/create-discount-dto.ts +208 -0
- package/src/lib/Apis/models/create-draft-dto.ts +67 -0
- package/src/lib/Apis/models/create-email-template-dto.ts +51 -0
- package/src/lib/Apis/models/create-event-dto.ts +52 -0
- package/src/lib/Apis/models/create-marketing-campaign-dto.ts +81 -0
- package/src/lib/Apis/models/create-message-dto.ts +57 -0
- package/src/lib/Apis/models/create-notification-dto.ts +75 -0
- package/src/lib/Apis/models/create-product-attribute-dto.ts +33 -0
- package/src/lib/Apis/models/create-product-dto.ts +94 -0
- package/src/lib/Apis/models/create-review-dto.ts +63 -0
- package/src/lib/Apis/models/create-role-dto.ts +57 -0
- package/src/lib/Apis/models/create-single-variant-product-dto.ts +155 -0
- package/src/lib/Apis/models/create-store-address-dto.ts +134 -0
- package/src/lib/Apis/models/create-store-dto.ts +105 -0
- package/src/lib/Apis/models/create-sub-category-dto.ts +45 -0
- package/src/lib/Apis/models/create-user-dto.ts +89 -0
- package/src/lib/Apis/models/create-user-group-dto.ts +39 -0
- package/src/lib/Apis/models/create-variant-dto.ts +119 -0
- package/src/lib/Apis/models/create-zone-dto.ts +82 -0
- package/src/lib/Apis/models/custom-product-dto.ts +63 -0
- package/src/lib/Apis/models/default-payment-method-request-dto.ts +27 -0
- package/src/lib/Apis/models/delete-file-dto.ts +27 -0
- package/src/lib/Apis/models/delete-many-files-dto.ts +27 -0
- package/src/lib/Apis/models/discount-paginated-response.ts +52 -0
- package/src/lib/Apis/models/discount.ts +245 -0
- package/src/lib/Apis/models/discounts-insights-dto.ts +57 -0
- package/src/lib/Apis/models/draft.ts +79 -0
- package/src/lib/Apis/models/email-invoice-dto.ts +45 -0
- package/src/lib/Apis/models/email-template-response-dto.ts +117 -0
- package/src/lib/Apis/models/event.ts +76 -0
- package/src/lib/Apis/models/extended-product-dto.ts +204 -0
- package/src/lib/Apis/models/fileproccesor-upload-body.ts +27 -0
- package/src/lib/Apis/models/forget-password.ts +27 -0
- package/src/lib/Apis/models/frequently-bought-product-dto.ts +71 -0
- package/src/lib/Apis/models/general-stats-response-dto.ts +40 -0
- package/src/lib/Apis/models/generate-day-slots-dto.ts +51 -0
- package/src/lib/Apis/models/generate-month-slots-dto.ts +57 -0
- package/src/lib/Apis/models/generate-week-slots-dto.ts +57 -0
- package/src/lib/Apis/models/google-analytics-request-dto.ts +55 -0
- package/src/lib/Apis/models/google-analytics-response-dto.ts +39 -0
- package/src/lib/Apis/models/group-with-no-users-dto.ts +75 -0
- package/src/lib/Apis/models/group-with-users-dto.ts +76 -0
- package/src/lib/Apis/models/images-upload-body.ts +27 -0
- package/src/lib/Apis/models/index.ts +197 -0
- package/src/lib/Apis/models/inventory-paginated-response.ts +75 -0
- package/src/lib/Apis/models/link-stats-response-dto.ts +40 -0
- package/src/lib/Apis/models/login-dto.ts +33 -0
- package/src/lib/Apis/models/manual-discount.ts +49 -0
- package/src/lib/Apis/models/manual-order-dto.ts +133 -0
- package/src/lib/Apis/models/manual-shipping-dto.ts +45 -0
- package/src/lib/Apis/models/marketing-campaign-content-dto.ts +27 -0
- package/src/lib/Apis/models/marketing-list-contact-dto.ts +51 -0
- package/src/lib/Apis/models/move-subcategory-dto.ts +27 -0
- package/src/lib/Apis/models/my-favorite-list-dto.ts +52 -0
- package/src/lib/Apis/models/notification.ts +93 -0
- package/src/lib/Apis/models/object-id.ts +21 -0
- package/src/lib/Apis/models/open-api.ts +33 -0
- package/src/lib/Apis/models/order-paginated-response.ts +52 -0
- package/src/lib/Apis/models/order.ts +214 -0
- package/src/lib/Apis/models/orders-insights-dto.ts +69 -0
- package/src/lib/Apis/models/paginated-products-dto.ts +52 -0
- package/src/lib/Apis/models/payment-method-data.ts +34 -0
- package/src/lib/Apis/models/payment-method.ts +51 -0
- package/src/lib/Apis/models/payment-time-line-dto.ts +56 -0
- package/src/lib/Apis/models/payment.ts +182 -0
- package/src/lib/Apis/models/payments-insights-dto.ts +69 -0
- package/src/lib/Apis/models/payments-paginated-response.ts +52 -0
- package/src/lib/Apis/models/pick-type-class.ts +51 -0
- package/src/lib/Apis/models/populated-chat-dto.ts +95 -0
- package/src/lib/Apis/models/populated-discount.ts +246 -0
- package/src/lib/Apis/models/populated-order.ts +209 -0
- package/src/lib/Apis/models/prefered-pick-or-delivery-time-dto.ts +33 -0
- package/src/lib/Apis/models/price-range.ts +33 -0
- package/src/lib/Apis/models/product-attribute.ts +57 -0
- package/src/lib/Apis/models/product-variant.ts +167 -0
- package/src/lib/Apis/models/product.ts +136 -0
- package/src/lib/Apis/models/products-insights-dto.ts +45 -0
- package/src/lib/Apis/models/rate-dto.ts +123 -0
- package/src/lib/Apis/models/refill-request-dto.ts +75 -0
- package/src/lib/Apis/models/refill-request.ts +105 -0
- package/src/lib/Apis/models/register-or-login-with-gmail.ts +27 -0
- package/src/lib/Apis/models/reserve-appointment.ts +261 -0
- package/src/lib/Apis/models/review.ts +93 -0
- package/src/lib/Apis/models/role.ts +69 -0
- package/src/lib/Apis/models/schedule-campaign-draft-dto.ts +27 -0
- package/src/lib/Apis/models/send-test-email-dto.ts +28 -0
- package/src/lib/Apis/models/shallow-parent-category-dto.ts +33 -0
- package/src/lib/Apis/models/shipment-details-dto.ts +88 -0
- package/src/lib/Apis/models/shipment-status-dto.ts +117 -0
- package/src/lib/Apis/models/shipment-with-order.ts +105 -0
- package/src/lib/Apis/models/shipment.ts +104 -0
- package/src/lib/Apis/models/shipping-info.ts +88 -0
- package/src/lib/Apis/models/single-browser-stats-dto.ts +45 -0
- package/src/lib/Apis/models/single-contact-aggregated-stats-dto.ts +129 -0
- package/src/lib/Apis/models/single-contact-list-stats-dto.ts +117 -0
- package/src/lib/Apis/models/single-country-stats-dto.ts +39 -0
- package/src/lib/Apis/models/single-general-stats.ts +153 -0
- package/src/lib/Apis/models/single-link-stats-dto.ts +39 -0
- package/src/lib/Apis/models/single-message-populated.ts +59 -0
- package/src/lib/Apis/models/single-notification-dto.ts +99 -0
- package/src/lib/Apis/models/single-product-media.ts +74 -0
- package/src/lib/Apis/models/single-recipient-dto.ts +33 -0
- package/src/lib/Apis/models/single-suggest-attribute.ts +33 -0
- package/src/lib/Apis/models/statistic-dto.ts +171 -0
- package/src/lib/Apis/models/store-entity.ts +117 -0
- package/src/lib/Apis/models/store.ts +135 -0
- package/src/lib/Apis/models/sub-category-headlines-only-response-dto.ts +39 -0
- package/src/lib/Apis/models/sub-category.ts +93 -0
- package/src/lib/Apis/models/suggest-attributes.ts +28 -0
- package/src/lib/Apis/models/suggested-slot.ts +33 -0
- package/src/lib/Apis/models/table-cell-dto.ts +33 -0
- package/src/lib/Apis/models/table-dto.ts +34 -0
- package/src/lib/Apis/models/tadmin-session-data.ts +47 -0
- package/src/lib/Apis/models/track-dto.ts +94 -0
- package/src/lib/Apis/models/tracking-status-location-base.ts +45 -0
- package/src/lib/Apis/models/tracking-status-substatus.ts +39 -0
- package/src/lib/Apis/models/tracking-status.ts +71 -0
- package/src/lib/Apis/models/transfere-patient-request.ts +123 -0
- package/src/lib/Apis/models/transfere-patients-request-dto.ts +99 -0
- package/src/lib/Apis/models/tuser-session-data.ts +34 -0
- package/src/lib/Apis/models/update-address-dto.ts +134 -0
- package/src/lib/Apis/models/update-blog-dto.ts +45 -0
- package/src/lib/Apis/models/update-campaign-draft-content-dto.ts +27 -0
- package/src/lib/Apis/models/update-category-dto.ts +45 -0
- package/src/lib/Apis/models/update-discount-dto.ts +208 -0
- package/src/lib/Apis/models/update-event-dto.ts +52 -0
- package/src/lib/Apis/models/update-items-order-dto.ts +27 -0
- package/src/lib/Apis/models/update-marketing-camp-draft-dto.ts +81 -0
- package/src/lib/Apis/models/update-message-dto.ts +57 -0
- package/src/lib/Apis/models/update-product-attribute-dto.ts +33 -0
- package/src/lib/Apis/models/update-product-dto.ts +96 -0
- package/src/lib/Apis/models/update-product-variant-dto.ts +119 -0
- package/src/lib/Apis/models/update-refill-request-dto.ts +75 -0
- package/src/lib/Apis/models/update-review-dto.ts +63 -0
- package/src/lib/Apis/models/update-role-dto.ts +57 -0
- package/src/lib/Apis/models/update-store-dto.ts +105 -0
- package/src/lib/Apis/models/update-sub-category-dto.ts +45 -0
- package/src/lib/Apis/models/update-transfere-patients-request-dto.ts +99 -0
- package/src/lib/Apis/models/update-user-dto.ts +239 -0
- package/src/lib/Apis/models/update-user-group-dto.ts +39 -0
- package/src/lib/Apis/models/update-zone-dto.ts +82 -0
- package/src/lib/Apis/models/upload-pdf-body.ts +27 -0
- package/src/lib/Apis/models/used-by.ts +87 -0
- package/src/lib/Apis/models/user-entity.ts +220 -0
- package/src/lib/Apis/models/user-group.ts +75 -0
- package/src/lib/Apis/models/user-insights-dto.ts +39 -0
- package/src/lib/Apis/models/user-with-no-id.ts +226 -0
- package/src/lib/Apis/models/user.ts +232 -0
- package/src/lib/Apis/models/users-paginated-response.ts +52 -0
- package/src/lib/Apis/models/verify-email-dto.ts +33 -0
- package/src/lib/Apis/models/zone-single-size.ts +51 -0
- package/src/lib/Apis/models/zone.ts +106 -0
- package/src/lib/Apis/wrapper.ts +37 -0
- package/src/lib/api/auth.ts +81 -0
- package/src/lib/api/cart.ts +42 -0
- package/src/lib/api/client.ts +118 -0
- package/src/lib/api/orders.ts +53 -0
- package/src/lib/api/products.ts +51 -0
- package/src/lib/api-adapter/auth-adapter.ts +196 -0
- package/src/lib/api-adapter/cart-adapter.ts +193 -0
- package/src/lib/api-adapter/config.ts +76 -0
- package/src/lib/api-adapter/index.ts +13 -0
- package/src/lib/api-adapter/mappers.ts +147 -0
- package/src/lib/api-adapter/orders-adapter.ts +195 -0
- package/src/lib/api-adapter/products-adapter.ts +194 -0
- package/src/lib/types/index.ts +152 -0
- package/src/lib/utils/colors.ts +51 -0
- package/src/lib/utils/format.ts +48 -0
- package/src/providers/AuthProvider.tsx +117 -0
- package/src/providers/CartProvider.tsx +131 -0
- package/src/providers/EcommerceProvider.tsx +34 -0
- package/src/providers/ThemeProvider.tsx +57 -0
- package/src/screens/CartScreen.tsx +140 -0
- package/src/screens/CheckoutScreen.tsx +340 -0
- package/src/screens/CurrentOrdersScreen.tsx +85 -0
- package/src/screens/LoginScreen.tsx +149 -0
- package/src/screens/OrdersScreen.tsx +86 -0
- package/src/screens/ProductDetailScreen.tsx +255 -0
- package/src/screens/ProfileScreen.tsx +211 -0
- package/src/screens/RegisterScreen.tsx +200 -0
- package/src/screens/ShopScreen.tsx +233 -0
- package/src/styles/globals.css +51 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { motion } from 'framer-motion';
|
|
5
|
+
import { useForm } from 'react-hook-form';
|
|
6
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { User, Mail, Phone, LogOut, Package, Heart } from 'lucide-react';
|
|
9
|
+
import { Button } from '@/components/ui/Button';
|
|
10
|
+
import { Input } from '@/components/ui/Input';
|
|
11
|
+
import { useAuth } from '@/providers/AuthProvider';
|
|
12
|
+
import { useRouter } from 'next/navigation';
|
|
13
|
+
import { toast } from 'sonner';
|
|
14
|
+
import { getInitials } from '@/lib/utils/format';
|
|
15
|
+
import Link from 'next/link';
|
|
16
|
+
|
|
17
|
+
const profileSchema = z.object({
|
|
18
|
+
firstName: z.string().min(2, 'First name is required'),
|
|
19
|
+
lastName: z.string().min(2, 'Last name is required'),
|
|
20
|
+
email: z.string().email('Invalid email address'),
|
|
21
|
+
phone: z.string().optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
type ProfileFormData = z.infer<typeof profileSchema>;
|
|
25
|
+
|
|
26
|
+
export function ProfileScreen() {
|
|
27
|
+
const router = useRouter();
|
|
28
|
+
const { user, updateUser, logout } = useAuth();
|
|
29
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
30
|
+
|
|
31
|
+
const {
|
|
32
|
+
register,
|
|
33
|
+
handleSubmit,
|
|
34
|
+
formState: { errors },
|
|
35
|
+
} = useForm<ProfileFormData>({
|
|
36
|
+
resolver: zodResolver(profileSchema),
|
|
37
|
+
defaultValues: {
|
|
38
|
+
firstName: user?.firstName || '',
|
|
39
|
+
lastName: user?.lastName || '',
|
|
40
|
+
email: user?.email || '',
|
|
41
|
+
phone: user?.phone || '',
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const onSubmit = async (data: ProfileFormData) => {
|
|
46
|
+
setIsSubmitting(true);
|
|
47
|
+
try {
|
|
48
|
+
await updateUser(data);
|
|
49
|
+
toast.success('Profile updated successfully!');
|
|
50
|
+
} catch (error: any) {
|
|
51
|
+
toast.error(error.response?.data?.message || 'Failed to update profile');
|
|
52
|
+
} finally {
|
|
53
|
+
setIsSubmitting(false);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const handleLogout = async () => {
|
|
58
|
+
await logout();
|
|
59
|
+
toast.success('Logged out successfully');
|
|
60
|
+
router.push('/');
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
if (!user) {
|
|
64
|
+
router.push('/login');
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const menuItems = [
|
|
69
|
+
{
|
|
70
|
+
icon: Package,
|
|
71
|
+
label: 'My Orders',
|
|
72
|
+
href: '/orders',
|
|
73
|
+
description: 'View and track your orders',
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
icon: Heart,
|
|
77
|
+
label: 'Wishlist',
|
|
78
|
+
href: '/wishlist',
|
|
79
|
+
description: 'Your saved products',
|
|
80
|
+
},
|
|
81
|
+
];
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<div className="min-h-screen bg-gray-50 py-12">
|
|
85
|
+
<div className="container mx-auto px-4 max-w-4xl">
|
|
86
|
+
{/* Header */}
|
|
87
|
+
<motion.div
|
|
88
|
+
initial={{ opacity: 0, y: 20 }}
|
|
89
|
+
animate={{ opacity: 1, y: 0 }}
|
|
90
|
+
className="mb-8"
|
|
91
|
+
>
|
|
92
|
+
<h1 className="text-4xl font-bold text-gray-900 mb-2">My Account</h1>
|
|
93
|
+
<p className="text-gray-600">Manage your profile and preferences</p>
|
|
94
|
+
</motion.div>
|
|
95
|
+
|
|
96
|
+
<div className="grid md:grid-cols-3 gap-8">
|
|
97
|
+
{/* Sidebar */}
|
|
98
|
+
<div className="md:col-span-1">
|
|
99
|
+
<motion.div
|
|
100
|
+
initial={{ opacity: 0, y: 20 }}
|
|
101
|
+
animate={{ opacity: 1, y: 0 }}
|
|
102
|
+
transition={{ delay: 0.1 }}
|
|
103
|
+
className="bg-white rounded-2xl p-6 shadow-sm"
|
|
104
|
+
>
|
|
105
|
+
{/* Avatar */}
|
|
106
|
+
<div className="flex flex-col items-center mb-6">
|
|
107
|
+
<div className="w-24 h-24 bg-gradient-to-br from-primary-500 to-secondary-500 rounded-full flex items-center justify-center text-white text-3xl font-bold mb-4">
|
|
108
|
+
{getInitials(user.firstName, user.lastName)}
|
|
109
|
+
</div>
|
|
110
|
+
<h3 className="text-xl font-bold text-gray-900">
|
|
111
|
+
{user.firstName} {user.lastName}
|
|
112
|
+
</h3>
|
|
113
|
+
<p className="text-gray-600">{user.email}</p>
|
|
114
|
+
</div>
|
|
115
|
+
|
|
116
|
+
{/* Menu Items */}
|
|
117
|
+
<nav className="space-y-2">
|
|
118
|
+
{menuItems.map((item) => (
|
|
119
|
+
<Link
|
|
120
|
+
key={item.href}
|
|
121
|
+
href={item.href}
|
|
122
|
+
className="flex items-start gap-3 p-3 rounded-lg hover:bg-gray-50 transition-colors group"
|
|
123
|
+
>
|
|
124
|
+
<item.icon className="w-5 h-5 text-gray-500 group-hover:text-primary-600 mt-0.5" />
|
|
125
|
+
<div>
|
|
126
|
+
<p className="font-medium text-gray-900 group-hover:text-primary-600">
|
|
127
|
+
{item.label}
|
|
128
|
+
</p>
|
|
129
|
+
<p className="text-sm text-gray-500">{item.description}</p>
|
|
130
|
+
</div>
|
|
131
|
+
</Link>
|
|
132
|
+
))}
|
|
133
|
+
</nav>
|
|
134
|
+
|
|
135
|
+
{/* Logout Button */}
|
|
136
|
+
<button
|
|
137
|
+
onClick={handleLogout}
|
|
138
|
+
className="w-full mt-6 flex items-center justify-center gap-2 p-3 text-red-600 hover:bg-red-50 rounded-lg transition-colors font-medium"
|
|
139
|
+
>
|
|
140
|
+
<LogOut className="w-5 h-5" />
|
|
141
|
+
Logout
|
|
142
|
+
</button>
|
|
143
|
+
</motion.div>
|
|
144
|
+
</div>
|
|
145
|
+
|
|
146
|
+
{/* Main Content */}
|
|
147
|
+
<div className="md:col-span-2">
|
|
148
|
+
<motion.div
|
|
149
|
+
initial={{ opacity: 0, y: 20 }}
|
|
150
|
+
animate={{ opacity: 1, y: 0 }}
|
|
151
|
+
transition={{ delay: 0.2 }}
|
|
152
|
+
className="bg-white rounded-2xl p-8 shadow-sm"
|
|
153
|
+
>
|
|
154
|
+
<h2 className="text-2xl font-bold text-gray-900 mb-6">
|
|
155
|
+
Personal Information
|
|
156
|
+
</h2>
|
|
157
|
+
|
|
158
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-6">
|
|
159
|
+
<div className="grid grid-cols-2 gap-4">
|
|
160
|
+
<Input
|
|
161
|
+
label="First Name"
|
|
162
|
+
{...register('firstName')}
|
|
163
|
+
error={errors.firstName?.message}
|
|
164
|
+
/>
|
|
165
|
+
<Input
|
|
166
|
+
label="Last Name"
|
|
167
|
+
{...register('lastName')}
|
|
168
|
+
error={errors.lastName?.message}
|
|
169
|
+
/>
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
<Input
|
|
173
|
+
type="email"
|
|
174
|
+
label="Email Address"
|
|
175
|
+
{...register('email')}
|
|
176
|
+
error={errors.email?.message}
|
|
177
|
+
/>
|
|
178
|
+
|
|
179
|
+
<Input
|
|
180
|
+
type="tel"
|
|
181
|
+
label="Phone Number"
|
|
182
|
+
{...register('phone')}
|
|
183
|
+
error={errors.phone?.message}
|
|
184
|
+
/>
|
|
185
|
+
|
|
186
|
+
<div className="flex gap-4">
|
|
187
|
+
<Button
|
|
188
|
+
type="submit"
|
|
189
|
+
size="lg"
|
|
190
|
+
isLoading={isSubmitting}
|
|
191
|
+
>
|
|
192
|
+
Save Changes
|
|
193
|
+
</Button>
|
|
194
|
+
<Button
|
|
195
|
+
type="button"
|
|
196
|
+
variant="outline"
|
|
197
|
+
size="lg"
|
|
198
|
+
onClick={() => router.push('/account/change-password')}
|
|
199
|
+
>
|
|
200
|
+
Change Password
|
|
201
|
+
</Button>
|
|
202
|
+
</div>
|
|
203
|
+
</form>
|
|
204
|
+
</motion.div>
|
|
205
|
+
</div>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
</div>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { motion } from 'framer-motion';
|
|
5
|
+
import { useForm } from 'react-hook-form';
|
|
6
|
+
import { zodResolver } from '@hookform/resolvers/zod';
|
|
7
|
+
import { z } from 'zod';
|
|
8
|
+
import { UserPlus, Eye, EyeOff } from 'lucide-react';
|
|
9
|
+
import { Button } from '@/components/ui/Button';
|
|
10
|
+
import { Input } from '@/components/ui/Input';
|
|
11
|
+
import { useAuth } from '@/providers/AuthProvider';
|
|
12
|
+
import { useRouter } from 'next/navigation';
|
|
13
|
+
import { toast } from 'sonner';
|
|
14
|
+
import Link from 'next/link';
|
|
15
|
+
|
|
16
|
+
const registerSchema = z.object({
|
|
17
|
+
firstName: z.string().min(2, 'First name is required'),
|
|
18
|
+
lastName: z.string().min(2, 'Last name is required'),
|
|
19
|
+
email: z.string().email('Invalid email address'),
|
|
20
|
+
phone: z.string().optional(),
|
|
21
|
+
password: z.string().min(6, 'Password must be at least 6 characters'),
|
|
22
|
+
confirmPassword: z.string(),
|
|
23
|
+
}).refine((data) => data.password === data.confirmPassword, {
|
|
24
|
+
message: "Passwords don't match",
|
|
25
|
+
path: ['confirmPassword'],
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
type RegisterFormData = z.infer<typeof registerSchema>;
|
|
29
|
+
|
|
30
|
+
export function RegisterScreen() {
|
|
31
|
+
const router = useRouter();
|
|
32
|
+
const { register: registerUser } = useAuth();
|
|
33
|
+
const [showPassword, setShowPassword] = useState(false);
|
|
34
|
+
const [showConfirmPassword, setShowConfirmPassword] = useState(false);
|
|
35
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
36
|
+
|
|
37
|
+
const {
|
|
38
|
+
register,
|
|
39
|
+
handleSubmit,
|
|
40
|
+
formState: { errors },
|
|
41
|
+
} = useForm<RegisterFormData>({
|
|
42
|
+
resolver: zodResolver(registerSchema),
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
const onSubmit = async (data: RegisterFormData) => {
|
|
46
|
+
setIsSubmitting(true);
|
|
47
|
+
try {
|
|
48
|
+
const { confirmPassword, ...registerData } = data;
|
|
49
|
+
await registerUser(registerData);
|
|
50
|
+
toast.success('Account created successfully!');
|
|
51
|
+
router.push('/');
|
|
52
|
+
} catch (error: any) {
|
|
53
|
+
toast.error(error.response?.data?.message || 'Failed to create account');
|
|
54
|
+
} finally {
|
|
55
|
+
setIsSubmitting(false);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<div className="min-h-screen bg-gradient-to-br from-primary-600 via-primary-700 to-secondary-600 flex items-center justify-center p-4">
|
|
61
|
+
<motion.div
|
|
62
|
+
initial={{ opacity: 0, y: 20 }}
|
|
63
|
+
animate={{ opacity: 1, y: 0 }}
|
|
64
|
+
className="w-full max-w-md"
|
|
65
|
+
>
|
|
66
|
+
<div className="bg-white rounded-3xl shadow-2xl p-8">
|
|
67
|
+
{/* Header */}
|
|
68
|
+
<div className="text-center mb-8">
|
|
69
|
+
<div className="w-16 h-16 bg-primary-100 rounded-full flex items-center justify-center mx-auto mb-4">
|
|
70
|
+
<UserPlus className="w-8 h-8 text-primary-600" />
|
|
71
|
+
</div>
|
|
72
|
+
<h1 className="text-3xl font-bold text-gray-900 mb-2">Create Account</h1>
|
|
73
|
+
<p className="text-gray-600">Join us and start shopping</p>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
{/* Form */}
|
|
77
|
+
<form onSubmit={handleSubmit(onSubmit)} className="space-y-5">
|
|
78
|
+
<div className="grid grid-cols-2 gap-4">
|
|
79
|
+
<Input
|
|
80
|
+
label="First Name"
|
|
81
|
+
placeholder="John"
|
|
82
|
+
{...register('firstName')}
|
|
83
|
+
error={errors.firstName?.message}
|
|
84
|
+
/>
|
|
85
|
+
<Input
|
|
86
|
+
label="Last Name"
|
|
87
|
+
placeholder="Doe"
|
|
88
|
+
{...register('lastName')}
|
|
89
|
+
error={errors.lastName?.message}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
<Input
|
|
94
|
+
type="email"
|
|
95
|
+
label="Email Address"
|
|
96
|
+
placeholder="you@example.com"
|
|
97
|
+
{...register('email')}
|
|
98
|
+
error={errors.email?.message}
|
|
99
|
+
/>
|
|
100
|
+
|
|
101
|
+
<Input
|
|
102
|
+
type="tel"
|
|
103
|
+
label="Phone Number (Optional)"
|
|
104
|
+
placeholder="+1 (555) 000-0000"
|
|
105
|
+
{...register('phone')}
|
|
106
|
+
error={errors.phone?.message}
|
|
107
|
+
/>
|
|
108
|
+
|
|
109
|
+
<div className="relative">
|
|
110
|
+
<Input
|
|
111
|
+
type={showPassword ? 'text' : 'password'}
|
|
112
|
+
label="Password"
|
|
113
|
+
placeholder="••••••••"
|
|
114
|
+
{...register('password')}
|
|
115
|
+
error={errors.password?.message}
|
|
116
|
+
/>
|
|
117
|
+
<button
|
|
118
|
+
type="button"
|
|
119
|
+
onClick={() => setShowPassword(!showPassword)}
|
|
120
|
+
className="absolute right-3 top-[42px] text-gray-500 hover:text-gray-700"
|
|
121
|
+
>
|
|
122
|
+
{showPassword ? (
|
|
123
|
+
<EyeOff className="w-5 h-5" />
|
|
124
|
+
) : (
|
|
125
|
+
<Eye className="w-5 h-5" />
|
|
126
|
+
)}
|
|
127
|
+
</button>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div className="relative">
|
|
131
|
+
<Input
|
|
132
|
+
type={showConfirmPassword ? 'text' : 'password'}
|
|
133
|
+
label="Confirm Password"
|
|
134
|
+
placeholder="••••••••"
|
|
135
|
+
{...register('confirmPassword')}
|
|
136
|
+
error={errors.confirmPassword?.message}
|
|
137
|
+
/>
|
|
138
|
+
<button
|
|
139
|
+
type="button"
|
|
140
|
+
onClick={() => setShowConfirmPassword(!showConfirmPassword)}
|
|
141
|
+
className="absolute right-3 top-[42px] text-gray-500 hover:text-gray-700"
|
|
142
|
+
>
|
|
143
|
+
{showConfirmPassword ? (
|
|
144
|
+
<EyeOff className="w-5 h-5" />
|
|
145
|
+
) : (
|
|
146
|
+
<Eye className="w-5 h-5" />
|
|
147
|
+
)}
|
|
148
|
+
</button>
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
<div className="flex items-start gap-2">
|
|
152
|
+
<input
|
|
153
|
+
type="checkbox"
|
|
154
|
+
required
|
|
155
|
+
className="w-4 h-4 text-primary-600 rounded mt-1"
|
|
156
|
+
/>
|
|
157
|
+
<label className="text-sm text-gray-700">
|
|
158
|
+
I agree to the{' '}
|
|
159
|
+
<Link href="/terms" className="text-primary-600 hover:text-primary-700">
|
|
160
|
+
Terms of Service
|
|
161
|
+
</Link>{' '}
|
|
162
|
+
and{' '}
|
|
163
|
+
<Link href="/privacy" className="text-primary-600 hover:text-primary-700">
|
|
164
|
+
Privacy Policy
|
|
165
|
+
</Link>
|
|
166
|
+
</label>
|
|
167
|
+
</div>
|
|
168
|
+
|
|
169
|
+
<Button
|
|
170
|
+
type="submit"
|
|
171
|
+
size="lg"
|
|
172
|
+
isLoading={isSubmitting}
|
|
173
|
+
className="w-full"
|
|
174
|
+
>
|
|
175
|
+
Create Account
|
|
176
|
+
</Button>
|
|
177
|
+
</form>
|
|
178
|
+
|
|
179
|
+
{/* Divider */}
|
|
180
|
+
<div className="relative my-8">
|
|
181
|
+
<div className="absolute inset-0 flex items-center">
|
|
182
|
+
<div className="w-full border-t border-gray-200"></div>
|
|
183
|
+
</div>
|
|
184
|
+
<div className="relative flex justify-center text-sm">
|
|
185
|
+
<span className="px-4 bg-white text-gray-500">Already have an account?</span>
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
|
|
189
|
+
{/* Login Link */}
|
|
190
|
+
<Link href="/login">
|
|
191
|
+
<Button variant="outline" size="lg" className="w-full">
|
|
192
|
+
Sign In
|
|
193
|
+
</Button>
|
|
194
|
+
</Link>
|
|
195
|
+
</div>
|
|
196
|
+
</motion.div>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import React, { useState } from 'react';
|
|
4
|
+
import { motion } from 'framer-motion';
|
|
5
|
+
import { Search, SlidersHorizontal, X } from 'lucide-react';
|
|
6
|
+
import { ProductCard } from '@/components/ProductCard';
|
|
7
|
+
import { ProductCardSkeleton } from '@/components/ui/Skeleton';
|
|
8
|
+
import { EmptyState } from '@/components/EmptyState';
|
|
9
|
+
import { Button } from '@/components/ui/Button';
|
|
10
|
+
import { Input } from '@/components/ui/Input';
|
|
11
|
+
import { useProducts, useCategories } from '@/hooks/useProducts';
|
|
12
|
+
import { ProductFilters } from '@/lib/types';
|
|
13
|
+
import { Package } from 'lucide-react';
|
|
14
|
+
import { useRouter } from 'next/navigation';
|
|
15
|
+
|
|
16
|
+
export function ShopScreen() {
|
|
17
|
+
const router = useRouter();
|
|
18
|
+
const [filters, setFilters] = useState<ProductFilters>({});
|
|
19
|
+
const [page, setPage] = useState(1);
|
|
20
|
+
const [showFilters, setShowFilters] = useState(false);
|
|
21
|
+
const [searchQuery, setSearchQuery] = useState('');
|
|
22
|
+
|
|
23
|
+
const { products, isLoading, pagination } = useProducts(filters, page, 20);
|
|
24
|
+
const { categories } = useCategories();
|
|
25
|
+
|
|
26
|
+
const handleSearch = (e: React.FormEvent) => {
|
|
27
|
+
e.preventDefault();
|
|
28
|
+
setFilters({ ...filters, search: searchQuery });
|
|
29
|
+
setPage(1);
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const handleCategoryChange = (category: string) => {
|
|
33
|
+
setFilters({ ...filters, category: category === filters.category ? undefined : category });
|
|
34
|
+
setPage(1);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const handleClearFilters = () => {
|
|
38
|
+
setFilters({});
|
|
39
|
+
setSearchQuery('');
|
|
40
|
+
setPage(1);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const hasActiveFilters = Object.keys(filters).length > 0;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<div className="min-h-screen bg-gray-50">
|
|
47
|
+
{/* Hero Section */}
|
|
48
|
+
<section className="bg-gradient-to-br from-primary-600 via-primary-700 to-secondary-600 text-white py-20">
|
|
49
|
+
<div className="container mx-auto px-4">
|
|
50
|
+
<motion.div
|
|
51
|
+
initial={{ opacity: 0, y: 20 }}
|
|
52
|
+
animate={{ opacity: 1, y: 0 }}
|
|
53
|
+
className="max-w-3xl mx-auto text-center"
|
|
54
|
+
>
|
|
55
|
+
<h1 className="text-5xl md:text-6xl font-bold mb-6">
|
|
56
|
+
Discover Amazing Products
|
|
57
|
+
</h1>
|
|
58
|
+
<p className="text-xl text-primary-100 mb-8">
|
|
59
|
+
Browse our curated collection of quality products at great prices
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
{/* Search Bar */}
|
|
63
|
+
<form onSubmit={handleSearch} className="max-w-2xl mx-auto">
|
|
64
|
+
<div className="relative">
|
|
65
|
+
<Input
|
|
66
|
+
type="search"
|
|
67
|
+
placeholder="Search for products..."
|
|
68
|
+
value={searchQuery}
|
|
69
|
+
onChange={(e) => setSearchQuery(e.target.value)}
|
|
70
|
+
className="pr-12 text-lg"
|
|
71
|
+
/>
|
|
72
|
+
<button
|
|
73
|
+
type="submit"
|
|
74
|
+
className="absolute right-2 top-1/2 -translate-y-1/2 p-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors"
|
|
75
|
+
>
|
|
76
|
+
<Search className="w-5 h-5" />
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
</form>
|
|
80
|
+
</motion.div>
|
|
81
|
+
</div>
|
|
82
|
+
</section>
|
|
83
|
+
|
|
84
|
+
{/* Main Content */}
|
|
85
|
+
<div className="container mx-auto px-4 py-12">
|
|
86
|
+
<div className="flex gap-8">
|
|
87
|
+
{/* Sidebar Filters - Desktop */}
|
|
88
|
+
<aside className="hidden lg:block w-64 flex-shrink-0">
|
|
89
|
+
<div className="bg-white rounded-2xl p-6 shadow-sm sticky top-24">
|
|
90
|
+
<div className="flex items-center justify-between mb-6">
|
|
91
|
+
<h3 className="text-lg font-bold text-gray-900">Filters</h3>
|
|
92
|
+
{hasActiveFilters && (
|
|
93
|
+
<button
|
|
94
|
+
onClick={handleClearFilters}
|
|
95
|
+
className="text-sm text-primary-600 hover:text-primary-700 font-medium"
|
|
96
|
+
>
|
|
97
|
+
Clear All
|
|
98
|
+
</button>
|
|
99
|
+
)}
|
|
100
|
+
</div>
|
|
101
|
+
|
|
102
|
+
{/* Categories */}
|
|
103
|
+
<div className="space-y-4">
|
|
104
|
+
<h4 className="font-semibold text-gray-900">Categories</h4>
|
|
105
|
+
<div className="space-y-2">
|
|
106
|
+
{categories.map((category) => (
|
|
107
|
+
<label
|
|
108
|
+
key={category.id}
|
|
109
|
+
className="flex items-center gap-3 cursor-pointer hover:bg-gray-50 p-2 rounded-lg transition-colors"
|
|
110
|
+
>
|
|
111
|
+
<input
|
|
112
|
+
type="checkbox"
|
|
113
|
+
checked={filters.category === category.slug}
|
|
114
|
+
onChange={() => handleCategoryChange(category.slug)}
|
|
115
|
+
className="w-5 h-5 text-primary-600 rounded"
|
|
116
|
+
/>
|
|
117
|
+
<span className="text-gray-700">{category.name}</span>
|
|
118
|
+
<span className="ml-auto text-sm text-gray-500">
|
|
119
|
+
({category.productCount})
|
|
120
|
+
</span>
|
|
121
|
+
</label>
|
|
122
|
+
))}
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
|
|
126
|
+
{/* Stock Status */}
|
|
127
|
+
<div className="space-y-4 mt-8">
|
|
128
|
+
<h4 className="font-semibold text-gray-900">Availability</h4>
|
|
129
|
+
<label className="flex items-center gap-3 cursor-pointer hover:bg-gray-50 p-2 rounded-lg transition-colors">
|
|
130
|
+
<input
|
|
131
|
+
type="checkbox"
|
|
132
|
+
checked={filters.inStock === true}
|
|
133
|
+
onChange={(e) =>
|
|
134
|
+
setFilters({ ...filters, inStock: e.target.checked ? true : undefined })
|
|
135
|
+
}
|
|
136
|
+
className="w-5 h-5 text-primary-600 rounded"
|
|
137
|
+
/>
|
|
138
|
+
<span className="text-gray-700">In Stock Only</span>
|
|
139
|
+
</label>
|
|
140
|
+
</div>
|
|
141
|
+
</div>
|
|
142
|
+
</aside>
|
|
143
|
+
|
|
144
|
+
{/* Products Grid */}
|
|
145
|
+
<div className="flex-1">
|
|
146
|
+
{/* Mobile Filter Button */}
|
|
147
|
+
<div className="lg:hidden mb-6">
|
|
148
|
+
<Button
|
|
149
|
+
variant="outline"
|
|
150
|
+
onClick={() => setShowFilters(!showFilters)}
|
|
151
|
+
className="w-full"
|
|
152
|
+
>
|
|
153
|
+
<SlidersHorizontal className="w-5 h-5" />
|
|
154
|
+
Filters
|
|
155
|
+
{hasActiveFilters && (
|
|
156
|
+
<span className="ml-2 px-2 py-0.5 bg-primary-600 text-white text-xs rounded-full">
|
|
157
|
+
Active
|
|
158
|
+
</span>
|
|
159
|
+
)}
|
|
160
|
+
</Button>
|
|
161
|
+
</div>
|
|
162
|
+
|
|
163
|
+
{/* Results Header */}
|
|
164
|
+
<div className="flex items-center justify-between mb-6">
|
|
165
|
+
<p className="text-gray-600">
|
|
166
|
+
{isLoading ? (
|
|
167
|
+
'Loading...'
|
|
168
|
+
) : (
|
|
169
|
+
<>
|
|
170
|
+
Showing {products.length} of {pagination.total} products
|
|
171
|
+
</>
|
|
172
|
+
)}
|
|
173
|
+
</p>
|
|
174
|
+
</div>
|
|
175
|
+
|
|
176
|
+
{/* Products Grid */}
|
|
177
|
+
{isLoading ? (
|
|
178
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
179
|
+
{Array.from({ length: 6 }).map((_, i) => (
|
|
180
|
+
<ProductCardSkeleton key={i} />
|
|
181
|
+
))}
|
|
182
|
+
</div>
|
|
183
|
+
) : products.length > 0 ? (
|
|
184
|
+
<>
|
|
185
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
|
|
186
|
+
{products.map((product) => (
|
|
187
|
+
<ProductCard
|
|
188
|
+
key={product.id}
|
|
189
|
+
product={product}
|
|
190
|
+
onClickProduct={(p) => router.push(`/products/${p.id}`)}
|
|
191
|
+
/>
|
|
192
|
+
))}
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
{/* Pagination */}
|
|
196
|
+
{pagination.totalPages > 1 && (
|
|
197
|
+
<div className="flex justify-center items-center gap-2 mt-12">
|
|
198
|
+
<Button
|
|
199
|
+
variant="outline"
|
|
200
|
+
onClick={() => setPage(page - 1)}
|
|
201
|
+
disabled={page === 1}
|
|
202
|
+
>
|
|
203
|
+
Previous
|
|
204
|
+
</Button>
|
|
205
|
+
<span className="px-4 text-gray-700">
|
|
206
|
+
Page {page} of {pagination.totalPages}
|
|
207
|
+
</span>
|
|
208
|
+
<Button
|
|
209
|
+
variant="outline"
|
|
210
|
+
onClick={() => setPage(page + 1)}
|
|
211
|
+
disabled={page === pagination.totalPages}
|
|
212
|
+
>
|
|
213
|
+
Next
|
|
214
|
+
</Button>
|
|
215
|
+
</div>
|
|
216
|
+
)}
|
|
217
|
+
</>
|
|
218
|
+
) : (
|
|
219
|
+
<EmptyState
|
|
220
|
+
icon={Package}
|
|
221
|
+
title="No products found"
|
|
222
|
+
description="Try adjusting your filters or search query"
|
|
223
|
+
actionLabel={hasActiveFilters ? 'Clear Filters' : undefined}
|
|
224
|
+
onAction={hasActiveFilters ? handleClearFilters : undefined}
|
|
225
|
+
/>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
</div>
|
|
229
|
+
</div>
|
|
230
|
+
</div>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
@layer base {
|
|
6
|
+
* {
|
|
7
|
+
@apply border-gray-200;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
body {
|
|
11
|
+
@apply bg-gray-50 text-gray-900 antialiased;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@layer utilities {
|
|
16
|
+
.animate-gradient {
|
|
17
|
+
animation: gradient 3s ease infinite;
|
|
18
|
+
background-size: 200% 200%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@keyframes gradient {
|
|
22
|
+
0% {
|
|
23
|
+
background-position: 0% 50%;
|
|
24
|
+
}
|
|
25
|
+
50% {
|
|
26
|
+
background-position: 100% 50%;
|
|
27
|
+
}
|
|
28
|
+
100% {
|
|
29
|
+
background-position: 0% 50%;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/* Custom scrollbar */
|
|
35
|
+
::-webkit-scrollbar {
|
|
36
|
+
width: 8px;
|
|
37
|
+
height: 8px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
::-webkit-scrollbar-track {
|
|
41
|
+
@apply bg-gray-100;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
::-webkit-scrollbar-thumb {
|
|
45
|
+
@apply bg-gray-300 rounded-full;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
::-webkit-scrollbar-thumb:hover {
|
|
49
|
+
@apply bg-gray-400;
|
|
50
|
+
}
|
|
51
|
+
|