This commit is contained in:
sdarbinyan
2026-06-01 00:47:26 +04:00
parent 49f69f6af0
commit 4d8dc6b59c
22 changed files with 1266 additions and 353 deletions

View File

@@ -29,6 +29,12 @@
{{ 'auth.loginWithTelegram' | translate }}
</button>
@if (loginUrl()) {
<a class="bot-link" [href]="loginUrl()" target="_blank" rel="noopener noreferrer">
{{ loginUrl() }}
</a>
}
<div class="qr-section">
<p class="qr-hint">{{ 'auth.orScanQr' | translate }}</p>
@@ -57,12 +63,12 @@
</div>
}
@case ('error') {
<div class="qr-container">
<img [src]="'https://api.qrserver.com/v1/create-qr-code/?size=180x180&data=' + encodedQrUrl()"
alt="QR Code"
width="180"
height="180"
loading="lazy" />
<div class="qr-container qr-error" (click)="refreshQr()">
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M1 4v6h6M23 20v-6h-6"/>
<path d="M20.49 9A9 9 0 0 0 5.64 5.64L1 10m22 4l-4.64 4.36A9 9 0 0 1 3.51 15"/>
</svg>
<span>{{ 'auth.qrError' | translate }}</span>
</div>
}
}

View File

@@ -102,6 +102,20 @@ h2 {
}
}
.bot-link {
display: block;
margin-top: 10px;
color: var(--accent-color, #497671);
font-size: 12px;
line-height: 1.35;
overflow-wrap: anywhere;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
.qr-section {
margin-top: 20px;
@@ -158,6 +172,26 @@ h2 {
font-size: 13px;
}
}
&.qr-error {
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8px;
width: 204px;
height: 204px;
cursor: pointer;
color: var(--text-secondary, #999);
transition: color 0.2s ease;
&:hover {
color: var(--accent-color, #497671);
}
span {
font-size: 13px;
}
}
}
}

View File

@@ -19,10 +19,11 @@ export class TelegramLoginComponent implements OnDestroy {
status = this.authService.status;
loginUrl = signal('');
qrToken = signal('');
webSessionID = signal('');
qrStatus = signal<'loading' | 'ready' | 'expired' | 'error'>('loading');
encodedQrUrl = computed(() => encodeURIComponent(this.loginUrl()));
private readonly pollIntervalMs = 5000;
private pollTimer?: ReturnType<typeof setInterval>;
constructor() {
@@ -45,12 +46,14 @@ export class TelegramLoginComponent implements OnDestroy {
}
openTelegramLogin(): void {
window.open(this.loginUrl(), '_blank');
const url = this.loginUrl();
if (!url) return;
window.open(url, '_blank');
if (!this.pollTimer) {
if (this.qrToken()) {
this.startPolling(this.qrToken());
} else {
this.startSessionPolling();
const webSessionID = this.webSessionID();
if (webSessionID) {
this.startPolling(webSessionID);
}
}
}
@@ -62,43 +65,25 @@ export class TelegramLoginComponent implements OnDestroy {
private initQrLogin(): void {
this.qrStatus.set('loading');
this.authService.createQrToken().subscribe({
this.loginUrl.set('');
this.webSessionID.set('');
this.authService.createWebSession().subscribe({
next: (res) => {
this.loginUrl.set(res.url);
this.qrToken.set(res.token);
this.webSessionID.set(res.webSessionID);
this.qrStatus.set('ready');
this.startPolling(res.token);
this.startPolling(res.webSessionID);
},
error: () => {
this.loginUrl.set(this.authService.getTelegramLoginUrl());
this.qrStatus.set('error');
this.startSessionPolling();
}
});
}
private startSessionPolling(): void {
private startPolling(webSessionID: string): void {
this.stopPolling();
let checks = 0;
this.pollTimer = setInterval(() => {
checks++;
if (checks > 100) {
this.stopPolling();
this.qrStatus.set('expired');
return;
}
this.authService.checkSessionOnce().subscribe(session => {
if (session && session.active) {
this.stopPolling();
this.syncCartAndComplete(session.sessionId);
}
});
}, 3000);
}
private startPolling(token: string): void {
this.stopPolling();
if (!token) return;
if (!webSessionID) return;
let checks = 0;
this.pollTimer = setInterval(() => {
@@ -109,28 +94,18 @@ export class TelegramLoginComponent implements OnDestroy {
return;
}
this.authService.pollQrToken(token).subscribe({
next: (res) => {
switch (res.status) {
case 'confirmed':
this.stopPolling();
if (res.session) {
this.syncCartAndComplete(res.session.sessionId);
} else {
this.authService.onTelegramLoginComplete();
}
break;
case 'expired':
this.stopPolling();
this.qrStatus.set('expired');
break;
this.authService.checkSessionOnce(webSessionID).subscribe({
next: (session) => {
if (session?.active) {
this.stopPolling();
this.syncCartAndComplete(session.sessionId);
}
},
error: () => {
// Network error — keep polling
}
});
}, 3000);
}, this.pollIntervalMs);
}
private syncCartAndComplete(sessionId: string): void {