diff --git a/scripts/start-infrastructure.sh b/scripts/start-infrastructure.sh index 81a7da0..3fe77c7 100755 --- a/scripts/start-infrastructure.sh +++ b/scripts/start-infrastructure.sh @@ -13,11 +13,10 @@ else echo "PostgreSQL container does not exist. Creating..." docker run -d \ --name TRAKQR_POSTGRES \ - -v "${DATA_ROOT}/postgres:/var/lib/postgresql/data" \ + -v "${DATA_ROOT}/postgres:/var/lib/postgresql" \ -p 5400:5432 \ -e POSTGRES_USER=sa \ -e POSTGRES_PASSWORD=P@ssword123! \ -e POSTGRES_DB=trakqr \ postgres:$POSTGRES_VERSION fi - diff --git a/src/TrackApi/TrackQrApi/Program.cs b/src/TrackApi/TrackQrApi/Program.cs index bae949e..9c9452d 100644 --- a/src/TrackApi/TrackQrApi/Program.cs +++ b/src/TrackApi/TrackQrApi/Program.cs @@ -194,7 +194,6 @@ try app.UseCors(); app.UseRateLimiter(); - app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); @@ -209,4 +208,4 @@ try catch (Exception ex) { Log.Fatal(ex, "Application terminated unexpectedly"); -} \ No newline at end of file +} diff --git a/src/TrackApi/TrackQrApi/Properties/launchSettings.json b/src/TrackApi/TrackQrApi/Properties/launchSettings.json index a374255..67a4141 100644 --- a/src/TrackApi/TrackQrApi/Properties/launchSettings.json +++ b/src/TrackApi/TrackQrApi/Properties/launchSettings.json @@ -1,11 +1,11 @@ { "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { - "https": { + "http": { "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": false, - "applicationUrl": "https://0.0.0.0:42001", + "applicationUrl": "http://0.0.0.0:42001", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/src/TrackApi/TrackQrApi/appsettings.Development.json b/src/TrackApi/TrackQrApi/appsettings.Development.json index 1bf01b2..197b48e 100644 --- a/src/TrackApi/TrackQrApi/appsettings.Development.json +++ b/src/TrackApi/TrackQrApi/appsettings.Development.json @@ -6,10 +6,10 @@ } }, "App": { - "PublicUrl": "https://192.168.1.17:42001" + "PublicUrl": "http://192.168.1.2:42001" }, "ConnectionStrings": { - "PostgresConnection": "Host=localhost;Port=5400;Database=trakqr;Username=sa;Password=P@ssword123!" + "PostgresConnection": "Host=192.168.1.2;Port=5400;Database=trakqr;Username=sa;Password=P@ssword123!" }, "Jwt": { "Secret": "dev-secret-key-min-32-characters-long-for-hmac256!", @@ -21,12 +21,12 @@ "Provider": "console", "FromEmail": "noreply@trakqr.local", "FromName": "TrakQR", - "BaseUrl": "http://localhost:5173" + "BaseUrl": "http://192.168.1.2:5173" }, "Cors": { "AllowedOrigins": [ - "http://localhost:5173", - "https://localhost:5173" + "http://192.168.1.2:5173", + "https://192.168.1.2:5173" ] }, "Stripe": { diff --git a/src/frontend/.env.development b/src/frontend/.env.development new file mode 100644 index 0000000..9558682 --- /dev/null +++ b/src/frontend/.env.development @@ -0,0 +1 @@ +VITE_API_URL=http://192.168.1.2:42001 diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index d40189a..fdca23a 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -15,7 +15,8 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.4", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vite-svg-loader": "^5.1.1" } }, "node_modules/@babel/helper-string-parser": { @@ -907,6 +908,21 @@ "url": "https://github.com/sponsors/antfu" } }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/copy-anything": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", @@ -921,11 +937,169 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/css-select": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz", + "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-tree": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz", + "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.30", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css-what": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", + "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==", + "dev": true, + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/csso": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz", + "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==", + "dev": true, + "dependencies": { + "css-tree": "~2.2.0" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/css-tree": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz", + "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.28", + "source-map-js": "^1.0.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0", + "npm": ">=7.0.0" + } + }, + "node_modules/csso/node_modules/mdn-data": { + "version": "2.0.28", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz", + "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==", + "dev": true + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==" }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dev": true, + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "dev": true, + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/entities": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.0.tgz", @@ -1018,11 +1192,23 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mdn-data": { + "version": "2.0.30", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz", + "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", + "dev": true + }, "node_modules/mitt": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==" }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -1040,6 +1226,18 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -1143,6 +1341,15 @@ "fsevents": "~2.3.2" } }, + "node_modules/sax": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.5.0.tgz", + "integrity": "sha512-21IYA3Q5cQf089Z6tgaUTr7lDAyzoTPx5HRtbhsME8Udispad8dC/+sziTNugOEx54ilvatQ9YCzl4KQLPcRHA==", + "dev": true, + "engines": { + "node": ">=11.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1170,6 +1377,31 @@ "node": ">=16" } }, + "node_modules/svgo": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.3.tgz", + "integrity": "sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==", + "dev": true, + "dependencies": { + "commander": "^7.2.0", + "css-select": "^5.1.0", + "css-tree": "^2.3.1", + "css-what": "^6.1.0", + "csso": "^5.0.5", + "picocolors": "^1.0.0", + "sax": "^1.5.0" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/svgo" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", @@ -1229,6 +1461,19 @@ } } }, + "node_modules/vite-svg-loader": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/vite-svg-loader/-/vite-svg-loader-5.1.1.tgz", + "integrity": "sha512-RPzcXA/EpKJA0585x58DBgs7my2VfeJ+j2j1EoHY4Zh82Y7hV4cR1fElgy2aZi85+QSrcLLoTStQ5uZjD68u+Q==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "svgo": "^3.3.3" + }, + "peerDependencies": { + "vue": ">=3.2.13" + } + }, "node_modules/vue": { "version": "3.5.26", "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.26.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index d67a900..0b30b92 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -16,6 +16,7 @@ }, "devDependencies": { "@vitejs/plugin-vue": "^5.0.4", - "vite": "^5.2.0" + "vite": "^5.2.0", + "vite-svg-loader": "^5.1.1" } } diff --git a/src/frontend/src/api/base.js b/src/frontend/src/api/base.js index 5e4b1cf..481fdd2 100644 --- a/src/frontend/src/api/base.js +++ b/src/frontend/src/api/base.js @@ -1,4 +1,4 @@ -const API_BASE = 'https://localhost:42001'; +const API_BASE = import.meta.env.VITE_API_URL; class BaseClient { constructor() { @@ -29,7 +29,7 @@ class BaseClient { const response = await fetch(`${API_BASE}${path}`, options); - if (response.status === 401) { + if (response.status === 401 && this.token) { this.setToken(null); window.location.href = '/login'; throw new Error('Unauthorized'); diff --git a/src/frontend/src/assets/icons/eye-closed.svg b/src/frontend/src/assets/icons/eye-closed.svg new file mode 100644 index 0000000..84a08a8 --- /dev/null +++ b/src/frontend/src/assets/icons/eye-closed.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/frontend/src/assets/icons/eye-open.svg b/src/frontend/src/assets/icons/eye-open.svg new file mode 100644 index 0000000..ebdcbc5 --- /dev/null +++ b/src/frontend/src/assets/icons/eye-open.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/frontend/src/components/PasswordInput.vue b/src/frontend/src/components/PasswordInput.vue new file mode 100644 index 0000000..c8e5bde --- /dev/null +++ b/src/frontend/src/components/PasswordInput.vue @@ -0,0 +1,86 @@ + + + + + diff --git a/src/frontend/src/style.css b/src/frontend/src/style.css index cfa237a..1c0cd00 100644 --- a/src/frontend/src/style.css +++ b/src/frontend/src/style.css @@ -41,6 +41,12 @@ a { gap: 48px; } +main { + display: flex; + flex-direction: column; + gap: 40px; +} + .topbar { display: flex; align-items: center; @@ -206,16 +212,12 @@ a { .qr-grid { width: 180px; height: 180px; - background: repeating-linear-gradient( - 90deg, + background: repeating-linear-gradient(90deg, rgba(15, 23, 42, 0.9) 0 8px, - transparent 8px 16px - ), - repeating-linear-gradient( - 0deg, + transparent 8px 16px), + repeating-linear-gradient(0deg, rgba(15, 23, 42, 0.9) 0 8px, - transparent 8px 16px - ); + transparent 8px 16px); border-radius: 16px; box-shadow: inset 0 0 0 10px #fff; } @@ -235,7 +237,7 @@ a { .feature { background: rgba(255, 255, 255, 0.85); border-radius: 20px; - padding: 18px 20px; + padding: 24px 28px; border: 1px solid var(--line); box-shadow: 0 12px 30px rgba(15, 23, 42, 0.08); transition: transform 0.2s ease; @@ -303,6 +305,7 @@ a { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); @@ -313,6 +316,7 @@ a { from { transform: scaleY(0.6); } + to { transform: scaleY(1); } diff --git a/src/frontend/src/views/auth/Login.vue b/src/frontend/src/views/auth/Login.vue index 0dfc45c..14aac0d 100644 --- a/src/frontend/src/views/auth/Login.vue +++ b/src/frontend/src/views/auth/Login.vue @@ -25,23 +25,13 @@
- -
@@ -69,6 +59,7 @@ import { ref } from 'vue'; import { useRouter, useRoute } from 'vue-router'; import { useAuthStore } from '../../stores/auth'; +import PasswordInput from '../../components/PasswordInput.vue'; const router = useRouter(); const route = useRoute(); @@ -77,8 +68,6 @@ const authStore = useAuthStore(); const email = ref(''); const password = ref(''); -const showPassword = ref(false); - const handleSubmit = async () => { const success = await authStore.login(email.value, password.value); if (success) { @@ -218,33 +207,5 @@ const handleSubmit = async () => { color: var(--accent); font-weight: 500; } -.password-field { - position: relative; - display: flex; - align-items: center; -} - -.password-field input { - width: 100%; - padding-right: 72px; /* space for the button */ -} - -.password-toggle { - position: absolute; - right: 10px; - height: 32px; - padding: 0 10px; - border: 1px solid var(--line); - border-radius: 10px; - background: var(--surface); - color: var(--muted); - font-size: 0.85rem; - cursor: pointer; -} - -.password-toggle:hover { - color: var(--accent); - border-color: var(--accent); -} diff --git a/src/frontend/src/views/auth/Register.vue b/src/frontend/src/views/auth/Register.vue index d9d9859..69114ac 100644 --- a/src/frontend/src/views/auth/Register.vue +++ b/src/frontend/src/views/auth/Register.vue @@ -24,10 +24,9 @@
- - @@ -66,6 +64,7 @@ import { ref, computed } from 'vue'; import { useRouter } from 'vue-router'; import { useAuthStore } from '../../stores/auth'; +import PasswordInput from '../../components/PasswordInput.vue'; const router = useRouter(); const authStore = useAuthStore(); diff --git a/src/frontend/src/views/auth/ResetPassword.vue b/src/frontend/src/views/auth/ResetPassword.vue index 1d1bd9d..48f7417 100644 --- a/src/frontend/src/views/auth/ResetPassword.vue +++ b/src/frontend/src/views/auth/ResetPassword.vue @@ -13,10 +13,9 @@
- - @@ -62,6 +60,7 @@ import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { authApi } from '../../api/auth.js'; +import PasswordInput from '../../components/PasswordInput.vue'; const route = useRoute(); diff --git a/src/frontend/src/views/links/Links.vue b/src/frontend/src/views/links/Links.vue index 8ac5d9a..5918d25 100644 --- a/src/frontend/src/views/links/Links.vue +++ b/src/frontend/src/views/links/Links.vue @@ -213,10 +213,9 @@
-
@@ -311,6 +310,7 @@