Offline-first photo capture app for Nextcloud with: - Camera capture with continuous mode (auto-reopens after each photo) - File browser with fullscreen image gallery, swipe navigation, and rename - Upload queue with background sync engine - Admin panel for Nextcloud user management - Service worker for offline-first caching (v13) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
209 lines
4.6 KiB
HTML
209 lines
4.6 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Login - NextSnap{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="container">
|
|
<div class="login-container">
|
|
<div class="login-header">
|
|
<div class="app-icon">📷</div>
|
|
<h1 class="login-title">NextSnap</h1>
|
|
<p class="login-subtitle">Offline-first photo capture for Nextcloud</p>
|
|
</div>
|
|
|
|
<form id="login-form" class="login-form">
|
|
<div class="form-group">
|
|
<label for="username">Nextcloud Username</label>
|
|
<input
|
|
type="text"
|
|
id="username"
|
|
name="username"
|
|
class="form-input"
|
|
placeholder="Enter your username"
|
|
autocomplete="username"
|
|
autocapitalize="none"
|
|
autocorrect="off"
|
|
autofocus
|
|
required
|
|
>
|
|
<span class="field-error" id="username-error"></span>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password">Password</label>
|
|
<input
|
|
type="password"
|
|
id="password"
|
|
name="password"
|
|
class="form-input"
|
|
placeholder="Enter your password"
|
|
autocomplete="current-password"
|
|
required
|
|
>
|
|
<span class="field-error" id="password-error"></span>
|
|
</div>
|
|
|
|
<div id="error-message" class="error-message hidden"></div>
|
|
|
|
<button type="submit" class="btn btn-primary btn-login" id="login-btn">
|
|
<span id="login-btn-text">Login</span>
|
|
<span id="login-btn-loading" class="hidden">
|
|
<span class="spinner"></span> Logging in...
|
|
</span>
|
|
</button>
|
|
</form>
|
|
|
|
<div class="login-footer">
|
|
<p class="help-text">
|
|
<strong>Tip:</strong> Use your Nextcloud credentials to login
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block extra_css %}
|
|
<style>
|
|
body {
|
|
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 100%);
|
|
}
|
|
|
|
.login-container {
|
|
max-width: 400px;
|
|
margin: 2rem auto;
|
|
padding: 2rem;
|
|
background: var(--bg-secondary);
|
|
border-radius: 16px;
|
|
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
|
}
|
|
|
|
.login-header {
|
|
text-align: center;
|
|
margin-bottom: 2rem;
|
|
}
|
|
|
|
.app-icon {
|
|
font-size: 4rem;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.login-title {
|
|
font-size: 2rem;
|
|
font-weight: 700;
|
|
margin-bottom: 0.5rem;
|
|
}
|
|
|
|
.login-subtitle {
|
|
color: var(--text-secondary);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.login-form {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.form-group {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 0.5rem;
|
|
}
|
|
|
|
.form-group label {
|
|
font-weight: 600;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.form-input {
|
|
width: 100%;
|
|
padding: 0.75rem;
|
|
font-size: 1rem;
|
|
border: 2px solid var(--bg-tertiary);
|
|
border-radius: 8px;
|
|
background: var(--bg-primary);
|
|
color: var(--text-primary);
|
|
transition: border-color 0.2s;
|
|
}
|
|
|
|
.form-input:focus {
|
|
outline: none;
|
|
border-color: var(--accent);
|
|
}
|
|
|
|
.form-input.error {
|
|
border-color: var(--error);
|
|
}
|
|
|
|
.field-error {
|
|
color: var(--error);
|
|
font-size: 0.85rem;
|
|
min-height: 1.2rem;
|
|
}
|
|
|
|
.error-message {
|
|
padding: 0.75rem;
|
|
background: var(--error);
|
|
color: white;
|
|
border-radius: 8px;
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.btn-login {
|
|
margin-top: 0.5rem;
|
|
position: relative;
|
|
}
|
|
|
|
.btn-login:disabled {
|
|
opacity: 0.6;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.spinner {
|
|
display: inline-block;
|
|
width: 14px;
|
|
height: 14px;
|
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
|
border-top-color: white;
|
|
border-radius: 50%;
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes spin {
|
|
to { transform: rotate(360deg); }
|
|
}
|
|
|
|
.login-footer {
|
|
margin-top: 2rem;
|
|
padding-top: 1.5rem;
|
|
border-top: 1px solid var(--bg-tertiary);
|
|
}
|
|
|
|
.help-text {
|
|
text-align: center;
|
|
color: var(--text-secondary);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* Mobile responsiveness */
|
|
@media (max-width: 480px) {
|
|
.login-container {
|
|
margin: 1rem;
|
|
padding: 1.5rem;
|
|
}
|
|
|
|
.app-icon {
|
|
font-size: 3rem;
|
|
}
|
|
|
|
.login-title {
|
|
font-size: 1.75rem;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|
|
|
|
{% block extra_js %}
|
|
<script src="{{ url_for('static', filename='js/auth.js') }}"></script>
|
|
{% endblock %}
|