Compare commits

..

112 Commits

Author SHA1 Message Date
e86966062a update loyalty discount calculations 2026-01-17 21:22:24 +03:00
3fe0c68526 clean code 2026-01-17 20:03:50 +03:00
5cb681c4a8 apply call waiter api 2026-01-17 12:14:19 +03:00
f97b83062c apply restaurant decimails formating & fix tax calcualtions 2026-01-17 11:44:04 +03:00
69425580d6 apply "thawani" way payment 2026-01-17 11:05:00 +03:00
8083e9ec96 apply items validation and fix taxes list viewing 2026-01-17 10:04:34 +03:00
4dfa08d26c room number value sending 2026-01-15 14:28:00 +03:00
788b05d6f4 enhance openening times UI styles 2026-01-15 11:51:19 +03:00
0ce2d320a8 intergrate openening times & show extras 2026-01-15 11:43:36 +03:00
046773cb8b fixes 2026-01-15 11:08:54 +03:00
e50d2dfd4c delivery: fix map updating and enhance UI 2026-01-15 07:49:58 +03:00
ab5867b0cb implment send amount gift api 2026-01-15 07:22:48 +03:00
68a12a4796 fix time range 2026-01-15 06:36:21 +03:00
5d08498f8c transaltiosn & add clear coupon discount 2026-01-15 06:34:20 +03:00
d0de05cfb0 Revert "If token exists: browser back navigates to /${subdomain}"
This reverts commit 093d4279d9.
2026-01-15 06:26:50 +03:00
8a4e6691d4 When a user with a token navigates to /:subdomain/login or /:subdomain/otp, they are redirected to /${subdomain}. The redirect uses replace to avoid adding a history entry. 2026-01-15 06:26:35 +03:00
adc98200cb room & office number fix sending 2026-01-15 06:22:09 +03:00
26a16fa8d7 remove title 2026-01-15 06:15:56 +03:00
093d4279d9 If token exists: browser back navigates to /${subdomain} 2026-01-15 06:15:48 +03:00
2c676eb153 update tax in summaries 2026-01-14 23:21:02 +03:00
6f1f4441f1 done btn logic 2026-01-14 23:07:27 +03:00
7b6fe140ad increase time & show extra 2026-01-14 22:53:11 +03:00
44e2730428 fix price format (default oman price) 2026-01-14 22:33:07 +03:00
aaef1bc11b order: stop polling when the order has "closed" or "canceled_by_customer" status. 2026-01-14 22:15:04 +03:00
ba350e877f handle cancel case in order stepper 2026-01-14 22:12:14 +03:00
a68480c075 hide unnecessary elements 2026-01-14 21:59:02 +03:00
ce68b8b978 fixes 2026-01-14 21:30:32 +03:00
3a72b8e933 fix transations 2026-01-14 18:53:09 +03:00
9d061ae8a6 fix cards 2026-01-14 18:34:00 +03:00
a232fdabe2 show data in cards 2026-01-14 17:37:37 +03:00
b71330745e Merge remote-tracking branch 'origin/main' 2026-01-14 17:37:15 +03:00
4fb03a287e translations & don't open product details BM if restaurant is closed 2026-01-14 17:00:25 +03:00
b84d0646af Merge remote-tracking branch 'origin/main' 2026-01-14 15:57:39 +03:00
179bca3e9f hide cancel order btn open closing order 2026-01-14 15:56:40 +03:00
e87c3e0783 calcualte tax before sending it 2026-01-14 15:56:16 +03:00
bd556a5ef7 Merge remote-tracking branch 'origin/main' 2026-01-14 15:47:03 +03:00
6bf4cb6399 hide tip temprorary 2026-01-14 15:37:04 +03:00
a0e558d416 cart: add confirmation popup for clear btn 2026-01-14 15:35:58 +03:00
4ed288099c remove unused 2026-01-14 15:34:51 +03:00
41605781f4 menu: make back btn navigate to service page if token existed 2026-01-14 15:28:39 +03:00
a337ee11e2 clear cart on logout 2026-01-14 15:28:02 +03:00
e95411460f extra & variant fixes 2026-01-14 14:36:32 +03:00
5c7e64b17c fix translation 2026-01-14 00:16:03 +03:00
2c7a8d369a loyalty: enhance card texts 2026-01-14 00:09:49 +03:00
eb6ca34162 loyalty: enhance card texts 2026-01-14 00:00:53 +03:00
1d9ae7190e enhance loyalty section UI 2026-01-13 23:09:37 +03:00
a06147dfa4 loyalty & reward: initial commit 2026-01-13 06:24:25 +03:00
69bc9d4dee menu drawer: update list 2026-01-13 04:33:06 +03:00
2b0516ad0d fix main dropdown styles 2026-01-12 16:20:59 +03:00
eed10e8faf fix adding 2026-01-12 11:05:43 +03:00
f988fd3a4e gift: enhance items flow 2026-01-12 00:18:55 +03:00
167b26e0b9 redeem: keep intergration 2026-01-11 23:44:46 +03:00
b0288ebcf6 redeem: integration 2026-01-11 15:25:45 +03:00
6271c14eff redeem: initial commit 2026-01-08 23:26:45 +03:00
ebe9928091 pickup time: intial commit 2026-01-06 22:41:50 +03:00
ad036d1e64 apply collect card 2026-01-06 20:56:41 +03:00
14c36518cc worling on add car bottom sheet 2026-01-06 09:03:51 +03:00
20ef4f416c working on car card & car view BS 2026-01-05 23:30:03 +03:00
c8bf8ff621 working on tip card and BS 2026-01-05 20:00:54 +03:00
ab265bf09a CouponBottomSheet: ehancements 2026-01-05 15:37:05 +03:00
8563d90e8f update coupon code 2026-01-05 08:50:11 +03:00
3ab162ee5c fix vat and taxes sending 2026-01-04 08:38:17 +03:00
102415fe8b fixes
- clean coupon on success order
- in menu on clicking on the sticky categories make scroll jump to the category title postion exactly
2026-01-04 07:46:21 +03:00
f294138d30 fixes
- change refresh icon & apply refreshing logic
- apply validation in action btn in menu
- preserve on customer info state upon refresh
2026-01-04 07:00:56 +03:00
13cce2f12f show qty 2026-01-02 06:25:57 +03:00
1c5c212574 add split bill participants BS 2026-01-02 06:14:44 +03:00
b33bae562d order: fixing 2026-01-02 05:28:46 +03:00
f1ae1568a8 enhacnements 2026-01-02 05:18:09 +03:00
d337eb4220 fix cards place items 2026-01-01 16:18:46 +03:00
78f1227a3d gift: enhacnements 2026-01-01 16:16:25 +03:00
e5b86d09a3 add transaltions 2026-01-01 14:58:59 +03:00
ab0a4219a2 add card details validation 2026-01-01 14:56:21 +03:00
527f4686de enhance E-Amount card 2026-01-01 14:52:40 +03:00
5e209d5d78 enhance brief menu card styles 2026-01-01 13:56:05 +03:00
7d2925eeae enhance card raduis & order 2026-01-01 13:38:06 +03:00
6933e1ba3a remove extra title 2026-01-01 12:32:50 +03:00
a6abb089d2 YouMightAlsoLike: enhnace UI + styles 2026-01-01 12:14:02 +03:00
3b0b8ceab6 checkout: add card deyails & card amount BT 2025-12-31 23:49:04 +03:00
1d2c1fda87 order: enhance UI & styles 2025-12-31 17:46:46 +03:00
2416b37069 preserve cart & checkout elment on refersh 2025-12-31 16:23:37 +03:00
71e1d71c96 gift: working on UI ans styles 2025-12-31 14:28:11 +03:00
38c83d5143 add order details & enhance the checkout page 2025-12-31 00:57:05 +03:00
7119ead8c2 cart: enhancements 2025-12-31 00:10:09 +03:00
5d523e2508 checkout: UI + styles enhacnements 2025-12-30 23:50:42 +03:00
3ed5c4d5d6 gift: update UI and voucher & items BS 2025-12-30 11:09:52 +03:00
f3b8bdba63 add read me file 2025-12-30 09:34:37 +03:00
e8c4bc909c general updates 2025-12-30 03:35:14 +03:00
dd11665743 menu: enhnace styles 2025-12-30 01:39:58 +03:00
a64f92d1fd add rate feature to orders page & enhance menu page 2025-12-30 01:22:49 +03:00
0edd13f2fb orders: activate rate feature 2025-12-30 00:45:36 +03:00
e9dc1aa580 orders details: initial commit 2025-12-30 00:01:12 +03:00
d77047fdb2 QR: update UI and styles 2025-12-28 10:52:55 +03:00
85636630e3 update footer BS height & enhnace custom split bill BS styles 2025-12-28 10:34:13 +03:00
b261f3508f split bill: reorder flow 2025-12-26 00:25:26 +03:00
86c12e2c53 enhnace styles 2025-12-26 00:00:29 +03:00
a87d1d7716 update logic 2025-12-25 23:51:01 +03:00
4bf18087cc clean code 2025-12-25 23:39:45 +03:00
611f26f6ff OrderTypesBottomSheet: add change order type logic 2025-12-25 22:52:57 +03:00
a2f8b943a1 OrderTypesBottomSheet: initial commit 2025-12-25 22:48:01 +03:00
ce9092d634 cart & checkout: UI enhacnements 2025-12-25 21:13:25 +03:00
90e729cdce implemt QRBottomSheet 2025-12-25 00:35:44 +03:00
f35cf0bd3a add qr bottom sheet 2025-12-25 00:35:32 +03:00
a98d1b7790 cart: fix description text height 2025-12-24 23:08:30 +03:00
c84168cec5 checkout: keep enhacning 2025-12-24 23:02:26 +03:00
567930e27e tmp 2025-12-24 22:53:36 +03:00
19212860c3 checkout: reafactor UI 2025-12-24 22:34:11 +03:00
66d77d0621 menu: enhancements 2025-12-24 21:13:07 +03:00
898f570a40 menu: fixes 2025-12-24 15:58:31 +03:00
ee42afacf3 ProBottomSheet: enhance UI 2025-12-23 23:44:54 +03:00
5c27303695 menu: on click on prduct open details as BS, also add redirect button to detials page if user click on redirect button 2025-12-23 01:36:46 +03:00
fc1a75bc4b menu: UI enhancements 2025-12-22 23:49:44 +03:00
f580653ef2 menu: enhance styles 2025-12-21 23:00:41 +03:00
212 changed files with 13515 additions and 3654 deletions

596
README.md
View File

@@ -1 +1,595 @@
# Fascano # Web Menu React Application
A comprehensive restaurant ordering system built with React, TypeScript, and Ant Design. This application enables customers to browse menus, place orders, manage carts, split bills, and track orders across multiple restaurant services including dine-in, delivery, pickup, room service, office delivery, and gift orders.
## 🚀 Tech Stack
- **Framework**: React 18.3.1 with TypeScript
- **UI Library**: Ant Design 6.1.1
- **State Management**: Redux Toolkit 2.2.6
- **Routing**: React Router DOM 6.24.1 (Hash Router)
- **Internationalization**: i18next & react-i18next (English/Arabic with RTL support)
- **Date/Time**: dayjs
- **Build Tool**: Vite 5.3.1
- **Maps**: Google Maps API (@googlemaps/react-wrapper)
## 📁 Project Structure
```
src/
├── pages/ # Application pages
├── components/ # Reusable components
├── layouts/ # Layout components
├── routes/ # Routing configuration
├── redux/ # Redux store, slices, and API
├── contexts/ # React contexts
├── hooks/ # Custom React hooks
├── utils/ # Utility functions and constants
├── assets/ # Static assets (images, locales)
├── i18n/ # Internationalization setup
└── ThemeConstants.ts # Theme configuration
```
## 📄 Main Pages
### 1. **Restaurant Page** (`/pages/restaurant/page.tsx`)
- **Route**: `/:subdomain`
- **Purpose**: Landing page for each restaurant
- **Features**:
- Displays restaurant information and services
- Shows available order types (dine-in, delivery, pickup, etc.)
- Restaurant branding and promotions
### 2. **Menu Page** (`/pages/menu/page.tsx`)
- **Route**: `/:subdomain/menu`
- **Purpose**: Browse restaurant menu with categories and products
- **Features**:
- Category-based navigation
- Product cards with images and prices
- Search functionality
- Add to cart integration
- Order type selection (dine-in, delivery, pickup, etc.)
- Responsive design (mobile/desktop)
### 3. **Product Detail Page** (`/pages/product/page.tsx`)
- **Route**: `/:subdomain/product/:productId`
- **Purpose**: Detailed view of individual products
- **Features**:
- Product information and images
- Variant selection (sizes, options)
- Extra items and add-ons
- Quantity selection
- Add to cart functionality
- Customization options
### 4. **Cart Page** (`/pages/cart/page.tsx`)
- **Route**: `/:subdomain/cart`
- **Purpose**: Shopping cart management
- **Features**:
- View cart items with quantities
- Modify quantities or remove items
- Special requests input
- Table number selection (dine-in)
- Car plate input (pickup)
- Date/time selection for scheduled orders
- Coupon application
- Loyalty points usage
- Order summary with totals
### 5. **Checkout Page** (`/pages/checkout/page.tsx`)
- **Route**: `/:subdomain/checkout`
- **Purpose**: Order review and customer information
- **Features**:
- Customer information form
- Address selection/input
- Room/Office details (for room service/office delivery)
- Gift details (for gift orders)
- Payment method selection
- Order summary
- Place order functionality
### 6. **Pay Page** (`/pages/pay/page.tsx`)
- **Route**: `/:subdomain/pay`
- **Purpose**: Payment and bill splitting
- **Features**:
- Payment processing
- Split bill options:
- Custom amount
- Equal split
- Pay for specific items
- QR code generation for split payments
- Payment summary
### 7. **Split Bill Page** (`/pages/split-bill/page.tsx`)
- **Route**: `/:subdomain/split-bill`
- **Purpose**: Manage split bill payments
- **Features**:
- Total people count
- Individual payment tracking
- Payment summary per person
- QR code sharing
### 8. **Order Page** (`/pages/order/page.tsx`)
- **Route**: `/:subdomain/order/:orderId`
- **Purpose**: Active order tracking
- **Features**:
- Order status stepper
- Real-time order updates
- Countdown timer for preparation time (when in progress)
- Circular progress indicator
- Order details and items
- Cancel order option
### 9. **Orders List Page** (`/pages/orders/page.tsx`)
- **Route**: `/:subdomain/orders/:orderId?`
- **Purpose**: View order history
- **Features**:
- List of past orders
- Order status badges (completed, closed, preparing)
- Order details view
- Re-order functionality
- Rate order functionality
- Protected route (requires authentication)
### 10. **Order Details Page** (`/pages/orders/OrderDetails.tsx`)
- **Purpose**: Detailed view of a specific order
- **Features**:
- Order information (ID, date, restaurant)
- Status badge with color coding:
- **Closed**: Red background (`#EA1F221F`)
- **Completed**: Green background (`#28A7451F`)
- **Preparing**: Yellow background (`#FFB7001F`)
- Order items list
- Payment details
- Rate order button
- Re-order button
### 11. **Search Page** (`/pages/search/page.tsx`)
- **Route**: `/:subdomain/search`
- **Purpose**: Search products across the menu
- **Features**:
- Product search functionality
- Search results display
- Filter options
### 12. **Address Page** (`/pages/address/page.tsx`)
- **Route**: `/:subdomain/address`
- **Purpose**: Manage delivery addresses
- **Features**:
- Address input/selection
- Google Maps integration
- Address validation
### 13. **Login Page** (`/pages/login/page.tsx`)
- **Route**: `/:subdomain/login`
- **Purpose**: User authentication
- **Features**:
- Phone number input
- OTP verification flow
### 14. **OTP Page** (`/pages/otp/page.tsx`)
- **Route**: `/:subdomain/otp`
- **Purpose**: OTP verification
- **Features**:
- OTP input component
- Resend OTP functionality
- Verification handling
### 15. **Redeem Page** (`/pages/redeem/page.tsx`)
- **Route**: `/:subdomain/gift/redeem/:voucherId`
- **Purpose**: Redeem gift vouchers
- **Features**:
- Gift voucher redemption
- Voucher validation
### 16. **Error Pages** (`/pages/errors/`)
- **Routes**: Various error routes
- **Purpose**: Handle application errors
- **Pages**:
- `Error400.tsx` - Bad Request
- `Error403.tsx` - Forbidden
- `Error404.tsx` - Not Found
- `Error500.tsx` - Server Error
- `Error503.tsx` - Service Unavailable
## 🧩 Main Components
### Layout Components
#### **AppLayout** (`/layouts/app/AppLayout.tsx`)
- Main application layout wrapper
- Provides consistent structure across pages
#### **HeaderMenuDrawer** (`/layouts/app/HeaderMenuDrawer.tsx`)
- Responsive header navigation
- Mobile drawer menu
- Language and theme switchers
#### **FooterNav** (`/layouts/app/FooterNav.tsx`)
- Bottom navigation bar
- Quick access to main sections
### UI Components
#### **ProBottomSheet** (`/components/ProBottomSheet/ProBottomSheet.tsx`)
- Custom bottom sheet component
- Draggable and dismissible
- Prevents body scroll when open
- Snap points support
- Used throughout the app for modals and forms
#### **ProText** (`/components/ProText.tsx`)
- Custom text component with theme support
- Typography consistency
#### **ProTitle** (`/components/ProTitle.tsx`)
- Custom title component
- Consistent heading styles
#### **ProInputCard** (`/components/ProInputCard/ProInputCard.tsx`)
- Card wrapper for input sections
- Consistent styling for form sections
#### **ArabicPrice** (`/components/ArabicPrice/ArabicPrice.tsx`)
- Price display component
- Supports RTL formatting
- Currency formatting
#### **PaymentDetails** (`/components/PaymentDetails/PaymentDetails.tsx`)
- Displays order payment breakdown
- Shows subtotal, taxes, fees, discounts
- Final total calculation
#### **PaymentMethods** (`/components/PaymentMethods/PaymentMethods.tsx`)
- Payment method selection
- Multiple payment options
#### **OrderSummary** (`/components/OrderSummary/OrderSummary.tsx`)
- Order items summary
- Quantity and price display
### Bottom Sheet Components
#### **OrderTypesBottomSheet** (`/components/CustomBottomSheet/OrderTypesBottomSheet.tsx`)
- Select order type (dine-in, delivery, pickup, etc.)
- Dynamic height based on available services
- Service availability handling
#### **TipBottomSheet** (`/components/CustomBottomSheet/TipBottomSheet.tsx`)
- Add tip to order
- Percentage or custom amount
#### **CouponBottomSheet** (`/components/CustomBottomSheet/CouponBottomSheet.tsx`)
- Apply coupon codes
- Discount application
#### **DatePickerBottomSheet** (`/components/CustomBottomSheet/DatePickerBottomSheet.tsx`)
- Date selection for scheduled orders
- Time picker integration
#### **RateBottomSheet** (`/components/CustomBottomSheet/RateBottomSheet.tsx`)
- Rate completed orders
- Star rating system
- Feedback submission
#### **CancelOrderBottomSheet** (`/components/CustomBottomSheet/CancelOrderBottomSheet.tsx`)
- Cancel active orders
- Confirmation dialog
#### **MapBottomSheet** (`/components/CustomBottomSheet/MapBottomSheet.tsx`)
- Google Maps integration
- Location selection
- Address input
#### **OpeningTimesBottomSheet** (`/components/CustomBottomSheet/OpeningTimesBottomSheet.tsx`)
- Display restaurant opening hours
- Operating schedule
### Split Bill Components
#### **CustomAmountChoiceBottomSheet** (`/pages/pay/components/splitBill/CustomAmountChoiceBottomSheet.tsx`)
- Custom amount input for split bills
- Real-time preview of totals
- Debounced input (300ms)
- Service fee display
#### **EqualltyChoiceBottomSheet** (`/pages/pay/components/splitBill/EqualltyChoiceBottomSheet.tsx`)
- Equal split calculation
- Interactive spinner wheel (donut chart)
- Total amount display in center
#### **PayForYourItemsChoiceBottomSheet** (`/pages/pay/components/splitBill/PayForYourItemsChoiceBottomSheet.tsx`)
- Select specific items to pay for
- Circular checkbox selection
- Item-based payment calculation
#### **QRBottomSheet** (`/pages/pay/components/splitBill/QRBottomSheet.tsx`)
- QR code generation for split payments
- Share payment link
### Menu Components
#### **CategoriesList** (`/pages/menu/components/CategoriesList/CategoriesList.tsx`)
- Category navigation
- Sticky header on scroll
- Active category highlighting
#### **MenuList** (`/pages/menu/components/MenuList/MenuList.tsx`)
- Product list display
- Category filtering
- Infinite scroll support
#### **ProductCard** (`/pages/menu/components/MenuList/ProductCard.tsx`)
- Individual product card
- Image, name, price display
- "Customizable" label
- RTL support
#### **AddToCartButton** (`/pages/menu/components/AddToCartButton/AddToCartButton.tsx`)
- Add product to cart
- Quantity selection
- Multi-layer box shadow styling
#### **MenuFooter** (`/pages/menu/components/MenuFooter/MenuFooter.tsx`)
- Fixed footer with cart summary
- Navigate to cart/pay
- Disabled state when cart is empty
- Warning message for empty cart
#### **ProductPreviewDialog** (`/pages/menu/components/ProductPreviewDialog/ProductPreviewDialog.tsx`)
- Quick product preview
- Modal dialog display
### Cart Components
#### **CartFooter** (`/pages/cart/components/cartFooter/CartFooter.tsx`)
- Cart summary footer
- Proceed to checkout button
#### **SpecialRequestCard** (`/pages/cart/components/specialRequest/SpecialRequestCard.tsx`)
- Special instructions input
- Bottom sheet for detailed input
#### **TimeEstimateCard** (`/pages/cart/components/timeEstimate/TimeEstimateCard.tsx`)
- Estimated preparation time
- Date/time picker
#### **CouponCard** (`/pages/cart/components/CouponCard.tsx`)
- Coupon code input
- Discount display
#### **TableNumberCard** (`/pages/cart/components/TableNumberCard.tsx`)
- Table number selection (dine-in)
#### **CarPlateCard** (`/pages/cart/components/CarPlateCard.tsx`)
- Car plate input (pickup)
### Form Components
#### **ProInputNumber** (`/components/Inputs/ProInputNumber.tsx`)
- Custom number input
- Consistent styling
#### **ProPhoneInput** (`/components/ProPhoneInput.tsx`)
- Phone number input with country code
- Validation
#### **ProDatePicker** (`/components/ProDatePicker/ProDatePicker.tsx`)
- Date selection component
- Time picker integration
#### **ProCheckboxGroups** (`/components/ProCheckboxGroups/ProCheckboxGroups.tsx`)
- Checkbox group component
- Multiple selection handling
#### **ProRatioGroups** (`/components/ProRatioGroups/ProRatioGroups.tsx`)
- Radio button group
- Single selection handling
#### **OtpInput** (`/components/OtpInput/OtpInput.tsx`)
- OTP code input
- Multi-digit input fields
### Icon Components (`/components/Icons/`)
- Comprehensive icon library
- Custom SVG icons for:
- Order types (dine-in, delivery, pickup, etc.)
- Cart, payment, location
- Social media
- Navigation (back, next, etc.)
### Utility Components
#### **ImageWithFallback** (`/components/ImageWithFallback/ImageWithFallback.tsx`)
- Image component with fallback
- Error handling
#### **ImagePreloader** (`/components/ImagePreloader/ImagePreloader.tsx`)
- Preload images for better performance
#### **Loader** (`/components/Loader/Loader.tsx`)
- Loading spinner
- Full-page loading state
#### **LoadingSpinner** (`/components/LoadingSpinner/LoadingSpinner.tsx`)
- Reusable spinner component
#### **ErrorBoundaries** (`/components/ErrorBoundaries/ErrorBoundaries.tsx`)
- Error boundary wrapper
- Error handling
#### **AccessDenied** (`/components/AccessDenied/AccessDenied.tsx`)
- Access denied page
- Permission handling
#### **LanguageSwitch** (`/components/LanguageSwitch/LanguageSwitch.tsx`)
- Language switcher
- English/Arabic toggle
#### **ThemeSwitch** (`/components/ThemeSwitch/ThemeSwitch.tsx`)
- Theme switcher
- Light/Dark mode toggle
#### **FloatingButton** (`/components/FloatingButton/FloatingButton.tsx`)
- Floating action button
- Scroll to top functionality
#### **WheelPicker** (`/components/WheelPicker/`)
- Custom wheel picker component
- Used for time/date selection
## 🎨 Key Features
### 1. **Multi-Service Ordering**
- Dine-in orders
- Delivery orders
- Pickup orders
- Room service
- Office delivery
- Gift orders
- Scheduled orders
- Table booking
### 2. **Bill Splitting**
- Custom amount splitting
- Equal split among multiple people
- Pay for specific items
- QR code sharing for split payments
### 3. **Real-Time Order Tracking**
- Order status stepper
- Countdown timer for preparation
- Circular progress indicator
- Status updates
### 4. **Internationalization**
- English and Arabic support
- RTL (Right-to-Left) layout support
- Dynamic language switching
### 5. **Theme Support**
- Light and Dark themes
- Theme persistence
- CSS custom properties
### 6. **Responsive Design**
- Mobile-first approach
- Tablet and desktop support
- Adaptive layouts
- Touch-friendly interactions
### 7. **Cart Management**
- Add/remove items
- Quantity modification
- Special requests
- Coupon application
- Loyalty points integration
### 8. **Payment Integration**
- Multiple payment methods
- Payment summary
- Split bill support
### 9. **User Authentication**
- Phone number login
- OTP verification
- Protected routes
### 10. **Order History**
- View past orders
- Order details
- Re-order functionality
- Rate orders
## 🔧 Development
### Prerequisites
- Node.js 18+
- npm or yarn
### Installation
```bash
npm install
```
### Development Server
```bash
npm run dev
```
### Build
```bash
npm run build
```
### Preview Production Build
```bash
npm run preview
```
### Linting
```bash
npm run lint
```
## 📱 Routes Overview
- `/` - Restaurant landing page
- `/:subdomain` - Restaurant home
- `/:subdomain/menu` - Menu browsing
- `/:subdomain/product/:productId` - Product details
- `/:subdomain/cart` - Shopping cart
- `/:subdomain/checkout` - Checkout
- `/:subdomain/pay` - Payment
- `/:subdomain/split-bill` - Split bill management
- `/:subdomain/order/:orderId` - Active order tracking
- `/:subdomain/orders/:orderId?` - Order history
- `/:subdomain/search` - Product search
- `/:subdomain/address` - Address management
- `/:subdomain/login` - Login
- `/:subdomain/otp` - OTP verification
- `/:subdomain/gift/redeem/:voucherId` - Gift redemption
## 🎯 State Management
The application uses Redux Toolkit for state management with the following main slices:
- **Cart/Order State**: Manages cart items, order type, special requests, etc.
- **Theme State**: Manages light/dark theme
- **Locale State**: Manages language and RTL direction
- **API State**: RTK Query for API calls
## 🌐 API Integration
The application uses RTK Query for API integration:
- Restaurant details
- Menu data
- Order creation and tracking
- User authentication
- Payment processing
## 📝 Notes
- All footer buttons across bottom sheets and pages have a standardized height of **48px**
- The application uses hash routing for better compatibility
- Custom bottom sheets prevent body scrolling when open
- Debounced inputs are used for better performance (e.g., 300ms for custom amount input)
- The app supports both mobile and desktop views with responsive breakpoints
## 🔐 Authentication
Some routes are protected using the `PrivateRoute` component:
- Order history (`/orders`)
- Error pages requiring authentication
## 🎨 Styling
- CSS Modules for component-specific styles
- Global CSS for theme variables
- Ant Design theme customization
- Custom CSS properties for theming
- RTL support with dynamic direction switching
---
**Built with ❤️ using React, TypeScript, and Ant Design**

View File

@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/me.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Fascano | Restaurant</title> <title>Fascano | Restaurant</title>
</head> </head>

View File

@@ -4,7 +4,7 @@
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite --open", "dev": "vite",
"build": "vite build", "build": "vite build",
"production": "vite --mode production --open", "production": "vite --mode production --open",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
@@ -37,6 +37,7 @@
"@typescript-eslint/eslint-plugin": "^7.13.1", "@typescript-eslint/eslint-plugin": "^7.13.1",
"@typescript-eslint/parser": "^7.13.1", "@typescript-eslint/parser": "^7.13.1",
"@vitejs/plugin-react": "^4.3.1", "@vitejs/plugin-react": "^4.3.1",
"baseline-browser-mapping": "^2.9.11",
"eslint": "^8.57.0", "eslint": "^8.57.0",
"eslint-plugin-react-hooks": "^4.6.2", "eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7", "eslint-plugin-react-refresh": "^0.4.7",

BIN
public/thawani.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -2,12 +2,12 @@
import type { ThemeConfig } from "antd"; import type { ThemeConfig } from "antd";
export const colors = { export const colors = {
primary: "#FFC600", primary: "#FFB700",
secondary: "linear-gradient(135deg, #00C1D4 0%, #FF5F6D 100%)", secondary: "linear-gradient(135deg, #00C1D4 0%, #FF5F6D 100%)",
darkSpace: "linear-gradient(to right, #0F0525, #2A0B45)", darkSpace: "linear-gradient(to right, #0F0525, #2A0B45)",
}; };
export const ProGray1 = "#8a8a8a"; export const ProGray1 = "#434E5C";
export const ProGreen = "#03B100"; export const ProGreen = "#03B100";
export const ProGreen2 = "#52c41a"; export const ProGreen2 = "#52c41a";
export const ProDraft = "#f36c00"; export const ProDraft = "#f36c00";
@@ -108,6 +108,7 @@ export const SharedThemeVars = {
colorPrimary: colors.primary, colorPrimary: colors.primary,
fontFamily: "var(--font-roboto)", fontFamily: "var(--font-roboto)",
colorLink: colors.primary, colorLink: colors.primary,
lineHeight: "140%"
// colorItemTextSelected: colors.primary // Menu Item Active Item Color // colorItemTextSelected: colors.primary // Menu Item Active Item Color
}; };
@@ -174,8 +175,8 @@ export const themeConfig: ThemeConfig = {
boxShadow: "0 1px 3px rgba(0,0,0,0.1)", boxShadow: "0 1px 3px rgba(0,0,0,0.1)",
padding: 16, padding: 16,
paddingLG: 16, paddingLG: 16,
borderRadius: 24, borderRadius: 16,
borderRadiusLG: 24, borderRadiusLG: 16,
}, },
Carousel: { Carousel: {
colorBgContainer: lightColors["800"], colorBgContainer: lightColors["800"],

View File

@@ -76,7 +76,8 @@
"room": "الغرفة", "room": "الغرفة",
"office": "المكتب", "office": "المكتب",
"booking": "الحجز", "booking": "الحجز",
"scheduledOrder": "الطلب المجدول" "scheduledOrder": "الطلب المجدول",
"rewardsAndLoyalty": "المكافأة واللايفور"
}, },
"home": { "home": {
"title": "العنوان", "title": "العنوان",
@@ -91,7 +92,7 @@
"scheduledOrder": "طلب مجدول" "scheduledOrder": "طلب مجدول"
}, },
"promotion": { "promotion": {
"title": "الترويجات", "title": "عرض التفاصيل",
"description": "احصل على خصم 10% على طلبك الأول" "description": "احصل على خصم 10% على طلبك الأول"
} }
}, },
@@ -108,6 +109,7 @@
"email_label": "البريد الإلكتروني" "email_label": "البريد الإلكتروني"
}, },
"menu": { "menu": {
"orderTypes": "طرق الطلب",
"meal": "الوجبة", "meal": "الوجبة",
"title": "القائمة", "title": "القائمة",
"ourMenu": "قائمتنا", "ourMenu": "قائمتنا",
@@ -128,7 +130,7 @@
"payDescription": "الدفع", "payDescription": "الدفع",
"rating": "التقييم ", "rating": "التقييم ",
"loyaltyPoints": "نقاط الولاء", "loyaltyPoints": "نقاط الولاء",
"loyaltyDescription": "اشترى {{value}} وجبات واحصل على وجبة مجانية", "loyaltyDescription": "اشترى {{value}} وجبات واحصل على وجبة مجانية!",
"youMightAlsoLike": "قد تعجبك أيضاً..", "youMightAlsoLike": "قد تعجبك أيضاً..",
"choose1": "اختر 1", "choose1": "اختر 1",
"specialRequest": "طلب خاص", "specialRequest": "طلب خاص",
@@ -162,9 +164,29 @@
"openingHours": "ساعات العمل: {{openingTime}} - {{closingTime}}", "openingHours": "ساعات العمل: {{openingTime}} - {{closingTime}}",
"restaurantIsClosed": "المطعم مغلق", "restaurantIsClosed": "المطعم مغلق",
"address": "العنوان", "address": "العنوان",
"openingTimes": "ساعات العمل" "openingTimes": "ساعات العمل",
"customizable": "قابل للتخصيص",
"youHaveXEarnedRewardsReadyToRedeem": "🎉 لديك {{rewards}} مكافأة مستحقة للاستخدام!",
"justXMorePurchasesToUnlockYourFREEItem": "فقط {{cups}} أكثر للفتح الوجبة المجانية!",
"youreJustXCupsAwayFromYourNextReward": "🎉 أنت فقط {{cups}} أكثر للحصول على المكافأة التالية!",
"callWaiter": "اتصل بالرادير",
"balance": "الرصيد",
"closed": "مغلق",
"sunday": "الأحد",
"monday": "الإثنين",
"tuesday": "الثلاثاء",
"wednesday": "الأربعاء",
"thursday": "الخميس",
"friday": "الجمعة",
"saturday": "السبت",
"selectYourTable": "اختر طاولتك",
"calledWaiterSuccess": "تم اتصال الرادير بنجاح"
}, },
"cart": { "cart": {
"addSpecialRequestOptional": "إضافة طلب خاص (اختياري)",
"continueToGiftDetails": "متابعة إلى تفاصيل الهدية",
"continueToGiftDetailsDescription": "يرجى إدخال تفاصيل المستلم والمرسل لمتابعة.",
"leaveANoteHere": "أي تعليمات خاصة لطلبك..",
"title": "السلة", "title": "السلة",
"emptyCart": "سلة المشتريات فارغة", "emptyCart": "سلة المشتريات فارغة",
"emptyCartMessage": "يبدو أنك لم تضيف أي عناصر إلى سلة المشتريات بعد. ابدأ في استكشاف قائمتنا للعثور على وجبات لذيذة!", "emptyCartMessage": "يبدو أنك لم تضيف أي عناصر إلى سلة المشتريات بعد. ابدأ في استكشاف قائمتنا للعثور على وجبات لذيذة!",
@@ -186,6 +208,12 @@
"cancel": "إلغاء", "cancel": "إلغاء",
"success": "تم حذف العنصر من سلة المشتريات" "success": "تم حذف العنصر من سلة المشتريات"
}, },
"clearCartConfirmation": {
"title": "مسح السلة",
"content": "هل أنت متأكد أنك تريد مسح السلة؟",
"confirm": "مسح",
"cancel": "إلغاء"
},
"quantity": "الكمية", "quantity": "الكمية",
"price": "السعر", "price": "السعر",
"perItem": "للقطعة", "perItem": "للقطعة",
@@ -259,9 +287,22 @@
"am": "ص", "am": "ص",
"pm": "م", "pm": "م",
"cannotSelectPastDate": "لا يمكنك اختيار تاريخ سابق. يرجى اختيار اليوم أو تاريخ مستقبلي.", "cannotSelectPastDate": "لا يمكنك اختيار تاريخ سابق. يرجى اختيار اليوم أو تاريخ مستقبلي.",
"checkRequiredFields": "يرجى التحقق من الحقول المطلوبة" "checkRequiredFields": "يرجى التحقق من الحقول المطلوبة",
"loyalty": "ولاء",
"vat": "ضريبة القيمة المضافة ({{value}}%))",
"otherTaxes": "{{name}} ({{value}}%)"
}, },
"checkout": { "checkout": {
"addCarDetails": "إضافة تفاصيل السيارة",
"soTheRestaurantCanRecognizeYourCarWhenYouArrive": "لتتمكن المطعم من التعرف على سيارتك عند وصولك.",
"customerName": "اسم العميل",
"paymentSummary": "ملخص الدفع",
"holdayGiftCard": "هدية العيد",
"messageIncluded": "الرسالة مضمنة",
"save": "حفظ",
"to": "ل",
"giftSummary": "ملخص الهدية",
"customerInformation": "تفاصيل العميل",
"title": "الدفع", "title": "الدفع",
"cash": "كاش", "cash": "كاش",
"creditDebitCard": "بطاقة ائتمان/ائتمان", "creditDebitCard": "بطاقة ائتمان/ائتمان",
@@ -280,7 +321,23 @@
"expiresInDescription": "تنتهي في:12/26", "expiresInDescription": "تنتهي في:12/26",
"phoneNumber": "رقم الهاتف", "phoneNumber": "رقم الهاتف",
"pleaseSelectPaymentMethod": "يرجى اختيار طريقة الدفع", "pleaseSelectPaymentMethod": "يرجى اختيار طريقة الدفع",
"pleaseEnterPhoneNumber": "يرجى إدخال رقم الهاتف" "pleaseEnterPhoneNumber": "يرجى إدخال رقم الهاتف",
"viewOrder": "عرض الطلب",
"itemsSummary": "ملخص العناصر",
"removeSplitBill": "إزالة التقسيم",
"cardBalance": "رصيد البطاقة",
"collectAtCounter": "استلام في المكتب",
"collectAtParking": "استلام في الموقف",
"scheduled": "مجدول",
"pickupNow": "استلام الآن",
"pickupEstimate": "تقدير الاستلام",
"today": "اليوم",
"change": "تغيير",
"pickup": "استلام",
"setPickupTime": "تحديد وقت الاستلام",
"carPlateNumber": "رقم لوحة السيارة",
"noItems": "لا يوجد عناصر في السلة",
"thawani": "ثواني"
}, },
"address": { "address": {
"title": "العنوان", "title": "العنوان",
@@ -332,7 +389,9 @@
"gotIt": "فهمت", "gotIt": "فهمت",
"howItWorksDescription": "يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق.", "howItWorksDescription": "يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق. يمكنك إرسال هدية إلى أي شخص عبر التطبيق.",
"senderEmail": "البريد الإلكتروني المرسل", "senderEmail": "البريد الإلكتروني المرسل",
"save": "حفظ" "save": "حفظ",
"pleaseEnterRoomNumber": "يرجى إدخال رقم الغرفة",
"pleaseEnterOfficeNumber": "يرجى إدخال رقم المكتب"
}, },
"login": { "login": {
"singup/Login": "الدخول / التسجيل", "singup/Login": "الدخول / التسجيل",
@@ -361,7 +420,19 @@
"confirmOTPSuccess": "تم التحقق من رمز التحقق بنجاح" "confirmOTPSuccess": "تم التحقق من رمز التحقق بنجاح"
}, },
"order": { "order": {
"whosPaidTheirShareSplitBill": "من قام بدفع نصيبه من الفاتورة؟",
"seeWhoPaidAndHowMuch": "اعرض من قام بدفع نصيبه من الفاتورة وكم يبلغ نصيبه",
"personHasPaid": "قام بدفع نصيبه من الفاتورة",
"inviteOthersToBill": "دع الآخرين يدفعوا معك",
"title": "الطلب", "title": "الطلب",
"newOrder": "طلب جديد",
"paymentSummary": "ملخص الدفع",
"aStaffMemberWillCollectTheCashFromYouAtYourTable": "سيجمع موظف المطعم المال منك عند طاولتك",
"orderSummary": "ملخص الطلب",
"cashPaymentConfirmed": "تم الدفع بالكاش بنجاح",
"callWaiter": "اتصل بالخادم",
"yourOrder": "طلبك",
"done": "تم",
"yourOrderFromFascanoRestaurant": "طلبك من مطعم فاسكانو", "yourOrderFromFascanoRestaurant": "طلبك من مطعم فاسكانو",
"muscat": "مسقط", "muscat": "مسقط",
"reserved": "محجوز", "reserved": "محجوز",
@@ -380,7 +451,8 @@
"pleaseLoginToAllowRating": "يرجى تسجيل الدخول لتمكين التقييم", "pleaseLoginToAllowRating": "يرجى تسجيل الدخول لتمكين التقييم",
"remainingTime": "الوقت المتبقي", "remainingTime": "الوقت المتبقي",
"sec": "ثانية", "sec": "ثانية",
"min": "دقيقة" "min": "دقيقة",
"inviteToBill": "دع الجميع يدفعوا معك"
}, },
"orderTypes": { "orderTypes": {
"dine-in": "في المطعم", "dine-in": "في المطعم",
@@ -412,27 +484,142 @@
"payButtonDescription": "الدفع للطلب" "payButtonDescription": "الدفع للطلب"
}, },
"splitBill": { "splitBill": {
"serviceFee": "نصيب الخدمة",
"title": "تقسيم الفاتورة", "title": "تقسيم الفاتورة",
"description": "تقسيم الفاتورة", "description": "تقسيم الفاتورة",
"splitBill": "تقسيم الفاتورة", "splitBill": "تقسيم الفاتورة",
"splitBillDescription": "تقسيم الفاتورة", "splitBillDescription": "تقسيم الفاتورة",
"splitBillButton": "تقسيم الفاتورة", "customAmount": "مبلغ مخصص",
"divideEqually": "تقسيم الى حسب العدد",
"payForItems": "دفع للطلبات الخاصة بي",
"splitBillButtonDescription": "تقسيم الفاتورة", "splitBillButtonDescription": "تقسيم الفاتورة",
"payAsCustomAmount": "دفع كمبلغ مخصص",
"payAsSplitAmount": "دفع كمبلغ مقسم",
"divideTheBillEqually": "تقسيم الفاتورة الى حسب العدد",
"payForYourItems": "دفع للطلبات الخاصة بي",
"enterCustomAmount": "أدخل المبلغ المخصص", "enterCustomAmount": "أدخل المبلغ المخصص",
"amountPlaceholder": "0.00", "amountPlaceholder": "0.00",
"divisionPreview": "معاينة التقسيم", "divisionPreview": "معاينة التقسيم",
"yourAmount": "مبلغك", "yourAmount": "مبلغك",
"selectedTotal": "المجموع المحدد", "selectedTotal": "المجموع المحدد",
"splitBillAmount": "مبلغ تقسيم الفاتورة", "splitBillAmount": "مبلغ تقسيم الفاتورة",
"removeSplit": زالة التقسيم", "cancelSplit": لغاء التقسيم",
"amount": "المبلغ", "amount": "المبلغ",
"howMuchWouldYouLikeToPay": "كم مبلغ تريد دفعه؟", "howMuchWouldYouLikeToPay": "كم مبلغ تريد دفعه؟",
"confirm": "تأكيد", "confirm": "تأكيد",
"totalBill": "الفاتورة الكلية", "totalBill": "الفاتورة الكلية",
"remainingToPay": "المبلغ المتبقي" "remainingToPay": "المبلغ المتبقي",
"scanQRCodeToPay": "مسح الكود الباري الى الدفع",
"copyQRCode": "نسخ الكود الباري",
"payWithQR": "دفع باستخدام الكود الباري",
"cancel": "إلغاء",
"done": "تم",
"inviteEveryonePayingWithYou": "دع الجميع يدفعوا معك",
"includesAllOfTaxesCharges": "يشمل جميع الضرائب والرسوم",
"shareLink": "شارك الرابط",
"shareLinkDescription": "يمكن للأصدقاء مسح الكود الباري أو استخدام الرابط لعرض الفاتورة ودفع نصيبهم بأمان من هاتفهم.",
"shareThisToSplitTheBill": "شارك هذا لتقسيم الفاتورة"
},
"gift": {
"items": "هدية العناصر",
"balance": "هدية الرصيد",
"itemsAndBalance": "العناصر والرصيد",
"selectGiftType": "اختر نوع الهدية"
},
"eGiftCards": {
"title": "بطاقات الهدية",
"pickCardForYourGift": "اختر بطاقة لهديتك",
"chooseDesignToMatchTheOccasion": "اختر تصميم ليتناسب مع المناسبة. يمكنك إضافة ملاحظة لاحقاً."
},
"cardDetails": {
"title": "تفاصيل البطاقة",
"addGiftDetails": "أضف تفاصيل الهدية",
"description": "اضف تفاصيل الهدية بالرسالة، ووقت التوصيل، وتفاصيل المستلم.",
"checkout": "الدفع",
"yourName": "اسمك",
"yourPhone": "رقم هاتفك",
"keepMyNameSecret": "الاحتفاظ باسمي مخفياً",
"receiverInformation": "تفاصيل المستلم",
"costumeAmount": "مبلغ البطاقة",
"enterCustomOucherAmount": "أدخل مبلغ البطاقة المخصص",
"amount": "المبلغ",
"eCardAmount": "مبلغ البطاقة الإلكترونية",
"receiverName": "اسم المستلم",
"edit": "تعديل",
"yourInformation": "تفاصيلك",
"minimumAmountShouldBe1OMR": "يجب أن يكون المبلغ الأدنى 1 OMR",
"add": "أضف",
"senderNameRequired": "يجب أن يكون اسم المرسل مطلوب",
"receiverNameRequired": "يجب أن يكون اسم المستلم مطلوب"
},
"car": {
"addCar": "إضافة سيارة",
"selectCar": "اختر السيارة",
"addCarDetails": "إضافة تفاصيل السيارة",
"brand": "العلامة التجارية",
"color": "اللون",
"category": "الفئة",
"plateNumber": "رقم السيارة"
},
"redeem": {
"title": "استخدم الهدية",
"redeem": "استخدم",
"redeemDescription": "استخدم بطاقة الهدية",
"redeemButton": "استخدم",
"redeemButtonDescription": "استخدم بطاقة الهدية",
"showThisCodeAtTheRestaurant": "اعرض هذا الرمز في المطعم",
"addGiftDetails": "اضف تفاصيل الهدية",
"description": "اضف تفاصيل الهدية بالرسالة، ووقت التوصيل، وتفاصيل المستلم.",
"checkout": "الدفع",
"yourName": "اسمك",
"yourPhone": "رقم هاتفك",
"keepMyNameSecret": "الاحتفاظ باسمي مخفياً",
"receiverInformation": "تفاصيل المستلم",
"costumeAmount": "مبلغ البطاقة",
"enterCustomOucherAmount": "أدخل مبلغ البطاقة المخصص",
"amount": "المبلغ",
"eCardAmount": "مبلغ البطاقة الإلكترونية",
"receiverName": "اسم المستلم",
"edit": "تعديل",
"yourInformation": "تفاصيلك",
"minimumAmountShouldBe1OMR": "يجب أن يكون المبلغ الأدنى 1 OMR",
"add": "إضافة",
"senderNameRequired": "يجب أن يكون اسم المرسل مطلوب",
"receiverNameRequired": "يجب أن يكون اسم المستلم مطلوب",
"includesFreeItemsInThisOrder": "يشمل العناصر المجانية في هذا الطلب",
"redeemGiftedItems": "استخدم الهديات",
"pending": "قيد المعالجة",
"useThisCodeIfScanningNotPossible": "استخدم هذا الرمز إذا كان من المستحيل المسح",
"voucherWillBeAppliedAtCheckout": "سيتم تطبيق القسيمة عند الدفع",
"yourGiftCardBalance": "رصيد بطاقة الهدية",
"redeemNow": "استخدم الآن",
"restaurantLocation": "موقع المطعم",
"voucherBalance": "رصيد القسيمة",
"getDirections": "الحصول على الاتجاهات",
"giftedItems": "العناصر المهدية",
"viewAll": "عرض الكل",
"voucherCodeCopied": "تم نسخ رمز القسيمة",
"copyFailed": "فشل نسخ رمز القسيمة"
},
"rewardsAndLoyalty": {
"title": "المكافأة واللايفور",
"description": "المكافأة واللايفور",
"rewardsAndLoyalty": "المكافأة واللايفور",
"rewardsAndLoyaltyDescription": "المكافأة واللايفور",
"rewardsAndLoyaltyButton": "المكافأة واللايفور",
"rewardsAndLoyaltyButtonDescription": "المكافأة واللايفور",
"completedPurchases": "المشتريات المكتملة",
"totalPurchased": "المشتريات الكلية",
"saved": "المحفوظ",
"almosthere": "قريب جدا!",
"youreJustXCupsAwayFromYourNextReward": "أنت فقط {{cups}} أكواب بعيد عن المكافأة التالية!",
"youCanRedeemDuringTheCheckout": "يمكنك استخدام المكافأة أثناء الدفع",
"youCurrentlyHave": "لديك",
"freeItems": "عناصر مجانية",
"redeemNow": "استخدم الآن",
"yourAvailableRewards": "المكافأات المتاحة",
"loyaltyHistory": "سجل الولاء",
"loyaltyHistoryDescription": "سجل الولاء",
"earnedPoints": "حصل على {{points}} نقطة",
"order": "الطلب",
"yourOrderFrom": "طلبك من {{restaurantName}}",
"earned": "حصل على",
"xPoints": "{{points}} نقطة"
} }
} }

View File

@@ -76,7 +76,8 @@
"noMenuItemsAvailable": "No menu items available", "noMenuItemsAvailable": "No menu items available",
"restaurantCover": "Restaurant Cover", "restaurantCover": "Restaurant Cover",
"restaurantLogo": "Restaurant Logo", "restaurantLogo": "Restaurant Logo",
"scheduledOrder": "Scheduled Order" "scheduledOrder": "Scheduled Order",
"rewardsAndLoyalty": "Rewards And Loyalty"
}, },
"home": { "home": {
"title": "title", "title": "title",
@@ -107,7 +108,7 @@
"scheduledOrder": "Scheduled Order" "scheduledOrder": "Scheduled Order"
}, },
"promotion": { "promotion": {
"title": "Promotions", "title": "Show details",
"description": "Get 10% off your first order" "description": "Get 10% off your first order"
} }
}, },
@@ -124,6 +125,7 @@
"email_label": "Email" "email_label": "Email"
}, },
"menu": { "menu": {
"orderTypes": "Order Types",
"meal": "Meal", "meal": "Meal",
"title": "Menu", "title": "Menu",
"ourMenu": "Our Menu", "ourMenu": "Our Menu",
@@ -142,7 +144,7 @@
"close": "Close", "close": "Close",
"rating": "Rating ", "rating": "Rating ",
"loyaltyPoints": "Loyalty Points", "loyaltyPoints": "Loyalty Points",
"loyaltyDescription": "Buy {{value}} meals and get 1 FREE", "loyaltyDescription": "Buy {{value}} meals and get 1 FREE!",
"choose1": "Choose 1", "choose1": "Choose 1",
"youMightAlsoLike": "You might also like..", "youMightAlsoLike": "You might also like..",
"specialRequest": "Special Request", "specialRequest": "Special Request",
@@ -174,9 +176,35 @@
"pay": "Pay", "pay": "Pay",
"payDescription": "Pay for your order", "payDescription": "Pay for your order",
"address": "Address", "address": "Address",
"openingTimes": "Opening Times" "openingTimes": "Opening Times",
"customizable": "Customizable",
"youHaveXEarnedRewardsReadyToRedeem": "🎉 You have {{rewards}} rewards ready to redeem!",
"justXMorePurchasesToUnlockYourFREEItem": "Just {{cups}} more purchases to unlock your FREE item!",
"youreJustXCupsAwayFromYourNextReward": "🎉 You're just {{cups}} stamps away from your next reward!",
"callWaiter": "Call Waiter",
"balance": "Balance",
"closed": "Closed",
"sunday": "Sunday",
"monday": "Monday",
"tuesday": "Tuesday",
"wednesday": "Wednesday",
"thursday": "Thursday",
"friday": "Friday",
"saturday": "Saturday",
"selectYourTable": "Select your table",
"calledWaiterSuccess": "Waiter has been called successfully"
}, },
"cart": { "cart": {
"remainingToPay": "Remaining to Pay",
"remainingVoucherAmount": "Remaining Voucher Amount",
"voucherApplied": "Voucher Applied",
"giftedItems": "Gifted Items",
"voucherBalance": "Voucher Balance",
"addSpecialRequestOptional": "Add Special Request (Optional)",
"continueToGiftDetails": "Continue to Gift Details",
"continueToGiftDetailsDescription": "Please fill in the details of the gift recipient and sender to continue.",
"leaveANoteHere": "Leave a note here..",
"changeTable": "Change Table",
"title": "Cart", "title": "Cart",
"emptyCart": "Cart is empty", "emptyCart": "Cart is empty",
"emptyCartMessage": "Looks like you haven't added any items to your cart yet. Start exploring our menu to find delicious meals!", "emptyCartMessage": "Looks like you haven't added any items to your cart yet. Start exploring our menu to find delicious meals!",
@@ -197,6 +225,12 @@
"cancel": "Cancel", "cancel": "Cancel",
"success": ",Item removed from cart" "success": ",Item removed from cart"
}, },
"clearCartConfirmation": {
"title": "Clear Cart",
"content": "Are you sure you want to clear the cart?",
"confirm": "Clear",
"cancel": "Cancel"
},
"quantity": "Quantity", "quantity": "Quantity",
"price": "Price", "price": "Price",
"perItem": "Per Item", "perItem": "Per Item",
@@ -269,9 +303,24 @@
"am": "AM", "am": "AM",
"pm": "PM", "pm": "PM",
"cannotSelectPastDate": "You cannot select a past date. Please select today or a future date.", "cannotSelectPastDate": "You cannot select a past date. Please select today or a future date.",
"checkRequiredFields": "Please check required fields" "checkRequiredFields": "Please check required fields",
"applyYourAvailableRewardsToGetDiscountsOnItemsInYourCart": "Apply your available rewards to get discounts on items in your cart",
"loyalty": "Loyalty",
"vat": "Vat ({{value}}%)",
"otherTaxes": "{{name}} ({{value}}%)"
}, },
"checkout": { "checkout": {
"addCarDetails": "Add Car Details",
"soTheRestaurantCanRecognizeYourCarWhenYouArrive": "So the restaurant can recognize your car when you arrive.",
"customerName": "Customer Name",
"save": "Save",
"paymentSummary": "Payment Summary",
"orderSummary": "Order Summary",
"holdayGiftCard": "Holday gift card",
"messageIncluded": "Message included",
"to": "To",
"giftSummary": "Gift Summary",
"customerInformation": "Customer Information",
"title": "Checkout", "title": "Checkout",
"cash": "Cash", "cash": "Cash",
"creditDebitCard": "Credit/Debit Card", "creditDebitCard": "Credit/Debit Card",
@@ -290,7 +339,24 @@
"expiresInDescription": "Expires in:12/26", "expiresInDescription": "Expires in:12/26",
"phoneNumber": "Phone Number", "phoneNumber": "Phone Number",
"pleaseSelectPaymentMethod": "Please select payment method", "pleaseSelectPaymentMethod": "Please select payment method",
"pleaseEnterPhoneNumber": "Please enter phone number" "pleaseEnterPhoneNumber": "Please enter phone number",
"viewOrder": "View Order",
"itemsSummary": "Items Summary",
"removeSplitBill": "Remove Split Bill",
"cardBalance": "Card Balance",
"collectAtCounter": "Collect at counter",
"collectAtParking": "Collect at parking",
"scheduled": "Scheduled",
"pickupNow": "Pickup Now",
"pickupEstimate": "Pickup Estimate",
"now": "Now",
"today": "Today",
"change": "Change",
"pickup": "Pickup",
"setPickupTime": "Set Pickup Time",
"carPlateNumber": "Car Plate Number",
"noItems": "No items in cart",
"thawani": "Thawani"
}, },
"address": { "address": {
"title": "Address", "title": "Address",
@@ -343,7 +409,9 @@
"gotIt": "Got It", "gotIt": "Got It",
"howItWorksDescription": "The gifted amount will be credited directly to your friend's wallet in the app. The recipient can use the amount to book a session of their choice within the app. The gifted amount is non-refundable and can only be used for booking sessions.", "howItWorksDescription": "The gifted amount will be credited directly to your friend's wallet in the app. The recipient can use the amount to book a session of their choice within the app. The gifted amount is non-refundable and can only be used for booking sessions.",
"senderEmail": "Sender Email", "senderEmail": "Sender Email",
"save": "Save" "save": "Save",
"pleaseEnterRoomNumber": "Please enter room number",
"pleaseEnterOfficeNumber": "Please enter office number"
}, },
"login": { "login": {
"singup/Login": "Sing up / Login", "singup/Login": "Sing up / Login",
@@ -372,7 +440,17 @@
"confirmOTPSuccess": "OTP confirmed successfully" "confirmOTPSuccess": "OTP confirmed successfully"
}, },
"order": { "order": {
"whosPaidTheirShareSplitBill": "Who's paid their share of the split bill?",
"seeWhoPaidAndHowMuch": "See who paid and how much",
"personHasPaid": "Person has paid",
"inviteOthersToBill": "Invite others to bill",
"title": "Order", "title": "Order",
"newOrder": "New Order",
"aStaffMemberWillCollectTheCashFromYouAtYourTable": "A staff member will collect the cash from you at your table",
"cashPaymentConfirmed": "Cash payment confirmed",
"callWaiter": "Call Waiter",
"yourOrder": "Your Order",
"done": "Done",
"yourOrderFromFascanoRestaurant": "Your order from Fascano restaurant", "yourOrderFromFascanoRestaurant": "Your order from Fascano restaurant",
"muscat": "Muscat", "muscat": "Muscat",
"reserved": "Reserved", "reserved": "Reserved",
@@ -391,7 +469,8 @@
"pleaseLoginToAllowRating": "Please login to allow rating", "pleaseLoginToAllowRating": "Please login to allow rating",
"remainingTime": "Remaining Time", "remainingTime": "Remaining Time",
"sec": "Sec", "sec": "Sec",
"min": "Min" "min": "Min",
"inviteToBill": "Invite to Bill"
}, },
"orderTypes": { "orderTypes": {
"dine-in": "Dine In", "dine-in": "Dine In",
@@ -424,27 +503,144 @@
"payButtonDescription": "Pay for your order" "payButtonDescription": "Pay for your order"
}, },
"splitBill": { "splitBill": {
"serviceFee": "Service Fee",
"title": "Split Bill", "title": "Split Bill",
"description": "Split Bill", "description": "Split Bill",
"splitBill": "Split Bill", "splitBill": "Split Bill",
"splitBillDescription": "Split Bill", "splitBillDescription": "Split Bill",
"splitBillButton": "Split Bill", "customAmount": "Custom Amount",
"divideEqually": "Divide Equally",
"payForItems": "Pay for Items",
"splitBillButtonDescription": "Split Bill", "splitBillButtonDescription": "Split Bill",
"payAsCustomAmount": "Pay as Custom Amount",
"payAsSplitAmount": "Pay as Split Amount",
"divideTheBillEqually": "Divide the Bill Equally",
"payForYourItems": "Pay for Your Items",
"enterCustomAmount": "Enter Custom Amount", "enterCustomAmount": "Enter Custom Amount",
"amountPlaceholder": "0.00", "amountPlaceholder": "0.00",
"divisionPreview": "Division Preview", "divisionPreview": "Division Preview",
"yourAmount": "Your Amount", "yourAmount": "Your Amount",
"selectedTotal": "Selected Total", "selectedTotal": "Selected Total",
"splitBillAmount": "Split Bill Amount", "splitBillAmount": "Split Bill Amount",
"removeSplit": "Remove Split", "cancelSplit": "Cancel Split",
"amount": "Amount", "amount": "Amount",
"howMuchWouldYouLikeToPay": "How much would you like to pay?", "howMuchWouldYouLikeToPay": "How much would you like to pay?",
"confirm": "Confirm", "confirm": "Confirm",
"totalBill": "Total Bill", "totalBill": "Total Bill",
"remainingToPay": "Remaining to Pay" "remainingToPay": "Remaining to Pay",
"scanQRCodeToPay": "Scan QR Code to Pay",
"copyQRCode": "Copy QR Code",
"payWithQR": "Pay with QR",
"cancel": "Cancel",
"done": "Done",
"inviteEveryonePayingWithYou": "Invite everyone paying with you",
"includesAllOfTaxesCharges": "includes all of taxes charges",
"shareLink": "Share Link",
"shareLinkDescription": "Your friends can scan the QR code or use the link to view the bill and pay their share securely from their phone.",
"shareThisToSplitTheBill": "Share this to split the bill"
},
"gift": {
"items": "Gift Items",
"balance": "Gift Balance",
"itemsAndBalance": "Items and Balance",
"selectGiftType": "Select Gift Type"
},
"eGiftCards": {
"title": "Gift Cards",
"pickCardForYourGift": "Pick a card for your gift",
"chooseDesignToMatchTheOccasion": "Choose a design to match the occasion. You can add a message next."
},
"cardDetails": {
"title": "Card Details",
"addGiftDetails": "Add Gift Details",
"description": "Personalize your gift with a message, delivery timing, and recipient details.",
"checkout": "Checkout",
"yourName": "Your Name",
"yourPhone": "Your Phone",
"keepMyNameSecret": "Keep my name secret",
"receiverInformation": "Receiver Information",
"costumeAmount": "Costume amount",
"enterCustomOucherAmount": "Enter custom oucher amount",
"amount": "Amount",
"eCardAmount": "E-Card Amount",
"receiverName": "Receiver Name",
"edit": "Edit",
"yourInformation": "Your Information",
"minimumAmountShouldBe1OMR": "Minimum amount should be 1 OMR",
"add": "Add",
"senderNameRequired": "Sender name is required",
"receiverNameRequired": "Receiver name is required"
},
"car": {
"addCar": "Add Car",
"selectCar": "Select Car",
"addCarDetails": "Add Car Details",
"brand": "Brand",
"color": "Color",
"category": "Category",
"plateNumber": "Plate Number"
},
"redeem": {
"title": "Redeem Gift",
"redeem": "Redeem",
"redeemDescription": "Redeem your gift card",
"redeemButton": "Redeem",
"redeemButtonDescription": "Redeem your gift card",
"showThisCodeAtTheRestaurant": "Show this code at the restaurant",
"addGiftDetails": "Add Gift Details",
"description": "Personalize your gift with a message, delivery timing, and recipient details.",
"checkout": "Checkout",
"yourName": "Your Name",
"yourPhone": "Your Phone",
"keepMyNameSecret": "Keep my name secret",
"receiverInformation": "Receiver Information",
"costumeAmount": "Costume amount",
"enterCustomOucherAmount": "Enter custom oucher amount",
"amount": "Amount",
"eCardAmount": "E-Card Amount",
"receiverName": "Receiver Name",
"edit": "Edit",
"yourInformation": "Your Information",
"minimumAmountShouldBe1OMR": "Minimum amount should be 1 OMR",
"add": "Add",
"senderNameRequired": "Sender name is required",
"receiverNameRequired": "Receiver name is required",
"includesFreeItemsInThisOrder": "Includes free items in this order",
"redeemGiftedItems": "Redeem gifted items",
"pending": "Pending",
"useThisCodeIfScanningNotPossible": "Use this code if scanning is not possible",
"voucherWillBeAppliedAtCheckout": "Voucher will be applied at checkout",
"yourGiftCardBalance": "Your gift card balance",
"redeemNow": "Redeem Now",
"restaurantLocation": "Restaurant Location",
"voucherBalance": "Voucher Balance",
"getDirections": "Get Directions",
"giftedItems": "Gifted Items",
"viewAll": "View All",
"voucherCodeCopied": "Voucher code copied!",
"copyFailed": "Failed to copy voucher code",
"hiX": "Hi {{name}}!",
"youHaveReceivedAGiftCarFromX": "You have received a gift car from {{name}}!"
},
"rewardsAndLoyalty": {
"title": "Rewards And Loyalty",
"description": "Rewards And Loyalty",
"rewardsAndLoyalty": "Rewards And Loyalty",
"rewardsAndLoyaltyDescription": "Rewards And Loyalty",
"rewardsAndLoyaltyButton": "Rewards And Loyalty",
"rewardsAndLoyaltyButtonDescription": "Rewards And Loyalty",
"completedPurchases": "Completed Purchases",
"totalPurchased": "Total Purchased",
"saved": "Saved",
"almosthere": "Almost here!",
"youreJustXCupsAwayFromYourNextReward": "You're just {{cups}} cups away from your next reward!",
"youCanRedeemDuringTheCheckout": "You can redeem during the checkout",
"youCurrentlyHave": "You currently have",
"freeItems": "free items",
"redeemNow": "Redeem Now",
"yourAvailableRewards": "Your available rewards",
"loyaltyHistory": "Loyalty History",
"loyaltyHistoryDescription": "Loyalty History",
"earnedPoints": "Earned {{points}} points",
"order": "Order",
"yourOrderFrom": "Your order from {{restaurantName}}",
"earned": "Earned",
"xPoints": "{{points}} points"
} }
} }

View File

@@ -27,8 +27,12 @@ export default function Ads1({ className }: { className?: string }) {
> >
<ProTitle <ProTitle
style={{ style={{
marginTop: 5, fontFamily: "Roboto",
fontSize: 14, fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 16,
lineHeight: "100%",
letterSpacing: "2%",
}} }}
> >
{t("home.promotion.description")} {t("home.promotion.description")}
@@ -38,6 +42,7 @@ export default function Ads1({ className }: { className?: string }) {
style={{ style={{
marginTop: 5, marginTop: 5,
fontSize: 14, fontSize: 14,
color:"#00AC17"
}} }}
> >
{t("home.promotion.title")} {t("home.promotion.title")}

View File

@@ -63,7 +63,7 @@
} }
.adsCard :global(.ant-card-body) { .adsCard :global(.ant-card-body) {
padding: 8px !important; padding: 10px !important;
} }
:global(.darkApp) .adsCard { :global(.darkApp) .adsCard {

View File

@@ -8,11 +8,7 @@ import { useTranslation } from "react-i18next";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import styles from "./Ads2.module.css"; import styles from "./Ads2.module.css";
export default function Ads2({ export default function Ads2({ className }: { className?: string }) {
className,
}: {
className?: string;
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
@@ -26,8 +22,12 @@ export default function Ads2({
<Card className={styles.adsCard}> <Card className={styles.adsCard}>
<ProTitle <ProTitle
style={{ style={{
marginTop: 5, fontFamily: "Roboto",
fontSize: 14, fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 16,
lineHeight: "100%",
letterSpacing: "2%",
}} }}
> >
{t("home.promotion.description")} {t("home.promotion.description")}
@@ -37,9 +37,10 @@ export default function Ads2({
style={{ style={{
marginTop: 5, marginTop: 5,
fontSize: 14, fontSize: 14,
color: "#00AC17",
}} }}
> >
{t("home.promotion.title")} {t("home.showDetails")}
</ProText> </ProText>
<div <div

View File

@@ -1,6 +1,7 @@
import React from "react"; import React from "react";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import ProText from "../ProText"; import ProText from "../ProText";
import { formatPriceUi } from "utils/helpers";
interface ArabicPriceProps { interface ArabicPriceProps {
price: number | string; price: number | string;
@@ -9,6 +10,7 @@ interface ArabicPriceProps {
type?: "secondary" | "success" | "warning" | "danger"; type?: "secondary" | "success" | "warning" | "danger";
className?: string; className?: string;
hideCurrency?: boolean; hideCurrency?: boolean;
textStyle?: React.CSSProperties;
} }
const ArabicPrice: React.FC<ArabicPriceProps> = ({ const ArabicPrice: React.FC<ArabicPriceProps> = ({
@@ -18,12 +20,14 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
type, type,
className, className,
hideCurrency = false, hideCurrency = false,
textStyle = {},
}) => { }) => {
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const { restaurant } = useAppSelector((state) => state.order); const { restaurant } = useAppSelector((state) => state.order);
// Format the price to ensure it has 2 decimal places // Format the price to ensure it has 2 decimal places
const formattedPrice = typeof price === "number" ? price.toFixed(2) : price; const formattedPrice =
typeof price === "number" ? formatPriceUi(price, restaurant?.currency_decimals ?? 3) : price;
const { textDecoration, ...restStyle } = style; const { textDecoration, ...restStyle } = style;
const decorationStyle = textDecoration const decorationStyle = textDecoration
? ({ textDecoration } as React.CSSProperties) ? ({ textDecoration } as React.CSSProperties)
@@ -49,19 +53,21 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
lineHeight: 1, lineHeight: 1,
fontSize: "14px", fontSize: "14px",
...(decorationStyle ?? {}), ...(decorationStyle ?? {}),
...textStyle,
}} }}
> >
{formattedPrice} {formattedPrice}
</span> </span>
<span style={{ margin: "0 2px" }} /> <span style={{ margin: "0 3px" }} />
<span <span
style={{ style={{
fontSize: "14px", fontSize: "14px",
verticalAlign: "baseline", verticalAlign: "baseline",
lineHeight: 1, lineHeight: 1,
position: "relative", position: "relative",
top: -3, top: -2,
...(decorationStyle ?? {}), ...(decorationStyle ?? {}),
...textStyle,
}} }}
> >
{isRTL ? restaurant.local_currency : restaurant.global_currency} {isRTL ? restaurant.local_currency : restaurant.global_currency}
@@ -76,6 +82,7 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
display: "inline-block", display: "inline-block",
fontSize: "14px", fontSize: "14px",
...(decorationStyle ?? {}), ...(decorationStyle ?? {}),
...textStyle,
}} }}
> >
{formattedPrice} {formattedPrice}
@@ -88,13 +95,16 @@ const ArabicPrice: React.FC<ArabicPriceProps> = ({
lineHeight: 1, lineHeight: 1,
display: "inline-block", display: "inline-block",
...(decorationStyle ?? {}), ...(decorationStyle ?? {}),
...textStyle,
}} }}
> >
{isRTL ? restaurant.local_currency : restaurant.global_currency} {isRTL ? restaurant.local_currency : restaurant.global_currency}
</span> </span>
</> </>
) : ( ) : (
<span style={decorationStyle}>{formattedPrice}</span> <span style={{ ...decorationStyle, ...textStyle }}>
{formattedPrice}
</span>
)} )}
</ProText> </ProText>
); );

View File

@@ -104,7 +104,7 @@ export default function CartActionsButtons({ item }: { item: CartItem }) {
)} )}
<InputNumber <InputNumber
min={1} min={1}
max={100} max={99}
value={item.quantity || 1} value={item.quantity || 1}
onChange={(value: number | null) => onChange={(value: number | null) =>
dispatch( dispatch(
@@ -134,9 +134,10 @@ export default function CartActionsButtons({ item }: { item: CartItem }) {
}), }),
) )
} }
disabled={item.quantity >= 99}
className={styles.addButton} className={styles.addButton}
style={{ style={{
backgroundColor: colors.primary, backgroundColor: "#FFC600",
width: 28, width: 28,
height: 28, height: 28,
border: "none", border: "none",

View File

@@ -0,0 +1,126 @@
import { Button, Form, Input } from "antd";
import ProPhoneInput from "components/ProPhoneInput";
import { useEffect } from "react";
import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
const { TextArea } = Input;
interface CarDetailsType {
brandId: string;
category: string;
color: string;
plateNumber: string;
}
interface AddCarBottomSheetProps {
isOpen: boolean;
onClose: () => void;
onSave: (value: CarDetailsType) => void;
}
export function AddCarBottomSheet({
isOpen,
onClose,
onSave,
}: AddCarBottomSheetProps) {
const { t } = useTranslation();
const [carForm] = Form.useForm();
useEffect(() => {
carForm.setFieldsValue({});
}, [carForm]);
const handleSave = () => {
onSave(carForm.getFieldsValue());
carForm.resetFields();
onClose();
};
const handleCancel = () => {
carForm.resetFields();
onClose();
};
return (
<ProBottomSheet
isOpen={isOpen}
onClose={handleCancel}
title={t("car.addCarDetails")}
showCloseButton={false}
initialSnap={1}
height={475}
snapPoints={[475]}
>
<Form
layout="vertical"
style={{ marginTop: 24}}
name="carForm"
form={carForm}
>
<div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
<Form.Item
name="brandId"
rules={[{ required: true, message: "" }]}
colon={false}
>
<Input
placeholder={t("car.brand")}
size="large"
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
autoFocus={false}
/>
</Form.Item>
<Form.Item
name="color"
rules={[{ required: true, message: "" }]}
colon={false}
>
<Input
placeholder={t("car.color")}
size="large"
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
autoFocus={false}
/>
</Form.Item>
<Form.Item
name="category"
rules={[{ required: true, message: "" }]}
colon={false}
>
<Input
placeholder={t("car.category")}
size="large"
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
autoFocus={false}
/>
</Form.Item>
<Form.Item
name="plateNumber"
rules={[{ required: true, message: "" }]}
colon={false}
>
<Input
placeholder={t("car.plateNumber")}
size="large"
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
autoFocus={false}
/>
</Form.Item>
</div>
</Form>
<br />
<Button
type="primary"
style={{ width: "100%", boxShadow: "none" }}
onClick={handleSave}
>
{t("car.save")}
</Button>
</ProBottomSheet>
);
}

View File

@@ -6,7 +6,7 @@ import CancelPopupIcon from "components/Icons/CancelPopupIcon";
import NextIcon from "components/Icons/NextIcon"; import NextIcon from "components/Icons/NextIcon";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom"; import { useNavigate, useParams } from "react-router-dom";
import { useCancelOrderMutation } from "redux/api/others"; import { useCancelOrderMutation } from "redux/api/others";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet"; import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
@@ -18,7 +18,8 @@ export function CancelOrderBottomSheet() {
const { t } = useTranslation(); const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const { orderId } = useParams(); const { orderId, subdomain } = useParams();
const navigate = useNavigate();
const [cancelOrder] = useCancelOrderMutation(); const [cancelOrder] = useCancelOrderMutation();
@@ -31,6 +32,7 @@ export function CancelOrderBottomSheet() {
message.error(res.error.data.message); message.error(res.error.data.message);
} else { } else {
message.success(res.data.message); message.success(res.data.message);
navigate(`/${subdomain}`);
} }
}); });
}; };
@@ -122,7 +124,7 @@ export function CancelOrderBottomSheet() {
> >
<Button <Button
type="primary" type="primary"
style={{ width: "100%", height: 50, color: "#FFF" }} style={{ width: "100%", height: 48, color: "#FFF" }}
onClick={handleKeepOrder} onClick={handleKeepOrder}
> >
{t("order.keepOrder")} {t("order.keepOrder")}
@@ -132,7 +134,7 @@ export function CancelOrderBottomSheet() {
type="primary" type="primary"
style={{ style={{
width: "100%", width: "100%",
height: 50, height: 48,
color: "#ea1f22", color: "#ea1f22",
borderColor: "#ea1f22", borderColor: "#ea1f22",
backgroundColor: "#fff", backgroundColor: "#fff",

View File

@@ -2,6 +2,10 @@ import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet"; import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import ProRatioGroups from "../ProRatioGroups/ProRatioGroups"; import ProRatioGroups from "../ProRatioGroups/ProRatioGroups";
import { colors } from "ThemeConstants";
import { updateCoupon } from "features/order/orderSlice";
import { useAppDispatch } from "redux/hooks";
import { Button } from "antd";
interface CouponBottomSheetProps { interface CouponBottomSheetProps {
isOpen: boolean; isOpen: boolean;
@@ -18,13 +22,14 @@ export function CouponBottomSheet({
}: CouponBottomSheetProps) { }: CouponBottomSheetProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
const dispatch = useAppDispatch();
useEffect(() => { useEffect(() => {
setValue(initialValue); setValue(initialValue);
}, [initialValue]); }, [initialValue]);
const handleSave = () => { const handleSave = () => {
onSave(value); onSave(value);
dispatch(updateCoupon(value));
onClose(); onClose();
}; };
@@ -38,32 +43,65 @@ export function CouponBottomSheet({
isOpen={isOpen} isOpen={isOpen}
onClose={handleCancel} onClose={handleCancel}
title={t("cart.coupon")} title={t("cart.coupon")}
showCloseButton={false}
initialSnap={1} initialSnap={1}
height={350} height={385}
snapPoints={["30vh"]} snapPoints={[385]}
> >
<div> <div style={{ padding: "16px 0" }}>
<ProRatioGroups <ProRatioGroups
options={[ options={[
{ {
label: "50% off, Min order : SDG 10,000", label: "50% off, Min order : SDG 10,000",
value: "50", value: "7CAB1",
price: "0" price: "7CAB1",
}, },
{ {
label: "Buy one get one free, Min order : SDG 5,000", label: "Buy one get one free, Min order : SDG 5,000",
value: "buy", value: "7CAB2",
price: "0" price: "7CAB2",
}, },
{ {
label: "30% off on select items, Min order : SDG 15,000", label: "30% off on select items, Min order : SDG 15,000",
value: "30", value: "7CABO",
price: "0" price: "7CABO",
}, },
]} ]}
onRatioClick={handleSave} value={value}
onRatioClick={(value) => setValue(value)}
showDivider={true}
optionsStyle={{
fontSize: 12,
fontWeight: 500,
color: "#5F6C7B",
}}
valueStyle={{
fontSize: 12,
fontWeight: 500,
color: colors.primary,
}}
/> />
<div
style={{
display: "flex",
gap: 12,
marginTop: 20,
}}
>
<Button
type="primary"
style={{
flex: 1,
boxShadow: "none",
height: 48,
width: "100%",
}}
onClick={handleSave}
disabled={!value}
>
{t("cart.save")}
</Button>
</div>
</div> </div>
</ProBottomSheet> </ProBottomSheet>
); );

View File

@@ -17,23 +17,6 @@
width: 100%; width: 100%;
} }
.rateServiceCard{
width: 100%;
height: 48px;
display: flex;
justify-content: center;
padding: 12px 18px !important;
row-gap: 10px;
transition: all 0.3s ease;
border-radius: 50px;
}
.rateServiceCard :global(.ant-card-body) {
padding: 0px !important;
text-align: start;
width: 100%;
}
.serviceIcon path { .serviceIcon path {
stroke: #ea1f22; stroke: #ea1f22;
} }
@@ -47,3 +30,96 @@
width: 24px; width: 24px;
height: 24px; height: 24px;
} }
/* Make AntD checkbox look like a circular check indicator (scoped via CSS modules) */
.circleCheckbox :global(.ant-checkbox-inner) {
width: 24px;
height: 24px;
border-radius: 50% !important;
border: 1.5px solid #d5d8da;
background: transparent;
}
.circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner) {
border-radius: 50% !important;
background: transparent;
border-color: #ffb700;
}
/* Replace AntD checkmark with a filled inner circle when checked (match SVG) */
.circleCheckbox :global(.ant-checkbox-inner::after) {
content: "";
border: 0 !important;
transform: none !important;
width: 0;
height: 0;
left: 50%;
top: 50%;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-checkbox-inner::after) {
left: auto;
right: 50%;
}
.circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner::after) {
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
background: #ffb700;
}
:global(.ant-app-rtl)
.circleCheckbox
:global(.ant-checkbox-checked .ant-checkbox-inner::after) {
margin-left: auto;
margin-right: -9px;
}
/* Apply same circular style to Radio buttons */
.radioCheckbox :global(.ant-radio-inner) {
width: 24px;
height: 24px;
border-radius: 50% !important;
border: 1.5px solid #d5d8da;
background: transparent;
}
.radioCheckbox :global(.ant-radio-checked .ant-radio-inner) {
border-radius: 50% !important;
background: transparent;
border-color: #ffb700;
}
.radioCheckbox :global(.ant-radio-inner::after) {
content: "";
border: 0 !important;
transform: none !important;
width: 0;
height: 0;
left: 50%;
top: 50%;
}
:global(.ant-app-rtl) .radioCheckbox :global(.ant-radio-inner::after) {
left: auto;
right: 50%;
}
.radioCheckbox :global(.ant-radio-checked .ant-radio-inner::after) {
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
background: #ffb700;
}
:global(.ant-app-rtl)
.radioCheckbox
:global(.ant-radio-checked .ant-radio-inner::after) {
margin-left: auto;
margin-right: -9px;
}

View File

@@ -277,7 +277,7 @@ export default function DatePickerBottomSheet({
<Button <Button
type="primary" type="primary"
style={{ width: "100%", height: 50, marginTop: 10 }} style={{ width: "100%", height: 48, marginTop: 10 }}
onClick={handleConfirm} onClick={handleConfirm}
> >
{t("common.confirm")} {t("common.confirm")}

View File

@@ -0,0 +1,98 @@
import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import { updateGiftDetails } from "features/order/orderSlice";
import { useAppDispatch } from "redux/hooks";
import GiftItemsBtnIcon from "components/Icons/GiftItemsBtnIcon";
import GiftBalanceBtnIcon from "components/Icons/GiftBalanceBtnIcon";
import GiftItemsAndBalanceBtnIcon from "components/Icons/GiftItemsAndBalanceBtnIcon";
import ProText from "components/ProText";
import { useParams, useNavigate } from "react-router-dom";
interface GiftTypeBottomSheetProps {
isOpen: boolean;
onClose: () => void;
onSave?: () => void;
}
export enum GiftType {
Items = "items",
Vouchers = "vouchers",
ItemsAndVouchers = "itemsAndVouchers",
}
export function GiftTypeBottomSheet({
isOpen,
onClose,
onSave,
}: GiftTypeBottomSheetProps) {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { subdomain } = useParams();
const navigate = useNavigate();
const handleSave = (giftType: GiftType) => {
dispatch(updateGiftDetails({ giftType }));
onClose();
onSave?.();
if (giftType !== GiftType.Vouchers)
navigate(`/${subdomain}/menu?orderType=gift`);
else navigate(`/${subdomain}/e-gift-cards`);
};
const handleCancel = () => {
onClose();
};
const textStyle: React.CSSProperties = {
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#86858E",
};
return (
<ProBottomSheet
isOpen={isOpen}
onClose={handleCancel}
title={t("gift.selectGiftType")}
showCloseButton={false}
initialSnap={1}
height={260}
snapPoints={[260]}
>
<div
style={{
display: "flex",
flexDirection: "row",
gap: 10,
justifyContent: "space-between",
padding: 24,
}}
>
<div style={{ width: 60 }} onClick={() => handleSave(GiftType.Items)}>
<GiftItemsBtnIcon />
<br />
<ProText style={textStyle}>{t("gift.items")}</ProText>
</div>
<div
style={{ width: 60 }}
onClick={() => handleSave(GiftType.Vouchers)}
>
<GiftBalanceBtnIcon />
<br />
<ProText style={textStyle}>{t("gift.balance")}</ProText>
</div>
<div
style={{ width: 60 }}
onClick={() => handleSave(GiftType.ItemsAndVouchers)}
>
<GiftItemsAndBalanceBtnIcon />
<br />
<ProText style={textStyle}>{t("gift.itemsAndBalance")}</ProText>
</div>
</div>
</ProBottomSheet>
);
}

View File

@@ -1,4 +1,3 @@
import { Status, Wrapper } from "@googlemaps/react-wrapper"; import { Status, Wrapper } from "@googlemaps/react-wrapper";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
@@ -74,7 +73,7 @@ function MapComponent({
}); });
setMap(newMap); setMap(newMap);
console.log('Map initialized successfully'); console.log("Map initialized successfully");
// If we have an initial location, add a marker // If we have an initial location, add a marker
if (initialLocation) { if (initialLocation) {
@@ -148,16 +147,14 @@ function MapComponent({
geocoder.geocode({ location: { lat, lng } }, (results, status) => { geocoder.geocode({ location: { lat, lng } }, (results, status) => {
if (status === "OK" && results && results[0]) { if (status === "OK" && results && results[0]) {
const address = results[0].formatted_address; const address = results[0].formatted_address;
console.log( console.log("Initial marker drag - calling onLocationSelect:", {
"Initial marker drag - calling onLocationSelect:", lat,
{ lat, lng, address }, lng,
); address,
});
onLocationSelect?.(lat, lng, address); onLocationSelect?.(lat, lng, address);
} else { } else {
console.log( console.log("Geocoding failed on initial marker drag:", status);
"Geocoding failed on initial marker drag:",
status,
);
// Fallback: use coordinates as address if geocoding fails // Fallback: use coordinates as address if geocoding fails
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`; const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
console.log( console.log(
@@ -201,19 +198,29 @@ function MapComponent({
// Get address from coordinates // Get address from coordinates
const geocoder = new google.maps.Geocoder(); const geocoder = new google.maps.Geocoder();
geocoder.geocode({ location: { lat, lng } }, (results, status) => { geocoder.geocode(
{ location: { lat, lng } },
(results, status) => {
if (status === "OK" && results && results[0]) { if (status === "OK" && results && results[0]) {
const address = results[0].formatted_address; const address = results[0].formatted_address;
console.log('Marker drag - calling onLocationSelect:', { lat, lng, address }); console.log("Marker drag - calling onLocationSelect:", {
lat,
lng,
address,
});
onLocationSelect?.(lat, lng, address); onLocationSelect?.(lat, lng, address);
} else { } else {
console.log('Geocoding failed on drag:', status); console.log("Geocoding failed on drag:", status);
// Fallback: use coordinates as address if geocoding fails // Fallback: use coordinates as address if geocoding fails
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`; const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
console.log('Using fallback address for drag:', fallbackAddress); console.log(
"Using fallback address for drag:",
fallbackAddress,
);
onLocationSelect?.(lat, lng, fallbackAddress); onLocationSelect?.(lat, lng, fallbackAddress);
} }
}); },
);
} }
}); });
@@ -222,13 +229,17 @@ function MapComponent({
geocoder.geocode({ location: { lat, lng } }, (results, status) => { geocoder.geocode({ location: { lat, lng } }, (results, status) => {
if (status === "OK" && results && results[0]) { if (status === "OK" && results && results[0]) {
const address = results[0].formatted_address; const address = results[0].formatted_address;
console.log('Map click - calling onLocationSelect:', { lat, lng, address }); console.log("Map click - calling onLocationSelect:", {
lat,
lng,
address,
});
onLocationSelect?.(lat, lng, address); onLocationSelect?.(lat, lng, address);
} else { } else {
console.log('Geocoding failed on click:', status); console.log("Geocoding failed on click:", status);
// Fallback: use coordinates as address if geocoding fails // Fallback: use coordinates as address if geocoding fails
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`; const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
console.log('Using fallback address:', fallbackAddress); console.log("Using fallback address:", fallbackAddress);
onLocationSelect?.(lat, lng, fallbackAddress); onLocationSelect?.(lat, lng, fallbackAddress);
} }
}); });
@@ -238,6 +249,57 @@ function MapComponent({
} }
}, [map, onLocationSelect, readOnly, initialLocation]); }, [map, onLocationSelect, readOnly, initialLocation]);
// Update map center and marker when initialLocation changes (after map is initialized)
useEffect(() => {
if (map && initialLocation) {
// Update map center
map.setCenter({
lat: initialLocation.lat,
lng: initialLocation.lng,
});
map.setZoom(15);
// Update or create marker
if (markerRef.current) {
// Update existing marker position
markerRef.current.setPosition({
lat: initialLocation.lat,
lng: initialLocation.lng,
});
} else {
// Create new marker if it doesn't exist
const newMarker = new google.maps.Marker({
position: { lat: initialLocation.lat, lng: initialLocation.lng },
map: map,
draggable: !readOnly,
});
markerRef.current = newMarker;
// Add drag end listener if not readOnly
if (!readOnly && onLocationSelect) {
newMarker.addListener("dragend", () => {
const position = newMarker.getPosition();
if (position) {
const lat = position.lat();
const lng = position.lng();
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ location: { lat, lng } }, (results, status) => {
if (status === "OK" && results && results[0]) {
const address = results[0].formatted_address;
onLocationSelect(lat, lng, address);
} else {
const fallbackAddress = `Location: ${lat.toFixed(6)}, ${lng.toFixed(6)}`;
onLocationSelect(lat, lng, fallbackAddress);
}
});
}
});
}
}
}
}, [map, initialLocation, readOnly, onLocationSelect]);
return ( return (
<div <div
ref={mapRef} ref={mapRef}

View File

@@ -50,7 +50,7 @@ export function InfoButtonSheet({
<ProText type="secondary" style={{textAlign:"center"}}>{description}</ProText> <ProText type="secondary" style={{textAlign:"center"}}>{description}</ProText>
</div> </div>
<br /> <br />
<Button type="primary" style={{ width: "100%", boxShadow: "none" }} onClick={handleSave}> <Button type="primary" style={{ width: "100%", boxShadow: "none", height: 48 }} onClick={handleSave}>
{t("address.gotIt")} {t("address.gotIt")}
</Button> </Button>
</ProBottomSheet> </ProBottomSheet>

View File

@@ -62,5 +62,5 @@
.actionButton { .actionButton {
flex: 1; flex: 1;
height: 50px; height: 48px;
} }

View File

@@ -1,22 +1,58 @@
import { Button } from "antd"; import { Card } from "antd";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet"; import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import ProText from "components/ProText"; import ProText from "components/ProText";
import ProTitle from "components/ProTitle"; import ProTitle from "components/ProTitle";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import { useGetOpeningTimesQuery } from "redux/api/others";
import { useMemo } from "react";
import TimeIcon from "components/Icons/order/TimeIcon";
interface OpeningTimesBottomSheetProps { interface OpeningTimesBottomSheetProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
} }
const textStyle: React.CSSProperties = { // Helper function to format time (HH:mm to 12h format)
fontWeight: 400, const formatTime = (time: string | null | undefined): string => {
fontStyle: "Regular", if (!time) return "";
fontSize: 14,
lineHeight: "140%", // If already in 12h format (contains AM/PM), return as is
letterSpacing: "0%", if (time.includes("AM") || time.includes("PM")) {
marginBottom: 4, return time;
}
// Parse 24h format (HH:mm)
const [hours, minutes] = time.split(":");
const hour24 = parseInt(hours, 10);
if (isNaN(hour24)) return time;
const hour12 = hour24 % 12 || 12;
const ampm = hour24 >= 12 ? "PM" : "AM";
return `${hour12}:${minutes} ${ampm}`;
};
// Helper function to get time ranges for a day
const getDayTimes = (
openingTimes: any,
dayIndex: number,
): { shift1: string | null; shift2: string | null } => {
if (!openingTimes) {
return { shift1: null, shift2: null };
}
const from1 = openingTimes[`${dayIndex}_from` as keyof typeof openingTimes];
const to1 = openingTimes[`${dayIndex}_to` as keyof typeof openingTimes];
const from2 = openingTimes[`2_${dayIndex}_from` as keyof typeof openingTimes];
const to2 = openingTimes[`2_${dayIndex}_to` as keyof typeof openingTimes];
const shift1 =
from1 && to1 ? `${formatTime(from1)} - ${formatTime(to1)}` : null;
const shift2 =
from2 && to2 ? `${formatTime(from2)} - ${formatTime(to2)}` : null;
return { shift1, shift2 };
}; };
export function OpeningTimesBottomSheet({ export function OpeningTimesBottomSheet({
@@ -26,6 +62,12 @@ export function OpeningTimesBottomSheet({
const { t } = useTranslation(); const { t } = useTranslation();
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const { restaurant } = useAppSelector((state) => state.order); const { restaurant } = useAppSelector((state) => state.order);
const { data: openingTimes } = useGetOpeningTimesQuery(
restaurant?.restautantId,
{
skip: !restaurant?.restautantId,
},
);
const days = [ const days = [
"sunday", "sunday",
@@ -37,7 +79,11 @@ export function OpeningTimesBottomSheet({
"saturday", "saturday",
]; ];
const todayIndex = new Date().getDay(); const todayIndex = new Date().getDay();
const todayDay = days[todayIndex];
// Memoize day times to avoid recalculating on every render
const dayTimes = useMemo(() => {
return days.map((_, index) => getDayTimes(openingTimes, index));
}, [openingTimes]);
return ( return (
<ProBottomSheet <ProBottomSheet
@@ -46,170 +92,205 @@ export function OpeningTimesBottomSheet({
title={t("menu.openingTimes")} title={t("menu.openingTimes")}
showCloseButton={false} showCloseButton={false}
initialSnap={1} initialSnap={1}
height={445} height={600}
snapPoints={[445]} snapPoints={[600]}
> >
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "column", flexDirection: "column",
padding: 20, padding: "20px 0",
gap: 20,
maxHeight: "calc(600px - 120px)",
overflowY: "auto",
}}
>
{/* Address Section */}
<Card
bordered={false}
style={{
backgroundColor: "#f8f9fa",
borderRadius: 12,
boxShadow: "none",
}}
bodyStyle={{ padding: "16px" }}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 8,
}}
>
<ProTitle
level={5}
style={{
marginBottom: 0,
fontSize: 16,
fontWeight: 600,
color: "#333",
}}
>
{t("menu.address")}
</ProTitle>
<ProText
type="secondary"
style={{
fontSize: 14,
lineHeight: "20px",
color: "#666",
}} }}
> >
<ProTitle level={5}>{t("menu.address")}</ProTitle>
<ProText type="secondary">
{isRTL ? restaurant?.addressAR : restaurant?.address} {isRTL ? restaurant?.addressAR : restaurant?.address}
</ProText> </ProText>
</div>
</Card>
<ProTitle level={5}>{t("menu.openingTimes")}</ProTitle> {/* Opening Times Section */}
<div style={{ display: "flex", justifyContent: "space-between" }}> <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
<ProText <div
type="secondary"
style={{ style={{
...textStyle, display: "flex",
fontWeight: todayDay === "sunday" ? 700 : 400, alignItems: "center",
gap: 8,
marginBottom: 4,
}} }}
> >
sunday <TimeIcon color="#333" />
</ProText> <ProTitle
<ProText level={5}
type="secondary"
style={{ style={{
...textStyle, marginBottom: 0,
fontWeight: todayDay === "sunday" ? 700 : 400, fontSize: 16,
fontWeight: 600,
color: "#333",
}} }}
> >
10:00 AM to 10:00 PM {t("menu.openingTimes")}
</ProTitle>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 8,
}}
>
{days.map((day, index) => {
const isToday = index === todayIndex;
const { shift1, shift2 } = dayTimes[index];
const hasShifts = shift1 || shift2;
return (
<Card
key={day}
bordered
style={{
borderRadius: 12,
borderColor: isToday ? "#1890ff" : "#e8e8e8",
borderWidth: isToday ? 2 : 1,
backgroundColor: isToday ? "#f0f8ff" : "#ffffff",
boxShadow: isToday
? "0 2px 8px rgba(24, 144, 255, 0.15)"
: "0 1px 2px rgba(0, 0, 0, 0.05)",
transition: "all 0.2s ease",
}}
bodyStyle={{ padding: "14px 16px" }}
>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: 16,
}}
>
<div
style={{
display: "flex",
alignItems: "center",
gap: 12,
flex: 1,
}}
>
{isToday && (
<div
style={{
width: 6,
height: 6,
borderRadius: "50%",
backgroundColor: "#1890ff",
flexShrink: 0,
}}
/>
)}
<ProText
style={{
fontSize: 15,
fontWeight: isToday ? 600 : 500,
color: isToday ? "#1890ff" : "#333",
textTransform: "capitalize",
}}
>
{t(`menu.${day}`)}
</ProText> </ProText>
</div> </div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<div
style={{
display: "flex",
flexDirection: "column",
alignItems: isRTL ? "flex-start" : "flex-end",
gap: shift2 ? 4 : 0,
flex: 1,
}}
>
{hasShifts ? (
<>
<ProText
style={{
fontSize: 14,
fontWeight: isToday ? 500 : 400,
color: isToday ? "#1890ff" : "#666",
lineHeight: "20px",
}}
>
{shift1}
</ProText>
{shift2 && (
<ProText
style={{
fontSize: 14,
fontWeight: isToday ? 500 : 400,
color: isToday ? "#1890ff" : "#666",
lineHeight: "20px",
}}
>
{shift2}
</ProText>
)}
</>
) : (
<ProText <ProText
type="secondary" type="secondary"
style={{ style={{
...textStyle, fontSize: 14,
fontWeight: todayDay === "monday" ? 700 : 400, color: "#999",
fontStyle: "italic",
}} }}
> >
monday {t("menu.closed")}
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "monday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between"}}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "tuesday" ? 700 : 400,
}}
>
tuesday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "tuesday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "wednesday" ? 700 : 400,
}}
>
wednesday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "wednesday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "thursday" ? 700 : 400,
}}
>
thursday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "thursday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "friday" ? 700 : 400,
}}
>
friday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "friday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "saturday" ? 700 : 400,
}}
>
saturday
</ProText>
<ProText
type="secondary"
style={{
...textStyle,
fontWeight: todayDay === "saturday" ? 700 : 400,
}}
>
10:00 AM to 10:00 PM
</ProText> </ProText>
)}
</div>
</div>
</Card>
);
})}
</div>
</div> </div>
</div> </div>
<Button
type="primary"
style={{ width: "100%", height: 50 }}
onClick={onClose}
>
{t("menu.close")}
</Button>
</ProBottomSheet> </ProBottomSheet>
); );
} }

View File

@@ -0,0 +1,150 @@
import { Button } from "antd";
import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import NextIcon from "components/Icons/NextIcon";
import { useAppSelector } from "redux/hooks";
import { OrderType } from "pages/checkout/hooks/types";
import { useSearchParams } from "react-router-dom";
interface OrderTypesBottomSheetBottomSheetProps {
isOpen: boolean;
onClose: () => void;
}
export function OrderTypesBottomSheet({
isOpen,
onClose,
}: OrderTypesBottomSheetBottomSheetProps) {
const { t } = useTranslation();
const { restaurant, orderType, hiddenServices } = useAppSelector(
(state) => state.order,
);
const [searchParams, setSearchParams] = useSearchParams();
const buttonStyle = {
height: 48,
width: "100%",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
};
// Calculate height: base 620px, subtract 48px for each hidden service
const calculatedHeight = 620 - hiddenServices * 64;
const handleOrderTypeSelect = (selectedOrderType: OrderType) => {
searchParams.set("orderType", selectedOrderType);
setSearchParams(searchParams);
onClose();
};
return (
<ProBottomSheet
isOpen={isOpen}
onClose={onClose}
title={t("menu.orderTypes")}
initialSnap={1}
height={calculatedHeight}
snapPoints={[calculatedHeight.toString()]}
>
<div
style={{
display: "flex",
flexDirection: "column",
gap: 16,
margin: "16px 0",
}}
>
{restaurant?.dineIn == true && (
<Button
icon={<NextIcon />}
iconPlacement="end"
style={buttonStyle}
type={orderType === OrderType.DineIn ? "primary" : "default"}
onClick={() => handleOrderTypeSelect(OrderType.DineIn)}
>
{t("orderTypes.dine-in")}
</Button>
)}
{restaurant?.delivery == true && (
<Button
icon={<NextIcon />}
iconPlacement="end"
style={buttonStyle}
type={orderType === OrderType.Delivery ? "primary" : "default"}
onClick={() => handleOrderTypeSelect(OrderType.Delivery)}
>
{t("orderTypes.delivery")}
</Button>
)}
{restaurant?.pickup == true && (
<Button
icon={<NextIcon />}
iconPlacement="end"
style={buttonStyle}
type={orderType === OrderType.Pickup ? "primary" : "default"}
onClick={() => handleOrderTypeSelect(OrderType.Pickup)}
>
{t("orderTypes.pickup")}
</Button>
)}
{restaurant?.gift == true && (
<Button
icon={<NextIcon />}
iconPlacement="end"
style={buttonStyle}
type={orderType === OrderType.Gift ? "primary" : "default"}
onClick={() => handleOrderTypeSelect(OrderType.Gift)}
>
{t("orderTypes.gift")}
</Button>
)}
{restaurant?.toRoom == true && (
<Button
icon={<NextIcon />}
iconPlacement="end"
style={buttonStyle}
type={orderType === OrderType.ToRoom ? "primary" : "default"}
onClick={() => handleOrderTypeSelect(OrderType.ToRoom)}
>
{t("orderTypes.room")}
</Button>
)}
{restaurant?.toOffice == true && (
<Button
icon={<NextIcon />}
iconPlacement="end"
style={buttonStyle}
type={orderType === OrderType.ToOffice ? "primary" : "default"}
onClick={() => handleOrderTypeSelect(OrderType.ToOffice)}
>
{t("orderTypes.office")}
</Button>
)}
{restaurant?.is_schedule_order_enabled == 1 && (
<Button
icon={<NextIcon />}
iconPlacement="end"
style={buttonStyle}
type={
orderType === OrderType.ScheduledOrder ? "primary" : "default"
}
onClick={() => handleOrderTypeSelect(OrderType.ScheduledOrder)}
>
{t("orderTypes.scheduled_order")}
</Button>
)}
{restaurant?.is_booking_enabled == 1 && (
<Button
icon={<NextIcon />}
iconPlacement="end"
style={buttonStyle}
type={orderType === OrderType.Booking ? "primary" : "default"}
onClick={() => handleOrderTypeSelect(OrderType.Booking)}
>
{t("orderTypes.booking")}
</Button>
)}
</div>
</ProBottomSheet>
);
}

View File

@@ -1,22 +1,26 @@
// import { useGlobals } from "../../hooks/useGlobals"; // import { useGlobals } from "../../hooks/useGlobals";
import { Button, Card, message } from "antd"; import { Button, message } from "antd";
import BackIcon from "components/Icons/BackIcon";
import NextIcon from "components/Icons/NextIcon";
import RateIcon from "components/Icons/order/RateIcon"; import RateIcon from "components/Icons/order/RateIcon";
import { useState } from "react"; import { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useGetUserDetailsQuery, useRateOrderMutation } from "redux/api/others"; import { useRateOrderMutation } from "redux/api/others";
import { useGetUserDetailsQuery } from "redux/api/others";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import { colors } from "ThemeConstants";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet"; import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import ProText from "../ProText"; import ProText from "../ProText";
import ProTitle from "../ProTitle";
import styles from "./CustomBottomSheet.module.css";
import { colors } from "ThemeConstants";
export function RateBottomSheet() { export function RateBottomSheet({
isOpen,
onClose,
orderIdProp,
}: {
isOpen: boolean;
onClose: () => void;
orderIdProp?: string | undefined;
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [isOpen, setIsOpen] = useState(false);
const [rating, setRating] = useState<number>(0); const [rating, setRating] = useState<number>(0);
const { isRTL } = useAppSelector((state) => state.locale); const { isRTL } = useAppSelector((state) => state.locale);
const { orderId } = useParams(); const { orderId } = useParams();
@@ -27,10 +31,10 @@ export function RateBottomSheet() {
}); });
const handleSubmitRating = () => { const handleSubmitRating = () => {
setIsOpen(false); onClose();
if (getUserDetails?.id) { if (getUserDetails?.id) {
rateOrder({ rateOrder({
orderID: orderId || "", orderID: orderId || orderIdProp || "",
rating: rating, rating: rating,
comment: "This is a test comment", comment: "This is a test comment",
userID: getUserDetails?.id.toString() || "", userID: getUserDetails?.id.toString() || "",
@@ -82,32 +86,9 @@ export function RateBottomSheet() {
return ( return (
<> <>
<Card className={styles.rateServiceCard} onClick={() => setIsOpen(true)}>
<div
style={{
display: "flex",
flexDirection: "row",
justifyContent: "center",
marginTop: 1,
}}
>
<div style={{ display: "flex", flexDirection: "row", gap: 10 }}>
<ProTitle
level={5}
style={{
marginTop: 1,
fontSize: 14,
}}
>
{t("order.rateOrder")}
</ProTitle>
</div>
</div>
</Card>
<ProBottomSheet <ProBottomSheet
isOpen={isOpen} isOpen={isOpen}
onClose={() => setIsOpen(false)} onClose={onClose}
title={t("order.rateOrder")} title={t("order.rateOrder")}
showCloseButton={false} showCloseButton={false}
initialSnap={1} initialSnap={1}
@@ -233,7 +214,7 @@ export function RateBottomSheet() {
type="primary" type="primary"
style={{ style={{
width: "100%", width: "100%",
height: 50, height: 48,
color: colors.primary, color: colors.primary,
borderColor: colors.primary, borderColor: colors.primary,
backgroundColor: "#fff", backgroundColor: "#fff",

View File

@@ -1,23 +1,25 @@
import { Button, Input } from "antd"; import { Button, Input, InputNumber } from "antd";
import WaiterRewardingIcon from "components/Icons/waiter/WaiterRewardingIcon"; import WaiterRewardingIcon from "components/Icons/waiter/WaiterRewardingIcon";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet"; import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
import { updateTip } from "features/order/orderSlice";
import { useAppDispatch } from "redux/hooks";
import ProText from "components/ProText";
interface TipBottomSheetProps { interface TipBottomSheetProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
initialValue: string; initialValue: string;
onSave: (value: string) => void;
} }
export function TipBottomSheet({ export function TipBottomSheet({
isOpen, isOpen,
onClose, onClose,
initialValue, initialValue,
onSave,
}: TipBottomSheetProps) { }: TipBottomSheetProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
useEffect(() => { useEffect(() => {
@@ -25,7 +27,8 @@ export function TipBottomSheet({
}, [initialValue]); }, [initialValue]);
const handleSave = () => { const handleSave = () => {
onSave(value); const numAmount = parseFloat(value) || 0;
dispatch(updateTip(numAmount.toString()));
onClose(); onClose();
}; };
@@ -39,10 +42,9 @@ export function TipBottomSheet({
isOpen={isOpen} isOpen={isOpen}
onClose={handleCancel} onClose={handleCancel}
title={t("cart.tip")} title={t("cart.tip")}
showCloseButton={false}
initialSnap={1} initialSnap={1}
height={370} height={380}
snapPoints={["30vh"]} snapPoints={[380]}
> >
<div <div
style={{ style={{
@@ -50,26 +52,43 @@ export function TipBottomSheet({
flexDirection: "column", flexDirection: "column",
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
gap: 10,
}} }}
> >
<div style={{ marginTop: 20 }}>
<WaiterRewardingIcon /> <WaiterRewardingIcon />
</div>
<br /> <ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
textAlign: "left",
width: "100%",
}}
>
{t("cart.amount")}
</ProText>
<Input <InputNumber
value={value} value={value}
onChange={(e) => setValue(e.target.value)} onChange={(value) => setValue(value?.toString() || "")}
placeholder={t("cart.amount")} placeholder={t("cart.amount")}
autoFocus={false} autoFocus={false}
size="large" size="large"
style={{ height: 48, width: "100%", marginBottom: 8 }}
min={"0"}
/> />
<br />
<Button <Button
type="primary" type="primary"
style={{ width: "100%", height: 50 }} style={{ width: "100%", height: 48 }}
onClick={handleSave} onClick={handleSave}
disabled={value === "" || parseFloat(value) <= 0}
> >
{t("cart.addTip")} {t("cart.addTip")}
</Button> </Button>

View File

@@ -2,29 +2,27 @@ import { Button, Input, Modal } from "antd";
import WaiterRewardingIcon from "components/Icons/waiter/WaiterRewardingIcon"; import WaiterRewardingIcon from "components/Icons/waiter/WaiterRewardingIcon";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useAppDispatch } from "redux/hooks";
import { updateTip } from "features/order/orderSlice";
interface TipDialogProps { interface TipDialogProps {
isOpen: boolean; isOpen: boolean;
onClose: () => void; onClose: () => void;
initialValue: string; initialValue: string;
onSave: (value: string) => void;
} }
export function TipDialog({ export function TipDialog({ isOpen, onClose, initialValue }: TipDialogProps) {
isOpen,
onClose,
initialValue,
onSave,
}: TipDialogProps) {
const { t } = useTranslation(); const { t } = useTranslation();
const [value, setValue] = useState(initialValue); const [value, setValue] = useState(initialValue);
const dispatch = useAppDispatch();
useEffect(() => { useEffect(() => {
setValue(initialValue); setValue(initialValue);
}, [initialValue]); }, [initialValue]);
const handleSave = () => { const handleSave = () => {
onSave(value); const numAmount = parseFloat(value) || 0;
dispatch(updateTip(numAmount.toString()));
onClose(); onClose();
}; };

View File

@@ -0,0 +1,63 @@
interface BranchesIconType {
className?: string;
onClick?: () => void;
}
const BranchesIcon = ({ className, onClick }: BranchesIconType) => {
return (
<svg
width="15"
height="16"
viewBox="0 0 15 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M5.40234 3.18555C5.54014 2.593 6.04569 1.00063 7.49875 1.00063C8.95667 1.00063 9.4607 2.60361 9.59656 3.19141"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.7224 15H2.27935C1.70007 15 1.23047 14.5305 1.23047 13.9512V4.23958C1.23047 3.66035 1.70007 3.19075 2.27935 3.19075H12.7224C13.3017 3.19075 13.7713 3.66035 13.7713 4.23958V13.9512C13.7713 14.5305 13.3017 15 12.7224 15Z"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10.4466 8.39983C10.7649 8.92011 10.9482 9.53128 10.9482 10.1851V10.6016H4.05078V10.1851C4.05078 8.2877 5.59476 6.74955 7.4995 6.74955C8.09085 6.74955 8.64745 6.8978 9.13388 7.15906"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.90234 10.6016H12.0989"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.5 6.75V5.94196"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M10.6209 12.0625H4.37929C4.18146 12.0625 4.0038 11.9419 3.93143 11.7585L3.47656 10.605H11.5236L11.0687 11.7585C10.9964 11.9419 10.8187 12.0625 10.6209 12.0625Z"
stroke="#333333"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default BranchesIcon;

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,55 @@
interface CardAmountIconType {
className?: string;
onClick?: () => void;
}
const CardAmountIcon = ({ className, onClick }: CardAmountIconType) => {
return (
<svg
width="64"
height="42"
viewBox="0 0 64 42"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<rect width="64" height="42" rx="4" fill="#FAFAFA" />
<path
opacity="0.5"
d="M26 18H30"
stroke="#E8B400"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M40.833 19H38.231C36.446 19 35 20.343 35 22C35 23.657 36.447 25 38.23 25H40.833C40.917 25 40.958 25 40.993 24.998C41.533 24.965 41.963 24.566 41.998 24.065C42 24.033 42 23.994 42 23.917V20.083C42 20.006 42 19.967 41.998 19.935C41.962 19.434 41.533 19.035 40.993 19.002C40.958 19 40.917 19 40.833 19Z"
stroke="#4C4A58"
stroke-width="1.5"
/>
<path
d="M40.965 19C40.887 17.128 40.637 15.98 39.828 15.172C38.657 14 36.771 14 33 14H30C26.229 14 24.343 14 23.172 15.172C22.001 16.344 22 18.229 22 22C22 25.771 22 27.657 23.172 28.828C24.344 29.999 26.229 30 30 30H33C36.771 30 38.657 30 39.828 28.828C40.637 28.02 40.888 26.872 40.965 25"
stroke="#4C4A58"
stroke-width="1.5"
/>
<path
opacity="0.5"
d="M26 13.9999L29.735 11.5229C30.2604 11.1816 30.8735 11 31.5 11C32.1265 11 32.7396 11.1816 33.265 11.5229L37 13.9999"
stroke="#E8B400"
stroke-width="1.5"
stroke-linecap="round"
/>
<path
opacity="0.5"
d="M37.9922 22H38.0012"
stroke="#E8B400"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
};
export default CardAmountIcon;

View File

@@ -0,0 +1,47 @@
interface CoinsIconType {
className?: string;
onClick?: () => void;
}
const CoinsIcon = ({ className, onClick }: CoinsIconType) => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M15.8555 9.85547V12.4269C15.8555 13.5412 13.1692 14.9983 9.85547 14.9983C6.54175 14.9983 3.85547 13.5412 3.85547 12.4269V10.284"
stroke="#FFC600"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M4.10938 10.5083C4.8508 11.4923 7.14366 12.4154 9.85737 12.4154C13.1711 12.4154 15.8574 11.0388 15.8574 9.85598C15.8574 9.1917 15.0114 8.46398 13.6837 7.95312"
stroke="#FFC600"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M13.2852 5.57031V8.14174C13.2852 9.25603 10.5989 10.7132 7.28516 10.7132C3.97144 10.7132 1.28516 9.25603 1.28516 8.14174V5.57031"
stroke="#FFC600"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.28516 8.12914C10.5989 8.12914 13.2852 6.75257 13.2852 5.56971C13.2852 4.38686 10.5989 3 7.28516 3C3.97144 3 1.28516 4.386 1.28516 5.56971C1.28516 6.75257 3.97144 8.12914 7.28516 8.12914Z"
stroke="#FFC600"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
};
export default CoinsIcon;

View File

@@ -0,0 +1,33 @@
interface CopyIconType {
className?: string;
onClick?: () => void;
}
const CopyIcon = ({ className, onClick }: CopyIconType) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M5.83203 8.05453C5.83203 7.46509 6.06619 6.89979 6.48299 6.48299C6.89979 6.06619 7.46509 5.83203 8.05453 5.83203H15.2762C15.5681 5.83203 15.8571 5.88952 16.1267 6.00121C16.3964 6.1129 16.6414 6.27661 16.8477 6.48299C17.0541 6.68936 17.2178 6.93437 17.3295 7.20402C17.4412 7.47366 17.4987 7.76267 17.4987 8.05453V15.2762C17.4987 15.5681 17.4412 15.8571 17.3295 16.1267C17.2178 16.3964 17.0541 16.6414 16.8477 16.8477C16.6414 17.0541 16.3964 17.2178 16.1267 17.3295C15.8571 17.4412 15.5681 17.4987 15.2762 17.4987H8.05453C7.76267 17.4987 7.47366 17.4412 7.20402 17.3295C6.93437 17.2178 6.68936 17.0541 6.48299 16.8477C6.27661 16.6414 6.1129 16.3964 6.00121 16.1267C5.88952 15.8571 5.83203 15.5681 5.83203 15.2762V8.05453Z"
stroke="#A4A3AA"
stroke-linecap="round"
stroke-linejoin="round"
/>
<path
d="M3.34333 13.9475C3.0875 13.8021 2.87471 13.5916 2.72658 13.3374C2.57846 13.0832 2.50028 12.7942 2.5 12.5V4.16667C2.5 3.25 3.25 2.5 4.16667 2.5H12.5C13.125 2.5 13.465 2.82083 13.75 3.33333"
stroke="#A4A3AA"
stroke-linecap="round"
stroke-linejoin="round"
/>
</svg>
);
};
export default CopyIcon;

View File

@@ -0,0 +1,25 @@
interface CupIconType {
className?: string;
onClick?: () => void;
}
const CupIcon = ({ className, onClick }: CupIconType) => {
return (
<svg
width="20"
height="20"
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M4.16797 2.5H16.668C17.11 2.5 17.5339 2.67559 17.8465 2.98816C18.159 3.30072 18.3346 3.72464 18.3346 4.16667V6.66667C18.3346 7.10869 18.159 7.53262 17.8465 7.84518C17.5339 8.15774 17.11 8.33333 16.668 8.33333H15.0013V10.8333C15.0013 11.7174 14.6501 12.5652 14.025 13.1904C13.3999 13.8155 12.552 14.1667 11.668 14.1667H6.66797C5.78391 14.1667 4.93607 13.8155 4.31095 13.1904C3.68582 12.5652 3.33464 11.7174 3.33464 10.8333V3.33333C3.33464 3.11232 3.42243 2.90036 3.57871 2.74408C3.73499 2.5878 3.94695 2.5 4.16797 2.5ZM15.0013 4.16667V6.66667H16.668V4.16667H15.0013ZM1.66797 15.8333H16.668V17.5H1.66797V15.8333Z"
fill="#FFB700"
/>
</svg>
);
};
export default CupIcon;

View File

@@ -1,22 +1,26 @@
interface DeleteIconType { interface DeleteIconType {
className?: string; className?: string;
onClick?: () => void; onClick?: () => void;
color?: string;
dimension?: number;
} }
const DeleteIcon = ({ className, onClick }: DeleteIconType) => { const DeleteIcon = ({ className, onClick, color, dimension }: DeleteIconType) => {
return ( return (
<svg <svg
width="12" width={dimension || "14"}
height="12" height={dimension || "14"}
viewBox="0 0 12 12" viewBox="0 0 14 14"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className={className} className={className}
onClick={onClick} onClick={onClick}
> >
<path <path
d="M3 2.4V0.6C3 0.44087 3.06321 0.288258 3.17574 0.175736C3.28826 0.0632141 3.44087 0 3.6 0H8.4C8.55913 0 8.71174 0.0632141 8.82426 0.175736C8.93679 0.288258 9 0.44087 9 0.6V2.4H12V3.6H10.8V11.4C10.8 11.5591 10.7368 11.7117 10.6243 11.8243C10.5117 11.9368 10.3591 12 10.2 12H1.8C1.64087 12 1.48826 11.9368 1.37574 11.8243C1.26321 11.7117 1.2 11.5591 1.2 11.4V3.6H0V2.4H3ZM6.8484 7.2L7.9092 6.1392L7.0608 5.2908L6 6.3516L4.9392 5.2908L4.0908 6.1392L5.1516 7.2L4.0908 8.2608L4.9392 9.1092L6 8.0484L7.0608 9.1092L7.9092 8.2608L6.8484 7.2ZM4.2 1.2V2.4H7.8V1.2H4.2Z" d="M11.375 3.20866L11.0133 9.05658C10.9212 10.5505 10.8751 11.2977 10.5 11.835C10.3148 12.1005 10.0764 12.3246 9.8 12.493C9.24175 12.8337 8.49333 12.8337 6.9965 12.8337C5.49733 12.8337 4.74775 12.8337 4.18833 12.4924C3.91177 12.3237 3.67337 12.0992 3.48833 11.8332C3.11383 11.2954 3.06833 10.547 2.9785 9.05074L2.625 3.20866M1.75 3.20866H12.25M9.366 3.20866L8.96758 2.38733C8.70333 1.84133 8.57092 1.56891 8.34283 1.39858C8.29217 1.36084 8.23852 1.32729 8.18242 1.29824C7.92983 1.16699 7.6265 1.16699 7.02042 1.16699C6.39858 1.16699 6.08767 1.16699 5.83042 1.30349C5.77355 1.33395 5.71931 1.36907 5.66825 1.40849C5.43783 1.58524 5.30892 1.86816 5.05108 2.43341L4.69758 3.20866M5.54167 9.62533V6.12533M8.45833 9.62533V6.12533"
fill="#DC2626" stroke={color || "#DD4143"}
strokeWidth="1.5"
strokeLinecap="round"
/> />
</svg> </svg>
); );

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,25 @@
interface DirectionsIconType {
className?: string;
onClick?: () => void;
}
const DirectionsIcon = ({ className, onClick }: DirectionsIconType) => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M7.5 9H10.125V9.975C10.125 10.15 10.2 10.2687 10.35 10.3312C10.5 10.3938 10.6375 10.3625 10.7625 10.2375L12.225 8.775C12.375 8.625 12.45 8.45 12.45 8.25C12.45 8.05 12.375 7.875 12.225 7.725L10.7625 6.2625C10.6375 6.1375 10.5 6.10625 10.35 6.16875C10.2 6.23125 10.125 6.35 10.125 6.525V7.5H6.75C6.5375 7.5 6.3595 7.572 6.216 7.716C6.0725 7.86 6.0005 8.038 6 8.25V10.5C6 10.7125 6.072 10.8907 6.216 11.0347C6.36 11.1788 6.538 11.2505 6.75 11.25C6.962 11.2495 7.14025 11.1775 7.28475 11.034C7.42925 10.8905 7.501 10.7125 7.5 10.5V9ZM9 16.5C8.8125 16.5 8.62825 16.4625 8.44725 16.3875C8.26625 16.3125 8.1005 16.2 7.95 16.05L1.95 10.05C1.8 9.9 1.6875 9.73425 1.6125 9.55275C1.5375 9.37125 1.5 9.187 1.5 9C1.5 8.813 1.5375 8.62875 1.6125 8.44725C1.6875 8.26575 1.8 8.1 1.95 7.95L7.95 1.95C8.1 1.8 8.26575 1.6875 8.44725 1.6125C8.62875 1.5375 8.813 1.5 9 1.5C9.187 1.5 9.3715 1.5375 9.5535 1.6125C9.7355 1.6875 9.901 1.8 10.05 1.95L16.05 7.95C16.2 8.1 16.3125 8.26575 16.3875 8.44725C16.4625 8.62875 16.5 8.813 16.5 9C16.5 9.187 16.4625 9.3715 16.3875 9.5535C16.3125 9.7355 16.2 9.901 16.05 10.05L10.05 16.05C9.9 16.2 9.73425 16.3125 9.55275 16.3875C9.37125 16.4625 9.187 16.5 9 16.5ZM6 12L9 15L15 9L9 3L3 9L6 12Z"
fill="#E8B400"
/>
</svg>
);
};
export default DirectionsIcon;

View File

@@ -1,29 +1,33 @@
interface EditIconType { interface EditIconType {
className?: string; className?: string;
onClick?: () => void; onClick?: () => void;
color?: string;
} }
const EditIcon = ({ className, onClick }: EditIconType) => { const EditIcon = ({ className, onClick, color }: EditIconType) => {
return ( return (
<svg <svg
width="16" width="16"
height="17" height="16"
viewBox="0 0 16 17" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className={className} className={className}
onClick={onClick} onClick={onClick}
> >
<g clipPath="url(#clip0_1571_6925)">
<path <path
d="M5.33323 10.3008C5.33323 9.97468 5.33323 9.81162 5.37007 9.65817C5.40274 9.52212 5.45661 9.39206 5.52971 9.27276C5.61217 9.13821 5.72747 9.02291 5.95808 8.7923L12.3333 2.41714C12.8855 1.86486 13.781 1.86486 14.3333 2.41714C14.8855 2.96943 14.8855 3.86486 14.3333 4.41714L7.95809 10.7923C7.72749 11.0229 7.61219 11.1382 7.47763 11.2207C7.35834 11.2938 7.22828 11.3476 7.09223 11.3803C6.93878 11.4171 6.77572 11.4171 6.4496 11.4171H5.33323V10.3008Z" d="M7.33203 2.66617H4.53203C3.41193 2.66617 2.85187 2.66617 2.42405 2.88415C2.04773 3.0759 1.74176 3.38186 1.55002 3.75819C1.33203 4.18601 1.33203 4.74606 1.33203 5.86617V11.4662C1.33203 12.5863 1.33203 13.1463 1.55002 13.5741C1.74176 13.9505 2.04773 14.2564 2.42405 14.4482C2.85187 14.6662 3.41193 14.6662 4.53203 14.6662H10.132C11.2521 14.6662 11.8122 14.6662 12.24 14.4482C12.6163 14.2564 12.9223 13.9505 13.114 13.5741C13.332 13.1463 13.332 12.5863 13.332 11.4662V8.66617M5.33201 10.6662H6.44838C6.7745 10.6662 6.93756 10.6662 7.09101 10.6293C7.22706 10.5967 7.35711 10.5428 7.47641 10.4697C7.61097 10.3872 7.72627 10.2719 7.95687 10.0413L14.332 3.66617C14.8843 3.11388 14.8843 2.21845 14.332 1.66617C13.7797 1.11388 12.8843 1.11388 12.332 1.66617L5.95685 8.04133C5.72625 8.27193 5.61095 8.38723 5.52849 8.52179C5.45539 8.64108 5.40152 8.77114 5.36885 8.90719C5.33201 9.06064 5.33201 9.2237 5.33201 9.54982V10.6662Z"
fill="var(--primary)" stroke={color || "#B58D00"}
/>
<path
d="M7.33325 3.41714H4.53325C3.41315 3.41714 2.85309 3.41714 2.42527 3.63513C2.04895 3.82688 1.74299 4.13284 1.55124 4.50916C1.33325 4.93699 1.33325 5.49704 1.33325 6.61714V12.2171C1.33325 13.3372 1.33325 13.8973 1.55124 14.3251C1.74299 14.7014 2.04895 15.0074 2.42527 15.1992C2.85309 15.4171 3.41315 15.4171 4.53325 15.4171H10.1333C11.2534 15.4171 11.8134 15.4171 12.2412 15.1992C12.6176 15.0074 12.9235 14.7014 13.1153 14.3251C13.3333 13.8973 13.3333 13.3372 13.3333 12.2171V9.41714M5.33323 11.4171H6.4496C6.77572 11.4171 6.93878 11.4171 7.09223 11.3803C7.22828 11.3476 7.35834 11.2938 7.47763 11.2207C7.61219 11.1382 7.72749 11.0229 7.95809 10.7923L14.3333 4.41714C14.8855 3.86486 14.8855 2.96943 14.3333 2.41714C13.781 1.86486 12.8855 1.86486 12.3333 2.41714L5.95808 8.79231C5.72747 9.02291 5.61217 9.13821 5.52971 9.27276C5.45661 9.39206 5.40274 9.52212 5.37007 9.65817C5.33323 9.81162 5.33323 9.97468 5.33323 10.3008V11.4171Z"
stroke="#333333"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />
</g>
<defs>
<clipPath id="clip0_1571_6925">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg> </svg>
); );
}; };

View File

@@ -0,0 +1,60 @@
interface GiftBalanceBtnIconType {
className?: string;
onClick?: () => void;
dimension?: string;
}
const GiftBalanceBtnIcon = ({
className,
onClick,
dimension,
}: GiftBalanceBtnIconType) => {
return (
<svg
width={dimension || "60"}
height={dimension || "60"}
viewBox="0 0 60 60"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<rect x="0.5" y="0.5" width="59" height="59" rx="29.5" fill="white" />
<rect
x="0.5"
y="0.5"
width="59"
height="59"
rx="29.5"
stroke="url(#paint0_linear_1528_31202)"
/>
<path
d="M16.25 30C16.25 24.8149 16.25 22.2216 17.8615 20.6115C19.473 19.0014 22.0649 19 27.25 19H32.75C37.9351 19 40.5284 19 42.1385 20.6115C43.7486 22.223 43.75 24.8149 43.75 30C43.75 35.1851 43.75 37.7784 42.1385 39.3885C40.527 40.9986 37.9351 41 32.75 41H27.25C22.0649 41 19.4716 41 17.8615 39.3885C16.2514 37.777 16.25 35.1851 16.25 30Z"
stroke="#FFC600"
strokeWidth="1.5"
/>
<path
opacity="0.5"
d="M27.25 35.5H21.75M32.75 35.5H30.6875M16.25 27.25H43.75"
stroke="#FFC600"
strokeWidth="1.5"
strokeLinecap="round"
/>
<defs>
<linearGradient
id="paint0_linear_1528_31202"
x1="60"
y1="-2.86102e-06"
x2="-132"
y2="194"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FFFCF5" />
<stop offset="0.454842" stopColor="#FFD466" />
</linearGradient>
</defs>
</svg>
);
};
export default GiftBalanceBtnIcon;

View File

@@ -0,0 +1,45 @@
interface GiftIconType {
className?: string;
onClick?: () => void;
}
const GiftIcon = ({ className, onClick }: GiftIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M13 4.67188H3C2.44772 4.67188 2 5.11959 2 5.67187V13.0052C2 13.5575 2.44772 14.0052 3 14.0052H13C13.5523 14.0052 14 13.5575 14 13.0052V5.67187C14 5.11959 13.5523 4.67188 13 4.67188Z"
stroke="#7950E6"
strokeLinejoin="round"
/>
<path
d="M2 10.6719H14V13.0052C14 13.2704 13.8946 13.5248 13.7071 13.7123C13.5196 13.8999 13.2652 14.0052 13 14.0052H3C2.73478 14.0052 2.48043 13.8999 2.29289 13.7123C2.10536 13.5248 2 13.2704 2 13.0052V10.6719Z"
stroke="#7950E6"
strokeLinejoin="round"
/>
<path
d="M6.33464 4.66927C7.25511 4.66927 8.0013 3.92308 8.0013 3.0026C8.0013 2.08213 7.25511 1.33594 6.33464 1.33594C5.41416 1.33594 4.66797 2.08213 4.66797 3.0026C4.66797 3.92308 5.41416 4.66927 6.33464 4.66927Z"
stroke="#7950E6"
/>
<path
d="M9.33333 4.66667C10.0697 4.66667 10.6667 4.06971 10.6667 3.33333C10.6667 2.59695 10.0697 2 9.33333 2C8.59695 2 8 2.59695 8 3.33333C8 4.06971 8.59695 4.66667 9.33333 4.66667Z"
stroke="#7950E6"
/>
<path
d="M5.66797 6.67188L8.0013 4.67188L10.3346 6.67188"
stroke="#7950E6"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default GiftIcon;

View File

@@ -0,0 +1,74 @@
interface GiftItemsAndBalanceBtnIconType {
className?: string;
onClick?: () => void;
dimension?: string;
}
const GiftItemsAndBalanceBtnIcon = ({
className,
onClick,
dimension,
}: GiftItemsAndBalanceBtnIconType) => {
return (
<svg
width={dimension || "60"}
height={dimension || "60"}
viewBox="0 0 60 60"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<rect x="0.5" y="0.5" width="59" height="59" rx="29.5" fill="white" />
<rect
x="0.5"
y="0.5"
width="59"
height="59"
rx="29.5"
stroke="url(#paint0_linear_1528_31188)"
/>
<path
d="M35.1562 40.6562C33.9374 40.6563 32.7305 40.4162 31.6045 39.9498C30.4784 39.4833 29.4553 38.7997 28.5934 37.9378C27.7316 37.076 27.0479 36.0528 26.5815 34.9268C26.1151 33.8007 25.875 32.5938 25.875 31.375C25.875 30.1562 26.1151 28.9493 26.5815 27.8232C27.0479 26.6972 27.7316 25.674 28.5934 24.8122C29.4553 23.9503 30.4784 23.2667 31.6045 22.8002C32.7305 22.3338 33.9374 22.0937 35.1562 22.0938"
stroke="#FFC600"
strokeLinecap="round"
/>
<path
d="M19.6875 22.0938V27.25M19.6875 40.6562V30.3438M17.1094 27.25H22.2656M22.7812 22.0938V26.5818C22.7812 31.5978 16.5938 31.5978 16.5938 26.5818V22.0938"
stroke="#FFC600"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M31.375 31.25C31.375 28.6574 31.375 27.3608 32.1807 26.5557C32.9865 25.7507 34.2824 25.75 36.875 25.75H39.625C42.2176 25.75 43.5142 25.75 44.3192 26.5557C45.1243 27.3615 45.125 28.6574 45.125 31.25C45.125 33.8426 45.125 35.1392 44.3192 35.9442C43.5135 36.7493 42.2176 36.75 39.625 36.75H36.875C34.2824 36.75 32.9858 36.75 32.1807 35.9442C31.3757 35.1385 31.375 33.8426 31.375 31.25Z"
stroke="#FFC600"
/>
<g opacity="0.5">
<path
d="M36.875 34H34.125ZM39.625 34H38.5938ZM31.375 29.875H45.125Z"
fill="white"
/>
<path
d="M36.875 34H34.125M39.625 34H38.5938M31.375 29.875H45.125"
stroke="#FFC600"
strokeLinecap="round"
/>
</g>
<defs>
<linearGradient
id="paint0_linear_1528_31188"
x1="60"
y1="-2.86102e-06"
x2="-132"
y2="194"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FFFCF5" />
<stop offset="0.454842" stopColor="#FFD466" />
</linearGradient>
</defs>
</svg>
);
};
export default GiftItemsAndBalanceBtnIcon;

View File

@@ -0,0 +1,67 @@
interface GiftItemsBtnIconType {
className?: string;
onClick?: () => void;
dimension?: string;
}
const GiftItemsBtnIcon = ({
className,
onClick,
dimension,
}: GiftItemsBtnIconType) => {
return (
<svg
width={dimension || "60"}
height={dimension || "60"}
viewBox="0 0 60 60"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<rect x="0.5" y="0.5" width="59" height="59" rx="29.5" fill="white" />
<rect
x="0.5"
y="0.5"
width="59"
height="59"
rx="29.5"
stroke="url(#paint0_linear_1528_12064)"
/>
<path
d="M42.375 36.875C40.5516 36.875 38.803 36.1507 37.5136 34.8614C36.2243 33.572 35.5 31.8234 35.5 30C35.5 28.1766 36.2243 26.428 37.5136 25.1386C38.803 23.8493 40.5516 23.125 42.375 23.125"
stroke="#FFC600"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M42.375 42.375C40.7499 42.375 39.1407 42.0549 37.6393 41.433C36.1379 40.8111 34.7737 39.8996 33.6246 38.7504C32.4754 37.6013 31.5639 36.2371 30.942 34.7357C30.3201 33.2343 30 31.6251 30 30C30 28.3749 30.3201 26.7657 30.942 25.2643C31.5639 23.7629 32.4754 22.3987 33.6246 21.2496C34.7737 20.1004 36.1379 19.1889 37.6393 18.567C39.1407 17.9451 40.7499 17.625 42.375 17.625"
stroke="#FFC600"
strokeWidth="1.5"
strokeLinecap="round"
/>
<path
d="M21.75 17.625V24.5M21.75 42.375V28.625M18.3125 24.5H25.1875M25.875 17.625V23.609C25.875 30.297 17.625 30.297 17.625 23.609V17.625"
stroke="#FFC600"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
<defs>
<linearGradient
id="paint0_linear_1528_12064"
x1="60"
y1="-2.86102e-06"
x2="-132"
y2="194"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="#FFFCF5" />
<stop offset="0.454842" stopColor="#FFD466" />
</linearGradient>
</defs>
</svg>
);
};
export default GiftItemsBtnIcon;

View File

@@ -0,0 +1,27 @@
interface LoginIconType {
className?: string;
onClick?: () => void;
}
const LoginIcon = ({ className, onClick }: LoginIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M10 2H10.8C11.9201 2 12.4802 2 12.908 2.21799C13.2843 2.40973 13.5903 2.71569 13.782 3.09202C14 3.51984 14 4.0799 14 5.2V10.8C14 11.9201 14 12.4802 13.782 12.908C13.5903 13.2843 13.2843 13.5903 12.908 13.782C12.4802 14 11.9201 14 10.8 14H10M6.66667 4.66667L10 8M10 8L6.66667 11.3333M10 8L2 8"
stroke="#333333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default LoginIcon;

View File

@@ -0,0 +1,37 @@
interface LoyaltyAndRewardIconType {
className?: string;
onClick?: () => void;
color?: string
}
const LoyaltyAndRewardIcon = ({ className, onClick, color }: LoyaltyAndRewardIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M1.33203 7.9987C1.33203 5.32736 1.33203 3.99203 2.11336 3.16203C2.8947 2.33203 4.15136 2.33203 6.66536 2.33203H9.33203C11.846 2.33203 13.1034 2.33203 13.884 3.16203C14.6647 3.99203 14.6654 5.32736 14.6654 7.9987C14.6654 10.67 14.6654 12.0054 13.884 12.8354C13.1027 13.6654 11.846 13.6654 9.33203 13.6654H6.66536C4.15136 13.6654 2.89403 13.6654 2.11336 12.8354C1.3327 12.0054 1.33203 10.67 1.33203 7.9987Z"
stroke={color || "#333333"}
/>
<path
d="M4.59189 7.22074C5.21789 6.85541 5.76389 7.00274 6.09189 7.23741C6.22655 7.33341 6.29389 7.38141 6.33322 7.38141C6.37322 7.38141 6.43989 7.33341 6.57455 7.23741C6.90255 7.00274 7.44855 6.85541 8.07455 7.22074C8.89589 7.70074 9.08122 9.28341 7.18789 10.6187C6.82655 10.8727 6.64589 11.0001 6.33322 11.0001C6.02055 11.0001 5.83989 10.8734 5.47922 10.6187C3.58522 9.28341 3.76989 7.70074 4.59189 7.22074Z"
stroke={color || "#333333"}
strokeLinecap="round"
/>
<path
d="M12 11H10"
stroke={color || "#333333"}
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default LoyaltyAndRewardIcon;

View File

@@ -0,0 +1,27 @@
interface MyOrderIconType {
className?: string;
onClick?: () => void;
}
const MyOrderIcon = ({ className, onClick }: MyOrderIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M10.6656 5.9987V3.9987C10.6656 2.52594 9.4717 1.33203 7.99894 1.33203C6.52618 1.33203 5.33228 2.52594 5.33228 3.9987V5.9987M2.39361 6.90001L1.99361 11.1667C1.87988 12.3798 1.82302 12.9864 2.02431 13.4549C2.20114 13.8665 2.51102 14.2067 2.90429 14.4212C3.35196 14.6654 3.96119 14.6654 5.17964 14.6654H10.8182C12.0367 14.6654 12.6459 14.6654 13.0936 14.4212C13.4869 14.2067 13.7967 13.8665 13.9736 13.4549C14.1749 12.9864 14.118 12.3798 14.0043 11.1667L13.6043 6.90001C13.5082 5.8756 13.4602 5.36339 13.2298 4.97614C13.0269 4.63509 12.7272 4.36211 12.3687 4.19193C11.9616 3.9987 11.4472 3.9987 10.4182 3.9987L5.57964 3.9987C4.55074 3.9987 4.03628 3.9987 3.62922 4.19193C3.27072 4.3621 2.97095 4.63509 2.76805 4.97614C2.53767 5.36339 2.48965 5.87559 2.39361 6.90001Z"
stroke="#333333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default MyOrderIcon;

View File

@@ -0,0 +1,29 @@
interface NoteIconType {
className?: string;
onClick?: () => void;
}
const NoteIcon = ({ className, onClick }: NoteIconType) => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M12 16H12.008M12 8V13M3.23005 7.913L7.91005 3.23C8.06005 3.08 8.26005 3 8.48005 3H15.53C15.74 3 15.95 3.08 16.1 3.23L20.77 7.903C20.92 8.053 21 8.253 21 8.473V15.527C21 15.737 20.92 15.947 20.77 16.097L16.1 20.77C15.95 20.92 15.75 21 15.53 21H8.47005C8.36456 21.0011 8.2599 20.9814 8.16208 20.9419C8.06425 20.9025 7.9752 20.844 7.90005 20.77L3.23005 16.097C3.15602 16.0218 3.09759 15.9328 3.05812 15.835C3.01865 15.7371 2.99891 15.6325 3.00005 15.527V8.473C3.00005 8.263 3.08005 8.053 3.23005 7.903V7.913Z"
stroke="#3D3B4A"
strokeWidth="1.5"
strokeMiterlimit="10"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default NoteIcon;

View File

@@ -1,13 +1,15 @@
interface PlusIconType { interface PlusIconType {
className?: string; className?: string;
onClick?: () => void; onClick?: () => void;
dimension?: string
color?: string
} }
const PlusIcon = ({ className, onClick }: PlusIconType) => { const PlusIcon = ({ className, onClick, dimension, color }: PlusIconType) => {
return ( return (
<svg <svg
width="16" width={dimension || "16"}
height="16" height={dimension || "16"}
viewBox="0 0 16 16" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -16,7 +18,7 @@ const PlusIcon = ({ className, onClick }: PlusIconType) => {
> >
<path <path
d="M7.99992 3.3335V12.6668M3.33325 8.00016H12.6666" d="M7.99992 3.3335V12.6668M3.33325 8.00016H12.6666"
stroke="#FFD633" stroke={color || "#FFD633"}
strokeWidth="1.5" strokeWidth="1.5"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"

View File

@@ -0,0 +1,51 @@
interface PopularIconType {
className?: string;
onClick?: () => void;
}
const PopularIcon = ({ className, onClick }: PopularIconType) => {
return (
<svg
fill="#FFC600"
height="30"
width="30"
version="1.1"
id="Icons"
xmlns="http://www.w3.org/2000/svg"
xmlnsXlink="http://www.w3.org/1999/xlink"
viewBox="0 0 32 32"
xmlSpace="preserve"
className={className}
onClick={onClick}
>
<g>
<path
d="M12,17c0.8-4.2,1.9-5.3,6.1-6.1c0.5-0.1,0.8-0.5,0.8-1s-0.3-0.9-0.8-1C13.9,8.1,12.8,7,12,2.8C11.9,2.3,11.5,2,11,2
c-0.5,0-0.9,0.3-1,0.8C9.2,7,8.1,8.1,3.9,8.9C3.5,9,3.1,9.4,3.1,9.9s0.3,0.9,0.8,1c4.2,0.8,5.3,1.9,6.1,6.1c0.1,0.5,0.5,0.8,1,0.8
S11.9,17.4,12,17z"
/>
<path
d="M22,24c-2.8-0.6-3.4-1.2-4-4c-0.1-0.5-0.5-0.8-1-0.8s-0.9,0.3-1,0.8c-0.6,2.8-1.2,3.4-4,4c-0.5,0.1-0.8,0.5-0.8,1
s0.3,0.9,0.8,1c2.8,0.6,3.4,1.2,4,4c0.1,0.5,0.5,0.8,1,0.8s0.9-0.3,1-0.8c0.6-2.8,1.2-3.4,4-4c0.5-0.1,0.8-0.5,0.8-1
S22.4,24.1,22,24z"
/>
<path
d="M29.2,14c-2.2-0.4-2.7-0.9-3.1-3.1c-0.1-0.5-0.5-0.8-1-0.8c-0.5,0-0.9,0.3-1,0.8c-0.4,2.2-0.9,2.7-3.1,3.1
c-0.5,0.1-0.8,0.5-0.8,1s0.3,0.9,0.8,1c2.2,0.4,2.7,0.9,3.1,3.1c0.1,0.5,0.5,0.8,1,0.8c0.5,0,0.9-0.3,1-0.8
c0.4-2.2,0.9-2.7,3.1-3.1c0.5-0.1,0.8-0.5,0.8-1S29.7,14.1,29.2,14z"
/>
<path
d="M5.7,22.3C5.4,22,5,21.9,4.6,22.1c-0.1,0-0.2,0.1-0.3,0.2c-0.1,0.1-0.2,0.2-0.2,0.3C4,22.7,4,22.9,4,23s0,0.3,0.1,0.4
c0.1,0.1,0.1,0.2,0.2,0.3c0.1,0.1,0.2,0.2,0.3,0.2C4.7,24,4.9,24,5,24c0.1,0,0.3,0,0.4-0.1s0.2-0.1,0.3-0.2
c0.1-0.1,0.2-0.2,0.2-0.3C6,23.3,6,23.1,6,23s0-0.3-0.1-0.4C5.9,22.5,5.8,22.4,5.7,22.3z"
/>
<path
d="M28,7c0.3,0,0.5-0.1,0.7-0.3C28.9,6.5,29,6.3,29,6s-0.1-0.5-0.3-0.7c-0.1-0.1-0.2-0.2-0.3-0.2c-0.2-0.1-0.5-0.1-0.8,0
c-0.1,0-0.2,0.1-0.3,0.2C27.1,5.5,27,5.7,27,6c0,0.3,0.1,0.5,0.3,0.7C27.5,6.9,27.7,7,28,7z"
/>
</g>
</svg>
);
};
export default PopularIcon;

View File

@@ -0,0 +1,55 @@
interface QRIconType {
className?: string;
onClick?: () => void;
}
const QRIcon = ({ className, onClick }: QRIconType) => {
return (
<svg
width="116"
height="116"
viewBox="0 0 116 116"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M4.8335 4.8335H53.1668V53.1668H4.8335V4.8335ZM14.5002 14.5002V43.5002H43.5002V14.5002H14.5002Z"
fill="black"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M24.1665 24.1665H33.8332V33.8332H24.1665V24.1665Z"
fill="black"
/>
<path
d="M62.8335 4.8335H111.167V53.1668H62.8335V4.8335ZM72.5002 14.5002V43.5002H101.5V14.5002H72.5002Z"
fill="black"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M82.1665 24.1665H91.8332V33.8332H82.1665V24.1665Z"
fill="black"
/>
<path
d="M4.8335 62.8335H53.1668V111.167H4.8335V62.8335ZM14.5002 72.5002V101.5H43.5002V72.5002H14.5002Z"
fill="black"
/>
<path
fillRule="evenodd"
clipRule="evenodd"
d="M24.1665 82.1665H33.8332V91.8332H24.1665V82.1665Z"
fill="black"
/>
<path
d="M111.167 91.8335H91.8335V111.167H62.8335V62.8335V91.8335H72.5002V101.5H82.1668V72.5002H72.5002V62.8335H67.6668H82.1668V72.5002H91.8335V82.1668H101.5V62.8335H111.167V91.8335ZM111.167 101.5V111.167H101.5V101.5H111.167Z"
fill="black"
/>
</svg>
);
};
export default QRIcon;

View File

@@ -0,0 +1,31 @@
interface RCardIconType {
className?: string;
onClick?: () => void;
}
const RCardIcon = ({ className, onClick }: RCardIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M1.33203 6.66732H14.6654V5.46732C14.6654 4.72058 14.6654 4.34721 14.52 4.062C14.3922 3.81112 14.1882 3.60714 13.9374 3.47931C13.6521 3.33398 13.2788 3.33398 12.532 3.33398H3.46536C2.71863 3.33398 2.34526 3.33398 2.06004 3.47931C1.80916 3.60714 1.60519 3.81111 1.47736 4.062C1.33203 4.34721 1.33203 4.72058 1.33203 5.46732V6.66732Z"
fill="#FFB700"
/>
<path
d="M12.6654 14.0007V10.0007M10.6654 12.0007H14.6654M14.6654 6.66732H1.33203M14.6654 8.00065V5.46732C14.6654 4.72058 14.6654 4.34721 14.52 4.062C14.3922 3.81112 14.1882 3.60714 13.9374 3.47931C13.6521 3.33398 13.2788 3.33398 12.532 3.33398H3.46536C2.71863 3.33398 2.34526 3.33398 2.06004 3.47931C1.80916 3.60714 1.60519 3.81111 1.47736 4.062C1.33203 4.34721 1.33203 4.72058 1.33203 5.46732V10.534C1.33203 11.2807 1.33203 11.6541 1.47736 11.9393C1.60519 12.1902 1.80916 12.3942 2.06004 12.522C2.34526 12.6673 2.71863 12.6673 3.46536 12.6673H7.9987"
stroke="#333333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default RCardIcon;

View File

@@ -0,0 +1,33 @@
interface RaiseIconType {
className?: string;
onClick?: () => void;
}
const RaiseIcon = ({ className, onClick }: RaiseIconType) => {
return (
<svg
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M2.25 12.75L6.75 8.25L9.75 11.25L15.75 5.25"
stroke="#FFC600"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M12.75 5.25H15.75V8.25"
stroke="#FFC600"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default RaiseIcon;

View File

@@ -0,0 +1,29 @@
interface RefershIconType {
className?: string;
onClick?: () => void;
dimension?: number;
}
const RefershIcon = ({ className, onClick, dimension }: RefershIconType) => {
return (
<svg
width={dimension || "18"}
height={dimension || "18"}
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M1.5 7.5C1.5 7.5 1.59099 6.86307 4.22703 4.22703C6.86307 1.59099 11.1369 1.59099 13.773 4.22703C14.7069 5.16099 15.31 6.30054 15.5821 7.5M1.5 7.5V3M1.5 7.5H6M16.5 10.5C16.5 10.5 16.409 11.1369 13.773 13.773C11.1369 16.409 6.86307 16.409 4.22703 13.773C3.29307 12.839 2.69002 11.6995 2.41787 10.5M16.5 10.5V15M16.5 10.5H12"
stroke="#5F6C7B"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default RefershIcon;

View File

@@ -0,0 +1,83 @@
interface ScheduleOrderIconType {
className?: string;
onClick?: () => void;
}
const ScheduleOrderIcon = ({ className, onClick }: ScheduleOrderIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<g clip-path="url(#clip0_1498_49735)">
<path
d="M4.96953 0.0218744C4.91016 0.0281248 4.71953 0.0625 4.54766 0.0968752C2.56328 0.490624 0.925781 1.9375 0.282031 3.86562C0.0507812 4.5625 0.0195312 4.7875 0.0195312 5.67188C0.0195312 6.55625 0.0507812 6.78125 0.282031 7.47188C1.01016 9.65937 3.01016 11.2125 5.26328 11.3406C5.62891 11.3625 6.28516 11.3344 6.41016 11.2969C6.46641 11.2781 6.46953 11.3 6.46953 12.1469C6.46953 12.8 6.47891 13.0312 6.51016 13.0656C6.52891 13.0938 7.57578 13.7625 8.83203 14.5562C10.6133 15.6813 11.1414 16 11.2195 16C11.2914 16 11.9695 15.6562 13.5727 14.8094C14.8133 14.1531 15.8633 13.5844 15.907 13.5406L15.9852 13.4688L15.9945 11.2219L16.0008 8.975L15.932 8.89375C15.8758 8.82812 13.1227 7.30625 11.6039 6.49687C11.3789 6.37813 11.2883 6.31563 11.2977 6.28125C11.3352 6.14375 11.3477 5.50313 11.3164 5.1875C11.2414 4.43125 11.0664 3.8125 10.7539 3.17188C9.92266 1.48125 8.34766 0.328125 6.47578 0.046875C6.19453 0.00312519 5.26328 -0.00937462 4.96953 0.0218744ZM6.29141 0.546875C8.26016 0.80625 9.92266 2.16875 10.5445 4.03438C10.757 4.66875 10.7977 4.93437 10.7945 5.6875C10.7914 6.05625 10.7789 6.4 10.7602 6.45C10.7289 6.55 10.7477 6.54062 8.06328 7.95937C7.26328 8.38125 6.57891 8.75625 6.54141 8.79063C6.46953 8.85313 6.46953 8.85938 6.46953 9.8125V10.7719L6.22891 10.7937C5.82266 10.8281 5.25703 10.8156 4.91953 10.7656C2.21641 10.3625 0.253906 7.86562 0.528906 5.17812C0.657031 3.95625 1.16016 2.89687 2.02891 2.03125C2.24766 1.81563 2.54766 1.55312 2.69766 1.44687C3.27578 1.04062 4.02266 0.724999 4.70703 0.596875C5.27578 0.490624 5.74766 0.474999 6.29141 0.546875ZM13.2883 8.01562L15.1852 9.04375L14.6695 9.32188L14.157 9.6L12.2258 8.54375C11.1664 7.9625 10.2727 7.47188 10.2414 7.45C10.1914 7.41875 10.2508 7.37813 10.6945 7.14687L11.2039 6.87813L11.2977 6.93437C11.3508 6.9625 12.2445 7.45 13.2883 8.01562ZM11.7664 8.85625L13.8914 10.0219L13.9008 10.6156L13.907 11.2062L13.6258 11.3844L13.3445 11.5594V10.9906C13.3445 10.5437 13.3352 10.4062 13.3008 10.3562C13.2758 10.3187 12.3195 9.78125 11.1758 9.15625C10.0289 8.53125 9.09453 8.00938 9.09453 8C9.09453 7.98125 9.59453 7.69063 9.62578 7.69063C9.63516 7.69063 10.5977 8.2125 11.7664 8.85625ZM10.5477 9.41875L12.5352 10.4938L12.3383 10.6031C11.5164 11.0531 11.2414 11.1906 11.1945 11.1719C11.0164 11.1062 7.29141 8.97813 7.31328 8.95625C7.36953 8.9 8.48828 8.31875 8.52266 8.32812C8.54453 8.33437 9.45391 8.825 10.5477 9.41875ZM9.33516 10.7406L10.9352 11.6406L10.9383 13.4625V15.2875L10.8477 15.2281C10.7977 15.1969 9.90703 14.6313 8.86328 13.9688L6.97266 12.7656L6.96953 11.0875V9.40937L7.35391 9.625C7.56328 9.74375 8.45391 10.2469 9.33516 10.7406ZM15.4383 11.3531V13.2062L15.2289 13.3187C15.1102 13.3812 14.307 13.8062 13.4383 14.2656C12.5695 14.725 11.7727 15.15 11.6664 15.2094L11.4695 15.3156V13.4937V11.6719L12.1352 11.3094L12.7977 10.9437L12.8133 11.5344C12.8289 12.0875 12.832 12.1281 12.8977 12.1906C12.9383 12.2313 13.0039 12.2594 13.057 12.2594C13.1539 12.2594 14.2727 11.6625 14.3508 11.5719C14.382 11.5344 14.3945 11.3312 14.407 10.7844L14.4227 10.0469L14.9227 9.775C15.1977 9.625 15.4258 9.5 15.432 9.5C15.4352 9.5 15.4383 10.3344 15.4383 11.3531Z"
fill="#434E5C"
/>
<path
d="M5.49778 0.96545C5.42903 1.01858 5.42278 1.05608 5.41341 1.32795C5.40716 1.49045 5.41028 1.66233 5.41966 1.70295C5.46653 1.89045 5.70091 1.96858 5.84778 1.8467C5.91966 1.79045 5.92278 1.76858 5.92278 1.40608C5.92278 1.04358 5.91966 1.0217 5.84778 0.96545C5.75403 0.89045 5.59153 0.89045 5.49778 0.96545Z"
fill="#434E5C"
/>
<path
d="M7.78125 1.50937C7.68125 1.55937 7.40625 2.04688 7.40625 2.175C7.40625 2.32812 7.5125 2.4375 7.65938 2.4375C7.80938 2.4375 7.85625 2.39375 8.025 2.08437C8.2 1.76875 8.2 1.60312 8.02812 1.51562C7.92188 1.45937 7.88125 1.45937 7.78125 1.50937Z"
fill="#434E5C"
/>
<path
d="M3.17282 1.60938C3.06344 1.71562 3.07594 1.8125 3.23844 2.1125C3.39157 2.39688 3.46657 2.46875 3.60407 2.46875C3.72907 2.46875 3.79782 2.425 3.84469 2.3125C3.88219 2.22187 3.87594 2.19688 3.75094 1.95C3.56032 1.57187 3.52282 1.53125 3.37282 1.53125C3.28219 1.53125 3.22594 1.55312 3.17282 1.60938Z"
fill="#434E5C"
/>
<path
d="M5.5375 2.53164C5.5 2.54726 5.45312 2.59726 5.4375 2.64101C5.41875 2.68789 5.40625 3.13476 5.40625 3.70664V4.68789L5.32187 4.71914C4.96875 4.84726 4.71875 5.16601 4.66875 5.55039C4.64375 5.76914 4.7 6.02226 4.83125 6.23476L4.9375 6.41601L4.5 7.15664C4.16562 7.72226 4.0625 7.92539 4.0625 8.00664C4.0625 8.14101 4.1125 8.21289 4.2375 8.24726C4.4375 8.30351 4.47812 8.25664 4.96875 7.42539C5.36562 6.75039 5.42812 6.65976 5.48437 6.67851C5.51875 6.68789 5.63437 6.69726 5.74062 6.69414C6.33437 6.68789 6.79062 6.12851 6.68437 5.54101C6.61562 5.18164 6.40937 4.90039 6.10625 4.75976L5.94062 4.68476L5.93125 3.65664C5.92187 2.51601 5.925 2.54414 5.71562 2.51601C5.65625 2.50664 5.57812 2.51601 5.5375 2.53164ZM5.90625 5.25351C6.14062 5.38476 6.225 5.70039 6.08437 5.90976C5.98125 6.05976 5.825 6.15664 5.67812 6.15664C5.30937 6.15664 5.05937 5.72851 5.25937 5.43476C5.41875 5.19414 5.675 5.12539 5.90625 5.25351Z"
fill="#434E5C"
/>
<path
d="M1.58125 3.19063C1.51562 3.24062 1.5 3.28125 1.5 3.39062C1.5 3.46875 1.51875 3.54688 1.54688 3.57188C1.63125 3.65937 2.11875 3.90625 2.2 3.90625C2.32812 3.90625 2.4375 3.77813 2.4375 3.62812C2.4375 3.48438 2.38438 3.4375 2.025 3.24062C1.7875 3.10938 1.69688 3.1 1.58125 3.19063Z"
fill="#434E5C"
/>
<path
d="M9.2125 3.25903C8.9 3.43403 8.84375 3.48715 8.84375 3.60903C8.84375 3.75278 8.92813 3.8434 9.07812 3.86215C9.18438 3.87465 9.24375 3.85278 9.46875 3.72778C9.77812 3.55278 9.8375 3.4684 9.78125 3.29653C9.70938 3.08403 9.55 3.07153 9.2125 3.25903Z"
fill="#434E5C"
/>
<path
d="M1.04133 5.43711C0.900707 5.49648 0.860082 5.71836 0.966332 5.84648C1.02258 5.91523 1.05383 5.92148 1.37258 5.93086L1.71946 5.94336L1.81321 5.84961C1.92258 5.73711 1.93508 5.60586 1.84758 5.49648C1.79133 5.42773 1.76321 5.42148 1.44758 5.41523C1.26321 5.40898 1.07883 5.42148 1.04133 5.43711Z"
fill="#434E5C"
/>
<path
d="M9.50183 5.49375C9.40183 5.59375 9.38621 5.66875 9.44246 5.79062C9.49558 5.90625 9.59871 5.9375 9.93621 5.9375C10.1987 5.9375 10.255 5.92812 10.3268 5.87187C10.3925 5.82187 10.4081 5.78125 10.4081 5.675C10.4081 5.45937 10.3456 5.425 9.93621 5.4125L9.59558 5.4L9.50183 5.49375Z"
fill="#434E5C"
/>
<path
d="M1.85 7.62187C1.70625 7.70312 1.575 7.79063 1.55937 7.81562C1.54375 7.84062 1.53125 7.90937 1.53125 7.97187C1.53125 8.1125 1.63437 8.21875 1.77187 8.21875C1.87187 8.21875 2.35937 7.96875 2.43125 7.88125C2.49687 7.79688 2.47812 7.6125 2.39687 7.5375C2.28125 7.43125 2.14687 7.45 1.85 7.62187Z"
fill="#434E5C"
/>
<path
d="M3.50938 8.92188C3.4125 8.99062 3.15625 9.475 3.15625 9.59375C3.15625 9.7125 3.28437 9.84375 3.40625 9.84375C3.53438 9.84375 3.6375 9.74375 3.77812 9.48438C3.9375 9.19687 3.95 9.0375 3.825 8.94063C3.72813 8.8625 3.60313 8.85625 3.50938 8.92188Z"
fill="#434E5C"
/>
<path
d="M5.54087 9.44375C5.43149 9.49687 5.40024 9.625 5.41274 9.9625C5.42524 10.3406 5.46274 10.4062 5.67524 10.4062C5.78149 10.4062 5.82212 10.3906 5.87212 10.325C5.92837 10.2531 5.93774 10.1969 5.93774 9.91563C5.93774 9.59688 5.93462 9.5875 5.84712 9.49687C5.75024 9.40312 5.65962 9.3875 5.54087 9.44375Z"
fill="#434E5C"
/>
<path
d="M7.48566 10.8906C7.38879 10.9844 7.37941 11.1844 7.46379 11.2563C7.49191 11.2844 8.12316 11.6531 8.86691 12.075C9.82629 12.6219 10.245 12.8438 10.3138 12.8438C10.3763 12.8438 10.4388 12.8156 10.4919 12.7625C10.5575 12.6969 10.5669 12.6625 10.5544 12.5687C10.5419 12.5094 10.5169 12.4406 10.495 12.4187C10.4732 12.3969 9.84191 12.0281 9.09816 11.5969C8.19191 11.075 7.71066 10.8125 7.65129 10.8125C7.59816 10.8125 7.52941 10.8438 7.48566 10.8906Z"
fill="#434E5C"
/>
<path
d="M7.45209 11.8591C7.35834 11.9498 7.34896 12.1185 7.43021 12.2123C7.51459 12.3123 10.1583 13.8248 10.2708 13.8373C10.4833 13.8623 10.6083 13.5904 10.4615 13.4248C10.4021 13.3623 8.14896 12.0185 7.78646 11.831C7.64896 11.7591 7.53959 11.7685 7.45209 11.8591Z"
fill="#434E5C"
/>
<path
d="M9.71782 0.0685797C9.63032 0.165454 9.63657 0.327954 9.73345 0.421704L9.80845 0.499829H11.9522H14.0928V3.38108V6.26233L13.5678 5.74045C13.1053 5.28108 13.0303 5.21858 12.9397 5.21858C12.8053 5.21858 12.6866 5.33733 12.6866 5.47483C12.6866 5.56545 12.7678 5.65608 13.4459 6.33733C13.8616 6.75608 14.2334 7.11233 14.2741 7.12795C14.3241 7.1467 14.3772 7.14045 14.4459 7.11233C14.5022 7.08733 14.8741 6.74045 15.2741 6.33733C15.9834 5.6217 15.9991 5.60295 15.9991 5.47795C15.9991 5.37483 15.9834 5.3342 15.9178 5.2842C15.7397 5.14358 15.6897 5.17483 15.1334 5.72483L14.6241 6.23108L14.6178 3.18108C14.6084 0.23108 14.6053 0.127954 14.5491 0.0654545C14.4897 -0.000170708 14.4897 -0.000170708 12.1366 -0.000170708H9.78345L9.71782 0.0685797Z"
fill="#434E5C"
/>
</g>
</svg>
);
};
export default ScheduleOrderIcon;

View File

@@ -0,0 +1,83 @@
interface ScheduleOrderIconType {
className?: string;
onClick?: () => void;
}
const ScheduleOrderIcon = ({ className, onClick }: ScheduleOrderIconType) => {
return (
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<g clipPath="url(#clip0_1498_49735)">
<path
d="M4.96953 0.0218744C4.91016 0.0281248 4.71953 0.0625 4.54766 0.0968752C2.56328 0.490624 0.925781 1.9375 0.282031 3.86562C0.0507812 4.5625 0.0195312 4.7875 0.0195312 5.67188C0.0195312 6.55625 0.0507812 6.78125 0.282031 7.47188C1.01016 9.65937 3.01016 11.2125 5.26328 11.3406C5.62891 11.3625 6.28516 11.3344 6.41016 11.2969C6.46641 11.2781 6.46953 11.3 6.46953 12.1469C6.46953 12.8 6.47891 13.0312 6.51016 13.0656C6.52891 13.0938 7.57578 13.7625 8.83203 14.5562C10.6133 15.6813 11.1414 16 11.2195 16C11.2914 16 11.9695 15.6562 13.5727 14.8094C14.8133 14.1531 15.8633 13.5844 15.907 13.5406L15.9852 13.4688L15.9945 11.2219L16.0008 8.975L15.932 8.89375C15.8758 8.82812 13.1227 7.30625 11.6039 6.49687C11.3789 6.37813 11.2883 6.31563 11.2977 6.28125C11.3352 6.14375 11.3477 5.50313 11.3164 5.1875C11.2414 4.43125 11.0664 3.8125 10.7539 3.17188C9.92266 1.48125 8.34766 0.328125 6.47578 0.046875C6.19453 0.00312519 5.26328 -0.00937462 4.96953 0.0218744ZM6.29141 0.546875C8.26016 0.80625 9.92266 2.16875 10.5445 4.03438C10.757 4.66875 10.7977 4.93437 10.7945 5.6875C10.7914 6.05625 10.7789 6.4 10.7602 6.45C10.7289 6.55 10.7477 6.54062 8.06328 7.95937C7.26328 8.38125 6.57891 8.75625 6.54141 8.79063C6.46953 8.85313 6.46953 8.85938 6.46953 9.8125V10.7719L6.22891 10.7937C5.82266 10.8281 5.25703 10.8156 4.91953 10.7656C2.21641 10.3625 0.253906 7.86562 0.528906 5.17812C0.657031 3.95625 1.16016 2.89687 2.02891 2.03125C2.24766 1.81563 2.54766 1.55312 2.69766 1.44687C3.27578 1.04062 4.02266 0.724999 4.70703 0.596875C5.27578 0.490624 5.74766 0.474999 6.29141 0.546875ZM13.2883 8.01562L15.1852 9.04375L14.6695 9.32188L14.157 9.6L12.2258 8.54375C11.1664 7.9625 10.2727 7.47188 10.2414 7.45C10.1914 7.41875 10.2508 7.37813 10.6945 7.14687L11.2039 6.87813L11.2977 6.93437C11.3508 6.9625 12.2445 7.45 13.2883 8.01562ZM11.7664 8.85625L13.8914 10.0219L13.9008 10.6156L13.907 11.2062L13.6258 11.3844L13.3445 11.5594V10.9906C13.3445 10.5437 13.3352 10.4062 13.3008 10.3562C13.2758 10.3187 12.3195 9.78125 11.1758 9.15625C10.0289 8.53125 9.09453 8.00938 9.09453 8C9.09453 7.98125 9.59453 7.69063 9.62578 7.69063C9.63516 7.69063 10.5977 8.2125 11.7664 8.85625ZM10.5477 9.41875L12.5352 10.4938L12.3383 10.6031C11.5164 11.0531 11.2414 11.1906 11.1945 11.1719C11.0164 11.1062 7.29141 8.97813 7.31328 8.95625C7.36953 8.9 8.48828 8.31875 8.52266 8.32812C8.54453 8.33437 9.45391 8.825 10.5477 9.41875ZM9.33516 10.7406L10.9352 11.6406L10.9383 13.4625V15.2875L10.8477 15.2281C10.7977 15.1969 9.90703 14.6313 8.86328 13.9688L6.97266 12.7656L6.96953 11.0875V9.40937L7.35391 9.625C7.56328 9.74375 8.45391 10.2469 9.33516 10.7406ZM15.4383 11.3531V13.2062L15.2289 13.3187C15.1102 13.3812 14.307 13.8062 13.4383 14.2656C12.5695 14.725 11.7727 15.15 11.6664 15.2094L11.4695 15.3156V13.4937V11.6719L12.1352 11.3094L12.7977 10.9437L12.8133 11.5344C12.8289 12.0875 12.832 12.1281 12.8977 12.1906C12.9383 12.2313 13.0039 12.2594 13.057 12.2594C13.1539 12.2594 14.2727 11.6625 14.3508 11.5719C14.382 11.5344 14.3945 11.3312 14.407 10.7844L14.4227 10.0469L14.9227 9.775C15.1977 9.625 15.4258 9.5 15.432 9.5C15.4352 9.5 15.4383 10.3344 15.4383 11.3531Z"
fill="#434E5C"
/>
<path
d="M5.49778 0.96545C5.42903 1.01858 5.42278 1.05608 5.41341 1.32795C5.40716 1.49045 5.41028 1.66233 5.41966 1.70295C5.46653 1.89045 5.70091 1.96858 5.84778 1.8467C5.91966 1.79045 5.92278 1.76858 5.92278 1.40608C5.92278 1.04358 5.91966 1.0217 5.84778 0.96545C5.75403 0.89045 5.59153 0.89045 5.49778 0.96545Z"
fill="#434E5C"
/>
<path
d="M7.78125 1.50937C7.68125 1.55937 7.40625 2.04688 7.40625 2.175C7.40625 2.32812 7.5125 2.4375 7.65938 2.4375C7.80938 2.4375 7.85625 2.39375 8.025 2.08437C8.2 1.76875 8.2 1.60312 8.02812 1.51562C7.92188 1.45937 7.88125 1.45937 7.78125 1.50937Z"
fill="#434E5C"
/>
<path
d="M3.17282 1.60938C3.06344 1.71562 3.07594 1.8125 3.23844 2.1125C3.39157 2.39688 3.46657 2.46875 3.60407 2.46875C3.72907 2.46875 3.79782 2.425 3.84469 2.3125C3.88219 2.22187 3.87594 2.19688 3.75094 1.95C3.56032 1.57187 3.52282 1.53125 3.37282 1.53125C3.28219 1.53125 3.22594 1.55312 3.17282 1.60938Z"
fill="#434E5C"
/>
<path
d="M5.5375 2.53164C5.5 2.54726 5.45312 2.59726 5.4375 2.64101C5.41875 2.68789 5.40625 3.13476 5.40625 3.70664V4.68789L5.32187 4.71914C4.96875 4.84726 4.71875 5.16601 4.66875 5.55039C4.64375 5.76914 4.7 6.02226 4.83125 6.23476L4.9375 6.41601L4.5 7.15664C4.16562 7.72226 4.0625 7.92539 4.0625 8.00664C4.0625 8.14101 4.1125 8.21289 4.2375 8.24726C4.4375 8.30351 4.47812 8.25664 4.96875 7.42539C5.36562 6.75039 5.42812 6.65976 5.48437 6.67851C5.51875 6.68789 5.63437 6.69726 5.74062 6.69414C6.33437 6.68789 6.79062 6.12851 6.68437 5.54101C6.61562 5.18164 6.40937 4.90039 6.10625 4.75976L5.94062 4.68476L5.93125 3.65664C5.92187 2.51601 5.925 2.54414 5.71562 2.51601C5.65625 2.50664 5.57812 2.51601 5.5375 2.53164ZM5.90625 5.25351C6.14062 5.38476 6.225 5.70039 6.08437 5.90976C5.98125 6.05976 5.825 6.15664 5.67812 6.15664C5.30937 6.15664 5.05937 5.72851 5.25937 5.43476C5.41875 5.19414 5.675 5.12539 5.90625 5.25351Z"
fill="#434E5C"
/>
<path
d="M1.58125 3.19063C1.51562 3.24062 1.5 3.28125 1.5 3.39062C1.5 3.46875 1.51875 3.54688 1.54688 3.57188C1.63125 3.65937 2.11875 3.90625 2.2 3.90625C2.32812 3.90625 2.4375 3.77813 2.4375 3.62812C2.4375 3.48438 2.38438 3.4375 2.025 3.24062C1.7875 3.10938 1.69688 3.1 1.58125 3.19063Z"
fill="#434E5C"
/>
<path
d="M9.2125 3.25903C8.9 3.43403 8.84375 3.48715 8.84375 3.60903C8.84375 3.75278 8.92813 3.8434 9.07812 3.86215C9.18438 3.87465 9.24375 3.85278 9.46875 3.72778C9.77812 3.55278 9.8375 3.4684 9.78125 3.29653C9.70938 3.08403 9.55 3.07153 9.2125 3.25903Z"
fill="#434E5C"
/>
<path
d="M1.04133 5.43711C0.900707 5.49648 0.860082 5.71836 0.966332 5.84648C1.02258 5.91523 1.05383 5.92148 1.37258 5.93086L1.71946 5.94336L1.81321 5.84961C1.92258 5.73711 1.93508 5.60586 1.84758 5.49648C1.79133 5.42773 1.76321 5.42148 1.44758 5.41523C1.26321 5.40898 1.07883 5.42148 1.04133 5.43711Z"
fill="#434E5C"
/>
<path
d="M9.50183 5.49375C9.40183 5.59375 9.38621 5.66875 9.44246 5.79062C9.49558 5.90625 9.59871 5.9375 9.93621 5.9375C10.1987 5.9375 10.255 5.92812 10.3268 5.87187C10.3925 5.82187 10.4081 5.78125 10.4081 5.675C10.4081 5.45937 10.3456 5.425 9.93621 5.4125L9.59558 5.4L9.50183 5.49375Z"
fill="#434E5C"
/>
<path
d="M1.85 7.62187C1.70625 7.70312 1.575 7.79063 1.55937 7.81562C1.54375 7.84062 1.53125 7.90937 1.53125 7.97187C1.53125 8.1125 1.63437 8.21875 1.77187 8.21875C1.87187 8.21875 2.35937 7.96875 2.43125 7.88125C2.49687 7.79688 2.47812 7.6125 2.39687 7.5375C2.28125 7.43125 2.14687 7.45 1.85 7.62187Z"
fill="#434E5C"
/>
<path
d="M3.50938 8.92188C3.4125 8.99062 3.15625 9.475 3.15625 9.59375C3.15625 9.7125 3.28437 9.84375 3.40625 9.84375C3.53438 9.84375 3.6375 9.74375 3.77812 9.48438C3.9375 9.19687 3.95 9.0375 3.825 8.94063C3.72813 8.8625 3.60313 8.85625 3.50938 8.92188Z"
fill="#434E5C"
/>
<path
d="M5.54087 9.44375C5.43149 9.49687 5.40024 9.625 5.41274 9.9625C5.42524 10.3406 5.46274 10.4062 5.67524 10.4062C5.78149 10.4062 5.82212 10.3906 5.87212 10.325C5.92837 10.2531 5.93774 10.1969 5.93774 9.91563C5.93774 9.59688 5.93462 9.5875 5.84712 9.49687C5.75024 9.40312 5.65962 9.3875 5.54087 9.44375Z"
fill="#434E5C"
/>
<path
d="M7.48566 10.8906C7.38879 10.9844 7.37941 11.1844 7.46379 11.2563C7.49191 11.2844 8.12316 11.6531 8.86691 12.075C9.82629 12.6219 10.245 12.8438 10.3138 12.8438C10.3763 12.8438 10.4388 12.8156 10.4919 12.7625C10.5575 12.6969 10.5669 12.6625 10.5544 12.5687C10.5419 12.5094 10.5169 12.4406 10.495 12.4187C10.4732 12.3969 9.84191 12.0281 9.09816 11.5969C8.19191 11.075 7.71066 10.8125 7.65129 10.8125C7.59816 10.8125 7.52941 10.8438 7.48566 10.8906Z"
fill="#434E5C"
/>
<path
d="M7.45209 11.8591C7.35834 11.9498 7.34896 12.1185 7.43021 12.2123C7.51459 12.3123 10.1583 13.8248 10.2708 13.8373C10.4833 13.8623 10.6083 13.5904 10.4615 13.4248C10.4021 13.3623 8.14896 12.0185 7.78646 11.831C7.64896 11.7591 7.53959 11.7685 7.45209 11.8591Z"
fill="#434E5C"
/>
<path
d="M9.71782 0.0685797C9.63032 0.165454 9.63657 0.327954 9.73345 0.421704L9.80845 0.499829H11.9522H14.0928V3.38108V6.26233L13.5678 5.74045C13.1053 5.28108 13.0303 5.21858 12.9397 5.21858C12.8053 5.21858 12.6866 5.33733 12.6866 5.47483C12.6866 5.56545 12.7678 5.65608 13.4459 6.33733C13.8616 6.75608 14.2334 7.11233 14.2741 7.12795C14.3241 7.1467 14.3772 7.14045 14.4459 7.11233C14.5022 7.08733 14.8741 6.74045 15.2741 6.33733C15.9834 5.6217 15.9991 5.60295 15.9991 5.47795C15.9991 5.37483 15.9834 5.3342 15.9178 5.2842C15.7397 5.14358 15.6897 5.17483 15.1334 5.72483L14.6241 6.23108L14.6178 3.18108C14.6084 0.23108 14.6053 0.127954 14.5491 0.0654545C14.4897 -0.000170708 14.4897 -0.000170708 12.1366 -0.000170708H9.78345L9.71782 0.0685797Z"
fill="#434E5C"
/>
</g>
</svg>
);
};
export default ScheduleOrderIcon;

View File

@@ -0,0 +1,33 @@
interface SuccessIconType {
className?: string;
onClick?: () => void;
}
const SuccessIcon = ({ className, onClick }: SuccessIconType) => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M12 22C17.5228 22 22 17.5228 22 12C22 6.47715 17.5228 2 12 2C6.47715 2 2 6.47715 2 12C2 17.5228 6.47715 22 12 22Z"
stroke="#32AD6D"
strokeWidth="1.5"
/>
<path
d="M8.5 12.5L10.5 14.5L15.5 9.5"
stroke="#32AD6D"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default SuccessIcon;

View File

@@ -0,0 +1,27 @@
interface UploadIconType {
className?: string;
onClick?: () => void;
}
const UploadIcon = ({ className, onClick }: UploadIconType) => {
return (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<path
d="M12 6V13.8M12 6L14.5 8.4M12 6L9.5 8.4M7 12V16.8C7 17.1183 7.1317 17.4235 7.36612 17.6485C7.60054 17.8736 7.91848 18 8.25 18H15.75C16.0815 18 16.3995 17.8736 16.6339 17.6485C16.8683 17.4235 17 17.1183 17 16.8V12"
stroke="black"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
};
export default UploadIcon;

View File

@@ -6,16 +6,16 @@ interface CouponHeartType {
const CouponHeartIcon = ({ className, onClick }: CouponHeartType) => { const CouponHeartIcon = ({ className, onClick }: CouponHeartType) => {
return ( return (
<svg <svg
width="15" width="16"
height="14" height="16"
viewBox="0 0 15 14" viewBox="0 0 16 16"
fill="none" fill="none"
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
className={className} className={className}
onClick={onClick} onClick={onClick}
> >
<path <path
d="M5.49992 6.08333L6.83325 7.41667L9.83325 4.41667M7.49535 2.17388C6.16245 0.6156 3.93975 0.196428 2.26972 1.62334C0.599681 3.05026 0.364564 5.43598 1.67605 7.1236C2.66665 8.39829 5.48078 10.9573 6.79859 12.1333C7.04084 12.3494 7.16197 12.4575 7.30381 12.5001C7.42694 12.537 7.56377 12.537 7.6869 12.5001C7.82874 12.4575 7.94986 12.3494 8.19211 12.1333C9.50993 10.9573 12.3241 8.39829 13.3147 7.1236C14.6261 5.43598 14.4197 3.03525 12.721 1.62334C11.0223 0.211438 8.82826 0.6156 7.49535 2.17388Z" d="M5.9987 7.33333L7.33203 8.66667L10.332 5.66667M7.99413 3.42388C6.66123 1.8656 4.43853 1.44643 2.76849 2.87334C1.09846 4.30026 0.863343 6.68598 2.17483 8.3736C3.16542 9.64829 5.97956 12.2073 7.29737 13.3833C7.53962 13.5994 7.66075 13.7075 7.80258 13.7501C7.92572 13.787 8.06255 13.787 8.18568 13.7501C8.32752 13.7075 8.44864 13.5994 8.69089 13.3833C10.0087 12.2073 12.8228 9.64829 13.8134 8.3736C15.1249 6.68598 14.9185 4.28525 13.2198 2.87334C11.521 1.46144 9.32703 1.8656 7.99413 3.42388Z"
stroke="white" stroke="white"
strokeWidth="1.5" strokeWidth="1.5"
strokeLinecap="round" strokeLinecap="round"

View File

@@ -16,7 +16,7 @@ const InvoiceIcon = ({ className, onClick }: InvoiceIconType) => {
> >
<path <path
d="M7.83317 7.41504H3.83317M5.1665 10.0817H3.83317M9.1665 4.74837H3.83317M11.8332 4.61504V11.5484C11.8332 12.6685 11.8332 13.2285 11.6152 13.6564C11.4234 14.0327 11.1175 14.3386 10.7412 14.5304C10.3133 14.7484 9.75328 14.7484 8.63317 14.7484H4.3665C3.2464 14.7484 2.68635 14.7484 2.25852 14.5304C1.8822 14.3386 1.57624 14.0327 1.38449 13.6564C1.1665 13.2285 1.1665 12.6685 1.1665 11.5484V4.61504C1.1665 3.49493 1.1665 2.93488 1.38449 2.50706C1.57624 2.13073 1.8822 1.82477 2.25852 1.63303C2.68635 1.41504 3.2464 1.41504 4.3665 1.41504H8.63317C9.75328 1.41504 10.3133 1.41504 10.7412 1.63303C11.1175 1.82477 11.4234 2.13073 11.6152 2.50706C11.8332 2.93488 11.8332 3.49493 11.8332 4.61504Z" d="M7.83317 7.41504H3.83317M5.1665 10.0817H3.83317M9.1665 4.74837H3.83317M11.8332 4.61504V11.5484C11.8332 12.6685 11.8332 13.2285 11.6152 13.6564C11.4234 14.0327 11.1175 14.3386 10.7412 14.5304C10.3133 14.7484 9.75328 14.7484 8.63317 14.7484H4.3665C3.2464 14.7484 2.68635 14.7484 2.25852 14.5304C1.8822 14.3386 1.57624 14.0327 1.38449 13.6564C1.1665 13.2285 1.1665 12.6685 1.1665 11.5484V4.61504C1.1665 3.49493 1.1665 2.93488 1.38449 2.50706C1.57624 2.13073 1.8822 1.82477 2.25852 1.63303C2.68635 1.41504 3.2464 1.41504 4.3665 1.41504H8.63317C9.75328 1.41504 10.3133 1.41504 10.7412 1.63303C11.1175 1.82477 11.4234 2.13073 11.6152 2.50706C11.8332 2.93488 11.8332 3.49493 11.8332 4.61504Z"
stroke="#666666" stroke="#333333"
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />

View File

@@ -0,0 +1,26 @@
interface NewRateIconType {
className?: string;
onClick?: () => void;
}
const NewRateIcon = ({ className, onClick }: NewRateIconType) => {
return (
<svg
width="28"
height="28"
viewBox="0 0 28 28"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<circle cx="14" cy="14" r="13.5" fill="black" />
<path
d="M19.2216 16.509C19.2946 16.436 19.3814 16.3995 19.4819 16.3995C19.5824 16.3995 19.6691 16.436 19.7421 16.509L20.9376 17.709C21.0106 17.782 21.0471 17.8705 21.0471 17.9745C21.0471 18.0785 21.0106 18.167 20.9376 18.24C20.8646 18.313 20.7761 18.3495 20.6721 18.3495C20.5681 18.3495 20.4796 18.313 20.4066 18.24L19.2066 17.0438C19.1336 16.9707 19.0996 16.8817 19.1046 16.7767C19.1096 16.6718 19.1491 16.5825 19.2216 16.509ZM19.7406 7.79775C19.8136 7.87075 19.8501 7.95975 19.8501 8.06475C19.8501 8.16975 19.8136 8.259 19.7406 8.3325L18.5444 9.528C18.4714 9.601 18.3846 9.6375 18.2841 9.6375C18.1836 9.6375 18.0969 9.601 18.0239 9.528C17.9509 9.455 17.9144 9.366 17.9144 9.261C17.9144 9.156 17.9509 9.06675 18.0239 8.99325L19.2096 7.79775C19.2826 7.72425 19.3711 7.6875 19.4751 7.6875C19.5791 7.6875 19.6676 7.72425 19.7406 7.79775ZM8.25963 7.812C8.33263 7.739 8.42188 7.7025 8.52737 7.7025C8.63238 7.7025 8.72162 7.739 8.79512 7.812L9.99062 8.997C10.0636 9.0705 10.1001 9.15925 10.1001 9.26325C10.1001 9.36675 10.0636 9.45525 9.99062 9.52875C9.91762 9.60175 9.82837 9.63825 9.72287 9.63825C9.61787 9.63825 9.52887 9.60175 9.45587 9.52875L8.25963 8.3325C8.18663 8.2595 8.15012 8.17275 8.15012 8.07225C8.15012 7.97175 8.18663 7.885 8.25963 7.812ZM8.77937 16.524C8.85237 16.597 8.88887 16.6838 8.88887 16.7843C8.88887 16.8847 8.85237 16.9715 8.77937 17.0445L7.58312 18.24C7.51012 18.313 7.42337 18.3495 7.32287 18.3495C7.22237 18.3495 7.13562 18.313 7.06262 18.24C6.98962 18.167 6.95312 18.0778 6.95312 17.9723C6.95312 17.8668 6.98962 17.7775 7.06262 17.7045L8.24762 16.509C8.32112 16.436 8.40987 16.4017 8.51387 16.4062C8.61737 16.4113 8.70587 16.451 8.77937 16.524ZM14.0001 17.4945L10.6139 19.527C10.5339 19.5665 10.4599 19.5825 10.3919 19.575C10.3234 19.567 10.2569 19.5435 10.1924 19.5045C10.1279 19.4645 10.0789 19.408 10.0454 19.335C10.0119 19.261 10.0089 19.1813 10.0364 19.0958L10.9341 15.2925L7.94687 12.7095C7.87937 12.6545 7.83737 12.5887 7.82087 12.5122C7.80387 12.4363 7.81087 12.363 7.84187 12.2925C7.87287 12.222 7.91412 12.1647 7.96562 12.1207C8.01712 12.0767 8.08713 12.047 8.17563 12.0315L12.1019 11.6985L13.6379 8.0865C13.6719 8.004 13.7209 7.9445 13.7849 7.908C13.8489 7.8715 13.9206 7.8535 14.0001 7.854C14.0796 7.8545 14.1516 7.87275 14.2161 7.90875C14.2806 7.94475 14.3294 8.00425 14.3624 8.08725L15.9126 11.6992L19.8389 12.0322C19.9274 12.0472 19.9974 12.0772 20.0489 12.1222C20.1009 12.1657 20.1424 12.2228 20.1734 12.2933C20.2044 12.3637 20.2114 12.437 20.1944 12.513C20.1774 12.5895 20.1351 12.6553 20.0676 12.7102L17.0811 15.2933L17.9781 19.1108C18.0061 19.1963 18.0031 19.2735 17.9691 19.3425C17.9351 19.4115 17.8861 19.4657 17.8221 19.5052C17.7576 19.5442 17.6914 19.5675 17.6234 19.575C17.5549 19.583 17.4806 19.5673 17.4006 19.5278L14.0001 17.4945Z"
fill="white"
/>
</svg>
);
};
export default NewRateIcon;

View File

@@ -17,7 +17,7 @@ const RateIcon = ({ className, onClick }: RateIconType) => {
<path <path
d="M26.4351 131.263C18.2009 129.563 9.45442 127.066 4.20797 120.496C-0.552335 114.535 -1.36302 105.7 2.23265 98.9722C7.4747 89.1632 20.3229 84.8319 24.4402 74.5003C27.401 67.0706 24.9679 58.7427 24.4263 50.7633C23.852 42.2992 25.5683 33.5602 30.0859 26.379C34.6035 19.1981 42.0585 13.7282 50.4398 12.4131C62.6515 10.4973 74.6822 17.2329 87.0409 17.0031C101.49 16.7343 113.941 7.17211 127.741 2.8807C135.732 0.395575 144.272 -0.303884 152.561 0.847569C160.267 1.91822 168.283 4.99429 172.479 11.5457C176.514 17.8476 176.178 25.8983 178.116 33.1264C180.159 40.7481 184.913 47.6136 191.329 52.2072C195.925 55.498 201.301 57.6369 205.675 61.218C209.591 64.4246 212.539 68.6589 215.048 73.0551C218.27 78.7035 220.892 84.8722 221.249 91.3654C221.937 103.879 213.897 115.647 203.429 122.538C192.961 129.429 180.37 132.237 167.99 134.188C158.175 135.734 148.289 136.826 138.373 137.458C128.55 138.083 118.471 138.613 108.638 137.94C105.646 137.735 102.889 136.912 99.9902 136.244C96.2772 135.389 92.5503 135.76 88.7856 135.959C78.9527 136.48 69.0879 136.405 59.2637 135.738C49.4395 135.072 39.6552 133.813 29.9831 131.967C28.7985 131.741 27.616 131.506 26.4351 131.263Z" d="M26.4351 131.263C18.2009 129.563 9.45442 127.066 4.20797 120.496C-0.552335 114.535 -1.36302 105.7 2.23265 98.9722C7.4747 89.1632 20.3229 84.8319 24.4402 74.5003C27.401 67.0706 24.9679 58.7427 24.4263 50.7633C23.852 42.2992 25.5683 33.5602 30.0859 26.379C34.6035 19.1981 42.0585 13.7282 50.4398 12.4131C62.6515 10.4973 74.6822 17.2329 87.0409 17.0031C101.49 16.7343 113.941 7.17211 127.741 2.8807C135.732 0.395575 144.272 -0.303884 152.561 0.847569C160.267 1.91822 168.283 4.99429 172.479 11.5457C176.514 17.8476 176.178 25.8983 178.116 33.1264C180.159 40.7481 184.913 47.6136 191.329 52.2072C195.925 55.498 201.301 57.6369 205.675 61.218C209.591 64.4246 212.539 68.6589 215.048 73.0551C218.27 78.7035 220.892 84.8722 221.249 91.3654C221.937 103.879 213.897 115.647 203.429 122.538C192.961 129.429 180.37 132.237 167.99 134.188C158.175 135.734 148.289 136.826 138.373 137.458C128.55 138.083 118.471 138.613 108.638 137.94C105.646 137.735 102.889 136.912 99.9902 136.244C96.2772 135.389 92.5503 135.76 88.7856 135.959C78.9527 136.48 69.0879 136.405 59.2637 135.738C49.4395 135.072 39.6552 133.813 29.9831 131.967C28.7985 131.741 27.616 131.506 26.4351 131.263Z"
fill="#FFB700" fill="#FFB700"
fill-opacity="0.12" fillOpacity="0.12"
/> />
<path <path
d="M149.572 150H69.6144C67.3832 150 65.5576 148.174 65.5576 145.943V145.546C65.5576 143.315 67.3832 141.489 69.6144 141.489H149.572C151.803 141.489 153.629 143.315 153.629 145.546V145.943C153.629 148.174 151.803 150 149.572 150Z" d="M149.572 150H69.6144C67.3832 150 65.5576 148.174 65.5576 145.943V145.546C65.5576 143.315 67.3832 141.489 69.6144 141.489H149.572C151.803 141.489 153.629 143.315 153.629 145.546V145.943C153.629 148.174 151.803 150 149.572 150Z"
@@ -42,22 +42,22 @@ const RateIcon = ({ className, onClick }: RateIconType) => {
<path <path
d="M142.231 13.9854C142.231 10.7734 139.603 8.14526 136.391 8.14526H134.134C114.797 23.8846 96.1683 40.5151 79.0111 58.5438C78.5128 59.0675 78.0168 59.5935 77.5195 60.1188V89.5343C80.7173 86.54 83.9063 83.5363 87.0804 80.5174C88.8163 78.8666 90.5472 77.2104 92.2788 75.5549C108.944 60.8335 125.605 46.108 142.231 31.3423L142.231 13.9854Z" d="M142.231 13.9854C142.231 10.7734 139.603 8.14526 136.391 8.14526H134.134C114.797 23.8846 96.1683 40.5151 79.0111 58.5438C78.5128 59.0675 78.0168 59.5935 77.5195 60.1188V89.5343C80.7173 86.54 83.9063 83.5363 87.0804 80.5174C88.8163 78.8666 90.5472 77.2104 92.2788 75.5549C108.944 60.8335 125.605 46.108 142.231 31.3423L142.231 13.9854Z"
fill="#FFB700" fill="#FFB700"
fill-opacity="0.12" fillOpacity="0.12"
/> />
<path <path
d="M95.8987 119.961C93.5809 122.338 91.2756 124.728 88.9967 127.149C85.3554 131.016 81.8398 134.99 78.3311 138.969C79.3493 140.693 81.2222 141.859 83.3584 141.859H99.6052C101.571 139.656 103.547 137.461 105.543 135.286C111.156 129.168 116.897 123.176 122.743 117.288C129.246 111.064 135.741 104.833 142.23 98.5942V76.4097C137.105 80.4905 132.061 84.6713 127.162 89.03C116.203 98.7802 105.84 109.178 95.8987 119.961Z" d="M95.8987 119.961C93.5809 122.338 91.2756 124.728 88.9967 127.149C85.3554 131.016 81.8398 134.99 78.3311 138.969C79.3493 140.693 81.2222 141.859 83.3584 141.859H99.6052C101.571 139.656 103.547 137.461 105.543 135.286C111.156 129.168 116.897 123.176 122.743 117.288C129.246 111.064 135.741 104.833 142.23 98.5942V76.4097C137.105 80.4905 132.061 84.6713 127.162 89.03C116.203 98.7802 105.84 109.178 95.8987 119.961Z"
fill="#FFB700" fill="#FFB700"
fill-opacity="0.12" fillOpacity="0.12"
/> />
<path <path
d="M142.231 116.867C136.361 122.2 130.579 127.631 124.896 133.164C121.948 136.035 119.027 138.934 116.13 141.858H130.067C132.121 139.96 134.17 138.057 136.206 136.14C138.217 134.247 140.225 132.35 142.231 130.453V116.867Z" d="M142.231 116.867C136.361 122.2 130.579 127.631 124.896 133.164C121.948 136.035 119.027 138.934 116.13 141.858H130.067C132.121 139.96 134.17 138.057 136.206 136.14C138.217 134.247 140.225 132.35 142.231 130.453V116.867Z"
fill="#FFB700" fill="#FFB700"
fill-opacity="0.12" fillOpacity="0.12"
/> />
<path <path
d="M114.568 8.14502H100.676C92.7524 14.6636 85.003 21.3827 77.5195 28.407V37.3576C83.8437 32.2819 90.1625 27.196 96.5533 22.2048C102.559 17.5142 108.55 12.811 114.568 8.14502Z" d="M114.568 8.14502H100.676C92.7524 14.6636 85.003 21.3827 77.5195 28.407V37.3576C83.8437 32.2819 90.1625 27.196 96.5533 22.2048C102.559 17.5142 108.55 12.811 114.568 8.14502Z"
fill="#FFB700" fill="#FFB700"
fill-opacity="0.12" fillOpacity="0.12"
/> />
<path <path
d="M111.138 11.8819C111.138 12.6409 110.523 13.2561 109.764 13.2561C109.005 13.2561 108.39 12.6409 108.39 11.8819C108.39 11.1229 109.005 10.5073 109.764 10.5073C110.523 10.5073 111.138 11.1226 111.138 11.8819Z" d="M111.138 11.8819C111.138 12.6409 110.523 13.2561 109.764 13.2561C109.005 13.2561 108.39 12.6409 108.39 11.8819C108.39 11.1229 109.005 10.5073 109.764 10.5073C110.523 10.5073 111.138 11.1226 111.138 11.8819Z"

View File

@@ -1,9 +1,10 @@
interface TimeIconType { interface TimeIconType {
className?: string; className?: string;
onClick?: () => void; onClick?: () => void;
color?: string;
} }
const TimeIcon = ({ className, onClick }: TimeIconType) => { const TimeIcon = ({ className, onClick, color }: TimeIconType) => {
return ( return (
<svg <svg
width="17" width="17"
@@ -17,7 +18,7 @@ const TimeIcon = ({ className, onClick }: TimeIconType) => {
<g clipPath="url(#clip0_2448_7814)"> <g clipPath="url(#clip0_2448_7814)">
<path <path
d="M8.50016 4.08171V8.08171L11.1668 9.41504M15.1668 8.08171C15.1668 11.7636 12.1821 14.7484 8.50016 14.7484C4.81826 14.7484 1.8335 11.7636 1.8335 8.08171C1.8335 4.39981 4.81826 1.41504 8.50016 1.41504C12.1821 1.41504 15.1668 4.39981 15.1668 8.08171Z" d="M8.50016 4.08171V8.08171L11.1668 9.41504M15.1668 8.08171C15.1668 11.7636 12.1821 14.7484 8.50016 14.7484C4.81826 14.7484 1.8335 11.7636 1.8335 8.08171C1.8335 4.39981 4.81826 1.41504 8.50016 1.41504C12.1821 1.41504 15.1668 4.39981 15.1668 8.08171Z"
stroke="#666666" stroke={color || "#333333"}
strokeLinecap="round" strokeLinecap="round"
strokeLinejoin="round" strokeLinejoin="round"
/> />

View File

@@ -8,6 +8,8 @@ interface InputCardProps {
name: string; name: string;
placeholder: string; placeholder: string;
value: string; value: string;
required?: boolean;
reuireqMessage?: string;
} }
export default function InputCard({ export default function InputCard({
@@ -15,6 +17,8 @@ export default function InputCard({
name, name,
placeholder, placeholder,
value, value,
required = false,
reuireqMessage = "",
}: InputCardProps) { }: InputCardProps) {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
@@ -22,17 +26,16 @@ export default function InputCard({
}; };
return ( return (
<> <>
<ProInputCard title={title} dividerStyle={{ margin: "5px 0 0 0" }}> <ProInputCard title={title}>
<Form.Item <Form.Item
label={title}
name={name} name={name}
style={{ position: "relative", top: -5 }} rules={[{ required, message: reuireqMessage }]}
> >
<Input <Input
placeholder={placeholder} placeholder={placeholder}
size="large" size="large"
autoFocus={false} autoFocus={false}
style={{ padding: "7px 11px", height: 50, borderRadius: 888 }} style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
value={value} value={value}
onChange={handleChange} onChange={handleChange}
/> />

View File

@@ -0,0 +1,20 @@
import { InputNumber, InputNumberProps } from "antd";
export const ProInputNumber = (props: InputNumberProps) => {
return (
<InputNumber
style={{
fontFamily: "Outfit",
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
height: 48,
width: "100%",
}}
min={"0"}
{...props}
/>
);
};

View File

@@ -3,16 +3,25 @@
flex-direction: row; flex-direction: row;
justify-content: end; justify-content: end;
padding: 0 16px; padding: 0 16px;
gap: 10px; gap: 8px;
position: absolute; position: absolute;
top: 50px; top: 50px;
z-index: 1000; z-index: 1000;
right: 0;
} }
:global(.ant-app-rtl) .languageSwitch { :global(.ant-app-rtl) .languageSwitch {
left: 0; left: 0;
right: auto;
} }
:global(.ant-app-ltr) .languageSwitch { .refreshIcon {
right: 0; cursor: pointer;
position: relative;
top: 2px;
margin-right: 3px;
}
:global(.ant-app-rtl) .refreshIcon {
margin-left: 3px;
} }

View File

@@ -1,4 +1,4 @@
import { GlobalOutlined, ShakeOutlined } from "@ant-design/icons"; import { GlobalOutlined } from "@ant-design/icons";
import { setLocale, setLocalesThunk } from "features/locale/localeSlice"; import { setLocale, setLocalesThunk } from "features/locale/localeSlice";
import i18n from "i18n/i18n"; import i18n from "i18n/i18n";
import { useTransition } from "react"; import { useTransition } from "react";
@@ -6,6 +6,7 @@ import { useDispatch } from "react-redux";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import ProText from "../ProText"; import ProText from "../ProText";
import styles from "./LanguageSwitch.module.css"; import styles from "./LanguageSwitch.module.css";
import RefershIcon from "components/Icons/RefershIcon";
export function LanguageSwitch() { export function LanguageSwitch() {
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -23,11 +24,15 @@ export function LanguageSwitch() {
}); });
}; };
const refreshPage = () => {
window.location.reload();
};
return ( return (
<div className={styles.languageSwitch}> <div className={styles.languageSwitch}>
<GlobalOutlined <GlobalOutlined
style={{ style={{
color: themeName === "dark" ? "#fff" : "#434E5C", color: themeName === "dark" ? "#fff" : "#333333",
fontSize: 15, fontSize: 15,
marginRight: 3, marginRight: 3,
cursor: isPending ? "wait" : "pointer", cursor: isPending ? "wait" : "pointer",
@@ -37,23 +42,19 @@ export function LanguageSwitch() {
/> />
<ProText <ProText
style={{ style={{
color: themeName === "dark" ? "#fff" : "#434E5C", color: themeName === "dark" ? "#fff" : "#333333",
fontSize: 14, fontSize: 14,
marginRight: 3, marginRight: 3,
opacity: isPending ? 0.7 : 1, opacity: isPending ? 0.7 : 1,
[isRTL ? "marginLeft" : "marginRight"]: 3, [isRTL ? "marginLeft" : "marginRight"]: 3,
}} }}
onClick={changeLanguage}
> >
{isPending ? "..." : isRTL ? "English" : "Arabic"} {isPending ? "..." : isRTL ? "English" : "Arabic"}
</ProText> </ProText>
<ShakeOutlined <div onClick={refreshPage}>
style={{ <RefershIcon dimension={18} className={styles.refreshIcon} />
color: themeName === "dark" ? "#fff" : "#434E5C", </div>
fontSize: 15,
marginRight: 3,
[isRTL ? "marginLeft" : "marginRight"]: 3,
}}
/>
</div> </div>
); );
} }

View File

@@ -10,7 +10,7 @@
background-color: var(--secondary-background); background-color: var(--secondary-background);
border: none; border: none;
margin-bottom: 1rem; margin-bottom: 1rem;
margin: 16px; margin: 0 16px 20px 16px;
border-radius: 6px; border-radius: 6px;
} }

View File

@@ -1,41 +1,26 @@
import { Card, Col, Image, Row } from "antd"; import { Card, Col, Image, Row } from "antd";
import LoyaltyIcon from "components/Icons/cart/LoyaltyIcons";
import PresentIcon from "components/Icons/cart/PresentIcon";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useParams } from "react-router-dom"; import { Link, useParams } from "react-router-dom";
import { useGetRestaurantDetailsQuery } from "redux/api/others";
import { ACCESS_TOKEN } from "utils/constants"; import { ACCESS_TOKEN } from "utils/constants";
import { colors } from "ThemeConstants.ts"; import { colors } from "ThemeConstants.ts";
import ProText from "../ProText"; import ProText from "../ProText";
import styles from "./LoyaltyCard.module.css"; import styles from "./LoyaltyCard.module.css";
import { useAppSelector } from "redux/hooks";
const LoyaltyCard = () => { const LoyaltyCard = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { subdomain } = useParams(); const { subdomain } = useParams();
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain); const { restaurant } = useAppSelector((state) => state.order);
const { isRTL } = useAppSelector((state) => state.locale);
const token = localStorage.getItem(ACCESS_TOKEN); const token = localStorage.getItem(ACCESS_TOKEN);
const isHasLoyaltyGift = const loyaltyStamps = restaurant?.loyalty_stamps ?? 0;
(restaurant?.loyalty_stamps || 0) - const customerLoyaltyPoints = restaurant?.customer_loyalty_points ?? 0;
(restaurant?.customer_loyalty_points || 0) <= const remainingToNextReward =
0; loyaltyStamps - (customerLoyaltyPoints % loyaltyStamps);
return ( return (
<div className={styles.loyaltyContainer}> <div className={styles.loyaltyContainer}>
<Card className={styles.loyaltyCard}> <Card className={styles.loyaltyCard}>
{isHasLoyaltyGift && (
<div className={styles.congratulationsContainer}>
<PresentIcon className={styles.congratulationsIcon} />
<div>
<p className={styles.congratulationsText}>
{t("menu.congratulations")}
</p>
<p className={styles.congratulationsSubtext}>
{t("menu.loyaltyGiftEarned")}
</p>
</div>
</div>
)}
{!isHasLoyaltyGift && (
<Row <Row
justify="space-between" justify="space-between"
align="middle" align="middle"
@@ -43,11 +28,20 @@ const LoyaltyCard = () => {
> >
<Col> <Col>
<Row align="middle" gutter={[8, 8]}> <Row align="middle" gutter={[8, 8]}>
<Col> {/* <Col>
<LoyaltyIcon className={styles.loyaltyIcon} /> <LoyaltyIcon className={styles.loyaltyIcon} />
</Col> </Col> */}
<Col> <Col>
<ProText style={{ fontSize: "1rem", fontWeight: 400 }}> <ProText
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: "16px",
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{t("menu.loyaltyPoints")} {t("menu.loyaltyPoints")}
</ProText> </ProText>
</Col> </Col>
@@ -56,15 +50,24 @@ const LoyaltyCard = () => {
type="secondary" type="secondary"
strong strong
style={{ style={{
fontSize: "14px", fontWeight: 400,
fontWeight: 700, fontStyle: "Regular",
position: "relative", fontSize: "12px",
lineHeight: "140%",
letterSpacing: "0%",
top: -2, top: -2,
color: "#777580",
}} }}
> >
{token && {token &&
customerLoyaltyPoints < loyaltyStamps &&
t("menu.loyaltyDescription", { t("menu.loyaltyDescription", {
value: restaurant?.loyalty_stamps ?? 0, value: loyaltyStamps,
})}
{token &&
customerLoyaltyPoints >= loyaltyStamps &&
t("menu.youHaveXEarnedRewardsReadyToRedeem", {
rewards: Math.floor(customerLoyaltyPoints / loyaltyStamps),
})} })}
{!token && ( {!token && (
<div style={{ paddingTop: 4 }}> <div style={{ paddingTop: 4 }}>
@@ -75,34 +78,43 @@ const LoyaltyCard = () => {
textDecoration: "underline", textDecoration: "underline",
fontSize: "14px", fontSize: "14px",
fontWeight: 400, fontWeight: 400,
padding: "0 4px", padding: isRTL ? "0 0 0 4px" : "0 4px 0 0",
}} }}
> >
{t("menu.joinUs")} {t("menu.joinUs")}
</Link> </Link>
<span style={{ fontSize: "14px", color: colors.secondary }}> <span
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: "12px",
lineHeight: "140%",
letterSpacing: "0%",
color: "#777580",
}}
>
{t("menu.joinUsDescription")} {t("menu.joinUsDescription")}
</span> </span>
</div> </div>
)} )}
</ProText> </ProText>
</Col> </Col>
<Col>{/* <PresentIcon /> */}</Col> {/* <Col> {isHasLoyaltyGift && <PresentIcon />}</Col> */}
</Row> </Row>
)} {token && (
{token && !isHasLoyaltyGift && ( <>
<div <div
style={{ style={{
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
gap: 10, gap: 12,
overflow: "auto", overflow: "auto",
scrollbarWidth: "none", scrollbarWidth: "none",
paddingBottom: 12,
}} }}
> >
{Array.from({ length: restaurant?.loyalty_stamps || 0 }).map( {Array.from({ length: loyaltyStamps }).map((_, index) => {
(_, index) => { const currentPoints = customerLoyaltyPoints % loyaltyStamps;
const currentPoints = restaurant?.customer_loyalty_points || 0;
const isCollected = index < currentPoints; const isCollected = index < currentPoints;
return ( return (
<Col key={index}> <Col key={index}>
@@ -119,9 +131,30 @@ const LoyaltyCard = () => {
/> />
</Col> </Col>
); );
}, })}
)}
</div> </div>
<ProText
strong
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: "12px",
lineHeight: "140%",
letterSpacing: "0%",
color:
customerLoyaltyPoints < loyaltyStamps ? "#777580" : "#32AD6D",
}}
>
{customerLoyaltyPoints < loyaltyStamps &&
t("menu.justXMorePurchasesToUnlockYourFREEItem", {
cups: remainingToNextReward,
})}
{customerLoyaltyPoints >= loyaltyStamps &&
t("menu.youreJustXCupsAwayFromYourNextReward", {
cups: remainingToNextReward,
})}
</ProText>
</>
)} )}
</Card> </Card>
</div> </div>

View File

@@ -11,10 +11,11 @@
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
font-size: 16px; font-size: 16px;
min-height: 24px;
} }
.summaryDivider { .summaryDivider {
margin: 8px 0 !important; margin: 0 !important;
} }
.totalRow { .totalRow {

View File

@@ -1,4 +1,4 @@
import { Card, Checkbox, Divider, Space } from "antd"; import { Card, Divider, Space, Tag } from "antd";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import { import {
selectCart, selectCart,
@@ -6,18 +6,17 @@ import {
selectDiscountTotal, selectDiscountTotal,
selectGrandTotal, selectGrandTotal,
selectHighestPricedLoyaltyItem, selectHighestPricedLoyaltyItem,
selectLoyaltyValidation, totalTaxes,
selectTaxAmount,
updateUseLoyaltyPoints,
} from "features/order/orderSlice"; } from "features/order/orderSlice";
import { OrderType } from "pages/checkout/hooks/types"; import { OrderType } from "pages/checkout/hooks/types";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import { useGetRestaurantDetailsQuery } from "redux/api/others"; import { useGetRestaurantDetailsQuery } from "redux/api/others";
import { useAppDispatch, useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import ProText from "../ProText"; import ProText from "../ProText";
import ProTitle from "../ProTitle"; import ProTitle from "../ProTitle";
import styles from "./OrderSummary.module.css"; import styles from "./OrderSummary.module.css";
import { CSSProperties, useMemo } from "react";
export default function OrderSummary() { export default function OrderSummary() {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -25,11 +24,9 @@ export default function OrderSummary() {
const { subdomain } = useParams(); const { subdomain } = useParams();
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain); const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain);
const { orderType } = useAppSelector(selectCart); const { orderType } = useAppSelector(selectCart);
const dispatch = useAppDispatch();
const subtotal = useAppSelector(selectCartTotal); const subtotal = useAppSelector(selectCartTotal);
const loyaltyValidation = useAppSelector(selectLoyaltyValidation);
const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem); const highestLoyaltyItem = useAppSelector(selectHighestPricedLoyaltyItem);
const taxAmount = useAppSelector(selectTaxAmount); const totalTaxesAmount = useAppSelector(totalTaxes);
const grandTotal = useAppSelector(selectGrandTotal); const grandTotal = useAppSelector(selectGrandTotal);
const discountAmount = useAppSelector(selectDiscountTotal); const discountAmount = useAppSelector(selectDiscountTotal);
@@ -38,79 +35,214 @@ export default function OrderSummary() {
(restaurant?.customer_loyalty_points ?? 0) <= (restaurant?.customer_loyalty_points ?? 0) <=
0; 0;
const titlesStyle: CSSProperties = {
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
};
const vat = ((restaurant?.vat ?? 0) / 100) * (subtotal - discountAmount) || 0;
// Calculate individual taxes
const taxesList = useMemo(() => {
if (!restaurant?.taxes) return [];
const subtotalAfterDiscount = subtotal - discountAmount;
return restaurant.taxes
.filter((tax) => tax.is_active === 1)
.map((tax) => {
const amount = ((Number(tax.percentage) || 0) / 100) * subtotalAfterDiscount;
return {
id: tax.id,
name: tax.name,
name_local: tax.name_local,
percentage: tax.percentage,
amount,
};
});
}, [restaurant?.taxes, subtotal, discountAmount]);
return ( return (
<> <>
<Card className={`${styles.orderSummary}`}> <Card className={`${styles.orderSummary}`}>
<ProTitle style={{ fontSize: 18 }}>{t("cart.orderSummary")}</ProTitle> <ProTitle
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 18,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{t("cart.orderSummary")}
</ProTitle>
<Divider style={{ margin: "15px 0 15px 0" }} /> <Divider style={{ margin: "15px 0 15px 0" }} />
<Space orientation="vertical" style={{ width: "100%" }}> <Space orientation="vertical" style={{ width: "100%", gap: 16 }}>
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.basketTotal")}</ProText> <ProText type="secondary" style={titlesStyle}>
<ArabicPrice price={subtotal} /> {t("cart.basketTotal")}
</ProText>
<ArabicPrice price={subtotal} textStyle={titlesStyle} />
</div> </div>
{orderType === OrderType.Delivery && ( {orderType === OrderType.Delivery && (
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.deliveryFee")}</ProText> <ProText type="secondary" style={titlesStyle}>
<ArabicPrice price={Number(restaurant?.delivery_fees || 0)} /> {t("cart.deliveryFee")}
</ProText>
<ArabicPrice
price={Number(restaurant?.delivery_fees || 0)}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div> </div>
)} )}
{orderType !== OrderType.Redeem && (
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.discount")}</ProText> <ProText type="secondary" style={titlesStyle}>
<ArabicPrice price={discountAmount} /> {t("cart.discount")}
</ProText>
<div
style={{
display: "flex",
alignItems: "center",
minHeight: "24px",
}}
>
{isHasLoyaltyGift &&
useLoyaltyPoints &&
highestLoyaltyItem &&
restaurant?.is_loyalty_enabled === 1 ? (
<Tag
color="green"
style={{
backgroundColor: "#EDFEF5",
borderRadius: "4px",
padding: "3px 10px",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
placeItems: "center",
margin: "0 10px",
position: "relative",
top: -1,
}}
>
{t("cart.loyalty")}
</Tag>
) : null}
<ArabicPrice
price={discountAmount + (useLoyaltyPoints && restaurant?.is_loyalty_enabled === 1 ? ((highestLoyaltyItem?.price || 0) * (totalTaxesAmount / 100)) : 0)}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div> </div>
<div className={styles.summaryRow}>
<ProText type="secondary">{t("cart.tax")}</ProText>
<ArabicPrice price={taxAmount || 0} />
</div> </div>
{splitBillAmount > 0 && ( )}
{/* {orderType !== OrderType.Redeem && (
<div className={styles.summaryRow}> <div className={styles.summaryRow}>
<ProText type="secondary"> <ProText type="secondary" style={titlesStyle}>
{t("cart.tip")}
</ProText>
<ArabicPrice
price={tip || 0}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
)} */}
{orderType === OrderType.Redeem && (
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.giftedItems")}
</ProText>
<ArabicPrice
price={0}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
)}
{orderType === OrderType.Redeem && (
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.voucherApplied")}
</ProText>
<ArabicPrice
price={0}
textStyle={{ ...titlesStyle, color: "#32AD6D" }}
/>
</div>
)}
{orderType !== OrderType.Redeem && restaurant?.vat && (
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.vat", { value: restaurant.vat })}
</ProText>
<ArabicPrice
price={vat}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
)}
{orderType !== OrderType.Redeem &&
taxesList.map((tax) => (
<div key={tax.id} className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.otherTaxes", {
name: tax.name || tax.name_local,
value: tax.percentage,
})}
</ProText>
<ArabicPrice
price={tax.amount}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
))}
{orderType !== OrderType.Redeem && splitBillAmount > 0 && (
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("splitBill.splitBillAmount")} {t("splitBill.splitBillAmount")}
</ProText> </ProText>
<ArabicPrice price={splitBillAmount} /> <ArabicPrice
price={splitBillAmount}
textStyle={{ ...titlesStyle, color: "#434E5C" }}
/>
</div> </div>
)} )}
<Divider className={styles.summaryDivider} /> <Divider className={styles.summaryDivider} />
<div className={`${styles.summaryRow} ${styles.totalRow}`}> <div className={`${styles.summaryRow} ${styles.totalRow}`}>
<ProText strong type="secondary"> <ProText
{t("cart.totalAmount")} style={{
</ProText> fontWeight: 600,
<ArabicPrice price={grandTotal} /> fontStyle: "SemiBold",
</div> fontSize: 18,
</Space> lineHeight: "140%",
letterSpacing: "0%",
{isHasLoyaltyGift && restaurant?.is_loyalty_enabled === 1 && ( textAlign: "center",
<> color: "#333333",
<br />
<br />
<Checkbox
checked={useLoyaltyPoints}
onChange={(value) => {
dispatch(updateUseLoyaltyPoints(value.target.checked));
}} }}
> >
{t("cart.useLoyaltyPoints")} {orderType === OrderType.Redeem
</Checkbox> ? t("cart.remainingToPay")
</> : t("cart.totalAmount")}
)} </ProText>
<ArabicPrice
{isHasLoyaltyGift && loyaltyValidation.errorMessage && ( price={grandTotal}
<div style={{ marginTop: 8, color: "red", fontSize: "12px" }}> textStyle={{
{t(loyaltyValidation.errorMessage)} fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 18,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
}}
/>
</div> </div>
)} </Space>
{isHasLoyaltyGift &&
useLoyaltyPoints &&
highestLoyaltyItem &&
restaurant?.is_loyalty_enabled === 1 && (
<div style={{ marginTop: 8, color: "green", fontSize: "12px" }}>
{t("cart.loyaltyDiscountApplied", {
itemName: highestLoyaltyItem.name,
amount: Math.round(highestLoyaltyItem.price || 0).toFixed(2),
})}
</div>
)}
</Card> </Card>
</> </>
); );

View File

@@ -12,12 +12,35 @@ export default function PaymentDetails({ order }: { order?: Order }) {
return ( return (
<> <>
<Card className={`${styles.orderSummary}`}> <Card className={`${styles.orderSummary}`}>
<ProTitle style={{ fontSize: 18 }}>{t("cart.orderSummary")}</ProTitle> <ProTitle style={{ fontSize: 18 }}>{t("cart.paymentSummary")}</ProTitle>
<Divider style={{ margin: "15px 0 15px 0" }} /> <Divider style={{ margin: "15px 0 15px 0" }} />
<Space direction="vertical" style={{ width: "100%" }}> <Space orientation="vertical" style={{ width: "100%" }}>
<div className={`${styles.summaryRow} ${styles.totalRow}`}> <div className={`${styles.summaryRow} ${styles.totalRow}`}>
<ProText strong>{t("cart.totalAmount")}</ProText> <ProText
<ArabicPrice price={order?.total_price || 0} strong /> style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#333333",
}}
>
{t("cart.totalAmount")}
</ProText>
<ArabicPrice
price={order?.total_price || 0}
textStyle={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#434E5C",
}}
/>
</div> </div>
</Space> </Space>
</Card> </Card>

View File

@@ -24,7 +24,105 @@
.differentCardIcon { .differentCardIcon {
position: relative; position: relative;
top: 1px top: 1px;
} }
.eCardIcon {
position: relative;
top: 3px;
margin-right: 4px;
margin-left: 0;
}
:global(.ant-app-rtl) .eCardIcon {
margin-right: 0;
margin-left: 4px;
}
/* Make AntD checkbox look like a circular check indicator (scoped via CSS modules) */
.circleCheckbox :global(.ant-checkbox-inner) {
width: 24px;
height: 24px;
border-radius: 50% !important;
border: 1.5px solid #d5d8da;
background: transparent;
}
.circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner) {
border-radius: 50% !important;
background: transparent;
border-color: #ffb700;
}
/* Replace AntD checkmark with a filled inner circle when checked (match SVG) */
.circleCheckbox :global(.ant-checkbox-inner::after) {
content: "";
border: 0 !important;
transform: none !important;
width: 0;
height: 0;
left: 50%;
top: 50%;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-checkbox-inner::after) {
left: auto;
right: 50%;
}
.circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner::after) {
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
background: #ffb700;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner::after) {
margin-left: auto;
margin-right: -9px;
}
/* Apply same circular style to Radio buttons */
.circleCheckbox :global(.ant-radio-inner) {
width: 24px;
height: 24px;
border-radius: 50% !important;
border: 1.5px solid #d5d8da;
background: transparent;
}
.circleCheckbox :global(.ant-radio-checked .ant-radio-inner) {
border-radius: 50% !important;
background: transparent;
border-color: #ffb700;
}
.circleCheckbox :global(.ant-radio-inner::after) {
content: "";
border: 0 !important;
transform: none !important;
width: 0;
height: 0;
left: 50%;
top: 50%;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-radio-inner::after) {
left: auto;
right: 50%;
}
.circleCheckbox :global(.ant-radio-checked .ant-radio-inner::after) {
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
background: #ffb700;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-radio-checked .ant-radio-inner::after) {
margin-left: auto;
margin-right: -9px;
}

View File

@@ -1,7 +1,7 @@
import { Form, Radio, Space } from "antd"; import { Form, Image, Radio, Space } from "antd";
import { Group } from "antd/es/radio"; import { Group } from "antd/es/radio";
import ArabicPrice from "components/ArabicPrice"; import ArabicPrice from "components/ArabicPrice";
import DifferentCardIcon from "components/Icons/paymentMethods/DifferentCardIcon"; // import DifferentCardIcon from "components/Icons/paymentMethods/DifferentCardIcon";
import ProText from "components/ProText"; import ProText from "components/ProText";
import { import {
selectCart, selectCart,
@@ -14,15 +14,18 @@ import { colors, ProGray1 } from "../../ThemeConstants";
import ProInputCard from "../ProInputCard/ProInputCard"; import ProInputCard from "../ProInputCard/ProInputCard";
import styles from "./PaymentMethods.module.css"; import styles from "./PaymentMethods.module.css";
import { OrderType } from "pages/checkout/hooks/types.ts"; import { OrderType } from "pages/checkout/hooks/types.ts";
import { formatPriceUi } from "utils/helpers";
const PaymentMethods = () => { const PaymentMethods = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { paymentMethod, orderType } = useAppSelector(selectCart); const { paymentMethod, orderType } = useAppSelector(selectCart);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const grandTotal = useAppSelector(selectGrandTotal); const grandTotal = useAppSelector(selectGrandTotal);
const { restaurant } = useAppSelector((state) => state.order);
// const { isRTL } = useAppSelector((state) => state.locale);
const options: { const options: {
label: string; label: React.ReactNode;
value: string; value: string;
price?: string; price?: string;
icon?: React.ReactNode; icon?: React.ReactNode;
@@ -32,39 +35,70 @@ const PaymentMethods = () => {
...(orderType !== OrderType.Gift ...(orderType !== OrderType.Gift
? [ ? [
{ {
label: t("checkout.cash"), label: (
<>
<ProText
style={{
color: "#E8B400",
// [isRTL ? "marginLeft" : "marginRight"]: 4,
}}
>
{/* $ */}
</ProText>
<ProText style={{ color: "#5F6C7B" }}>
{t("checkout.cash")}
</ProText>
</>
),
value: "cash", value: "cash",
price: grandTotal.toString(), price: formatPriceUi(grandTotal, restaurant.currency_decimals ?? 3),
style: { style: {
color: colors.primary, color: colors.primary,
}, },
}, },
] ]
: []), : []),
// {
// label: t("checkout.creditDebitCard"),
// value: "creditDebitCard",
// price: t("checkout.expiresIn") + ":12/26",
// hideCurrency: true,
// },
{ {
label: t("checkout.creditDebitCard"), label: (
value: "creditDebitCard", <>
price: t("checkout.expiresIn") + ":12/26", {/* <RCardIcon className={styles.eCardIcon} /> */}
hideCurrency: true, <ProText style={{ color: "#5F6C7B" }}>
}, {t("checkout.thawani")}
{ </ProText>
label: t("checkout.differentCard"), </>
value: "differentCard", ),
value: "thawani",
icon: ( icon: (
<div className={styles.differentCardIcon}> <div className={styles.differentCardIcon}>
<DifferentCardIcon /> <Image
preview={false}
src={"thawani.png"}
alt="thawani"
width={24}
height={24}
style={{
position: "relative",
bottom: 3,
}}
/>
</div> </div>
), ),
hideCurrency: true, hideCurrency: true,
}, },
{ // {
label: t("checkout.fascanoWallet"), // label: t("checkout.fascanoWallet"),
value: "fascanoWallet", // value: "fascanoWallet",
price: "7.50", // price: "7.50",
style: { // style: {
color: colors.primary, // color: colors.primary,
}, // },
}, // },
]; ];
const onPaymentSelect = (value: string) => { const onPaymentSelect = (value: string) => {
@@ -88,15 +122,16 @@ const PaymentMethods = () => {
}} }}
size="large" size="large"
> >
<Space orientation="vertical" style={{ width: "100%" }}> <Space orientation="vertical" style={{ width: "100%", gap: 16 }}>
{options.map((option) => ( {options.map((option) => (
<div key={option.value}> <div key={option.value}>
<Radio <Radio
key={option.value} key={option.value}
value={option.value} value={option.value}
onClick={() => onPaymentSelect(option.value)} onClick={() => onPaymentSelect(option.value)}
className={styles.circleCheckbox}
style={{ style={{
height: 50, height: 48,
borderRadius: 888, borderRadius: 888,
border: "1px solid #DDD", border: "1px solid #DDD",
padding: 16, padding: 16,

View File

@@ -2,6 +2,7 @@ import { CloseOutlined } from "@ant-design/icons";
import { Button } from "antd"; import { Button } from "antd";
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import { useAppSelector } from "redux/hooks"; import { useAppSelector } from "redux/hooks";
import { darkColors, lightColors } from "ThemeConstants";
interface ProBottomSheetProps { interface ProBottomSheetProps {
isOpen: boolean; isOpen: boolean;
@@ -112,24 +113,43 @@ export function ProBottomSheet({
[isDragging, currentSnap, snapPoints.length, onClose], [isDragging, currentSnap, snapPoints.length, onClose],
); );
// Track if drag started from handle/header area
const dragStartElementRef = useRef<HTMLElement | null>(null);
// Touch event handlers // Touch event handlers
const handleTouchStart = useCallback( const handleTouchStart = useCallback(
(e: React.TouchEvent) => { (e: React.TouchEvent) => {
// Only allow drag if starting from handle or header area
const target = e.target as HTMLElement;
const isHandle = target.closest('[style*="cursor: grab"]') !== null;
const isHeader = target.closest('[style*="borderBottom"]') !== null;
if (isHandle || isHeader) {
dragStartElementRef.current = target;
startDrag(e.touches[0].clientY); startDrag(e.touches[0].clientY);
}
}, },
[startDrag], [startDrag],
); );
const handleTouchMove = useCallback( const handleTouchMove = useCallback(
(e: React.TouchEvent) => { (e: React.TouchEvent) => {
// Only handle drag if it started from handle/header
if (!isDragging || !dragStartElementRef.current) return;
// Prevent scroll from affecting the bottom sheet
e.preventDefault();
handleDrag(e.touches[0].clientY); handleDrag(e.touches[0].clientY);
}, },
[handleDrag], [handleDrag, isDragging],
); );
const handleTouchEnd = useCallback( const handleTouchEnd = useCallback(
(e: React.TouchEvent) => { (e: React.TouchEvent) => {
if (dragStartElementRef.current) {
endDrag(e.changedTouches[0].clientY); endDrag(e.changedTouches[0].clientY);
dragStartElementRef.current = null;
}
}, },
[endDrag], [endDrag],
); );
@@ -192,7 +212,10 @@ export function ProBottomSheet({
right: 0, right: 0,
bottom: 0, bottom: 0,
height: snapPoints[currentSnap] || height, height: snapPoints[currentSnap] || height,
backgroundColor: themeName === "dark" ? "#0a0a0a" : "#ffffff", backgroundColor:
themeName === "dark"
? darkColors.secondaryBgColor
: lightColors.secondaryBgColor,
borderTopLeftRadius: 20, borderTopLeftRadius: 20,
borderTopRightRadius: 20, borderTopRightRadius: 20,
boxShadow: boxShadow:
@@ -237,7 +260,10 @@ export function ProBottomSheet({
flex: 1, flex: 1,
overflow: "auto", overflow: "auto",
padding: "0 20px", padding: "0 20px",
backgroundColor: themeName === "dark" ? "#0a0a0a" : "#ffffff", backgroundColor:
themeName === "dark"
? darkColors.secondaryBgColor
: lightColors.secondaryBgColor,
color: themeName === "dark" ? "#ffffff" : "#000000", color: themeName === "dark" ? "#ffffff" : "#000000",
...contentStyle, ...contentStyle,
}; };
@@ -253,6 +279,8 @@ export function ProBottomSheet({
border: `1px solid ${themeName === "dark" ? "#424242" : "transparent"}`, border: `1px solid ${themeName === "dark" ? "#424242" : "transparent"}`,
transition: "all 0.3s ease", transition: "all 0.3s ease",
cursor: "pointer", cursor: "pointer",
width: 30,
height: 30,
}; };
const titleStyle: React.CSSProperties = { const titleStyle: React.CSSProperties = {
@@ -318,7 +346,19 @@ export function ProBottomSheet({
</div> </div>
)} )}
<div style={contentWrapperStyle}>{children}</div> <div
style={contentWrapperStyle}
onTouchStart={(e) => {
// Prevent touch events in content from triggering sheet drag
e.stopPropagation();
}}
onTouchMove={(e) => {
// Allow normal scrolling in content
e.stopPropagation();
}}
>
{children}
</div>
</div> </div>
</> </>
); );

View File

@@ -5,9 +5,14 @@ interface ProCheckboxGroupsProps {
options: any[]; options: any[];
value?: string[]; value?: string[];
onChange?: (values: string[]) => void; onChange?: (values: string[]) => void;
showPrice?: boolean;
} }
const ProCheckboxGroups = ({ options, ...props }: ProCheckboxGroupsProps) => { const ProCheckboxGroups = ({
options,
showPrice,
...props
}: ProCheckboxGroupsProps) => {
return ( return (
<Checkbox.Group <Checkbox.Group
{...props} {...props}
@@ -15,7 +20,7 @@ const ProCheckboxGroups = ({ options, ...props }: ProCheckboxGroupsProps) => {
width: "100%", width: "100%",
}} }}
> >
<Space direction="vertical" style={{ width: "100%" }}> <Space orientation="vertical" style={{ width: "100%" }}>
{options.map((option: any) => ( {options.map((option: any) => (
<Checkbox <Checkbox
key={option.value} key={option.value}
@@ -38,7 +43,9 @@ const ProCheckboxGroups = ({ options, ...props }: ProCheckboxGroupsProps) => {
> >
{option.label} {option.label}
</ProText> </ProText>
{showPrice && (
<ProText style={{ fontSize: "1rem" }}>{option.price}</ProText> <ProText style={{ fontSize: "1rem" }}>{option.price}</ProText>
)}
</div> </div>
</Checkbox> </Checkbox>
))} ))}

View File

@@ -6,6 +6,8 @@ import { useAppSelector } from "redux/hooks";
import { ProBlack2 } from "ThemeConstants"; import { ProBlack2 } from "ThemeConstants";
import ProTitle from "../ProTitle"; import ProTitle from "../ProTitle";
import useBreakPoint from "hooks/useBreakPoint"; import useBreakPoint from "hooks/useBreakPoint";
import NextIcon from "components/Icons/NextIcon";
const { Text } = Typography; const { Text } = Typography;
interface ProHeaderProps { interface ProHeaderProps {
@@ -41,7 +43,6 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
top: 0, top: 0,
display: "flex", display: "flex",
zIndex: 1000, zIndex: 1000,
flexDirection: isRTL ? "row-reverse" : "row",
justifyContent: "space-between", justifyContent: "space-between",
alignItems: "center", // This centers vertically alignItems: "center", // This centers vertically
backgroundColor: themeName === "light" ? "white" : ProBlack2, backgroundColor: themeName === "light" ? "white" : ProBlack2,
@@ -52,10 +53,9 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
> >
<Button <Button
type="text" type="text"
icon={<BackIcon />} icon={isRTL ? <NextIcon /> : <BackIcon />}
style={{ style={{
flex: 0, // Don't allow this to grow flex: 0, // Don't allow this to grow
marginRight: "auto", // Push it to the start
}} }}
onClick={handleBack} onClick={handleBack}
/> />
@@ -77,7 +77,20 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
}} }}
{...other} {...other}
> >
<ProTitle level={5}>{children}</ProTitle> <ProTitle
level={5}
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#333333",
}}
>
{children}
</ProTitle>
</Text> </Text>
</div> </div>
@@ -96,7 +109,20 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
}} }}
{...other} {...other}
> >
<ProTitle level={5}>{children}</ProTitle> <ProTitle
level={5}
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#333333",
}}
>
{children}
</ProTitle>
</Text> </Text>
</div> </div>
</> </>

View File

@@ -28,12 +28,23 @@ const ProInputCard: FunctionComponent<ProInputCardProps> = ({
}} }}
> >
{title && typeof title === "string" && ( {title && typeof title === "string" && (
<ProTitle style={{ fontSize: 18 }}> {title} </ProTitle> <ProTitle
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 18,
lineHeight: "140%",
letterSpacing: "0%",
color: '#333333',
}}
>
{title}
</ProTitle>
)} )}
{title && typeof title !== "string" && title} {title && typeof title !== "string" && title}
<div style={{ position: "relative", top: 0 }}>{titleRight}</div> <div style={{ position: "relative", top: 0 }}>{titleRight}</div>
</div> </div>
<Divider style={{ margin: "5px 0 15px 0", ...dividerStyle }} /> <Divider style={{ margin: "3px 0 12px 0", ...dividerStyle }} />
{children} {children}
</Card> </Card>
); );

View File

@@ -13,6 +13,7 @@ interface ProPhoneInput {
getValueCallback?: (value: string) => void; getValueCallback?: (value: string) => void;
value?: string; value?: string;
onChange?: (value: string) => void; onChange?: (value: string) => void;
hiddenLabel?: boolean;
} }
const ProPhoneInput: FunctionComponent<ProPhoneInput> = ({ const ProPhoneInput: FunctionComponent<ProPhoneInput> = ({
@@ -21,6 +22,7 @@ const ProPhoneInput: FunctionComponent<ProPhoneInput> = ({
getValueCallback, getValueCallback,
value, value,
onChange, onChange,
hiddenLabel = true,
}) => { }) => {
const form = useFormInstance(); const form = useFormInstance();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -29,7 +31,7 @@ const ProPhoneInput: FunctionComponent<ProPhoneInput> = ({
return ( return (
<Form.Item <Form.Item
name={propName} name={propName}
label={label} label={hiddenLabel ? undefined : label}
rules={[ rules={[
{ required: true, message: t("validation.phoneRequired") }, { required: true, message: t("validation.phoneRequired") },
{ {
@@ -60,7 +62,7 @@ const ProPhoneInput: FunctionComponent<ProPhoneInput> = ({
themeName={themeName} themeName={themeName}
placeholder={t("login.mobileNumber")} placeholder={t("login.mobileNumber")}
propName={propName} propName={propName}
label={label} label={hiddenLabel ? undefined : label}
/> />
</Form.Item> </Form.Item>
); );
@@ -86,7 +88,7 @@ export const PhoneInputWrapper = ({
const inputStyle = useMemo( const inputStyle = useMemo(
() => ({ () => ({
borderRadius: 1000, borderRadius: 1000,
height: 50, height: 48,
width: "100%", width: "100%",
color: themeName === "light" ? "#000" : "#FFF", color: themeName === "light" ? "#000" : "#FFF",
backgroundColor: themeName === "light" ? "#FFF" : ProBlack1, backgroundColor: themeName === "light" ? "#FFF" : ProBlack1,

View File

@@ -17,3 +17,92 @@
width: 24px !important; width: 24px !important;
height: 24px !important; height: 24px !important;
} }
/* Make AntD checkbox look like a circular check indicator (scoped via CSS modules) */
.circleCheckbox :global(.ant-checkbox-inner) {
width: 24px;
height: 24px;
border-radius: 50% !important;
border: 1.5px solid #d5d8da;
background: transparent;
}
.circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner) {
border-radius: 50% !important;
background: transparent;
border-color: #ffb700;
}
/* Replace AntD checkmark with a filled inner circle when checked (match SVG) */
.circleCheckbox :global(.ant-checkbox-inner::after) {
content: "";
border: 0 !important;
transform: none !important;
width: 0;
height: 0;
left: 50%;
top: 50%;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-checkbox-inner::after) {
left: auto;
right: 50%;
}
.circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner::after) {
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
background: #ffb700;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner::after) {
margin-left: auto;
margin-right: -9px;
}
/* Apply same circular style to Radio buttons */
.circleCheckbox :global(.ant-radio-inner) {
width: 24px;
height: 24px;
border-radius: 50% !important;
border: 1.5px solid #d5d8da;
background: transparent;
}
.circleCheckbox :global(.ant-radio-checked .ant-radio-inner) {
border-radius: 50% !important;
background: transparent;
border-color: #ffb700;
}
.circleCheckbox :global(.ant-radio-inner::after) {
content: "";
border: 0 !important;
transform: none !important;
width: 0;
height: 0;
left: 50%;
top: 50%;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-radio-inner::after) {
left: auto;
right: 50%;
}
.circleCheckbox :global(.ant-radio-checked .ant-radio-inner::after) {
width: 18px;
height: 18px;
margin-left: -9px;
margin-top: -9px;
border-radius: 50%;
background: #ffb700;
}
:global(.ant-app-rtl) .circleCheckbox :global(.ant-radio-checked .ant-radio-inner::after) {
margin-left: auto;
margin-right: -9px;
}

View File

@@ -1,4 +1,4 @@
import { Radio, RadioChangeEvent, Space } from "antd"; import { Divider, Radio, RadioChangeEvent, Space } from "antd";
import ProText from "components/ProText"; import ProText from "components/ProText";
import styles from "./ProRatioGroups.module.css"; import styles from "./ProRatioGroups.module.css";
@@ -7,6 +7,9 @@ interface ProRatioGroupsProps {
onRatioClick?: (value: string) => void; onRatioClick?: (value: string) => void;
onChange?: (e: RadioChangeEvent) => void; onChange?: (e: RadioChangeEvent) => void;
value?: string; value?: string;
optionsStyle?: React.CSSProperties;
valueStyle?: React.CSSProperties;
showDivider?: boolean;
} }
const ProRatioGroups = ({ const ProRatioGroups = ({
@@ -14,9 +17,13 @@ const ProRatioGroups = ({
onRatioClick, onRatioClick,
onChange, onChange,
value, value,
optionsStyle,
valueStyle,
showDivider = false,
...props ...props
}: ProRatioGroupsProps) => { }: ProRatioGroupsProps) => {
const handleChange = (e: RadioChangeEvent) => { const handleChange = (e: RadioChangeEvent) => {
console.log(e.target.value);
// If onChange is provided (from Form.Item), use it // If onChange is provided (from Form.Item), use it
if (onChange) { if (onChange) {
onChange(e); onChange(e);
@@ -37,9 +44,14 @@ const ProRatioGroups = ({
onChange={handleChange} onChange={handleChange}
{...props} {...props}
> >
<Space direction="vertical" style={{ width: "100%" }}> <Space orientation="vertical" style={{ width: "100%" }}>
{options.map((option) => ( {options.map((option) => (
<Radio key={option.value} value={option.value}> <>
<Radio
key={option.value}
value={option.value}
className={styles.circleCheckbox}
>
<div <div
style={{ style={{
display: "flex", display: "flex",
@@ -47,18 +59,26 @@ const ProRatioGroups = ({
justifyContent: "space-between", justifyContent: "space-between",
width: "100%", width: "100%",
padding: "8px 0", padding: "8px 0",
placeItems: "center",
}} }}
> >
<ProText <ProText
style={{ style={{
fontSize: "1rem", fontSize: "1rem",
...optionsStyle,
}} }}
> >
{option.label} {option.label}
</ProText> </ProText>
<ProText style={{ fontSize: "1rem" }}>{option?.price}</ProText> <ProText style={{ fontSize: "1rem", ...valueStyle }}>
{option?.price}
</ProText>
</div> </div>
</Radio> </Radio>
{showDivider && options.length !== options.length - 1 && (
<Divider style={{ margin: "0 0 16px 0 " }} />
)}
</>
))} ))}
</Space> </Space>
</Radio.Group> </Radio.Group>

View File

@@ -1,47 +0,0 @@
import { Loader } from "components/Loader/Loader";
import { Navigate, useParams } from "react-router-dom";
import { useAppSelector } from "redux/hooks";
export const PrivateRoute = ({
children,
}: // permission,
{
children: JSX.Element;
permission?: string;
}) => {
const { token, loading } = useAppSelector((state) => state.auth);
const { subdomain } = useParams();
// const { data: user, isLoading: loadingUser } = useGetSignedUserInfoQuery();
// useEffect(() => {
// if (user) dispatch(setUser(user));
// }, [dispatch, user]);
if (loading) {
return <Loader />;
}
// let newPermissions: any[] = [];
// // aggregate the rules from multi
// user?.roles?.forEach(
// (r) => (newPermissions = [...newPermissions, ...r.permissions])
// );
// const userHasRequiredPermission = permission
// ? !!newPermissions.find((p) => p.name === permission)
// : true;
console.log(token);
if (!token) {
return <Navigate to={`/${subdomain}/login`} />;
}
// if (token && !userHasRequiredPermission) {
// return <AccessDenied />; // build your won access denied page (sth like 404)
// }
return children;
};

View File

@@ -0,0 +1,177 @@
import { Space, Divider } from "antd";
import ProText from "components/ProText.tsx";
import styles from "pages/cart/cart.module.css";
import ArabicPrice from "components/ArabicPrice";
import ImageWithFallback from "components/ImageWithFallback";
import CartActionsButtons from "components/CartActionsButtons/CartActionsButtons.tsx";
import { CartItem } from "utils/types/appTypes.ts";
import { useAppSelector } from "redux/hooks.ts";
import useBreakPoint from "hooks/useBreakPoint.ts";
import { OrderItem } from "pages/checkout/hooks/types.ts";
type ProductChoicesCardProps = {
product: CartItem | OrderItem;
addDividerAfter: boolean;
};
export default function ProductChoicesCard({
product,
addDividerAfter,
}: ProductChoicesCardProps) {
const { isRTL } = useAppSelector((state) => state.locale);
const { isMobile, isTablet } = useBreakPoint();
const getMenuItemImageStyle = () => {
if (isMobile) {
return {
width: 115,
height: 96,
};
}
return {
width: 120,
height: 120,
};
};
return (
<div style={{ position: "relative" }}>
<Space
size="middle"
style={{
display: "flex",
alignItems: "center",
justifyContent: "space-between",
height: "100%",
}}
>
<Space orientation="vertical" size="small">
<div style={{}}>
<ProText
style={{
margin: 0,
lineClamp: 1,
fontSize: isMobile ? 14 : isTablet ? 16 : 18,
fontWeight: 600,
}}
>
{product.name}
</ProText>
<ProText
type="secondary"
className={`${styles.itemDescription} responsive-text`}
style={{
margin: 0,
lineClamp: 1,
padding: isMobile ? "3px 0" : isTablet ? 8 : 10,
fontSize: isMobile ? 14 : isTablet ? 18 : 20,
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 1,
overflow: "hidden",
textOverflow: "ellipsis",
wordWrap: "break-word",
overflowWrap: "break-word",
lineHeight: "1.4",
maxHeight: isMobile ? "3em" : isTablet ? "5em" : "7em",
fontWeight: 500,
letterSpacing: "0.01em",
width: "100%",
}}
>
{product.type === "CartItem" &&
(isRTL
? product.variant?.optionsAR.map((o) => o.value).join(", ")
: product.variant?.options.map((o) => o.value).join(", "))}
{product.type === "OrderItem" && product.variantName}
</ProText>
{product.extras && (
<ProText
type="secondary"
className={`${styles.itemDescription} responsive-text`}
style={{
margin: 0,
lineClamp: 1,
fontSize: isMobile ? 14 : isTablet ? 18 : 20,
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 1,
overflow: "hidden",
textOverflow: "ellipsis",
wordWrap: "break-word",
overflowWrap: "break-word",
lineHeight: "1.4",
maxHeight: isMobile ? "3em" : isTablet ? "5em" : "7em",
fontWeight: 500,
letterSpacing: "0.01em",
width: "100%",
}}
>
{product.extras.map((o) => (
<span key={o.id}>{o.name}</span>
))}
</ProText>
)}
{product.type === "CartItem"
? product.extrasgroupnew?.map((o) => (
<ProText
type="secondary"
className={`${styles.itemDescription} responsive-text`}
style={{
margin: 0,
lineClamp: 1,
padding: isMobile ? "3px 0" : isTablet ? 8 : 10,
fontSize: isMobile ? 14 : isTablet ? 18 : 20,
display: "-webkit-box",
WebkitBoxOrient: "vertical",
WebkitLineClamp: 1,
overflow: "hidden",
textOverflow: "ellipsis",
wordWrap: "break-word",
overflowWrap: "break-word",
lineHeight: "1.4",
maxHeight: isMobile ? "3em" : isTablet ? "5em" : "7em",
fontWeight: 500,
letterSpacing: "0.01em",
width: "100%",
}}
>
{isRTL ? o.extrasStringAR : o.extrasString}
</ProText>
))
: // ToDo
product.itemline}
</div>
<div style={{}}>
<ArabicPrice price={product.price} style={{ fontStyle: "bold" }} />
</div>
</Space>
<div style={{ position: "relative" }}>
<ImageWithFallback
src={product.image}
alt={product.name}
className={`${styles.menuItemImage} responsive-image`}
{...getMenuItemImageStyle()}
fallbackSrc={
"https://fascano-space.s3.me-central-1.amazonaws.com/uploads/restorants/685a8fc884a8c_large.jpg"
}
/>
{product.type === "CartItem" && (
<div
style={{
position: "absolute",
right: 3,
bottom: 3,
}}
>
<CartActionsButtons item={product} />
</div>
)}
</div>
</Space>
{addDividerAfter && <Divider style={{ margin: "10px 0" }} />}
</div>
);
}

View File

@@ -0,0 +1,22 @@
import { Loader } from "components/Loader/Loader";
import { Navigate, useParams } from "react-router-dom";
import { useAppSelector } from "redux/hooks";
export const PublicRoute = ({
children,
}: {
children: JSX.Element;
}) => {
const { token, loading } = useAppSelector((state) => state.auth);
const { subdomain } = useParams();
if (loading) {
return <Loader />;
}
if (token) {
return <Navigate to={`/${subdomain}`} replace />;
}
return children;
};

View File

@@ -42,12 +42,22 @@ export function ScrollHandlerProvider({ children }: { children: ReactNode }) {
const scrollToCategory = useCallback((categoryId: number) => { const scrollToCategory = useCallback((categoryId: number) => {
const categoryRef = categoryRefs.current?.[categoryId]; const categoryRef = categoryRefs.current?.[categoryId];
if (categoryRef) { if (categoryRef) {
categoryRef.scrollIntoView({ // Get the sticky header height (70px when sticky, 0 when not)
const stickyHeaderHeight = isCategoriesSticky && categoriesContainerRef.current
? categoriesContainerRef.current.offsetHeight
: 0;
// Calculate the position of the category element
const elementPosition = categoryRef.getBoundingClientRect().top;
const offsetPosition = elementPosition + window.pageYOffset - stickyHeaderHeight;
// Scroll to the exact position, accounting for sticky header
window.scrollTo({
top: offsetPosition,
behavior: "smooth", behavior: "smooth",
block: "start",
}); });
} }
}, []); }, [isCategoriesSticky, categoriesContainerRef]);
return ( return (
<ScrollHandlerContext.Provider <ScrollHandlerContext.Provider

View File

@@ -26,6 +26,7 @@ export interface OfficeDetailsType {
} }
export interface GiftDetailsType { export interface GiftDetailsType {
amount: number;
receiverName: string; receiverName: string;
receiverPhone: string; receiverPhone: string;
message: string; message: string;
@@ -33,6 +34,8 @@ export interface GiftDetailsType {
senderPhone: string; senderPhone: string;
senderEmail: string; senderEmail: string;
isSecret: boolean; isSecret: boolean;
cardId: number;
giftType: "items" | "vouchers" | "itemsAndVouchers";
} }
interface DiscountData { interface DiscountData {
@@ -52,7 +55,7 @@ interface CartState {
giftDetails: GiftDetailsType | null; giftDetails: GiftDetailsType | null;
coupon: string; coupon: string;
tip: string; tip: string;
tables: string[]; table: string;
estimateTime: Date; estimateTime: Date;
estimateTimeDate: Date; estimateTimeDate: Date;
estimateTimeTime: string; estimateTimeTime: string;
@@ -68,8 +71,14 @@ interface CartState {
pickupDate: string; pickupDate: string;
pickupTime: string; pickupTime: string;
pickupType: string; pickupType: string;
estimateWay: string;
order: any; order: any;
splitBillAmount: number; splitBillAmount: number;
customerName: string;
totalServices: number;
hiddenServices: number;
visibleServices: number;
fee: number;
} }
// localStorage keys // localStorage keys
@@ -100,6 +109,11 @@ export const CART_STORAGE_KEYS = {
PICKUP_TIME: "fascano_pickup_time", PICKUP_TIME: "fascano_pickup_time",
PICKUP_TYPE: "fascano_pickup_type", PICKUP_TYPE: "fascano_pickup_type",
ORDER: "fascano_order", ORDER: "fascano_order",
TOTAL_SERVICES: "fascano_total_services",
HIDDEN_SERVICES: "fascano_hidden_services",
VISIBLE_SERVICES: "fascano_visible_services",
ESTIMATE_WAY: "fascano_estimate_way",
CUSTOMER_NAME: "fascano_customer_name",
} as const; } as const;
// Utility functions for localStorage // Utility functions for localStorage
@@ -139,7 +153,7 @@ const initialState: CartState = {
giftDetails: getFromLocalStorage(CART_STORAGE_KEYS.GIFT_DETAILS, null), giftDetails: getFromLocalStorage(CART_STORAGE_KEYS.GIFT_DETAILS, null),
coupon: getFromLocalStorage(CART_STORAGE_KEYS.COUPON, ""), coupon: getFromLocalStorage(CART_STORAGE_KEYS.COUPON, ""),
tip: getFromLocalStorage(CART_STORAGE_KEYS.TIP, ""), tip: getFromLocalStorage(CART_STORAGE_KEYS.TIP, ""),
tables: getFromLocalStorage(CART_STORAGE_KEYS.TABLES, []), table: getFromLocalStorage(CART_STORAGE_KEYS.TABLES, ""),
estimateTime: new Date( estimateTime: new Date(
getFromLocalStorage( getFromLocalStorage(
CART_STORAGE_KEYS.ESTIMATE_TIME, CART_STORAGE_KEYS.ESTIMATE_TIME,
@@ -185,8 +199,14 @@ const initialState: CartState = {
pickupDate: getFromLocalStorage(CART_STORAGE_KEYS.PICKUP_DATE, ""), pickupDate: getFromLocalStorage(CART_STORAGE_KEYS.PICKUP_DATE, ""),
pickupTime: getFromLocalStorage(CART_STORAGE_KEYS.PICKUP_TIME, ""), pickupTime: getFromLocalStorage(CART_STORAGE_KEYS.PICKUP_TIME, ""),
pickupType: getFromLocalStorage(CART_STORAGE_KEYS.PICKUP_TYPE, ""), pickupType: getFromLocalStorage(CART_STORAGE_KEYS.PICKUP_TYPE, ""),
estimateWay: getFromLocalStorage(CART_STORAGE_KEYS.ESTIMATE_WAY, ""),
order: getFromLocalStorage(CART_STORAGE_KEYS.ORDER, null), order: getFromLocalStorage(CART_STORAGE_KEYS.ORDER, null),
splitBillAmount: 0, splitBillAmount: 0,
customerName: getFromLocalStorage(CART_STORAGE_KEYS.CUSTOMER_NAME, ""),
totalServices: 8,
hiddenServices: 0,
visibleServices: 0,
fee: 0,
}; };
const orderSlice = createSlice({ const orderSlice = createSlice({
@@ -198,11 +218,36 @@ const orderSlice = createSlice({
}, },
updateRestaurant(state, action: PayloadAction<Partial<RestaurantDetails>>) { updateRestaurant(state, action: PayloadAction<Partial<RestaurantDetails>>) {
state.restaurant = action.payload; state.restaurant = action.payload;
state.fee = Number(action.payload.delivery_fees || 0);
state.visibleServices = [
action.payload.dineIn,
action.payload.delivery,
action.payload.pickup,
action.payload.gift,
action.payload.toRoom,
action.payload.toOffice,
action.payload.is_schedule_order_enabled,
action.payload.is_booking_enabled,
].filter(Boolean).length;
state.hiddenServices = state.totalServices - state.visibleServices;
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
localStorage.setItem( localStorage.setItem(
CART_STORAGE_KEYS.RESTAURANT, CART_STORAGE_KEYS.RESTAURANT,
JSON.stringify(state.restaurant), JSON.stringify(state.restaurant),
); );
localStorage.setItem(
CART_STORAGE_KEYS.TOTAL_SERVICES,
JSON.stringify(state.totalServices),
);
localStorage.setItem(
CART_STORAGE_KEYS.HIDDEN_SERVICES,
JSON.stringify(state.hiddenServices),
);
localStorage.setItem(
CART_STORAGE_KEYS.VISIBLE_SERVICES,
JSON.stringify(state.visibleServices),
);
} }
}, },
addItem( addItem(
@@ -308,7 +353,7 @@ const orderSlice = createSlice({
state.phone = ""; state.phone = "";
state.coupon = ""; state.coupon = "";
state.tip = ""; state.tip = "";
state.tables = []; state.table = "";
state.location = null; state.location = null;
state.roomDetails = null; state.roomDetails = null;
state.officeDetails = null; state.officeDetails = null;
@@ -319,10 +364,27 @@ const orderSlice = createSlice({
state.collectionMethod = ""; state.collectionMethod = "";
state.paymentMethod = ""; state.paymentMethod = "";
state.loyaltyValidationError = null; state.loyaltyValidationError = null;
state.discount = {
value: 0,
isGift: false,
isDiscount: false,
};
state.plateCar = "";
state.pickupDate = "";
state.pickupTime = "";
state.pickupType = "";
state.estimateWay = "";
state.order = null;
state.splitBillAmount = 0;
state.customerName = "";
// Clear all cart data from localStorage // Clear all cart data from localStorage
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
Object.values(CART_STORAGE_KEYS) Object.values(CART_STORAGE_KEYS)
.filter((key) => key !== CART_STORAGE_KEYS.ORDER_TYPE) .filter(
(key) =>
key !== CART_STORAGE_KEYS.ORDER_TYPE &&
key !== CART_STORAGE_KEYS.PAYMENT_METHOD,
)
.forEach((key) => { .forEach((key) => {
localStorage.removeItem(key); localStorage.removeItem(key);
}); });
@@ -366,19 +428,19 @@ const orderSlice = createSlice({
localStorage.setItem(CART_STORAGE_KEYS.TIP, JSON.stringify(state.tip)); localStorage.setItem(CART_STORAGE_KEYS.TIP, JSON.stringify(state.tip));
} }
}, },
updateTables(state, action: PayloadAction<string[]>) { updateTables(state, action: PayloadAction<string>) {
state.tables = action.payload; state.table = action.payload;
// Sync to localStorage // Sync to localStorage
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
localStorage.setItem( localStorage.setItem(
CART_STORAGE_KEYS.TABLES, CART_STORAGE_KEYS.TABLES,
JSON.stringify(state.tables), JSON.stringify(state.table),
); );
} }
}, },
removeTable(state) { removeTable(state) {
state.tables = []; state.table = "";
// Sync to localStorage // Sync to localStorage
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
@@ -432,8 +494,14 @@ const orderSlice = createSlice({
); );
} }
}, },
updateGiftDetails(state, action: PayloadAction<GiftDetailsType | null>) { updateGiftDetails(
state.giftDetails = action.payload; state,
action: PayloadAction<Partial<GiftDetailsType> | null>,
) {
state.giftDetails = {
...state.giftDetails,
...action.payload,
} as GiftDetailsType;
// Sync to localStorage // Sync to localStorage
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
@@ -628,15 +696,37 @@ const orderSlice = createSlice({
); );
} }
}, },
updateOrder(state, action: PayloadAction<any>) { updateEstimateWay(state, action: PayloadAction<string>) {
state.order = action.payload; state.estimateWay = action.payload;
if (typeof window !== "undefined") { if (typeof window !== "undefined") {
localStorage.setItem(CART_STORAGE_KEYS.ORDER, JSON.stringify(state.order)); localStorage.setItem(
CART_STORAGE_KEYS.ESTIMATE_WAY,
JSON.stringify(state.estimateWay),
);
}
},
updateOrder(state, action: PayloadAction<any>) {
state.order = { ...(state.order || {}), ...action.payload };
if (typeof window !== "undefined") {
localStorage.setItem(
CART_STORAGE_KEYS.ORDER,
JSON.stringify(state.order),
);
} }
}, },
updateSplitBillAmount(state, action: PayloadAction<number>) { updateSplitBillAmount(state, action: PayloadAction<number>) {
state.splitBillAmount = action.payload; state.splitBillAmount = action.payload;
}, },
updateCustomerName(state, action: PayloadAction<string>) {
state.customerName = action.payload;
if (typeof window !== "undefined") {
localStorage.setItem(
CART_STORAGE_KEYS.CUSTOMER_NAME,
JSON.stringify(state.customerName),
);
}
},
}, },
}); });
@@ -673,29 +763,29 @@ export const {
updatePickupDate, updatePickupDate,
updatePickupTime, updatePickupTime,
updatePickUpType, updatePickUpType,
updateEstimateWay,
updateOrder, updateOrder,
updateSplitBillAmount, updateSplitBillAmount,
updateCustomerName,
} = orderSlice.actions; } = orderSlice.actions;
// Tax calculation helper functions // Tax calculation helper functions
const calculateTaxAmount = ( const calculateTaxAmount = (
state: RootState,
amount: number, amount: number,
tax: Tax, tax: Tax,
): number => { ): number => {
const percentage = parseFloat(tax.percentage); const percentage = parseFloat(tax.percentage);
return (((state.order.restaurant?.vat || 0) + percentage) * amount) / 100; return (percentage * amount) / 100;
}; };
const calculateTotalTax = ( const calculateTotalTax = (
state: RootState,
subtotal: number, subtotal: number,
taxes: Tax[], taxes: Tax[],
): number => { ): number => {
return taxes return taxes
.filter((tax) => tax.is_active === 1) .filter((tax) => tax.is_active === 1)
.reduce( .reduce(
(total, tax) => total + calculateTaxAmount(state, subtotal, tax), (total, tax) => total + calculateTaxAmount(subtotal, tax),
0, 0,
); );
}; };
@@ -734,21 +824,31 @@ export const selectLoyaltyItems = createSelector([selectOrderItems], (items) =>
items.filter((item) => item.isHasLoyalty), items.filter((item) => item.isHasLoyalty),
); );
// Tax selectors
export const selectTaxes = (state: RootState) => state.order.restaurant.taxes;
export const totalTaxes = (state: RootState) => {
const taxes = selectTaxes(state);
const totalTaxes = taxes?.reduce((total, tax) => total + parseFloat(tax.percentage), 0) || 0;
const vat = (state.order.restaurant?.vat || 0);
return totalTaxes + vat;
};
export const selectHighestPricedLoyaltyItem = (state: RootState) => { export const selectHighestPricedLoyaltyItem = (state: RootState) => {
const loyaltyItems = selectLoyaltyItems(state); const loyaltyItems = selectLoyaltyItems(state);
if (loyaltyItems.length === 0) return null; if (loyaltyItems.length === 0) return null;
const highestItem = loyaltyItems.reduce((highest, current) =>
return loyaltyItems.reduce((highest, current) =>
current.price > highest.price ? current : highest, current.price > highest.price ? current : highest,
); )
return highestItem;
}; };
export const selectDiscountTotal = (state: RootState) => export const selectDiscountTotal = (state: RootState) => {
(state.order.discount.value / 100) * selectCartTotal(state) + return (state.order.discount.value / 100) * selectCartTotal(state) +
(state.order.useLoyaltyPoints && (state.order.useLoyaltyPoints &&
state.order.restaurant?.is_loyalty_enabled === 1 state.order.restaurant?.is_loyalty_enabled === 1
? selectHighestPricedLoyaltyItem(state)?.price || 0 ? ((selectHighestPricedLoyaltyItem(state)?.price || 0))
: 0); : 0);
}
export const selectLoyaltyValidation = createSelector( export const selectLoyaltyValidation = createSelector(
[(state: RootState) => state.order.useLoyaltyPoints, selectLoyaltyItems], [(state: RootState) => state.order.useLoyaltyPoints, selectLoyaltyItems],
@@ -763,25 +863,33 @@ export const selectLoyaltyValidation = createSelector(
}), }),
); );
// Tax selectors
export const selectTaxes = (state: RootState) => state.order.restaurant.taxes;
export const selectTaxAmount = (state: RootState) => { export const selectTaxAmount = (state: RootState) => {
const subtotal = selectCartTotal(state) - selectDiscountTotal(state); const subtotal = selectCartTotal(state) - selectDiscountTotal(state);
const taxes = selectTaxes(state); const taxes = selectTaxes(state);
return calculateTotalTax(state, subtotal, taxes || []); return calculateTotalTax(subtotal, taxes || []);
}; };
export const selectGrandTotal = (state: RootState) => { export const selectGrandTotal = (state: RootState) => {
const totalDiscount = selectDiscountTotal(state); const totalDiscount = selectDiscountTotal(state);
const taxAmount = selectTaxAmount(state); const taxAmount = selectTaxAmount(state);
const vatAmount = ((state.order.restaurant?.vat || 0) / 100) * (selectCartTotal(state) - selectDiscountTotal(state));
const subtotal = selectCartTotal(state); const subtotal = selectCartTotal(state);
const deliveryFee = const deliveryFee =
state.order.orderType === OrderType.Delivery state.order.orderType === OrderType.Delivery
? Number(state.order.restaurant?.delivery_fees) || 0 ? Number(state.order.restaurant?.delivery_fees) || 0
: 0; : 0;
return subtotal + taxAmount - totalDiscount + deliveryFee - state.order.splitBillAmount; return (
subtotal -
totalDiscount +
taxAmount +
vatAmount -
deliveryFee
// state.order.splitBillAmount +
// Number(state.order.tip)
);
}; };
export default orderSlice.reducer; export default orderSlice.reducer;

View File

@@ -1,39 +0,0 @@
import { useEffect, useState } from "react";
export function useDetectBarcode() {
const [barcode, setBarcode] = useState<string>("");
const [lastBarcode, setLastBarcode] = useState<string>("");
useEffect(() => {
let interval: any;
const handleKeydown = (evt: any) => {
if (interval) clearInterval(interval);
if (evt.code === "Enter") {
if (barcode) handleBarcode(barcode);
setBarcode("");
return;
}
if (evt.key !== "Shift") setBarcode((prev) => prev + evt.key);
interval = setInterval(() => setBarcode(""), 20);
};
// Adding the event listener when the component mounts
document.addEventListener("keydown", handleKeydown);
// Cleanup the event listener when the component unmounts
return () => {
document.removeEventListener("keydown", handleKeydown);
if (interval) clearInterval(interval);
};
}, [barcode]);
const handleBarcode = (scannedBarcode: string) => {
setLastBarcode(scannedBarcode);
};
return lastBarcode;
}

View File

@@ -1,17 +1,34 @@
import { updateRestaurant } from "features/order/orderSlice"; import { updateRestaurant, clearCart } from "features/order/orderSlice";
import { useEffect } from "react"; import { useEffect, useRef } from "react";
import { useAppDispatch } from "redux/hooks"; import { useAppDispatch } from "redux/hooks";
import { RestaurantDetails } from "utils/types/appTypes"; import { RestaurantDetails } from "utils/types/appTypes";
/** /**
* Custom hook to automatically load restaurant into Redux store * Custom hook to automatically load restaurant into Redux store
* when restaurant data is available * when restaurant data is available
* Clears the cart when the restaurant (subdomain) changes
*/ */
export const useRestaurant = (restaurant: RestaurantDetails | undefined) => { export const useRestaurant = (restaurant: RestaurantDetails | undefined) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const previousRestaurantIdRef = useRef<string | null>(null);
useEffect(() => { useEffect(() => {
if (restaurant) { if (restaurant) {
const currentRestaurantId = restaurant.restautantId;
// Check if restaurant has changed
if (
previousRestaurantIdRef.current !== null &&
previousRestaurantIdRef.current !== currentRestaurantId
) {
// Restaurant changed, clear the cart
dispatch(clearCart());
}
// Update the previous restaurant ID
previousRestaurantIdRef.current = currentRestaurantId;
// Update restaurant in store
dispatch(updateRestaurant(restaurant)); dispatch(updateRestaurant(restaurant));
} }
}, [restaurant, dispatch]); }, [restaurant, dispatch]);

View File

@@ -1,52 +0,0 @@
import { useRef, useState, useCallback } from "react";
type Props = { isEnabled: boolean; swipeAction: () => void };
export default function useSwipeUp({ isEnabled, swipeAction }: Props) {
// Swipe detection
const startYRef = useRef(0);
const [isSwiping, setIsSwiping] = useState(false);
const containerRef = useRef<HTMLDivElement>(null);
// Touch event handlers for swipe detection
const handleTouchStart = useCallback(
(e: React.TouchEvent) => {
if (!isEnabled) return;
startYRef.current = e.touches[0].clientY;
setIsSwiping(true);
},
[isEnabled],
);
/*
const handleTouchMove = useCallback(
(e: React.TouchEvent) => {
if (!isSwiping) return;
},
[isSwiping],
);
*/
const handleTouchEnd = useCallback(
(e: React.TouchEvent) => {
if (!isSwiping || !isEnabled) return;
const endY = e.changedTouches[0].clientY;
const deltaY = startYRef.current - endY;
const threshold = 70; // Threshold to detect a swipe up
if (deltaY > threshold) {
// Swipe up detected
swipeAction();
}
setIsSwiping(false);
},
[isSwiping, isEnabled],
);
return {
containerRef,
handleTouchStart,
handleTouchEnd,
};
}

View File

@@ -1,7 +0,0 @@
import { useAppSelector } from "redux/hooks";
export function useTranslations() {
const { isRTL } = useAppSelector((state) => state.locale);
const nameProp = isRTL ? "arabic_name" : "name";
return [nameProp] as const ;
}

View File

@@ -1,8 +1,8 @@
/* Import Ant Design reset */ /* Import Ant Design reset */
@import "antd/dist/reset.css"; @import "antd/dist/reset.css";
/* Import Nunito Sans from Google Fonts */ /* Import Outfit from Google Fonts */
@import url("https://fonts.googleapis.com/css2?family=Nunito+Sans:ital,opsz,wght@0,6..12,200..1000;1,6..12,200..1000&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&display=swap");
:root { :root {
--background: #f7f7f7; --background: #f7f7f7;
@@ -37,6 +37,9 @@
0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1); 0 1px 3px 0 rgba(0, 0, 0, 0.1), 0 1px 2px -1px rgba(0, 0, 0, 0.1);
--tw-ring-offset-shadow: 0 0 rgba(0, 0, 0, 0); --tw-ring-offset-shadow: 0 0 rgba(0, 0, 0, 0);
--tw-ring-shadow: 0 0 rgba(0, 0, 0, 0); --tw-ring-shadow: 0 0 rgba(0, 0, 0, 0);
--greylight-hover: rgba(234, 234, 234, 1);
--variable-collection-PP: rgba(255, 183, 0, 1);
} }
/* Dark theme variables */ /* Dark theme variables */
@@ -47,7 +50,7 @@
--secondary-foreground: #ffffff; --secondary-foreground: #ffffff;
--border: #363636; --border: #363636;
--text-color: #000; --text-color: #000;
--text-color-gray: #5f6c7b; /* rgba(67, 78, 92, 1); */ --text-color-gray: #777580; /* rgba(67, 78, 92, 1); */
} }
html, html,

View File

@@ -1,13 +0,0 @@
import { Layout } from 'antd';
const { Footer } = Layout;
type FooterNavProps = React.HTMLAttributes<HTMLDivElement>;
const FooterNav = ({ ...others }: FooterNavProps) => {
return (
<Footer {...others}>AntD Dashboard © 2023 Created by Design Sparx</Footer>
);
};
export default FooterNav;

View File

@@ -122,7 +122,18 @@ export default function HeaderMenuDrawer() {
style={mobileMenuLinkStyle(locale === item.key)} style={mobileMenuLinkStyle(locale === item.key)}
> >
{item.icon} {item.icon}
<ProText>{item.label.props.children}</ProText> <ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: 0,
color: "#333333",
}}
>
{item.label.props.children}
</ProText>
</div> </div>
</div> </div>
))} ))}

View File

@@ -1,13 +1,15 @@
import { import {
BgColorsOutlined, BgColorsOutlined,
HomeOutlined, GlobalOutlined,
LoginOutlined,
LogoutOutlined, LogoutOutlined,
MenuOutlined,
TranslationOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import BranchesIcon from "components/Icons/BranchesIcon";
import LoginIcon from "components/Icons/LoginIcon";
import LoyaltyAndRewardIcon from "components/Icons/LoyaltyAndRewardIcon";
import MyOrderIcon from "components/Icons/MyOrderIcon";
import { logoutThunk } from "features/auth/authSlice"; import { logoutThunk } from "features/auth/authSlice";
import { setLocale, setLocalesThunk } from "features/locale/localeSlice"; import { setLocale, setLocalesThunk } from "features/locale/localeSlice";
import { clearCart } from "features/order/orderSlice";
import { toggleTheme } from "features/theme/themeSlice"; import { toggleTheme } from "features/theme/themeSlice";
import i18n from "i18n/i18n"; import i18n from "i18n/i18n";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -28,7 +30,7 @@ export default function useHeaderMenu() {
{ {
key: "language", key: "language",
icon: ( icon: (
<TranslationOutlined <GlobalOutlined
style={{ color: themeName === "dark" ? "white" : "#1f2937" }} style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
/> />
), ),
@@ -58,38 +60,44 @@ export default function useHeaderMenu() {
}, },
{ {
key: "orders", key: "orders",
icon: ( icon: <MyOrderIcon />,
<HomeOutlined
style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
/>
),
label: <Link to={`/${subdomain}/orders`}>{t("common.myOrder")}</Link>, label: <Link to={`/${subdomain}/orders`}>{t("common.myOrder")}</Link>,
onClick: () => { onClick: () => {
navigate(`/${subdomain}/orders`); navigate(`/${subdomain}/orders`);
}, },
}, },
{ {
key: "branches", key: "rewardsAndLoyalty",
icon: ( icon: <LoyaltyAndRewardIcon />,
<MenuOutlined label: (
style={{ color: themeName === "dark" ? "white" : "#1f2937" }} <Link to={`/${subdomain}/rewards-and-loyalty`}>
/> {t("common.rewardsAndLoyalty")}
</Link>
), ),
onClick: () => {
navigate(`/${subdomain}/rewards-and-loyalty`);
},
},
{
key: "branches",
icon: <BranchesIcon />,
label: <Link to={`/${subdomain}/menu`}>{t("common.branches")}</Link>, label: <Link to={`/${subdomain}/menu`}>{t("common.branches")}</Link>,
}, },
...(!token ? [{ ...(!token
? [
{
key: "login", key: "login",
icon: ( icon: <LoginIcon />,
<LoginOutlined
style={{ color: themeName === "dark" ? "white" : "#1f2937" }}
/>
),
label: <Link to={`/${subdomain}/login`}>{t("common.login")}</Link>, label: <Link to={`/${subdomain}/login`}>{t("common.login")}</Link>,
onClick: () => { onClick: () => {
navigate(`/${subdomain}/login`); navigate(`/${subdomain}/login`);
}, },
}] : []), },
...(token ? [{ ]
: []),
...(token
? [
{
key: "logout", key: "logout",
icon: ( icon: (
<LogoutOutlined <LogoutOutlined
@@ -99,8 +107,11 @@ export default function useHeaderMenu() {
label: <div>{t("common.logout")}</div>, label: <div>{t("common.logout")}</div>,
onClick: () => { onClick: () => {
dispatch(logoutThunk()); dispatch(logoutThunk());
dispatch(clearCart());
}, },
}] : []), },
]
: []),
]; ];
return { menuItems }; return { menuItems };
} }

View File

@@ -1,70 +0,0 @@
import { BugFilled } from "@ant-design/icons";
import { MenuProps } from "antd";
import { PATHS } from "utils/constants";
// import WarehouseIcon from "components/Icons/WarehouseIcon";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
export default function useSidebarItems() {
type MenuItem = Required<MenuProps>["items"][number] & {
permission: string;
children?: MenuItem[];
};
const { t } = useTranslation();
// const [isAuth] = useAuth();
const getItem = (
label: React.ReactNode,
key: React.Key,
permission?: string,
icon?: React.ReactNode,
children?: MenuItem[],
type?: "group"
): MenuItem => {
return {
key,
icon,
children,
label,
type,
permission,
} as MenuItem;
};
// Recursive function to filter items based on permissions
const getGrantedItems = (items: any[]): MenuItem[] => {
return items
.filter(() => true)// Filter out items without permission
.map((item: any) => {
if (item.children) {
// Recursively filter children
return {
...item,
children: getGrantedItems(item.children),
};
}
return item;
});
};
const items: MenuProps["items"] = [
getItem(
t("menu"),
PATHS.menu,
undefined,
<div style={{ marginTop: 10 }}>
<Link style={{}} to={PATHS.menu}>
<BugFilled className="icon-container pos-icon" />
</Link>
</div>
),
];
// if we have a menu with empty children after applying "getGrantedItems"
// we going to remove it
const grantedItems = getGrantedItems(items).filter(
(i) => (i.children && i.children.length !== 0) || !i.children
);
return grantedItems;
}

View File

@@ -0,0 +1,76 @@
.checkoutContainer {
display: flex;
flex-direction: column;
padding: 16px;
gap: 16px;
overflow: auto;
scrollbar-width: none;
}
.carouselContainer {
display: flex;
align-items: center;
justify-content: center;
gap: 16px;
width: 100%;
margin-bottom: 24px;
}
.cardWrapper {
display: flex;
align-items: center;
justify-content: center;
}
.cardImage {
width: 205px;
height: 134px;
object-fit: cover;
border-radius: 8px;
}
.arrowButton {
display: flex;
align-items: center;
justify-content: center;
min-width: 40px;
height: 40px;
padding: 0;
border: none;
background: transparent;
cursor: pointer;
}
.arrowButton:hover {
background: rgba(0, 0, 0, 0.04);
border-radius: 50%;
}
/* CheckoutButton Styles */
.checkoutButtonContainer {
width: 100%;
padding: 16px 16px 0;
position: sticky;
bottom: 0;
left: 0;
height: 80px;
display: flex;
flex-direction: row;
justify-content: space-around;
gap: 1rem;
background-color: var(--secondary-background);
box-shadow: none;
z-index: 999;
}
/* Dark theme styles for checkout */
:global(.darkApp) .checkoutButtonContainer {
background-color: #000000 !important;
}
.checkoutButton {
width: 100%;
height: 48px;
margin-bottom: 16px;
box-shadow: none;
}

View File

@@ -0,0 +1,192 @@
import { Layout, Image, Button, Form, Skeleton } from "antd";
import ProHeader from "components/ProHeader/ProHeader";
import { useTranslation } from "react-i18next";
import styles from "./CardDetails.module.css";
import ProText from "components/ProText";
import { useGetEGiftCardsQuery } from "redux/api/others";
import { selectCart } from "features/order/orderSlice";
import { useAppSelector } from "redux/hooks";
import { useState, useEffect, useCallback } from "react";
import BackIcon from "components/Icons/BackIcon";
import NextIcon from "components/Icons/NextIcon";
import cardStyles from "./CardDetails.module.css";
import ReceivernformationCard from "./components/ReceivernformationCard/ReceivernformationCard";
import SenderformationCard from "./components/SenderformationCard/SenderformationCard";
import TimeEstimateCard from "pages/cart/components/timeEstimate/TimeEstimateCard";
import { useNavigate, useParams } from "react-router-dom";
import GiftAmountCard from "./components/GiftAmountCard/GiftAmountCard";
import { GiftAmountBottomSheet } from "./components/GiftAmountBottomSheet";
import PickupTimeCard from "pages/checkout/components/pickupEstimate/TimeEstimateCard";
export default function CardDetailsPage() {
const { t } = useTranslation();
const { data: cards, isLoading } = useGetEGiftCardsQuery();
const { giftDetails } = useAppSelector(selectCart);
const { isRTL } = useAppSelector((state) => state.locale);
const [currentIndex, setCurrentIndex] = useState(0);
const [isGiftAmountBottomSheetOpen, setIsGiftAmountBottomSheetOpen] =
useState(false);
const { subdomain } = useParams();
const navigate = useNavigate();
const [form] = Form.useForm();
// Find the initial index based on selected cardId from gift details
useEffect(() => {
if (cards && giftDetails?.cardId) {
const index = cards.findIndex(
(card) => card?.id?.toString() === giftDetails.cardId.toString(),
);
if (index !== -1) {
setCurrentIndex(index);
}
}
}, [cards, giftDetails?.cardId]);
const handlePrevious = () => {
if (cards && cards.length > 0) {
setCurrentIndex((prev) => (prev === 0 ? cards.length - 1 : prev - 1));
}
};
const handleNext = () => {
if (cards && cards.length > 0) {
setCurrentIndex((prev) => (prev === cards.length - 1 ? 0 : prev + 1));
}
};
const currentCard = cards && cards.length > 0 ? cards[currentIndex] : null;
const handleCheckout = useCallback(async () => {
try {
await form.validateFields();
navigate(`/${subdomain}/checkout`);
} catch (error) {
console.log(error);
}
}, [subdomain, form, navigate]);
useEffect(() => {
form.setFieldsValue({
receiverName: giftDetails?.receiverName,
senderName: giftDetails?.senderName,
senderPhone: giftDetails?.senderPhone,
senderEmail: giftDetails?.senderEmail,
receiverPhone: giftDetails?.receiverPhone,
message: giftDetails?.message,
amount: giftDetails?.amount,
cardId: giftDetails?.cardId,
giftType: giftDetails?.giftType,
isSecret: giftDetails?.isSecret,
});
}, [giftDetails?.receiverName, giftDetails?.senderName, form]);
return (
<>
<Layout>
<ProHeader>{t("cardDetails.title")}</ProHeader>
<Layout.Content className={styles.checkoutContainer}>
<div
style={{
textAlign: "center",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
margin: "50px 28px 35px 28px",
gap: 8,
}}
>
<ProText
style={{ fontSize: 16, fontWeight: 600, color: "#333333" }}
>
{t("cardDetails.addGiftDetails")}
</ProText>
<ProText
style={{ fontSize: 14, fontWeight: 400, color: "#95949C" }}
>
{t("cardDetails.description")}
</ProText>
</div>
{isLoading || !currentCard ? (
<div className={cardStyles.carouselContainer}>
<Skeleton.Avatar
active
size={40}
shape="circle"
style={{ flexShrink: 0 }}
/>
<div className={cardStyles.cardWrapper}>
<Skeleton.Image
active
style={{
width: 205,
height: 134,
borderRadius: 8,
}}
/>
</div>
<Skeleton.Avatar
active
size={40}
shape="circle"
style={{ flexShrink: 0 }}
/>
</div>
) : (
<div className={cardStyles.carouselContainer}>
<Button
type="text"
className={cardStyles.arrowButton}
onClick={isRTL ? handleNext : handlePrevious}
icon={<BackIcon iconSize={24} />}
/>
<div className={cardStyles.cardWrapper}>
<Image
src={currentCard.imageURL}
alt={currentCard.image}
width={205}
height={134}
className={cardStyles.cardImage}
/>
</div>
<Button
type="text"
className={cardStyles.arrowButton}
onClick={isRTL ? handlePrevious : handleNext}
icon={<NextIcon iconSize={24} />}
/>
</div>
)}
<Form
form={form}
layout="vertical"
style={{ display: "flex", flexDirection: "column", gap: 16 }}
>
{giftDetails?.giftType !== "items" && (
<GiftAmountCard
onOpen={() => setIsGiftAmountBottomSheetOpen(true)}
/>
)}
<ReceivernformationCard />
<SenderformationCard />
<PickupTimeCard />
</Form>
</Layout.Content>
<Layout.Footer className={styles.checkoutButtonContainer}>
<Button
type="primary"
shape="round"
className={styles.checkoutButton}
onClick={handleCheckout}
>
{t("cardDetails.checkout")}
</Button>
</Layout.Footer>
</Layout>
<GiftAmountBottomSheet
isOpen={isGiftAmountBottomSheetOpen}
onClose={() => setIsGiftAmountBottomSheetOpen(false)}
/>
</>
);
}

View File

@@ -0,0 +1,81 @@
import { Card, Divider, Image } from "antd";
import { EGiftCard } from "../type";
import { useGetEGiftCardsQuery } from "redux/api/others";
import LoadingSpinner from "components/LoadingSpinner/LoadingSpinner";
import ProText from "components/ProText";
import { useTranslation } from "react-i18next";
import { updateGiftDetails } from "features/order/orderSlice";
import { useAppDispatch } from "redux/hooks";
import { useNavigate, useParams } from "react-router-dom";
export default function ECardList() {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const { subdomain } = useParams();
const { data: eGiftCards, isLoading } = useGetEGiftCardsQuery();
const { t } = useTranslation();
console.log(eGiftCards);
const handleCardClick = (id: number) => {
dispatch(updateGiftDetails({ cardId: id.toString() }));
navigate(`/${subdomain}/card-details`);
};
if (isLoading) {
return <LoadingSpinner />;
}
return (
<Card style={{ textAlign: "center" }}>
<div style={{ paddingBottom: 12 }}>
<ProText
style={{
fontSize: 16,
fontWeight: 600,
color: "#333333",
}}
>
{t("eGiftCards.pickCardForYourGift")}
</ProText>
</div>
<ProText
style={{
fontFamily: "Outfit",
fontWeight: 400,
fontStyle: "Regular",
fontSize: 14,
lineHeight: "140%",
letterSpacing: 0,
textAlign: "center",
color: "#95949C",
}}
>
{t("eGiftCards.chooseDesignToMatchTheOccasion")}
</ProText>
<Divider style={{ margin: "12px 0 16px 0" }} />
<div
style={{
display: "grid",
gridTemplateColumns: "1fr 1fr",
padding: "8px",
}}
>
{eGiftCards?.map((card: EGiftCard) => (
<Image
src={card.imageURL}
alt={card.image}
width={135}
height={88}
preview={false}
onClick={() => handleCardClick(card.id)}
style={{
width: "100%",
height: "100%",
objectFit: "cover",
borderRadius: 8,
}}
/>
))}
</div>
</Card>
);
}

View File

@@ -0,0 +1,116 @@
import { Button, Form } from "antd";
import { ProBottomSheet } from "components/ProBottomSheet/ProBottomSheet.tsx";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { selectCart, updateGiftDetails } from "features/order/orderSlice";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import ProText from "components/ProText";
import { ProInputNumber } from "components/Inputs/ProInputNumber";
interface SplitBillChoiceBottomSheetProps {
isOpen: boolean;
onClose: () => void;
}
export function GiftAmountBottomSheet({
isOpen,
onClose,
}: SplitBillChoiceBottomSheetProps) {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { giftDetails } = useAppSelector(selectCart);
const [amount, setAmount] = useState<string>(
giftDetails?.amount && giftDetails?.amount > 0
? giftDetails?.amount?.toString()
: "",
);
const handleSave = () => {
const numAmount = parseFloat(amount) || 0;
dispatch(updateGiftDetails({ amount: numAmount }));
onClose();
};
return (
<ProBottomSheet
isOpen={isOpen}
onClose={onClose}
title={t("cardDetails.customAmount")}
showCloseButton={true}
initialSnap={1}
height={295}
snapPoints={[295]}
contentStyle={{
padding: 0,
}}
>
<div
style={{
padding: "20px 20px 0 20px",
display: "flex",
flexDirection: "column",
}}
>
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
color: "#333333",
}}
>
{t("cardDetails.enterCustomOucherAmount")}
</ProText>
<Form.Item name="amount">
<ProInputNumber
value={amount}
onChange={(value) => {
setAmount(value?.toString() || "");
dispatch(updateGiftDetails({ amount: Number(value) || 0 }));
}}
placeholder={t("cardDetails.amount")}
min={0}
/>
</Form.Item>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
color: "#B1B0B6",
}}
>
{t("cardDetails.minimumAmountShouldBe1OMR")}
</ProText>
</div>
</div>
<div
style={{
display: "flex",
gap: 12,
margin: 20,
}}
>
<Button
type="primary"
style={{
flex: 1,
boxShadow: "none",
height: 48,
}}
onClick={handleSave}
disabled={!amount || parseFloat(amount) <= 0}
>
{t("cardDetails.add")}
</Button>
</div>
</ProBottomSheet>
);
}

View File

@@ -0,0 +1,24 @@
.customerInformationCard {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 16px;
width: 100%;
}
.customerInformationCard > * {
min-width: 0;
width: 100%;
}
/* Reduce gap on small screens to prevent overflow */
@media (max-width: 480px) {
.customerInformationCard {
gap: 8px;
}
}
.editIcon {
position: absolute;
top: -7px;
right: 5px;
}

View File

@@ -0,0 +1,166 @@
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import { useTranslation } from "react-i18next";
import { Button } from "antd";
import styles from "./GiftAmountCard.module.css";
import ArabicPrice from "components/ArabicPrice";
import ProText from "components/ProText";
import EditIcon from "components/Icons/EditIcon";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import { updateGiftDetails } from "features/order/orderSlice";
import { useState, useEffect, useMemo } from "react";
export default function GiftAmountCard({ onOpen }: { onOpen: () => void }) {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { giftDetails } = useAppSelector((state) => state.order);
const [selectedAmount, setSelectedAmount] = useState<number | null>(null);
useEffect(() => {
const amount = giftDetails?.amount;
if (amount === 10 || amount === 20 || amount === 30) {
setSelectedAmount(amount);
} else {
setSelectedAmount(null);
}
}, [giftDetails?.amount]);
const handleAmountClick = (amount: number) => {
setSelectedAmount(amount);
dispatch(updateGiftDetails({ amount }));
};
const isDefaultAmount = useMemo(() => {
const amount = giftDetails?.amount;
return amount === 10 || amount === 20 || amount === 30;
}, [giftDetails?.amount]);
return (
<ProInputCard title={t("cardDetails.eCardAmount")}>
<div className={styles.customerInformationCard}>
<Button
onClick={() => handleAmountClick(10)}
style={{
borderRadius: 100,
height: 40,
border: "none",
backgroundColor:
selectedAmount === 10 && isDefaultAmount
? "#FFB7001F"
: "#5F6C7B0D",
}}
>
<ArabicPrice
price={10.0}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color:
selectedAmount === 10 && isDefaultAmount
? "#CC9300"
: "#5F6C7B",
}}
/>
</Button>
<Button
onClick={() => handleAmountClick(20)}
style={{
borderRadius: 100,
height: 40,
border: "none",
backgroundColor:
selectedAmount === 20 && isDefaultAmount
? "#FFB7001F"
: "#5F6C7B0D",
}}
>
<ArabicPrice
price={20.0}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color:
selectedAmount === 20 && isDefaultAmount
? "#CC9300"
: "#5F6C7B",
}}
/>
</Button>
<Button
onClick={() => handleAmountClick(30)}
style={{
borderRadius: 100,
height: 40,
border: "none",
backgroundColor:
selectedAmount === 30 && isDefaultAmount
? "#FFB7001F"
: "#5F6C7B0D",
}}
>
<ArabicPrice
price={30.0}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color:
selectedAmount === 30 && isDefaultAmount
? "#CC9300"
: "#5F6C7B",
}}
/>
</Button>
</div>
<Button
style={{
marginTop: 16,
borderRadius: 100,
width: "100%",
height: 40,
border: "none",
backgroundColor:
giftDetails?.amount && !isDefaultAmount ? "#FFB7001F" : "#030014",
}}
icon={
giftDetails?.amount && !isDefaultAmount ? (
<EditIcon
className={styles.editIcon}
color={
giftDetails?.amount && !isDefaultAmount ? "#CC9300" : "#FFF"
}
/>
) : undefined
}
iconPlacement="start"
onClick={onOpen}
>
<ProText
style={{
color: giftDetails?.amount && !isDefaultAmount ? "#CC9300" : "#FFF",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
}}
>
{giftDetails?.amount && !isDefaultAmount
? giftDetails?.amount
: t("cardDetails.costumeAmount")}
</ProText>
</Button>
</ProInputCard>
);
}

View File

@@ -0,0 +1,5 @@
.customerInformationCard {
display: flex;
flex-direction: column;
gap: 16px;
}

View File

@@ -0,0 +1,56 @@
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import ProPhoneInput from "components/ProPhoneInput";
import { selectCart, updateGiftDetails } from "features/order/orderSlice";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks";
import { Form, Input } from "antd";
import styles from "./ReceivernformationCard.module.css";
import TextArea from "antd/es/input/TextArea";
export default function ReceivernformationCard() {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const { giftDetails } = useAppSelector(selectCart);
return (
<ProInputCard title={t("cardDetails.receiverInformation")}>
<div className={styles.customerInformationCard}>
<Form.Item name="receiverName" rules={[{ required: true, message: t("cardDetails.receiverNameRequired") }]}>
<Input
placeholder={t("cardDetails.receiverName")}
size="large"
autoFocus={false}
style={{ padding: "7px 11px", height: 48, borderRadius: 888 }}
value={giftDetails?.receiverName}
onChange={(e) => {
dispatch(updateGiftDetails({ receiverName: e.target.value }));
}}
/>
</Form.Item>
<ProPhoneInput
propName="receiverPhone"
value={giftDetails?.receiverPhone}
onChange={(e) => {
dispatch(updateGiftDetails({ receiverPhone: e }));
}}
/>
<Form.Item name="message">
<TextArea
placeholder={t("address.message")}
size="large"
style={{
fontSize: 14,
borderRadius: 10,
}}
rows={2}
autoFocus={false}
value={giftDetails?.message}
onChange={(e) => {
dispatch(updateGiftDetails({ message: e.target.value }));
}}
/>
</Form.Item>
</div>
</ProInputCard>
);
}

View File

@@ -0,0 +1,5 @@
.customerInformationCard {
display: flex;
flex-direction: column;
gap: 16px;
}

Some files were not shown because too many files have changed in this diff Show More