webcodelab commited on
Commit
f472dc2
ยท
verified ยท
1 Parent(s): 03f8899

Update app/templates/dashboard.html

Browse files
Files changed (1) hide show
  1. app/templates/dashboard.html +255 -25
app/templates/dashboard.html CHANGED
@@ -1,34 +1,264 @@
1
  {% extends "base.html" %}
2
- {% block title %}Dashboard{% endblock %}
 
 
 
 
 
 
 
 
 
 
3
  {% block content %}
4
- <h2>Welcome, {{ user.username }}!</h2>
5
- <p>This is your protected dashboard.</p>
6
- <h5>Your API Key</h5>
7
- <div class="input-group mb-3">
8
- <input type="text" class="form-control" id="apiKey" value="{{ user.api_key }}" readonly>
9
- <button class="btn btn-outline-secondary" type="button" onclick="copyApiKey()">Copy</button>
10
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
 
12
- <form method="POST" action="{{ url_for('auth.regenerate_api_key') }}">
13
- <button class="btn btn-warning">Regenerate API Key</button>
14
- </form>
15
- <div>
16
- <p><a href="{{ url_for('auth.docs') }}">๐Ÿ“˜ View API Documentation</a></p>
 
 
 
 
 
 
 
 
17
 
18
- </div>
19
- <!-- <p><a href="{{ url_for('auth.docs') }}">๐Ÿ“˜ View API Documentation</a></p> -->
 
20
 
21
- <a href="{{ url_for('auth.logout') }}" class="btn btn-danger">Logout</a>
 
 
22
 
23
- <script>
24
- function copyApiKey() {
25
- const input = document.getElementById("apiKey");
26
- input.select();
27
- input.setSelectionRange(0, 99999); // For mobile
28
- document.execCommand("copy");
29
- alert("API key copied to clipboard!");
30
- }
31
- </script>
32
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  {% endblock %}
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  {% extends "base.html" %}
2
+ {% block title %}Dashboard โ€“ Resume Ranker API{% endblock %}
3
+
4
+ {% block extra_head %}
5
+ {#
6
+ No extra head elements are strictly needed here if base.html links
7
+ to your main CSS (e.g., login.css, which will be expanded with dashboard styles).
8
+ If you prefer to keep dashboard-specific CSS separate, you'd link it here:
9
+ <link rel="stylesheet" href="{{ url_for('static', filename='css/dashboard.css') }}">
10
+ #}
11
+ {% endblock %}
12
+
13
  {% block content %}
14
+ <div class="dashboard-wrapper">
15
+ <aside class="sidebar" id="sidebar">
16
+ <div class="sidebar-header">
17
+ <div class="logo">ResumeRanker<span style="color:var(--accent)">.API</span></div>
18
+ <button class="close-sidebar" id="closeSidebar" aria-label="Close menu">
19
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-x"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
20
+ </button>
21
+ </div>
22
+ <nav class="sidebar-nav">
23
+ <a href="{{ url_for('auth.dashboard') }}" class="nav-item active">
24
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-grid"><rect x="3" y="3" width="7" height="7"></rect><rect x="14" y="3" width="7" height="7"></rect><rect x="14" y="14" width="7" height="7"></rect><rect x="3" y="14" width="7" height="7"></rect></svg>
25
+ <span>Dashboard</span>
26
+ </a>
27
+ <a href="{{ url_for('auth.api_keys') }}" class="nav-item">
28
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-key"><path d="M19 10H5a2 2 0 0 0-2 2v6a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-6a2 2 0 0 0-2-2z"></path><circle cx="12" cy="16" r="2"></circle><path d="M15 10V4.5a2.5 2.5 0 0 0-5 0V10"></path></svg>
29
+ <span>My API Keys</span>
30
+ </a>
31
+ <a href="{{ url_for('auth.resume_rankings') }}" class="nav-item">
32
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-award"><circle cx="12" cy="8" r="7"></circle><polyline points="8.21 13.89 7 23 12 20 17 23 15.79 13.89"></polyline></svg>
33
+ <span>Resume Rankings</span>
34
+ </a>
35
+ <a href="{{ url_for('auth.upload_resumes') }}" class="nav-item">
36
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-upload-cloud"><polyline points="16 16 12 12 8 16"></polyline><line x1="12" y1="12" x2="12" y2="21"></line><path d="M20.39 18.39A5 5 0 0 0 18 9h-1.26A8 8 0 1 0 3 16.3"></path><polyline points="16 16 12 12 8 16"></polyline></svg>
37
+ <span>Upload Resumes</span>
38
+ </a>
39
+ <a href="{{ url_for('auth.settings') }}" class="nav-item">
40
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-settings"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
41
+ <span>Settings</span>
42
+ </a>
43
+ <a href="{{ url_for('auth.logout') }}" class="nav-item logout-item">
44
+ <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-log-out"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="17 16 22 12 17 8"></polyline><line x1="22" y1="12" x2="10" y2="12"></line></svg>
45
+ <span>Logout</span>
46
+ </a>
47
+ </nav>
48
+ </aside>
49
 
50
+ <div class="main-content-area" id="mainContentArea">
51
+ <header class="dashboard-header">
52
+ <button class="hamburger-dashboard" id="hamburgerDashboard" aria-label="Open sidebar menu">
53
+ <span></span><span></span><span></span>
54
+ </button>
55
+ <div class="user-settings">
56
+ <span class="user-name">{{ user.username }}</span>
57
+ <div class="user-avatar">
58
+ <img src="https://ui-avatars.com/api/?name={{ user.username | urlencode }}&background=2563eb&color=fff&size=30" alt="User Avatar">
59
+ </div>
60
+ <button class="theme-toggle" id="themeToggleDashboard" aria-label="Toggle dark mode">๐ŸŒ™</button>
61
+ </div>
62
+ </header>
63
 
64
+ <div class="content-padding">
65
+ <h1 class="welcome-title">Welcome, {{ user.username }}!</h1>
66
+ <p class="subtitle">Here's an overview of your ResumeRanker API activity.</p>
67
 
68
+ {% if error %}
69
+ <div class="error-message">{{ error }}</div>
70
+ {% endif %}
71
 
72
+ {% if success %}
73
+ <div class="success-message">{{ success }}</div>
74
+ {% endif %}
 
 
 
 
 
 
75
 
76
+ <div class="analytics-grid">
77
+ <div class="analytics-card">
78
+ <p class="card-label">Average Score</p>
79
+ <h2 class="card-value">{{ analytics.average_score | default('N/A') }}</h2>
80
+ </div>
81
+ <div class="analytics-card">
82
+ <p class="card-label">Top Resume</p>
83
+ <h2 class="card-value">{{ analytics.top_resume | default('N/A') }}</h2>
84
+ </div>
85
+ <div class="analytics-card">
86
+ <p class="card-label">Last Upload</p>
87
+ <h2 class="card-value">{{ analytics.last_upload_date | default('N/A') }}</h2>
88
+ </div>
89
+ </div>
90
+
91
+ <div class="section-card api-key-section">
92
+ <h3 class="section-title">Your API Key</h3>
93
+ <p class="section-description">Use this key to authenticate your requests to the ResumeRanker API.</p>
94
+ <div class="input-group">
95
+ <input type="text" class="form-input" id="apiKey" value="{{ user.api_key }}" readonly>
96
+ <button class="btn primary-btn copy-btn" type="button" onclick="copyApiKey()">Copy</button>
97
+ </div>
98
+ <form method="POST" action="{{ url_for('auth.regenerate_api_key') }}" class="api-key-form">
99
+ <button class="btn secondary-btn">Regenerate API Key</button>
100
+ </form>
101
+ <p class="docs-link"><a href="{{ url_for('auth.docs') }}">๐Ÿ“˜ View API Documentation</a></p>
102
+ </div>
103
+
104
+ <div class="section-card upload-section">
105
+ <h3 class="section-title">Upload Resumes</h3>
106
+ <p class="section-description">Drag and drop your resume files here, or click to browse.</p>
107
+ <div class="drop-area" id="dropArea">
108
+ <input type="file" id="fileInput" multiple accept=".pdf,.doc,.docx" style="display: none;">
109
+ <p>Drag & Drop files or <span class="browse-link">Click to browse</span></p>
110
+ <ul id="fileList" class="uploaded-files-list"></ul>
111
+ </div>
112
+ <button class="btn primary-btn upload-now-btn" id="uploadNowBtn" style="display: none;">Upload Now</button>
113
+ </div>
114
+
115
+ <div class="section-card recent-uploads-section">
116
+ <h3 class="section-title">Recent Uploads</h3>
117
+ {% if recent_uploads %}
118
+ <div class="table-responsive">
119
+ <table class="data-table">
120
+ <thead>
121
+ <tr>
122
+ <th>Resume Name</th>
123
+ <th>Score</th>
124
+ <th>Uploaded Date</th>
125
+ <th>Action</th>
126
+ </tr>
127
+ </thead>
128
+ <tbody>
129
+ {% for resume in recent_uploads %}
130
+ <tr>
131
+ <td data-label="Resume Name">{{ resume.name }}</td>
132
+ <td data-label="Score">{{ resume.score }}</td>
133
+ <td data-label="Uploaded Date">{{ resume.uploaded_date }}</td>
134
+ <td data-label="Action"><a href="{{ url_for('auth.view_result', resume_id=resume.id) }}" class="btn small-btn view-btn">View Result</a></td>
135
+ </tr>
136
+ {% endfor %}
137
+ </tbody>
138
+ </table>
139
+ </div>
140
+ {% else %}
141
+ <p class="no-data-message">No recent uploads yet. Start by uploading a resume!</p>
142
+ {% endif %}
143
+ </div>
144
+ </div>
145
+ </div>
146
+ </div>
147
  {% endblock %}
148
 
149
+ {% block extra_scripts %}
150
+ <script>
151
+ // Theme toggle (specific to dashboard header)
152
+ const themeToggleDashboard = document.getElementById('themeToggleDashboard');
153
+ const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
154
+ function setTheme(theme) {
155
+ document.documentElement.setAttribute('data-theme', theme);
156
+ localStorage.setItem('theme', theme);
157
+ themeToggleDashboard.textContent = theme === 'dark' ? 'โ˜€๏ธ' : '๐ŸŒ™';
158
+ }
159
+ const savedTheme = localStorage.getItem('theme');
160
+ setTheme(savedTheme || (prefersDark ? 'dark' : 'light'));
161
+ themeToggleDashboard.onclick = () => {
162
+ const current = document.documentElement.getAttribute('data-theme');
163
+ setTheme(current === 'dark' ? 'light' : 'dark');
164
+ };
165
+
166
+ // Sidebar toggle for mobile
167
+ const hamburgerDashboard = document.getElementById('hamburgerDashboard');
168
+ const sidebar = document.getElementById('sidebar');
169
+ const mainContentArea = document.getElementById('mainContentArea');
170
+ const closeSidebarBtn = document.getElementById('closeSidebar');
171
+
172
+ hamburgerDashboard.onclick = () => {
173
+ sidebar.classList.add('open');
174
+ mainContentArea.classList.add('shifted');
175
+ };
176
+ closeSidebarBtn.onclick = () => {
177
+ sidebar.classList.remove('open');
178
+ mainContentArea.classList.remove('shifted');
179
+ };
180
+ // Close sidebar if click outside on mobile
181
+ mainContentArea.onclick = (event) => {
182
+ if (sidebar.classList.contains('open') && !sidebar.contains(event.target) && !hamburgerDashboard.contains(event.target)) {
183
+ sidebar.classList.remove('open');
184
+ mainContentArea.classList.remove('shifted');
185
+ }
186
+ };
187
+ // Close sidebar when clicking a nav item on mobile
188
+ Array.from(sidebar.getElementsByClassName('nav-item')).forEach(item => {
189
+ item.onclick = () => {
190
+ if (window.innerWidth <= 900) { // Assuming 900px is your mobile breakpoint
191
+ sidebar.classList.remove('open');
192
+ mainContentArea.classList.remove('shifted');
193
+ }
194
+ };
195
+ });
196
+
197
+
198
+ // API Key Copy function
199
+ function copyApiKey() {
200
+ const input = document.getElementById("apiKey");
201
+ input.select();
202
+ input.setSelectionRange(0, 99999); // For mobile
203
+ document.execCommand("copy");
204
+ // Add a more subtle visual feedback like a tooltip or temporary text change
205
+ const originalText = document.querySelector('.copy-btn').textContent;
206
+ document.querySelector('.copy-btn').textContent = 'Copied!';
207
+ setTimeout(() => {
208
+ document.querySelector('.copy-btn').textContent = originalText;
209
+ }, 1500);
210
+ }
211
+
212
+ // Drag and Drop / File Upload Logic
213
+ const dropArea = document.getElementById('dropArea');
214
+ const fileInput = document.getElementById('fileInput');
215
+ const fileList = document.getElementById('fileList');
216
+ const uploadNowBtn = document.getElementById('uploadNowBtn');
217
+ let filesToUpload = [];
218
+
219
+ dropArea.addEventListener('click', () => fileInput.click());
220
+
221
+ dropArea.addEventListener('dragover', (e) => {
222
+ e.preventDefault();
223
+ dropArea.classList.add('drag-over');
224
+ });
225
+
226
+ dropArea.addEventListener('dragleave', () => {
227
+ dropArea.classList.remove('drag-over');
228
+ });
229
+
230
+ dropArea.addEventListener('drop', (e) => {
231
+ e.preventDefault();
232
+ dropArea.classList.remove('drag-over');
233
+ handleFiles(e.dataTransfer.files);
234
+ });
235
+
236
+ fileInput.addEventListener('change', (e) => {
237
+ handleFiles(e.target.files);
238
+ });
239
+
240
+ function handleFiles(files) {
241
+ filesToUpload = [...filesToUpload, ...Array.from(files)];
242
+ fileList.innerHTML = ''; // Clear current list before repopulating
243
+ filesToUpload.forEach(file => {
244
+ const listItem = document.createElement('li');
245
+ listItem.textContent = file.name;
246
+ fileList.appendChild(listItem);
247
+ });
248
+ if (filesToUpload.length > 0) {
249
+ uploadNowBtn.style.display = 'block';
250
+ }
251
+ }
252
+
253
+ uploadNowBtn.addEventListener('click', () => {
254
+ // Here you would implement your actual file upload logic using Fetch API or XMLHttpRequest
255
+ console.log('Uploading files:', filesToUpload);
256
+ alert('Files are ready to be uploaded! (Implement actual upload via AJAX/Fetch)');
257
+ // Example: Clear after simulated upload
258
+ filesToUpload = [];
259
+ fileList.innerHTML = '';
260
+ uploadNowBtn.style.display = 'none';
261
+ });
262
+
263
+ </script>
264
+ {% endblock %}