Compare commits
3 Commits
9a7b00649b
...
ca04de4885
| Author | SHA1 | Date | |
|---|---|---|---|
| ca04de4885 | |||
| 82428c5589 | |||
| d5a0f1169c |
2
.gitignore
vendored
@@ -41,6 +41,8 @@ yarn-debug.log*
|
|||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
.pnpm-debug.log*
|
.pnpm-debug.log*
|
||||||
dump.rdb
|
dump.rdb
|
||||||
|
logs/
|
||||||
|
*.log
|
||||||
|
|
||||||
# System files
|
# System files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
78
backend/create_favicons.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Create properly sized favicon images from the logo
|
||||||
|
"""
|
||||||
|
from PIL import Image
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Paths
|
||||||
|
logo_path = "/media/pts/Website/PromptTech_Solution_Site/Logo/PTB-logo.png"
|
||||||
|
public_dir = "/media/pts/Website/PromptTech_Solution_Site/frontend/public"
|
||||||
|
|
||||||
|
# Load the original logo
|
||||||
|
print("Loading logo...")
|
||||||
|
logo = Image.open(logo_path)
|
||||||
|
print(f"Original size: {logo.size}")
|
||||||
|
|
||||||
|
# Convert to RGBA if needed
|
||||||
|
if logo.mode != 'RGBA':
|
||||||
|
logo = logo.convert('RGBA')
|
||||||
|
|
||||||
|
# Define sizes to create
|
||||||
|
sizes = [
|
||||||
|
(16, 16, "favicon-16x16.png"),
|
||||||
|
(32, 32, "favicon-32x32.png"),
|
||||||
|
(48, 48, "favicon-48x48.png"),
|
||||||
|
(64, 64, "favicon-64x64.png"),
|
||||||
|
(180, 180, "apple-touch-icon.png"),
|
||||||
|
(192, 192, "android-chrome-192x192.png"),
|
||||||
|
(512, 512, "android-chrome-512x512.png"),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Create each size
|
||||||
|
for width, height, filename in sizes:
|
||||||
|
# Create a square canvas
|
||||||
|
size = (width, height)
|
||||||
|
|
||||||
|
# Resize with high quality
|
||||||
|
resized = logo.copy()
|
||||||
|
resized.thumbnail(size, Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
|
# Create a new image with transparent background
|
||||||
|
new_img = Image.new('RGBA', size, (255, 255, 255, 0))
|
||||||
|
|
||||||
|
# Calculate position to center the logo
|
||||||
|
paste_x = (size[0] - resized.size[0]) // 2
|
||||||
|
paste_y = (size[1] - resized.size[1]) // 2
|
||||||
|
|
||||||
|
# Paste the resized logo
|
||||||
|
new_img.paste(resized, (paste_x, paste_y), resized)
|
||||||
|
|
||||||
|
# Save
|
||||||
|
output_path = os.path.join(public_dir, filename)
|
||||||
|
new_img.save(output_path, "PNG", optimize=True)
|
||||||
|
print(f"Created: {filename} ({width}x{height})")
|
||||||
|
|
||||||
|
# Create favicon.ico with multiple sizes
|
||||||
|
print("\nCreating favicon.ico with multiple sizes...")
|
||||||
|
ico_sizes = [(16, 16), (32, 32), (48, 48)]
|
||||||
|
ico_images = []
|
||||||
|
|
||||||
|
for width, height in ico_sizes:
|
||||||
|
resized = logo.copy()
|
||||||
|
resized.thumbnail((width, height), Image.Resampling.LANCZOS)
|
||||||
|
|
||||||
|
# Create centered image
|
||||||
|
new_img = Image.new('RGBA', (width, height), (255, 255, 255, 0))
|
||||||
|
paste_x = (width - resized.size[0]) // 2
|
||||||
|
paste_y = (height - resized.size[1]) // 2
|
||||||
|
new_img.paste(resized, (paste_x, paste_y), resized)
|
||||||
|
|
||||||
|
ico_images.append(new_img)
|
||||||
|
|
||||||
|
# Save as ICO
|
||||||
|
favicon_path = os.path.join(public_dir, "favicon.ico")
|
||||||
|
ico_images[0].save(favicon_path, format='ICO', sizes=[(img.width, img.height) for img in ico_images])
|
||||||
|
print(f"Created: favicon.ico with sizes: {[(img.width, img.height) for img in ico_images]}")
|
||||||
|
|
||||||
|
print("\n✅ All favicon images created successfully!")
|
||||||
39
backend/update_hero_image.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Update the About page hero section image URL in the database."""
|
||||||
|
|
||||||
|
import asyncio
|
||||||
|
from sqlalchemy import text
|
||||||
|
from database import async_engine
|
||||||
|
|
||||||
|
async def update_hero_image():
|
||||||
|
"""Update the hero section image_url in about_content table."""
|
||||||
|
|
||||||
|
image_url = "/uploads/media/aa5bcc15-3b1e-4ed8-8708-1a3dceb9494d.jpg"
|
||||||
|
|
||||||
|
async with async_engine.begin() as conn:
|
||||||
|
# Update the hero section image_url
|
||||||
|
result = await conn.execute(
|
||||||
|
text("""
|
||||||
|
UPDATE about_content
|
||||||
|
SET image_url = :image_url,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE section = 'hero'
|
||||||
|
"""),
|
||||||
|
{"image_url": image_url}
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"✅ Updated hero section image_url to: {image_url}")
|
||||||
|
print(f" Rows affected: {result.rowcount}")
|
||||||
|
|
||||||
|
# Verify the update
|
||||||
|
verify = await conn.execute(
|
||||||
|
text("SELECT section, image_url FROM about_content WHERE section = 'hero'")
|
||||||
|
)
|
||||||
|
row = verify.fetchone()
|
||||||
|
if row:
|
||||||
|
print(f"✅ Verified - Section: {row[0]}, Image URL: {row[1]}")
|
||||||
|
else:
|
||||||
|
print("❌ No hero section found!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(update_hero_image())
|
||||||
BIN
frontend/public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
frontend/public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
frontend/public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 9.7 KiB |
BIN
frontend/public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 497 B |
BIN
frontend/public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
BIN
frontend/public/favicon-48x48.png
Normal file
|
After Width: | Height: | Size: 2.0 KiB |
BIN
frontend/public/favicon-64x64.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 3.6 KiB After Width: | Height: | Size: 523 B |
@@ -2,7 +2,17 @@
|
|||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<!-- Favicon for all browsers -->
|
||||||
|
<link rel="icon" type="image/x-icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="%PUBLIC_URL%/favicon-16x16.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="32x32" href="%PUBLIC_URL%/favicon-32x32.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="48x48" href="%PUBLIC_URL%/favicon-48x48.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="64x64" href="%PUBLIC_URL%/favicon-64x64.png" />
|
||||||
|
<!-- Apple Touch Icon -->
|
||||||
|
<link rel="apple-touch-icon" href="%PUBLIC_URL%/apple-touch-icon.png" />
|
||||||
|
<!-- Android Chrome Icons -->
|
||||||
|
<link rel="icon" type="image/png" sizes="192x192" href="%PUBLIC_URL%/android-chrome-192x192.png" />
|
||||||
|
<link rel="icon" type="image/png" sizes="512x512" href="%PUBLIC_URL%/android-chrome-512x512.png" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
|
|||||||
26
frontend/public/manifest.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"short_name": "PromptTech",
|
||||||
|
"name": "PromptTech Solutions",
|
||||||
|
"description": "Your trusted destination for premium electronics and professional repair services",
|
||||||
|
"icons": [
|
||||||
|
{
|
||||||
|
"src": "favicon.ico",
|
||||||
|
"sizes": "16x16 32x32 48x48",
|
||||||
|
"type": "image/x-icon"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "android-chrome-192x192.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "192x192"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"src": "android-chrome-512x512.png",
|
||||||
|
"type": "image/png",
|
||||||
|
"sizes": "512x512"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"start_url": ".",
|
||||||
|
"display": "standalone",
|
||||||
|
"theme_color": "#000000",
|
||||||
|
"background_color": "#ffffff"
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import { useCart } from "../../context/CartContext";
|
|||||||
import { useAuth } from "../../context/AuthContext";
|
import { useAuth } from "../../context/AuthContext";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
const ProductCard = ({ product }) => {
|
const ProductCard = ({ product, viewMode = "grid" }) => {
|
||||||
const { addToCart } = useCart();
|
const { addToCart } = useCart();
|
||||||
const { isAuthenticated } = useAuth();
|
const { isAuthenticated } = useAuth();
|
||||||
|
|
||||||
@@ -41,6 +41,96 @@ const ProductCard = ({ product }) => {
|
|||||||
return product.image_url;
|
return product.image_url;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// List View Layout
|
||||||
|
if (viewMode === "list") {
|
||||||
|
return (
|
||||||
|
<Link
|
||||||
|
to={`/products/${product.id}`}
|
||||||
|
className="group flex gap-4 overflow-hidden rounded-xl border border-border/50 bg-card hover:border-primary/50 transition-all duration-300 hover-lift card-hover-border p-4"
|
||||||
|
data-testid={`product-card-${product.id}`}
|
||||||
|
>
|
||||||
|
{/* Image Container - Fixed Size */}
|
||||||
|
<div className="relative w-32 h-32 flex-shrink-0 overflow-hidden bg-muted rounded-lg">
|
||||||
|
<img
|
||||||
|
src={getImageUrl()}
|
||||||
|
alt={product.name}
|
||||||
|
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
||||||
|
loading="lazy"
|
||||||
|
/>
|
||||||
|
{/* Stock Badge */}
|
||||||
|
{product.stock <= 5 && product.stock > 0 && (
|
||||||
|
<Badge className="absolute top-2 left-2 bg-orange-500 hover:bg-orange-600 text-xs">
|
||||||
|
{product.stock} left
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{product.stock === 0 && (
|
||||||
|
<Badge variant="destructive" className="absolute top-2 left-2 text-xs">
|
||||||
|
Out of Stock
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Content - Flex Container */}
|
||||||
|
<div className="flex-1 flex flex-col justify-between min-w-0">
|
||||||
|
<div className="space-y-2">
|
||||||
|
{/* Category & Brand */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Badge variant="secondary" className="text-xs capitalize">
|
||||||
|
{product.category}
|
||||||
|
</Badge>
|
||||||
|
{product.brand && (
|
||||||
|
<span className="text-xs text-muted-foreground">
|
||||||
|
{product.brand}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Title */}
|
||||||
|
<h3 className="font-semibold text-base leading-tight line-clamp-1 group-hover:text-primary transition-colors font-['Outfit']">
|
||||||
|
{product.name}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
{/* Description if available */}
|
||||||
|
{product.description && (
|
||||||
|
<p className="text-sm text-muted-foreground line-clamp-2">
|
||||||
|
{product.description}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Price & Actions */}
|
||||||
|
<div className="flex items-center justify-between pt-2">
|
||||||
|
<span className="text-xl font-bold font-['Outfit']">
|
||||||
|
${product.price.toFixed(2)}
|
||||||
|
</span>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
className="rounded-full"
|
||||||
|
data-testid={`view-product-${product.id}`}
|
||||||
|
>
|
||||||
|
<Eye className="h-4 w-4 mr-1" />
|
||||||
|
View
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
className="rounded-full px-4 btn-press"
|
||||||
|
onClick={handleAddToCart}
|
||||||
|
disabled={product.stock === 0}
|
||||||
|
data-testid={`add-to-cart-${product.id}`}
|
||||||
|
>
|
||||||
|
<ShoppingCart className="h-4 w-4 mr-1" />
|
||||||
|
Add to Cart
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grid View Layout (Default)
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
to={`/products/${product.id}`}
|
to={`/products/${product.id}`}
|
||||||
|
|||||||
@@ -221,8 +221,11 @@ const About = () => {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<img
|
<img
|
||||||
src={
|
src={
|
||||||
content.hero?.image_url ||
|
content.hero?.image_url
|
||||||
"/uploads/media/aa5bcc15-3b1e-4ed8-8708-1a3dceb9494d.jpg"
|
? content.hero.image_url.startsWith('/uploads')
|
||||||
|
? `${process.env.REACT_APP_BACKEND_URL}${content.hero.image_url}`
|
||||||
|
: content.hero.image_url
|
||||||
|
: `${process.env.REACT_APP_BACKEND_URL}/uploads/media/aa5bcc15-3b1e-4ed8-8708-1a3dceb9494d.jpg`
|
||||||
}
|
}
|
||||||
alt="Tech Repair Services"
|
alt="Tech Repair Services"
|
||||||
className="rounded-2xl shadow-2xl w-full h-auto max-h-[500px] object-cover"
|
className="rounded-2xl shadow-2xl w-full h-auto max-h-[500px] object-cover"
|
||||||
@@ -603,8 +606,11 @@ const About = () => {
|
|||||||
<div className="relative mb-4 overflow-hidden rounded-2xl aspect-square">
|
<div className="relative mb-4 overflow-hidden rounded-2xl aspect-square">
|
||||||
<img
|
<img
|
||||||
src={
|
src={
|
||||||
member.image_url ||
|
member.image_url
|
||||||
"https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400"
|
? member.image_url.startsWith('/uploads')
|
||||||
|
? `${process.env.REACT_APP_BACKEND_URL}${member.image_url}`
|
||||||
|
: member.image_url
|
||||||
|
: "https://images.unsplash.com/photo-1472099645785-5658abf4ff4e?w=400"
|
||||||
}
|
}
|
||||||
alt={member.name}
|
alt={member.name}
|
||||||
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
className="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110"
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ const Products = () => {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{filteredProducts.map((product) => (
|
{filteredProducts.map((product) => (
|
||||||
<ProductCard key={product.id} product={product} />
|
<ProductCard key={product.id} product={product} viewMode={viewMode} />
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
91
scripts/commit_local.sh
Executable file
@@ -0,0 +1,91 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Local Git Commit Script
|
||||||
|
# This script commits changes to the local repository
|
||||||
|
|
||||||
|
echo "===================================="
|
||||||
|
echo "Local Git Commit Script"
|
||||||
|
echo "===================================="
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Show current status
|
||||||
|
echo "Current Git Status:"
|
||||||
|
git status
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Ask user for commit type
|
||||||
|
echo "Select what to commit:"
|
||||||
|
echo "1) All changes (including logs)"
|
||||||
|
echo "2) Code changes only (exclude logs)"
|
||||||
|
echo "3) Custom selection"
|
||||||
|
read -p "Enter your choice (1-3): " choice
|
||||||
|
|
||||||
|
case $choice in
|
||||||
|
1)
|
||||||
|
echo ""
|
||||||
|
echo "Adding all changes..."
|
||||||
|
git add .
|
||||||
|
;;
|
||||||
|
2)
|
||||||
|
echo ""
|
||||||
|
echo "Adding code changes (excluding logs)..."
|
||||||
|
git add frontend/public/favicon*.png
|
||||||
|
git add frontend/public/android-chrome-*.png
|
||||||
|
git add frontend/public/apple-touch-icon.png
|
||||||
|
git add frontend/public/manifest.json
|
||||||
|
git add frontend/public/favicon.ico
|
||||||
|
git add frontend/public/index.html
|
||||||
|
git add frontend/src/components/cards/ProductCard.js
|
||||||
|
git add frontend/src/pages/About.js
|
||||||
|
git add frontend/src/pages/Products.js
|
||||||
|
git add backend/create_favicons.py
|
||||||
|
git add backend/update_hero_image.py
|
||||||
|
;;
|
||||||
|
3)
|
||||||
|
echo ""
|
||||||
|
echo "Enter the files/folders to add (space-separated):"
|
||||||
|
read -p "> " files
|
||||||
|
git add $files
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Invalid choice. Exiting."
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Staged changes:"
|
||||||
|
git status
|
||||||
|
echo ""
|
||||||
|
|
||||||
|
# Get commit message from user
|
||||||
|
read -p "Enter commit message: " commit_message
|
||||||
|
|
||||||
|
if [ -z "$commit_message" ]; then
|
||||||
|
echo "Error: Commit message cannot be empty."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Commit the changes
|
||||||
|
echo ""
|
||||||
|
echo "Committing changes..."
|
||||||
|
git commit -m "$commit_message"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "✓ Changes committed successfully!"
|
||||||
|
echo ""
|
||||||
|
echo "Current status:"
|
||||||
|
git log -1 --oneline
|
||||||
|
echo ""
|
||||||
|
git status
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "✗ Commit failed!"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "===================================="
|
||||||
|
echo "Commit completed successfully!"
|
||||||
|
echo "===================================="
|
||||||
37
scripts/quick_commit.sh
Executable file
@@ -0,0 +1,37 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Quick Git Commit Script
|
||||||
|
# Usage: ./quick_commit.sh "Your commit message"
|
||||||
|
|
||||||
|
if [ -z "$1" ]; then
|
||||||
|
echo "Usage: ./quick_commit.sh \"Your commit message\""
|
||||||
|
echo ""
|
||||||
|
echo "Current changes:"
|
||||||
|
git status --short
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
COMMIT_MSG="$1"
|
||||||
|
|
||||||
|
echo "Adding all changes (excluding logs)..."
|
||||||
|
|
||||||
|
# Add specific files (excluding logs)
|
||||||
|
git add frontend/public/*.png
|
||||||
|
git add frontend/public/*.ico
|
||||||
|
git add frontend/public/manifest.json
|
||||||
|
git add frontend/public/index.html
|
||||||
|
git add frontend/src/
|
||||||
|
git add backend/*.py
|
||||||
|
|
||||||
|
echo ""
|
||||||
|
echo "Committing: $COMMIT_MSG"
|
||||||
|
git commit -m "$COMMIT_MSG"
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo ""
|
||||||
|
echo "✓ Committed successfully!"
|
||||||
|
git log -1 --oneline
|
||||||
|
else
|
||||||
|
echo ""
|
||||||
|
echo "✗ Commit failed or nothing to commit"
|
||||||
|
fi
|
||||||