feat: Add ShareCard page for bot sharing with QR code
- Add ShareCard action to BotsController for generating bot share page - Create ShareCard.cshtml view with gradient card design - Generate QR code for Telegram bot link using QRCode.js - Display bot info including type, status, description, and version - Add copy link and print card functionality - Add Share Bot button to bot Details page 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
a1af91807e
commit
a6b4ec8fa6
@ -350,6 +350,23 @@ public class BotsController : Controller
|
||||
return RedirectToAction(nameof(Details), new { id });
|
||||
}
|
||||
|
||||
// GET: Admin/Bots/ShareCard/5
|
||||
public async Task<IActionResult> ShareCard(Guid id)
|
||||
{
|
||||
var bot = await _botService.GetBotByIdAsync(id);
|
||||
if (bot == null)
|
||||
return NotFound();
|
||||
|
||||
// Build the tg.me link
|
||||
var telegramLink = !string.IsNullOrEmpty(bot.PlatformUsername)
|
||||
? $"https://t.me/{bot.PlatformUsername}"
|
||||
: null;
|
||||
|
||||
ViewData["TelegramLink"] = telegramLink;
|
||||
|
||||
return View(bot);
|
||||
}
|
||||
|
||||
private string GenerateBotFatherCommands(BotWizardDto dto)
|
||||
{
|
||||
var commands = new List<string>
|
||||
|
||||
@ -306,7 +306,10 @@
|
||||
<a href="/Admin/Bots/Metrics/@Model.Id" class="btn btn-success">
|
||||
<i class="bi bi-graph-up"></i> View Detailed Metrics
|
||||
</a>
|
||||
|
||||
<a href="/Admin/Bots/ShareCard/@Model.Id" class="btn btn-info">
|
||||
<i class="bi bi-share"></i> Share Bot
|
||||
</a>
|
||||
|
||||
@if (Model.Status == LittleShop.Enums.BotStatus.Active)
|
||||
{
|
||||
<form action="/Admin/Bots/Suspend/@Model.Id" method="post">
|
||||
|
||||
336
LittleShop/Areas/Admin/Views/Bots/ShareCard.cshtml
Normal file
336
LittleShop/Areas/Admin/Views/Bots/ShareCard.cshtml
Normal file
@ -0,0 +1,336 @@
|
||||
@model LittleShop.Models.Bot
|
||||
@using LittleShop.Enums
|
||||
@{
|
||||
ViewData["Title"] = "Share Bot";
|
||||
Layout = "_Layout";
|
||||
var telegramLink = ViewData["TelegramLink"] as string;
|
||||
var hasLink = !string.IsNullOrEmpty(telegramLink);
|
||||
}
|
||||
|
||||
<style>
|
||||
.share-card {
|
||||
max-width: 400px;
|
||||
margin: 0 auto;
|
||||
border-radius: 20px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
|
||||
.share-card-header {
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.share-card-header h2 {
|
||||
margin: 0;
|
||||
font-size: 1.8rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.share-card-header .bot-username {
|
||||
opacity: 0.9;
|
||||
font-size: 1rem;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.share-card-body {
|
||||
background: white;
|
||||
padding: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 15px;
|
||||
display: inline-block;
|
||||
margin-bottom: 20px;
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
#qrcode {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#qrcode canvas {
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.telegram-link {
|
||||
display: block;
|
||||
padding: 15px 25px;
|
||||
background: linear-gradient(135deg, #0088cc 0%, #00a8e8 100%);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
border-radius: 50px;
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.telegram-link:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 5px 20px rgba(0, 136, 204, 0.4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.telegram-link i {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.link-display {
|
||||
background: #f8f9fa;
|
||||
padding: 12px 20px;
|
||||
border-radius: 10px;
|
||||
font-family: monospace;
|
||||
font-size: 0.9rem;
|
||||
color: #495057;
|
||||
word-break: break-all;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.copy-btn {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.copy-btn:hover {
|
||||
background: #5a6268;
|
||||
}
|
||||
|
||||
.copy-btn.copied {
|
||||
background: #28a745;
|
||||
}
|
||||
|
||||
.bot-info-list {
|
||||
text-align: left;
|
||||
background: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.bot-info-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 8px 0;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.bot-info-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.bot-info-label {
|
||||
color: #6c757d;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.bot-info-value {
|
||||
font-weight: 600;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.status-badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: #d4edda;
|
||||
color: #155724;
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
background: #f8d7da;
|
||||
color: #721c24;
|
||||
}
|
||||
|
||||
.print-btn {
|
||||
background: transparent;
|
||||
border: 2px solid #6c757d;
|
||||
color: #6c757d;
|
||||
padding: 10px 25px;
|
||||
border-radius: 25px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.print-btn:hover {
|
||||
background: #6c757d;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.no-link-warning {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
padding: 15px;
|
||||
border-radius: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
@@media print {
|
||||
.no-print {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.share-card {
|
||||
box-shadow: none;
|
||||
border: 2px solid #ddd;
|
||||
}
|
||||
|
||||
body {
|
||||
background: white !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="container-fluid">
|
||||
<div class="row mb-4 no-print">
|
||||
<div class="col">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a href="@Url.Action("Index", "Dashboard")">Dashboard</a></li>
|
||||
<li class="breadcrumb-item"><a href="@Url.Action("Index", "Bots")">Bots</a></li>
|
||||
<li class="breadcrumb-item"><a href="@Url.Action("Details", "Bots", new { id = Model.Id })">@Model.Name</a></li>
|
||||
<li class="breadcrumb-item active">Share</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-6">
|
||||
<div class="share-card">
|
||||
<div class="share-card-header">
|
||||
<h2>@Model.Name</h2>
|
||||
@if (!string.IsNullOrEmpty(Model.PlatformUsername))
|
||||
{
|
||||
<div class="bot-username">@@@Model.PlatformUsername</div>
|
||||
}
|
||||
</div>
|
||||
<div class="share-card-body">
|
||||
@if (hasLink)
|
||||
{
|
||||
<div class="qr-container">
|
||||
<div id="qrcode"></div>
|
||||
</div>
|
||||
|
||||
<p class="text-muted mb-3">Scan to start chatting</p>
|
||||
|
||||
<a href="@telegramLink" target="_blank" class="telegram-link">
|
||||
<i class="fab fa-telegram-plane"></i> Open in Telegram
|
||||
</a>
|
||||
|
||||
<div class="link-display" id="linkDisplay">@telegramLink</div>
|
||||
|
||||
<button class="copy-btn" onclick="copyLink()">
|
||||
<i class="fas fa-copy"></i> Copy Link
|
||||
</button>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="no-link-warning">
|
||||
<i class="fas fa-exclamation-triangle"></i>
|
||||
<strong>No Telegram username configured</strong>
|
||||
<p class="mb-0 mt-2">This bot doesn't have a Telegram username yet. Configure the bot with a valid token to enable sharing.</p>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="bot-info-list">
|
||||
<div class="bot-info-item">
|
||||
<span class="bot-info-label">Type</span>
|
||||
<span class="bot-info-value">
|
||||
@if (Model.Type == BotType.Telegram)
|
||||
{
|
||||
<i class="fab fa-telegram text-info"></i>
|
||||
}
|
||||
@Model.Type
|
||||
</span>
|
||||
</div>
|
||||
<div class="bot-info-item">
|
||||
<span class="bot-info-label">Status</span>
|
||||
<span class="status-badge @(Model.Status == BotStatus.Active ? "status-active" : "status-inactive")">
|
||||
@Model.Status
|
||||
</span>
|
||||
</div>
|
||||
@if (!string.IsNullOrEmpty(Model.Description))
|
||||
{
|
||||
<div class="bot-info-item">
|
||||
<span class="bot-info-label">Description</span>
|
||||
<span class="bot-info-value">@Model.Description</span>
|
||||
</div>
|
||||
}
|
||||
<div class="bot-info-item">
|
||||
<span class="bot-info-label">Version</span>
|
||||
<span class="bot-info-value">@Model.Version</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button class="print-btn no-print" onclick="window.print()">
|
||||
<i class="fas fa-print"></i> Print Card
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="text-center mt-4 no-print">
|
||||
<a href="@Url.Action("Details", new { id = Model.Id })" class="btn btn-outline-secondary">
|
||||
<i class="fas fa-arrow-left"></i> Back to Details
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@section Scripts {
|
||||
<script src="https://cdn.jsdelivr.net/npm/qrcode@1.5.3/build/qrcode.min.js"></script>
|
||||
<script>
|
||||
@if (hasLink)
|
||||
{
|
||||
<text>
|
||||
// Generate QR Code
|
||||
QRCode.toCanvas(document.createElement('canvas'), '@telegramLink', {
|
||||
width: 200,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#ffffff'
|
||||
}
|
||||
}, function (error, canvas) {
|
||||
if (error) {
|
||||
console.error(error);
|
||||
return;
|
||||
}
|
||||
document.getElementById('qrcode').appendChild(canvas);
|
||||
});
|
||||
</text>
|
||||
}
|
||||
|
||||
function copyLink() {
|
||||
const link = '@telegramLink';
|
||||
navigator.clipboard.writeText(link).then(function() {
|
||||
const btn = document.querySelector('.copy-btn');
|
||||
btn.classList.add('copied');
|
||||
btn.innerHTML = '<i class="fas fa-check"></i> Copied!';
|
||||
setTimeout(function() {
|
||||
btn.classList.remove('copied');
|
||||
btn.innerHTML = '<i class="fas fa-copy"></i> Copy Link';
|
||||
}, 2000);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user