User setup added
This commit is contained in:
@@ -843,7 +843,7 @@ function App() {
|
||||
<div className="demo-credentials-box">
|
||||
<h4>💡 Dev Sandbox Access:</h4>
|
||||
{loginType === 'SYSTEM' ? (
|
||||
<p>Default Admin: <strong>admin</strong> / Password: <strong>admin123</strong></p>
|
||||
<p>Default Admin: <strong>admin</strong> / Password: <strong>admin</strong></p>
|
||||
) : (
|
||||
<p>Sign in with your Google account. First-time users will be asked for a mobile number to complete registration.</p>
|
||||
)}
|
||||
|
||||
@@ -2,15 +2,21 @@ package com.rit.canteen.sales.service;
|
||||
|
||||
import com.rit.canteen.sales.model.BaseItem;
|
||||
import com.rit.canteen.sales.model.Product;
|
||||
import com.rit.canteen.sales.model.Stall;
|
||||
import com.rit.canteen.sales.model.SystemUser;
|
||||
import com.rit.canteen.sales.repository.BaseItemRepository;
|
||||
import com.rit.canteen.sales.repository.ProductRepository;
|
||||
import com.rit.canteen.sales.repository.StallRepository;
|
||||
import com.rit.canteen.sales.repository.SystemUserRepository;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
import java.util.List;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
public class DatabaseSeeder implements CommandLineRunner {
|
||||
@@ -21,6 +27,15 @@ public class DatabaseSeeder implements CommandLineRunner {
|
||||
@Autowired
|
||||
private ProductRepository productRepository;
|
||||
|
||||
@Autowired
|
||||
private StallRepository stallRepository;
|
||||
|
||||
@Autowired
|
||||
private SystemUserRepository systemUserRepository;
|
||||
|
||||
@Autowired
|
||||
private BCryptPasswordEncoder passwordEncoder;
|
||||
|
||||
@Autowired
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@@ -34,6 +49,8 @@ public class DatabaseSeeder implements CommandLineRunner {
|
||||
repairLobColumns();
|
||||
seedCategories();
|
||||
seedProducts();
|
||||
seedStalls();
|
||||
seedMasterUser();
|
||||
}
|
||||
|
||||
private void repairFeedbackSchema() {
|
||||
@@ -254,4 +271,42 @@ public class DatabaseSeeder implements CommandLineRunner {
|
||||
p.setActive(true);
|
||||
return p;
|
||||
}
|
||||
|
||||
private void seedStalls() {
|
||||
if (stallRepository.count() == 0) {
|
||||
List<Product> allProducts = productRepository.findAll();
|
||||
|
||||
Stall s1 = new Stall("Main Canteen Stall", "Primary food counter serving meals, fast food, and beverages", null);
|
||||
Stall s2 = new Stall("Bakery & Juice Counter", "Fresh bakery items, pastries, snacks, and fresh fruit juices", null);
|
||||
Stall s3 = new Stall("South Indian Express", "Authentic dosas, idlis, vadas, and South Indian breakfast specials", null);
|
||||
|
||||
s1.setProducts(allProducts);
|
||||
s2.setProducts(allProducts.stream()
|
||||
.filter(p -> "Bakery & Sweets".equalsIgnoreCase(p.getCategory()) || "Beverages & Drinks".equalsIgnoreCase(p.getCategory()) || "Snacks & Quick Bites".equalsIgnoreCase(p.getCategory()))
|
||||
.toList());
|
||||
s3.setProducts(allProducts.stream()
|
||||
.filter(p -> "Snacks & Quick Bites".equalsIgnoreCase(p.getCategory()) || "Indian Main Course".equalsIgnoreCase(p.getCategory()))
|
||||
.toList());
|
||||
|
||||
stallRepository.saveAll(List.of(s1, s2, s3));
|
||||
System.out.println(">>> SEEDED DEFAULT STALLS (Main Canteen, Bakery & Juice Counter, South Indian Express)");
|
||||
}
|
||||
}
|
||||
|
||||
private void seedMasterUser() {
|
||||
Optional<SystemUser> existingAdmin = systemUserRepository.findByEmail("admin");
|
||||
if (existingAdmin.isEmpty()) {
|
||||
SystemUser admin = new SystemUser();
|
||||
admin.setName("Admin Master");
|
||||
admin.setEmail("admin");
|
||||
admin.setPassword(passwordEncoder.encode("admin"));
|
||||
admin.setRole("MASTER");
|
||||
admin.setPermissions(List.of("dashboard", "sale", "customers", "purchases", "inventory", "expense", "reports", "stores", "table", "wallet", "promotions", "feedback"));
|
||||
admin.setViewOnly(false);
|
||||
systemUserRepository.save(admin);
|
||||
System.out.println(">>> SEEDED ADMIN MASTER USER (username: admin, password: admin)");
|
||||
} else {
|
||||
System.out.println(">>> ADMIN MASTER USER EXISTS. Skipping admin user creation.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ app.jwt.expiration-ms=86400000
|
||||
# Master Account — REQUIRED environment variables
|
||||
# ============================================================
|
||||
app.master.username=${MASTER_USER:admin}
|
||||
app.master.password=${MASTER_PASSWORD:admin123}
|
||||
app.master.password=${MASTER_PASSWORD:admin}
|
||||
|
||||
# File upload configuration
|
||||
spring.servlet.multipart.max-file-size=10MB
|
||||
|
||||
239
create_master_user.py
Normal file
239
create_master_user.py
Normal file
@@ -0,0 +1,239 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Master User Creation Script for Positeasy Canteen Automation System.
|
||||
|
||||
This script creates or updates a master administrator user in the database
|
||||
with username 'admin' and password 'admin', and synchronizes the application configuration.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import argparse
|
||||
import bcrypt
|
||||
import psycopg2
|
||||
|
||||
# Default Configuration
|
||||
DEFAULT_USERNAME = "admin"
|
||||
DEFAULT_PASSWORD = "admin"
|
||||
DEFAULT_NAME = "Admin Master"
|
||||
DEFAULT_ROLE = "MASTER"
|
||||
|
||||
DEFAULT_DB_HOST = "localhost"
|
||||
DEFAULT_DB_PORT = 5432
|
||||
DEFAULT_DB_NAME = "positeasy"
|
||||
DEFAULT_DB_USER = "postgres"
|
||||
DEFAULT_DB_PASS = "sidharth"
|
||||
|
||||
ALL_PERMISSIONS = [
|
||||
"dashboard",
|
||||
"sale",
|
||||
"customers",
|
||||
"purchases",
|
||||
"inventory",
|
||||
"expense",
|
||||
"reports",
|
||||
"stores",
|
||||
"table",
|
||||
"wallet",
|
||||
"promotions",
|
||||
"feedback"
|
||||
]
|
||||
|
||||
def hash_password(password: str) -> str:
|
||||
"""Hash password using bcrypt compatible with Spring Security BCryptPasswordEncoder."""
|
||||
salt = bcrypt.gensalt(rounds=10)
|
||||
hashed = bcrypt.hashpw(password.encode('utf-8'), salt)
|
||||
return hashed.decode('utf-8')
|
||||
|
||||
def parse_application_properties(prop_path: str) -> dict:
|
||||
"""Parse db credentials from backend application.properties if available."""
|
||||
config = {}
|
||||
if not os.path.exists(prop_path):
|
||||
return config
|
||||
|
||||
with open(prop_path, 'r', encoding='utf-8') as f:
|
||||
for line in f:
|
||||
line = line.strip()
|
||||
if line and not line.startswith('#') and '=' in line:
|
||||
key, value = line.split('=', 1)
|
||||
config[key.strip()] = value.strip()
|
||||
return config
|
||||
|
||||
def update_application_properties(prop_path: str, username: str, password: str) -> bool:
|
||||
"""Update app.master.username and app.master.password in application.properties."""
|
||||
if not os.path.exists(prop_path):
|
||||
print(f"⚠️ [Config] File not found: {prop_path}")
|
||||
return False
|
||||
|
||||
with open(prop_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
# Update or add app.master.username and app.master.password
|
||||
new_user_line = f"app.master.username=${{MASTER_USER:{username}}}"
|
||||
new_pass_line = f"app.master.password=${{MASTER_PASSWORD:{password}}}"
|
||||
|
||||
if re.search(r'^app\.master\.username=.*$', content, flags=re.MULTILINE):
|
||||
content = re.sub(r'^app\.master\.username=.*$', new_user_line, content, flags=re.MULTILINE)
|
||||
else:
|
||||
content += f"\n{new_user_line}"
|
||||
|
||||
if re.search(r'^app\.master\.password=.*$', content, flags=re.MULTILINE):
|
||||
content = re.sub(r'^app\.master\.password=.*$', new_pass_line, content, flags=re.MULTILINE)
|
||||
else:
|
||||
content += f"\n{new_pass_line}"
|
||||
|
||||
with open(prop_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
|
||||
print(f"✅ [Config] Updated {prop_path} with default master credentials ({username}/{password}).")
|
||||
return True
|
||||
|
||||
def update_frontend_demo_credentials(app_tsx_path: str, username: str, password: str) -> bool:
|
||||
"""Update UI demo credentials displayed in api-dashboard if file exists."""
|
||||
if not os.path.exists(app_tsx_path):
|
||||
return False
|
||||
|
||||
with open(app_tsx_path, 'r', encoding='utf-8') as f:
|
||||
content = f.read()
|
||||
|
||||
old_pattern = r'Default Admin:\s*<strong>[^<]*</strong>\s*/\s*Password:\s*<strong>[^<]*</strong>'
|
||||
new_text = f'Default Admin: <strong>{username}</strong> / Password: <strong>{password}</strong>'
|
||||
|
||||
if re.search(old_pattern, content):
|
||||
content = re.sub(old_pattern, new_text, content)
|
||||
with open(app_tsx_path, 'w', encoding='utf-8') as f:
|
||||
f.write(content)
|
||||
print(f"✅ [UI] Updated demo credentials in {app_tsx_path}.")
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_or_update_master_user(db_params: dict, username: str, password: str, name: str) -> bool:
|
||||
"""Insert or update master user in PostgreSQL database."""
|
||||
print(f"\n🔌 Connecting to PostgreSQL database '{db_params['dbname']}' on {db_params['host']}:{db_params['port']}...")
|
||||
|
||||
try:
|
||||
conn = psycopg2.connect(**db_params)
|
||||
conn.autocommit = False
|
||||
cur = conn.cursor()
|
||||
|
||||
# Check if system_users table exists
|
||||
cur.execute("""
|
||||
SELECT EXISTS (
|
||||
SELECT FROM information_schema.tables
|
||||
WHERE table_name = 'system_users'
|
||||
);
|
||||
""")
|
||||
table_exists = cur.fetchone()[0]
|
||||
|
||||
if not table_exists:
|
||||
print("❌ Table 'system_users' does not exist in the database. Has backend schema initialized?")
|
||||
conn.close()
|
||||
return False
|
||||
|
||||
# Check if master user with this username (stored in email column) exists
|
||||
cur.execute("SELECT id, name, role FROM system_users WHERE email = %s;", (username,))
|
||||
row = cur.fetchone()
|
||||
|
||||
hashed_pass = hash_password(password)
|
||||
|
||||
if row:
|
||||
user_id = row[0]
|
||||
print(f"🔄 Existing master user found (ID: {user_id}). Updating credentials...")
|
||||
cur.execute("""
|
||||
UPDATE system_users
|
||||
SET name = %s, password = %s, role = %s, view_only = false
|
||||
WHERE id = %s;
|
||||
""", (name, hashed_pass, DEFAULT_ROLE, user_id))
|
||||
else:
|
||||
print(f"➕ Creating new master user '{username}'...")
|
||||
cur.execute("""
|
||||
INSERT INTO system_users (name, email, password, role, view_only)
|
||||
VALUES (%s, %s, %s, %s, false)
|
||||
RETURNING id;
|
||||
""", (name, username, hashed_pass, DEFAULT_ROLE))
|
||||
user_id = cur.fetchone()[0]
|
||||
|
||||
# Reset permissions for this user
|
||||
cur.execute("DELETE FROM system_user_permissions WHERE user_id = %s;", (user_id,))
|
||||
for perm in ALL_PERMISSIONS:
|
||||
cur.execute("""
|
||||
INSERT INTO system_user_permissions (user_id, permission)
|
||||
VALUES (%s, %s);
|
||||
""", (user_id, perm))
|
||||
|
||||
conn.commit()
|
||||
|
||||
# Verification query
|
||||
cur.execute("""
|
||||
SELECT u.id, u.name, u.email, u.role, count(p.permission)
|
||||
FROM system_users u
|
||||
LEFT JOIN system_user_permissions p ON u.id = p.user_id
|
||||
WHERE u.id = %s
|
||||
GROUP BY u.id, u.name, u.email, u.role;
|
||||
""", (user_id,))
|
||||
ver = cur.fetchone()
|
||||
cur.close()
|
||||
conn.close()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 MASTER USER CREATED / UPDATED SUCCESSFULLY")
|
||||
print("=" * 50)
|
||||
print(f" User ID: {ver[0]}")
|
||||
print(f" Name: {ver[1]}")
|
||||
print(f" Username: {ver[2]}")
|
||||
print(f" Password: {password}")
|
||||
print(f" Role: {ver[3]}")
|
||||
print(f" Permissions: {ver[4]} granted ({', '.join(ALL_PERMISSIONS[:4])}...)")
|
||||
print("=" * 50 + "\n")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Database error: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Create or update master user for Positeasy Canteen Automation.")
|
||||
parser.add_argument("-u", "--username", default=DEFAULT_USERNAME, help="Master username (default: admin)")
|
||||
parser.add_argument("-p", "--password", default=DEFAULT_PASSWORD, help="Master password (default: admin)")
|
||||
parser.add_argument("-n", "--name", default=DEFAULT_NAME, help="Display name for master user")
|
||||
parser.add_argument("--host", default=DEFAULT_DB_HOST, help="PostgreSQL host (default: localhost)")
|
||||
parser.add_argument("--port", type=int, default=DEFAULT_DB_PORT, help="PostgreSQL port (default: 5432)")
|
||||
parser.add_argument("--dbname", default=DEFAULT_DB_NAME, help="PostgreSQL database name (default: positeasy)")
|
||||
parser.add_argument("--dbuser", default=DEFAULT_DB_USER, help="PostgreSQL database user (default: postgres)")
|
||||
parser.add_argument("--dbpass", default=DEFAULT_DB_PASS, help="PostgreSQL database password")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
project_root = os.path.dirname(os.path.abspath(__file__))
|
||||
prop_path = os.path.join(project_root, "backend", "src", "main", "resources", "application.properties")
|
||||
app_tsx_path = os.path.join(project_root, "api-dashboard", "src", "App.tsx")
|
||||
|
||||
db_params = {
|
||||
"host": args.host,
|
||||
"port": args.port,
|
||||
"dbname": args.dbname,
|
||||
"user": args.dbuser,
|
||||
"password": args.dbpass
|
||||
}
|
||||
|
||||
print("🚀 Starting Master User setup script...")
|
||||
|
||||
# 1. Database creation / update
|
||||
db_success = create_or_update_master_user(db_params, args.username, args.password, args.name)
|
||||
|
||||
# 2. Sync application.properties
|
||||
update_application_properties(prop_path, args.username, args.password)
|
||||
|
||||
# 3. Sync frontend dashboard UI
|
||||
update_frontend_demo_credentials(app_tsx_path, args.username, args.password)
|
||||
|
||||
if db_success:
|
||||
print("✨ All steps completed successfully! You can now log in with:")
|
||||
print(f" Username: {args.username}")
|
||||
print(f" Password: {args.password}")
|
||||
else:
|
||||
print("⚠️ Database operation failed or incomplete. Check database connectivity.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -97,7 +97,7 @@ const ItemCard: React.FC<ItemCardProps> = ({ item, isLast, variant = 'list' }) =
|
||||
) : (
|
||||
<div className="list-placeholder">🍲</div>
|
||||
)}
|
||||
{item.isPopular && <span className="bestseller-badge">Bestseller</span>}
|
||||
{item.isPopular && item.stock !== 0 && <span className="bestseller-badge">Bestseller</span>}
|
||||
{item.stock === 0 && <span className="soldout-badge">Sold Out</span>}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -267,3 +267,328 @@ main::-webkit-scrollbar {
|
||||
.veg-nonveg-indicator.non-veg .dot {
|
||||
background-color: #ef4444;
|
||||
}
|
||||
|
||||
/* DESKTOP SCREEN OPTIMIZATIONS */
|
||||
@media (min-width: 769px) {
|
||||
body {
|
||||
display: block;
|
||||
background-color: var(--surface);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
margin: 0 !important;
|
||||
height: auto !important;
|
||||
min-height: 100vh !important;
|
||||
border-radius: 0 !important;
|
||||
border: none !important;
|
||||
box-shadow: none !important;
|
||||
background-color: var(--surface) !important;
|
||||
}
|
||||
|
||||
main {
|
||||
overflow-y: visible !important;
|
||||
}
|
||||
|
||||
/* Home Screen layout adjustments */
|
||||
.welcome-section {
|
||||
display: flex !important;
|
||||
justify-content: space-between !important;
|
||||
align-items: center !important;
|
||||
padding: 32px 60px 16px !important;
|
||||
}
|
||||
|
||||
.search-bar-container {
|
||||
padding: 16px 60px 24px !important;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
max-width: 550px !important;
|
||||
}
|
||||
|
||||
/* Grid listings on desktop - Full Width */
|
||||
.categories-grid {
|
||||
grid-template-columns: repeat(10, 1fr) !important;
|
||||
gap: 20px !important;
|
||||
padding: 0 60px 24px !important;
|
||||
}
|
||||
|
||||
.category-image-wrapper {
|
||||
max-width: 90px !important;
|
||||
max-height: 90px !important;
|
||||
}
|
||||
|
||||
.stalls-carousel {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(4, 1fr) !important;
|
||||
gap: 24px !important;
|
||||
padding: 16px 60px 24px !important;
|
||||
overflow-x: visible !important;
|
||||
}
|
||||
|
||||
.stall-card {
|
||||
min-width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
height: 200px !important;
|
||||
}
|
||||
|
||||
.popular-carousel {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(5, 1fr) !important;
|
||||
gap: 24px !important;
|
||||
padding: 16px 60px 24px !important;
|
||||
overflow-x: visible !important;
|
||||
}
|
||||
|
||||
/* Multi-column items listing */
|
||||
.recommended-section .items-list,
|
||||
.search-results-section .items-list,
|
||||
.stall-items-section .items-list {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(3, 1fr) !important;
|
||||
gap: 24px !important;
|
||||
padding: 16px 60px 24px !important;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
padding: 24px 60px 12px !important;
|
||||
}
|
||||
|
||||
/* Floating Pill Bottom Navigation Bar */
|
||||
.bottom-nav {
|
||||
position: fixed !important;
|
||||
bottom: 24px !important;
|
||||
left: 50% !important;
|
||||
transform: translateX(-50%) !important;
|
||||
width: 500px !important;
|
||||
border-radius: 100px !important;
|
||||
box-shadow: 0 10px 40px rgba(44, 58, 42, 0.15) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
height: 64px !important;
|
||||
padding-bottom: 0 !important;
|
||||
z-index: 1001 !important;
|
||||
background: var(--surface) !important;
|
||||
}
|
||||
|
||||
/* Redesigned Item Detail Page for Desktop */
|
||||
.item-detail-page {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
padding: 0 !important;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.item-detail-page .detail-header-overlay {
|
||||
position: static !important;
|
||||
width: 100% !important;
|
||||
border-bottom: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
.item-detail-page main {
|
||||
display: flex !important;
|
||||
flex-direction: row !important;
|
||||
gap: 40px !important;
|
||||
padding: 40px 60px !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.item-detail-page .item-hero {
|
||||
flex: 1 !important;
|
||||
height: 480px !important;
|
||||
border-radius: 20px !important;
|
||||
box-shadow: 0 10px 30px rgba(44, 58, 42, 0.08) !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
|
||||
.item-detail-page .item-hero-image {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
object-fit: cover !important;
|
||||
}
|
||||
|
||||
.item-detail-page .item-details-content {
|
||||
flex: 1.2 !important;
|
||||
margin-top: 0 !important;
|
||||
padding: 0 !important;
|
||||
background: transparent !important;
|
||||
box-shadow: none !important;
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
.item-detail-page .item-name-large {
|
||||
font-size: 2.2rem !important;
|
||||
font-weight: 850 !important;
|
||||
color: var(--text-dark) !important;
|
||||
}
|
||||
|
||||
.item-detail-page .item-subtitle {
|
||||
font-size: 1rem !important;
|
||||
color: var(--primary) !important;
|
||||
font-weight: 700 !important;
|
||||
}
|
||||
|
||||
.item-detail-page .item-long-description {
|
||||
font-size: 0.95rem !important;
|
||||
line-height: 1.6 !important;
|
||||
color: var(--text-mid) !important;
|
||||
margin-bottom: 24px !important;
|
||||
}
|
||||
|
||||
.item-detail-page .item-extra-info {
|
||||
background: var(--primary-light) !important;
|
||||
border-radius: 16px !important;
|
||||
padding: 20px !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
margin-bottom: 24px !important;
|
||||
}
|
||||
|
||||
.item-detail-page .item-footer {
|
||||
position: static !important;
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: space-between !important;
|
||||
background: var(--surface) !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
border-radius: 20px !important;
|
||||
padding: 20px 24px !important;
|
||||
box-shadow: var(--shadow) !important;
|
||||
margin-top: 24px !important;
|
||||
}
|
||||
|
||||
.item-detail-page .footer-price-info {
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
}
|
||||
|
||||
.item-detail-page .total-label {
|
||||
font-size: 0.8rem !important;
|
||||
text-transform: uppercase !important;
|
||||
letter-spacing: 0.5px !important;
|
||||
}
|
||||
|
||||
.item-detail-page .total-value {
|
||||
font-size: 1.8rem !important;
|
||||
font-weight: 850 !important;
|
||||
color: var(--primary) !important;
|
||||
}
|
||||
|
||||
.item-detail-page .primary-action-button {
|
||||
height: 48px !important;
|
||||
padding: 0 32px !important;
|
||||
font-size: 0.95rem !important;
|
||||
border-radius: 100px !important;
|
||||
width: auto !important;
|
||||
min-width: 160px !important;
|
||||
}
|
||||
|
||||
/* Stall Detail Page Split Flow */
|
||||
.stall-detail-page main.detail-content {
|
||||
display: flex !important;
|
||||
gap: 40px !important;
|
||||
padding: 24px 60px !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.stall-main-card {
|
||||
flex: 1 !important;
|
||||
max-width: 320px !important;
|
||||
margin: 0 !important;
|
||||
position: sticky !important;
|
||||
top: 24px !important;
|
||||
background: var(--surface) !important;
|
||||
padding: 24px !important;
|
||||
border-radius: 20px !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
box-shadow: var(--shadow) !important;
|
||||
z-index: 100 !important;
|
||||
}
|
||||
|
||||
.stall-items-section {
|
||||
flex: 2.2 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.stall-items-section .section-title {
|
||||
padding: 0 0 16px 0 !important;
|
||||
}
|
||||
|
||||
.stall-items-section .items-list {
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(2, 1fr) !important;
|
||||
gap: 20px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Cart split flow */
|
||||
.cart-page main {
|
||||
display: flex !important;
|
||||
gap: 40px !important;
|
||||
padding: 24px 60px !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.cart-items-section {
|
||||
flex: 1.6 !important;
|
||||
}
|
||||
|
||||
.bill-details {
|
||||
flex: 1 !important;
|
||||
margin-top: 0 !important;
|
||||
background: var(--surface) !important;
|
||||
padding: 24px !important;
|
||||
border-radius: 16px !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
box-shadow: var(--shadow) !important;
|
||||
}
|
||||
|
||||
.cart-footer {
|
||||
padding: 20px 60px !important;
|
||||
border-top: 1px solid var(--border) !important;
|
||||
}
|
||||
|
||||
/* Checkout split flow */
|
||||
.checkout-page main {
|
||||
display: flex !important;
|
||||
gap: 40px !important;
|
||||
padding: 24px 60px !important;
|
||||
align-items: flex-start !important;
|
||||
}
|
||||
|
||||
.checkout-section {
|
||||
flex: 1.6 !important;
|
||||
margin-bottom: 0 !important;
|
||||
background: transparent !important;
|
||||
}
|
||||
|
||||
.checkout-summary-container-desktop {
|
||||
flex: 1 !important;
|
||||
display: flex !important;
|
||||
flex-direction: column !important;
|
||||
gap: 24px !important;
|
||||
}
|
||||
|
||||
.checkout-page .order-summary-mini,
|
||||
.checkout-page .insufficient-warning-premium {
|
||||
margin: 0 !important;
|
||||
background: var(--surface) !important;
|
||||
padding: 24px !important;
|
||||
border-radius: 16px !important;
|
||||
border: 1px solid var(--border) !important;
|
||||
box-shadow: var(--shadow) !important;
|
||||
}
|
||||
|
||||
/* Handle footer buttons in desktop splits */
|
||||
.checkout-footer {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.desktop-checkout-action {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide desktop checkout actions on mobile */
|
||||
.desktop-checkout-action {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -336,6 +336,7 @@ const CheckoutScreen: React.FC = () => {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div className="checkout-summary-container-desktop">
|
||||
<div className="order-summary-mini">
|
||||
<div className="summary-row">
|
||||
<span>{paymentMethod === 'RAZORPAY' ? 'Amount payable' : 'Tokens to be deducted'}</span>
|
||||
@@ -382,6 +383,17 @@ const CheckoutScreen: React.FC = () => {
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
<div className="desktop-checkout-action">
|
||||
<button
|
||||
className="place-order-button ritz-order-btn"
|
||||
onClick={handlePlaceOrder}
|
||||
disabled={isProcessing || cart.length === 0 || (paymentMethod === 'RITZ_TOKEN' && isInsufficient)}
|
||||
>
|
||||
{payLabel()}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<div className="checkout-footer">
|
||||
|
||||
@@ -7,16 +7,23 @@
|
||||
position: relative;
|
||||
height: 220px;
|
||||
width: 100%;
|
||||
z-index: 1 !important;
|
||||
}
|
||||
|
||||
.detail-header.sticky {
|
||||
height: auto;
|
||||
padding: 12px 16px;
|
||||
background: var(--surface);
|
||||
z-index: 100;
|
||||
z-index: 100 !important;
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.detail-content {
|
||||
position: relative;
|
||||
z-index: 50 !important;
|
||||
margin-top: -40px !important;
|
||||
}
|
||||
|
||||
.stall-detail-page .add-to-cart-container {
|
||||
position: absolute;
|
||||
bottom: -12px;
|
||||
@@ -77,13 +84,13 @@
|
||||
}
|
||||
|
||||
.stall-main-card {
|
||||
margin: -40px 16px 20px;
|
||||
background: var(--surface);
|
||||
margin: 0 16px 20px;
|
||||
background: var(--surface) !important;
|
||||
border-radius: 24px;
|
||||
padding: 24px;
|
||||
padding: 20px;
|
||||
position: relative;
|
||||
box-shadow: var(--shadow);
|
||||
z-index: 5;
|
||||
z-index: 100 !important;
|
||||
}
|
||||
|
||||
.stall-header-info {
|
||||
|
||||
Reference in New Issue
Block a user