Jun 28 - Update tenant self-service signup, trial period enforcement

This commit is contained in:
2026-06-28 18:51:04 -04:00
parent e6b5be8c4d
commit 54fa5b8c87
7 changed files with 415 additions and 30 deletions
+158
View File
@@ -0,0 +1,158 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Create your workspace — JQC</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<style>
body { background: #f6f7f9; }
.signup-card { max-width: 520px; margin: 2.5rem auto; }
.brand { font-weight: 700; color: #1a56db; font-size: 1.1rem; }
.subdomain-preview { font-size: .8rem; color: #6b7280; margin-top: .25rem; }
</style>
</head>
<body>
<div class="signup-card px-3">
<div class="text-center mb-4 mt-4">
<div class="brand mb-1"><i class="bi bi-check2-circle me-1"></i>Janitorial QC</div>
<h1 class="h4 mb-0">Create your workspace</h1>
<p class="text-muted" style="font-size:.9rem;">14-day free trial · No credit card required</p>
</div>
{% with messages = get_flashed_messages(with_categories=true) %}
{% for cat, msg in messages %}
<div class="alert alert-{{ 'danger' if cat == 'danger' else cat }} alert-dismissible fade show" role="alert">
{{ msg }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endwith %}
<div class="card border-0 shadow-sm">
<div class="card-body p-4">
<form method="post" novalidate>
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="mb-3">
<label class="form-label fw-semibold" style="font-size:.875rem;">Company Name</label>
<input type="text" name="company_name" id="company_name"
class="form-control {{ 'is-invalid' if form.company_name.errors }}"
value="{{ form.company_name.data or '' }}"
placeholder="Acme Cleaning Co." autofocus>
{% for e in form.company_name.errors %}
<div class="invalid-feedback">{{ e }}</div>
{% endfor %}
</div>
<div class="mb-3">
<label class="form-label fw-semibold" style="font-size:.875rem;">Your Full Name</label>
<input type="text" name="full_name"
class="form-control {{ 'is-invalid' if form.full_name.errors }}"
value="{{ form.full_name.data or '' }}"
placeholder="Jane Smith">
{% for e in form.full_name.errors %}
<div class="invalid-feedback">{{ e }}</div>
{% endfor %}
</div>
<div class="mb-3">
<label class="form-label fw-semibold" style="font-size:.875rem;">Work Email</label>
<input type="email" name="email"
class="form-control {{ 'is-invalid' if form.email.errors }}"
value="{{ form.email.data or '' }}"
placeholder="you@company.com">
{% for e in form.email.errors %}
<div class="invalid-feedback">{{ e }}</div>
{% endfor %}
</div>
<div class="mb-3">
<label class="form-label fw-semibold" style="font-size:.875rem;">Subdomain</label>
<div class="input-group">
<input type="text" name="subdomain" id="subdomain"
class="form-control {{ 'is-invalid' if form.subdomain.errors }}"
value="{{ form.subdomain.data or '' }}"
placeholder="acme"
pattern="[a-z0-9][a-z0-9\-]{0,30}[a-z0-9]"
style="text-transform:lowercase;">
<span class="input-group-text text-muted" style="font-size:.85rem;">.{{ base_domain }}</span>
{% for e in form.subdomain.errors %}
<div class="invalid-feedback">{{ e }}</div>
{% endfor %}
</div>
<div class="subdomain-preview" id="subdomain_preview"></div>
</div>
<div class="mb-3">
<label class="form-label fw-semibold" style="font-size:.875rem;">Plan</label>
<select name="plan" class="form-select {{ 'is-invalid' if form.plan.errors }}">
{% for value, label in form.plan.choices %}
<option value="{{ value }}" {{ 'selected' if form.plan.data == value }}>{{ label }}</option>
{% endfor %}
</select>
{% for e in form.plan.errors %}
<div class="invalid-feedback">{{ e }}</div>
{% endfor %}
</div>
<div class="mb-3">
<label class="form-label fw-semibold" style="font-size:.875rem;">Password</label>
<input type="password" name="password"
class="form-control {{ 'is-invalid' if form.password.errors }}"
placeholder="Minimum 8 characters">
{% for e in form.password.errors %}
<div class="invalid-feedback">{{ e }}</div>
{% endfor %}
</div>
<div class="mb-4">
<label class="form-label fw-semibold" style="font-size:.875rem;">Confirm Password</label>
<input type="password" name="confirm"
class="form-control {{ 'is-invalid' if form.confirm.errors }}">
{% for e in form.confirm.errors %}
<div class="invalid-feedback">{{ e }}</div>
{% endfor %}
</div>
<button type="submit" class="btn btn-primary w-100">
<i class="bi bi-rocket-takeoff me-1"></i> Create Workspace
</button>
</form>
</div>
</div>
<p class="text-center text-muted mt-3" style="font-size:.82rem;">
Already have a workspace?
<a href="https://{{ base_domain }}">Sign in</a>
</p>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<script>
// Auto-suggest subdomain from company name
var BASE_DOMAIN = {{ base_domain | tojson }};
document.getElementById('company_name').addEventListener('input', function () {
var sub = document.getElementById('subdomain');
if (sub.dataset.userEdited) return;
var slug = this.value.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '')
.slice(0, 32);
sub.value = slug;
updatePreview(slug);
});
document.getElementById('subdomain').addEventListener('input', function () {
this.dataset.userEdited = '1';
this.value = this.value.toLowerCase().replace(/[^a-z0-9-]/g, '');
updatePreview(this.value);
});
function updatePreview(slug) {
var el = document.getElementById('subdomain_preview');
el.textContent = slug ? 'Your workspace: ' + slug + '.' + BASE_DOMAIN : '';
}
updatePreview(document.getElementById('subdomain').value);
</script>
</body>
</html>