├── core ├── __init__.py ├── asgi.py ├── wsgi.py ├── urls.py └── settings.py ├── apps ├── dashboard │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── admin.py │ ├── urls.py │ ├── models.py │ └── views.py ├── subscriptions │ ├── migrations │ │ ├── __init__.py │ │ └── 0001_initial.py │ ├── urls.py │ ├── models.py │ ├── admin.py │ ├── views.py │ └── README.md ├── landing │ ├── urls.py │ ├── admin.py │ └── views.py └── accounts │ └── admin.py ├── requirements.txt ├── .gitignore ├── manage.py ├── LICENSE ├── .env.example ├── templates ├── account │ ├── password_change_done.html │ ├── logout.html │ ├── inactive.html │ ├── password_reset_done.html │ ├── email_change_done.html │ ├── password_reset_from_key_done.html │ ├── email_confirm.html │ ├── password_reset.html │ ├── base.html │ ├── email_change.html │ ├── delete.html │ ├── signup.html │ ├── password_change.html │ ├── login.html │ ├── password_reset_from_key.html │ └── social_connections.html ├── subscriptions │ └── subscription.html ├── dashboard │ ├── subscription_plans.html │ ├── home.html │ ├── profile.html │ ├── settings.html │ └── base.html ├── landing │ ├── pricing.html │ ├── home.html │ └── features.html └── base.html ├── CONTRIBUTING.md ├── static └── css │ └── design-system.css └── README.md /core/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/dashboard/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /apps/subscriptions/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Django 2 | django-allauth 3 | django-tailwind 4 | django-htmx 5 | django-crispy-forms 6 | crispy-tailwind 7 | python-dotenv 8 | whitenoise 9 | gunicorn 10 | stripe -------------------------------------------------------------------------------- /apps/landing/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = 'landing' 5 | 6 | urlpatterns = [ 7 | path('', views.home, name='home'), 8 | path('pricing/', views.pricing, name='pricing'), 9 | path('features/', views.features, name='features'), 10 | ] -------------------------------------------------------------------------------- /apps/subscriptions/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = 'subscriptions' 5 | 6 | urlpatterns = [ 7 | path('', views.subscription_page, name='subscription_page'), 8 | path('create/', views.create_subscription, name='create_subscription'), 9 | path('webhook/', views.stripe_webhook, name='stripe_webhook'), 10 | ] -------------------------------------------------------------------------------- /apps/dashboard/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.admin import AdminSite 3 | from django.utils.translation import gettext_lazy as _ 4 | 5 | class DashboardAdminSite(AdminSite): 6 | site_header = _('Administración del Dashboard') 7 | site_title = _('Dashboard Admin') 8 | index_title = _('Bienvenido al Panel de Administración') 9 | 10 | dashboard_admin_site = DashboardAdminSite(name='dashboard_admin') 11 | -------------------------------------------------------------------------------- /apps/landing/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.admin import AdminSite 3 | from django.utils.translation import gettext_lazy as _ 4 | 5 | class LandingAdminSite(AdminSite): 6 | site_header = _('Administración de la Landing Page') 7 | site_title = _('Landing Admin') 8 | index_title = _('Bienvenido al Panel de Administración de la Landing') 9 | 10 | landing_admin_site = LandingAdminSite(name='landing_admin') 11 | 12 | -------------------------------------------------------------------------------- /core/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for core project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /core/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for core project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /apps/subscriptions/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth import get_user_model 3 | 4 | User = get_user_model() 5 | 6 | class StripeCustomer(models.Model): 7 | user = models.OneToOneField(User, on_delete=models.CASCADE) 8 | stripe_customer_id = models.CharField(max_length=255) 9 | stripe_subscription_id = models.CharField(max_length=255, blank=True, null=True) 10 | subscription_status = models.CharField(max_length=50, blank=True, null=True) 11 | created_at = models.DateTimeField(auto_now_add=True) 12 | updated_at = models.DateTimeField(auto_now=True) 13 | 14 | def __str__(self): 15 | return f"{self.user.email} - {self.subscription_status}" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Python 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | *.so 6 | .Python 7 | build/ 8 | develop-eggs/ 9 | dist/ 10 | downloads/ 11 | eggs/ 12 | .eggs/ 13 | lib/ 14 | lib64/ 15 | parts/ 16 | sdist/ 17 | var/ 18 | wheels/ 19 | *.egg-info/ 20 | .installed.cfg 21 | *.egg 22 | 23 | # Django 24 | *.log 25 | local_settings.py 26 | db.sqlite3 27 | db.sqlite3-journal 28 | media/ 29 | 30 | # Virtual Environment 31 | venv/ 32 | .venv/ 33 | ENV/ 34 | 35 | # IDE 36 | .idea/ 37 | .vscode/ 38 | *.swp 39 | *.swo 40 | 41 | # OS 42 | .DS_Store 43 | Thumbs.db 44 | 45 | # Environment variables 46 | .env 47 | .env.local 48 | .env.*.local 49 | 50 | # Static files 51 | staticfiles/ 52 | 53 | # Coverage 54 | .coverage 55 | htmlcov/ -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /apps/dashboard/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | 4 | app_name = 'dashboard' 5 | 6 | urlpatterns = [ 7 | path('', views.dashboard_home, name='home'), 8 | path('profile/', views.profile, name='profile'), 9 | path('settings/', views.settings, name='settings'), 10 | path('settings/generate-api-key/', views.generate_api_key, name='generate_api_key'), 11 | path('subscription/plans/', views.subscription_plans, name='subscription_plans'), 12 | path('subscription/plans//subscribe/', views.subscribe_to_plan, name='subscribe_to_plan'), 13 | path('subscription/cancel/', views.cancel_subscription, name='cancel_subscription'), 14 | path('subscription/trial/', views.start_trial, name='start_trial'), 15 | ] -------------------------------------------------------------------------------- /apps/subscriptions/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import StripeCustomer 3 | 4 | @admin.register(StripeCustomer) 5 | class StripeCustomerAdmin(admin.ModelAdmin): 6 | list_display = ('user', 'subscription_status', 'created_at', 'updated_at') 7 | list_filter = ('subscription_status', 'created_at') 8 | search_fields = ('user__email', 'stripe_customer_id', 'stripe_subscription_id') 9 | readonly_fields = ('created_at', 'updated_at') 10 | fieldsets = ( 11 | ('Información del Usuario', { 12 | 'fields': ('user',) 13 | }), 14 | ('Información de Stripe', { 15 | 'fields': ('stripe_customer_id', 'stripe_subscription_id', 'subscription_status') 16 | }), 17 | ('Fechas', { 18 | 'fields': ('created_at', 'updated_at'), 19 | 'classes': ('collapse',) 20 | }), 21 | ) 22 | 23 | def has_add_permission(self, request): 24 | return False -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Erik Taveras 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # Django Settings 2 | DEBUG=True 3 | SECRET_KEY=your-secret-key-here 4 | ALLOWED_HOSTS=localhost,127.0.0.1 5 | 6 | # Database Configuration 7 | # Default is SQLite3, uncomment and configure for PostgreSQL 8 | # DB_ENGINE=django.db.backends.postgresql 9 | # DB_NAME=your_db_name 10 | # DB_USER=your_db_user 11 | # DB_PASSWORD=your_db_password 12 | # DB_HOST=localhost 13 | # DB_PORT=5432 14 | 15 | # Email Configuration 16 | EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend 17 | EMAIL_HOST=smtp.gmail.com 18 | EMAIL_PORT=587 19 | EMAIL_USE_TLS=True 20 | EMAIL_HOST_USER=your-email@gmail.com 21 | EMAIL_HOST_PASSWORD=your-app-password 22 | 23 | # Stripe Configuration 24 | STRIPE_PUBLIC_KEY=your-stripe-publishable-key 25 | STRIPE_SECRET_KEY=your-stripe-secret-key 26 | STRIPE_WEBHOOK_SECRET=your-stripe-webhook-secret 27 | STRIPE_PRICE_ID=your-stripe-price-id 28 | 29 | # Security Settings 30 | CSRF_TRUSTED_ORIGINS=http://localhost:8000,https://yourdomain.com 31 | 32 | # Application Settings 33 | SITE_ID=1 34 | ACCOUNT_EMAIL_REQUIRED=True 35 | ACCOUNT_USERNAME_REQUIRED=False 36 | ACCOUNT_AUTHENTICATION_METHOD=email 37 | ACCOUNT_EMAIL_VERIFICATION=mandatory 38 | ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE=True 39 | ACCOUNT_UNIQUE_EMAIL=True 40 | -------------------------------------------------------------------------------- /apps/subscriptions/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2025-04-25 02:04 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='StripeCustomer', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('stripe_customer_id', models.CharField(max_length=255)), 22 | ('stripe_subscription_id', models.CharField(blank=True, max_length=255, null=True)), 23 | ('subscription_status', models.CharField(blank=True, max_length=50, null=True)), 24 | ('created_at', models.DateTimeField(auto_now_add=True)), 25 | ('updated_at', models.DateTimeField(auto_now=True)), 26 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), 27 | ], 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /apps/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.contrib.auth.admin import UserAdmin 3 | from django.contrib.auth import get_user_model 4 | 5 | User = get_user_model() 6 | 7 | admin.site.unregister(User) 8 | 9 | @admin.register(User) 10 | class CustomUserAdmin(UserAdmin): 11 | list_display = ('email', 'first_name', 'last_name', 'is_staff', 'is_active', 'date_joined') 12 | list_filter = ('is_staff', 'is_active', 'date_joined') 13 | search_fields = ('email', 'first_name', 'last_name') 14 | ordering = ('-date_joined',) 15 | 16 | fieldsets = ( 17 | (None, {'fields': ('email', 'password')}), 18 | ('Información Personal', {'fields': ('first_name', 'last_name')}), 19 | ('Permisos', { 20 | 'fields': ('is_active', 'is_staff', 'is_superuser', 'groups', 'user_permissions'), 21 | }), 22 | ('Fechas Importantes', {'fields': ('last_login', 'date_joined')}), 23 | ) 24 | 25 | add_fieldsets = ( 26 | (None, { 27 | 'classes': ('wide',), 28 | 'fields': ('email', 'password1', 'password2'), 29 | }), 30 | ) 31 | 32 | def get_queryset(self, request): 33 | return super().get_queryset(request).select_related('stripecustomer') -------------------------------------------------------------------------------- /core/urls.py: -------------------------------------------------------------------------------- 1 | """ 2 | URL configuration for core project. 3 | 4 | The `urlpatterns` list routes URLs to views. For more information please see: 5 | https://docs.djangoproject.com/en/5.2/topics/http/urls/ 6 | Examples: 7 | Function views 8 | 1. Add an import: from my_app import views 9 | 2. Add a URL to urlpatterns: path('', views.home, name='home') 10 | Class-based views 11 | 1. Add an import: from other_app.views import Home 12 | 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') 13 | Including another URLconf 14 | 1. Import the include() function: from django.urls import include, path 15 | 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) 16 | """ 17 | from django.contrib import admin 18 | from django.urls import path, include 19 | from django.conf import settings 20 | from django.conf.urls.static import static 21 | 22 | urlpatterns = [ 23 | path('admin/', admin.site.urls), 24 | path('accounts/', include('allauth.urls')), 25 | path('', include('apps.landing.urls')), 26 | path('dashboard/', include('apps.dashboard.urls')), 27 | path('subscriptions/', include('apps.subscriptions.urls')), 28 | ] 29 | 30 | if settings.DEBUG: 31 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 32 | -------------------------------------------------------------------------------- /templates/account/password_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Email Changed - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | EMAIL UPDATED 11 |

12 |

13 | Your email address has been successfully updated. 14 |

15 |
16 | 17 |
18 |
19 |
20 | 21 |
22 |

23 | Success 24 |

25 |
26 |

27 | Your email address has been updated. You can continue using the application. 28 |

29 |
30 |
31 |
32 |
33 | 34 | 39 |
40 |
41 |
42 | {% endblock %} -------------------------------------------------------------------------------- /templates/account/logout.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Sign Out - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | SIGN OUT 11 |

12 |

13 | Are you sure you want to sign out? 14 |

15 |
16 | 17 |
18 | {% csrf_token %} 19 | {% if redirect_field_value %} 20 | 21 | {% endif %} 22 | 23 |
24 | 31 |
32 | 33 | 38 |
39 |
40 |
41 | {% endblock %} -------------------------------------------------------------------------------- /apps/landing/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | from django.views.decorators.http import require_http_methods 3 | from django.conf import settings 4 | 5 | @require_http_methods(['GET']) 6 | def home(request): 7 | features = [ 8 | { 9 | 'title': 'Dashboard Intuitivo', 10 | 'description': 'Panel de control fácil de usar con todas tus métricas importantes.', 11 | 'icon': 'chart-bar' 12 | }, 13 | { 14 | 'title': 'Autenticación Segura', 15 | 'description': 'Sistema de autenticación robusto con verificación de email.', 16 | 'icon': 'shield-check' 17 | }, 18 | { 19 | 'title': 'Diseño Responsive', 20 | 'description': 'Interfaz moderna que funciona perfectamente en todos los dispositivos.', 21 | 'icon': 'device-mobile' 22 | }, 23 | { 24 | 'title': 'Suscripciones Flexibles', 25 | 'description': 'Sistema de pagos seguro con Stripe para gestionar suscripciones.', 26 | 'icon': 'credit-card' 27 | } 28 | ] 29 | 30 | pricing = { 31 | 'monthly_price': '9.99', 32 | 'features': [ 33 | 'Acceso completo al dashboard', 34 | 'Soporte prioritario', 35 | 'Características premium', 36 | 'Actualizaciones ilimitadas', 37 | 'API access', 38 | 'Backups diarios' 39 | ] 40 | } 41 | 42 | return render(request, 'landing/home.html', { 43 | 'features': features, 44 | 'pricing': pricing, 45 | }) 46 | 47 | @require_http_methods(['GET']) 48 | def pricing(request): 49 | return render(request, 'landing/pricing.html') 50 | 51 | @require_http_methods(['GET']) 52 | def features(request): 53 | return render(request, 'landing/features.html') -------------------------------------------------------------------------------- /templates/account/inactive.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Account Inactive - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | ACCOUNT INACTIVE 11 |

12 |

13 | This account is currently inactive. 14 |

15 |
16 | 17 |
18 |
19 | 20 |
21 |

22 | Account Status 23 |

24 |
25 |

26 | Your account has been marked as inactive. This could be due to: 27 |

28 |
    29 |
  • Email verification pending
  • 30 |
  • Account suspension
  • 31 |
  • Administrative action
  • 32 |
33 |
34 |
35 |
36 |
37 | 38 |
39 |

40 | Please contact support if you believe this is an error. 41 |

42 | 47 |
48 |
49 |
50 | {% endblock %} -------------------------------------------------------------------------------- /templates/account/password_reset_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Password Reset Sent - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | CHECK YOUR EMAIL 11 |

12 |

13 | We've sent you an email with instructions to reset your password. 14 |

15 |
16 | 17 |
18 |
19 |
20 | 21 |
22 |

23 | Email sent 24 |

25 |
26 |

27 | If you don't see the email in your inbox, please check your spam folder. 28 |

29 |
30 |
31 |
32 |
33 | 34 |
35 |

36 | Didn't receive the email? 37 |

38 |
39 | {% csrf_token %} 40 | 43 |
44 |
45 | 46 | 51 |
52 |
53 |
54 | {% endblock %} -------------------------------------------------------------------------------- /templates/account/email_change_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Email Changed - Django SaaS Boilerplate{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | Email changed 11 |

12 |

13 | Your email address has been successfully updated. 14 |

15 |
16 | 17 |
18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 |

27 | Success 28 |

29 |
30 |

31 | Your email address has been updated. You can continue using the application. 32 |

33 |
34 |
35 |
36 |
37 | 38 | 43 |
44 |
45 |
46 | {% endblock %} -------------------------------------------------------------------------------- /templates/account/password_reset_from_key_done.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Password Reset Complete - Django SaaS Boilerplate{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | Password reset complete 11 |

12 |

13 | Your password has been successfully reset. 14 |

15 |
16 | 17 |
18 |
19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 |

27 | Success 28 |

29 |
30 |

31 | Your password has been changed. You can now sign in with your new password. 32 |

33 |
34 |
35 |
36 |
37 | 38 | 43 |
44 |
45 |
46 | {% endblock %} -------------------------------------------------------------------------------- /templates/account/email_confirm.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Verify Email - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | VERIFY YOUR EMAIL 11 |

12 |

13 | We've sent you an email with a verification link. Please check your inbox and click the link to verify your email address. 14 |

15 |
16 | 17 |
18 |
19 |
20 | 21 |
22 |

23 | Email verification required 24 |

25 |
26 |

27 | If you don't see the email in your inbox, please check your spam folder. 28 |

29 |
30 |
31 |
32 |
33 | 34 |
35 |

36 | Didn't receive the email? 37 |

38 |
39 | {% csrf_token %} 40 | 43 |
44 |
45 | 46 | 51 |
52 |
53 |
54 | {% endblock %} -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Django SaaS Boilerplate 2 | 3 | We love your input! We want to make contributing to Django SaaS Boilerplate as easy and transparent as possible, whether it's: 4 | 5 | - Reporting a bug 6 | - Discussing the current state of the code 7 | - Submitting a fix 8 | - Proposing new features 9 | - Becoming a maintainer 10 | 11 | ## We Develop with Github 12 | We use GitHub to host code, to track issues and feature requests, as well as accept pull requests. 13 | 14 | ## We Use [Github Flow](https://guides.github.com/introduction/flow/index.html) 15 | Pull requests are the best way to propose changes to the codebase. We actively welcome your pull requests: 16 | 17 | 1. Fork the repo and create your branch from `main`. 18 | 2. If you've added code that should be tested, add tests. 19 | 3. If you've changed APIs, update the documentation. 20 | 4. Ensure the test suite passes. 21 | 5. Make sure your code lints. 22 | 6. Issue that pull request! 23 | 24 | ## Any contributions you make will be under the MIT Software License 25 | In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern. 26 | 27 | ## Report bugs using Github's [issue tracker](https://github.com/eriktaveras/django-saas-boilerplate/issues) 28 | We use GitHub issues to track public bugs. Report a bug by [opening a new issue](https://github.com/eriktaveras/django-saas-boilerplate/issues/new); it's that easy! 29 | 30 | ## Write bug reports with detail, background, and sample code 31 | 32 | **Great Bug Reports** tend to have: 33 | 34 | - A quick summary and/or background 35 | - Steps to reproduce 36 | - Be specific! 37 | - Give sample code if you can. 38 | - What you expected would happen 39 | - What actually happens 40 | - Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 41 | 42 | ## Contact the Maintainer 43 | 44 | If you have any questions or need help, feel free to reach out to the maintainer: 45 | 46 | - **Erik Taveras** - Full Stack Solutions Developer 47 | - Website: [www.eriktaveras.com](https://www.eriktaveras.com) 48 | - Email: [hello@eriktaveras.com](mailto:hello@eriktaveras.com) 49 | 50 | ## License 51 | By contributing, you agree that your contributions will be licensed under its MIT License. -------------------------------------------------------------------------------- /templates/account/password_reset.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Reset Password - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | RESET PASSWORD 11 |

12 |

13 | Enter your email address and we'll send you a link to reset your password. 14 |

15 |
16 | 17 |
18 | {% csrf_token %} 19 | 20 |
21 | 22 | 25 |
26 | 27 | {% if form.errors %} 28 |
29 |
30 | 31 |
32 |

33 | Error 34 |

35 |
36 |
    37 | {% for field, errors in form.errors.items %} 38 | {% for error in errors %} 39 |
  • {{ error }}
  • 40 | {% endfor %} 41 | {% endfor %} 42 |
43 |
44 |
45 |
46 |
47 | {% endif %} 48 | 49 |
50 | 57 |
58 | 59 | 64 |
65 |
66 |
67 | {% endblock %} -------------------------------------------------------------------------------- /templates/account/base.html: -------------------------------------------------------------------------------- 1 | {% comment %} 2 | base.html - Plantilla base con el sistema de diseño monocromático 3 | {% endcomment %} 4 | 5 | 6 | 7 | 8 | 9 | {% block title %}Django SaaS Boilerplate{% endblock %} 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 44 | 45 | 102 | 103 | 104 | {% block content %}{% endblock %} 105 | 106 | -------------------------------------------------------------------------------- /templates/account/email_change.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Change Email - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | CHANGE EMAIL 11 |

12 |

13 | Enter your new email address and current password to confirm the change. 14 |

15 |
16 | 17 |
18 | {% csrf_token %} 19 | 20 |
21 | 22 | 25 |
26 | 27 |
28 | 29 | 32 |
33 | 34 | {% if form.errors %} 35 |
36 |
37 | 38 |
39 |

40 | Error 41 |

42 |
43 |
    44 | {% for field, errors in form.errors.items %} 45 | {% for error in errors %} 46 |
  • {{ error }}
  • 47 | {% endfor %} 48 | {% endfor %} 49 |
50 |
51 |
52 |
53 |
54 | {% endif %} 55 | 56 |
57 | 64 |
65 | 66 | 71 |
72 |
73 |
74 | {% endblock %} -------------------------------------------------------------------------------- /apps/dashboard/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.conf import settings 3 | from django.utils import timezone 4 | 5 | class SubscriptionPlan(models.Model): 6 | name = models.CharField(max_length=100) 7 | slug = models.SlugField(unique=True) 8 | description = models.TextField() 9 | price = models.DecimalField(max_digits=10, decimal_places=2) 10 | interval = models.CharField( 11 | max_length=20, 12 | choices=[ 13 | ('monthly', 'Monthly'), 14 | ('yearly', 'Yearly'), 15 | ], 16 | default='monthly' 17 | ) 18 | features = models.JSONField(default=list) 19 | is_active = models.BooleanField(default=True) 20 | created_at = models.DateTimeField(auto_now_add=True) 21 | updated_at = models.DateTimeField(auto_now=True) 22 | 23 | class Meta: 24 | verbose_name = 'Subscription Plan' 25 | verbose_name_plural = 'Subscription Plans' 26 | 27 | def __str__(self): 28 | return f"{self.name} ({self.get_interval_display()})" 29 | 30 | class UserSettings(models.Model): 31 | user = models.OneToOneField( 32 | settings.AUTH_USER_MODEL, 33 | on_delete=models.CASCADE, 34 | related_name='settings' 35 | ) 36 | # Notification preferences 37 | notify_comments = models.BooleanField(default=False) 38 | notify_updates = models.BooleanField(default=False) 39 | notify_marketing = models.BooleanField(default=False) 40 | 41 | # API settings 42 | api_key = models.CharField(max_length=64, blank=True, null=True) 43 | api_key_created_at = models.DateTimeField(null=True, blank=True) 44 | 45 | # Subscription settings 46 | subscription_plan = models.ForeignKey( 47 | SubscriptionPlan, 48 | on_delete=models.SET_NULL, 49 | null=True, 50 | blank=True, 51 | related_name='subscribers' 52 | ) 53 | subscription_status = models.CharField( 54 | max_length=20, 55 | choices=[ 56 | ('active', 'Active'), 57 | ('inactive', 'Inactive'), 58 | ('cancelled', 'Cancelled'), 59 | ('trial', 'Trial'), 60 | ], 61 | default='inactive' 62 | ) 63 | subscription_start_date = models.DateTimeField(null=True, blank=True) 64 | subscription_end_date = models.DateTimeField(null=True, blank=True) 65 | trial_end_date = models.DateTimeField(null=True, blank=True) 66 | 67 | created_at = models.DateTimeField(auto_now_add=True) 68 | updated_at = models.DateTimeField(auto_now=True) 69 | 70 | class Meta: 71 | verbose_name = 'User Settings' 72 | verbose_name_plural = 'User Settings' 73 | 74 | def __str__(self): 75 | return f"Settings for {self.user.email}" 76 | 77 | @property 78 | def is_subscription_active(self): 79 | if self.subscription_status != 'active': 80 | return False 81 | if self.subscription_end_date and self.subscription_end_date < timezone.now(): 82 | return False 83 | return True 84 | 85 | @property 86 | def is_trial_active(self): 87 | if self.subscription_status != 'trial': 88 | return False 89 | if self.trial_end_date and self.trial_end_date < timezone.now(): 90 | return False 91 | return True -------------------------------------------------------------------------------- /apps/dashboard/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 5.0.2 on 2025-04-25 02:03 2 | 3 | import django.db.models.deletion 4 | from django.conf import settings 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 14 | ] 15 | 16 | operations = [ 17 | migrations.CreateModel( 18 | name='SubscriptionPlan', 19 | fields=[ 20 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 21 | ('name', models.CharField(max_length=100)), 22 | ('slug', models.SlugField(unique=True)), 23 | ('description', models.TextField()), 24 | ('price', models.DecimalField(decimal_places=2, max_digits=10)), 25 | ('interval', models.CharField(choices=[('monthly', 'Monthly'), ('yearly', 'Yearly')], default='monthly', max_length=20)), 26 | ('features', models.JSONField(default=list)), 27 | ('is_active', models.BooleanField(default=True)), 28 | ('created_at', models.DateTimeField(auto_now_add=True)), 29 | ('updated_at', models.DateTimeField(auto_now=True)), 30 | ], 31 | options={ 32 | 'verbose_name': 'Subscription Plan', 33 | 'verbose_name_plural': 'Subscription Plans', 34 | }, 35 | ), 36 | migrations.CreateModel( 37 | name='UserSettings', 38 | fields=[ 39 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 40 | ('notify_comments', models.BooleanField(default=False)), 41 | ('notify_updates', models.BooleanField(default=False)), 42 | ('notify_marketing', models.BooleanField(default=False)), 43 | ('api_key', models.CharField(blank=True, max_length=64, null=True)), 44 | ('api_key_created_at', models.DateTimeField(blank=True, null=True)), 45 | ('subscription_status', models.CharField(choices=[('active', 'Active'), ('inactive', 'Inactive'), ('cancelled', 'Cancelled'), ('trial', 'Trial')], default='inactive', max_length=20)), 46 | ('subscription_start_date', models.DateTimeField(blank=True, null=True)), 47 | ('subscription_end_date', models.DateTimeField(blank=True, null=True)), 48 | ('trial_end_date', models.DateTimeField(blank=True, null=True)), 49 | ('created_at', models.DateTimeField(auto_now_add=True)), 50 | ('updated_at', models.DateTimeField(auto_now=True)), 51 | ('subscription_plan', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='subscribers', to='dashboard.subscriptionplan')), 52 | ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='settings', to=settings.AUTH_USER_MODEL)), 53 | ], 54 | options={ 55 | 'verbose_name': 'User Settings', 56 | 'verbose_name_plural': 'User Settings', 57 | }, 58 | ), 59 | ] 60 | -------------------------------------------------------------------------------- /templates/account/delete.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Delete Account - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | DELETE ACCOUNT 11 |

12 |

13 | This action cannot be undone. Please be certain. 14 |

15 |
16 | 17 |
18 |
19 | 20 |
21 |

22 | Warning 23 |

24 |
25 |

26 | Deleting your account will permanently remove all your data and cannot be undone. 27 |

28 |
29 |
30 |
31 |
32 | 33 |
34 | {% csrf_token %} 35 | 36 |
37 | 38 | 41 |
42 | 43 | {% if form.errors %} 44 |
45 |
46 | 47 |
48 |

49 | Error 50 |

51 |
52 |
    53 | {% for field, errors in form.errors.items %} 54 | {% for error in errors %} 55 |
  • {{ error }}
  • 56 | {% endfor %} 57 | {% endfor %} 58 |
59 |
60 |
61 |
62 |
63 | {% endif %} 64 | 65 |
66 | 73 |
74 | 75 | 80 |
81 |
82 |
83 | {% endblock %} -------------------------------------------------------------------------------- /apps/subscriptions/views.py: -------------------------------------------------------------------------------- 1 | import stripe 2 | from django.conf import settings 3 | from django.contrib.auth.decorators import login_required 4 | from django.http import JsonResponse, HttpResponse 5 | from django.shortcuts import render, redirect 6 | from django.views.decorators.csrf import csrf_exempt 7 | from django.views.decorators.http import require_POST 8 | from .models import StripeCustomer 9 | 10 | stripe.api_key = settings.STRIPE_SECRET_KEY 11 | 12 | @login_required 13 | def subscription_page(request): 14 | try: 15 | # Obtener o crear el cliente de Stripe 16 | stripe_customer = StripeCustomer.objects.get(user=request.user) 17 | subscription = stripe.Subscription.retrieve(stripe_customer.stripe_subscription_id) 18 | return render(request, 'subscriptions/subscription.html', { 19 | 'subscription': subscription, 20 | 'STRIPE_PUBLIC_KEY': settings.STRIPE_PUBLIC_KEY 21 | }) 22 | except StripeCustomer.DoesNotExist: 23 | return render(request, 'subscriptions/subscription.html', { 24 | 'STRIPE_PUBLIC_KEY': settings.STRIPE_PUBLIC_KEY 25 | }) 26 | 27 | @login_required 28 | def create_subscription(request): 29 | if request.method == 'POST': 30 | # Crear o obtener el cliente de Stripe 31 | try: 32 | stripe_customer = StripeCustomer.objects.get(user=request.user) 33 | customer = stripe_customer.stripe_customer_id 34 | except StripeCustomer.DoesNotExist: 35 | customer = stripe.Customer.create( 36 | email=request.user.email, 37 | source=request.POST['stripeToken'] 38 | ) 39 | stripe_customer = StripeCustomer.objects.create( 40 | user=request.user, 41 | stripe_customer_id=customer.id 42 | ) 43 | 44 | # Crear la suscripción 45 | subscription = stripe.Subscription.create( 46 | customer=customer, 47 | items=[{'price': settings.STRIPE_PRICE_ID}], 48 | payment_behavior='default_incomplete', 49 | expand=['latest_invoice.payment_intent'], 50 | ) 51 | 52 | stripe_customer.stripe_subscription_id = subscription.id 53 | stripe_customer.subscription_status = subscription.status 54 | stripe_customer.save() 55 | 56 | return JsonResponse({ 57 | 'subscription_id': subscription.id, 58 | 'client_secret': subscription.latest_invoice.payment_intent.client_secret, 59 | }) 60 | 61 | return redirect('subscription_page') 62 | 63 | @csrf_exempt 64 | @require_POST 65 | def stripe_webhook(request): 66 | payload = request.body 67 | sig_header = request.META.get('HTTP_STRIPE_SIGNATURE') 68 | 69 | try: 70 | event = stripe.Webhook.construct_event( 71 | payload, sig_header, settings.STRIPE_WEBHOOK_SECRET 72 | ) 73 | except ValueError: 74 | return HttpResponse(status=400) 75 | except stripe.error.SignatureVerificationError: 76 | return HttpResponse(status=400) 77 | 78 | if event['type'] == 'customer.subscription.updated': 79 | subscription = event['data']['object'] 80 | stripe_customer = StripeCustomer.objects.get(stripe_subscription_id=subscription.id) 81 | stripe_customer.subscription_status = subscription.status 82 | stripe_customer.save() 83 | 84 | return HttpResponse(status=200) -------------------------------------------------------------------------------- /apps/subscriptions/README.md: -------------------------------------------------------------------------------- 1 | # Stripe Subscription System 2 | 3 | A robust and flexible subscription system built with Django and Stripe, designed to handle recurring payments and subscription management for your SaaS application. 4 | 5 | ## 🌟 Features 6 | 7 | - 💳 Secure payment processing with Stripe 8 | - 🔄 Recurring subscription management 9 | - 📊 Subscription status tracking 10 | - 🔔 Webhook integration for real-time updates 11 | - 🎨 Responsive UI with Tailwind CSS 12 | - 🔒 Secure authentication integration 13 | - 📱 Mobile-friendly design 14 | 15 | ## 🚀 Quick Start 16 | 17 | ### 1. Stripe Account Setup 18 | 19 | 1. Create a [Stripe](https://stripe.com) account if you don't have one 20 | 2. Navigate to the Stripe Dashboard -> Developers -> API keys 21 | 3. Copy your Publishable and Secret keys 22 | 23 | ### 2. Product Configuration 24 | 25 | 1. Go to Stripe Dashboard -> Products 26 | 2. Create a new product 27 | 3. Add a recurring price (e.g., $9.99/month) 28 | 4. Copy the Price ID 29 | 30 | ### 3. Webhook Setup 31 | 32 | 1. Go to Stripe Dashboard -> Developers -> Webhooks 33 | 2. Add endpoint: `https://your-domain.com/subscriptions/webhook/` 34 | 3. Select event: `customer.subscription.updated` 35 | 4. Copy the Webhook Signing Secret 36 | 37 | ### 4. Environment Configuration 38 | 39 | Add the following to your `.env` file: 40 | ```env 41 | STRIPE_PUBLIC_KEY=pk_test_... 42 | STRIPE_SECRET_KEY=sk_test_... 43 | STRIPE_WEBHOOK_SECRET=whsec_... 44 | STRIPE_PRICE_ID=price_... 45 | ``` 46 | 47 | ## 📁 Module Structure 48 | 49 | ``` 50 | subscriptions/ 51 | ├── models.py # Subscription and customer models 52 | ├── views.py # Subscription views and logic 53 | ├── urls.py # URL routing 54 | ├── webhooks.py # Stripe webhook handlers 55 | └── templates/ # Subscription templates 56 | └── subscription.html 57 | ``` 58 | 59 | ## 🔧 Customization 60 | 61 | ### Subscription Plans 62 | - Modify subscription plans in the Stripe Dashboard 63 | - Update `STRIPE_PRICE_ID` in your `.env` file 64 | - Customize plan features in `models.py` 65 | 66 | ### UI Customization 67 | - Edit `templates/subscriptions/subscription.html` for UI changes 68 | - Modify styles in your Tailwind CSS configuration 69 | - Add custom JavaScript for enhanced interactivity 70 | 71 | ### Business Logic 72 | - Extend `views.py` for custom subscription logic 73 | - Modify webhook handlers in `webhooks.py` 74 | - Add custom validation in `models.py` 75 | 76 | ## 🔒 Security Features 77 | 78 | - Environment variable protection for sensitive keys 79 | - Webhook signature verification 80 | - CSRF protection on all forms 81 | - Login-required views 82 | - Secure payment processing 83 | - PCI compliance through Stripe 84 | 85 | ## 📚 API Reference 86 | 87 | ### Models 88 | - `Subscription`: Tracks subscription status and details 89 | - `Customer`: Stores customer information and Stripe IDs 90 | 91 | ### Views 92 | - `subscription_view`: Main subscription page 93 | - `webhook`: Handles Stripe webhook events 94 | - `cancel_subscription`: Manages subscription cancellation 95 | 96 | ### Webhooks 97 | - `handle_subscription_updated`: Processes subscription updates 98 | - `handle_payment_succeeded`: Handles successful payments 99 | - `handle_payment_failed`: Manages failed payments 100 | 101 | ## 🤝 Contributing 102 | 103 | We welcome contributions! Please see our [Contributing Guide](../../CONTRIBUTING.md) for details. 104 | 105 | ## 📝 License 106 | 107 | This module is part of the Django SaaS Boilerplate and is licensed under the MIT License. -------------------------------------------------------------------------------- /templates/account/signup.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Sign Up - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | CREATE ACCOUNT 11 |

12 |

13 | Or 14 | 15 | sign in to your existing account 16 | 17 |

18 |
19 |
20 | {% csrf_token %} 21 | {% if redirect_field_value %} 22 | 23 | {% endif %} 24 | 25 |
26 | 27 | 30 |
31 | 32 |
33 | 34 | 37 |
38 | 39 |
40 | 41 | 44 |
45 | 46 | {% if form.errors %} 47 |
48 |
49 | 50 |
51 |

52 | Error 53 |

54 |
55 |
    56 | {% for field, errors in form.errors.items %} 57 | {% for error in errors %} 58 |
  • {{ error }}
  • 59 | {% endfor %} 60 | {% endfor %} 61 |
62 |
63 |
64 |
65 |
66 | {% endif %} 67 | 68 |
69 | 76 |
77 |
78 |
79 |
80 | {% endblock %} -------------------------------------------------------------------------------- /templates/subscriptions/subscription.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | {% block content %} 4 |
5 |

SUBSCRIPTION

6 | 7 | {% if subscription %} 8 |
9 |

SUBSCRIPTION STATUS

10 |

Status: {{ subscription.status }}

11 |

Next billing date: {{ subscription.current_period_end|date:"F j, Y" }}

12 | 13 | {% if subscription.status == 'active' %} 14 | 17 | {% endif %} 18 |
19 | {% else %} 20 |
21 |

PREMIUM PLAN

22 |

Access all premium features for only $9.99/month

23 | 24 |
25 |
26 | 27 | 30 |
31 |
32 | {% endif %} 33 |
34 | 35 | {% if not subscription %} 36 | {% block extra_js %} 37 | 38 | 96 | {% endblock %} 97 | {% endif %} 98 | {% endblock %} -------------------------------------------------------------------------------- /templates/account/password_change.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Change Password - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | CHANGE PASSWORD 11 |

12 |

13 | Please enter your current password and choose a new one 14 |

15 |
16 | 17 |
18 | {% csrf_token %} 19 | 20 |
21 | 22 | 25 |
26 | 27 |
28 | 29 | 32 |
33 | 34 |
35 | 36 | 39 |
40 | 41 | {% if form.errors %} 42 |
43 |
44 | 45 |
46 |

47 | Error 48 |

49 |
50 |
    51 | {% for field, errors in form.errors.items %} 52 | {% for error in errors %} 53 |
  • {{ error }}
  • 54 | {% endfor %} 55 | {% endfor %} 56 |
57 |
58 |
59 |
60 |
61 | {% endif %} 62 | 63 |
64 | 71 |
72 | 73 | 78 |
79 |
80 |
81 | {% endblock %} -------------------------------------------------------------------------------- /templates/account/login.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Sign In - Django SaaS{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 | 9 |
10 |

11 | SIGN IN 12 |

13 |

14 | Or 15 | 16 | create a new account 17 | 18 |

19 |
20 |
21 | {% csrf_token %} 22 | {% if redirect_field_value %} 23 | 24 | {% endif %} 25 | 26 |
27 | 28 | 31 |
32 |
33 | 34 | 37 |
38 | 39 |
40 |
41 | 43 | 46 |
47 | 48 | 53 |
54 | 55 | {% if form.errors %} 56 |
57 |
58 | 59 |
60 |

61 | Error 62 |

63 |
64 |
    65 | {% for field, errors in form.errors.items %} 66 | {% for error in errors %} 67 |
  • {{ error }}
  • 68 | {% endfor %} 69 | {% endfor %} 70 |
71 |
72 |
73 |
74 |
75 | {% endif %} 76 | 77 |
78 | 85 |
86 |
87 |
88 |
89 | {% endblock %} -------------------------------------------------------------------------------- /templates/account/password_reset_from_key.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Reset Password - Django SaaS Boilerplate{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | Reset your password 11 |

12 |

13 | Enter your new password below. 14 |

15 |
16 |
17 | {% csrf_token %} 18 | 19 | 20 | 21 |
22 |
23 | 24 | 27 |
28 |
29 | 30 | 33 |
34 |
35 | 36 | {% if form.errors %} 37 |
38 |
39 |
40 |

41 | There was an error with your submission 42 |

43 |
44 |
    45 | {% for field, errors in form.errors.items %} 46 | {% for error in errors %} 47 |
  • {{ error }}
  • 48 | {% endfor %} 49 | {% endfor %} 50 |
51 |
52 |
53 |
54 |
55 | {% endif %} 56 | 57 |
58 | 70 |
71 | 72 | 77 |
78 |
79 |
80 | {% endblock %} -------------------------------------------------------------------------------- /templates/dashboard/subscription_plans.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard/base.html" %} 2 | 3 | {% block title %}Subscription Plans - SaaS Platform{% endblock %} 4 | 5 | {% block dashboard_content %} 6 |
7 |
8 |

9 | CHOOSE YOUR PLAN 10 |

11 |

12 | Select the plan that best fits your needs 13 |

14 |
15 | 16 | {% if is_trial_active %} 17 |
18 |
19 |
20 | 21 | 22 | 23 |
24 |
25 |

26 | TRIAL PERIOD ACTIVE 27 |

28 |
29 |

30 | Your trial period ends on {{ trial_end_date|date:"F j, Y" }}. Choose a plan to continue using the service. 31 |

32 |
33 |
34 |
35 |
36 | {% endif %} 37 | 38 |
39 | {% for plan in plans %} 40 |
41 |
42 |

{{ plan.name }}

43 |

{{ plan.description }}

44 |

45 | ${{ plan.price }} 46 | /{{ plan.interval }} 47 |

48 |
49 | {% csrf_token %} 50 | 58 |
59 |
60 |
61 |

WHAT'S INCLUDED

62 |
    63 | {% for feature in plan.features %} 64 |
  • 65 | 66 | 67 | 68 | {{ feature }} 69 |
  • 70 | {% endfor %} 71 |
72 |
73 |
74 | {% endfor %} 75 |
76 | 77 | {% if not is_subscription_active and not is_trial_active %} 78 |
79 |
80 | {% csrf_token %} 81 | 85 |
86 |
87 | {% endif %} 88 |
89 | {% endblock %} -------------------------------------------------------------------------------- /static/css/design-system.css: -------------------------------------------------------------------------------- 1 | /* Monochromatic Minimalist Design System */ 2 | 3 | /* Import Space Grotesk font */ 4 | @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500&display=swap'); 5 | 6 | /* Root Variables */ 7 | :root { 8 | /* Color Palette */ 9 | --color-black: #121212; 10 | --color-dark-gray: #292929; 11 | --color-gray: #4a4a4a; 12 | --color-light-gray: #e0e0e0; 13 | --color-white: #ffffff; 14 | --color-error: #d32f2f; 15 | 16 | /* Typography */ 17 | --font-primary: 'Space Grotesk', sans-serif; 18 | --font-size-base: 1rem; 19 | --font-size-h1: 1.875rem; 20 | --font-size-h2: 1.5rem; 21 | --font-size-small: 0.875rem; 22 | 23 | /* Spacing */ 24 | --spacing-xs: 0.5rem; 25 | --spacing-sm: 1rem; 26 | --spacing-md: 1.5rem; 27 | --spacing-lg: 2rem; 28 | --spacing-xl: 3rem; 29 | 30 | /* Transitions */ 31 | --transition-base: all 0.3s ease; 32 | } 33 | 34 | /* Base Styles */ 35 | * { 36 | margin: 0; 37 | padding: 0; 38 | box-sizing: border-box; 39 | } 40 | 41 | body { 42 | font-family: var(--font-primary); 43 | font-size: var(--font-size-base); 44 | line-height: 1.5; 45 | color: var(--color-black); 46 | background-color: var(--color-white); 47 | } 48 | 49 | /* Typography */ 50 | h1, h2, h3, h4, h5, h6 { 51 | font-weight: 500; 52 | letter-spacing: -0.02em; 53 | margin-bottom: var(--spacing-md); 54 | } 55 | 56 | h1 { 57 | font-size: var(--font-size-h1); 58 | } 59 | 60 | h2 { 61 | font-size: var(--font-size-h2); 62 | } 63 | 64 | /* Cards and Containers */ 65 | .card { 66 | background-color: var(--color-white); 67 | border: 1px solid var(--color-light-gray); 68 | border-radius: 0; 69 | transition: var(--transition-base); 70 | padding: var(--spacing-lg); 71 | margin-bottom: var(--spacing-lg); 72 | } 73 | 74 | /* Buttons */ 75 | .btn { 76 | display: inline-block; 77 | padding: 0.75rem 1.5rem; 78 | font-weight: 500; 79 | border-radius: 0; 80 | transition: var(--transition-base); 81 | text-transform: uppercase; 82 | letter-spacing: 0.05em; 83 | border: none; 84 | cursor: pointer; 85 | font-family: var(--font-primary); 86 | font-size: var(--font-size-base); 87 | } 88 | 89 | .btn-primary { 90 | background-color: var(--color-black); 91 | color: var(--color-white); 92 | } 93 | 94 | .btn-primary:hover { 95 | background-color: var(--color-dark-gray); 96 | } 97 | 98 | /* Forms */ 99 | input, textarea, select { 100 | display: block; 101 | width: 100%; 102 | padding: 0.75rem 1rem; 103 | border: 1px solid var(--color-black); 104 | background-color: var(--color-white); 105 | font-family: var(--font-primary); 106 | font-size: var(--font-size-base); 107 | margin-bottom: var(--spacing-md); 108 | } 109 | 110 | .input-with-icon { 111 | padding-left: 3rem; 112 | position: relative; 113 | } 114 | 115 | input:focus, textarea:focus, select:focus { 116 | outline: none; 117 | border-color: var(--color-dark-gray); 118 | box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1); 119 | } 120 | 121 | /* Form Labels */ 122 | label { 123 | display: block; 124 | font-weight: 500; 125 | margin-bottom: var(--spacing-xs); 126 | text-transform: uppercase; 127 | letter-spacing: 0.05em; 128 | font-size: var(--font-size-small); 129 | } 130 | 131 | /* Form Icons */ 132 | .form-icon { 133 | position: absolute; 134 | top: 50%; 135 | left: 1rem; 136 | transform: translateY(-50%); 137 | color: var(--color-black); 138 | } 139 | 140 | /* Error States */ 141 | .error { 142 | color: var(--color-error); 143 | font-size: var(--font-size-small); 144 | margin-top: var(--spacing-xs); 145 | } 146 | 147 | /* Responsive Design */ 148 | @media (max-width: 768px) { 149 | :root { 150 | --font-size-h1: 1.5rem; 151 | --font-size-h2: 1.25rem; 152 | } 153 | 154 | .card { 155 | padding: var(--spacing-md); 156 | } 157 | 158 | .btn { 159 | width: 100%; 160 | text-align: center; 161 | } 162 | } 163 | 164 | /* Utility Classes */ 165 | .text-center { 166 | text-align: center; 167 | } 168 | 169 | .mt-1 { margin-top: var(--spacing-xs); } 170 | .mt-2 { margin-top: var(--spacing-sm); } 171 | .mt-3 { margin-top: var(--spacing-md); } 172 | .mt-4 { margin-top: var(--spacing-lg); } 173 | .mt-5 { margin-top: var(--spacing-xl); } 174 | 175 | .mb-1 { margin-bottom: var(--spacing-xs); } 176 | .mb-2 { margin-bottom: var(--spacing-sm); } 177 | .mb-3 { margin-bottom: var(--spacing-md); } 178 | .mb-4 { margin-bottom: var(--spacing-lg); } 179 | .mb-5 { margin-bottom: var(--spacing-xl); } 180 | 181 | /* Grid System */ 182 | .grid { 183 | display: grid; 184 | gap: var(--spacing-md); 185 | } 186 | 187 | .grid-2 { 188 | grid-template-columns: repeat(2, 1fr); 189 | } 190 | 191 | .grid-3 { 192 | grid-template-columns: repeat(3, 1fr); 193 | } 194 | 195 | .grid-4 { 196 | grid-template-columns: repeat(4, 1fr); 197 | } 198 | 199 | @media (max-width: 768px) { 200 | .grid-2, .grid-3, .grid-4 { 201 | grid-template-columns: 1fr; 202 | } 203 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyperion : Django + HTMX SaaS Boilerplate 2 | 3 |
4 | Django 5.0 5 | HTMX 6 | Tailwind CSS 7 | Alpine.js 8 | Stripe 9 | License: MIT 10 |
11 | 12 | A modern, production-ready Django boilerplate for building SaaS applications with HTMX, Tailwind CSS, and Alpine.js. This template provides everything you need to kickstart your next SaaS project with best practices and modern tooling. 13 | 14 | 15 | 16 | ## 🌟 Features 17 | 18 | - 🚀 **Django 5.0** with modern best practices and security features 19 | - 🎨 **Tailwind CSS** for beautiful, responsive designs 20 | - ⚡ **HTMX** for dynamic interactions without complex JavaScript 21 | - 🎯 **Alpine.js** for lightweight JavaScript functionality 22 | - 🔐 **User Authentication** with django-allauth 23 | - ✉️ **Email Verification** system 24 | - 📱 **Responsive Landing Page** with modern design 25 | - 👑 **Django Admin Panel** customization 26 | - 💳 **Subscription System** with Stripe integration 27 | - 📊 **User Dashboard** with analytics 28 | - 🔒 **Role-based Access Control** 29 | - 🎨 **Modern UI Components** 30 | - 📈 **SEO Optimization** 31 | - 🔍 **Search Functionality** 32 | - 📱 **Mobile-First Approach** 33 | 34 | ## 🚀 Quick Start 35 | 36 | 1. **Clone the repository:** 37 | ```bash 38 | git clone https://github.com/eriktaveras/django-saas-boilerplate.git 39 | cd django-saas-boilerplate 40 | ``` 41 | 42 | 2. **Create and activate a virtual environment:** 43 | ```bash 44 | python -m venv venv 45 | source venv/bin/activate # On Windows: venv\Scripts\activate 46 | ``` 47 | 48 | 3. **Install dependencies:** 49 | ```bash 50 | pip install -r requirements.txt 51 | ``` 52 | 53 | 4. **Run migrations:** 54 | ```bash 55 | python manage.py migrate 56 | ``` 57 | 58 | 5. **Create a superuser:** 59 | ```bash 60 | python manage.py createsuperuser 61 | ``` 62 | 63 | 6. **Run the development server:** 64 | ```bash 65 | python manage.py runserver 66 | ``` 67 | 68 | Visit http://localhost:8000 to see your application! 69 | 70 | ## 📁 Project Structure 71 | 72 | ``` 73 | ├── core/ # Main Django project 74 | │ ├── settings/ # Django settings 75 | │ ├── urls.py # URL configuration 76 | │ └── wsgi.py # WSGI configuration 77 | ├── apps/ # Django applications 78 | │ ├── accounts/ # User authentication 79 | │ ├── landing/ # Landing page 80 | │ ├── dashboard/ # User dashboard 81 | │ └── subscriptions/ # Subscription management 82 | ├── static/ # Static files 83 | │ ├── css/ # CSS files 84 | │ ├── js/ # JavaScript files 85 | │ └── images/ # Image assets 86 | ├── templates/ # HTML templates 87 | │ ├── base.html # Base template 88 | │ ├── components/ # Reusable components 89 | │ └── pages/ # Page templates 90 | └── manage.py # Django management script 91 | ``` 92 | 93 | ## 🛠️ Technology Stack 94 | 95 | - **Backend:** Django 5.0 96 | - **Frontend:** HTMX, Alpine.js 97 | - **Styling:** Tailwind CSS 98 | - **Database:** PostgreSQL (recommended) 99 | - **Authentication:** django-allauth 100 | - **Payments:** Stripe 101 | - **Email:** SendGrid (recommended) 102 | 103 | 104 | ## 🤝 Contributing 105 | 106 | We love your input! We want to make contributing to Django SaaS Boilerplate as easy and transparent as possible. Please see our [Contributing Guide](CONTRIBUTING.md) for details. 107 | 108 | ## 📝 License 109 | 110 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 111 | 112 | ## 👨‍💻 Maintainer 113 | 114 |
115 | Erik Taveras 116 |
117 | 118 | **Erik Taveras** - Full Stack Solutions Developer 119 | 120 | - 🌐 Website: [www.eriktaveras.com](https://www.eriktaveras.com) 121 | - 📧 Email: [hello@eriktaveras.com](mailto:hello@eriktaveras.com) 122 | - 💻 GitHub: [@eriktaveras](https://github.com/eriktaveras) 123 | - 🔗 LinkedIn: [Erik Taveras](https://linkedin.com/in/eriktaveras) 124 | 125 | Specialized in Python, Django, and building scalable web applications and API solutions for businesses and startups. 126 | 127 | ## 🙏 Acknowledgments 128 | 129 | - [Django](https://www.djangoproject.com/) 130 | - [Tailwind CSS](https://tailwindcss.com/) 131 | - [HTMX](https://htmx.org/) 132 | - [Alpine.js](https://alpinejs.dev/) 133 | - [Stripe](https://stripe.com/) 134 | - [Font Awesome](https://fontawesome.com/) 135 | - [Space Grotesk Font](https://fonts.google.com/specimen/Space+Grotesk) 136 | 137 | ## Star History 138 | 139 | [![Star History Chart](https://api.star-history.com/svg?repos=eriktaveras/django-saas-boilerplate&type=Date)](https://www.star-history.com/#eriktaveras/django-saas-boilerplate&Date) 140 | 141 | ## 📫 Contact & Support 142 | 143 | If you have any questions or suggestions, please: 144 | 145 | - 📝 Open an issue 146 | - 📧 Reach out at [hello@eriktaveras.com](mailto:hello@eriktaveras.com) 147 | - 🐦 Follow [@eriktaveras](https://twitter.com/eriktaveras) on Twitter 148 | 149 |
150 |

Built with ❤️ by Erik Taveras

151 |
152 | -------------------------------------------------------------------------------- /core/settings.py: -------------------------------------------------------------------------------- 1 | """ 2 | Django settings for core project. 3 | 4 | Generated by 'django-admin startproject' using Django 5.2. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/5.2/topics/settings/ 8 | 9 | For the full list of settings and their values, see 10 | https://docs.djangoproject.com/en/5.2/ref/settings/ 11 | """ 12 | 13 | import os 14 | from pathlib import Path 15 | from dotenv import load_dotenv 16 | 17 | # Load environment variables 18 | load_dotenv() 19 | 20 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 21 | BASE_DIR = Path(__file__).resolve().parent.parent 22 | 23 | 24 | # Quick-start development settings - unsuitable for production 25 | # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ 26 | 27 | # SECURITY WARNING: keep the secret key used in production secret! 28 | SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-default-key-change-me') 29 | 30 | # SECURITY WARNING: don't run with debug turned on in production! 31 | DEBUG = os.getenv('DEBUG', 'True').lower() == 'true' 32 | 33 | ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', 'localhost,127.0.0.1').split(',') 34 | 35 | 36 | # Application definition 37 | 38 | INSTALLED_APPS = [ 39 | 'django.contrib.admin', 40 | 'django.contrib.auth', 41 | 'django.contrib.contenttypes', 42 | 'django.contrib.sessions', 43 | 'django.contrib.messages', 44 | 'django.contrib.staticfiles', 45 | 'django.contrib.sites', 46 | 47 | # Third party apps 48 | 'allauth', 49 | 'allauth.account', 50 | 'allauth.socialaccount', 51 | 'tailwind', 52 | 'django_htmx', 53 | 'crispy_forms', 54 | 'crispy_tailwind', 55 | 56 | # Local apps 57 | 'apps.accounts', 58 | 'apps.landing', 59 | 'apps.dashboard', 60 | 'apps.subscriptions', 61 | ] 62 | 63 | MIDDLEWARE = [ 64 | 'django.middleware.security.SecurityMiddleware', 65 | 'whitenoise.middleware.WhiteNoiseMiddleware', 66 | 'django.contrib.sessions.middleware.SessionMiddleware', 67 | 'django.middleware.common.CommonMiddleware', 68 | 'django.middleware.csrf.CsrfViewMiddleware', 69 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 70 | 'django.contrib.messages.middleware.MessageMiddleware', 71 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 72 | 'django_htmx.middleware.HtmxMiddleware', 73 | 'allauth.account.middleware.AccountMiddleware', 74 | ] 75 | 76 | ROOT_URLCONF = 'core.urls' 77 | 78 | TEMPLATES = [ 79 | { 80 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 81 | 'DIRS': [os.path.join(BASE_DIR, 'templates')], 82 | 'APP_DIRS': True, 83 | 'OPTIONS': { 84 | 'context_processors': [ 85 | 'django.template.context_processors.debug', 86 | 'django.template.context_processors.request', 87 | 'django.contrib.auth.context_processors.auth', 88 | 'django.contrib.messages.context_processors.messages', 89 | ], 90 | }, 91 | }, 92 | ] 93 | 94 | WSGI_APPLICATION = 'core.wsgi.application' 95 | 96 | 97 | # Database 98 | # https://docs.djangoproject.com/en/5.2/ref/settings/#databases 99 | 100 | DATABASES = { 101 | 'default': { 102 | 'ENGINE': 'django.db.backends.sqlite3', 103 | 'NAME': BASE_DIR / 'db.sqlite3', 104 | } 105 | } 106 | 107 | 108 | # Password validation 109 | # https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators 110 | 111 | AUTH_PASSWORD_VALIDATORS = [ 112 | { 113 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 114 | }, 115 | { 116 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 117 | }, 118 | { 119 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 120 | }, 121 | { 122 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 123 | }, 124 | ] 125 | 126 | 127 | # Internationalization 128 | # https://docs.djangoproject.com/en/5.2/topics/i18n/ 129 | 130 | LANGUAGE_CODE = 'en-us' 131 | 132 | TIME_ZONE = 'UTC' 133 | 134 | USE_I18N = True 135 | 136 | USE_TZ = True 137 | 138 | 139 | # Static files (CSS, JavaScript, Images) 140 | # https://docs.djangoproject.com/en/5.2/howto/static-files/ 141 | 142 | STATIC_URL = 'static/' 143 | STATIC_ROOT = os.path.join(BASE_DIR, 'staticfiles') 144 | STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] 145 | 146 | # Default primary key field type 147 | # https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field 148 | 149 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 150 | 151 | # Tailwind configuration 152 | TAILWIND_APP_NAME = 'theme' 153 | 154 | # Crispy Forms 155 | CRISPY_ALLOWED_TEMPLATE_PACKS = 'tailwind' 156 | CRISPY_TEMPLATE_PACK = 'tailwind' 157 | 158 | # Django Allauth configuration 159 | AUTHENTICATION_BACKENDS = [ 160 | 'django.contrib.auth.backends.ModelBackend', 161 | 'allauth.account.auth_backends.AuthenticationBackend', 162 | ] 163 | 164 | SITE_ID = 1 165 | ACCOUNT_EMAIL_REQUIRED = True 166 | ACCOUNT_USERNAME_REQUIRED = False 167 | ACCOUNT_AUTHENTICATION_METHOD = 'email' 168 | ACCOUNT_EMAIL_VERIFICATION = 'mandatory' 169 | ACCOUNT_SIGNUP_PASSWORD_ENTER_TWICE = True 170 | ACCOUNT_UNIQUE_EMAIL = True 171 | 172 | # Email configuration 173 | EMAIL_BACKEND = os.getenv('EMAIL_BACKEND', 'django.core.mail.backends.console.EmailBackend') 174 | EMAIL_HOST = os.getenv('EMAIL_HOST', 'smtp.gmail.com') 175 | EMAIL_PORT = int(os.getenv('EMAIL_PORT', 587)) 176 | EMAIL_USE_TLS = os.getenv('EMAIL_USE_TLS', 'True').lower() == 'true' 177 | EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', '') 178 | EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', '') 179 | 180 | # Login/Logout URLs 181 | LOGIN_REDIRECT_URL = '/dashboard' 182 | LOGOUT_REDIRECT_URL = '/' 183 | LOGIN_URL = 'account_login' 184 | 185 | # Stripe settings 186 | STRIPE_PUBLIC_KEY = os.getenv('STRIPE_PUBLIC_KEY', '') 187 | STRIPE_SECRET_KEY = os.getenv('STRIPE_SECRET_KEY', '') 188 | STRIPE_WEBHOOK_SECRET = os.getenv('STRIPE_WEBHOOK_SECRET', '') 189 | STRIPE_PRICE_ID = os.getenv('STRIPE_PRICE_ID', '') 190 | -------------------------------------------------------------------------------- /apps/dashboard/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render, redirect, get_object_or_404 2 | from django.contrib.auth.decorators import login_required 3 | from django.views.decorators.http import require_http_methods 4 | from django.contrib import messages 5 | from django.utils import timezone 6 | import secrets 7 | from .models import UserSettings, SubscriptionPlan 8 | 9 | @login_required 10 | @require_http_methods(['GET']) 11 | def dashboard_home(request): 12 | return render(request, 'dashboard/home.html') 13 | 14 | @login_required 15 | @require_http_methods(['GET', 'POST']) 16 | def profile(request): 17 | if request.method == 'POST': 18 | # Handle profile update 19 | user = request.user 20 | user.first_name = request.POST.get('first_name', '') 21 | user.last_name = request.POST.get('last_name', '') 22 | user.save() 23 | messages.success(request, 'Profile updated successfully.') 24 | return redirect('dashboard:profile') 25 | return render(request, 'dashboard/profile.html') 26 | 27 | @login_required 28 | @require_http_methods(['GET', 'POST']) 29 | def settings(request): 30 | # Get or create user settings 31 | user_settings, created = UserSettings.objects.get_or_create(user=request.user) 32 | 33 | if request.method == 'POST': 34 | # Handle notification settings 35 | user_settings.notify_comments = request.POST.get('comments') == 'on' 36 | user_settings.notify_updates = request.POST.get('updates') == 'on' 37 | user_settings.notify_marketing = request.POST.get('marketing') == 'on' 38 | user_settings.save() 39 | 40 | messages.success(request, 'Settings updated successfully.') 41 | return redirect('dashboard:settings') 42 | 43 | # Prepare context with current settings 44 | context = { 45 | 'notification_settings': { 46 | 'comments': user_settings.notify_comments, 47 | 'updates': user_settings.notify_updates, 48 | 'marketing': user_settings.notify_marketing, 49 | }, 50 | 'subscription': { 51 | 'plan': user_settings.subscription_plan, 52 | 'status': user_settings.subscription_status, 53 | 'is_active': user_settings.is_subscription_active, 54 | 'is_trial': user_settings.is_trial_active, 55 | 'start_date': user_settings.subscription_start_date, 56 | 'end_date': user_settings.subscription_end_date, 57 | 'trial_end_date': user_settings.trial_end_date, 58 | }, 59 | 'api': { 60 | 'has_key': bool(user_settings.api_key), 61 | 'key_created_at': user_settings.api_key_created_at, 62 | } 63 | } 64 | return render(request, 'dashboard/settings.html', context) 65 | 66 | @login_required 67 | @require_http_methods(['POST']) 68 | def generate_api_key(request): 69 | user_settings, created = UserSettings.objects.get_or_create(user=request.user) 70 | 71 | # Generate a new API key 72 | api_key = secrets.token_urlsafe(32) 73 | user_settings.api_key = api_key 74 | user_settings.api_key_created_at = timezone.now() 75 | user_settings.save() 76 | 77 | messages.success(request, 'API key generated successfully.') 78 | return redirect('dashboard:settings') 79 | 80 | @login_required 81 | @require_http_methods(['GET']) 82 | def subscription_plans(request): 83 | plans = SubscriptionPlan.objects.filter(is_active=True) 84 | user_settings = UserSettings.objects.get(user=request.user) 85 | 86 | context = { 87 | 'plans': plans, 88 | 'current_plan': user_settings.subscription_plan, 89 | 'subscription_status': user_settings.subscription_status, 90 | 'is_subscription_active': user_settings.is_subscription_active, 91 | 'is_trial_active': user_settings.is_trial_active, 92 | } 93 | return render(request, 'dashboard/subscription_plans.html', context) 94 | 95 | @login_required 96 | @require_http_methods(['POST']) 97 | def subscribe_to_plan(request, plan_slug): 98 | plan = get_object_or_404(SubscriptionPlan, slug=plan_slug, is_active=True) 99 | user_settings = UserSettings.objects.get(user=request.user) 100 | 101 | # Check if user already has an active subscription 102 | if user_settings.is_subscription_active: 103 | messages.warning(request, 'You already have an active subscription.') 104 | return redirect('dashboard:subscription_plans') 105 | 106 | # Update user settings with new subscription 107 | user_settings.subscription_plan = plan 108 | user_settings.subscription_status = 'active' 109 | user_settings.subscription_start_date = timezone.now() 110 | 111 | # Set subscription end date based on interval 112 | if plan.interval == 'monthly': 113 | user_settings.subscription_end_date = timezone.now() + timezone.timedelta(days=30) 114 | else: # yearly 115 | user_settings.subscription_end_date = timezone.now() + timezone.timedelta(days=365) 116 | 117 | user_settings.save() 118 | 119 | messages.success(request, f'Successfully subscribed to {plan.name} plan.') 120 | return redirect('dashboard:settings') 121 | 122 | @login_required 123 | @require_http_methods(['POST']) 124 | def cancel_subscription(request): 125 | user_settings = UserSettings.objects.get(user=request.user) 126 | 127 | if not user_settings.is_subscription_active: 128 | messages.warning(request, 'You do not have an active subscription to cancel.') 129 | return redirect('dashboard:settings') 130 | 131 | user_settings.subscription_status = 'cancelled' 132 | user_settings.save() 133 | 134 | messages.success(request, 'Your subscription has been cancelled.') 135 | return redirect('dashboard:settings') 136 | 137 | @login_required 138 | @require_http_methods(['POST']) 139 | def start_trial(request): 140 | user_settings = UserSettings.objects.get(user=request.user) 141 | 142 | if user_settings.is_subscription_active or user_settings.is_trial_active: 143 | messages.warning(request, 'You already have an active subscription or trial.') 144 | return redirect('dashboard:subscription_plans') 145 | 146 | # Start trial period (14 days) 147 | user_settings.subscription_status = 'trial' 148 | user_settings.trial_end_date = timezone.now() + timezone.timedelta(days=14) 149 | user_settings.save() 150 | 151 | messages.success(request, 'Trial period started successfully.') 152 | return redirect('dashboard:settings') -------------------------------------------------------------------------------- /templates/account/social_connections.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Social Connections - Django SaaS Boilerplate{% endblock %} 4 | 5 | {% block content %} 6 |
7 |
8 |
9 |

10 | Social Connections 11 |

12 |

13 | Connect your social media accounts for easier login. 14 |

15 |
16 | 17 |
18 | 19 |
20 |
21 |
22 |
23 | 24 | 25 | 26 |
27 |

GitHub

28 |

Connect your GitHub account

29 |
30 |
31 |
32 | {% csrf_token %} 33 | 37 |
38 |
39 |
40 |
41 | 42 | 43 |
44 |
45 |
46 |
47 | 48 | 49 | 50 |
51 |

Twitter

52 |

Connect your Twitter account

53 |
54 |
55 |
56 | {% csrf_token %} 57 | 61 |
62 |
63 |
64 |
65 | 66 | 67 |
68 |
69 |
70 |
71 | 72 | 73 | 74 |
75 |

Google

76 |

Connect your Google account

77 |
78 |
79 |
80 | {% csrf_token %} 81 | 85 |
86 |
87 |
88 |
89 |
90 | 91 | 96 |
97 |
98 | {% endblock %} -------------------------------------------------------------------------------- /templates/dashboard/home.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard/base.html" %} 2 | 3 | {% block title %}Dashboard - SaaS Platform{% endblock %} 4 | 5 | {% block dashboard_content %} 6 |
7 |

DASHBOARD

8 |
9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 |
17 |
18 | 19 |
20 |
21 |
22 |
23 | Total Users 24 |
25 |
26 |
27 | 1 28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | 41 |
42 |
43 | 44 | 45 |
46 |
47 |
48 |
49 | 50 |
51 |
52 |
53 |
54 | Active Projects 55 |
56 |
57 |
58 | 0 59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | 72 |
73 |
74 | 75 | 76 |
77 |
78 |
79 |
80 | 81 |
82 |
83 |
84 |
85 | Subscription Status 86 |
87 |
88 |
89 | Free 90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | 103 |
104 |
105 |
106 |
107 | 108 | 109 |
110 |
111 |
112 |

113 | 114 | Recent Activity 115 |

116 |
117 |
118 |
    119 |
  • 120 |
    121 | 122 |
    123 |
    124 | 125 | 126 | 127 |
    128 |
    129 |
    130 |

    Account created

    131 |
    132 |
    133 | 134 |
    135 |
    136 |
    137 |
    138 |
  • 139 |
140 |
141 |
142 |
143 |
144 |
145 | 146 | 147 |
148 |
149 |
150 |

151 | 152 | Quick Actions 153 |

154 | 166 |
167 |
168 |
169 |
170 | {% endblock %} -------------------------------------------------------------------------------- /templates/dashboard/profile.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard/base.html" %} 2 | 3 | {% block title %}Profile - SaaS Platform{% endblock %} 4 | 5 | {% block dashboard_content %} 6 |
7 |

PROFILE

8 |
9 |
10 |
11 |
12 | {% csrf_token %} 13 |
14 |
15 | 16 |
17 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 29 |
30 |
31 | 32 | 33 |
34 |
35 | 36 | 37 |
38 | 39 |
40 | 41 | 42 |
43 |
44 | 45 | 46 |
47 | 48 | 49 |

50 | Your email address is verified. To change it, please contact support. 51 |

52 |
53 | 54 | 55 |
56 | 57 | 58 |
59 |
60 |
61 | 64 |
65 |
66 |
67 |
68 | 69 | 70 |
71 |
72 |
73 |

SECURITY

74 |

75 | Update your security settings and manage connected accounts. 76 |

77 |
78 |
79 |
80 |
81 | 82 |
83 |

PASSWORD

84 |

85 | Change your password or enable two-factor authentication. 86 |

87 | 92 |
93 | 94 | 95 |
96 |

CONNECTED ACCOUNTS

97 |

98 | Connect your social media accounts for easier login. 99 |

100 |
101 | 107 | 113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | 121 | 122 |
123 |
124 |
125 |

DANGER ZONE

126 |

127 | Permanently delete your account and all of your data. 128 |

129 |
130 |
131 |
132 |
133 |
134 |

DELETE ACCOUNT

135 |

136 | Once you delete your account, there is no going back. Please be certain. 137 |

138 | 141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | {% endblock %} -------------------------------------------------------------------------------- /templates/dashboard/settings.html: -------------------------------------------------------------------------------- 1 | {% extends "dashboard/base.html" %} 2 | 3 | {% block title %}Settings - SaaS Platform{% endblock %} 4 | 5 | {% block dashboard_content %} 6 |
7 |

SETTINGS

8 |
9 |
10 | 11 |
12 |
13 |
14 |

NOTIFICATIONS

15 |

16 | Decide which communications you'd like to receive. 17 |

18 |
19 |
20 |
21 | {% csrf_token %} 22 |
23 |
24 |
25 | BY EMAIL 26 |
27 |
28 |
29 | 32 |
33 |
34 | 35 |

Get notified when someone comments on your posts.

36 |
37 |
38 |
39 |
40 | 43 |
44 |
45 | 46 |

Get notified when we release new features and updates.

47 |
48 |
49 |
50 |
51 | 54 |
55 |
56 | 57 |

Receive marketing communications and special offers.

58 |
59 |
60 |
61 |
62 |
63 |
64 | 67 |
68 |
69 |
70 |
71 |
72 |
73 | 74 | 75 |
76 |
77 |
78 |

SUBSCRIPTION

79 |

80 | Manage your subscription and billing information. 81 |

82 |
83 |
84 |
85 |
86 | 87 |
88 |

CURRENT PLAN

89 |
90 |
91 |

You are currently on the {{ subscription.plan|title }} Plan

92 |

Status: {{ subscription.status|title }}

93 |
94 | 95 | UPGRADE PLAN 96 | 97 |
98 |
99 | 100 | 101 |
102 |

PAYMENT METHOD

103 |
104 |

No payment method added

105 | 108 |
109 |
110 | 111 | 112 |
113 |

BILLING HISTORY

114 |
115 |

No billing history available

116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | 124 | 125 |
126 |
127 |
128 |

API ACCESS

129 |

130 | Manage your API keys and access tokens. 131 |

132 |
133 |
134 |
135 |
136 |
137 |

API KEYS

138 |
139 | {% if api.has_key %} 140 |

API key generated on {{ api.key_created_at|date:"F j, Y" }}

141 | 144 | {% else %} 145 |

No API key generated

146 |
147 | {% csrf_token %} 148 | 151 |
152 | {% endif %} 153 |
154 |
155 | 156 |
157 |

WEBHOOKS

158 |
159 |

No webhooks configured

160 | 163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 | {% endblock %} -------------------------------------------------------------------------------- /templates/landing/pricing.html: -------------------------------------------------------------------------------- 1 | {% extends 'base.html' %} 2 | {% load static %} 3 | 4 | {% block title %}Pricing - SaaS Platform{% endblock %} 5 | 6 | {% block content %} 7 | 8 |
9 |
10 |
11 |

12 | SIMPLE AND TRANSPARENT PRICING 13 |

14 |

15 | Choose the plan that best fits your needs 16 |

17 |
18 |
19 |
20 | 21 | 22 |
23 |
24 |
25 | 26 |
27 |
28 |
29 | 30 | FREE PLAN 31 | 32 |
33 |
34 | $ 35 | 0 36 | /mo 37 |
38 |
39 |
40 |
    41 |
  • 42 | 43 |

    Basic dashboard access

    44 |
  • 45 |
  • 46 | 47 |

    Email support

    48 |
  • 49 |
  • 50 | 51 |

    Basic updates

    52 |
  • 53 |
54 | 59 |
60 |
61 | 62 | 63 |
64 |
65 |
66 | 67 | PREMIUM PLAN 68 | 69 |
70 |
71 | $ 72 | 9.99 73 | /mo 74 |
75 |
76 |
77 |
    78 |
  • 79 | 80 |

    Full dashboard access

    81 |
  • 82 |
  • 83 | 84 |

    Priority support

    85 |
  • 86 |
  • 87 | 88 |

    Premium features

    89 |
  • 90 |
  • 91 | 92 |

    Unlimited updates

    93 |
  • 94 |
  • 95 | 96 |

    API access

    97 |
  • 98 |
  • 99 | 100 |

    Daily backups

    101 |
  • 102 |
103 |
104 | {% if user.is_authenticated %} 105 | 106 | MANAGE SUBSCRIPTION 107 | 108 | {% else %} 109 | 110 | START NOW 111 | 112 | {% endif %} 113 |
114 |
115 |
116 | 117 | 118 |
119 |
120 |
121 | 122 | ENTERPRISE PLAN 123 | 124 |
125 |
126 | $ 127 | 49.99 128 | /mo 129 |
130 |
131 |
132 |
    133 |
  • 134 | 135 |

    Everything in Premium

    136 |
  • 137 |
  • 138 | 139 |

    24/7 support

    140 |
  • 141 |
  • 142 | 143 |

    Advanced customization

    144 |
  • 145 |
  • 146 | 147 |

    Guaranteed SLA

    148 |
  • 149 |
150 | 155 |
156 |
157 |
158 |
159 |
160 | 161 | 162 |
163 |
164 |
165 |

166 | FREQUENTLY ASKED QUESTIONS 167 |

168 |
169 |
170 |
171 | 172 | Can I change plans at any time? 173 |
174 |
175 | Yes, you can upgrade or downgrade your plan at any time. Changes will be applied at your next billing cycle. 176 |
177 |
178 | 179 |
180 |
181 | 182 | Do you offer a trial period? 183 |
184 |
185 | Yes, we offer a 14-day trial for all plans. No credit card required to start. 186 |
187 |
188 | 189 |
190 |
191 | 192 | What payment methods do you accept? 193 |
194 |
195 | We accept all major credit cards through Stripe. We also offer invoicing for enterprise plans. 196 |
197 |
198 |
199 |
200 |
201 |
202 | 203 | 204 |
205 |
206 |

207 | READY TO GET STARTED? 208 | Start building your SaaS today. 209 |

210 |
211 |
212 | 213 | START FREE 214 | 215 |
216 |
217 |
218 |
219 | {% endblock %} -------------------------------------------------------------------------------- /templates/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block title %}{% endblock %} 7 | 8 | 9 | 10 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% block extra_head %}{% endblock %} 39 | 40 | 41 | 42 | 143 | 144 | 145 | {% if messages %} 146 |
147 | {% for message in messages %} 148 | 172 | {% endfor %} 173 |
174 | {% endif %} 175 | 176 | 177 |
178 | {% block content %}{% endblock %} 179 |
180 | 181 | 182 | 240 | 241 | {% block extra_js %}{% endblock %} 242 | 243 | -------------------------------------------------------------------------------- /templates/dashboard/base.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | {% block title %}Dashboard - SaaS Platform{% endblock %} 7 | 8 | 9 | 10 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | {% block extra_head %}{% endblock %} 39 | 40 | 41 |
42 | 43 | 103 | 104 | 105 | 154 | 155 |
156 |
157 | 164 |
165 | 166 |
167 |
168 | {% block dashboard_content %}{% endblock %} 169 |
170 |
171 |
172 |
173 | 174 | {% block extra_js %}{% endblock %} 175 | 176 | -------------------------------------------------------------------------------- /templates/landing/home.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}SaaS Platform | Home{% endblock %} 4 | 5 | {% block content %} 6 | 7 |
8 |
9 |
10 |
11 |

12 | YOUR SAAS 13 | SOLUTION 14 |

15 |

16 | Launch your SaaS application in minutes, not months. Focus on what really matters: your business. 17 |

18 | 26 |
27 |
28 |
29 |
30 | Dashboard preview 31 |
32 |
33 |
34 |
35 |
36 |
37 | 38 | 39 |
40 |
41 |

42 | CUTTING-EDGE TECHNOLOGIES INCLUDED IN THIS BOILERPLATE 43 |

44 |
45 |
46 |
47 | 48 |
49 | Django 50 |
51 |
52 |
53 | 54 |
55 | Tailwind CSS 56 |
57 |
58 |
59 | 60 |
61 | PostgreSQL 62 |
63 |
64 |
65 | 66 |
67 | Alpine.js 68 |
69 |
70 |
71 | 72 |
73 | Stripe 74 |
75 |
76 |
77 |
78 | 79 | 80 |
81 |
82 |
83 |

FEATURES

84 |

85 | EVERYTHING YOU NEED FOR YOUR SAAS 86 |

87 |

88 | Built with best practices and modern technologies to help you launch your product quickly. 89 |

90 |
91 | 92 |
93 |
94 | {% for feature in features %} 95 |
96 |
97 |
98 |
99 | {% if feature.icon == 'chart-bar' %} 100 | 101 | {% elif feature.icon == 'shield-check' %} 102 | 103 | {% elif feature.icon == 'device-mobile' %} 104 | 105 | {% elif feature.icon == 'credit-card' %} 106 | 107 | {% else %} 108 | 109 | {% endif %} 110 |
111 |
112 |
113 |

{{ feature.title }}

114 |

{{ feature.description }}

115 |
116 |
117 |
118 | {% endfor %} 119 |
120 |
121 |
122 |
123 | 124 | 125 |
126 |
127 |
128 |

PRICING

129 |

130 | SIMPLE AND TRANSPARENT PLANS 131 |

132 |

133 | Start for free and scale as your business grows. 134 |

135 |
136 | 137 |
138 |
139 | 140 |
141 |
142 |

FREE

143 |

Perfect for testing and getting started.

144 |

145 | $0 146 | /month 147 |

148 | 149 | START FREE 150 | 151 |
152 |
153 |

WHAT'S INCLUDED

154 |
    155 |
  • 156 | 157 | Basic dashboard 158 |
  • 159 |
  • 160 | 161 | 1 user 162 |
  • 163 |
  • 164 | 165 | Maximum 100 records 166 |
  • 167 |
  • 168 | 169 | Community support 170 |
  • 171 |
172 |
173 |
174 | 175 | 176 |
177 |
178 |
179 |

PRO

180 | POPULAR 181 |
182 |

Everything you need for a growing business.

183 |

184 | ${{ pricing.monthly_price }} 185 | /month 186 |

187 | 188 | START 14-DAY TRIAL 189 | 190 |
191 |
192 |

WHAT'S INCLUDED

193 |
    194 | {% for feature in pricing.features %} 195 |
  • 196 | 197 | {{ feature }} 198 |
  • 199 | {% endfor %} 200 |
201 |
202 |
203 | 204 | 205 |
206 |
207 |

ENTERPRISE

208 |

Custom solutions for large businesses.

209 |

210 | Custom 211 |

212 | 213 | CONTACT US 214 | 215 |
216 |
217 |

WHAT'S INCLUDED

218 |
    219 |
  • 220 | 221 | All Pro features 222 |
  • 223 |
  • 224 | 225 | Unlimited users 226 |
  • 227 |
  • 228 | 229 | Guaranteed SLA 230 |
  • 231 |
  • 232 | 233 | 24/7 priority support 234 |
  • 235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 | 243 | 244 |
245 |
246 |
247 |

TESTIMONIALS

248 |

249 | WHAT OUR CUSTOMERS SAY 250 |

251 |

252 | Discover why businesses trust our SaaS solution. 253 |

254 |
255 | 256 |
257 |
258 |
259 |
260 |
261 | 262 |
263 |
264 |
265 |

John Smith

266 |

CEO, Tech Solutions

267 |
268 |
269 |
270 |

This SaaS template saved us months of development. Now we can focus on what really matters: our customers.

271 |
272 |
273 | 274 |
275 |
276 |
277 |
278 | 279 |
280 |
281 |
282 |

Sarah Johnson

283 |

CTO, Innovate Inc.

284 |
285 |
286 |
287 |

The Stripe integration and secure authentication are exactly what we needed. We implemented them and they worked flawlessly.

288 |
289 |
290 | 291 |
292 |
293 |
294 |
295 | 296 |
297 |
298 |
299 |

Michael Brown

300 |

Founder, StartupLabs

301 |
302 |
303 |
304 |

The subscription system works perfectly. Our customers can sign up and start using our platform in minutes.

305 |
306 |
307 |
308 |
309 |
310 | 311 | 312 |
313 |
314 |

315 | READY TO GET STARTED? 316 | Begin your free trial today. 317 |

318 |
319 |
320 | 321 | START FREE 322 | 323 |
324 |
325 | 326 | SEE FEATURES 327 | 328 |
329 |
330 |
331 |
332 | {% endblock %} 333 | 334 | {% block extra_js %} 335 | 338 | {% endblock %} -------------------------------------------------------------------------------- /templates/landing/features.html: -------------------------------------------------------------------------------- 1 | {% extends "base.html" %} 2 | 3 | {% block title %}Features - SaaS Platform{% endblock %} 4 | 5 | {% block content %} 6 | 7 |
8 |
9 |
10 |

11 | MAIN FEATURES 12 |

13 |

14 | Everything you need to launch your SaaS application, ready to use and customize. 15 |

16 |
17 |
18 |
19 | 20 | 21 |
22 |
23 |
24 |

ALL IN ONE PLACE

25 |

26 | A COMPLETE FRAMEWORK FOR YOUR SAAS 27 |

28 |

29 | Built with Django, Tailwind CSS, Alpine.js and HTMX to provide the best development and user experience. 30 |

31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 |
39 | 40 |
41 |
42 |

Robust Authentication

43 |

44 | Complete authentication system with registration, login, email verification, password recovery, and two-factor authentication. 45 |

46 |
47 |
    48 |
  • 49 | 50 |

    51 | Registration and login 52 |

    53 |
  • 54 |
  • 55 | 56 |

    57 | Email verification 58 |

    59 |
  • 60 |
  • 61 | 62 |
    63 |

    64 | Two-factor authentication (2FA) 65 |

    66 | COMING SOON 67 |
    68 |
  • 69 |
70 |
71 |
72 | 73 | 74 |
75 |
76 |
77 | 78 |
79 |
80 |

Payments & Subscriptions

81 |

82 | Complete integration with Stripe to manage payments and subscriptions securely and easily. 83 |

84 |
85 |
    86 |
  • 87 | 88 |

    89 | Stripe integration 90 |

    91 |
  • 92 |
  • 93 | 94 |
    95 |

    96 | PayPal integration 97 |

    98 | COMING SOON 99 |
    100 |
  • 101 |
  • 102 | 103 |

    104 | Automatic billing and receipts 105 |

    106 |
  • 107 |
108 |
109 |
110 | 111 | 112 |
113 |
114 |
115 | 116 |
117 |
118 |

Responsive Design

119 |

120 | Modern and adaptive interface that works perfectly on mobile devices, tablets, and desktop. 121 |

122 |
123 |
    124 |
  • 125 | 126 |

    127 | Developed with Tailwind CSS 128 |

    129 |
  • 130 |
  • 131 | 132 |

    133 | Mobile-first approach 134 |

    135 |
  • 136 |
  • 137 | 138 |

    139 | Reusable UI components 140 |

    141 |
  • 142 |
143 |
144 |
145 | 146 | 147 |
148 |
149 |
150 | 151 |
152 |
153 |

Intuitive Dashboard

154 |

155 | Easy-to-use control panel with all the important metrics for your users and for you. 156 |

157 |
158 |
    159 |
  • 160 | 161 |

    162 | User management area 163 |

    164 |
  • 165 |
  • 166 | 167 |
    168 |

    169 | Real-time charts and statistics 170 |

    171 | COMING SOON 172 |
    173 |
  • 174 |
  • 175 | 176 |

    177 | Subscription management 178 |

    179 |
  • 180 |
181 |
182 |
183 | 184 | 185 |
186 |
187 |
188 | 189 |
190 |
191 |

Team Management

192 |

193 | Multi-user functionality to collaborate with your team or clients efficiently. 194 |

195 |
196 |
    197 |
  • 198 | 199 |
    200 |

    201 | Team creation and management 202 |

    203 | COMING SOON 204 |
    205 |
  • 206 |
  • 207 | 208 |
    209 |

    210 | Role-based permission control 211 |

    212 | COMING SOON 213 |
    214 |
  • 215 |
  • 216 | 217 |
    218 |

    219 | Email invitations 220 |

    221 | COMING SOON 222 |
    223 |
  • 224 |
225 |
226 |
227 | 228 | 229 |
230 |
231 |
232 | 233 |
234 |
235 |

API & Integrations

236 |

237 | Complete RESTful API and hooks to integrate with other services and platforms. 238 |

239 |
240 |
    241 |
  • 242 | 243 |
    244 |

    245 | RESTful API with Django REST framework 246 |

    247 | COMING SOON 248 |
    249 |
  • 250 |
  • 251 | 252 |
    253 |

    254 | API documentation with Swagger/OpenAPI 255 |

    256 | COMING SOON 257 |
    258 |
  • 259 |
  • 260 | 261 |
    262 |

    263 | Webhooks for external integrations 264 |

    265 | COMING SOON 266 |
    267 |
  • 268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 | 276 | 277 |
278 |
279 |
280 |

TECHNICAL STACK

281 |

282 | BUILT WITH MODERN TECHNOLOGIES 283 |

284 |

285 | We use the best tools and frameworks to provide an exceptional development and user experience. 286 |

287 |
288 | 289 |
290 |
291 |
292 |
293 |
294 | 295 |
296 |

Django + Django REST Framework

297 |
298 |
299 |

300 | High-level Python web framework that encourages rapid development and clean, pragmatic design. 301 |

302 |
    303 |
  • 304 | 305 | MVT (Model-View-Template) Architecture 306 |
  • 307 |
  • 308 | 309 | Powerful and flexible ORM 310 |
  • 311 |
  • 312 | 313 |
    314 | Django REST Framework for APIs 315 | COMING SOON 316 |
    317 |
  • 318 |
319 |
320 |
321 | 322 |
323 |
324 |
325 | 326 |
327 |

Tailwind CSS

328 |
329 |
330 |

331 | Utility-first CSS framework for rapidly building custom designs without leaving your HTML. 332 |

333 |
    334 |
  • 335 | 336 | Utility classes for rapid development 337 |
  • 338 |
  • 339 | 340 | Highly customizable and extensible 341 |
  • 342 |
  • 343 | 344 | Responsive design by default 345 |
  • 346 |
347 |
348 |
349 | 350 |
351 |
352 |
353 | 354 |
355 |

Alpine.js

356 |
357 |
358 |

359 | Minimal JavaScript framework for adding behavior to your markup with a simple declarative syntax. 360 |

361 |
    362 |
  • 363 | 364 | Declarative and reactive syntax 365 |
  • 366 |
  • 367 | 368 | Lightweight (less than 8kb gzipped) 369 |
  • 370 |
  • 371 | 372 | Integrates perfectly with Tailwind 373 |
  • 374 |
375 |
376 |
377 | 378 |
379 |
380 |
381 | 382 |
383 |

HTMX

384 |
385 |
386 |

387 | Library that allows you to access modern browser features directly from HTML, without the need for JavaScript. 388 |

389 |
    390 |
  • 391 | 392 |
    393 | AJAX, CSS Transitions, WebSockets and Server Sent Events 394 | PARTIAL IMPLEMENTATION 395 |
    396 |
  • 397 |
  • 398 | 399 | Small (~14KB min.gz'd) 400 |
  • 401 |
  • 402 | 403 |
    404 | Interactive experiences without writing JavaScript 405 | IN DEVELOPMENT 406 |
    407 |
  • 408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 | 416 | 417 | 418 | 419 |
420 |
421 |

422 | READY TO GET STARTED? 423 | Begin your free trial today. 424 |

425 |
426 |
427 | 428 | START FREE 429 | 430 |
431 |
432 | 433 | SEE PRICING 434 | 435 |
436 |
437 |
438 |
439 | {% endblock %} --------------------------------------------------------------------------------