diff --git a/assets/pasted-20260302-154256-893bd58a.png b/assets/pasted-20260302-154256-893bd58a.png new file mode 100644 index 0000000..9b4113e Binary files /dev/null and b/assets/pasted-20260302-154256-893bd58a.png differ diff --git a/config/__pycache__/settings.cpython-311.pyc b/config/__pycache__/settings.cpython-311.pyc index 881731c..11b9d4c 100644 Binary files a/config/__pycache__/settings.cpython-311.pyc and b/config/__pycache__/settings.cpython-311.pyc differ diff --git a/config/settings.py b/config/settings.py index 291d043..9d9dc44 100644 --- a/config/settings.py +++ b/config/settings.py @@ -23,11 +23,13 @@ DEBUG = os.getenv("DJANGO_DEBUG", "true").lower() == "true" ALLOWED_HOSTS = [ "127.0.0.1", "localhost", + "travel-deals.flatlogic.app", os.getenv("HOST_FQDN", ""), ] CSRF_TRUSTED_ORIGINS = [ origin for origin in [ + "travel-deals.flatlogic.app", os.getenv("HOST_FQDN", ""), os.getenv("CSRF_TRUSTED_ORIGIN", "") ] if origin @@ -179,4 +181,4 @@ if EMAIL_USE_SSL: # Default primary key field type # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' \ No newline at end of file diff --git a/core/__pycache__/admin.cpython-311.pyc b/core/__pycache__/admin.cpython-311.pyc index 2964e11..4ebb78a 100644 Binary files a/core/__pycache__/admin.cpython-311.pyc and b/core/__pycache__/admin.cpython-311.pyc differ diff --git a/core/__pycache__/models.cpython-311.pyc b/core/__pycache__/models.cpython-311.pyc index 18a063c..f5f90d9 100644 Binary files a/core/__pycache__/models.cpython-311.pyc and b/core/__pycache__/models.cpython-311.pyc differ diff --git a/core/__pycache__/urls.cpython-311.pyc b/core/__pycache__/urls.cpython-311.pyc index ebb8c6e..d48e12e 100644 Binary files a/core/__pycache__/urls.cpython-311.pyc and b/core/__pycache__/urls.cpython-311.pyc differ diff --git a/core/__pycache__/views.cpython-311.pyc b/core/__pycache__/views.cpython-311.pyc index 8d204fa..421b1f4 100644 Binary files a/core/__pycache__/views.cpython-311.pyc and b/core/__pycache__/views.cpython-311.pyc differ diff --git a/core/admin.py b/core/admin.py index 8c38f3f..321f4ad 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,3 +1,14 @@ from django.contrib import admin +from .models import Deal, Lead -# Register your models here. +@admin.register(Deal) +class DealAdmin(admin.ModelAdmin): + list_display = ('title', 'deal_type', 'destination', 'current_price', 'is_published', 'created_at') + list_filter = ('deal_type', 'is_published', 'created_at') + search_fields = ('title', 'origin', 'destination', 'description') + +@admin.register(Lead) +class LeadAdmin(admin.ModelAdmin): + list_display = ('name', 'email', 'deal', 'created_at') + list_filter = ('created_at',) + search_fields = ('name', 'email', 'message') \ No newline at end of file diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 0000000..150cadf --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,46 @@ +# Generated by Django 5.2.7 on 2026-03-02 15:41 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ] + + operations = [ + migrations.CreateModel( + name='Deal', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('title', models.CharField(max_length=255)), + ('deal_type', models.CharField(choices=[('flight', 'Flight'), ('points', 'Points/Miles')], default='flight', max_length=20)), + ('origin', models.CharField(blank=True, max_length=100)), + ('destination', models.CharField(max_length=100)), + ('current_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('original_price', models.DecimalField(blank=True, decimal_places=2, max_digits=10, null=True)), + ('points_required', models.PositiveIntegerField(blank=True, null=True)), + ('description', models.TextField()), + ('image_url', models.URLField(blank=True, max_length=500)), + ('is_published', models.BooleanField(default=False)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('source_url', models.URLField(blank=True, max_length=500)), + ], + ), + migrations.CreateModel( + name='Lead', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ('email', models.EmailField(max_length=254)), + ('phone', models.CharField(blank=True, max_length=20)), + ('message', models.TextField(blank=True)), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('deal', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='leads', to='core.deal')), + ], + ), + ] diff --git a/core/migrations/__pycache__/0001_initial.cpython-311.pyc b/core/migrations/__pycache__/0001_initial.cpython-311.pyc new file mode 100644 index 0000000..477b83e Binary files /dev/null and b/core/migrations/__pycache__/0001_initial.cpython-311.pyc differ diff --git a/core/models.py b/core/models.py index 71a8362..d2b5354 100644 --- a/core/models.py +++ b/core/models.py @@ -1,3 +1,45 @@ from django.db import models +from django.urls import reverse -# Create your models here. +class Deal(models.Model): + DEAL_TYPES = ( + ('flight', 'Flight'), + ('points', 'Points/Miles'), + ) + + title = models.CharField(max_length=255) + deal_type = models.CharField(max_length=20, choices=DEAL_TYPES, default='flight') + origin = models.CharField(max_length=100, blank=True) + destination = models.CharField(max_length=100) + current_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + original_price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True) + points_required = models.PositiveIntegerField(null=True, blank=True) + description = models.TextField() + image_url = models.URLField(max_length=500, blank=True) + is_published = models.BooleanField(default=False) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + source_url = models.URLField(max_length=500, blank=True) + + def __str__(self): + return self.title + + def get_absolute_url(self): + return reverse('deal_detail', args=[str(self.id)]) + + @property + def discount_percentage(self): + if self.current_price and self.original_price: + return int((1 - (self.current_price / self.original_price)) * 100) + return None + +class Lead(models.Model): + deal = models.ForeignKey(Deal, on_delete=models.CASCADE, related_name='leads') + name = models.CharField(max_length=100) + email = models.EmailField() + phone = models.CharField(max_length=20, blank=True) + message = models.TextField(blank=True) + created_at = models.DateTimeField(auto_now_add=True) + + def __str__(self): + return f"Lead from {self.name} for {self.deal.title}" \ No newline at end of file diff --git a/core/templates/base.html b/core/templates/base.html index 1e7e5fb..23853c0 100644 --- a/core/templates/base.html +++ b/core/templates/base.html @@ -1,25 +1,87 @@ +{% load static %} - - - {% block title %}Knowledge Base{% endblock %} - {% if project_description %} - - - - {% endif %} - {% if project_image_url %} - - - {% endif %} - {% load static %} - - {% block head %}{% endblock %} + + + {% block title %}Travel Consult - Flight Price Drops & Points Deals{% endblock %} + + + + + + + + + {% block extra_head %}{% endblock %} - - {% block content %}{% endblock %} - + - +
+ {% if messages %} +
+ {% for message in messages %} +
+ {{ message }} + +
+ {% endfor %} +
+ {% endif %} + + {% block content %}{% endblock %} +
+ + + + + + {% block extra_js %}{% endblock %} + + \ No newline at end of file diff --git a/core/templates/core/index.html b/core/templates/core/index.html index faec813..840a585 100644 --- a/core/templates/core/index.html +++ b/core/templates/core/index.html @@ -1,145 +1,92 @@ -{% extends "base.html" %} +{% extends 'base.html' %} +{% load static %} -{% block title %}{{ project_name }}{% endblock %} - -{% block head %} - - - - -{% endblock %} +{% block title %}TravelConsult - Curated Flight & Points Deals{% endblock %} {% block content %} -
-
-

Analyzing your requirements and generating your app…

-
- Loading… +
+
+
+
+

Track Price Drops, Maximize Points.

+

We aggregate the best travel deals so you don't have to. From points-based upgrades to unbelievable flight flash sales.

+ +
+
+
+ HOT DEAL +
Round Trip: London → Bali
+

Airlines Flash Sale

+
+ $890 +

$450

+
+
+
+ POINTS DEAL +
New York → Paris Business
+

Amex → Virgin Transfer

+

15k Points

+
+
+
-

AppWizzy AI is collecting your requirements and applying the first changes.

-

This page will refresh automatically as the plan is implemented.

-

- Runtime: Django {{ django_version }} · Python {{ python_version }} - — UTC {{ current_time|date:"Y-m-d H:i:s" }} -

-
-
- -{% endblock %} \ No newline at end of file + + +
+
+
+
+

Latest Curated Opportunities

+

Updated in real-time by our network of travel consultants across the globe.

+
+
+ +
+ {% for deal in deals %} +
+
+ {% if deal.image_url %} + {{ deal.title }} + {% else %} + {{ deal.title }} + {% endif %} +
+ {{ deal.get_deal_type_display }} +

{{ deal.title }}

+

{{ deal.description|truncatewords:20 }}

+ +
+
+ {% if deal.deal_type == 'flight' %} + ${{ deal.original_price|floatformat:0 }} + ${{ deal.current_price|floatformat:0 }} + {% else %} + {{ deal.points_required|default:0 }} Points + {% endif %} +
+ View Deal +
+
+
+
+ {% empty %} +
+

No active deals found. Check back later!

+
+ {% endfor %} +
+
+
+ + +{% endblock %} diff --git a/core/urls.py b/core/urls.py index 6299e3d..af4a199 100644 --- a/core/urls.py +++ b/core/urls.py @@ -1,7 +1,8 @@ from django.urls import path -from .views import home +from .views import home, deal_detail urlpatterns = [ path("", home, name="home"), -] + path("deals//", deal_detail, name="deal_detail"), +] \ No newline at end of file diff --git a/core/views.py b/core/views.py index c9aed12..6ff2d39 100644 --- a/core/views.py +++ b/core/views.py @@ -1,25 +1,73 @@ -import os -import platform - -from django import get_version as django_version -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404, redirect +from .models import Deal, Lead +from django.contrib import messages from django.utils import timezone - def home(request): - """Render the landing screen with loader and environment details.""" - host_name = request.get_host().lower() - agent_brand = "AppWizzy" if host_name == "appwizzy.com" else "Flatlogic" - now = timezone.now() + """Render the landing page with published deals.""" + published_deals = Deal.objects.filter(is_published=True).order_by('-created_at')[:6] + + # Simple check for sample data + if not published_deals.exists(): + _create_sample_deals() + published_deals = Deal.objects.filter(is_published=True).order_by('-created_at')[:6] context = { - "project_name": "New Style", - "agent_brand": agent_brand, - "django_version": django_version(), - "python_version": platform.python_version(), - "current_time": now, - "host_name": host_name, - "project_description": os.getenv("PROJECT_DESCRIPTION", ""), - "project_image_url": os.getenv("PROJECT_IMAGE_URL", ""), + "deals": published_deals, + "current_time": timezone.now(), } return render(request, "core/index.html", context) + +def deal_detail(request, pk): + """Render a single deal and handle lead submissions.""" + deal = get_object_or_404(Deal, pk=pk) + + if request.method == "POST": + name = request.POST.get("name") + email = request.POST.get("email") + message = request.POST.get("message") + + if name and email: + Lead.objects.create(deal=deal, name=name, email=email, message=message) + messages.success(request, "Your request has been sent! We will contact you soon.") + return redirect('deal_detail', pk=pk) + else: + messages.error(request, "Please fill in all required fields.") + + return render(request, "core/deal_detail.html", {"deal": deal}) + +def _create_sample_deals(): + """Seed the database with sample deals if empty.""" + deals = [ + { + "title": "Unreal Points Deal: London to NYC", + "deal_type": "points", + "destination": "New York, USA", + "points_required": 15000, + "description": "Virgin Atlantic points deal. Unbeatable value for Business Class upgrade.", + "image_url": "https://images.pexels.com/photos/466685/pexels-photo-466685.jpeg?auto=compress&cs=tinysrgb&w=800", + "is_published": True + }, + { + "title": "Flight Drop: Bali Flash Sale", + "deal_type": "flight", + "destination": "Bali, Indonesia", + "current_price": 450.00, + "original_price": 890.00, + "description": "Round trip from LAX. Limited seats available for March/April travel.", + "image_url": "https://images.pexels.com/photos/2166553/pexels-photo-2166553.jpeg?auto=compress&cs=tinysrgb&w=800", + "is_published": True + }, + { + "title": "Business Class: Paris for Less", + "deal_type": "flight", + "destination": "Paris, France", + "current_price": 1200.00, + "original_price": 2500.00, + "description": "Lufthansa luxury at half the price. Multi-city options available.", + "image_url": "https://images.pexels.com/photos/338515/pexels-photo-338515.jpeg?auto=compress&cs=tinysrgb&w=800", + "is_published": True + }, + ] + for d in deals: + Deal.objects.get_or_create(title=d['title'], defaults=d) \ No newline at end of file diff --git a/static/css/custom.css b/static/css/custom.css index 925f6ed..d049450 100644 --- a/static/css/custom.css +++ b/static/css/custom.css @@ -1,4 +1,174 @@ -/* Custom styles for the application */ -body { - font-family: system-ui, -apple-system, sans-serif; +/* Custom Tokens */ +:root { + --primary-color: #1A237E; /* Deep Indigo */ + --secondary-color: #03A9F4; /* Sky Blue */ + --accent-color: #FF5722; /* Vibrant Orange */ + --bg-color: #F5F5F5; + --text-color: #333; + --card-bg: rgba(255, 255, 255, 0.85); } + +/* Typography */ +@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;600;700&family=Roboto:wght@300;400;500&display=swap'); + +body { + font-family: 'Roboto', sans-serif; + background-color: var(--bg-color); + color: var(--text-color); + overflow-x: hidden; +} + +h1, h2, h3, h4, h5, h6, .nav-link { + font-family: 'Poppins', sans-serif; + font-weight: 600; +} + +/* Hero Section */ +.hero-section { + background: linear-gradient(135deg, var(--primary-color) 0%, var(--secondary-color) 100%); + padding: 100px 0; + color: white; + position: relative; + overflow: hidden; +} + +.hero-section::after { + content: ''; + position: absolute; + top: -10%; + right: -5%; + width: 300px; + height: 300px; + background: rgba(255, 255, 255, 0.1); + border-radius: 50%; + filter: blur(50px); +} + +.hero-section::before { + content: ''; + position: absolute; + bottom: -10%; + left: -5%; + width: 400px; + height: 400px; + background: rgba(255, 255, 255, 0.05); + border-radius: 50%; + filter: blur(80px); +} + +.hero-title { + font-size: 3.5rem; + font-weight: 700; + margin-bottom: 20px; + text-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.hero-subtitle { + font-size: 1.25rem; + opacity: 0.9; + max-width: 600px; +} + +/* Glassmorphism Cards */ +.deal-card { + background: var(--card-bg); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + border-radius: 20px; + overflow: hidden; + transition: transform 0.3s ease, box-shadow 0.3s ease; + height: 100%; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.05); +} + +.deal-card:hover { + transform: translateY(-10px); + box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1); +} + +.deal-img { + height: 200px; + object-fit: cover; + width: 100%; +} + +.deal-body { + padding: 25px; +} + +.deal-badge { + background: var(--accent-color); + color: white; + padding: 5px 12px; + border-radius: 50px; + font-size: 0.8rem; + font-weight: 600; + display: inline-block; + margin-bottom: 10px; +} + +.price-tag { + font-size: 1.5rem; + font-weight: 700; + color: var(--primary-color); +} + +.old-price { + text-decoration: line-through; + color: #888; + font-size: 0.9rem; + margin-right: 8px; +} + +/* Buttons */ +.btn-primary { + background-color: var(--primary-color); + border: none; + padding: 12px 30px; + border-radius: 50px; + font-weight: 600; + transition: all 0.3s; +} + +.btn-primary:hover { + background-color: #0d124d; + transform: scale(1.05); +} + +.btn-accent { + background-color: var(--accent-color); + color: white; + border: none; + padding: 12px 30px; + border-radius: 50px; + font-weight: 600; + transition: all 0.3s; +} + +.btn-accent:hover { + background-color: #e64a19; + color: white; + transform: scale(1.05); +} + +/* Navbar */ +.navbar { + background: white !important; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); + padding: 15px 0; +} + +.navbar-brand { + font-weight: 700; + color: var(--primary-color) !important; + font-size: 1.5rem; +} + +.nav-link { + color: #555 !important; + margin: 0 10px; +} + +.nav-link:hover { + color: var(--primary-color) !important; +} \ No newline at end of file