sprygen 1.0.0

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.
Files changed (56) hide show
  1. package/README.md +80 -0
  2. package/dist/cli.js +55 -0
  3. package/package.json +53 -0
  4. package/templates/auth/AuthController.java.ejs +40 -0
  5. package/templates/auth/JwtAuthFilter.java.ejs +62 -0
  6. package/templates/auth/JwtService.java.ejs +81 -0
  7. package/templates/auth/SecurityConfig.java.ejs +65 -0
  8. package/templates/auth/UserDetailsServiceImpl.java.ejs +24 -0
  9. package/templates/entity/Entity.java.ejs +40 -0
  10. package/templates/entity/EntityController.java.ejs +92 -0
  11. package/templates/entity/EntityControllerTest.java.ejs +24 -0
  12. package/templates/entity/EntityDto.java.ejs +32 -0
  13. package/templates/entity/EntityRepository.java.ejs +9 -0
  14. package/templates/entity/EntityService.java.ejs +32 -0
  15. package/templates/project/java/config/CorsConfig.java.ejs +24 -0
  16. package/templates/project/java/config/SecurityConfig.java.ejs +76 -0
  17. package/templates/project/java/config/SecurityConfigSession.java.ejs +73 -0
  18. package/templates/project/java/config/SwaggerConfig.java.ejs +31 -0
  19. package/templates/project/java/controller/AdminController.java.ejs +82 -0
  20. package/templates/project/java/controller/AuthController.java.ejs +86 -0
  21. package/templates/project/java/controller/HomeController.java.ejs +63 -0
  22. package/templates/project/java/controller/ProfileController.java.ejs +65 -0
  23. package/templates/project/java/controller/UserController.java.ejs +35 -0
  24. package/templates/project/java/dto/AuthRequest.java.ejs +15 -0
  25. package/templates/project/java/dto/AuthResponse.java.ejs +18 -0
  26. package/templates/project/java/dto/ProfileUpdateRequest.java.ejs +20 -0
  27. package/templates/project/java/dto/RegisterRequest.java.ejs +30 -0
  28. package/templates/project/java/dto/UserDto.java.ejs +17 -0
  29. package/templates/project/java/entity/Role.java.ejs +6 -0
  30. package/templates/project/java/entity/User.java.ejs +97 -0
  31. package/templates/project/java/repository/UserRepository.java.ejs +11 -0
  32. package/templates/project/java/security/JwtAuthFilter.java.ejs +62 -0
  33. package/templates/project/java/security/UserDetailsServiceImpl.java.ejs +21 -0
  34. package/templates/project/java/service/JwtService.java.ejs +81 -0
  35. package/templates/project/java/service/UserService.java.ejs +32 -0
  36. package/templates/project/resources/application.yml.ejs +50 -0
  37. package/templates/project/resources/logback-spring.xml.ejs +41 -0
  38. package/templates/project/static/admin.html.ejs +163 -0
  39. package/templates/project/static/assets/app.js.ejs +340 -0
  40. package/templates/project/static/assets/style.css +533 -0
  41. package/templates/project/static/css/style.css +595 -0
  42. package/templates/project/static/dashboard.html.ejs +119 -0
  43. package/templates/project/static/index.html.ejs +96 -0
  44. package/templates/project/static/js/api.js +30 -0
  45. package/templates/project/static/js/auth.js +44 -0
  46. package/templates/project/static/js/nav.js.ejs +82 -0
  47. package/templates/project/static/js/ui.js +57 -0
  48. package/templates/project/static/login.html.ejs +71 -0
  49. package/templates/project/static/profile.html.ejs +163 -0
  50. package/templates/project/static/register.html.ejs +82 -0
  51. package/templates/project/thymeleaf/admin/users.html.ejs +111 -0
  52. package/templates/project/thymeleaf/dashboard.html.ejs +109 -0
  53. package/templates/project/thymeleaf/layout.html.ejs +75 -0
  54. package/templates/project/thymeleaf/login.html.ejs +56 -0
  55. package/templates/project/thymeleaf/profile.html.ejs +133 -0
  56. package/templates/project/thymeleaf/register.html.ejs +56 -0
@@ -0,0 +1,111 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" xmlns:th="http://www.thymeleaf.org"
3
+ xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
4
+ <head>
5
+ <meta charset="UTF-8"/>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
7
+ <title>Admin Panel — <%= projectName %></title>
8
+ <link rel="stylesheet" th:href="@{/css/style.css}"/>
9
+ </head>
10
+ <body>
11
+ <div id="app">
12
+ <div class="layout">
13
+ <aside class="sidebar">
14
+ <div class="sidebar-brand">
15
+ <div class="logo-icon">⚡</div>
16
+ <span class="logo-text"><%= projectName %></span>
17
+ </div>
18
+ <nav class="sidebar-nav">
19
+ <span class="nav-section-label">Navigation</span>
20
+ <a class="nav-link" th:href="@{/dashboard}"><span class="icon">⊞</span> Dashboard</a>
21
+ <a class="nav-link" th:href="@{/profile}"><span class="icon">◉</span> Profile</a>
22
+ <span class="nav-section-label">Administration</span>
23
+ <a class="nav-link active" th:href="@{/admin/users}"><span class="icon">⚙</span> Admin Panel</a>
24
+ </nav>
25
+ <div class="sidebar-footer">
26
+ <div class="user-widget">
27
+ <div class="user-avatar" th:text="${#strings.substring(user.firstName,0,1) + #strings.substring(user.lastName,0,1)}">??</div>
28
+ <div class="user-info">
29
+ <div class="user-name" th:text="${user.firstName + ' ' + user.lastName}">User</div>
30
+ <div class="user-role">👑 Admin</div>
31
+ </div>
32
+ </div>
33
+ <form th:action="@{/logout}" method="post" style="margin-top:10px;">
34
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
35
+ <button type="submit" class="btn btn-outline btn-full" style="font-size:0.8rem;">Sign Out</button>
36
+ </form>
37
+ </div>
38
+ </aside>
39
+
40
+ <div class="main">
41
+ <div class="topbar">
42
+ <span class="topbar-title">Admin Panel</span>
43
+ <span style="font-size:0.85rem;color:var(--text-muted);" th:text="${users.size()} + ' users'"></span>
44
+ </div>
45
+ <div class="page">
46
+ <div class="page-header">
47
+ <h1>User Management</h1>
48
+ <p>View, manage roles, and remove users from your application.</p>
49
+ </div>
50
+
51
+ <div th:if="${success}" class="alert alert-success visible" th:text="${success}"></div>
52
+ <div th:if="${error}" class="alert alert-error visible" th:text="${error}"></div>
53
+
54
+ <div class="card">
55
+ <div class="card-body">
56
+ <div class="table-wrap">
57
+ <table>
58
+ <thead>
59
+ <tr>
60
+ <th>ID</th>
61
+ <th>Name</th>
62
+ <th>Email</th>
63
+ <th>Role</th>
64
+ <th>Joined</th>
65
+ <th>Actions</th>
66
+ </tr>
67
+ </thead>
68
+ <tbody>
69
+ <tr th:each="u : ${users}">
70
+ <td style="color:var(--text-muted);font-size:0.8rem;" th:text="'#' + ${u.id}"></td>
71
+ <td><strong th:text="${u.firstName + ' ' + u.lastName}"></strong></td>
72
+ <td style="font-size:0.85rem;" th:text="${u.email}"></td>
73
+ <td>
74
+ <span th:if="${u.admin}" class="badge badge-admin">👑 Admin</span>
75
+ <span th:unless="${u.admin}" class="badge badge-user">User</span>
76
+ </td>
77
+ <td style="color:var(--text-muted);font-size:0.82rem;"
78
+ th:text="${u.createdAt != null} ? ${#temporals.format(u.createdAt,'MMM d, yyyy')} : '—'"></td>
79
+ <td>
80
+ <div class="flex gap-2">
81
+ <!-- Toggle role -->
82
+ <form th:action="@{/admin/users/{id}/role(id=${u.id})}" method="post" style="display:inline;">
83
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
84
+ <input type="hidden" name="role" th:value="${u.admin} ? 'ROLE_USER' : 'ROLE_ADMIN'"/>
85
+ <button type="submit" class="btn btn-outline btn-sm"
86
+ th:text="${u.admin} ? 'Make User' : 'Make Admin'"></button>
87
+ </form>
88
+ <!-- Delete -->
89
+ <form th:action="@{/admin/users/{id}/delete(id=${u.id})}" method="post"
90
+ onsubmit="return confirm('Delete this user? This cannot be undone.')">
91
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
92
+ <button type="submit" class="btn btn-danger btn-sm">Delete</button>
93
+ </form>
94
+ </div>
95
+ </td>
96
+ </tr>
97
+ <tr th:if="${users.empty}">
98
+ <td colspan="6" style="text-align:center;padding:32px;color:var(--text-muted);">No users found.</td>
99
+ </tr>
100
+ </tbody>
101
+ </table>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ </div>
109
+ </body>
110
+ </html>
111
+
@@ -0,0 +1,109 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" xmlns:th="http://www.thymeleaf.org"
3
+ xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
4
+ <head>
5
+ <meta charset="UTF-8"/>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
7
+ <title>Dashboard — <%= projectName %></title>
8
+ <link rel="stylesheet" th:href="@{/css/style.css}"/>
9
+ </head>
10
+ <body>
11
+ <div id="app">
12
+ <div class="layout">
13
+ <!-- Sidebar -->
14
+ <aside class="sidebar">
15
+ <div class="sidebar-brand">
16
+ <div class="logo-icon">⚡</div>
17
+ <span class="logo-text"><%= projectName %></span>
18
+ </div>
19
+ <nav class="sidebar-nav">
20
+ <span class="nav-section-label">Navigation</span>
21
+ <a class="nav-link active" th:href="@{/dashboard}"><span class="icon">⊞</span> Dashboard</a>
22
+ <a class="nav-link" th:href="@{/profile}"><span class="icon">◉</span> Profile</a>
23
+ <sec:authorize access="hasAuthority('ROLE_ADMIN')">
24
+ <span class="nav-section-label">Administration</span>
25
+ <a class="nav-link" th:href="@{/admin/users}"><span class="icon">⚙</span> Admin Panel</a>
26
+ </sec:authorize>
27
+ </nav>
28
+ <div class="sidebar-footer">
29
+ <div class="user-widget">
30
+ <div class="user-avatar"
31
+ th:text="${#strings.substring(user.firstName,0,1) + #strings.substring(user.lastName,0,1)}">??</div>
32
+ <div class="user-info">
33
+ <div class="user-name" th:text="${user.firstName + ' ' + user.lastName}">User</div>
34
+ <div class="user-role">
35
+ <sec:authorize access="hasAuthority('ROLE_ADMIN')">👑 Admin</sec:authorize>
36
+ <sec:authorize access="!hasAuthority('ROLE_ADMIN')">🙂 User</sec:authorize>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ <form th:action="@{/logout}" method="post" style="margin-top:10px;">
41
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
42
+ <button type="submit" class="btn btn-outline btn-full" style="font-size:0.8rem;">Sign Out</button>
43
+ </form>
44
+ </div>
45
+ </aside>
46
+
47
+ <!-- Main content -->
48
+ <div class="main">
49
+ <div class="topbar">
50
+ <span class="topbar-title">Dashboard</span>
51
+ <span style="font-size:0.85rem;color:var(--text-muted);"
52
+ th:text="${#temporals.format(#temporals.createNow(), 'EEEE, MMMM d')}"></span>
53
+ </div>
54
+ <div class="page">
55
+ <div class="page-header">
56
+ <h1>Welcome back, <span th:text="${user.firstName}">User</span> 👋</h1>
57
+ <p>Here's an overview of your account and the application.</p>
58
+ </div>
59
+
60
+ <div class="stats-grid">
61
+ <div class="stat-card">
62
+ <div class="stat-icon">👤</div>
63
+ <div class="stat-value">
64
+ <sec:authorize access="hasAuthority('ROLE_ADMIN')">Admin</sec:authorize>
65
+ <sec:authorize access="!hasAuthority('ROLE_ADMIN')">User</sec:authorize>
66
+ </div>
67
+ <div class="stat-label">Your Role</div>
68
+ </div>
69
+
70
+ <sec:authorize access="hasAuthority('ROLE_ADMIN')">
71
+ <div class="stat-card">
72
+ <div class="stat-icon">👥</div>
73
+ <div class="stat-value" th:text="${totalUsers}">—</div>
74
+ <div class="stat-label">Total Users</div>
75
+ </div>
76
+ <div class="stat-card">
77
+ <div class="stat-icon">🛡️</div>
78
+ <div class="stat-value" th:text="${adminCount}">—</div>
79
+ <div class="stat-label">Admins</div>
80
+ </div>
81
+ </sec:authorize>
82
+ </div>
83
+
84
+ <div class="card">
85
+ <div class="card-body">
86
+ <div class="section-header">
87
+ <span class="section-title">API Quick Reference</span>
88
+ </div>
89
+ <div class="table-wrap">
90
+ <table>
91
+ <thead><tr><th>Method</th><th>Route</th><th>Access</th></tr></thead>
92
+ <tbody>
93
+ <tr><td><span style="color:#10b981;font-weight:700;font-size:0.8rem;">POST</span></td><td style="font-family:monospace;font-size:0.82rem;">/api/v1/auth/register</td><td><span class="badge badge-success">Public</span></td></tr>
94
+ <tr><td><span style="color:#10b981;font-weight:700;font-size:0.8rem;">POST</span></td><td style="font-family:monospace;font-size:0.82rem;">/api/v1/auth/login</td><td><span class="badge badge-success">Public</span></td></tr>
95
+ <tr><td><span style="color:#60a5fa;font-weight:700;font-size:0.8rem;">GET</span></td><td style="font-family:monospace;font-size:0.82rem;">/api/v1/profile</td><td><span class="badge badge-user">Auth</span></td></tr>
96
+ <tr><td><span style="color:#f59e0b;font-weight:700;font-size:0.8rem;">PUT</span></td><td style="font-family:monospace;font-size:0.82rem;">/api/v1/profile</td><td><span class="badge badge-user">Auth</span></td></tr>
97
+ <tr><td><span style="color:#60a5fa;font-weight:700;font-size:0.8rem;">GET</span></td><td style="font-family:monospace;font-size:0.82rem;">/api/v1/admin/users</td><td><span class="badge badge-admin">Admin</span></td></tr>
98
+ </tbody>
99
+ </table>
100
+ </div>
101
+ </div>
102
+ </div>
103
+ </div>
104
+ </div>
105
+ </div>
106
+ </div>
107
+ </body>
108
+ </html>
109
+
@@ -0,0 +1,75 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" xmlns:th="http://www.thymeleaf.org"
3
+ xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
4
+ <head>
5
+ <meta charset="UTF-8" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title th:text="${pageTitle} + ' — <%= projectName %>'">Page — <%= projectName %></title>
8
+ <link rel="stylesheet" th:href="@{/css/style.css}" />
9
+ <link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>" />
10
+ </head>
11
+ <body>
12
+
13
+ <div id="app">
14
+ <!-- App shell with sidebar -->
15
+ <div class="layout" sec:authorize="isAuthenticated()">
16
+
17
+ <!-- Sidebar -->
18
+ <aside class="sidebar">
19
+ <div class="sidebar-brand">
20
+ <div class="logo-icon">⚡</div>
21
+ <span class="logo-text"><%= projectName %></span>
22
+ </div>
23
+ <nav class="sidebar-nav">
24
+ <span class="nav-section-label">Navigation</span>
25
+ <a class="nav-link" th:classappend="${activePage == 'dashboard'} ? 'active'" th:href="@{/dashboard}">
26
+ <span class="icon">⊞</span> Dashboard
27
+ </a>
28
+ <a class="nav-link" th:classappend="${activePage == 'profile'} ? 'active'" th:href="@{/profile}">
29
+ <span class="icon">◉</span> Profile
30
+ </a>
31
+ <sec:authorize access="hasAuthority('ROLE_ADMIN')">
32
+ <span class="nav-section-label">Administration</span>
33
+ <a class="nav-link" th:classappend="${activePage == 'admin'} ? 'active'" th:href="@{/admin/users}">
34
+ <span class="icon">⚙</span> Admin Panel
35
+ </a>
36
+ </sec:authorize>
37
+ </nav>
38
+ <div class="sidebar-footer">
39
+ <div class="user-widget">
40
+ <div class="user-avatar" sec:authentication="principal.firstName"
41
+ th:text="${#strings.substring(#authentication.principal.firstName,0,1) + #strings.substring(#authentication.principal.lastName,0,1)}">??</div>
42
+ <div class="user-info">
43
+ <div class="user-name" sec:authentication="name"
44
+ th:text="${#authentication.principal.firstName + ' ' + #authentication.principal.lastName}">User</div>
45
+ <div class="user-role">
46
+ <sec:authorize access="hasAuthority('ROLE_ADMIN')">👑 Admin</sec:authorize>
47
+ <sec:authorize access="!hasAuthority('ROLE_ADMIN')">🙂 User</sec:authorize>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ <form th:action="@{/logout}" method="post" style="margin-top:10px;">
52
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
53
+ <button type="submit" class="btn btn-outline btn-full" style="font-size:0.8rem;">Sign Out</button>
54
+ </form>
55
+ </div>
56
+ </aside>
57
+
58
+ <!-- Main content -->
59
+ <div class="main">
60
+ <div class="topbar">
61
+ <span class="topbar-title" th:text="${pageTitle}">Page</span>
62
+ </div>
63
+ <div class="page" th:replace="${content}">
64
+ <!-- Page content inserted here -->
65
+ </div>
66
+ </div>
67
+ </div>
68
+
69
+ <!-- Unauthenticated: just render content directly (login/register pages) -->
70
+ <div sec:authorize="!isAuthenticated()" th:replace="${content}"><!-- auth page content --></div>
71
+ </div>
72
+
73
+ </body>
74
+ </html>
75
+
@@ -0,0 +1,56 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" xmlns:th="http://www.thymeleaf.org">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Sign In — <%= projectName %></title>
7
+ <link rel="stylesheet" th:href="@{/css/style.css}"/>
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <div class="auth-page">
12
+ <div class="auth-container">
13
+ <div class="auth-brand">
14
+ <div class="logo-icon-lg">⚡</div>
15
+ <h1><%= projectName %></h1>
16
+ <p>Sign in to your account</p>
17
+ </div>
18
+ <div class="auth-card">
19
+
20
+ <!-- Error alert -->
21
+ <div th:if="${param.error}" class="alert alert-error visible">
22
+ Invalid email or password. Please try again.
23
+ </div>
24
+ <!-- Logout success -->
25
+ <div th:if="${param.logout}" class="alert alert-success visible">
26
+ You have been signed out successfully.
27
+ </div>
28
+
29
+ <form th:action="@{/login}" method="post">
30
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
31
+ <div class="form-group">
32
+ <label for="username">Email address</label>
33
+ <input id="username" name="username" type="email" placeholder="you@example.com" required autofocus/>
34
+ </div>
35
+ <div class="form-group">
36
+ <label for="password">Password</label>
37
+ <input id="password" name="password" type="password" placeholder="••••••••" required/>
38
+ </div>
39
+ <button type="submit" class="btn btn-primary btn-full">Sign In</button>
40
+ </form>
41
+
42
+ <div class="divider"></div>
43
+ <p style="text-align:center;font-size:0.85rem;color:var(--text-muted);">
44
+ Don't have an account?
45
+ <a th:href="@{/register}" style="color:var(--primary);text-decoration:none;font-weight:600;">Create one</a>
46
+ </p>
47
+ <p style="text-align:center;font-size:0.82rem;color:var(--text-dim);margin-top:8px;">
48
+ <a th:href="@{/}" style="color:var(--text-dim);">← Back to home</a>
49
+ </p>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </body>
55
+ </html>
56
+
@@ -0,0 +1,133 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" xmlns:th="http://www.thymeleaf.org"
3
+ xmlns:sec="http://www.thymeleaf.org/extras/spring-security">
4
+ <head>
5
+ <meta charset="UTF-8"/>
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
7
+ <title>Profile — <%= projectName %></title>
8
+ <link rel="stylesheet" th:href="@{/css/style.css}"/>
9
+ </head>
10
+ <body>
11
+ <div id="app">
12
+ <div class="layout">
13
+ <aside class="sidebar">
14
+ <div class="sidebar-brand">
15
+ <div class="logo-icon">⚡</div>
16
+ <span class="logo-text"><%= projectName %></span>
17
+ </div>
18
+ <nav class="sidebar-nav">
19
+ <span class="nav-section-label">Navigation</span>
20
+ <a class="nav-link" th:href="@{/dashboard}"><span class="icon">⊞</span> Dashboard</a>
21
+ <a class="nav-link active" th:href="@{/profile}"><span class="icon">◉</span> Profile</a>
22
+ <sec:authorize access="hasAuthority('ROLE_ADMIN')">
23
+ <span class="nav-section-label">Administration</span>
24
+ <a class="nav-link" th:href="@{/admin/users}"><span class="icon">⚙</span> Admin Panel</a>
25
+ </sec:authorize>
26
+ </nav>
27
+ <div class="sidebar-footer">
28
+ <div class="user-widget">
29
+ <div class="user-avatar" th:text="${#strings.substring(user.firstName,0,1) + #strings.substring(user.lastName,0,1)}">??</div>
30
+ <div class="user-info">
31
+ <div class="user-name" th:text="${user.firstName + ' ' + user.lastName}">User</div>
32
+ <div class="user-role">
33
+ <sec:authorize access="hasAuthority('ROLE_ADMIN')">👑 Admin</sec:authorize>
34
+ <sec:authorize access="!hasAuthority('ROLE_ADMIN')">🙂 User</sec:authorize>
35
+ </div>
36
+ </div>
37
+ </div>
38
+ <form th:action="@{/logout}" method="post" style="margin-top:10px;">
39
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
40
+ <button type="submit" class="btn btn-outline btn-full" style="font-size:0.8rem;">Sign Out</button>
41
+ </form>
42
+ </div>
43
+ </aside>
44
+
45
+ <div class="main">
46
+ <div class="topbar"><span class="topbar-title">My Profile</span></div>
47
+ <div class="page">
48
+
49
+ <!-- Success/error flash -->
50
+ <div th:if="${success}" class="alert alert-success visible" th:text="${success}"></div>
51
+ <div th:if="${error}" class="alert alert-error visible" th:text="${error}"></div>
52
+
53
+ <!-- Profile hero -->
54
+ <div class="profile-hero">
55
+ <div class="profile-avatar-lg"
56
+ th:text="${#strings.substring(user.firstName,0,1) + #strings.substring(user.lastName,0,1)}">??</div>
57
+ <div class="profile-hero-info">
58
+ <h2 th:text="${user.firstName + ' ' + user.lastName}">Name</h2>
59
+ <p th:text="${user.email}">email@example.com</p>
60
+ <div style="margin-top:8px;">
61
+ <sec:authorize access="hasAuthority('ROLE_ADMIN')">
62
+ <span class="badge badge-admin">👑 Admin</span>
63
+ </sec:authorize>
64
+ <sec:authorize access="!hasAuthority('ROLE_ADMIN')">
65
+ <span class="badge badge-user">User</span>
66
+ </sec:authorize>
67
+ </div>
68
+ </div>
69
+ </div>
70
+
71
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
72
+ <!-- Update profile -->
73
+ <div class="card">
74
+ <div class="card-body">
75
+ <div class="card-title">Personal Information</div>
76
+ <div class="card-muted" style="margin-bottom:20px;">Update your name and bio</div>
77
+ <form th:action="@{/profile}" method="post">
78
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
79
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
80
+ <div class="form-group">
81
+ <label>First Name</label>
82
+ <input name="firstName" type="text" th:value="${user.firstName}" required/>
83
+ </div>
84
+ <div class="form-group">
85
+ <label>Last Name</label>
86
+ <input name="lastName" type="text" th:value="${user.lastName}" required/>
87
+ </div>
88
+ </div>
89
+ <div class="form-group">
90
+ <label>Bio</label>
91
+ <textarea name="bio" rows="3" placeholder="Tell us about yourself…" th:text="${user.bio}"></textarea>
92
+ </div>
93
+ <div class="form-group">
94
+ <label>Avatar URL</label>
95
+ <input name="avatarUrl" type="url" th:value="${user.avatarUrl}" placeholder="https://…"/>
96
+ </div>
97
+ <button type="submit" class="btn btn-primary">Save Changes</button>
98
+ </form>
99
+ </div>
100
+ </div>
101
+
102
+ <!-- Change password -->
103
+ <div class="card">
104
+ <div class="card-body">
105
+ <div class="card-title">Change Password</div>
106
+ <div class="card-muted" style="margin-bottom:20px;">Update your password</div>
107
+ <form th:action="@{/profile/password}" method="post">
108
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
109
+ <div class="form-group">
110
+ <label>Current Password</label>
111
+ <input name="currentPassword" type="password" placeholder="••••••••"/>
112
+ </div>
113
+ <div class="form-group">
114
+ <label>New Password</label>
115
+ <input name="newPassword" type="password" placeholder="••••••••"/>
116
+ </div>
117
+ <div class="form-group">
118
+ <label>Confirm New Password</label>
119
+ <input name="confirmPassword" type="password" placeholder="••••••••"/>
120
+ </div>
121
+ <button type="submit" class="btn btn-primary">Update Password</button>
122
+ </form>
123
+ </div>
124
+ </div>
125
+ </div>
126
+
127
+ </div>
128
+ </div>
129
+ </div>
130
+ </div>
131
+ </body>
132
+ </html>
133
+
@@ -0,0 +1,56 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en" xmlns:th="http://www.thymeleaf.org">
3
+ <head>
4
+ <meta charset="UTF-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>Register — <%= projectName %></title>
7
+ <link rel="stylesheet" th:href="@{/css/style.css}"/>
8
+ </head>
9
+ <body>
10
+ <div id="app">
11
+ <div class="auth-page">
12
+ <div class="auth-container">
13
+ <div class="auth-brand">
14
+ <div class="logo-icon-lg">⚡</div>
15
+ <h1><%= projectName %></h1>
16
+ <p>Create your account</p>
17
+ </div>
18
+ <div class="auth-card">
19
+
20
+ <div th:if="${error}" class="alert alert-error visible" th:text="${error}"></div>
21
+
22
+ <form th:action="@{/register}" method="post">
23
+ <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
24
+ <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
25
+ <div class="form-group">
26
+ <label for="firstName">First Name</label>
27
+ <input id="firstName" name="firstName" type="text" placeholder="John" required/>
28
+ </div>
29
+ <div class="form-group">
30
+ <label for="lastName">Last Name</label>
31
+ <input id="lastName" name="lastName" type="text" placeholder="Doe" required/>
32
+ </div>
33
+ </div>
34
+ <div class="form-group">
35
+ <label for="email">Email address</label>
36
+ <input id="email" name="email" type="email" placeholder="you@example.com" required/>
37
+ </div>
38
+ <div class="form-group">
39
+ <label for="password">Password <span style="color:var(--text-dim);font-size:0.75rem;">(min 6 chars)</span></label>
40
+ <input id="password" name="password" type="password" placeholder="••••••••" required minlength="6"/>
41
+ </div>
42
+ <button type="submit" class="btn btn-primary btn-full">Create Account</button>
43
+ </form>
44
+
45
+ <div class="divider"></div>
46
+ <p style="text-align:center;font-size:0.85rem;color:var(--text-muted);">
47
+ Already have an account?
48
+ <a th:href="@{/login}" style="color:var(--primary);text-decoration:none;font-weight:600;">Sign in</a>
49
+ </p>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </body>
55
+ </html>
56
+