Skip to content

Commit 380b9c7

Browse files
authored
Merge pull request #1 from xconnio/registration
Add Register Add Login Add Login as guest Add Forgot password Add Verify email
2 parents d13e2e4 + 82c441a commit 380b9c7

19 files changed

Lines changed: 2184 additions & 19 deletions

.env.example

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# WAMP Connection Configuration
2+
VITE_WAMP_URL=ws://localhost:8080/ws
3+
VITE_WAMP_REALM=io.xconn.deskconn
4+
5+
# Registration Credentials (do not commit real values)
6+
VITE_REGISTRATION_AUTHID=deskconn-web-app
7+
VITE_REGISTRATION_SECRET=your-secret-here

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ dist-ssr
1414
coverage
1515
*.local
1616

17+
# Environment variables (local secrets)
18+
.env*.local
19+
1720
# Editor directories and files
1821
.vscode/*
1922
!.vscode/extensions.json

package.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,19 @@
1616
"format": "prettier --write --experimental-cli src/"
1717
},
1818
"dependencies": {
19+
"@noble/ed25519": "^3.0.0",
20+
"@popperjs/core": "^2.11.8",
21+
"bootstrap": "^5.3.8",
22+
"idb-keyval": "^6.2.2",
1923
"pinia": "^3.0.4",
2024
"vue": "^3.5.25",
21-
"vue-router": "^4.6.3"
25+
"vue-router": "^4.6.3",
26+
"vue3-otp-input": "^0.5.40",
27+
"xconn": "xconnio/xconn-js"
2228
},
2329
"devDependencies": {
2430
"@tsconfig/node24": "^24.0.3",
31+
"@types/bootstrap": "^5.2.10",
2532
"@types/node": "^24.10.1",
2633
"@vitejs/plugin-vue": "^6.0.2",
2734
"@vue/eslint-config-prettier": "^10.2.0",

src/App.vue

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,72 @@
1-
<script setup lang="ts"></script>
1+
<script setup lang="ts">
2+
import { onMounted } from 'vue'
3+
import { RouterView, useRouter } from 'vue-router'
4+
import { useAuthStore } from './stores/auth'
5+
6+
const router = useRouter()
7+
const authStore = useAuthStore()
8+
9+
const handleLogout = () => {
10+
authStore.logout()
11+
router.push('/login')
12+
}
13+
14+
onMounted(async () => {
15+
try {
16+
const success = await authStore.autoLogin()
17+
if (success) {
18+
if (
19+
router.currentRoute.value.path === '/login' ||
20+
router.currentRoute.value.path === '/register'
21+
) {
22+
router.push('/')
23+
}
24+
} else {
25+
// Check if auth is required for current route?
26+
// The router guards handle this mostly, but if we are on a protected route and auto-login fails,
27+
// we might want to redirect. But usually router guard does it.
28+
}
29+
} catch (err) {
30+
console.error('Auto-login failed', err)
31+
authStore.logout() // Ensure clean state
32+
if (router.currentRoute.value.path !== '/login') {
33+
router.push('/login')
34+
}
35+
}
36+
})
37+
</script>
238

339
<template>
4-
<h1>You did it!</h1>
5-
<p>
6-
Visit <a href="https://vuejs.org/" target="_blank" rel="noopener">vuejs.org</a> to read the
7-
documentation
8-
</p>
40+
<div class="min-vh-100 d-flex flex-column bg-light">
41+
<!-- Header -->
42+
<header class="navbar navbar-expand-lg header-theme">
43+
<div class="container-fluid">
44+
<router-link to="/" class="navbar-brand mb-0 h1 fw-bold ps-3 text-dark text-decoration-none"
45+
>Deskconn</router-link
46+
>
47+
48+
<div class="d-flex align-items-center me-3" v-if="authStore.isAuthenticated">
49+
<span class="me-3 text-dark"
50+
>Hello, {{ authStore.user?.name || authStore.user?.username }}</span
51+
>
52+
<a href="#" class="text-danger text-decoration-none" @click.prevent="handleLogout"
53+
>Logout</a
54+
>
55+
</div>
56+
</div>
57+
</header>
58+
59+
<!-- Main Content -->
60+
<main class="flex-grow-1 d-flex flex-column">
61+
<!-- Views are now responsible for their own layout (containers, centering, etc) -->
62+
<RouterView />
63+
</main>
64+
</div>
965
</template>
1066

11-
<style scoped></style>
67+
<style>
68+
/* Global styles if needed, though Bootstrap handles most */
69+
body {
70+
background-color: #f8f9fa;
71+
}
72+
</style>

src/assets/theme.css

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
@import url('https://fonts.googleapis.com/css2?family=Nunito:wght@300;400;600;700&display=swap');
2+
3+
/* Custom Theme Variables overriding Bootstrap defaults */
4+
:root {
5+
/* Restored Color Palette */
6+
--theme-yellow: #ffb800;
7+
--theme-dark: #2c2e33;
8+
--theme-bg-gradient: linear-gradient(135deg, #f0f4ff 0%, #e0e7ff 100%);
9+
--theme-surface: rgba(255, 255, 255, 0.95);
10+
11+
/* Bootstrap Overrides */
12+
--bs-body-font-family: 'Nunito', sans-serif;
13+
--bs-primary: var(--theme-yellow);
14+
--bs-primary-rgb: 255, 184, 0;
15+
--bs-secondary: var(--theme-dark);
16+
--bs-body-bg: #f8fafc;
17+
}
18+
19+
/* Global Styles */
20+
body {
21+
background: var(--theme-bg-gradient);
22+
min-height: 100vh;
23+
font-family: var(--bs-body-font-family) !important;
24+
color: #334155;
25+
}
26+
27+
/* Typography */
28+
h1,
29+
h2,
30+
h3,
31+
h4,
32+
h5,
33+
h6 {
34+
font-weight: 700;
35+
color: #1e293b;
36+
letter-spacing: -0.025em;
37+
}
38+
39+
/* Button Styling */
40+
.btn-primary {
41+
background-color: var(--theme-yellow);
42+
border-color: var(--theme-yellow);
43+
color: #ffffff;
44+
font-weight: 700;
45+
padding: 0.75rem 1.5rem;
46+
border-radius: 8px;
47+
transition: all 0.2s ease;
48+
}
49+
50+
.btn-primary:hover,
51+
.btn-primary:focus {
52+
background-color: #e5a500;
53+
border-color: #e5a500;
54+
color: #ffffff;
55+
transform: translateY(-1px);
56+
box-shadow:
57+
0 4px 6px -1px rgba(229, 165, 0, 0.2),
58+
0 2px 4px -1px rgba(229, 165, 0, 0.1);
59+
}
60+
/* Animation */
61+
.fade-in-up {
62+
animation: fadeInUp 0.5s ease-out;
63+
}
64+
65+
@keyframes fadeInUp {
66+
from {
67+
opacity: 0;
68+
transform: translateY(20px);
69+
}
70+
to {
71+
opacity: 1;
72+
transform: translateY(0);
73+
}
74+
}
75+
.btn-primary:active {
76+
transform: translateY(0);
77+
}
78+
79+
.btn-secondary {
80+
background-color: var(--theme-dark);
81+
border-color: var(--theme-dark);
82+
color: #fff;
83+
}
84+
85+
/* Card Styling */
86+
.card {
87+
border: 1px solid rgba(255, 255, 255, 0.5);
88+
border-radius: 16px;
89+
background-color: var(--theme-surface);
90+
backdrop-filter: blur(12px);
91+
box-shadow:
92+
0 20px 25px -5px rgba(0, 0, 0, 0.05),
93+
0 10px 10px -5px rgba(0, 0, 0, 0.01);
94+
}
95+
96+
/* Form Controls */
97+
.form-control {
98+
border-radius: 8px;
99+
border: 1px solid #e2e8f0;
100+
padding: 0.75rem 1rem;
101+
font-size: 0.95rem;
102+
transition: all 0.2s ease;
103+
}
104+
105+
.form-control:focus {
106+
border-color: var(--theme-primary);
107+
box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.1);
108+
}
109+
110+
.form-label {
111+
font-weight: 500;
112+
color: #475569;
113+
margin-bottom: 0.5rem;
114+
}
115+
116+
/* Links */
117+
a {
118+
text-decoration: none;
119+
color: var(--theme-primary);
120+
font-weight: 500;
121+
}
122+
123+
a:hover {
124+
color: var(--theme-primary-hover);
125+
text-decoration: underline;
126+
}

src/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const WAMP_URL = import.meta.env.VITE_WAMP_URL || 'ws://localhost:8080/ws'
2+
export const WAMP_REALM = import.meta.env.VITE_WAMP_REALM || 'io.xconn.deskconn'
3+
4+
// Registration specific credentials
5+
export const REGISTRATION_AUTHID = import.meta.env.VITE_REGISTRATION_AUTHID || 'deskconn-web-app'
6+
export const REGISTRATION_SECRET = import.meta.env.VITE_REGISTRATION_SECRET || ''

src/main.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { createApp } from 'vue'
22
import { createPinia } from 'pinia'
3+
import 'bootstrap/dist/css/bootstrap.min.css'
4+
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
5+
import './assets/theme.css'
36

47
import App from './App.vue'
58
import router from './router'

src/router/index.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,56 @@
11
import { createRouter, createWebHistory } from 'vue-router'
2+
import LoginView from '../views/LoginView.vue'
3+
import RegisterView from '../views/RegisterView.vue'
4+
import VerifyView from '../views/VerifyView.vue'
5+
import HomeView from '../views/HomeView.vue'
6+
import { useAuthStore } from '../stores/auth'
27

38
const router = createRouter({
49
history: createWebHistory(import.meta.env.BASE_URL),
5-
routes: [],
10+
routes: [
11+
{
12+
path: '/',
13+
name: 'home',
14+
component: HomeView,
15+
meta: { requiresAuth: true },
16+
},
17+
{
18+
path: '/login',
19+
name: 'login',
20+
component: LoginView,
21+
meta: { requiresGuest: true },
22+
},
23+
{
24+
path: '/register',
25+
name: 'register',
26+
component: RegisterView,
27+
meta: { requiresGuest: true },
28+
},
29+
{
30+
path: '/verify',
31+
name: 'verify',
32+
component: VerifyView,
33+
meta: { requiresGuest: true },
34+
},
35+
{
36+
path: '/forgot-password',
37+
name: 'forgot-password',
38+
component: () => import('../views/ForgotPasswordView.vue'),
39+
meta: { requiresGuest: true },
40+
},
41+
],
42+
})
43+
44+
router.beforeEach((to, from, next) => {
45+
const authStore = useAuthStore()
46+
47+
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
48+
next('/login')
49+
} else if (to.meta.requiresGuest && authStore.isAuthenticated) {
50+
next('/')
51+
} else {
52+
next()
53+
}
654
})
755

856
export default router

0 commit comments

Comments
 (0)