Congratulations! Your app is running. ๐
-
- @for (item of [
- { title: 'Explore the Docs', link: 'https://angular.dev' },
- { title: 'Learn with Tutorials', link: 'https://angular.dev/tutorials' },
- { title: 'Prompt and best practices for AI', link: 'https://angular.dev/ai/develop-with-ai'},
- { title: 'CLI Docs', link: 'https://angular.dev/tools/cli' },
- { title: 'Angular Language Service', link: 'https://angular.dev/tools/language-service' },
- { title: 'Angular DevTools', link: 'https://angular.dev/tools/devtools' },
- ]; track item.title) {
-
- {{ item.title }}
-
-
+
+
+
+
Start QR session
+
POST /userauth/qr/create
+
Returns a one-time token and Telegram deeplink for the QR image.
+
+
+
Poll QR confirmation
+
GET /userauth/qr/poll?token=...
+
Returns pending, confirmed, or expired. On confirmed, also returns the user session.
+
+
+
Read current session
+
GET /userauth/session
+
Cookie-based session lookup used for initial auth check and fallback polling.
+
+
+
Sync cart after login
+
POST /usersession/{sessionId}
+
Existing cart payload is preserved. Only the namespace is generalized for reuse.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+
+
Login required
+
Please log in via Telegram to proceed with your order.
+
+
+
+
+
+
+
+
+ Log in with Telegram
+
+
+
+
Or scan the QR code
+
+
+
+
+
+
+
+
+
+
+
+
+
QR code expired. Click to refresh
+
+
+
+
You will be redirected back after login.
+
+
-
+
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts
deleted file mode 100644
index 16efca5..0000000
--- a/src/app/app.routes.ts
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Routes } from '@angular/router';
-import { authGuard, guestGuard } from './guards/auth.guard';
-import { LoginComponent } from './components/login/login';
-import { ErrorComponent } from './components/error/error';
-import { SuccessComponent } from './components/success/success';
-
-export const routes: Routes = [
- {
- path: '',
- redirectTo: '/login',
- pathMatch: 'full'
- },
- {
- path: 'login',
- component: LoginComponent,
- canActivate: [guestGuard]
- },
- {
- path: 'error',
- component: ErrorComponent
- },
- {
- path: 'success',
- component: SuccessComponent,
- canActivate: [authGuard]
- },
- {
- path: '**',
- redirectTo: '/error'
- }
-];
diff --git a/src/app/app.scss b/src/app/app.scss
index e69de29..8607116 100644
--- a/src/app/app.scss
+++ b/src/app/app.scss
@@ -0,0 +1,415 @@
+:host {
+ --bg-page: linear-gradient(135deg, #f4f7fb 0%, #e8eef4 100%);
+ --bg-card: #ffffff;
+ --bg-hover: #f0f0f0;
+ --text-primary: #1a1a1a;
+ --text-secondary: #666666;
+ --accent-color: #497671;
+ --accent-light: rgba(73, 118, 113, 0.1);
+ --telegram: #2aabee;
+ --telegram-hover: #229ed9;
+ --border: #e8e8e8;
+ --shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
+
+ display: block;
+ min-height: 100vh;
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+ color: var(--text-primary);
+ background: var(--bg-page);
+}
+
+* {
+ box-sizing: border-box;
+}
+
+.page {
+ min-height: 100vh;
+ display: grid;
+ grid-template-columns: minmax(320px, 448px) minmax(320px, 560px);
+ gap: 32px;
+ padding: 40px 32px;
+ align-items: center;
+ justify-content: center;
+}
+
+.panel {
+ background: rgba(255, 255, 255, 0.72);
+ border: 1px solid rgba(255, 255, 255, 0.8);
+ border-radius: 28px;
+ padding: 24px;
+ box-shadow: 0 18px 50px rgba(38, 52, 73, 0.12);
+ backdrop-filter: blur(14px);
+}
+
+.info h1 {
+ margin: 0 0 12px;
+ font-size: 32px;
+ line-height: 1.1;
+}
+
+.info p {
+ margin: 0 0 18px;
+ color: var(--text-secondary);
+ line-height: 1.6;
+}
+
+.state-switcher {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin: 20px 0 24px;
+}
+
+.state-switcher button {
+ border: 1px solid #cfd8e3;
+ border-radius: 999px;
+ background: #fff;
+ color: var(--text-primary);
+ padding: 10px 14px;
+ font-size: 14px;
+ cursor: pointer;
+ transition: 0.2s ease;
+}
+
+.state-switcher button.active {
+ border-color: var(--accent-color);
+ background: var(--accent-light);
+ color: var(--accent-color);
+}
+
+.api-grid {
+ display: grid;
+ gap: 12px;
+ margin-top: 20px;
+}
+
+.api-card {
+ background: #fff;
+ border: 1px solid #eef2f7;
+ border-radius: 16px;
+ padding: 14px 16px;
+}
+
+.api-card strong {
+ display: block;
+ margin-bottom: 6px;
+ font-size: 14px;
+}
+
+.api-card code {
+ display: inline-block;
+ padding: 2px 8px;
+ border-radius: 999px;
+ background: #f3f7fb;
+ color: #21425f;
+ font-size: 13px;
+}
+
+.api-card p {
+ margin: 8px 0 0;
+ font-size: 13px;
+}
+
+.login-overlay {
+ position: relative;
+ min-height: 700px;
+ border-radius: 28px;
+ background: rgba(0, 0, 0, 0.5);
+ backdrop-filter: blur(4px);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: fadeIn 0.2s ease;
+ padding: 16px;
+}
+
+.login-dialog {
+ position: relative;
+ background: var(--bg-card);
+ border-radius: 20px;
+ padding: 32px 28px;
+ max-width: 400px;
+ width: 100%;
+ text-align: center;
+ box-shadow: var(--shadow);
+ animation: scaleIn 0.25s ease;
+}
+
+.close-btn {
+ position: absolute;
+ top: 12px;
+ right: 12px;
+ width: 32px;
+ height: 32px;
+ border: none;
+ border-radius: 50%;
+ background: var(--bg-hover);
+ color: var(--text-secondary);
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: all 0.2s ease;
+}
+
+.close-btn:hover {
+ background: #e0e0e0;
+ color: #333;
+}
+
+.login-icon {
+ margin: 0 auto 16px;
+ width: 72px;
+ height: 72px;
+ border-radius: 50%;
+ background: var(--accent-light);
+ color: var(--accent-color);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.login-dialog h2 {
+ margin: 0 0 8px;
+ font-size: 20px;
+ font-weight: 700;
+ color: var(--text-primary);
+}
+
+.login-desc {
+ margin: 0 0 24px;
+ font-size: 14px;
+ color: var(--text-secondary);
+ line-height: 1.5;
+}
+
+.telegram-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ width: 100%;
+ padding: 14px 24px;
+ border: none;
+ border-radius: 12px;
+ background: var(--telegram);
+ color: #fff;
+ font-size: 16px;
+ font-weight: 600;
+ cursor: pointer;
+ transition: all 0.2s ease;
+}
+
+.telegram-btn:hover {
+ background: var(--telegram-hover);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(42, 171, 238, 0.3);
+}
+
+.telegram-btn:active {
+ transform: translateY(0);
+}
+
+.tg-icon {
+ flex-shrink: 0;
+}
+
+.qr-section {
+ margin-top: 20px;
+}
+
+.qr-hint {
+ margin: 0 0 12px;
+ font-size: 13px;
+ color: #999;
+}
+
+.qr-container {
+ display: inline-flex;
+ padding: 12px;
+ background: #fff;
+ border-radius: 12px;
+ border: 1px solid var(--border);
+}
+
+.qr-container img {
+ display: block;
+ border-radius: 4px;
+}
+
+.qr-loading {
+ align-items: center;
+ justify-content: center;
+ width: 204px;
+ height: 204px;
+}
+
+.qr-loading .spinner,
+.login-status .spinner {
+ border-radius: 50%;
+ animation: spin 0.8s linear infinite;
+}
+
+.qr-loading .spinner {
+ width: 32px;
+ height: 32px;
+ border: 3px solid #e0e0e0;
+ border-top-color: var(--accent-color);
+}
+
+.qr-expired {
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 8px;
+ width: 204px;
+ height: 204px;
+ cursor: pointer;
+ color: #999;
+ transition: color 0.2s ease;
+}
+
+.qr-expired:hover {
+ color: var(--accent-color);
+}
+
+.qr-expired span {
+ font-size: 13px;
+}
+
+.login-note {
+ margin: 16px 0 0;
+ font-size: 12px;
+ color: #999;
+ line-height: 1.4;
+}
+
+.login-status {
+ display: none;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ padding: 16px;
+ color: var(--text-secondary);
+ font-size: 14px;
+}
+
+.login-status .spinner {
+ width: 20px;
+ height: 20px;
+ border: 2px solid #e0e0e0;
+ border-top-color: var(--accent-color);
+}
+
+.dialog-content[data-state='checking'] .login-status {
+ display: flex;
+}
+
+.dialog-content[data-state='checking'] .action-block,
+.dialog-content[data-state='loading'] .qr-ready,
+.dialog-content[data-state='loading'] .qr-expired,
+.dialog-content[data-state='expired'] .qr-ready,
+.dialog-content[data-state='expired'] .qr-loading,
+.dialog-content[data-state='error'] .qr-loading,
+.dialog-content[data-state='error'] .qr-expired,
+.dialog-content[data-state='checking'] .qr-section,
+.dialog-content[data-state='checking'] .login-note {
+ display: none;
+}
+
+.dialog-content[data-state='ready'] .qr-loading,
+.dialog-content[data-state='ready'] .qr-expired,
+.dialog-content[data-state='ready'] .qr-error,
+.dialog-content[data-state='loading'] .qr-ready,
+.dialog-content[data-state='loading'] .qr-expired,
+.dialog-content[data-state='loading'] .qr-error,
+.dialog-content[data-state='expired'] .qr-loading,
+.dialog-content[data-state='expired'] .qr-ready,
+.dialog-content[data-state='expired'] .qr-error,
+.dialog-content[data-state='error'] .qr-loading,
+.dialog-content[data-state='error'] .qr-expired {
+ display: none;
+}
+
+.dialog-content[data-state='error'] .qr-ready {
+ display: inline-flex;
+}
+
+.metadata {
+ margin-top: 22px;
+ padding-top: 18px;
+ border-top: 1px solid #e9edf2;
+ font-size: 13px;
+ color: var(--text-secondary);
+}
+
+.metadata ul {
+ margin: 10px 0 0;
+ padding-left: 18px;
+}
+
+.metadata li + li {
+ margin-top: 6px;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ }
+
+ to {
+ opacity: 1;
+ }
+}
+
+@keyframes scaleIn {
+ from {
+ opacity: 0;
+ transform: scale(0.95);
+ }
+
+ to {
+ opacity: 1;
+ transform: scale(1);
+ }
+}
+
+@keyframes spin {
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+@media (max-width: 980px) {
+ .page {
+ grid-template-columns: 1fr;
+ padding: 24px 16px 32px;
+ }
+
+ .login-overlay {
+ min-height: 560px;
+ }
+}
+
+@media (max-width: 480px) {
+ .panel {
+ border-radius: 22px;
+ padding: 18px;
+ }
+
+ .login-dialog {
+ padding: 24px 20px;
+ border-radius: 16px;
+ }
+
+ .qr-container img {
+ width: 140px;
+ height: 140px;
+ }
+
+ .qr-loading,
+ .qr-expired {
+ width: 164px;
+ height: 164px;
+ }
+}
diff --git a/src/app/app.ts b/src/app/app.ts
index 8a361b7..6498997 100644
--- a/src/app/app.ts
+++ b/src/app/app.ts
@@ -1,12 +1,17 @@
import { Component, signal } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
+
+type DialogState = 'ready' | 'loading' | 'checking' | 'expired' | 'error';
@Component({
selector: 'app-root',
- imports: [RouterOutlet],
- template: '
',
+ templateUrl: './app.html',
styleUrl: './app.scss'
})
export class App {
- protected readonly title = signal('auth-service');
+ protected readonly state = signal
('ready');
+ protected readonly states: DialogState[] = ['ready', 'loading', 'checking', 'expired', 'error'];
+
+ protected setState(state: DialogState): void {
+ this.state.set(state);
+ }
}
diff --git a/src/app/components/error/error.html b/src/app/components/error/error.html
deleted file mode 100644
index 4afef6f..0000000
--- a/src/app/components/error/error.html
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
Something Went Wrong
-
-
{{ errorMessage }}
-
-
- Error Code:
- {{ errorCode }}
-
-
-
-
- ๐ Try Again
-
-
- โ Go Back
-
-
-
- @if (errorDetails) {
-
-
- {{ showDetails ? 'โผ' : 'โถ' }} Technical Details
-
-
- @if (showDetails) {
-
-
{{ errorDetails | json }}
-
- }
-
- }
-
-
-
-
-
-
diff --git a/src/app/components/error/error.scss b/src/app/components/error/error.scss
deleted file mode 100644
index 1a89584..0000000
--- a/src/app/components/error/error.scss
+++ /dev/null
@@ -1,215 +0,0 @@
-.error-container {
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
- padding: 20px;
-}
-
-.error-card {
- background: white;
- border-radius: 16px;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
- max-width: 600px;
- width: 100%;
- padding: 48px 40px;
- text-align: center;
- animation: slideIn 0.4s ease-out;
-}
-
-@keyframes slideIn {
- from {
- opacity: 0;
- transform: translateY(20px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-.error-icon-container {
- margin-bottom: 24px;
-
- .error-icon {
- font-size: 80px;
- animation: shake 0.5s ease-in-out;
- }
-}
-
-@keyframes shake {
- 0%, 100% { transform: translateX(0); }
- 25% { transform: translateX(-10px); }
- 75% { transform: translateX(10px); }
-}
-
-.error-title {
- font-size: 32px;
- font-weight: 700;
- color: #2d3748;
- margin-bottom: 16px;
-}
-
-.error-message {
- font-size: 18px;
- color: #4a5568;
- margin-bottom: 24px;
- line-height: 1.6;
-}
-
-.error-code {
- display: inline-flex;
- align-items: center;
- gap: 8px;
- padding: 12px 20px;
- background: #fff5f5;
- border: 1px solid #feb2b2;
- border-radius: 8px;
- margin-bottom: 32px;
-
- .label {
- font-size: 14px;
- color: #742a2a;
- font-weight: 600;
- }
-
- .code {
- font-size: 14px;
- color: #e53e3e;
- font-family: 'Courier New', monospace;
- font-weight: 600;
- }
-}
-
-.error-actions {
- display: flex;
- gap: 12px;
- justify-content: center;
- margin-bottom: 32px;
-
- .btn {
- padding: 14px 32px;
- border: none;
- border-radius: 8px;
- font-size: 16px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
-
- &.btn-primary {
- background: #667eea;
- color: white;
-
- &:hover {
- background: #5568d3;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
- }
- }
-
- &.btn-secondary {
- background: #e2e8f0;
- color: #4a5568;
-
- &:hover {
- background: #cbd5e0;
- transform: translateY(-2px);
- }
- }
- }
-}
-
-.error-details-section {
- margin-bottom: 32px;
- text-align: left;
-
- .details-toggle {
- width: 100%;
- padding: 12px 16px;
- background: #f7fafc;
- border: 1px solid #e2e8f0;
- border-radius: 8px;
- font-size: 14px;
- color: #4a5568;
- cursor: pointer;
- text-align: left;
- transition: background 0.2s;
-
- &:hover {
- background: #edf2f7;
- }
- }
-
- .error-details {
- margin-top: 12px;
- padding: 16px;
- background: #1a202c;
- border-radius: 8px;
- overflow-x: auto;
-
- pre {
- margin: 0;
- color: #68d391;
- font-size: 12px;
- font-family: 'Courier New', monospace;
- white-space: pre-wrap;
- word-wrap: break-word;
- }
- }
-}
-
-.help-section {
- padding-top: 32px;
- border-top: 1px solid #e2e8f0;
-
- .help-text {
- font-size: 14px;
- color: #718096;
- margin-bottom: 8px;
- }
-
- .help-link {
- color: #667eea;
- text-decoration: none;
- font-weight: 600;
- font-size: 16px;
-
- &:hover {
- text-decoration: underline;
- }
- }
-}
-
-.error-footer {
- margin-top: 32px;
- text-align: center;
-
- p {
- font-size: 13px;
- color: rgba(255, 255, 255, 0.9);
- }
-}
-
-@media (max-width: 640px) {
- .error-card {
- padding: 32px 24px;
- }
-
- .error-title {
- font-size: 24px;
- }
-
- .error-message {
- font-size: 16px;
- }
-
- .error-actions {
- flex-direction: column;
-
- .btn {
- width: 100%;
- }
- }
-}
diff --git a/src/app/components/error/error.ts b/src/app/components/error/error.ts
deleted file mode 100644
index 2b5fe53..0000000
--- a/src/app/components/error/error.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Component, OnInit, inject } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { ActivatedRoute, Router } from '@angular/router';
-
-@Component({
- selector: 'app-error',
- imports: [CommonModule],
- templateUrl: './error.html',
- styleUrl: './error.scss'
-})
-export class ErrorComponent implements OnInit {
- private route = inject(ActivatedRoute);
- private router = inject(Router);
-
- errorMessage: string = 'An unexpected error occurred';
- errorCode: string = 'UNKNOWN_ERROR';
- showDetails: boolean = false;
- errorDetails: any = null;
-
- ngOnInit(): void {
- this.route.queryParams.subscribe(params => {
- this.errorMessage = params['message'] || this.errorMessage;
- this.errorCode = params['code'] || this.errorCode;
-
- if (params['details']) {
- try {
- this.errorDetails = JSON.parse(params['details']);
- } catch (e) {
- this.errorDetails = params['details'];
- }
- }
- });
- }
-
- goBack(): void {
- this.router.navigate(['/login']);
- }
-
- toggleDetails(): void {
- this.showDetails = !this.showDetails;
- }
-
- retry(): void {
- window.location.reload();
- }
-}
diff --git a/src/app/components/login/login.html b/src/app/components/login/login.html
deleted file mode 100644
index 006dfb2..0000000
--- a/src/app/components/login/login.html
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
-
- @if (!isLoading && !error) {
-
-
-
-
-
-
-
Scan to Login
-
- Open your authenticator app
- Scan this QR code
- You'll be logged in automatically
-
-
-
-
-
Expires in: {{ expiresIn }}s
-
- ๐ Refresh QR Code
-
-
-
- @if (isPolling) {
-
-
-
Waiting for authentication...
-
- }
-
- }
-
-
- @if (isLoading) {
-
-
-
Generating QR Code...
-
- }
-
-
- @if (error) {
-
-
โ ๏ธ
-
{{ error }}
-
- Try Again
-
-
- }
-
-
-
-
-
diff --git a/src/app/components/login/login.scss b/src/app/components/login/login.scss
deleted file mode 100644
index ebd6699..0000000
--- a/src/app/components/login/login.scss
+++ /dev/null
@@ -1,214 +0,0 @@
-.login-container {
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- padding: 20px;
-}
-
-.login-card {
- max-width: 500px;
- width: 100%;
- padding: 40px;
-}
-
-.login-header {
- text-align: center;
- margin-bottom: 30px;
-
- h1 {
- font-size: 28px;
- font-weight: 700;
- color: #2d3748;
- margin-bottom: 10px;
- }
-
- .project-name {
- font-size: 18px;
- font-weight: 600;
- color: #667eea;
- margin-bottom: 8px;
- }
-
- .device-info {
- font-size: 13px;
- color: #718096;
- }
-}
-
-.qr-section {
- text-align: center;
-}
-
-.qr-code-container {
- background: #f7fafc;
- border: 2px solid #e2e8f0;
- border-radius: 12px;
- padding: 20px;
- margin-bottom: 24px;
- display: inline-block;
-
- .qr-code {
- width: 240px;
- height: 240px;
- display: block;
- }
-}
-
-.instructions {
- margin-bottom: 24px;
- text-align: left;
-
- h3 {
- font-size: 18px;
- font-weight: 600;
- color: #2d3748;
- margin-bottom: 12px;
- }
-
- ol {
- padding-left: 20px;
- color: #4a5568;
- line-height: 1.8;
-
- li {
- margin-bottom: 8px;
- }
- }
-}
-
-.qr-info {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 20px;
- padding: 12px 16px;
- background: #f7fafc;
- border-radius: 8px;
-
- .expires {
- font-size: 14px;
- color: #718096;
- margin: 0;
- }
-
- .refresh-btn {
- background: #667eea;
- color: white;
- border: none;
- padding: 8px 16px;
- border-radius: 6px;
- font-size: 14px;
- cursor: pointer;
- transition: background 0.2s;
-
- &:hover {
- background: #5568d3;
- }
- }
-}
-
-.polling-indicator {
- padding: 16px;
- background: #ebf4ff;
- border-radius: 8px;
- display: flex;
- align-items: center;
- justify-content: center;
- gap: 12px;
-
- p {
- margin: 0;
- color: #2c5282;
- font-size: 14px;
- }
-}
-
-.loading-section {
- text-align: center;
- padding: 40px 0;
-
- p {
- margin-top: 16px;
- color: #718096;
- font-size: 16px;
- }
-}
-
-.error-section {
- text-align: center;
- padding: 40px 0;
-
- .error-icon {
- font-size: 48px;
- margin-bottom: 16px;
- }
-
- .error-message {
- color: #e53e3e;
- margin-bottom: 20px;
- font-size: 16px;
- }
-
- .retry-btn {
- background: #667eea;
- color: white;
- border: none;
- padding: 12px 32px;
- border-radius: 8px;
- font-size: 16px;
- cursor: pointer;
- transition: background 0.2s;
-
- &:hover {
- background: #5568d3;
- }
- }
-}
-
-.spinner {
- width: 20px;
- height: 20px;
- border: 3px solid #e2e8f0;
- border-top-color: #667eea;
- border-radius: 50%;
- animation: spin 0.8s linear infinite;
-
- &.large {
- width: 48px;
- height: 48px;
- border-width: 4px;
- }
-}
-
-@keyframes spin {
- to {
- transform: rotate(360deg);
- }
-}
-
-.login-footer {
- margin-top: 32px;
- text-align: center;
-
- p {
- font-size: 13px;
- color: rgba(255, 255, 255, 0.8);
- }
-}
-
-@media (max-width: 640px) {
- .login-card {
- padding: 24px;
- }
-
- .login-header h1 {
- font-size: 24px;
- }
-
- .qr-code-container .qr-code {
- width: 200px;
- height: 200px;
- }
-}
diff --git a/src/app/components/login/login.ts b/src/app/components/login/login.ts
deleted file mode 100644
index c49f479..0000000
--- a/src/app/components/login/login.ts
+++ /dev/null
@@ -1,150 +0,0 @@
-import { Component, OnInit, OnDestroy, inject } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { ActivatedRoute, Router } from '@angular/router';
-import { Subject, takeUntil } from 'rxjs';
-import { AuthService } from '../../services/auth.service';
-import { DeviceService } from '../../services/device.service';
-import { QRGenerationResponse, ProjectType } from '../../models/auth.model';
-import { environment } from '../../../environments/environment';
-
-@Component({
- selector: 'app-login',
- imports: [CommonModule],
- templateUrl: './login.html',
- styleUrl: './login.scss'
-})
-export class LoginComponent implements OnInit, OnDestroy {
- private authService = inject(AuthService);
- private deviceService = inject(DeviceService);
- private route = inject(ActivatedRoute);
- private router = inject(Router);
- private destroy$ = new Subject();
-
- qrCode: string = '';
- sessionId: string = '';
- project: ProjectType = 'unknown';
- projectName: string = '';
- isLoading: boolean = false;
- error: string = '';
- expiresIn: number = 0;
- isPolling: boolean = false;
- deviceInfo: string = '';
-
- ngOnInit(): void {
- // Get project from query params
- this.route.queryParams.pipe(takeUntil(this.destroy$)).subscribe(params => {
- this.project = params['project'] || 'unknown';
- this.projectName = this.getProjectName(this.project);
-
- // Get redirect URL if provided
- const redirect = params['redirect'];
- if (redirect) {
- localStorage.setItem('redirectUrl', redirect);
- }
-
- // Get context if provided
- const context = params['context'];
- if (context) {
- this.deviceService.setContext(context);
- }
-
- this.deviceService.setProject(this.project);
- this.deviceInfo = this.deviceService.getDeviceDescription();
-
- // Generate QR code
- this.generateQRCode();
- });
- }
-
- ngOnDestroy(): void {
- this.destroy$.next();
- this.destroy$.complete();
- }
-
- /**
- * Generate QR code for authentication
- */
- generateQRCode(): void {
- this.isLoading = true;
- this.error = '';
-
- this.authService.generateQRCode(this.project)
- .pipe(takeUntil(this.destroy$))
- .subscribe({
- next: (response: QRGenerationResponse) => {
- this.qrCode = response.qrCode;
- this.sessionId = response.sessionId;
- this.expiresIn = response.expiresIn;
- this.isLoading = false;
-
- // Start polling for authentication status
- this.startPolling();
-
- // Auto-refresh QR code before expiration
- setTimeout(() => {
- if (!this.authService.isAuthenticated()) {
- this.generateQRCode();
- }
- }, (response.expiresIn - 5) * 1000);
- },
- error: (error) => {
- this.error = error.message || 'Failed to generate QR code';
- this.isLoading = false;
- this.router.navigate(['/error'], {
- queryParams: {
- message: this.error,
- code: 'QR_GENERATION_FAILED'
- }
- });
- }
- });
- }
-
- /**
- * Start polling for authentication status
- */
- private startPolling(): void {
- if (this.isPolling) return;
-
- this.isPolling = true;
-
- this.authService.startQRAuthPolling(this.sessionId)
- .pipe(takeUntil(this.destroy$))
- .subscribe({
- next: (status) => {
- if (status.authenticated) {
- console.log('Authentication successful!');
- // Redirect will be handled by AuthService
- const redirectUrl = localStorage.getItem('redirectUrl');
- if (redirectUrl) {
- this.authService.redirectAfterAuth(redirectUrl);
- } else {
- this.router.navigate(['/success']);
- }
- }
- },
- error: (error) => {
- this.isPolling = false;
- console.error('Polling error:', error);
- },
- complete: () => {
- this.isPolling = false;
- }
- });
- }
-
- /**
- * Get project display name
- */
- private getProjectName(project: ProjectType): string {
- const projects = environment.projects as any;
- return projects[project] || project;
- }
-
- /**
- * Refresh QR code manually
- */
- refreshQR(): void {
- this.generateQRCode();
- }
-}
diff --git a/src/app/components/success/success.html b/src/app/components/success/success.html
deleted file mode 100644
index b41e56e..0000000
--- a/src/app/components/success/success.html
+++ /dev/null
@@ -1,50 +0,0 @@
-
-
-
-
-
Authentication Successful!
-
- @if (user) {
-
- Welcome back, {{ user.username }}
-
- }
-
- @if (!user) {
-
- You have been successfully authenticated.
-
- }
-
- @if (countdown > 0) {
-
-
- {{ countdown }}
-
-
Redirecting in {{ countdown }} second{{ countdown !== 1 ? 's' : '' }}...
-
- }
-
- @if (countdown < 0) {
-
-
- You can now close this window and return to your application.
-
-
- }
-
- @if (countdown > 0) {
-
-
- Continue Now โ
-
-
- }
-
-
-
-
diff --git a/src/app/components/success/success.scss b/src/app/components/success/success.scss
deleted file mode 100644
index 8ba199f..0000000
--- a/src/app/components/success/success.scss
+++ /dev/null
@@ -1,181 +0,0 @@
-.success-container {
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background: linear-gradient(135deg, #84fab0 0%, #8fd3f4 100%);
- padding: 20px;
-}
-
-.success-card {
- background: white;
- border-radius: 16px;
- box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
- max-width: 500px;
- width: 100%;
- padding: 48px 40px;
- text-align: center;
- animation: slideIn 0.4s ease-out;
-}
-
-@keyframes slideIn {
- from {
- opacity: 0;
- transform: translateY(20px);
- }
- to {
- opacity: 1;
- transform: translateY(0);
- }
-}
-
-.success-icon-container {
- margin-bottom: 24px;
-
- .success-icon {
- width: 80px;
- height: 80px;
- margin: 0 auto;
- background: #48bb78;
- color: white;
- font-size: 48px;
- font-weight: bold;
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- animation: scaleIn 0.5s ease-out;
- }
-}
-
-@keyframes scaleIn {
- 0% {
- transform: scale(0);
- opacity: 0;
- }
- 50% {
- transform: scale(1.1);
- }
- 100% {
- transform: scale(1);
- opacity: 1;
- }
-}
-
-.success-title {
- font-size: 28px;
- font-weight: 700;
- color: #2d3748;
- margin-bottom: 16px;
-}
-
-.success-message {
- font-size: 18px;
- color: #4a5568;
- margin-bottom: 32px;
- line-height: 1.6;
-
- strong {
- color: #48bb78;
- }
-}
-
-.redirect-info {
- margin-bottom: 32px;
-
- .countdown-circle {
- width: 80px;
- height: 80px;
- margin: 0 auto 16px;
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
- border-radius: 50%;
- display: flex;
- align-items: center;
- justify-content: center;
- position: relative;
- animation: pulse 1s ease-in-out infinite;
-
- .countdown-number {
- font-size: 36px;
- font-weight: 700;
- color: white;
- }
- }
-
- p {
- font-size: 16px;
- color: #718096;
- }
-}
-
-@keyframes pulse {
- 0%, 100% {
- transform: scale(1);
- opacity: 1;
- }
- 50% {
- transform: scale(1.05);
- opacity: 0.9;
- }
-}
-
-.no-redirect {
- margin-bottom: 32px;
-
- .info-text {
- font-size: 16px;
- color: #4a5568;
- padding: 16px;
- background: #f7fafc;
- border-radius: 8px;
- border-left: 4px solid #48bb78;
- }
-}
-
-.success-actions {
- .btn {
- padding: 14px 32px;
- border: none;
- border-radius: 8px;
- font-size: 16px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s;
-
- &.btn-primary {
- background: #667eea;
- color: white;
-
- &:hover {
- background: #5568d3;
- transform: translateY(-2px);
- box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
- }
- }
- }
-}
-
-.success-footer {
- margin-top: 32px;
- text-align: center;
-
- p {
- font-size: 13px;
- color: rgba(255, 255, 255, 0.9);
- }
-}
-
-@media (max-width: 640px) {
- .success-card {
- padding: 32px 24px;
- }
-
- .success-title {
- font-size: 24px;
- }
-
- .success-message {
- font-size: 16px;
- }
-}
diff --git a/src/app/components/success/success.ts b/src/app/components/success/success.ts
deleted file mode 100644
index c77b8b6..0000000
--- a/src/app/components/success/success.ts
+++ /dev/null
@@ -1,46 +0,0 @@
-import { Component, OnInit, inject } from '@angular/core';
-import { CommonModule } from '@angular/common';
-import { Router } from '@angular/router';
-import { AuthService } from '../../services/auth.service';
-
-@Component({
- selector: 'app-success',
- imports: [CommonModule],
- templateUrl: './success.html',
- styleUrl: './success.scss'
-})
-export class SuccessComponent implements OnInit {
- private authService = inject(AuthService);
- private router = inject(Router);
-
- countdown: number = 3;
- user: any = null;
-
- ngOnInit(): void {
- this.user = this.authService.getCurrentUser();
-
- // Countdown before redirect
- const interval = setInterval(() => {
- this.countdown--;
-
- if (this.countdown <= 0) {
- clearInterval(interval);
- this.redirect();
- }
- }, 1000);
- }
-
- redirect(): void {
- const redirectUrl = localStorage.getItem('redirectUrl');
- if (redirectUrl) {
- this.authService.redirectAfterAuth(redirectUrl);
- } else {
- // No redirect URL, show message
- this.countdown = -1;
- }
- }
-
- redirectNow(): void {
- this.redirect();
- }
-}
diff --git a/src/app/guards/auth.guard.ts b/src/app/guards/auth.guard.ts
deleted file mode 100644
index 3a362ee..0000000
--- a/src/app/guards/auth.guard.ts
+++ /dev/null
@@ -1,48 +0,0 @@
-import { inject } from '@angular/core';
-import { Router, CanActivateFn } from '@angular/router';
-import { AuthService } from '../services/auth.service';
-
-/**
- * Auth guard to protect routes
- * Redirects to login if user is not authenticated
- */
-export const authGuard: CanActivateFn = (route, state) => {
- const authService = inject(AuthService);
- const router = inject(Router);
-
- if (authService.isAuthenticated()) {
- return true;
- }
-
- // Store the attempted URL for redirecting after login
- const project = route.queryParams['project'] || 'unknown';
- localStorage.setItem('redirectUrl', state.url);
-
- // Redirect to login
- router.navigate(['/login'], {
- queryParams: {
- project: project,
- redirect: state.url
- }
- });
-
- return false;
-};
-
-/**
- * Guest guard - prevents authenticated users from accessing certain routes
- * Useful for login pages
- */
-export const guestGuard: CanActivateFn = (route, state) => {
- const authService = inject(AuthService);
-
- if (!authService.isAuthenticated()) {
- return true;
- }
-
- // Already authenticated, redirect to home or stored redirect URL
- const redirectUrl = localStorage.getItem('redirectUrl') || '/';
- window.location.href = redirectUrl;
-
- return false;
-};
diff --git a/src/app/models/auth.model.ts b/src/app/models/auth.model.ts
deleted file mode 100644
index e928520..0000000
--- a/src/app/models/auth.model.ts
+++ /dev/null
@@ -1,139 +0,0 @@
-/**
- * Project identifier for authentication
- */
-export type ProjectType = 'dexar' | 'novo' | 'fastcheck' | 'backoffice' | string;
-
-/**
- * Device type classification
- */
-export type DeviceType = 'mobile' | 'desktop' | 'tablet' | null;
-
-/**
- * Operating system type
- */
-export type DeviceOS = 'android' | 'ios' | 'windows' | 'macos' | 'linux' | null;
-
-/**
- * Authentication context
- */
-export type AuthContext = 'browser' | 'application' | 'telegram' | null;
-
-/**
- * Complete device information
- */
-export interface DeviceInfo {
- deviceType: DeviceType;
- deviceOS: DeviceOS;
- context: AuthContext;
- project: ProjectType;
- userAgent?: string;
- screenResolution?: string;
- browserName?: string;
- browserVersion?: string;
-}
-
-/**
- * QR code generation request
- */
-export interface QRGenerationRequest {
- project: ProjectType;
- deviceInfo: DeviceInfo;
-}
-
-/**
- * QR code generation response
- */
-export interface QRGenerationResponse {
- sessionId: string;
- qrCode: string; // base64 image or URL
- expiresAt: string;
- expiresIn: number; // seconds
-}
-
-/**
- * QR code scan request
- */
-export interface QRScanRequest {
- sessionId: string;
- deviceInfo: DeviceInfo;
-}
-
-/**
- * Authentication status check
- */
-export interface AuthStatusResponse {
- authenticated: boolean;
- token?: string;
- userId?: string;
- expiresAt?: string;
- userInfo?: {
- username: string;
- email?: string;
- role?: string;
- };
-}
-
-/**
- * Login request (traditional)
- */
-export interface LoginRequest {
- username: string;
- password: string;
- deviceInfo: DeviceInfo;
-}
-
-/**
- * Login response
- */
-export interface LoginResponse {
- success: boolean;
- token?: string;
- userId?: string;
- expiresAt?: string;
- message?: string;
- userInfo?: {
- username: string;
- email?: string;
- role?: string;
- };
-}
-
-/**
- * Session validation request
- */
-export interface SessionValidationRequest {
- token: string;
- deviceInfo: DeviceInfo;
-}
-
-/**
- * Session validation response
- */
-export interface SessionValidationResponse {
- valid: boolean;
- userId?: string;
- expiresAt?: string;
- userInfo?: {
- username: string;
- email?: string;
- role?: string;
- };
-}
-
-/**
- * Logout request
- */
-export interface LogoutRequest {
- token: string;
- deviceInfo: DeviceInfo;
-}
-
-/**
- * Error response
- */
-export interface ErrorResponse {
- error: boolean;
- message: string;
- code?: string;
- details?: any;
-}
diff --git a/src/app/services/api.service.ts b/src/app/services/api.service.ts
deleted file mode 100644
index dee3eeb..0000000
--- a/src/app/services/api.service.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { Injectable, inject } from '@angular/core';
-import { HttpClient, HttpHeaders } from '@angular/common/http';
-import { Observable, throwError } from 'rxjs';
-import { catchError } from 'rxjs/operators';
-import { environment } from '../../environments/environment';
-import {
- QRGenerationRequest,
- QRGenerationResponse,
- QRScanRequest,
- AuthStatusResponse,
- LoginRequest,
- LoginResponse,
- SessionValidationRequest,
- SessionValidationResponse,
- LogoutRequest,
- ErrorResponse
-} from '../models/auth.model';
-
-@Injectable({
- providedIn: 'root'
-})
-export class ApiService {
- private http = inject(HttpClient);
- private apiUrl = environment.apiUrl;
-
- /**
- * Generate QR code for authentication
- */
- generateQR(request: QRGenerationRequest): Observable {
- return this.http
- .post(`${this.apiUrl}/auth/qr/generate`, request)
- .pipe(catchError(this.handleError));
- }
-
- /**
- * Scan QR code and authenticate
- */
- scanQR(request: QRScanRequest): Observable {
- return this.http
- .post(`${this.apiUrl}/auth/qr/scan`, request)
- .pipe(catchError(this.handleError));
- }
-
- /**
- * Check authentication status for a session
- */
- checkAuthStatus(sessionId: string): Observable {
- return this.http
- .get(`${this.apiUrl}/auth/qr/status/${sessionId}`)
- .pipe(catchError(this.handleError));
- }
-
- /**
- * Traditional login with username and password
- */
- login(request: LoginRequest): Observable {
- return this.http
- .post(`${this.apiUrl}/auth/login`, request)
- .pipe(catchError(this.handleError));
- }
-
- /**
- * Validate session token
- */
- validateSession(request: SessionValidationRequest): Observable {
- const headers = new HttpHeaders({
- Authorization: `Bearer ${request.token}`
- });
-
- return this.http
- .post(
- `${this.apiUrl}/auth/validate`,
- { deviceInfo: request.deviceInfo },
- { headers }
- )
- .pipe(catchError(this.handleError));
- }
-
- /**
- * Logout and invalidate session
- */
- logout(request: LogoutRequest): Observable<{ success: boolean }> {
- const headers = new HttpHeaders({
- Authorization: `Bearer ${request.token}`
- });
-
- return this.http
- .post<{ success: boolean }>(
- `${this.apiUrl}/auth/logout`,
- { deviceInfo: request.deviceInfo },
- { headers }
- )
- .pipe(catchError(this.handleError));
- }
-
- /**
- * Handle HTTP errors
- */
- private handleError(error: any): Observable {
- let errorMessage = 'An error occurred';
-
- if (error.error instanceof ErrorEvent) {
- // Client-side error
- errorMessage = error.error.message;
- } else {
- // Server-side error
- errorMessage = error.error?.message || error.message || errorMessage;
- }
-
- console.error('API Error:', errorMessage, error);
- return throwError(() => ({
- error: true,
- message: errorMessage,
- code: error.status,
- details: error.error
- } as ErrorResponse));
- }
-}
diff --git a/src/app/services/auth.service.ts b/src/app/services/auth.service.ts
deleted file mode 100644
index 5b5c1b3..0000000
--- a/src/app/services/auth.service.ts
+++ /dev/null
@@ -1,226 +0,0 @@
-import { Injectable, inject } from '@angular/core';
-import { Router } from '@angular/router';
-import { BehaviorSubject, Observable, interval } from 'rxjs';
-import { switchMap, takeWhile, tap } from 'rxjs/operators';
-import { ApiService } from './api.service';
-import { DeviceService } from './device.service';
-import {
- LoginRequest,
- LoginResponse,
- QRGenerationResponse,
- AuthStatusResponse,
- ProjectType
-} from '../models/auth.model';
-import { environment } from '../../environments/environment';
-
-@Injectable({
- providedIn: 'root'
-})
-export class AuthService {
- private apiService = inject(ApiService);
- private deviceService = inject(DeviceService);
- private router = inject(Router);
-
- private authTokenSubject = new BehaviorSubject(this.getStoredToken());
- public authToken$ = this.authTokenSubject.asObservable();
-
- private currentUserSubject = new BehaviorSubject(null);
- public currentUser$ = this.currentUserSubject.asObservable();
-
- private isAuthenticatedSubject = new BehaviorSubject(this.hasValidToken());
- public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
-
- constructor() {
- // Validate token on init
- if (this.hasValidToken()) {
- this.validateCurrentSession();
- }
- }
-
- /**
- * Generate QR code for authentication
- */
- generateQRCode(project: ProjectType): Observable {
- const deviceInfo = this.deviceService.getDeviceInfo(project);
-
- return this.apiService.generateQR({ project, deviceInfo });
- }
-
- /**
- * Scan QR code and authenticate
- */
- scanQRCode(sessionId: string): Observable {
- const project = this.deviceService.getDeviceInfo().project;
- const deviceInfo = this.deviceService.getDeviceInfo(project);
-
- return this.apiService.scanQR({ sessionId, deviceInfo }).pipe(
- tap(response => {
- if (response.success && response.token) {
- this.handleSuccessfulAuth(response);
- }
- })
- );
- }
-
- /**
- * Start polling for QR code authentication status
- */
- startQRAuthPolling(sessionId: string): Observable {
- return interval(environment.sessionCheckInterval).pipe(
- switchMap(() => this.apiService.checkAuthStatus(sessionId)),
- takeWhile(status => !status.authenticated, true),
- tap(status => {
- if (status.authenticated && status.token) {
- this.handleSuccessfulAuth({
- success: true,
- token: status.token,
- userId: status.userId,
- expiresAt: status.expiresAt,
- userInfo: status.userInfo
- });
- }
- })
- );
- }
-
- /**
- * Traditional login with username and password
- */
- login(username: string, password: string, project: ProjectType): Observable {
- const deviceInfo = this.deviceService.getDeviceInfo(project);
- const request: LoginRequest = {
- username,
- password,
- deviceInfo
- };
-
- return this.apiService.login(request).pipe(
- tap(response => {
- if (response.success && response.token) {
- this.handleSuccessfulAuth(response);
- }
- })
- );
- }
-
- /**
- * Logout current user
- */
- logout(): void {
- const token = this.getStoredToken();
-
- if (token) {
- const deviceInfo = this.deviceService.getDeviceInfo();
- this.apiService.logout({ token, deviceInfo }).subscribe({
- next: () => this.clearAuth(),
- error: () => this.clearAuth()
- });
- } else {
- this.clearAuth();
- }
- }
-
- /**
- * Validate current session
- */
- private validateCurrentSession(): void {
- const token = this.getStoredToken();
-
- if (!token) {
- this.clearAuth();
- return;
- }
-
- const deviceInfo = this.deviceService.getDeviceInfo();
-
- this.apiService.validateSession({ token, deviceInfo }).subscribe({
- next: (response) => {
- if (response.valid) {
- this.currentUserSubject.next(response.userInfo);
- this.isAuthenticatedSubject.next(true);
- } else {
- this.clearAuth();
- }
- },
- error: () => {
- this.clearAuth();
- }
- });
- }
-
- /**
- * Handle successful authentication
- */
- private handleSuccessfulAuth(response: LoginResponse): void {
- if (response.token) {
- localStorage.setItem('authToken', response.token);
- localStorage.setItem('userId', response.userId || '');
- localStorage.setItem('expiresAt', response.expiresAt || '');
-
- this.authTokenSubject.next(response.token);
- this.currentUserSubject.next(response.userInfo);
- this.isAuthenticatedSubject.next(true);
- }
- }
-
- /**
- * Clear authentication data
- */
- private clearAuth(): void {
- localStorage.removeItem('authToken');
- localStorage.removeItem('userId');
- localStorage.removeItem('expiresAt');
-
- this.authTokenSubject.next(null);
- this.currentUserSubject.next(null);
- this.isAuthenticatedSubject.next(false);
- }
-
- /**
- * Get stored auth token
- */
- getStoredToken(): string | null {
- return localStorage.getItem('authToken');
- }
-
- /**
- * Check if user has valid token
- */
- hasValidToken(): boolean {
- const token = this.getStoredToken();
- const expiresAt = localStorage.getItem('expiresAt');
-
- if (!token || !expiresAt) {
- return false;
- }
-
- // Check if token is expired
- const expiryDate = new Date(expiresAt);
- const now = new Date();
-
- return expiryDate > now;
- }
-
- /**
- * Check if user is authenticated
- */
- isAuthenticated(): boolean {
- return this.isAuthenticatedSubject.value;
- }
-
- /**
- * Get current user
- */
- getCurrentUser(): any {
- return this.currentUserSubject.value;
- }
-
- /**
- * Redirect after authentication
- */
- redirectAfterAuth(defaultUrl: string = '/'): void {
- const redirectUrl = localStorage.getItem('redirectUrl') || defaultUrl;
- localStorage.removeItem('redirectUrl');
- window.location.href = redirectUrl;
- }
-}
diff --git a/src/app/services/device.service.ts b/src/app/services/device.service.ts
deleted file mode 100644
index f10720b..0000000
--- a/src/app/services/device.service.ts
+++ /dev/null
@@ -1,194 +0,0 @@
-import { Injectable } from '@angular/core';
-import { UAParser } from 'ua-parser-js';
-import { DeviceInfo, DeviceType, DeviceOS, AuthContext, ProjectType } from '../models/auth.model';
-
-@Injectable({
- providedIn: 'root'
-})
-export class DeviceService {
- private parser: UAParser;
- private detectedContext: AuthContext = null;
- private detectedProject: ProjectType = 'unknown';
-
- constructor() {
- this.parser = new UAParser();
- this.detectContext();
- }
-
- /**
- * Get complete device information
- */
- getDeviceInfo(project?: ProjectType, context?: AuthContext): DeviceInfo {
- return {
- deviceType: this.getDeviceType(),
- deviceOS: this.getDeviceOS(),
- context: context || this.detectedContext,
- project: project || this.detectedProject,
- userAgent: navigator.userAgent,
- screenResolution: `${window.screen.width}x${window.screen.height}`,
- browserName: this.getBrowserName(),
- browserVersion: this.getBrowserVersion()
- };
- }
-
- /**
- * Set project identifier
- */
- setProject(project: ProjectType): void {
- this.detectedProject = project;
- }
-
- /**
- * Set authentication context
- */
- setContext(context: AuthContext): void {
- this.detectedContext = context;
- }
-
- /**
- * Detect device type (mobile, desktop, tablet)
- */
- private getDeviceType(): DeviceType {
- const device = this.parser.getDevice();
-
- if (device.type === 'mobile') {
- return 'mobile';
- } else if (device.type === 'tablet') {
- return 'tablet';
- } else if (device.type === 'smarttv' || device.type === 'wearable' || device.type === 'embedded') {
- return null;
- }
-
- // Check by screen size if device type is not detected
- const width = window.innerWidth;
- if (width < 768) {
- return 'mobile';
- } else if (width >= 768 && width < 1024) {
- return 'tablet';
- } else {
- return 'desktop';
- }
- }
-
- /**
- * Detect operating system
- */
- private getDeviceOS(): DeviceOS {
- const os = this.parser.getOS();
- const osName = os.name?.toLowerCase() || '';
-
- if (osName.includes('android')) {
- return 'android';
- } else if (osName.includes('ios') || osName.includes('iphone') || osName.includes('ipad')) {
- return 'ios';
- } else if (osName.includes('windows')) {
- return 'windows';
- } else if (osName.includes('mac')) {
- return 'macos';
- } else if (osName.includes('linux')) {
- return 'linux';
- }
-
- return null;
- }
-
- /**
- * Detect authentication context
- */
- private detectContext(): void {
- // Check if running in Telegram WebApp
- if (this.isTelegramWebApp()) {
- this.detectedContext = 'telegram';
- return;
- }
-
- // Check if running as standalone app (PWA)
- if (this.isStandaloneApp()) {
- this.detectedContext = 'application';
- return;
- }
-
- // Check if running in Electron
- if (this.isElectron()) {
- this.detectedContext = 'application';
- return;
- }
-
- // Default to browser
- this.detectedContext = 'browser';
- }
-
- /**
- * Check if running in Telegram WebApp
- */
- private isTelegramWebApp(): boolean {
- return !!(window as any).Telegram?.WebApp;
- }
-
- /**
- * Check if running as standalone app (PWA)
- */
- private isStandaloneApp(): boolean {
- return (
- window.matchMedia('(display-mode: standalone)').matches ||
- (window.navigator as any).standalone === true ||
- document.referrer.includes('android-app://')
- );
- }
-
- /**
- * Check if running in Electron
- */
- private isElectron(): boolean {
- const ua = navigator.userAgent.toLowerCase();
- return ua.includes('electron');
- }
-
- /**
- * Get browser name
- */
- private getBrowserName(): string {
- const browser = this.parser.getBrowser();
- return browser.name || 'Unknown';
- }
-
- /**
- * Get browser version
- */
- private getBrowserVersion(): string {
- const browser = this.parser.getBrowser();
- return browser.version || 'Unknown';
- }
-
- /**
- * Check if device is mobile
- */
- isMobile(): boolean {
- return this.getDeviceType() === 'mobile';
- }
-
- /**
- * Check if device is desktop
- */
- isDesktop(): boolean {
- return this.getDeviceType() === 'desktop';
- }
-
- /**
- * Check if device is tablet
- */
- isTablet(): boolean {
- return this.getDeviceType() === 'tablet';
- }
-
- /**
- * Get readable device description
- */
- getDeviceDescription(): string {
- const deviceType = this.getDeviceType() || 'Unknown device';
- const os = this.getDeviceOS() || 'unknown OS';
- const context = this.detectedContext || 'unknown context';
-
- return `${deviceType} with ${os} via ${context}`;
- }
-}
diff --git a/src/environments/environment.production.ts b/src/environments/environment.production.ts
deleted file mode 100644
index 2e28ce0..0000000
--- a/src/environments/environment.production.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export const environment = {
- production: true,
- apiUrl: 'https://api.dx-projects.com/api',
- qrRefreshInterval: 60000,
- sessionCheckInterval: 2000,
- projects: {
- dexar: 'Dexar Platform',
- novo: 'Novo Markets',
- fastcheck: 'FastCheck',
- backoffice: 'Market BackOffice'
- }
-};
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
deleted file mode 100644
index 2b21e62..0000000
--- a/src/environments/environment.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-export const environment = {
- production: false,
- apiUrl: 'http://localhost:3000/api',
- qrRefreshInterval: 60000, // 60 seconds
- sessionCheckInterval: 2000, // 2 seconds
- projects: {
- dexar: 'Dexar Platform',
- novo: 'Novo Markets',
- fastcheck: 'FastCheck',
- backoffice: 'Market BackOffice'
- }
-};
diff --git a/src/index.html b/src/index.html
index 0175939..0b1cbe7 100644
--- a/src/index.html
+++ b/src/index.html
@@ -2,7 +2,7 @@
- AuthService
+ Telegram Login Dialog
diff --git a/src/styles.scss b/src/styles.scss
index 5cc49b9..b062178 100644
--- a/src/styles.scss
+++ b/src/styles.scss
@@ -1,24 +1,9 @@
* {
- margin: 0;
- padding: 0;
box-sizing: border-box;
}
-html, body {
- height: 100%;
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
-}
-
-:root {
- --primary-color: #667eea;
- --primary-dark: #5568d3;
- --secondary-color: #764ba2;
- --success-color: #48bb78;
- --error-color: #f56565;
- --warning-color: #ed8936;
- --text-primary: #2d3748;
- --text-secondary: #4a5568;
- --text-muted: #718096;
- --bg-light: #f7fafc;
- --border-color: #e2e8f0;
+html,
+body {
+ margin: 0;
+ min-height: 100%;
}