redesign provider detail page

This commit is contained in:
Tobias Brunner 2025-03-03 09:41:54 +01:00
parent 0d0a80af70
commit 2139df8a05
No known key found for this signature in database
4 changed files with 228 additions and 76 deletions

View file

@ -0,0 +1,33 @@
# Generated by Django 5.1.5 on 2025-03-03 08:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("services", "0014_plan_pricing"),
]
operations = [
migrations.AddField(
model_name="cloudprovider",
name="address",
field=models.TextField(blank=True, max_length=250, null=True),
),
migrations.AddField(
model_name="cloudprovider",
name="email",
field=models.EmailField(blank=True, max_length=254, null=True),
),
migrations.AddField(
model_name="cloudprovider",
name="linkedin",
field=models.URLField(blank=True),
),
migrations.AddField(
model_name="cloudprovider",
name="phone",
field=models.CharField(blank=True, max_length=25, null=True),
),
]

View file

@ -87,6 +87,10 @@ class CloudProvider(models.Model):
slug = models.SlugField(unique=True)
description = ProseEditorField()
website = models.URLField()
linkedin = models.URLField(blank=True)
phone = models.CharField(max_length=25, blank=True, null=True)
email = models.EmailField(max_length=254, blank=True, null=True)
address = models.TextField(max_length=250, blank=True, null=True)
logo = models.ImageField(
upload_to="cloud_provider_logos/",
validators=[validate_image_size],

View file

@ -143,29 +143,32 @@
{% if services %}
<div class="pt-40">
<h3 class="fs-24 fw-semibold lh-1 mb-12">Consulting for Services</h3>
<div class="row row-cols-1 row-cols-md-2 g-4">
<div class="row">
{% for service in services %}
<div class="col">
<div class="card h-100">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
<div class="col-12 col-md-6 mb-30">
<div class="card h-100 d-flex flex-column">
{% if service.logo %}
<div class="d-flex justify-content-between">
{% if service.logo %}
<div class="card__image flex-shrink-0">
<a href="{{ service.get_absolute_url }}">
<img src="{{ service.logo.url }}" alt="{{ service.name }} logo"
class="me-3" style="max-height: 50px; max-width: 100px; object-fit: contain;">
<img src="{{ service.logo.url }}" alt="{{ service.name }} logo" class="img-fluid">
</a>
</div>
{% endif %}
<div>
<h5 class="card-title mb-0"><a href="{{ service.get_absolute_url }}" class="text-decoration-none">{{ service.name }}</a></h5>
<div class="d-flex align-items-center mt-2">
{% for category in service.categories.all|slice:":1" %}
<span class="badge bg-light text-dark me-2">{{ category.name }}</span>
</div>
{% endif %}
<div class="card__content d-flex flex-column flex-grow-1">
<div class="card__header">
<h3 class="card__title"><a href="{{ service.get_absolute_url }}" class="text-decoration-none">{{ service.name }}</a></h3>
<p class="card__subtitle">
{% for category in service.categories.all %}
<span>{{ category.full_path }}</span>
{% endfor %}
</p>
</div>
</div>
</div>
<div class="card-text description-preview mb-3">
{{ service.description|safe|truncatewords_html:30 }}
<div class="card__desc flex-grow-1">
<p class="mb-0">{{ service.description|safe|truncatewords:30 }}</p>
</div>
</div>
</div>

View file

@ -5,27 +5,176 @@
{% block content %}
<section class="section bg-primary-subtle">
<div class="container mx-auto px-20 px-lg-0 pt-40 pb-60">
<div class="row align-items-center">
<div class="col-12 col-lg-5 mb-47 mb-lg-0">
<header class="section-primary__header">
<h2 class="section-h1 fs-40 fs-lg-64 mb-14">{{ provider.name }}</h2>
<div class="text-gray-300 mb-14">
<div class="rich-text-content">
<header class="section-primary__header text-center">
<h2 class="section-h1 fs-40 fs-lg-64 mb-24">Service Provider</h2>
</header>
</div>
</section>
<section class="section">
<div class="container mx-auto px-20 px-lg-0 pt-80 pb-60">
<div class="row">
<!-- Left Sidebar -->
<div class="col-12 col-lg-3">
<div class="pr-lg-6">
<!-- Logo -->
<div class="mb-40 border rounded-4 p-4 d-flex align-items-center justify-content-center" style="min-height: 160px;">
{% if provider.logo %}
<img class="img-fluid w-100 w-lg-auto" src="{{ provider.logo.url }}" alt="{{ provider.name }} logo" style="max-height: 120px; object-fit: contain;">
{% endif %}
</div>
<!-- Contact Information -->
<div class="mb-40">
<h3 class="fw-semibold mb-12">Contact Information</h3>
<ul class="list-unstyled space-y-12 fs-19 ps-0">
{% if provider.website %}
<li>
<a class="d-flex align-items-center text-gray-500 h-32 lh-32" href="{{ provider.website }}" target="_blank">
<span class="pr-10">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-globe" viewBox="0 0 16 16">
<path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8m7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472M3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933M8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855.173-.324.33-.682.468-1.068zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4z" fill="#9A63EC"/>
</svg>
</span>
<span>Website</span>
</a>
</li>
{% endif %}
{% if provider.linkedin %}
<li>
<a class="d-flex align-items-center text-gray-500 h-32 lh-32" href="{{ provider.linkedin }}" target="_blank">
<span class="pr-10">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-linkedin" viewBox="0 0 16 16">
<path d="M0 1.146C0 .513.526 0 1.175 0h13.65C15.474 0 16 .513 16 1.146v13.708c0 .633-.526 1.146-1.175 1.146H1.175C.526 16 0 15.487 0 14.854V1.146zm4.943 12.248V6.169H2.542v7.225h2.401m-1.2-8.212c.837 0 1.358-.554 1.358-1.248-.015-.709-.52-1.248-1.342-1.248-.822 0-1.359.54-1.359 1.248 0 .694.521 1.248 1.327 1.248h.016zm4.908 8.212V9.359c0-.216.016-.432.08-.586.173-.431.568-.878 1.232-.878.869 0 1.216.662 1.216 1.634v3.865h2.401V9.25c0-2.22-1.184-3.252-2.764-3.252-1.274 0-1.845.7-2.165 1.193v.025h-.016a5.54 5.54 0 0 1 .016-.025V6.169h-2.4c.03.678 0 7.225 0 7.225h2.4" fill="#9A63EC"/>
</svg>
</span>
<span>LinkedIn</span>
</a>
</li>
{% endif %}
{% if provider.email %}
<li>
<a class="d-flex align-items-center text-gray-500 h-32 lh-32" href="mailto:{{ provider.email }}">
<span class="pr-10">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-envelope-fill" viewBox="0 0 16 16">
<path d="M.05 3.555A2 2 0 0 1 2 2h12a2 2 0 0 1 1.95 1.555L8 8.414.05 3.555ZM0 4.697v7.104l5.803-3.558zM6.761 8.83l-6.57 4.027A2 2 0 0 0 2 14h12a2 2 0 0 0 1.808-1.144l-6.57-4.027L8 9.586l-1.239-.757Zm3.436-.586L16 11.801V4.697l-5.803 3.546Z" fill="#9A63EC"/>
</svg>
</span>
<span>{{ provider.email }}</span>
</a>
</li>
{% endif %}
{% if provider.phone %}
<li>
<a class="d-flex align-items-center text-gray-500 h-32 lh-32" href="tel:{{ provider.phone }}">
<span class="pr-10">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-telephone-fill" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M1.885.511a1.745 1.745 0 0 1 2.61.163L6.29 2.98c.329.423.445.974.315 1.494l-.547 2.19a.678.678 0 0 0 .178.643l2.457 2.457a.678.678 0 0 0 .644.178l2.189-.547a1.745 1.745 0 0 1 1.494.315l2.306 1.794c.829.645.905 1.87.163 2.611l-1.034 1.034c-.74.74-1.846 1.065-2.877.702a18.634 18.634 0 0 1-7.01-4.42 18.634 18.634 0 0 1-4.42-7.009c-.362-1.03-.037-2.137.703-2.877L1.885.511z" fill="#9A63EC"/>
</svg>
</span>
<span>{{ provider.phone }}</span>
</a>
</li>
{% endif %}
{% if provider.address %}
<li>
<div class="d-flex align-items-start text-gray-500 h-32 lh-32">
<span class="pr-10 pt-1">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-geo-alt-fill" viewBox="0 0 16 16">
<path d="M8 16s6-5.686 6-10A6 6 0 0 0 2 6c0 4.314 6 10 6 10m0-7a3 3 0 1 1 0-6 3 3 0 0 1 0 6" fill="#9A63EC"/>
</svg>
</span>
<span>{{ provider.address|linebreaksbr }}</span>
</div>
</li>
{% endif %}
</ul>
</div>
<!-- Partners Section (if applicable) -->
{% if provider.partners.exists %}
<div class="mb-40">
<h3 class="fw-semibold mb-12">Consulting Partners</h3>
<ul class="list-unstyled space-y-12 fs-19 ps-0">
{% for partner in provider.partners.all %}
<li>
<a class="d-flex align-items-center text-gray-500 h-32 lh-32" href="{{ partner.get_absolute_url }}">
<span class="pr-10">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-people-fill" viewBox="0 0 16 16">
<path d="M7 14s-1 0-1-1 1-4 5-4 5 3 5 4-1 1-1 1zm4-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6m-5.784 6A2.24 2.24 0 0 1 5 13c0-1.355.68-2.75 1.936-3.72A6.3 6.3 0 0 0 5 9c-4 0-5 3-5 4s1 1 1 1zM4.5 8a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5" fill="#9A63EC"/>
</svg>
</span>
<span>{{ partner.name }}</span>
</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
</div>
<!-- Main Content -->
<div class="col-12 col-lg-9">
<div class="pr-lg-32">
<!-- Header -->
<div class="pt-60 pb-lg-60 w-lg-70">
<header>
<h2 class="fs-50 fw-semibold lh-1 mb-12">{{ provider.name }}</h2>
</header>
<div class="fs-19 text-gray-500">
<button class="btn btn-tertiary btn-sm mr-12">Servala Service Provider</button>
</div>
</div>
<!-- Description -->
<div class="pt-40 pt-lg-34">
<h3 class="fs-24 fw-semibold lh-1 mb-12">About</h3>
<div class="fs-19 text-gray-500 rich-text-content">
{{ provider.description|safe }}
</div>
</div>
{% if provider.website %}
<div>
<a class="btn btn-primary btn-lg" href="{{ provider.website }}" target="_blank" role="button">Visit Website</a>
<!-- Services -->
{% if services %}
<div class="pt-40">
<h3 class="fs-24 fw-semibold lh-1 mb-12">Available Services</h3>
<div class="row">
{% for offering in provider.offerings.all %}
<div class="col-12 col-md-6 mb-30">
<div class="card h-100 d-flex flex-column">
{% if offering.service.logo or offering.service.is_featured %}
<div class="d-flex justify-content-between">
{% if offering.service.logo %}
<div class="card__image flex-shrink-0">
<a href="{{ offering.get_absolute_url }}">
<img src="{{ offering.service.logo.url }}" alt="{{ offering.service.name }} logo" class="img-fluid">
</a>
</div>
{% endif %}
</header>
</div>
<div class="col-12 col-lg-7">
<div>
{% if provider.logo %}
<div class="logo-container p-3 rounded" style="background: rgba(255, 255, 255, 0.9); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);">
<img class="img-fluid d-block ml-lg-auto" src="{{ provider.logo.url }}" alt="{{ provider.name }} logo">
{% endif %}
<div class="card__content d-flex flex-column flex-grow-1">
<div class="card__header">
<h3 class="card__title"><a href="{{ offering.get_absolute_url }}" class="text-decoration-none">{{ offering.service.name }}</a></h3>
<p class="card__subtitle">
{% for category in offering.service.categories.all %}
<span>{{ category.full_path }}</span>
{% endfor %}
</p>
</div>
<div class="card__desc flex-grow-1">
<p class="mb-0">{{ offering.service.description|safe|truncatewords:30 }}</p>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
@ -33,41 +182,4 @@
</div>
</div>
</section>
<div class="container mx-auto px-20 px-lg-0 py-60">
<h3 class="section-h2 fs-32 fs-lg-48 mb-40 mt-5">Available Services</h3>
<p>The following services are available on {{ provider.name }} through Servala</p>
<div class="row row-cols-1 row-cols-md-2 g-4">
{% for offering in provider.offerings.all %}
<div class="col">
<div class="card h-100">
<div class="card-body">
<div class="d-flex align-items-center mb-3">
{% if offering.service.logo %}
<a href="{{ offering.get_absolute_url }}">
<img src="{{ offering.service.logo.url }}" alt="{{ offering.service.name }} logo"
class="me-3" style="max-height: 50px; max-width: 100px; object-fit: contain;">
</a>
{% endif %}
<div>
<h5 class="card-title mb-0">
<a href="{{ offering.get_absolute_url }}" class="text-decoration-none">{{ offering.service.name }}</a>
</h5>
</div>
</div>
<div class="card-text description-preview mb-3">
{{ offering.description|safe|truncatewords_html:30 }}
</div>
</div>
</div>
</div>
{% empty %}
<div class="col-12">
<div class="alert alert-info">
No services available from this provider yet.
</div>
</div>
{% endfor %}
</div>
</div>
{% endblock %}