Compare commits
5 Commits
a64f92d1fd
...
5d523e2508
| Author | SHA1 | Date | |
|---|---|---|---|
| 5d523e2508 | |||
| 3ed5c4d5d6 | |||
| f3b8bdba63 | |||
| e8c4bc909c | |||
| dd11665743 |
596
README.md
596
README.md
@@ -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**
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
import type { ThemeConfig } from "antd";
|
import type { ThemeConfig } from "antd";
|
||||||
|
|
||||||
export const colors = {
|
export const colors = {
|
||||||
primary: "#CC9300",
|
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)",
|
||||||
};
|
};
|
||||||
@@ -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
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -425,12 +425,10 @@
|
|||||||
"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": "معاينة التقسيم",
|
||||||
@@ -453,5 +451,11 @@
|
|||||||
"shareLink": "شارك الرابط",
|
"shareLink": "شارك الرابط",
|
||||||
"shareLinkDescription": "يمكن للأصدقاء مسح الكود الباري أو استخدام الرابط لعرض الفاتورة ودفع نصيبهم بأمان من هاتفهم.",
|
"shareLinkDescription": "يمكن للأصدقاء مسح الكود الباري أو استخدام الرابط لعرض الفاتورة ودفع نصيبهم بأمان من هاتفهم.",
|
||||||
"shareThisToSplitTheBill": "شارك هذا لتقسيم الفاتورة"
|
"shareThisToSplitTheBill": "شارك هذا لتقسيم الفاتورة"
|
||||||
|
},
|
||||||
|
"gift": {
|
||||||
|
"items": "هدية العناصر",
|
||||||
|
"balance": "هدية الرصيد",
|
||||||
|
"itemsAndBalance": "العناصر والرصيد",
|
||||||
|
"selectGiftType": "اختر نوع الهدية"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -437,12 +437,10 @@
|
|||||||
"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",
|
||||||
@@ -465,5 +463,11 @@
|
|||||||
"shareLink": "Share Link",
|
"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.",
|
"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"
|
"shareThisToSplitTheBill": "Share this to split the bill"
|
||||||
|
},
|
||||||
|
"gift": {
|
||||||
|
"items": "Gift Items",
|
||||||
|
"balance": "Gift Balance",
|
||||||
|
"itemsAndBalance": "Items and Balance",
|
||||||
|
"selectGiftType": "Select Gift Type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
83
src/components/CustomBottomSheet/GiftTypeBottomSheet.tsx
Normal file
83
src/components/CustomBottomSheet/GiftTypeBottomSheet.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { ProBottomSheet } from "../ProBottomSheet/ProBottomSheet";
|
||||||
|
import { updateGiftType } 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";
|
||||||
|
|
||||||
|
interface GiftTypeBottomSheetProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSave?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function GiftTypeBottomSheet({
|
||||||
|
isOpen,
|
||||||
|
onClose,
|
||||||
|
onSave,
|
||||||
|
}: GiftTypeBottomSheetProps) {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const handleSave = (value: string) => {
|
||||||
|
dispatch(updateGiftType(value));
|
||||||
|
onClose();
|
||||||
|
onSave?.();
|
||||||
|
};
|
||||||
|
|
||||||
|
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("items")}>
|
||||||
|
<GiftItemsBtnIcon />
|
||||||
|
<br />
|
||||||
|
<ProText style={textStyle}>{t("gift.items")}</ProText>
|
||||||
|
</div>
|
||||||
|
<div style={{ width: 60 }} onClick={() => handleSave("vouchers")}>
|
||||||
|
<GiftBalanceBtnIcon />
|
||||||
|
<br />
|
||||||
|
<ProText style={textStyle}>{t("gift.balance")}</ProText>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
style={{ width: 60 }}
|
||||||
|
onClick={() => handleSave("itemsAndVouchers")}
|
||||||
|
>
|
||||||
|
<GiftItemsAndBalanceBtnIcon />
|
||||||
|
<br />
|
||||||
|
<ProText style={textStyle}>{t("gift.itemsAndBalance")}</ProText>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ProBottomSheet>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,23 +7,26 @@ const EditIcon = ({ className, onClick }: 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="#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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
60
src/components/Icons/GiftBalanceBtnIcon.tsx
Normal file
60
src/components/Icons/GiftBalanceBtnIcon.tsx
Normal 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;
|
||||||
74
src/components/Icons/GiftItemsAndBalanceBtnIcon.tsx
Normal file
74
src/components/Icons/GiftItemsAndBalanceBtnIcon.tsx
Normal 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;
|
||||||
67
src/components/Icons/GiftItemsBtnIcon.tsx
Normal file
67
src/components/Icons/GiftItemsBtnIcon.tsx
Normal 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;
|
||||||
31
src/components/Icons/RCardIcon.tsx
Normal file
31
src/components/Icons/RCardIcon.tsx
Normal 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;
|
||||||
@@ -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"
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
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 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";
|
||||||
@@ -8,11 +7,13 @@ 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 { data: restaurant } = useGetRestaurantDetailsQuery(subdomain);
|
||||||
|
const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
const token = localStorage.getItem(ACCESS_TOKEN);
|
const token = localStorage.getItem(ACCESS_TOKEN);
|
||||||
const isHasLoyaltyGift =
|
const isHasLoyaltyGift =
|
||||||
(restaurant?.loyalty_stamps || 0) -
|
(restaurant?.loyalty_stamps || 0) -
|
||||||
@@ -29,11 +30,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>
|
||||||
@@ -42,10 +52,13 @@ 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 &&
|
||||||
@@ -61,12 +74,21 @@ 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>
|
||||||
@@ -80,7 +102,7 @@ const LoyaltyCard = () => {
|
|||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
gap: 10,
|
gap: 12,
|
||||||
overflow: "hidden",
|
overflow: "hidden",
|
||||||
scrollbarWidth: "none",
|
scrollbarWidth: "none",
|
||||||
}}
|
}}
|
||||||
|
|||||||
@@ -51,9 +51,20 @@ export default function OrderSummary() {
|
|||||||
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" style={titlesStyle}>
|
<ProText type="secondary" style={titlesStyle}>
|
||||||
{t("cart.basketTotal")}
|
{t("cart.basketTotal")}
|
||||||
@@ -67,7 +78,7 @@ export default function OrderSummary() {
|
|||||||
</ProText>
|
</ProText>
|
||||||
<ArabicPrice
|
<ArabicPrice
|
||||||
price={Number(restaurant?.delivery_fees || 0)}
|
price={Number(restaurant?.delivery_fees || 0)}
|
||||||
style={titlesStyle}
|
style={{ ...titlesStyle, color: "#434E5C" }}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -75,23 +86,32 @@ export default function OrderSummary() {
|
|||||||
<ProText type="secondary" style={titlesStyle}>
|
<ProText type="secondary" style={titlesStyle}>
|
||||||
{t("cart.discount")}
|
{t("cart.discount")}
|
||||||
</ProText>
|
</ProText>
|
||||||
<ArabicPrice price={discountAmount} style={titlesStyle} />
|
<ArabicPrice
|
||||||
|
price={discountAmount}
|
||||||
|
style={{ ...titlesStyle, color: "#434E5C" }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.summaryRow}>
|
<div className={styles.summaryRow}>
|
||||||
<ProText type="secondary" style={titlesStyle}>
|
<ProText type="secondary" style={titlesStyle}>
|
||||||
{t("cart.tax")}
|
{t("cart.tax")}
|
||||||
</ProText>
|
</ProText>
|
||||||
<ArabicPrice price={taxAmount || 0} style={titlesStyle} />
|
<ArabicPrice
|
||||||
|
price={taxAmount || 0}
|
||||||
|
style={{ ...titlesStyle, color: "#434E5C" }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{splitBillAmount > 0 && (
|
{splitBillAmount > 0 && (
|
||||||
<div className={styles.summaryRow}>
|
<div className={styles.summaryRow}>
|
||||||
<ProText type="secondary" style={titlesStyle}>
|
<ProText type="secondary" style={titlesStyle}>
|
||||||
{t("splitBill.splitBillAmount")}
|
{t("splitBill.splitBillAmount")}
|
||||||
</ProText>
|
</ProText>
|
||||||
<ArabicPrice price={splitBillAmount} style={titlesStyle} />
|
<ArabicPrice
|
||||||
|
price={splitBillAmount}
|
||||||
|
style={{ ...titlesStyle, color: "#434E5C" }}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Divider className={styles.summaryDivider} />
|
<Divider className={styles.summaryDivider} style={{ margin: "0" }} />
|
||||||
<div className={`${styles.summaryRow} ${styles.totalRow}`}>
|
<div className={`${styles.summaryRow} ${styles.totalRow}`}>
|
||||||
<ProText
|
<ProText
|
||||||
style={{
|
style={{
|
||||||
@@ -101,6 +121,7 @@ export default function OrderSummary() {
|
|||||||
lineHeight: "140%",
|
lineHeight: "140%",
|
||||||
letterSpacing: "0%",
|
letterSpacing: "0%",
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
|
color: "#333333",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t("cart.totalAmount")}
|
{t("cart.totalAmount")}
|
||||||
|
|||||||
@@ -27,4 +27,78 @@
|
|||||||
top: 1px
|
top: 1px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.eCardIcon {
|
||||||
|
position: relative;
|
||||||
|
top: 3px;
|
||||||
|
margin-right: 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%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circleCheckbox :global(.ant-checkbox-checked .ant-checkbox-inner::after) {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-left: -9px;
|
||||||
|
margin-top: -9px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ffb700;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circleCheckbox :global(.ant-radio-checked .ant-radio-inner::after) {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
margin-left: -9px;
|
||||||
|
margin-top: -9px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #ffb700;
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,15 +14,17 @@ 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 RCardIcon from "components/Icons/RCardIcon";
|
||||||
|
|
||||||
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 { 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,9 +34,21 @@ 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: grandTotal.toFixed(2),
|
||||||
style: {
|
style: {
|
||||||
color: colors.primary,
|
color: colors.primary,
|
||||||
},
|
},
|
||||||
@@ -48,7 +62,12 @@ const PaymentMethods = () => {
|
|||||||
// hideCurrency: true,
|
// hideCurrency: true,
|
||||||
// },
|
// },
|
||||||
{
|
{
|
||||||
label: t("checkout.differentCard"),
|
label: (
|
||||||
|
<>
|
||||||
|
<RCardIcon className={styles.eCardIcon} />
|
||||||
|
<ProText style={{color: '#5F6C7B'}}>{t("checkout.differentCard")}</ProText>
|
||||||
|
</>
|
||||||
|
),
|
||||||
value: "differentCard",
|
value: "differentCard",
|
||||||
icon: (
|
icon: (
|
||||||
<div className={styles.differentCardIcon}>
|
<div className={styles.differentCardIcon}>
|
||||||
@@ -56,7 +75,7 @@ const PaymentMethods = () => {
|
|||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
hideCurrency: true,
|
hideCurrency: true,
|
||||||
}
|
},
|
||||||
// {
|
// {
|
||||||
// label: t("checkout.fascanoWallet"),
|
// label: t("checkout.fascanoWallet"),
|
||||||
// value: "fascanoWallet",
|
// value: "fascanoWallet",
|
||||||
@@ -95,6 +114,7 @@ const PaymentMethods = () => {
|
|||||||
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: 48,
|
height: 48,
|
||||||
borderRadius: 888,
|
borderRadius: 888,
|
||||||
|
|||||||
@@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ const ProInputCard: FunctionComponent<ProInputCardProps> = ({
|
|||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
lineHeight: "140%",
|
lineHeight: "140%",
|
||||||
letterSpacing: "0%",
|
letterSpacing: "0%",
|
||||||
|
color: '#333333',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{title}
|
{title}
|
||||||
@@ -43,7 +44,7 @@ const ProInputCard: FunctionComponent<ProInputCardProps> = ({
|
|||||||
{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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export interface GiftDetailsType {
|
|||||||
senderPhone: string;
|
senderPhone: string;
|
||||||
senderEmail: string;
|
senderEmail: string;
|
||||||
isSecret: boolean;
|
isSecret: boolean;
|
||||||
|
cardId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DiscountData {
|
interface DiscountData {
|
||||||
@@ -75,7 +76,9 @@ interface CartState {
|
|||||||
hiddenServices: number;
|
hiddenServices: number;
|
||||||
visibleServices: number;
|
visibleServices: number;
|
||||||
fee: number;
|
fee: number;
|
||||||
}
|
giftType: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// localStorage keys
|
// localStorage keys
|
||||||
export const CART_STORAGE_KEYS = {
|
export const CART_STORAGE_KEYS = {
|
||||||
@@ -108,6 +111,7 @@ export const CART_STORAGE_KEYS = {
|
|||||||
TOTAL_SERVICES: "fascano_total_services",
|
TOTAL_SERVICES: "fascano_total_services",
|
||||||
HIDDEN_SERVICES: "fascano_hidden_services",
|
HIDDEN_SERVICES: "fascano_hidden_services",
|
||||||
VISIBLE_SERVICES: "fascano_visible_services",
|
VISIBLE_SERVICES: "fascano_visible_services",
|
||||||
|
GIFT_TYPE: "fascano_gift_type",
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
// Utility functions for localStorage
|
// Utility functions for localStorage
|
||||||
@@ -200,6 +204,7 @@ const initialState: CartState = {
|
|||||||
hiddenServices: 0,
|
hiddenServices: 0,
|
||||||
visibleServices: 0,
|
visibleServices: 0,
|
||||||
fee: 0,
|
fee: 0,
|
||||||
|
giftType: getFromLocalStorage(CART_STORAGE_KEYS.GIFT_TYPE, ""),
|
||||||
};
|
};
|
||||||
|
|
||||||
const orderSlice = createSlice({
|
const orderSlice = createSlice({
|
||||||
@@ -681,6 +686,9 @@ const orderSlice = createSlice({
|
|||||||
updateCustomerName(state, action: PayloadAction<string>) {
|
updateCustomerName(state, action: PayloadAction<string>) {
|
||||||
state.customerName = action.payload;
|
state.customerName = action.payload;
|
||||||
},
|
},
|
||||||
|
updateGiftType(state, action: PayloadAction<string>) {
|
||||||
|
state.giftType = action.payload;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -720,6 +728,7 @@ export const {
|
|||||||
updateOrder,
|
updateOrder,
|
||||||
updateSplitBillAmount,
|
updateSplitBillAmount,
|
||||||
updateCustomerName,
|
updateCustomerName,
|
||||||
|
updateGiftType,
|
||||||
} = orderSlice.actions;
|
} = orderSlice.actions;
|
||||||
|
|
||||||
// Tax calculation helper functions
|
// Tax calculation helper functions
|
||||||
|
|||||||
0
src/pages/EGiftCards/EGiftCards.module.css
Normal file
0
src/pages/EGiftCards/EGiftCards.module.css
Normal file
24
src/pages/EGiftCards/EGiftCards.tsx
Normal file
24
src/pages/EGiftCards/EGiftCards.tsx
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { Layout, Spin } from "antd";
|
||||||
|
import PaymentMethods from "components/PaymentMethods/PaymentMethods";
|
||||||
|
import ProHeader from "components/ProHeader/ProHeader";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import styles from "../address/address.module.css";
|
||||||
|
import ECardButton from "./components/ECardButton";
|
||||||
|
import { useGetEGiftCardsQuery } from "redux/api/others";
|
||||||
|
import ECardList from "./components/ECardList";
|
||||||
|
|
||||||
|
export default function EGiftCardsPage() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { data: eGiftCards, isLoading } = useGetEGiftCardsQuery();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout>
|
||||||
|
<ProHeader>{t("checkout.title")}</ProHeader>
|
||||||
|
<Layout.Content className={styles.checkoutContainer}>
|
||||||
|
<PaymentMethods />
|
||||||
|
{isLoading ? <Spin /> : <ECardList eGiftCards={eGiftCards || []} />}
|
||||||
|
</Layout.Content>
|
||||||
|
<ECardButton />
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
}
|
||||||
37
src/pages/EGiftCards/components/ECardButton.tsx
Normal file
37
src/pages/EGiftCards/components/ECardButton.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { Button, Layout, message } from "antd";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import styles from "../../address/address.module.css";
|
||||||
|
import { useCallback } from "react";
|
||||||
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
|
import { selectCart } from "features/order/orderSlice";
|
||||||
|
import { useAppSelector } from "redux/hooks";
|
||||||
|
|
||||||
|
export default function ECardButton() {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const { subdomain } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { giftDetails } = useAppSelector(selectCart);
|
||||||
|
|
||||||
|
const handleSelectCard = useCallback(() => {
|
||||||
|
if (giftDetails?.cardId) {
|
||||||
|
navigate(`/${subdomain}/cardDetails/${giftDetails?.cardId}`);
|
||||||
|
} else {
|
||||||
|
message.error(t("gift.pleaseSelectCard"));
|
||||||
|
}
|
||||||
|
}, [navigate, subdomain, giftDetails?.cardId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Layout.Footer className={styles.checkoutButtonContainer}>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
shape="round"
|
||||||
|
className={styles.placeOrderButton}
|
||||||
|
onClick={handleSelectCard}
|
||||||
|
>
|
||||||
|
{t("checkout.placeOrder")}
|
||||||
|
</Button>
|
||||||
|
</Layout.Footer>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
src/pages/EGiftCards/components/ECardList.tsx
Normal file
14
src/pages/EGiftCards/components/ECardList.tsx
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import { Card, Image } from "antd";
|
||||||
|
import { EGiftCard } from "../type";
|
||||||
|
|
||||||
|
export default function ECardList({ eGiftCards }: { eGiftCards: EGiftCard[] }) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{eGiftCards.map((card: EGiftCard) => (
|
||||||
|
<Card key={card.id}>
|
||||||
|
<Image src={card.imageURL} alt={card.image} />
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
9
src/pages/EGiftCards/type.ts
Normal file
9
src/pages/EGiftCards/type.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export interface EGiftCard {
|
||||||
|
id: number;
|
||||||
|
image: string;
|
||||||
|
restorant_id: number | null;
|
||||||
|
is_available: number;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
imageURL: string;
|
||||||
|
}
|
||||||
@@ -184,6 +184,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
border-color: #C0BFC4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.placeOrderButton {
|
.placeOrderButton {
|
||||||
|
|||||||
@@ -67,11 +67,6 @@
|
|||||||
top: 5px !important;
|
top: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.couponApplyIcon {
|
|
||||||
margin-left: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donateHandIcon {
|
.donateHandIcon {
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
@@ -434,11 +429,6 @@
|
|||||||
font-size: 14px !important;
|
font-size: 14px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.couponApplyIcon {
|
|
||||||
margin-left: 10px;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donateHandIcon {
|
.donateHandIcon {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
}
|
}
|
||||||
@@ -481,11 +471,6 @@
|
|||||||
font-size: 16px !important;
|
font-size: 16px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.couponApplyIcon {
|
|
||||||
margin-left: 12px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.donateHandIcon {
|
.donateHandIcon {
|
||||||
font-size: 32px;
|
font-size: 32px;
|
||||||
}
|
}
|
||||||
@@ -610,3 +595,14 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
top: 1px;
|
top: 1px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.couponApplyIcon {
|
||||||
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.editIcon {
|
||||||
|
position: absolute;
|
||||||
|
top: -7px;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Button, Form, Input, message } from "antd";
|
import { Button, Form, Input, message } from "antd";
|
||||||
import CouponHeartIcon from "components/Icons/cart/CouponHeart.tsx";
|
import CouponHeartIcon from "components/Icons/cart/CouponHeart.tsx";
|
||||||
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
|
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
|
||||||
|
import ProText from "components/ProText";
|
||||||
import {
|
import {
|
||||||
selectCart,
|
selectCart,
|
||||||
updateCoupon,
|
updateCoupon,
|
||||||
@@ -72,13 +73,8 @@ export default function CouponCard() {
|
|||||||
// <DonateIcon />
|
// <DonateIcon />
|
||||||
// </div>
|
// </div>
|
||||||
// }
|
// }
|
||||||
dividerStyle={{ margin: "5px 0 0 0" }}
|
|
||||||
>
|
|
||||||
<Form.Item
|
|
||||||
label={t("cart.couponCode")}
|
|
||||||
name="coupon"
|
|
||||||
style={{ position: "relative", top: -5 }}
|
|
||||||
>
|
>
|
||||||
|
<Form.Item name="coupon">
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("cart.couponCode")}
|
placeholder={t("cart.couponCode")}
|
||||||
size="large"
|
size="large"
|
||||||
@@ -93,13 +89,23 @@ export default function CouponCard() {
|
|||||||
width: 100,
|
width: 100,
|
||||||
height: 32,
|
height: 32,
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
backgroundColor: "black",
|
backgroundColor: "#333333",
|
||||||
color: "white",
|
color: "white",
|
||||||
|
fontWeight: 500,
|
||||||
|
fontStyle: "Medium",
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%"
|
||||||
}}
|
}}
|
||||||
onClick={() => handleCouponSave(coupon)}
|
onClick={() => handleCouponSave(coupon)}
|
||||||
|
icon={<CouponHeartIcon className={styles.couponApplyIcon} />}
|
||||||
|
iconPlacement="end"
|
||||||
|
>
|
||||||
|
<ProText
|
||||||
|
style={{ position: "relative", top: -1, color: "white" }}
|
||||||
>
|
>
|
||||||
{t("cart.apply")}
|
{t("cart.apply")}
|
||||||
<CouponHeartIcon className={styles.couponApplyIcon} />
|
</ProText>
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import styles from "pages/cart/cart.module.css";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
|
import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
|
||||||
import { colors } from "ThemeConstants.ts";
|
|
||||||
|
|
||||||
export default function RewardWaiterCard() {
|
export default function RewardWaiterCard() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -54,10 +53,32 @@ export default function RewardWaiterCard() {
|
|||||||
<DonateHandIcon className={styles.donateHandIcon} />
|
<DonateHandIcon className={styles.donateHandIcon} />
|
||||||
</div>
|
</div>
|
||||||
<div style={{ marginTop: 7 }}>
|
<div style={{ marginTop: 7 }}>
|
||||||
<ProTitle style={{ fontSize: 18, margin: 0 }}>
|
<div>
|
||||||
|
<ProText
|
||||||
|
style={{
|
||||||
|
fontWeight: 500,
|
||||||
|
fontStyle: "Medium",
|
||||||
|
fontSize: 16,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#333333",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t("cart.rewardYourWaiter")}
|
{t("cart.rewardYourWaiter")}
|
||||||
</ProTitle>
|
</ProText>
|
||||||
<ProText style={{ color: "rgba(95, 108, 123, 1)", fontSize: 12 }}>
|
</div>
|
||||||
|
<ProText
|
||||||
|
style={{
|
||||||
|
fontWeight: 400,
|
||||||
|
fontStyle: "Regular",
|
||||||
|
fontSize: 12,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#5F6C7B",
|
||||||
|
}}
|
||||||
|
>
|
||||||
{t("cart.rewardYourWaiter100")}
|
{t("cart.rewardYourWaiter100")}
|
||||||
</ProText>
|
</ProText>
|
||||||
</div>
|
</div>
|
||||||
@@ -74,41 +95,66 @@ export default function RewardWaiterCard() {
|
|||||||
style={{
|
style={{
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
height: 32,
|
height: 32,
|
||||||
borderColor: "rgba(255, 183, 0, 0.12)",
|
|
||||||
backgroundColor: colors.primary,
|
|
||||||
color: colors.primary,
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<ArabicPrice price={0.5} />
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
style={{
|
|
||||||
borderRadius: 100,
|
|
||||||
height: 32,
|
|
||||||
backgroundColor: "#5F6C7B0D",
|
|
||||||
color: "rgba(95, 108, 123, 1)",
|
|
||||||
border: "none",
|
border: "none",
|
||||||
|
backgroundColor: "#5F6C7B0D",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ArabicPrice price={1.0} />
|
<ArabicPrice
|
||||||
|
price={0.5}
|
||||||
|
textStyle={{
|
||||||
|
fontWeight: 500,
|
||||||
|
fontStyle: "Medium",
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#5F6C7B",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
style={{
|
style={{
|
||||||
borderRadius: 100,
|
borderRadius: 100,
|
||||||
height: 32,
|
height: 32,
|
||||||
color: colors.primary,
|
border: "none",
|
||||||
borderColor: "#d9d9d9",
|
backgroundColor: "#5F6C7B0D",
|
||||||
}}
|
}}
|
||||||
onClick={() => setIsTipOpen(true)}
|
|
||||||
>
|
>
|
||||||
<EditIcon />
|
<ArabicPrice
|
||||||
<ProText
|
price={1.0}
|
||||||
|
textStyle={{
|
||||||
|
fontWeight: 500,
|
||||||
|
fontStyle: "Medium",
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#5F6C7B",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
style={{
|
style={{
|
||||||
color: colors.primary,
|
borderRadius: 100,
|
||||||
|
height: 32,
|
||||||
|
border: "none",
|
||||||
|
backgroundColor: "#FFB7001F",
|
||||||
}}
|
}}
|
||||||
|
icon={<EditIcon className={styles.editIcon} />}
|
||||||
|
iconPlacement="end"
|
||||||
>
|
>
|
||||||
{t("cart.other")}
|
<ArabicPrice
|
||||||
</ProText>
|
price={3.0}
|
||||||
|
textStyle={{
|
||||||
|
fontWeight: 500,
|
||||||
|
fontStyle: "Medium",
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
textAlign: "center",
|
||||||
|
color: "#CC9300",
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { Link, useNavigate, useParams } from "react-router-dom";
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||||
import { useAppSelector } from "redux/hooks.ts";
|
import { useAppSelector } from "redux/hooks.ts";
|
||||||
import styles from "./footer.module.css";
|
import styles from "./footer.module.css";
|
||||||
|
import { OrderType } from "pages/checkout/hooks/types";
|
||||||
|
|
||||||
interface CartFooterProps {
|
interface CartFooterProps {
|
||||||
form: FormInstance;
|
form: FormInstance;
|
||||||
@@ -63,7 +64,9 @@ export default function CartFooter({ form }: CartFooterProps) {
|
|||||||
}}
|
}}
|
||||||
onClick={handleCheckoutClick}
|
onClick={handleCheckoutClick}
|
||||||
>
|
>
|
||||||
{t("cart.checkout")}
|
{orderType === OrderType.Gift
|
||||||
|
? t("cart.continueToGiftDetails")
|
||||||
|
: t("cart.checkout")}
|
||||||
</Button>
|
</Button>
|
||||||
</Layout.Footer>
|
</Layout.Footer>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -58,6 +58,17 @@ export default function CheckoutButton({ form }: { form: FormInstance }) {
|
|||||||
[orderType],
|
[orderType],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getSplitButtonTitle = useMemo(() => {
|
||||||
|
if (selectedSplitWay === "customAmount") {
|
||||||
|
return t("splitBill.customAmount");
|
||||||
|
} else if (selectedSplitWay === "equality") {
|
||||||
|
return t("splitBill.divideEqually");
|
||||||
|
} else if (selectedSplitWay === "payForItems") {
|
||||||
|
return t("splitBill.payForItems");
|
||||||
|
}
|
||||||
|
return t("checkout.splitBill");
|
||||||
|
}, [selectedSplitWay, t]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Layout.Footer className={styles.checkoutButtonContainer}>
|
<Layout.Footer className={styles.checkoutButtonContainer}>
|
||||||
@@ -66,9 +77,7 @@ export default function CheckoutButton({ form }: { form: FormInstance }) {
|
|||||||
className={styles.splitBillButton}
|
className={styles.splitBillButton}
|
||||||
onClick={handleSplitBillClick}
|
onClick={handleSplitBillClick}
|
||||||
>
|
>
|
||||||
{selectedSplitWay
|
{getSplitButtonTitle}
|
||||||
? t("checkout.removeSplitBill")
|
|
||||||
: t("checkout.splitBill")}
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
.customerInformationCard {
|
.customerInformationCard {
|
||||||
height: 215px !important;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
}
|
}
|
||||||
@@ -16,12 +16,9 @@ export default function CustomerInformationCard() {
|
|||||||
return (
|
return (
|
||||||
orderType !== OrderType.Gift && (
|
orderType !== OrderType.Gift && (
|
||||||
<>
|
<>
|
||||||
<ProInputCard
|
<ProInputCard title={t("checkout.customerInformation")}>
|
||||||
title={t("checkout.customerInformation")}
|
<div className={styles.customerInformationCard}>
|
||||||
className={styles.customerInformationCard}
|
<Form.Item name="customerName">
|
||||||
>
|
|
||||||
<div style={{ position: "relative", top: -25 }}>
|
|
||||||
<Form.Item label={t("checkout.customerName")} name="customerName">
|
|
||||||
<Input
|
<Input
|
||||||
placeholder={t("checkout.customerName")}
|
placeholder={t("checkout.customerName")}
|
||||||
size="large"
|
size="large"
|
||||||
@@ -33,9 +30,7 @@ export default function CustomerInformationCard() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</div>
|
<ProPhoneInput propName="phone" />
|
||||||
<div style={{ position: "relative", top: -35 }}>
|
|
||||||
<ProPhoneInput label={t("login.phone")} propName="phone" />
|
|
||||||
</div>
|
</div>
|
||||||
</ProInputCard>
|
</ProInputCard>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { Button, message } from "antd";
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
import { useNavigate, useParams } from "react-router-dom";
|
||||||
import { useGetRestaurantDetailsQuery } from "redux/api/others";
|
import { useGetRestaurantDetailsQuery } from "redux/api/others";
|
||||||
import { colors } from "ThemeConstants.ts";
|
|
||||||
import styles from "./AddToCartButton.module.css";
|
import styles from "./AddToCartButton.module.css";
|
||||||
import { useAppSelector, useAppDispatch } from "redux/hooks";
|
import { useAppSelector, useAppDispatch } from "redux/hooks";
|
||||||
import { Product } from "utils/types/appTypes";
|
import { Product } from "utils/types/appTypes";
|
||||||
@@ -215,7 +214,7 @@ export function AddToCartButton({ item }: { item: Product }) {
|
|||||||
onClick={handlePlusClick}
|
onClick={handlePlusClick}
|
||||||
className={styles.addButton}
|
className={styles.addButton}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: colors.primary,
|
backgroundColor: "#FFC600",
|
||||||
width: 28,
|
width: 28,
|
||||||
height: 28,
|
height: 28,
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
|
|||||||
@@ -118,9 +118,9 @@
|
|||||||
will-change: position, transform, opacity, filter;
|
will-change: position, transform, opacity, filter;
|
||||||
transform-origin: top center;
|
transform-origin: top center;
|
||||||
gap: 14px;
|
gap: 14px;
|
||||||
padding: 0.5rem 1rem 1rem 1rem;
|
padding: 0.5rem 24px 1rem 24px;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.categoriesContainer::-webkit-scrollbar {
|
.categoriesContainer::-webkit-scrollbar {
|
||||||
|
|||||||
@@ -291,8 +291,8 @@ export function CategoriesList({ categories }: CategoriesListProps) {
|
|||||||
|
|
||||||
color:
|
color:
|
||||||
activeCategory === category.id
|
activeCategory === category.id
|
||||||
? colors.primary
|
? "#FFC600"
|
||||||
: undefined,
|
: "#86858E",
|
||||||
fontWeight:
|
fontWeight:
|
||||||
activeCategory === category.id ? "600" : "500",
|
activeCategory === category.id ? "600" : "500",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
@@ -309,7 +309,7 @@ export function CategoriesList({ categories }: CategoriesListProps) {
|
|||||||
display: "inline-block",
|
display: "inline-block",
|
||||||
borderBottom:
|
borderBottom:
|
||||||
activeCategory === category.id && !isCategoriesSticky
|
activeCategory === category.id && !isCategoriesSticky
|
||||||
? `2px solid ${colors.primary}`
|
? `2px solid #FFC600`
|
||||||
: "none",
|
: "none",
|
||||||
paddingBottom: activeCategory === category.id ? 8 : 0,
|
paddingBottom: activeCategory === category.id ? 8 : 0,
|
||||||
marginTop: isCategoriesSticky ? 0 : 4,
|
marginTop: isCategoriesSticky ? 0 : 4,
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
margin-bottom: 105px !important;
|
margin-bottom: 105px !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 12px;
|
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,7 +35,7 @@
|
|||||||
.menuItemsGrid {
|
.menuItemsGrid {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 1rem;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Enhanced responsive menu section headers */
|
/* Enhanced responsive menu section headers */
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function MenuList({ data, categoryRefs }: MenuListProps) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.menuSections}>
|
<div className={styles.menuSections}>
|
||||||
{data?.categories?.map((category) => {
|
{data?.categories?.map((category, index) => {
|
||||||
const categoryProducts = productsByCategory?.[category.id] || [];
|
const categoryProducts = productsByCategory?.[category.id] || [];
|
||||||
if (categoryProducts.length === 0) return null;
|
if (categoryProducts.length === 0) return null;
|
||||||
|
|
||||||
@@ -66,8 +66,9 @@ export function MenuList({ data, categoryRefs }: MenuListProps) {
|
|||||||
categoryRefs.current[category.id] = el;
|
categoryRefs.current[category.id] = el;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
style={{ marginBottom: "1rem" }}
|
style={{ marginBottom: "2rem" }}
|
||||||
>
|
>
|
||||||
|
{index !== 0 && (
|
||||||
<ProTitle
|
<ProTitle
|
||||||
style={{
|
style={{
|
||||||
fontWeight: 600,
|
fontWeight: 600,
|
||||||
@@ -76,11 +77,13 @@ export function MenuList({ data, categoryRefs }: MenuListProps) {
|
|||||||
lineHeight: "140%",
|
lineHeight: "140%",
|
||||||
letterSpacing: "0%",
|
letterSpacing: "0%",
|
||||||
color: themeName === "dark" ? "#fff" : "#070707",
|
color: themeName === "dark" ? "#fff" : "#070707",
|
||||||
|
marginBottom: 16,
|
||||||
}}
|
}}
|
||||||
level={5}
|
level={5}
|
||||||
>
|
>
|
||||||
{isRTL ? category.name : category.name}
|
{isRTL ? category.name : category.name}
|
||||||
</ProTitle>
|
</ProTitle>
|
||||||
|
)}
|
||||||
<div className={styles.menuItemsGrid}>
|
<div className={styles.menuItemsGrid}>
|
||||||
{categoryProducts.map((item: Product) => (
|
{categoryProducts.map((item: Product) => (
|
||||||
<ProductCard
|
<ProductCard
|
||||||
|
|||||||
@@ -188,7 +188,7 @@ export default function ProductCard({ item, setIsBottomSheetOpen }: Props) {
|
|||||||
count={items
|
count={items
|
||||||
.filter((i) => i.id === item.id)
|
.filter((i) => i.id === item.id)
|
||||||
.reduce((total, item) => total + item.quantity, 0)}
|
.reduce((total, item) => total + item.quantity, 0)}
|
||||||
color={colors.primary}
|
color="#FFC600"
|
||||||
title={`${items
|
title={`${items
|
||||||
.filter((i) => i.id === item.id)
|
.filter((i) => i.id === item.id)
|
||||||
.reduce((total, item) => total + item.quantity, 0)} in cart`}
|
.reduce((total, item) => total + item.quantity, 0)} in cart`}
|
||||||
|
|||||||
@@ -4,8 +4,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.itemDescription {
|
.itemDescription {
|
||||||
font-size: 14px !important;
|
font-family: Roboto;
|
||||||
transition: color 0.3s ease;
|
font-weight: 400;
|
||||||
|
font-style: Regular;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 120%;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
color: #707070;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cover {
|
.cover {
|
||||||
@@ -533,9 +538,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.restaurantTitle {
|
.restaurantTitle {
|
||||||
font-size: 24px !important;
|
font-family: Roboto;
|
||||||
font-weight: bold;
|
font-weight: 600;
|
||||||
margin-bottom: 0px !important;
|
font-style: SemiBold;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 120%;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
color: #070707;
|
||||||
}
|
}
|
||||||
|
|
||||||
.restaurantDescription {
|
.restaurantDescription {
|
||||||
@@ -558,16 +567,26 @@
|
|||||||
|
|
||||||
.ratingScore {
|
.ratingScore {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 3px;
|
top:6px;
|
||||||
font-size: 12px;
|
font-family: Roboto;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
font-style: SemiBold;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 100%;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
color: #333333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ratingCount {
|
.ratingCount {
|
||||||
position: relative;
|
position: relative;
|
||||||
top: 3px;
|
top: 6px;
|
||||||
font-size: 12px;
|
font-family: Roboto;
|
||||||
color: #666;
|
font-weight: 400;
|
||||||
|
font-style: Regular;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 100%;
|
||||||
|
letter-spacing: 0%;
|
||||||
|
color: #506fd7;
|
||||||
}
|
}
|
||||||
|
|
||||||
.searchButtonContainer {
|
.searchButtonContainer {
|
||||||
|
|||||||
@@ -119,9 +119,9 @@ function MenuPage() {
|
|||||||
<div className={`${styles.headerContainer}`}>
|
<div className={`${styles.headerContainer}`}>
|
||||||
<div className={styles.contentWrapper}>
|
<div className={styles.contentWrapper}>
|
||||||
<div className={styles.restaurantHeaderTitleContainer}>
|
<div className={styles.restaurantHeaderTitleContainer}>
|
||||||
<ProTitle className={styles.restaurantTitle}>
|
<ProText className={styles.restaurantTitle}>
|
||||||
{isRTL ? restaurant?.nameAR : restaurant?.restautantName}
|
{isRTL ? restaurant?.nameAR : restaurant?.restautantName}
|
||||||
</ProTitle>
|
</ProText>
|
||||||
<Button
|
<Button
|
||||||
className={
|
className={
|
||||||
restaurant?.isOpened
|
restaurant?.isOpened
|
||||||
@@ -151,7 +151,7 @@ function MenuPage() {
|
|||||||
<div className={styles.ratingContainer}>
|
<div className={styles.ratingContainer}>
|
||||||
<StarFilled className={styles.ratingStar} />
|
<StarFilled className={styles.ratingStar} />
|
||||||
<ProText className={styles.ratingScore}>4.5</ProText>
|
<ProText className={styles.ratingScore}>4.5</ProText>
|
||||||
<ProText className={styles.ratingCount}>(2567)</ProText>
|
<ProText className={styles.ratingCount}>(2567) </ProText>
|
||||||
<ProText
|
<ProText
|
||||||
className={`${styles.itemDescription} ${styles.restaurantDescription} responsive-text`}
|
className={`${styles.itemDescription} ${styles.restaurantDescription} responsive-text`}
|
||||||
>
|
>
|
||||||
@@ -166,6 +166,35 @@ function MenuPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
style={{
|
||||||
|
height: 32,
|
||||||
|
borderRadius: 6,
|
||||||
|
paddingTop: 6,
|
||||||
|
paddingRight: 16,
|
||||||
|
paddingBottom: 6,
|
||||||
|
paddingLeft: 16,
|
||||||
|
gap: 8,
|
||||||
|
opacity: 1,
|
||||||
|
margin: 16,
|
||||||
|
backgroundColor: "#EBEBEC",
|
||||||
|
border: "none",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ProText
|
||||||
|
style={{
|
||||||
|
fontWeight: 500,
|
||||||
|
fontStyle: "Medium",
|
||||||
|
fontSize: 14,
|
||||||
|
lineHeight: "140%",
|
||||||
|
letterSpacing: "0%",
|
||||||
|
color: "#09237D",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Call Waiter
|
||||||
|
</ProText>
|
||||||
|
</Button>
|
||||||
|
|
||||||
<div className={`${styles.pageContainer}`}>
|
<div className={`${styles.pageContainer}`}>
|
||||||
<Space orientation="vertical" style={{ width: "100%" }}>
|
<Space orientation="vertical" style={{ width: "100%" }}>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export function CustomAmountChoiceBottomSheet({
|
|||||||
<ProBottomSheet
|
<ProBottomSheet
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={t("splitBill.payAsCustomAmount")}
|
title={t("splitBill.customAmount")}
|
||||||
showCloseButton={true}
|
showCloseButton={true}
|
||||||
initialSnap={1}
|
initialSnap={1}
|
||||||
height={425}
|
height={425}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ export function EqualltyChoiceBottomSheet({
|
|||||||
<ProBottomSheet
|
<ProBottomSheet
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={t("splitBill.divideTheBillEqually")}
|
title={t("splitBill.divideEqually")}
|
||||||
showCloseButton={true}
|
showCloseButton={true}
|
||||||
initialSnap={1}
|
initialSnap={1}
|
||||||
height={630}
|
height={630}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ export function PayForYourItemsChoiceBottomSheet({
|
|||||||
<ProBottomSheet
|
<ProBottomSheet
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={t("splitBill.payForYourItems")}
|
title={t("splitBill.payForItems")}
|
||||||
showCloseButton={true}
|
showCloseButton={true}
|
||||||
initialSnap={1}
|
initialSnap={1}
|
||||||
height={720}
|
height={720}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { ScheduleFilled } from "@ant-design/icons";
|
|
||||||
import { Card } from "antd";
|
import { Card } from "antd";
|
||||||
import BackIcon from "components/Icons/BackIcon";
|
import BackIcon from "components/Icons/BackIcon";
|
||||||
import DeliveryIcon from "components/Icons/DeliveryIcon";
|
import DeliveryIcon from "components/Icons/DeliveryIcon";
|
||||||
@@ -8,24 +7,29 @@ import PickupIcon from "components/Icons/PickupIcon";
|
|||||||
import SendGiftIcon from "components/Icons/SendGiftIcon";
|
import SendGiftIcon from "components/Icons/SendGiftIcon";
|
||||||
import ToOfficeIcon from "components/Icons/ToOfficeIcon";
|
import ToOfficeIcon from "components/Icons/ToOfficeIcon";
|
||||||
import ToRoomIcon from "components/Icons/ToRoomIcon";
|
import ToRoomIcon from "components/Icons/ToRoomIcon";
|
||||||
import ProTitle from "components/ProTitle";
|
|
||||||
import { updateOrderType } from "features/order/orderSlice";
|
import { updateOrderType } from "features/order/orderSlice";
|
||||||
import { OrderType } from "pages/checkout/hooks/types.ts";
|
import { OrderType } from "pages/checkout/hooks/types.ts";
|
||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import { Link, useParams } from "react-router-dom";
|
import { Link, useNavigate, useParams } from "react-router-dom";
|
||||||
import { useGetRestaurantDetailsQuery } from "redux/api/others";
|
import { useGetRestaurantDetailsQuery } from "redux/api/others";
|
||||||
import { useAppDispatch, useAppSelector } from "redux/hooks";
|
import { useAppDispatch, useAppSelector } from "redux/hooks";
|
||||||
import styles from "./restaurant.module.css";
|
import styles from "./restaurant.module.css";
|
||||||
import ScheduleOrderIcon from "components/Icons/ScheduleOrderIcon";
|
import ScheduleOrderIcon from "components/Icons/ScheduleOrderIcon";
|
||||||
|
import ProText from "components/ProText";
|
||||||
|
import { GiftTypeBottomSheet } from "components/CustomBottomSheet/GiftTypeBottomSheet";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function RestaurantServices() {
|
export default function RestaurantServices() {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { isRTL } = useAppSelector((state) => state.locale);
|
const { isRTL } = useAppSelector((state) => state.locale);
|
||||||
const { subdomain } = useParams();
|
const { subdomain } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain, {
|
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain, {
|
||||||
skip: !subdomain,
|
skip: !subdomain,
|
||||||
});
|
});
|
||||||
|
const [isGiftTypeBottomSheetOpen, setIsGiftTypeBottomSheetOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
dineIn,
|
dineIn,
|
||||||
@@ -188,12 +192,19 @@ export default function RestaurantServices() {
|
|||||||
return (
|
return (
|
||||||
<div className={getGridClass()}>
|
<div className={getGridClass()}>
|
||||||
{services?.map((s) => {
|
{services?.map((s) => {
|
||||||
|
const isGift = s?.id === OrderType.Gift;
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={s?.href}
|
to={isGift ? "#" : s?.href}
|
||||||
key={s?.id}
|
key={s?.id}
|
||||||
onClick={() => {
|
onClick={(e) => {
|
||||||
|
if (isGift) {
|
||||||
|
e.preventDefault();
|
||||||
dispatch(updateOrderType(s?.id));
|
dispatch(updateOrderType(s?.id));
|
||||||
|
setIsGiftTypeBottomSheetOpen(true);
|
||||||
|
} else {
|
||||||
|
dispatch(updateOrderType(s?.id));
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
@@ -218,18 +229,20 @@ export default function RestaurantServices() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{s?.icon}
|
{s?.icon}
|
||||||
<ProTitle
|
<ProText
|
||||||
level={5}
|
|
||||||
style={{
|
style={{
|
||||||
fontWeight: 500,
|
fontWeight: 500,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
lineHeight: "140%",
|
lineHeight: "140%",
|
||||||
letterSpacing: "0%",
|
letterSpacing: "0%",
|
||||||
verticalAlign: "middle",
|
verticalAlign: "middle",
|
||||||
|
color: "#434E5C",
|
||||||
|
position: "relative",
|
||||||
|
top: -1
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{s?.title}
|
{s?.title}
|
||||||
</ProTitle>
|
</ProText>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{isRTL ? (
|
{isRTL ? (
|
||||||
@@ -242,6 +255,14 @@ export default function RestaurantServices() {
|
|||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
<GiftTypeBottomSheet
|
||||||
|
isOpen={isGiftTypeBottomSheetOpen}
|
||||||
|
onClose={() => setIsGiftTypeBottomSheetOpen(false)}
|
||||||
|
onSave={() => {
|
||||||
|
setIsGiftTypeBottomSheetOpen(false);
|
||||||
|
navigate(`/${subdomain}/menu?orderType=${OrderType.Gift}`);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -143,9 +143,9 @@
|
|||||||
@media (max-width: 769px) {
|
@media (max-width: 769px) {
|
||||||
.servicesGrid {
|
.servicesGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 10px;
|
gap: 16px;
|
||||||
padding: 0 1rem;
|
padding: 0 1rem;
|
||||||
margin: 10px 0;
|
margin: 24px 0 10px 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +260,7 @@
|
|||||||
.servicesGrid {
|
.servicesGrid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: 20px;
|
gap: 24px;
|
||||||
padding: 0 32px;
|
padding: 0 32px;
|
||||||
margin: 32px 0;
|
margin: 32px 0;
|
||||||
max-width: 700px;
|
max-width: 700px;
|
||||||
@@ -603,7 +603,7 @@
|
|||||||
|
|
||||||
.servicesGrid {
|
.servicesGrid {
|
||||||
max-width: 1000px;
|
max-width: 1000px;
|
||||||
gap: 20px;
|
gap: 24px;
|
||||||
padding: 0 32px;
|
padding: 0 32px;
|
||||||
margin: 32px 0;
|
margin: 32px 0;
|
||||||
}
|
}
|
||||||
@@ -975,6 +975,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.scheduledOrderIcon {
|
.scheduledOrderIcon {
|
||||||
margin-top: -2px;
|
position: relative;
|
||||||
|
top: 1px;
|
||||||
font-size: 22px;
|
font-size: 22px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
RESTAURANT_DETAILS_URL,
|
RESTAURANT_DETAILS_URL,
|
||||||
TABLES_URL,
|
TABLES_URL,
|
||||||
USER_DETAILS_URL,
|
USER_DETAILS_URL,
|
||||||
|
EGIFT_CARDS_URL,
|
||||||
} from "utils/constants";
|
} from "utils/constants";
|
||||||
|
|
||||||
import { OrderDetails } from "pages/checkout/hooks/types";
|
import { OrderDetails } from "pages/checkout/hooks/types";
|
||||||
@@ -19,6 +20,7 @@ import {
|
|||||||
UserType,
|
UserType,
|
||||||
} from "utils/types/appTypes";
|
} from "utils/types/appTypes";
|
||||||
import { baseApi } from "./apiSlice";
|
import { baseApi } from "./apiSlice";
|
||||||
|
import { EGiftCard } from "pages/EGiftCards/type";
|
||||||
|
|
||||||
export const branchApi = baseApi.injectEndpoints({
|
export const branchApi = baseApi.injectEndpoints({
|
||||||
endpoints: (builder) => ({
|
endpoints: (builder) => ({
|
||||||
@@ -31,7 +33,7 @@ export const branchApi = baseApi.injectEndpoints({
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
transformResponse: (response: any) => {
|
transformResponse: (response: any) => {
|
||||||
return response?.result?.restaurants?.[0]
|
return response?.result?.restaurants?.[0];
|
||||||
},
|
},
|
||||||
providesTags: ["Restaurant"],
|
providesTags: ["Restaurant"],
|
||||||
}),
|
}),
|
||||||
@@ -160,6 +162,15 @@ export const branchApi = baseApi.injectEndpoints({
|
|||||||
}),
|
}),
|
||||||
invalidatesTags: ["Orders", "Restaurant"],
|
invalidatesTags: ["Orders", "Restaurant"],
|
||||||
}),
|
}),
|
||||||
|
getEGiftCards: builder.query<EGiftCard[], void>({
|
||||||
|
query: () => ({
|
||||||
|
url: EGIFT_CARDS_URL,
|
||||||
|
method: "GET",
|
||||||
|
}),
|
||||||
|
transformResponse: (response: any) => {
|
||||||
|
return response.result;
|
||||||
|
},
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
export const {
|
export const {
|
||||||
@@ -173,4 +184,5 @@ export const {
|
|||||||
useGetDiscountMutation,
|
useGetDiscountMutation,
|
||||||
useRateOrderMutation,
|
useRateOrderMutation,
|
||||||
useGetUserDetailsQuery,
|
useGetUserDetailsQuery,
|
||||||
|
useGetEGiftCardsQuery,
|
||||||
} = branchApi;
|
} = branchApi;
|
||||||
|
|||||||
@@ -108,3 +108,4 @@ export const SEND_OTP_URL = `${API_BASE_URL}sendOtp`;
|
|||||||
export const CONFIRM_OTP_URL = `${API_BASE_URL}confirmOtp`;
|
export const CONFIRM_OTP_URL = `${API_BASE_URL}confirmOtp`;
|
||||||
export const PAYMENT_CONFIRMATION_URL = `https://menu.fascano.com/payment/confirmation`;
|
export const PAYMENT_CONFIRMATION_URL = `https://menu.fascano.com/payment/confirmation`;
|
||||||
export const DISCOUNT_URL = `${BASE_URL}getDiscount`;
|
export const DISCOUNT_URL = `${BASE_URL}getDiscount`;
|
||||||
|
export const EGIFT_CARDS_URL = `${BASE_URL}gift/cards`;
|
||||||
Reference in New Issue
Block a user