Compare commits

...

5 Commits

Author SHA1 Message Date
5d523e2508 checkout: UI + styles enhacnements 2025-12-30 23:50:42 +03:00
3ed5c4d5d6 gift: update UI and voucher & items BS 2025-12-30 11:09:52 +03:00
f3b8bdba63 add read me file 2025-12-30 09:34:37 +03:00
e8c4bc909c general updates 2025-12-30 03:35:14 +03:00
dd11665743 menu: enhnace styles 2025-12-30 01:39:58 +03:00
48 changed files with 1511 additions and 189 deletions

596
README.md
View File

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

View File

@@ -2,7 +2,7 @@
import type { ThemeConfig } from "antd";
export const colors = {
primary: "#CC9300",
primary: "#FFB700",
secondary: "linear-gradient(135deg, #00C1D4 0%, #FF5F6D 100%)",
darkSpace: "linear-gradient(to right, #0F0525, #2A0B45)",
};
@@ -108,6 +108,7 @@ export const SharedThemeVars = {
colorPrimary: colors.primary,
fontFamily: "var(--font-roboto)",
colorLink: colors.primary,
lineHeight: "140%"
// colorItemTextSelected: colors.primary // Menu Item Active Item Color
};

View File

@@ -425,12 +425,10 @@
"description": "تقسيم الفاتورة",
"splitBill": "تقسيم الفاتورة",
"splitBillDescription": "تقسيم الفاتورة",
"splitBillButton": "تقسيم الفاتورة",
"customAmount": "مبلغ مخصص",
"divideEqually": "تقسيم الى حسب العدد",
"payForItems": "دفع للطلبات الخاصة بي",
"splitBillButtonDescription": "تقسيم الفاتورة",
"payAsCustomAmount": "دفع كمبلغ مخصص",
"payAsSplitAmount": "دفع كمبلغ مقسم",
"divideTheBillEqually": "تقسيم الفاتورة الى حسب العدد",
"payForYourItems": "دفع للطلبات الخاصة بي",
"enterCustomAmount": "أدخل المبلغ المخصص",
"amountPlaceholder": "0.00",
"divisionPreview": "معاينة التقسيم",
@@ -453,5 +451,11 @@
"shareLink": "شارك الرابط",
"shareLinkDescription": "يمكن للأصدقاء مسح الكود الباري أو استخدام الرابط لعرض الفاتورة ودفع نصيبهم بأمان من هاتفهم.",
"shareThisToSplitTheBill": "شارك هذا لتقسيم الفاتورة"
},
"gift": {
"items": "هدية العناصر",
"balance": "هدية الرصيد",
"itemsAndBalance": "العناصر والرصيد",
"selectGiftType": "اختر نوع الهدية"
}
}

View File

@@ -437,12 +437,10 @@
"description": "Split Bill",
"splitBill": "Split Bill",
"splitBillDescription": "Split Bill",
"splitBillButton": "Split Bill",
"customAmount": "Custom Amount",
"divideEqually": "Divide Equally",
"payForItems": "Pay for Items",
"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",
"amountPlaceholder": "0.00",
"divisionPreview": "Division Preview",
@@ -465,5 +463,11 @@
"shareLink": "Share Link",
"shareLinkDescription": "Your friends can scan the QR code or use the link to view the bill and pay their share securely from their phone.",
"shareThisToSplitTheBill": "Share this to split the bill"
},
"gift": {
"items": "Gift Items",
"balance": "Gift Balance",
"itemsAndBalance": "Items and Balance",
"selectGiftType": "Select Gift Type"
}
}

View 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>
);
}

View File

@@ -7,23 +7,26 @@ const EditIcon = ({ className, onClick }: EditIconType) => {
return (
<svg
width="16"
height="17"
viewBox="0 0 16 17"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<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"
fill="var(--primary)"
/>
<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"
strokeLinejoin="round"
/>
<g clipPath="url(#clip0_1571_6925)">
<path
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"
stroke="#B58D00"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<defs>
<clipPath id="clip0_1571_6925">
<rect width="16" height="16" fill="white" />
</clipPath>
</defs>
</svg>
);
};

View File

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

View File

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

View File

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

View File

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

View File

@@ -6,16 +6,16 @@ interface CouponHeartType {
const CouponHeartIcon = ({ className, onClick }: CouponHeartType) => {
return (
<svg
width="15"
height="14"
viewBox="0 0 15 14"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
onClick={onClick}
>
<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"
strokeWidth="1.5"
strokeLinecap="round"

View File

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

View File

@@ -1,5 +1,4 @@
import { Card, Col, Image, Row } from "antd";
import LoyaltyIcon from "components/Icons/cart/LoyaltyIcons";
import PresentIcon from "components/Icons/cart/PresentIcon";
import { useTranslation } from "react-i18next";
import { Link, useParams } from "react-router-dom";
@@ -8,11 +7,13 @@ import { ACCESS_TOKEN } from "utils/constants";
import { colors } from "ThemeConstants.ts";
import ProText from "../ProText";
import styles from "./LoyaltyCard.module.css";
import { useAppSelector } from "redux/hooks";
const LoyaltyCard = () => {
const { t } = useTranslation();
const { subdomain } = useParams();
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain);
const { isRTL } = useAppSelector((state) => state.locale);
const token = localStorage.getItem(ACCESS_TOKEN);
const isHasLoyaltyGift =
(restaurant?.loyalty_stamps || 0) -
@@ -29,11 +30,20 @@ const LoyaltyCard = () => {
>
<Col>
<Row align="middle" gutter={[8, 8]}>
<Col>
{/* <Col>
<LoyaltyIcon className={styles.loyaltyIcon} />
</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")}
</ProText>
</Col>
@@ -42,10 +52,13 @@ const LoyaltyCard = () => {
type="secondary"
strong
style={{
fontSize: "14px",
fontWeight: 700,
position: "relative",
fontWeight: 400,
fontStyle: "Regular",
fontSize: "12px",
lineHeight: "140%",
letterSpacing: "0%",
top: -2,
color: "#777580",
}}
>
{token &&
@@ -61,12 +74,21 @@ const LoyaltyCard = () => {
textDecoration: "underline",
fontSize: "14px",
fontWeight: 400,
padding: "0 4px",
padding: isRTL ? "0 0 0 4px" : "0 4px 0 0",
}}
>
{t("menu.joinUs")}
</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")}
</span>
</div>
@@ -80,7 +102,7 @@ const LoyaltyCard = () => {
style={{
display: "flex",
flexDirection: "row",
gap: 10,
gap: 12,
overflow: "hidden",
scrollbarWidth: "none",
}}

View File

@@ -51,9 +51,20 @@ export default function OrderSummary() {
return (
<>
<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" }} />
<Space orientation="vertical" style={{ width: "100%" }}>
<Space orientation="vertical" style={{ width: "100%", gap: 16 }}>
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.basketTotal")}
@@ -67,7 +78,7 @@ export default function OrderSummary() {
</ProText>
<ArabicPrice
price={Number(restaurant?.delivery_fees || 0)}
style={titlesStyle}
style={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
)}
@@ -75,23 +86,32 @@ export default function OrderSummary() {
<ProText type="secondary" style={titlesStyle}>
{t("cart.discount")}
</ProText>
<ArabicPrice price={discountAmount} style={titlesStyle} />
<ArabicPrice
price={discountAmount}
style={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("cart.tax")}
</ProText>
<ArabicPrice price={taxAmount || 0} style={titlesStyle} />
<ArabicPrice
price={taxAmount || 0}
style={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
{splitBillAmount > 0 && (
<div className={styles.summaryRow}>
<ProText type="secondary" style={titlesStyle}>
{t("splitBill.splitBillAmount")}
</ProText>
<ArabicPrice price={splitBillAmount} style={titlesStyle} />
<ArabicPrice
price={splitBillAmount}
style={{ ...titlesStyle, color: "#434E5C" }}
/>
</div>
)}
<Divider className={styles.summaryDivider} />
<Divider className={styles.summaryDivider} style={{ margin: "0" }} />
<div className={`${styles.summaryRow} ${styles.totalRow}`}>
<ProText
style={{
@@ -101,6 +121,7 @@ export default function OrderSummary() {
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#333333",
}}
>
{t("cart.totalAmount")}

View File

@@ -27,4 +27,78 @@
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;
}

View File

@@ -14,15 +14,17 @@ import { colors, ProGray1 } from "../../ThemeConstants";
import ProInputCard from "../ProInputCard/ProInputCard";
import styles from "./PaymentMethods.module.css";
import { OrderType } from "pages/checkout/hooks/types.ts";
import RCardIcon from "components/Icons/RCardIcon";
const PaymentMethods = () => {
const { t } = useTranslation();
const { paymentMethod, orderType } = useAppSelector(selectCart);
const dispatch = useAppDispatch();
const grandTotal = useAppSelector(selectGrandTotal);
const { isRTL } = useAppSelector((state) => state.locale);
const options: {
label: string;
label: React.ReactNode;
value: string;
price?: string;
icon?: React.ReactNode;
@@ -32,23 +34,40 @@ const PaymentMethods = () => {
...(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",
price: grandTotal.toString(),
price: grandTotal.toFixed(2),
style: {
color: colors.primary,
},
},
]
: []),
// {
// label: t("checkout.creditDebitCard"),
// value: "creditDebitCard",
// price: t("checkout.expiresIn") + ":12/26",
// hideCurrency: true,
// },
// {
// label: t("checkout.creditDebitCard"),
// value: "creditDebitCard",
// price: t("checkout.expiresIn") + ":12/26",
// hideCurrency: true,
// },
{
label: t("checkout.differentCard"),
label: (
<>
<RCardIcon className={styles.eCardIcon} />
<ProText style={{color: '#5F6C7B'}}>{t("checkout.differentCard")}</ProText>
</>
),
value: "differentCard",
icon: (
<div className={styles.differentCardIcon}>
@@ -56,7 +75,7 @@ const PaymentMethods = () => {
</div>
),
hideCurrency: true,
}
},
// {
// label: t("checkout.fascanoWallet"),
// value: "fascanoWallet",
@@ -95,6 +114,7 @@ const PaymentMethods = () => {
key={option.value}
value={option.value}
onClick={() => onPaymentSelect(option.value)}
className={styles.circleCheckbox}
style={{
height: 48,
borderRadius: 888,

View File

@@ -77,7 +77,20 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
}}
{...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>
</div>
@@ -96,7 +109,20 @@ const ProHeader: FunctionComponent<ProHeaderProps> = ({
}}
{...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>
</div>
</>

View File

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

View File

@@ -13,6 +13,7 @@ interface ProPhoneInput {
getValueCallback?: (value: string) => void;
value?: string;
onChange?: (value: string) => void;
hiddenLabel?: boolean;
}
const ProPhoneInput: FunctionComponent<ProPhoneInput> = ({
@@ -21,6 +22,7 @@ const ProPhoneInput: FunctionComponent<ProPhoneInput> = ({
getValueCallback,
value,
onChange,
hiddenLabel = true,
}) => {
const form = useFormInstance();
const { t } = useTranslation();
@@ -29,7 +31,7 @@ const ProPhoneInput: FunctionComponent<ProPhoneInput> = ({
return (
<Form.Item
name={propName}
label={label}
label={hiddenLabel ? undefined : label}
rules={[
{ required: true, message: t("validation.phoneRequired") },
{
@@ -60,7 +62,7 @@ const ProPhoneInput: FunctionComponent<ProPhoneInput> = ({
themeName={themeName}
placeholder={t("login.mobileNumber")}
propName={propName}
label={label}
label={hiddenLabel ? undefined : label}
/>
</Form.Item>
);

View File

@@ -33,6 +33,7 @@ export interface GiftDetailsType {
senderPhone: string;
senderEmail: string;
isSecret: boolean;
cardId: string;
}
interface DiscountData {
@@ -75,7 +76,9 @@ interface CartState {
hiddenServices: number;
visibleServices: number;
fee: number;
}
giftType: string;
}
// localStorage keys
export const CART_STORAGE_KEYS = {
@@ -108,6 +111,7 @@ export const CART_STORAGE_KEYS = {
TOTAL_SERVICES: "fascano_total_services",
HIDDEN_SERVICES: "fascano_hidden_services",
VISIBLE_SERVICES: "fascano_visible_services",
GIFT_TYPE: "fascano_gift_type",
} as const;
// Utility functions for localStorage
@@ -200,6 +204,7 @@ const initialState: CartState = {
hiddenServices: 0,
visibleServices: 0,
fee: 0,
giftType: getFromLocalStorage(CART_STORAGE_KEYS.GIFT_TYPE, ""),
};
const orderSlice = createSlice({
@@ -681,6 +686,9 @@ const orderSlice = createSlice({
updateCustomerName(state, action: PayloadAction<string>) {
state.customerName = action.payload;
},
updateGiftType(state, action: PayloadAction<string>) {
state.giftType = action.payload;
},
},
});
@@ -720,6 +728,7 @@ export const {
updateOrder,
updateSplitBillAmount,
updateCustomerName,
updateGiftType,
} = orderSlice.actions;
// Tax calculation helper functions

View 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>
);
}

View 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>
</>
);
}

View 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>
))}
</>
);
}

View 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;
}

View File

@@ -184,6 +184,7 @@
width: 100%;
font-size: 16px;
box-shadow: none;
border-color: #C0BFC4;
}
.placeOrderButton {

View File

@@ -67,11 +67,6 @@
top: 5px !important;
}
.couponApplyIcon {
margin-left: 8px;
font-size: 14px;
}
.donateHandIcon {
color: var(--primary);
font-size: 24px;
@@ -434,11 +429,6 @@
font-size: 14px !important;
}
.couponApplyIcon {
margin-left: 10px;
font-size: 16px;
}
.donateHandIcon {
font-size: 28px;
}
@@ -481,11 +471,6 @@
font-size: 16px !important;
}
.couponApplyIcon {
margin-left: 12px;
font-size: 18px;
}
.donateHandIcon {
font-size: 32px;
}
@@ -607,6 +592,17 @@
}
.deleteIcon {
position: relative;
top: 1px;
position: relative;
top: 1px;
}
.couponApplyIcon {
position: relative;
top: 1px;
}
.editIcon {
position: absolute;
top: -7px;
right: 5px;
}

View File

@@ -1,6 +1,7 @@
import { Button, Form, Input, message } from "antd";
import CouponHeartIcon from "components/Icons/cart/CouponHeart.tsx";
import ProInputCard from "components/ProInputCard/ProInputCard.tsx";
import ProText from "components/ProText";
import {
selectCart,
updateCoupon,
@@ -72,13 +73,8 @@ export default function CouponCard() {
// <DonateIcon />
// </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
placeholder={t("cart.couponCode")}
size="large"
@@ -93,13 +89,23 @@ export default function CouponCard() {
width: 100,
height: 32,
borderRadius: 100,
backgroundColor: "black",
backgroundColor: "#333333",
color: "white",
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%"
}}
onClick={() => handleCouponSave(coupon)}
icon={<CouponHeartIcon className={styles.couponApplyIcon} />}
iconPlacement="end"
>
{t("cart.apply")}
<CouponHeartIcon className={styles.couponApplyIcon} />
<ProText
style={{ position: "relative", top: -1, color: "white" }}
>
{t("cart.apply")}
</ProText>
</Button>
}
/>

View File

@@ -12,7 +12,6 @@ import styles from "pages/cart/cart.module.css";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useAppDispatch, useAppSelector } from "redux/hooks.ts";
import { colors } from "ThemeConstants.ts";
export default function RewardWaiterCard() {
const { t } = useTranslation();
@@ -54,10 +53,32 @@ export default function RewardWaiterCard() {
<DonateHandIcon className={styles.donateHandIcon} />
</div>
<div style={{ marginTop: 7 }}>
<ProTitle style={{ fontSize: 18, margin: 0 }}>
{t("cart.rewardYourWaiter")}
</ProTitle>
<ProText style={{ color: "rgba(95, 108, 123, 1)", fontSize: 12 }}>
<div>
<ProText
style={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 16,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#333333",
}}
>
{t("cart.rewardYourWaiter")}
</ProText>
</div>
<ProText
style={{
fontWeight: 400,
fontStyle: "Regular",
fontSize: 12,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#5F6C7B",
}}
>
{t("cart.rewardYourWaiter100")}
</ProText>
</div>
@@ -74,41 +95,66 @@ export default function RewardWaiterCard() {
style={{
borderRadius: 100,
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",
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
style={{
borderRadius: 100,
height: 32,
color: colors.primary,
borderColor: "#d9d9d9",
border: "none",
backgroundColor: "#5F6C7B0D",
}}
onClick={() => setIsTipOpen(true)}
>
<EditIcon />
<ProText
style={{
color: colors.primary,
<ArabicPrice
price={1.0}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#5F6C7B",
}}
>
{t("cart.other")}
</ProText>
/>
</Button>
<Button
style={{
borderRadius: 100,
height: 32,
border: "none",
backgroundColor: "#FFB7001F",
}}
icon={<EditIcon className={styles.editIcon} />}
iconPlacement="end"
>
<ArabicPrice
price={3.0}
textStyle={{
fontWeight: 500,
fontStyle: "Medium",
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
textAlign: "center",
color: "#CC9300",
}}
/>
</Button>
</div>
</Card>

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { Link, useNavigate, useParams } from "react-router-dom";
import { useAppSelector } from "redux/hooks.ts";
import styles from "./footer.module.css";
import { OrderType } from "pages/checkout/hooks/types";
interface CartFooterProps {
form: FormInstance;
@@ -63,7 +64,9 @@ export default function CartFooter({ form }: CartFooterProps) {
}}
onClick={handleCheckoutClick}
>
{t("cart.checkout")}
{orderType === OrderType.Gift
? t("cart.continueToGiftDetails")
: t("cart.checkout")}
</Button>
</Layout.Footer>
);

View File

@@ -58,6 +58,17 @@ export default function CheckoutButton({ form }: { form: FormInstance }) {
[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 (
<>
<Layout.Footer className={styles.checkoutButtonContainer}>
@@ -66,9 +77,7 @@ export default function CheckoutButton({ form }: { form: FormInstance }) {
className={styles.splitBillButton}
onClick={handleSplitBillClick}
>
{selectedSplitWay
? t("checkout.removeSplitBill")
: t("checkout.splitBill")}
{getSplitButtonTitle}
</Button>
)}

View File

@@ -1,3 +1,5 @@
.customerInformationCard {
height: 215px !important;
}
display: flex;
flex-direction: column;
gap: 16px;
}

View File

@@ -16,12 +16,9 @@ export default function CustomerInformationCard() {
return (
orderType !== OrderType.Gift && (
<>
<ProInputCard
title={t("checkout.customerInformation")}
className={styles.customerInformationCard}
>
<div style={{ position: "relative", top: -25 }}>
<Form.Item label={t("checkout.customerName")} name="customerName">
<ProInputCard title={t("checkout.customerInformation")}>
<div className={styles.customerInformationCard}>
<Form.Item name="customerName">
<Input
placeholder={t("checkout.customerName")}
size="large"
@@ -33,9 +30,7 @@ export default function CustomerInformationCard() {
}}
/>
</Form.Item>
</div>
<div style={{ position: "relative", top: -35 }}>
<ProPhoneInput label={t("login.phone")} propName="phone" />
<ProPhoneInput propName="phone" />
</div>
</ProInputCard>
</>

View File

@@ -3,7 +3,6 @@ import { Button, message } from "antd";
import { useTranslation } from "react-i18next";
import { useNavigate, useParams } from "react-router-dom";
import { useGetRestaurantDetailsQuery } from "redux/api/others";
import { colors } from "ThemeConstants.ts";
import styles from "./AddToCartButton.module.css";
import { useAppSelector, useAppDispatch } from "redux/hooks";
import { Product } from "utils/types/appTypes";
@@ -215,7 +214,7 @@ export function AddToCartButton({ item }: { item: Product }) {
onClick={handlePlusClick}
className={styles.addButton}
style={{
backgroundColor: colors.primary,
backgroundColor: "#FFC600",
width: 28,
height: 28,
position: "absolute",

View File

@@ -118,9 +118,9 @@
will-change: position, transform, opacity, filter;
transform-origin: top center;
gap: 14px;
padding: 0.5rem 1rem 1rem 1rem;
padding: 0.5rem 24px 1rem 24px;
user-select: none;
margin-bottom: 0.5rem;
margin-bottom: 16px;
}
.categoriesContainer::-webkit-scrollbar {

View File

@@ -291,8 +291,8 @@ export function CategoriesList({ categories }: CategoriesListProps) {
color:
activeCategory === category.id
? colors.primary
: undefined,
? "#FFC600"
: "#86858E",
fontWeight:
activeCategory === category.id ? "600" : "500",
whiteSpace: "nowrap",
@@ -309,7 +309,7 @@ export function CategoriesList({ categories }: CategoriesListProps) {
display: "inline-block",
borderBottom:
activeCategory === category.id && !isCategoriesSticky
? `2px solid ${colors.primary}`
? `2px solid #FFC600`
: "none",
paddingBottom: activeCategory === category.id ? 8 : 0,
marginTop: isCategoriesSticky ? 0 : 4,

View File

@@ -20,7 +20,6 @@
margin-bottom: 105px !important;
display: flex;
flex-direction: column;
gap: 12px;
padding: 0 1rem;
}
@@ -36,7 +35,7 @@
.menuItemsGrid {
display: flex;
flex-direction: column;
gap: 1rem;
gap: 12px;
}
/* Enhanced responsive menu section headers */

View File

@@ -54,7 +54,7 @@ export function MenuList({ data, categoryRefs }: MenuListProps) {
return (
<>
<div className={styles.menuSections}>
{data?.categories?.map((category) => {
{data?.categories?.map((category, index) => {
const categoryProducts = productsByCategory?.[category.id] || [];
if (categoryProducts.length === 0) return null;
@@ -66,21 +66,24 @@ export function MenuList({ data, categoryRefs }: MenuListProps) {
categoryRefs.current[category.id] = el;
}
}}
style={{ marginBottom: "1rem" }}
style={{ marginBottom: "2rem" }}
>
<ProTitle
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: "16px",
lineHeight: "140%",
letterSpacing: "0%",
color: themeName === "dark" ? "#fff" : "#070707",
}}
level={5}
>
{isRTL ? category.name : category.name}
</ProTitle>
{index !== 0 && (
<ProTitle
style={{
fontWeight: 600,
fontStyle: "SemiBold",
fontSize: "16px",
lineHeight: "140%",
letterSpacing: "0%",
color: themeName === "dark" ? "#fff" : "#070707",
marginBottom: 16,
}}
level={5}
>
{isRTL ? category.name : category.name}
</ProTitle>
)}
<div className={styles.menuItemsGrid}>
{categoryProducts.map((item: Product) => (
<ProductCard

View File

@@ -188,7 +188,7 @@ export default function ProductCard({ item, setIsBottomSheetOpen }: Props) {
count={items
.filter((i) => i.id === item.id)
.reduce((total, item) => total + item.quantity, 0)}
color={colors.primary}
color="#FFC600"
title={`${items
.filter((i) => i.id === item.id)
.reduce((total, item) => total + item.quantity, 0)} in cart`}

View File

@@ -4,8 +4,13 @@
}
.itemDescription {
font-size: 14px !important;
transition: color 0.3s ease;
font-family: Roboto;
font-weight: 400;
font-style: Regular;
font-size: 14px;
line-height: 120%;
letter-spacing: 0%;
color: #707070;
}
.cover {
@@ -533,9 +538,13 @@
}
.restaurantTitle {
font-size: 24px !important;
font-weight: bold;
margin-bottom: 0px !important;
font-family: Roboto;
font-weight: 600;
font-style: SemiBold;
font-size: 24px;
line-height: 120%;
letter-spacing: 0%;
color: #070707;
}
.restaurantDescription {
@@ -558,16 +567,26 @@
.ratingScore {
position: relative;
top: 3px;
font-size: 12px;
top:6px;
font-family: Roboto;
font-weight: 600;
font-style: SemiBold;
font-size: 10px;
line-height: 100%;
letter-spacing: 0%;
color: #333333;
}
.ratingCount {
position: relative;
top: 3px;
font-size: 12px;
color: #666;
top: 6px;
font-family: Roboto;
font-weight: 400;
font-style: Regular;
font-size: 10px;
line-height: 100%;
letter-spacing: 0%;
color: #506fd7;
}
.searchButtonContainer {

View File

@@ -119,9 +119,9 @@ function MenuPage() {
<div className={`${styles.headerContainer}`}>
<div className={styles.contentWrapper}>
<div className={styles.restaurantHeaderTitleContainer}>
<ProTitle className={styles.restaurantTitle}>
<ProText className={styles.restaurantTitle}>
{isRTL ? restaurant?.nameAR : restaurant?.restautantName}
</ProTitle>
</ProText>
<Button
className={
restaurant?.isOpened
@@ -151,7 +151,7 @@ function MenuPage() {
<div className={styles.ratingContainer}>
<StarFilled className={styles.ratingStar} />
<ProText className={styles.ratingScore}>4.5</ProText>
<ProText className={styles.ratingCount}>(2567)</ProText>
<ProText className={styles.ratingCount}>(2567) </ProText>
<ProText
className={`${styles.itemDescription} ${styles.restaurantDescription} responsive-text`}
>
@@ -166,6 +166,35 @@ function MenuPage() {
</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}`}>
<Space orientation="vertical" style={{ width: "100%" }}>
<div>

View File

@@ -71,7 +71,7 @@ export function CustomAmountChoiceBottomSheet({
<ProBottomSheet
isOpen={isOpen}
onClose={onClose}
title={t("splitBill.payAsCustomAmount")}
title={t("splitBill.customAmount")}
showCloseButton={true}
initialSnap={1}
height={425}

View File

@@ -67,7 +67,7 @@ export function EqualltyChoiceBottomSheet({
<ProBottomSheet
isOpen={isOpen}
onClose={onClose}
title={t("splitBill.divideTheBillEqually")}
title={t("splitBill.divideEqually")}
showCloseButton={true}
initialSnap={1}
height={630}

View File

@@ -69,7 +69,7 @@ export function PayForYourItemsChoiceBottomSheet({
<ProBottomSheet
isOpen={isOpen}
onClose={onClose}
title={t("splitBill.payForYourItems")}
title={t("splitBill.payForItems")}
showCloseButton={true}
initialSnap={1}
height={720}

View File

@@ -1,4 +1,3 @@
import { ScheduleFilled } from "@ant-design/icons";
import { Card } from "antd";
import BackIcon from "components/Icons/BackIcon";
import DeliveryIcon from "components/Icons/DeliveryIcon";
@@ -8,24 +7,29 @@ import PickupIcon from "components/Icons/PickupIcon";
import SendGiftIcon from "components/Icons/SendGiftIcon";
import ToOfficeIcon from "components/Icons/ToOfficeIcon";
import ToRoomIcon from "components/Icons/ToRoomIcon";
import ProTitle from "components/ProTitle";
import { updateOrderType } from "features/order/orderSlice";
import { OrderType } from "pages/checkout/hooks/types.ts";
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 { useAppDispatch, useAppSelector } from "redux/hooks";
import styles from "./restaurant.module.css";
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() {
const { t } = useTranslation();
const { isRTL } = useAppSelector((state) => state.locale);
const { subdomain } = useParams();
const navigate = useNavigate();
const dispatch = useAppDispatch();
const { data: restaurant } = useGetRestaurantDetailsQuery(subdomain, {
skip: !subdomain,
});
const [isGiftTypeBottomSheetOpen, setIsGiftTypeBottomSheetOpen] =
useState(false);
const {
dineIn,
@@ -188,12 +192,19 @@ export default function RestaurantServices() {
return (
<div className={getGridClass()}>
{services?.map((s) => {
const isGift = s?.id === OrderType.Gift;
return (
<Link
to={s?.href}
to={isGift ? "#" : s?.href}
key={s?.id}
onClick={() => {
dispatch(updateOrderType(s?.id));
onClick={(e) => {
if (isGift) {
e.preventDefault();
dispatch(updateOrderType(s?.id));
setIsGiftTypeBottomSheetOpen(true);
} else {
dispatch(updateOrderType(s?.id));
}
}}
style={{
width: "100%",
@@ -218,18 +229,20 @@ export default function RestaurantServices() {
}}
>
{s?.icon}
<ProTitle
level={5}
<ProText
style={{
fontWeight: 500,
fontSize: 14,
lineHeight: "140%",
letterSpacing: "0%",
verticalAlign: "middle",
color: "#434E5C",
position: "relative",
top: -1
}}
>
{s?.title}
</ProTitle>
</ProText>
</div>
{isRTL ? (
@@ -242,6 +255,14 @@ export default function RestaurantServices() {
</Link>
);
})}
<GiftTypeBottomSheet
isOpen={isGiftTypeBottomSheetOpen}
onClose={() => setIsGiftTypeBottomSheetOpen(false)}
onSave={() => {
setIsGiftTypeBottomSheetOpen(false);
navigate(`/${subdomain}/menu?orderType=${OrderType.Gift}`);
}}
/>
</div>
);
}

View File

@@ -143,9 +143,9 @@
@media (max-width: 769px) {
.servicesGrid {
display: grid;
gap: 10px;
gap: 16px;
padding: 0 1rem;
margin: 10px 0;
margin: 24px 0 10px 0;
}
}
@@ -260,7 +260,7 @@
.servicesGrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
gap: 24px;
padding: 0 32px;
margin: 32px 0;
max-width: 700px;
@@ -603,7 +603,7 @@
.servicesGrid {
max-width: 1000px;
gap: 20px;
gap: 24px;
padding: 0 32px;
margin: 32px 0;
}
@@ -975,6 +975,7 @@
}
.scheduledOrderIcon {
margin-top: -2px;
position: relative;
top: 1px;
font-size: 22px;
}

View File

@@ -9,6 +9,7 @@ import {
RESTAURANT_DETAILS_URL,
TABLES_URL,
USER_DETAILS_URL,
EGIFT_CARDS_URL,
} from "utils/constants";
import { OrderDetails } from "pages/checkout/hooks/types";
@@ -19,6 +20,7 @@ import {
UserType,
} from "utils/types/appTypes";
import { baseApi } from "./apiSlice";
import { EGiftCard } from "pages/EGiftCards/type";
export const branchApi = baseApi.injectEndpoints({
endpoints: (builder) => ({
@@ -31,7 +33,7 @@ export const branchApi = baseApi.injectEndpoints({
},
}),
transformResponse: (response: any) => {
return response?.result?.restaurants?.[0]
return response?.result?.restaurants?.[0];
},
providesTags: ["Restaurant"],
}),
@@ -160,6 +162,15 @@ export const branchApi = baseApi.injectEndpoints({
}),
invalidatesTags: ["Orders", "Restaurant"],
}),
getEGiftCards: builder.query<EGiftCard[], void>({
query: () => ({
url: EGIFT_CARDS_URL,
method: "GET",
}),
transformResponse: (response: any) => {
return response.result;
},
}),
}),
});
export const {
@@ -173,4 +184,5 @@ export const {
useGetDiscountMutation,
useRateOrderMutation,
useGetUserDetailsQuery,
useGetEGiftCardsQuery,
} = branchApi;

View File

@@ -108,3 +108,4 @@ export const SEND_OTP_URL = `${API_BASE_URL}sendOtp`;
export const CONFIRM_OTP_URL = `${API_BASE_URL}confirmOtp`;
export const PAYMENT_CONFIRMATION_URL = `https://menu.fascano.com/payment/confirmation`;
export const DISCOUNT_URL = `${BASE_URL}getDiscount`;
export const EGIFT_CARDS_URL = `${BASE_URL}gift/cards`;