- Fix TEST_ORACLE_USER quotes in .env.test for shell source compatibility - Format 14 frontend files with Prettier (stores, views, utils) - All 122 tests passing (77 telegram + 35 backend + 10 E2E) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
292 lines
6.8 KiB
Vue
292 lines
6.8 KiB
Vue
<template>
|
|
<main class="main-content">
|
|
<div class="app-container">
|
|
<!-- Page Header -->
|
|
<div class="page-header">
|
|
<h1 class="page-title">Telegram Bot</h1>
|
|
<p class="page-subtitle">
|
|
Conectează-ți contul pentru acces rapid din Telegram
|
|
</p>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div v-if="loading" class="loading-state">
|
|
<div class="loading-spinner"></div>
|
|
<p>Se generează codul...</p>
|
|
</div>
|
|
|
|
<!-- Main Card -->
|
|
<div v-else class="card">
|
|
<!-- Generate Button -->
|
|
<div class="generate-section">
|
|
<button
|
|
@click="generateCode"
|
|
:disabled="loading"
|
|
class="btn btn-primary btn-lg"
|
|
>
|
|
{{ loading ? "Se generează..." : "Generează Cod" }}
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Code Display & Actions -->
|
|
<div v-if="linkingCode" class="code-section">
|
|
<!-- Code Display -->
|
|
<div class="code-display">
|
|
<div class="code-header">
|
|
<span class="code-label">Cod</span>
|
|
<span class="code-timer">{{ formatTime(timeRemaining) }}</span>
|
|
</div>
|
|
<div class="code-value">{{ linkingCode }}</div>
|
|
</div>
|
|
|
|
<!-- Action Buttons -->
|
|
<div class="action-buttons">
|
|
<a
|
|
:href="telegramDeepLink"
|
|
target="_blank"
|
|
rel="noopener noreferrer"
|
|
class="btn btn-primary action-btn"
|
|
>
|
|
Deschide Telegram
|
|
</a>
|
|
<Button
|
|
:label="showQR ? 'Ascunde QR' : 'Arată QR'"
|
|
@click="showQR = !showQR"
|
|
class="action-btn"
|
|
outlined
|
|
/>
|
|
<Button
|
|
label="Copiază Cod"
|
|
@click="copyCode"
|
|
class="action-btn"
|
|
outlined
|
|
/>
|
|
</div>
|
|
|
|
<!-- QR Code Display -->
|
|
<div v-if="showQR" class="qr-section">
|
|
<QRCodeVue :value="telegramDeepLink" :size="200" level="H" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { ref, computed, onUnmounted } from "vue";
|
|
import { useToast } from "primevue/usetoast";
|
|
import Button from "primevue/button";
|
|
import Toast from "primevue/toast";
|
|
import QRCodeVue from "qrcode.vue";
|
|
import { apiService } from "../services/api";
|
|
|
|
const toast = useToast();
|
|
|
|
// State
|
|
const linkingCode = ref("");
|
|
const timeRemaining = ref(0);
|
|
const loading = ref(false);
|
|
const showQR = ref(false);
|
|
|
|
let countdownInterval = null;
|
|
|
|
// Config
|
|
const BOT_USERNAME =
|
|
import.meta.env.VITE_TELEGRAM_BOT_USERNAME || "roa2web_bot";
|
|
|
|
// Computed
|
|
const telegramDeepLink = computed(() => {
|
|
if (!linkingCode.value) return "";
|
|
return `https://t.me/${BOT_USERNAME}?start=${linkingCode.value}`;
|
|
});
|
|
|
|
// Methods
|
|
const generateCode = async () => {
|
|
loading.value = true;
|
|
showQR.value = false;
|
|
|
|
try {
|
|
const response = await apiService.post("/telegram/auth/generate-code");
|
|
linkingCode.value = response.data.linking_code;
|
|
timeRemaining.value = response.data.expires_in_minutes * 60;
|
|
|
|
toast.add({
|
|
severity: "success",
|
|
summary: "Cod Generat",
|
|
detail: "Alege o metodă de conectare",
|
|
life: 3000,
|
|
});
|
|
|
|
startCountdown();
|
|
} catch (error) {
|
|
console.error("Error generating code:", error);
|
|
toast.add({
|
|
severity: "error",
|
|
summary: "Eroare",
|
|
detail: error.response?.data?.detail || "Nu am putut genera codul",
|
|
life: 5000,
|
|
});
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
};
|
|
|
|
const startCountdown = () => {
|
|
if (countdownInterval) clearInterval(countdownInterval);
|
|
|
|
countdownInterval = setInterval(() => {
|
|
if (timeRemaining.value > 0) {
|
|
timeRemaining.value--;
|
|
} else {
|
|
clearInterval(countdownInterval);
|
|
linkingCode.value = "";
|
|
toast.add({
|
|
severity: "warn",
|
|
summary: "Cod Expirat",
|
|
detail: "Generează un cod nou",
|
|
life: 4000,
|
|
});
|
|
}
|
|
}, 1000);
|
|
};
|
|
|
|
const formatTime = (seconds) => {
|
|
const minutes = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return `${minutes}:${secs.toString().padStart(2, "0")}`;
|
|
};
|
|
|
|
const copyCode = async () => {
|
|
try {
|
|
await navigator.clipboard.writeText(linkingCode.value);
|
|
toast.add({
|
|
severity: "success",
|
|
summary: "Copiat",
|
|
detail: "Cod copiat în clipboard",
|
|
life: 2000,
|
|
});
|
|
} catch (error) {
|
|
const tempInput = document.createElement("input");
|
|
tempInput.value = linkingCode.value;
|
|
document.body.appendChild(tempInput);
|
|
tempInput.select();
|
|
document.execCommand("copy");
|
|
document.body.removeChild(tempInput);
|
|
toast.add({
|
|
severity: "success",
|
|
summary: "Copiat",
|
|
life: 2000,
|
|
});
|
|
}
|
|
};
|
|
|
|
onUnmounted(() => {
|
|
if (countdownInterval) clearInterval(countdownInterval);
|
|
});
|
|
</script>
|
|
|
|
<style scoped>
|
|
/* Page Header - Uses global .page-header pattern */
|
|
/* Loading - Uses global .loading-spinner pattern */
|
|
/* Card - Uses global .card pattern */
|
|
|
|
/* Generate Section */
|
|
.generate-section {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding-bottom: var(--space-lg);
|
|
border-bottom: 1px solid var(--color-border);
|
|
}
|
|
|
|
/* Generate button - Uses global .btn .btn-primary pattern */
|
|
|
|
/* Code Section */
|
|
.code-section {
|
|
margin-top: var(--space-lg);
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: var(--space-lg);
|
|
}
|
|
|
|
/* Code Display */
|
|
.code-display {
|
|
background: linear-gradient(
|
|
135deg,
|
|
rgba(67, 97, 238, 0.08),
|
|
rgba(67, 97, 238, 0.02)
|
|
);
|
|
border: 2px solid var(--color-primary);
|
|
border-radius: var(--radius-md);
|
|
padding: var(--space-md);
|
|
text-align: center;
|
|
}
|
|
|
|
.code-header {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-bottom: var(--space-xs);
|
|
font-size: var(--text-sm);
|
|
}
|
|
|
|
.code-label {
|
|
color: var(--color-text-secondary);
|
|
font-weight: var(--font-semibold);
|
|
}
|
|
|
|
.code-timer {
|
|
color: var(--color-primary);
|
|
font-weight: var(--font-bold);
|
|
font-family: "Courier New", monospace;
|
|
}
|
|
|
|
.code-value {
|
|
font-size: 2rem;
|
|
font-weight: var(--font-bold);
|
|
color: var(--color-primary);
|
|
letter-spacing: 0.3em;
|
|
font-family: "Courier New", monospace;
|
|
}
|
|
|
|
/* Action Buttons - Use global .btn patterns */
|
|
.action-buttons {
|
|
display: flex;
|
|
gap: var(--space-sm);
|
|
flex-wrap: wrap;
|
|
}
|
|
|
|
.action-btn {
|
|
flex: 1;
|
|
min-width: 160px;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* QR Section */
|
|
.qr-section {
|
|
display: flex;
|
|
justify-content: center;
|
|
padding: var(--space-lg);
|
|
background: white;
|
|
border-radius: var(--radius-md);
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
}
|
|
|
|
/* Responsive - Telegram-specific adjustments */
|
|
@media (max-width: 768px) {
|
|
.code-value {
|
|
font-size: 1.5rem;
|
|
letter-spacing: 0.2em;
|
|
}
|
|
|
|
.action-buttons {
|
|
flex-direction: column;
|
|
}
|
|
|
|
.action-btn {
|
|
width: 100%;
|
|
min-width: unset;
|
|
}
|
|
}
|
|
</style>
|