507 lines
21 KiB
HTML
507 lines
21 KiB
HTML
{% extends "base.html" %}
|
|
{% load i18n %}
|
|
|
|
{% block title %}{{ title }}{% endblock %}
|
|
|
|
{% block customCSS %}
|
|
<style>
|
|
/* Card Hover Effects */
|
|
.form-card {
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
}
|
|
|
|
.form-card:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
/* Button Hover Effects */
|
|
.btn-action {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-action:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
.btn-primary {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-primary:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(157, 34, 53, 0.4);
|
|
}
|
|
|
|
.btn-secondary {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-secondary:hover {
|
|
background-color: rgba(157, 34, 53, 0.05);
|
|
border-color: rgba(157, 34, 53, 0.3);
|
|
}
|
|
|
|
.btn-danger {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.btn-danger:hover {
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 4px 12px rgba(239, 68, 68, 0.4);
|
|
}
|
|
|
|
/* Input Focus Animation */
|
|
.form-input {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.form-input:focus {
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
/* Custom Form Fields */
|
|
.form-field {
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.form-field:focus-within {
|
|
transform: translateY(-1px);
|
|
}
|
|
|
|
.form-label {
|
|
color: #374151;
|
|
font-weight: 600;
|
|
font-size: 0.875rem;
|
|
display: block;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
padding: 0.75rem 1rem;
|
|
border: 2px solid #e5e7eb;
|
|
border-radius: 0.75rem;
|
|
background-color: #ffffff;
|
|
color: #111827;
|
|
font-size: 0.95rem;
|
|
transition: all 0.2s ease;
|
|
outline: none;
|
|
}
|
|
|
|
.form-input:focus {
|
|
border-color: #9d2235;
|
|
box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
|
|
}
|
|
|
|
.form-input::placeholder {
|
|
color: #9ca3af;
|
|
}
|
|
|
|
.form-input:disabled {
|
|
background-color: #f9fafb;
|
|
color: #9ca3af;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.form-select {
|
|
width: 100%;
|
|
padding: 0.75rem 1rem;
|
|
border: 2px solid #e5e7eb;
|
|
border-radius: 0.75rem;
|
|
background-color: #ffffff;
|
|
color: #111827;
|
|
font-size: 0.95rem;
|
|
transition: all 0.2s ease;
|
|
outline: none;
|
|
cursor: pointer;
|
|
appearance: none;
|
|
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");
|
|
background-position: right 0.75rem center;
|
|
background-repeat: no-repeat;
|
|
background-size: 1.5rem 1.5rem;
|
|
padding-right: 2.5rem;
|
|
}
|
|
|
|
.form-select:focus {
|
|
border-color: #9d2235;
|
|
box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
|
|
}
|
|
|
|
.form-select:disabled {
|
|
background-color: #f9fafb;
|
|
color: #9ca3af;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.form-textarea {
|
|
width: 100%;
|
|
padding: 0.75rem 1rem;
|
|
border: 2px solid #e5e7eb;
|
|
border-radius: 0.75rem;
|
|
background-color: #ffffff;
|
|
color: #111827;
|
|
font-size: 0.95rem;
|
|
transition: all 0.2s ease;
|
|
outline: none;
|
|
resize: vertical;
|
|
min-height: 100px;
|
|
}
|
|
|
|
.form-textarea:focus {
|
|
border-color: #9d2235;
|
|
box-shadow: 0 0 0 3px rgba(157, 34, 53, 0.1);
|
|
}
|
|
|
|
.form-helptext {
|
|
margin-top: 0.375rem;
|
|
font-size: 0.8rem;
|
|
color: #6b7280;
|
|
}
|
|
|
|
.form-error {
|
|
margin-top: 0.375rem;
|
|
font-size: 0.8rem;
|
|
color: #dc2626;
|
|
font-weight: 500;
|
|
}
|
|
|
|
.input-wrapper {
|
|
position: relative;
|
|
}
|
|
|
|
.input-icon {
|
|
position: absolute;
|
|
left: 1rem;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
color: #9ca3af;
|
|
pointer-events: none;
|
|
}
|
|
|
|
.form-input.with-icon {
|
|
padding-left: 2.75rem;
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="max-w-4xl mx-auto py-6 px-4 sm:px-6 lg:px-8">
|
|
|
|
<!-- Breadcrumb -->
|
|
<nav aria-label="breadcrumb" class="mb-6">
|
|
<ol class="flex items-center gap-2 text-sm">
|
|
<li>
|
|
<a href="{% url 'settings_list' %}"
|
|
class="text-gray-500 hover:text-temple-red transition-colors flex items-center gap-1">
|
|
<i data-lucide="settings" class="w-4 h-4"></i>
|
|
{% trans "Settings" %}
|
|
</a>
|
|
</li>
|
|
{% if setting %}
|
|
<li class="text-gray-400">/</li>
|
|
<li>
|
|
<a href="{% url 'settings_detail' setting.pk %}"
|
|
class="text-gray-500 hover:text-temple-red transition-colors">
|
|
{{ setting.key }}
|
|
</a>
|
|
</li>
|
|
<li class="text-gray-400">/</li>
|
|
<li class="text-temple-red font-semibold">{% trans "Update" %}</li>
|
|
{% else %}
|
|
<li class="text-gray-400">/</li>
|
|
<li class="text-temple-red font-semibold">{% trans "Create" %}</li>
|
|
{% endif %}
|
|
</ol>
|
|
</nav>
|
|
|
|
<!-- Page Header -->
|
|
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4 mb-6">
|
|
<div>
|
|
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 mb-2 flex items-center gap-3">
|
|
<div class="w-12 h-12 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
{% if setting %}
|
|
<i data-lucide="edit-3" class="w-6 h-6 text-temple-red"></i>
|
|
{% else %}
|
|
<i data-lucide="plus-circle" class="w-6 h-6 text-temple-red"></i>
|
|
{% endif %}
|
|
</div>
|
|
{{ title }}
|
|
</h1>
|
|
<p class="text-gray-500 text-sm sm:text-base">
|
|
{% if setting %}
|
|
{% trans "Update integration setting details" %}
|
|
{% else %}
|
|
{% trans "Create a new integration setting" %}
|
|
{% endif %}
|
|
</p>
|
|
</div>
|
|
<div class="flex gap-3">
|
|
{% if setting %}
|
|
<a href="{% url 'settings_detail' setting.pk %}"
|
|
class="btn-secondary inline-flex items-center gap-2 px-5 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300">
|
|
<i data-lucide="eye" class="w-5 h-5"></i>
|
|
{% trans "View Details" %}
|
|
</a>
|
|
<a href="{% url 'settings_delete' setting.pk %}"
|
|
class="btn-danger inline-flex items-center gap-2 px-5 py-3 bg-red-500 text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
|
|
<i data-lucide="trash-2" class="w-5 h-5"></i>
|
|
{% trans "Delete" %}
|
|
</a>
|
|
{% endif %}
|
|
<a href="{% url 'settings_list' %}"
|
|
class="btn-secondary inline-flex items-center gap-2 px-5 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300">
|
|
<i data-lucide="arrow-left" class="w-5 h-5"></i>
|
|
{% trans "Back to List" %}
|
|
</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Form Card -->
|
|
<div class="form-card bg-white rounded-2xl shadow-sm border border-gray-200 overflow-hidden">
|
|
<!-- Card Header -->
|
|
<div class="px-6 py-4 border-b border-gray-100 bg-gradient-to-r from-gray-50 to-white">
|
|
<div class="flex items-center gap-3">
|
|
<div class="w-10 h-10 rounded-xl bg-temple-red/10 flex items-center justify-center">
|
|
<i data-lucide="file-key" class="w-5 h-5 text-temple-red"></i>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-lg font-bold text-gray-900">{% trans "Setting Information" %}</h2>
|
|
<p class="text-sm text-gray-500">{% trans "Fill in the details below" %}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card Body -->
|
|
<div class="p-6 sm:p-8">
|
|
|
|
<!-- Non-Field Errors -->
|
|
{% if form.non_field_errors %}
|
|
<div class="mb-6 p-4 bg-red-50 border border-red-200 rounded-xl">
|
|
<div class="flex items-start gap-3">
|
|
<div class="w-10 h-10 rounded-full bg-red-100 flex items-center justify-center flex-shrink-0">
|
|
<i data-lucide="alert-circle" class="w-5 h-5 text-red-500"></i>
|
|
</div>
|
|
<div class="flex-1">
|
|
<h3 class="text-base font-bold text-red-700 mb-1">{% trans "Error" %}</h3>
|
|
{% for error in form.non_field_errors %}
|
|
<p class="text-sm text-red-600">{{ error }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Messages -->
|
|
{% if messages %}
|
|
<div class="space-y-3 mb-6">
|
|
{% for message in messages %}
|
|
<div class="flex items-start gap-3 p-4 rounded-xl border
|
|
{% if message.tags == 'error' %}bg-red-50 border-red-200 text-red-700
|
|
{% elif message.tags == 'success' %}bg-green-50 border-green-200 text-green-700
|
|
{% elif message.tags == 'warning' %}bg-yellow-50 border-yellow-200 text-yellow-700
|
|
{% else %}bg-blue-50 border-blue-200 text-blue-700{% endif %}">
|
|
<div class="w-10 h-10 rounded-full flex items-center justify-center flex-shrink-0
|
|
{% if message.tags == 'error' %}bg-red-100
|
|
{% elif message.tags == 'success' %}bg-green-100
|
|
{% elif message.tags == 'warning' %}bg-yellow-100
|
|
{% else %}bg-blue-100{% endif %}">
|
|
<i data-lucide="{% if message.tags == 'error' %}alert-circle
|
|
{% elif message.tags == 'success' %}check-circle
|
|
{% elif message.tags == 'warning' %}alert-triangle
|
|
{% else %}info{% endif %}"
|
|
class="w-5 h-5
|
|
{% if message.tags == 'error' %}text-red-500
|
|
{% elif message.tags == 'success' %}text-green-500
|
|
{% elif message.tags == 'warning' %}text-yellow-600
|
|
{% else %}text-blue-500{% endif %}">
|
|
</i>
|
|
</div>
|
|
<div class="flex-1">
|
|
<p class="text-sm font-medium">{{ message }}</p>
|
|
</div>
|
|
<button type="button"
|
|
onclick="this.parentElement.remove()"
|
|
class="text-current hover:opacity-70 transition-opacity">
|
|
<i data-lucide="x" class="w-4 h-4"></i>
|
|
</button>
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Form -->
|
|
<form method="post" novalidate id="settings-form" class="space-y-6">
|
|
{% csrf_token %}
|
|
|
|
<!-- Name Field -->
|
|
<div class="form-field">
|
|
<label for="{{ form.name.id_for_label }}" class="form-label flex items-center gap-2">
|
|
<i data-lucide="tag" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Name" %}
|
|
{% if form.name.field.required %}<span class="text-temple-red">*</span>{% endif %}
|
|
</label>
|
|
<input type="{{ form.name.field.widget.input_type }}"
|
|
name="{{ form.name.name }}"
|
|
id="{{ form.name.id_for_label }}"
|
|
class="form-input"
|
|
placeholder="{% trans 'Enter a friendly name for this setting' %}"
|
|
{% if form.name.value %}value="{{ form.name.value }}"{% endif %}>
|
|
{% if form.name.help_text %}
|
|
<p class="form-helptext">{{ form.name.help_text }}</p>
|
|
{% endif %}
|
|
{% for error in form.name.errors %}
|
|
<p class="form-error">{{ error }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Key Field -->
|
|
<div class="form-field">
|
|
<label for="{{ form.key.id_for_label }}" class="form-label flex items-center gap-2">
|
|
<i data-lucide="key" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Key" %}
|
|
{% if form.key.field.required %}<span class="text-temple-red">*</span>{% endif %}
|
|
</label>
|
|
<input type="{{ form.key.field.widget.input_type }}"
|
|
name="{{ form.key.name }}"
|
|
id="{{ form.key.id_for_label }}"
|
|
class="form-input"
|
|
placeholder="{% trans 'e.g., ZOOM_API_KEY' %}"
|
|
{% if form.key.value %}value="{{ form.key.value }}"{% endif %}>
|
|
{% if form.key.help_text %}
|
|
<p class="form-helptext">{{ form.key.help_text }}</p>
|
|
{% endif %}
|
|
{% for error in form.key.errors %}
|
|
<p class="form-error">{{ error }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Value Field -->
|
|
<div class="form-field">
|
|
<label for="{{ form.value.id_for_label }}" class="form-label flex items-center gap-2">
|
|
<i data-lucide="lock" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Value" %}
|
|
{% if form.value.field.required %}<span class="text-temple-red">*</span>{% endif %}
|
|
</label>
|
|
<input type="{{ form.value.field.widget.input_type }}"
|
|
name="{{ form.value.name }}"
|
|
id="{{ form.value.id_for_label }}"
|
|
class="form-input"
|
|
placeholder="{% trans 'Enter the secret value' %}"
|
|
{% if form.value.value %}value="{{ form.value.value }}"{% endif %}>
|
|
{% if form.value.help_text %}
|
|
<p class="form-helptext">{{ form.value.help_text }}</p>
|
|
{% endif %}
|
|
{% for error in form.value.errors %}
|
|
<p class="form-error">{{ error }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Category Field -->
|
|
<div class="form-field">
|
|
<label for="{{ form.category.id_for_label }}" class="form-label flex items-center gap-2">
|
|
<i data-lucide="folder" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Category" %}
|
|
{% if form.category.field.required %}<span class="text-temple-red">*</span>{% endif %}
|
|
</label>
|
|
{{ form.category.as_widget }}
|
|
{% if form.category.help_text %}
|
|
<p class="form-helptext">{{ form.category.help_text }}</p>
|
|
{% endif %}
|
|
{% for error in form.category.errors %}
|
|
<p class="form-error">{{ error }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Description Field -->
|
|
<div class="form-field">
|
|
<label for="{{ form.description.id_for_label }}" class="form-label flex items-center gap-2">
|
|
<i data-lucide="align-left" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Description" %}
|
|
{% if form.description.field.required %}<span class="text-temple-red">*</span>{% endif %}
|
|
</label>
|
|
<textarea name="{{ form.description.name }}"
|
|
id="{{ form.description.id_for_label }}"
|
|
class="form-textarea"
|
|
placeholder="{% trans 'Add a description...' %}">{% if form.description.value %}{{ form.description.value }}{% endif %}</textarea>
|
|
{% if form.description.help_text %}
|
|
<p class="form-helptext">{{ form.description.help_text }}</p>
|
|
{% endif %}
|
|
{% for error in form.description.errors %}
|
|
<p class="form-error">{{ error }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
|
|
<!-- Is Encrypted Field (if exists) -->
|
|
{% if form.is_encrypted %}
|
|
<div class="form-field">
|
|
<label for="{{ form.is_encrypted.id_for_label }}" class="form-label flex items-center gap-2">
|
|
<i data-lucide="shield-check" class="w-4 h-4 text-gray-400"></i>
|
|
{% trans "Encrypt Value" %}
|
|
</label>
|
|
<div class="flex items-center gap-3">
|
|
{{ form.is_encrypted.as_widget }}
|
|
<span class="text-sm text-gray-600">{% trans "Store this value encrypted in the database" %}</span>
|
|
</div>
|
|
{% if form.is_encrypted.help_text %}
|
|
<p class="form-helptext">{{ form.is_encrypted.help_text }}</p>
|
|
{% endif %}
|
|
{% for error in form.is_encrypted.errors %}
|
|
<p class="form-error">{{ error }}</p>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Form Actions -->
|
|
<div class="flex flex-col sm:flex-row gap-3 pt-6 border-t border-gray-200 mt-6">
|
|
<button form="settings-form" type="submit"
|
|
class="btn-primary flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py-3 bg-temple-red text-white rounded-xl font-semibold shadow-lg hover:shadow-xl">
|
|
<i data-lucide="save" class="w-5 h-5"></i>
|
|
{{ button_text }}
|
|
</button>
|
|
<a href="{% url 'settings_list' %}"
|
|
class="btn-secondary flex-1 sm:flex-none inline-flex items-center justify-center gap-2 px-8 py-3 border-2 border-gray-200 text-gray-700 rounded-xl font-semibold hover:border-gray-300">
|
|
<i data-lucide="x" class="w-5 h-5"></i>
|
|
{% trans "Cancel" %}
|
|
</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block customJS %}
|
|
<script>
|
|
// Initialize Lucide icons
|
|
lucide.createIcons();
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
// Auto-adjust textarea height
|
|
const textarea = document.querySelector('textarea[name="description"]');
|
|
if (textarea) {
|
|
textarea.addEventListener('input', function() {
|
|
this.style.height = 'auto';
|
|
this.style.height = this.scrollHeight + 'px';
|
|
});
|
|
// Set initial height
|
|
textarea.style.height = textarea.scrollHeight + 'px';
|
|
}
|
|
|
|
// Apply Tailwind classes to category select
|
|
const categorySelect = document.querySelector('select[name="category"]');
|
|
if (categorySelect) {
|
|
categorySelect.classList.add('form-select');
|
|
}
|
|
|
|
// Apply Tailwind classes to is_encrypted checkbox (if exists)
|
|
const encryptedCheckbox = document.querySelector('input[name="is_encrypted"]');
|
|
if (encryptedCheckbox) {
|
|
encryptedCheckbox.classList.add('w-5', 'h-5', 'text-temple-red', 'rounded', 'border-gray-300', 'focus:ring-temple-red', 'focus:border-temple-red');
|
|
}
|
|
});
|
|
</script>
|
|
{% endblock %}
|