vibetuner 2.26.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vibetuner/__init__.py +2 -0
- vibetuner/__main__.py +4 -0
- vibetuner/cli/__init__.py +141 -0
- vibetuner/cli/run.py +160 -0
- vibetuner/cli/scaffold.py +187 -0
- vibetuner/config.py +143 -0
- vibetuner/context.py +28 -0
- vibetuner/frontend/__init__.py +107 -0
- vibetuner/frontend/deps.py +41 -0
- vibetuner/frontend/email.py +45 -0
- vibetuner/frontend/hotreload.py +13 -0
- vibetuner/frontend/lifespan.py +37 -0
- vibetuner/frontend/middleware.py +151 -0
- vibetuner/frontend/oauth.py +196 -0
- vibetuner/frontend/routes/__init__.py +12 -0
- vibetuner/frontend/routes/auth.py +156 -0
- vibetuner/frontend/routes/debug.py +414 -0
- vibetuner/frontend/routes/health.py +37 -0
- vibetuner/frontend/routes/language.py +43 -0
- vibetuner/frontend/routes/meta.py +55 -0
- vibetuner/frontend/routes/user.py +94 -0
- vibetuner/frontend/templates.py +176 -0
- vibetuner/logging.py +87 -0
- vibetuner/models/__init__.py +14 -0
- vibetuner/models/blob.py +89 -0
- vibetuner/models/email_verification.py +84 -0
- vibetuner/models/mixins.py +76 -0
- vibetuner/models/oauth.py +57 -0
- vibetuner/models/registry.py +15 -0
- vibetuner/models/types.py +16 -0
- vibetuner/models/user.py +91 -0
- vibetuner/mongo.py +33 -0
- vibetuner/paths.py +250 -0
- vibetuner/services/__init__.py +0 -0
- vibetuner/services/blob.py +175 -0
- vibetuner/services/email.py +50 -0
- vibetuner/tasks/__init__.py +0 -0
- vibetuner/tasks/lifespan.py +28 -0
- vibetuner/tasks/worker.py +15 -0
- vibetuner/templates/email/magic_link.html.jinja +17 -0
- vibetuner/templates/email/magic_link.txt.jinja +5 -0
- vibetuner/templates/frontend/base/favicons.html.jinja +1 -0
- vibetuner/templates/frontend/base/footer.html.jinja +3 -0
- vibetuner/templates/frontend/base/header.html.jinja +0 -0
- vibetuner/templates/frontend/base/opengraph.html.jinja +7 -0
- vibetuner/templates/frontend/base/skeleton.html.jinja +45 -0
- vibetuner/templates/frontend/debug/collections.html.jinja +105 -0
- vibetuner/templates/frontend/debug/components/debug_nav.html.jinja +55 -0
- vibetuner/templates/frontend/debug/index.html.jinja +85 -0
- vibetuner/templates/frontend/debug/info.html.jinja +258 -0
- vibetuner/templates/frontend/debug/users.html.jinja +139 -0
- vibetuner/templates/frontend/debug/version.html.jinja +55 -0
- vibetuner/templates/frontend/email/magic_link.txt.jinja +5 -0
- vibetuner/templates/frontend/email_sent.html.jinja +83 -0
- vibetuner/templates/frontend/index.html.jinja +20 -0
- vibetuner/templates/frontend/lang/select.html.jinja +4 -0
- vibetuner/templates/frontend/login.html.jinja +89 -0
- vibetuner/templates/frontend/meta/browserconfig.xml.jinja +10 -0
- vibetuner/templates/frontend/meta/robots.txt.jinja +3 -0
- vibetuner/templates/frontend/meta/site.webmanifest.jinja +7 -0
- vibetuner/templates/frontend/meta/sitemap.xml.jinja +6 -0
- vibetuner/templates/frontend/user/edit.html.jinja +86 -0
- vibetuner/templates/frontend/user/profile.html.jinja +157 -0
- vibetuner/templates/markdown/.placeholder +0 -0
- vibetuner/templates.py +146 -0
- vibetuner/time.py +57 -0
- vibetuner/versioning.py +12 -0
- vibetuner-2.26.6.dist-info/METADATA +241 -0
- vibetuner-2.26.6.dist-info/RECORD +71 -0
- vibetuner-2.26.6.dist-info/WHEEL +4 -0
- vibetuner-2.26.6.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
{% extends "base/skeleton.html.jinja" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}
|
|
4
|
+
Debug - User Impersonation
|
|
5
|
+
{% endblock title %}
|
|
6
|
+
{% block body %}
|
|
7
|
+
<div class="container mx-auto px-4 py-8 max-w-4xl">
|
|
8
|
+
<!-- Header -->
|
|
9
|
+
<header class="mb-8">
|
|
10
|
+
<h1 class="text-4xl font-bold text-base-content mb-2">User Impersonation</h1>
|
|
11
|
+
<p class="text-base-content/70">Switch between users in debug mode for testing purposes</p>
|
|
12
|
+
<div class="alert alert-warning mt-4">
|
|
13
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
14
|
+
class="stroke-current shrink-0 h-6 w-6"
|
|
15
|
+
fill="none"
|
|
16
|
+
viewBox="0 0 24 24">
|
|
17
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
18
|
+
</svg>
|
|
19
|
+
<span>⚠️ Debug mode only - This feature is disabled in production</span>
|
|
20
|
+
</div>
|
|
21
|
+
</header>
|
|
22
|
+
<!-- Current User Status -->
|
|
23
|
+
{% if current_user_id %}
|
|
24
|
+
<div class="card bg-primary text-primary-content shadow-xl mb-6">
|
|
25
|
+
<div class="card-body">
|
|
26
|
+
<h2 class="card-title">
|
|
27
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
28
|
+
class="h-6 w-6"
|
|
29
|
+
fill="none"
|
|
30
|
+
viewBox="0 0 24 24"
|
|
31
|
+
stroke="currentColor">
|
|
32
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z" />
|
|
33
|
+
</svg>
|
|
34
|
+
Currently Impersonating
|
|
35
|
+
</h2>
|
|
36
|
+
<p class="opacity-90">
|
|
37
|
+
User ID: <code>{{ current_user_id }}</code>
|
|
38
|
+
</p>
|
|
39
|
+
<div class="card-actions justify-end">
|
|
40
|
+
<form method="post" action="{{ url_for('debug.stop_impersonation') }}">
|
|
41
|
+
<button type="submit" class="btn btn-secondary btn-sm">Stop Impersonation</button>
|
|
42
|
+
</form>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
{% else %}
|
|
47
|
+
<div class="alert alert-info mb-6">
|
|
48
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
49
|
+
class="stroke-current shrink-0 h-6 w-6"
|
|
50
|
+
fill="none"
|
|
51
|
+
viewBox="0 0 24 24">
|
|
52
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
53
|
+
</svg>
|
|
54
|
+
<span>No user currently impersonated. Select a user below to impersonate.</span>
|
|
55
|
+
</div>
|
|
56
|
+
{% endif %}
|
|
57
|
+
<!-- Users List -->
|
|
58
|
+
<div class="card bg-base-100 shadow-xl">
|
|
59
|
+
<div class="card-body">
|
|
60
|
+
<h2 class="card-title text-2xl mb-4">
|
|
61
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
62
|
+
class="h-6 w-6"
|
|
63
|
+
fill="none"
|
|
64
|
+
viewBox="0 0 24 24"
|
|
65
|
+
stroke="currentColor">
|
|
66
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197m13.5-9a2.25 2.25 0 11-4.5 0 2.25 2.25 0 014.5 0z" />
|
|
67
|
+
</svg>
|
|
68
|
+
Available Users ({{ users | length }})
|
|
69
|
+
</h2>
|
|
70
|
+
{% if users %}
|
|
71
|
+
<div class="overflow-x-auto">
|
|
72
|
+
<table class="table table-zebra w-full">
|
|
73
|
+
<thead>
|
|
74
|
+
<tr>
|
|
75
|
+
<th>Email</th>
|
|
76
|
+
<th>Name</th>
|
|
77
|
+
<th>ID</th>
|
|
78
|
+
<th>Created</th>
|
|
79
|
+
<th class="text-center">Actions</th>
|
|
80
|
+
</tr>
|
|
81
|
+
</thead>
|
|
82
|
+
<tbody>
|
|
83
|
+
{% for user in users %}
|
|
84
|
+
<tr class="{% if current_user_id == user.id|string %}bg-primary/20{% endif %}">
|
|
85
|
+
<td>
|
|
86
|
+
<div class="flex items-center gap-2">
|
|
87
|
+
{% if current_user_id == user.id|string %}<div class="badge badge-primary badge-xs">CURRENT</div>{% endif %}
|
|
88
|
+
<span class="font-mono text-sm">{{ user.email or "No email" }}</span>
|
|
89
|
+
</div>
|
|
90
|
+
</td>
|
|
91
|
+
<td>
|
|
92
|
+
<span class="font-medium">{{ user.name or "No name" }}</span>
|
|
93
|
+
</td>
|
|
94
|
+
<td>
|
|
95
|
+
<code class="text-xs bg-base-200 px-2 py-1 rounded">{{ user.id }}</code>
|
|
96
|
+
</td>
|
|
97
|
+
<td>
|
|
98
|
+
<span class="text-sm text-base-content/70">
|
|
99
|
+
{% if user.db_insert_dt %}
|
|
100
|
+
{{ user.db_insert_dt.strftime("%Y-%m-%d %H:%M") }}
|
|
101
|
+
{% else %}
|
|
102
|
+
Unknown
|
|
103
|
+
{% endif %}
|
|
104
|
+
</span>
|
|
105
|
+
</td>
|
|
106
|
+
<td class="text-center">
|
|
107
|
+
{% if current_user_id != user.id|string %}
|
|
108
|
+
<form method="post"
|
|
109
|
+
action="{{ url_for('debug.impersonate_user', user_id=user.id) }}"
|
|
110
|
+
class="inline">
|
|
111
|
+
<button type="submit" class="btn btn-outline btn-sm">Impersonate</button>
|
|
112
|
+
</form>
|
|
113
|
+
{% else %}
|
|
114
|
+
<span class="text-primary font-medium">Active</span>
|
|
115
|
+
{% endif %}
|
|
116
|
+
</td>
|
|
117
|
+
</tr>
|
|
118
|
+
{% endfor %}
|
|
119
|
+
</tbody>
|
|
120
|
+
</table>
|
|
121
|
+
</div>
|
|
122
|
+
{% else %}
|
|
123
|
+
<div class="alert alert-warning">
|
|
124
|
+
<svg xmlns="http://www.w3.org/2000/svg"
|
|
125
|
+
class="stroke-current shrink-0 h-6 w-6"
|
|
126
|
+
fill="none"
|
|
127
|
+
viewBox="0 0 24 24">
|
|
128
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.464 0L4.35 16.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
129
|
+
</svg>
|
|
130
|
+
<span>No users found in the database.</span>
|
|
131
|
+
</div>
|
|
132
|
+
{% endif %}
|
|
133
|
+
</div>
|
|
134
|
+
</div>
|
|
135
|
+
<!-- Debug Navigation Footer -->
|
|
136
|
+
{% include "debug/components/debug_nav.html.jinja" %}
|
|
137
|
+
|
|
138
|
+
</div>
|
|
139
|
+
{% endblock body %}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{% extends "base/skeleton.html.jinja" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}
|
|
4
|
+
Version Information - Debug
|
|
5
|
+
{% endblock title %}
|
|
6
|
+
{% block body %}
|
|
7
|
+
<div class="container mx-auto px-4 py-8 max-w-4xl">
|
|
8
|
+
<!-- Header -->
|
|
9
|
+
<header class="mb-8">
|
|
10
|
+
<h1 class="text-4xl font-bold text-base-content mb-2">Version Information</h1>
|
|
11
|
+
<p class="text-base-content/70">Application version and build details</p>
|
|
12
|
+
</header>
|
|
13
|
+
<!-- Version Details Card -->
|
|
14
|
+
<div class="card bg-base-100 shadow-xl border border-base-200">
|
|
15
|
+
<div class="card-body">
|
|
16
|
+
<h2 class="card-title text-primary mb-6">Build Information</h2>
|
|
17
|
+
<div class="grid md:grid-cols-2 gap-6">
|
|
18
|
+
<div class="flex items-center gap-4">
|
|
19
|
+
<div class="w-12 h-12 bg-primary/10 rounded-full flex items-center justify-center">
|
|
20
|
+
<svg class="w-6 h-6 text-primary"
|
|
21
|
+
fill="none"
|
|
22
|
+
stroke="currentColor"
|
|
23
|
+
viewBox="0 0 24 24">
|
|
24
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A1.994 1.994 0 013 12V7a4 4 0 014-4z">
|
|
25
|
+
</path>
|
|
26
|
+
</svg>
|
|
27
|
+
</div>
|
|
28
|
+
<div>
|
|
29
|
+
<h3 class="font-semibold text-base-content">Version</h3>
|
|
30
|
+
<p class="text-2xl font-bold text-primary">{{ version }}</p>
|
|
31
|
+
</div>
|
|
32
|
+
</div>
|
|
33
|
+
<div class="flex items-center gap-4">
|
|
34
|
+
<div class="w-12 h-12 bg-secondary/10 rounded-full flex items-center justify-center">
|
|
35
|
+
<svg class="w-6 h-6 text-secondary"
|
|
36
|
+
fill="none"
|
|
37
|
+
stroke="currentColor"
|
|
38
|
+
viewBox="0 0 24 24">
|
|
39
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 4V2a1 1 0 011-1h4a1 1 0 011 1v2m3 6V8a1 1 0 00-1-1H9a1 1 0 00-1 1v2m0 0v10a1 1 0 001 1h6a1 1 0 001-1V10H8z">
|
|
40
|
+
</path>
|
|
41
|
+
</svg>
|
|
42
|
+
</div>
|
|
43
|
+
<div>
|
|
44
|
+
<h3 class="font-semibold text-base-content">Version Hash</h3>
|
|
45
|
+
<p class="text-sm font-mono text-secondary bg-secondary/10 px-2 py-1 rounded">{{ v_hash }}</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
<!-- Debug Navigation Footer -->
|
|
52
|
+
{% include "debug/components/debug_nav.html.jinja" %}
|
|
53
|
+
|
|
54
|
+
</div>
|
|
55
|
+
{% endblock body %}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
{% extends "base/skeleton.html.jinja" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}
|
|
4
|
+
{{ _("Check Your Email") }}
|
|
5
|
+
{% endblock title %}
|
|
6
|
+
{% block body %}
|
|
7
|
+
<div class="min-h-screen bg-gradient-to-br from-primary to-secondary flex items-center justify-center p-4">
|
|
8
|
+
<div class="card w-full max-w-md bg-base-100 shadow-2xl backdrop-blur-sm bg-opacity-95">
|
|
9
|
+
<div class="card-body text-center">
|
|
10
|
+
<!-- Success Icon -->
|
|
11
|
+
<div class="flex justify-center mb-6">
|
|
12
|
+
<div class="bg-success/10 rounded-full p-4 animate-pulse">
|
|
13
|
+
<svg class="w-12 h-12 text-success"
|
|
14
|
+
fill="none"
|
|
15
|
+
stroke="currentColor"
|
|
16
|
+
viewBox="0 0 24 24">
|
|
17
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
18
|
+
</svg>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
<!-- Header -->
|
|
22
|
+
<h2 class="text-2xl font-bold text-base-content mb-4">{{ _("Check Your Email") }}</h2>
|
|
23
|
+
<!-- Main Message -->
|
|
24
|
+
<div class="space-y-4 mb-6">
|
|
25
|
+
<p class="text-base-content/80">{{ _("We've sent a secure login link to") }}</p>
|
|
26
|
+
<div class="bg-base-200/50 rounded-lg p-3 border border-base-300/50">
|
|
27
|
+
<p class="font-semibold text-primary break-all">{{ email }}</p>
|
|
28
|
+
</div>
|
|
29
|
+
<div class="alert alert-info bg-info/10 border-info/20 text-left">
|
|
30
|
+
<svg class="w-5 h-5 text-info shrink-0"
|
|
31
|
+
fill="none"
|
|
32
|
+
stroke="currentColor"
|
|
33
|
+
viewBox="0 0 24 24">
|
|
34
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
35
|
+
</svg>
|
|
36
|
+
<div class="text-sm">
|
|
37
|
+
<p class="font-medium text-info">{{ _("Quick! Link expires in 15 minutes") }}</p>
|
|
38
|
+
<p class="text-info/80">{{ _("Click the link in the email to sign in instantly.") }}</p>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
</div>
|
|
42
|
+
<!-- Troubleshooting -->
|
|
43
|
+
<div class="bg-warning/5 border border-warning/20 rounded-lg p-4 mb-6">
|
|
44
|
+
<div class="flex items-start gap-3">
|
|
45
|
+
<svg class="w-5 h-5 text-warning shrink-0 mt-0.5"
|
|
46
|
+
fill="none"
|
|
47
|
+
stroke="currentColor"
|
|
48
|
+
viewBox="0 0 24 24">
|
|
49
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 15.5c-.77.833.192 2.5 1.732 2.5z" />
|
|
50
|
+
</svg>
|
|
51
|
+
<div class="text-left">
|
|
52
|
+
<p class="font-medium text-warning text-sm">{{ _("Don't see the email?") }}</p>
|
|
53
|
+
<p class="text-warning/80 text-sm">{{ _("Check your spam folder or try signing in again.") }}</p>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<!-- Action Buttons -->
|
|
58
|
+
<div class="space-y-3">
|
|
59
|
+
<!-- Try Again Button -->
|
|
60
|
+
<a href="{{ url_for("auth_login") }}{% if next %}?next={{ next }}{% endif %}"
|
|
61
|
+
class="btn btn-outline btn-block gap-2 hover:btn-primary transition-all duration-300 hover:scale-[1.02]">
|
|
62
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
63
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
|
|
64
|
+
</svg>
|
|
65
|
+
{{ _("Try Again") }}
|
|
66
|
+
</a>
|
|
67
|
+
<!-- Back to Home -->
|
|
68
|
+
<a href="{{ url_for('homepage') }}"
|
|
69
|
+
class="btn btn-ghost btn-block gap-2 text-base-content/70 hover:text-base-content transition-all duration-300">
|
|
70
|
+
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
71
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6" />
|
|
72
|
+
</svg>
|
|
73
|
+
{{ _("Back to Home") }}
|
|
74
|
+
</a>
|
|
75
|
+
</div>
|
|
76
|
+
<!-- Security Note -->
|
|
77
|
+
<div class="mt-6 pt-4 border-t border-base-300/30">
|
|
78
|
+
<p class="text-xs text-base-content/50">🔒 {{ _("This link is secure and can only be used once") }}</p>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
{% endblock body %}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{% set BODY_CLASS = "min-h-screen bg-gradient-to-br from-slate-50 via-blue-50 to-indigo-100 flex flex-col justify-between" %}
|
|
2
|
+
{% extends "base/skeleton.html.jinja" %}
|
|
3
|
+
|
|
4
|
+
{% block body %}
|
|
5
|
+
<!-- Main Content -->
|
|
6
|
+
<div class="flex-1 flex items-center justify-center px-6">
|
|
7
|
+
<div class="text-center max-w-4xl mx-auto">
|
|
8
|
+
<!-- Main Title -->
|
|
9
|
+
<h1 class="text-6xl md:text-8xl font-bold text-slate-800 mb-6 tracking-tight">{{ project_name }}</h1>
|
|
10
|
+
<!-- Subtitle -->
|
|
11
|
+
<p class="text-xl md:text-2xl text-slate-600 font-light max-w-2xl mx-auto leading-relaxed">
|
|
12
|
+
{{ project_description }}
|
|
13
|
+
</p>
|
|
14
|
+
<!-- Decorative element -->
|
|
15
|
+
<div class="mt-12 flex justify-center">
|
|
16
|
+
<div class="w-24 h-1 bg-gradient-to-r from-blue-400 to-indigo-500 rounded-full"></div>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
{% endblock body %}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{% extends "base/skeleton.html.jinja" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}
|
|
4
|
+
{{ _("Sign In") }}
|
|
5
|
+
{% endblock title %}
|
|
6
|
+
{% block body %}
|
|
7
|
+
<div class="min-h-screen bg-gradient-to-br from-primary to-secondary flex items-center justify-center p-4">
|
|
8
|
+
<div class="card w-full max-w-md bg-base-100 shadow-2xl backdrop-blur-sm bg-opacity-95">
|
|
9
|
+
<div class="card-body">
|
|
10
|
+
<!-- Header -->
|
|
11
|
+
<div class="text-center mb-8">
|
|
12
|
+
<h1 class="text-3xl font-bold text-base-content mb-2">{{ _("Welcome Back") }}</h1>
|
|
13
|
+
<p class="text-base-content/70">{{ _("Choose your preferred sign-in method") }}</p>
|
|
14
|
+
</div>
|
|
15
|
+
<!-- OAuth Providers -->
|
|
16
|
+
{% if has_oauth and providers %}
|
|
17
|
+
<div class="space-y-3 mb-6">
|
|
18
|
+
{% for provider in providers %}
|
|
19
|
+
{% set login_url = url_for('login_with_' + provider) %}
|
|
20
|
+
{% if next %}
|
|
21
|
+
{% set login_url = login_url ~ '?next=' ~ next %}
|
|
22
|
+
{% endif %}
|
|
23
|
+
<a href="{{ login_url }}"
|
|
24
|
+
class="btn btn-outline btn-block justify-start gap-3 hover:btn-primary group transition-all duration-300 hover:scale-[1.02]">
|
|
25
|
+
<span>{{ _("Continue with %(provider)s") | format(provider=provider.title()) }}</span>
|
|
26
|
+
</a>
|
|
27
|
+
{% endfor %}
|
|
28
|
+
</div>
|
|
29
|
+
{% endif %}
|
|
30
|
+
<!-- Divider -->
|
|
31
|
+
{% if has_oauth and providers and has_email %}<div class="divider text-base-content/50">{{ _("OR") }}</div>{% endif %}
|
|
32
|
+
<!-- Email Magic Link -->
|
|
33
|
+
{% if has_email %}
|
|
34
|
+
<div class="bg-base-200/50 rounded-2xl p-6 border border-base-300/50">
|
|
35
|
+
<form method="post"
|
|
36
|
+
class="space-y-4"
|
|
37
|
+
action="{{ url_for('send_magic_link') }}">
|
|
38
|
+
{% if next %}<input type="hidden" name="next" value="{{ next }}" />{% endif %}
|
|
39
|
+
<div class="form-control">
|
|
40
|
+
<label class="label" for="email">
|
|
41
|
+
<span class="label-text font-medium">{{ _("Email Address") }}</span>
|
|
42
|
+
</label>
|
|
43
|
+
<div class="relative">
|
|
44
|
+
<input type="email"
|
|
45
|
+
id="email"
|
|
46
|
+
name="email"
|
|
47
|
+
class="input input-bordered w-full pr-12 focus:input-primary transition-all duration-300"
|
|
48
|
+
placeholder="{{ _('Enter your email address') }}"
|
|
49
|
+
required
|
|
50
|
+
autocomplete="email" />
|
|
51
|
+
<div class="absolute inset-y-0 right-0 flex items-center pr-3">
|
|
52
|
+
<svg class="w-5 h-5 text-base-content/40"
|
|
53
|
+
fill="none"
|
|
54
|
+
stroke="currentColor"
|
|
55
|
+
viewBox="0 0 24 24">
|
|
56
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 8l7.89 4.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
57
|
+
</svg>
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
<button type="submit"
|
|
62
|
+
class="btn btn-primary btn-block gap-2 group hover:scale-[1.02] transition-all duration-300">
|
|
63
|
+
<svg class="w-5 h-5 group-hover:rotate-12 transition-transform"
|
|
64
|
+
fill="none"
|
|
65
|
+
stroke="currentColor"
|
|
66
|
+
viewBox="0 0 24 24">
|
|
67
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
68
|
+
</svg>
|
|
69
|
+
<span>{{ _("Send Magic Link") }}</span>
|
|
70
|
+
</button>
|
|
71
|
+
<div class="alert alert-info bg-info/10 border-info/20">
|
|
72
|
+
<svg class="w-5 h-5 text-info shrink-0"
|
|
73
|
+
fill="none"
|
|
74
|
+
stroke="currentColor"
|
|
75
|
+
viewBox="0 0 24 24">
|
|
76
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
77
|
+
</svg>
|
|
78
|
+
<div class="text-sm">
|
|
79
|
+
<p class="font-medium text-info">{{ _("No password required!") }}</p>
|
|
80
|
+
<p class="text-info/80">{{ _("We'll send you a secure link to sign in instantly.") }}</p>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</form>
|
|
84
|
+
</div>
|
|
85
|
+
{% endif %}
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
{% endblock body %}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
{% extends "base/skeleton.html.jinja" %}
|
|
2
|
+
|
|
3
|
+
{% block title %}
|
|
4
|
+
{{ _("Edit Profile") }}
|
|
5
|
+
{% endblock title %}
|
|
6
|
+
{% block body %}
|
|
7
|
+
<div class="min-h-screen bg-gray-50">
|
|
8
|
+
<div class="container mx-auto px-4 py-8">
|
|
9
|
+
<div class="max-w-2xl mx-auto">
|
|
10
|
+
<!-- Page Header -->
|
|
11
|
+
<div class="mb-8">
|
|
12
|
+
<h1 class="text-3xl font-bold text-gray-900">{{ _("Edit Profile") }}</h1>
|
|
13
|
+
<p class="text-gray-600 mt-2">{{ _("Update your account information and preferences") }}</p>
|
|
14
|
+
</div>
|
|
15
|
+
<!-- Edit Form -->
|
|
16
|
+
<div class="bg-white shadow-lg rounded-lg p-6">
|
|
17
|
+
<form method="post" action="{{ url_for('user_edit_submit') }}">
|
|
18
|
+
<div class="space-y-6">
|
|
19
|
+
<!-- Name Field -->
|
|
20
|
+
<div>
|
|
21
|
+
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">{{ _("Display Name") }}</label>
|
|
22
|
+
<input type="text"
|
|
23
|
+
id="name"
|
|
24
|
+
name="name"
|
|
25
|
+
value="{{ user.name or '' }}"
|
|
26
|
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
|
27
|
+
required />
|
|
28
|
+
<p class="text-sm text-gray-600 mt-1">{{ _("This name will be displayed throughout the application") }}</p>
|
|
29
|
+
</div>
|
|
30
|
+
<!-- Language Preference -->
|
|
31
|
+
<div>
|
|
32
|
+
<label for="language" class="block text-sm font-medium text-gray-700 mb-2">{{ _("Preferred Language") }}</label>
|
|
33
|
+
<select id="language"
|
|
34
|
+
name="language"
|
|
35
|
+
class="w-full px-4 py-2 border border-gray-300 rounded-md focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
|
|
36
|
+
<option value="">{{ _("Use browser default") }}</option>
|
|
37
|
+
{% for lang_code, lang_name in locale_names.items() %}
|
|
38
|
+
<option value="{{ lang_code }}"
|
|
39
|
+
{% if current_language and current_language == lang_code %}selected{% endif %}>
|
|
40
|
+
{{ lang_name }}
|
|
41
|
+
</option>
|
|
42
|
+
{% endfor %}
|
|
43
|
+
</select>
|
|
44
|
+
<p class="text-sm text-gray-600 mt-1">{{ _("This will override browser language detection") }}</p>
|
|
45
|
+
</div>
|
|
46
|
+
<!-- Email (read-only) -->
|
|
47
|
+
<div>
|
|
48
|
+
<label for="email" class="block text-sm font-medium text-gray-700 mb-2">{{ _("Email Address") }}</label>
|
|
49
|
+
<input type="email"
|
|
50
|
+
id="email"
|
|
51
|
+
value="{{ user.email or '' }}"
|
|
52
|
+
class="w-full px-4 py-2 bg-gray-50 border border-gray-300 rounded-md text-gray-600"
|
|
53
|
+
readonly />
|
|
54
|
+
<p class="text-sm text-gray-600 mt-1">{{ _("Email cannot be changed and is managed through your OAuth provider") }}</p>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
<!-- Form Actions -->
|
|
58
|
+
<div class="flex justify-between items-center mt-8 pt-6 border-t border-gray-200">
|
|
59
|
+
<a href="{{ url_for('user_profile') }}" class="btn btn-ghost">{{ _("Cancel") }}</a>
|
|
60
|
+
<button type="submit" class="btn btn-primary">{{ _("Save Changes") }}</button>
|
|
61
|
+
</div>
|
|
62
|
+
</form>
|
|
63
|
+
</div>
|
|
64
|
+
<!-- Additional Info Card -->
|
|
65
|
+
<div class="mt-6 bg-blue-50 border border-blue-200 rounded-lg p-4">
|
|
66
|
+
<div class="flex items-start">
|
|
67
|
+
<div class="flex-shrink-0">
|
|
68
|
+
<svg class="h-5 w-5 text-blue-600"
|
|
69
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
70
|
+
viewBox="0 0 20 20"
|
|
71
|
+
fill="currentColor">
|
|
72
|
+
<path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clip-rule="evenodd" />
|
|
73
|
+
</svg>
|
|
74
|
+
</div>
|
|
75
|
+
<div class="ml-3">
|
|
76
|
+
<h3 class="text-sm font-medium text-blue-900">{{ _("About Language Preferences") }}</h3>
|
|
77
|
+
<p class="mt-1 text-sm text-blue-700">
|
|
78
|
+
{{ _("Your language preference will take priority over URL parameters, cookies, and browser settings. Leave empty to use automatic detection.") }}
|
|
79
|
+
</p>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
{% endblock body %}
|