littleshop/LittleShop/Areas/Admin/Views/Bots/ShareCardEmbed.cshtml
SysAdmin 646ecf77ee
All checks were successful
Build and Deploy LittleShop / Deploy to Production VPS (Manual Only) (push) Has been skipped
Build and Deploy LittleShop / Deploy to Pre-Production (CT109) (push) Successful in 1m7s
feat: Add ShareCardEmbed with local QR code generation and embed modal
- Add ShareCardEmbed.cshtml for embeddable public share card
- Add local qrcode.min.js (removed CDN dependency)
- Fix QR code generation by properly attaching canvas to DOM
- Add embed code modal with iframe and direct link copy buttons
- Use Url.Action() for proper URL generation
- Add bot discovery status migration

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 23:08:07 +00:00

212 lines
12 KiB
Plaintext

@model LittleShop.DTOs.BotDto
@using LittleShop.Enums
@{
Layout = null;
var telegramLink = ViewData["TelegramLink"] as string;
var hasLink = !string.IsNullOrEmpty(telegramLink);
var reviewCount = ViewData["ReviewCount"] as int? ?? 127;
var averageRating = ViewData["AverageRating"] as decimal? ?? 4.8m;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@Model.Name - Share Card</title>
<link href="/lib/bootstrap/css/bootstrap.min.css" rel="stylesheet">
<link href="/lib/fontawesome/css/all.min.css" rel="stylesheet">
<style>
* { box-sizing: border-box; }
body { margin: 0; padding: 20px; background: transparent; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; }
.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; min-height: 200px; min-width: 200px; }
#qrcode img, #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, .share-btn { background: #6c757d; color: white; border: none; padding: 10px 20px; border-radius: 25px; cursor: pointer; font-size: 0.9rem; transition: all 0.2s; margin: 5px; }
.copy-btn:hover, .share-btn:hover { background: #5a6268; }
.copy-btn.copied { background: #28a745; }
.share-btn { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); }
.share-btn:hover { opacity: 0.9; transform: translateY(-1px); }
.reviews-section { margin-top: 20px; padding: 20px; background: #f8f9fa; border-radius: 15px; }
.reviews-header { display: flex; align-items: center; justify-content: center; gap: 10px; margin-bottom: 15px; }
.star-rating { color: #ffc107; font-size: 1.2rem; }
.rating-text { font-weight: 700; font-size: 1.3rem; color: #212529; }
.review-count { color: #6c757d; font-size: 0.9rem; }
.review-ticker-container { overflow: hidden; position: relative; height: 80px; margin-top: 10px; }
.review-ticker { display: flex; flex-direction: column; animation: ticker 12s ease-in-out infinite; }
.review-item { padding: 10px 15px; background: white; border-radius: 10px; margin-bottom: 10px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); min-height: 60px; }
.review-item-header { display: flex; align-items: center; gap: 8px; margin-bottom: 5px; }
.review-item-stars { color: #ffc107; font-size: 0.75rem; }
.review-item-name { font-weight: 600; font-size: 0.85rem; color: #212529; }
.review-item-text { font-size: 0.8rem; color: #6c757d; line-height: 1.3; overflow: hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }
@@keyframes ticker { 0%, 25% { transform: translateY(0); } 33%, 58% { transform: translateY(-90px); } 66%, 91% { transform: translateY(-180px); } 100% { transform: translateY(0); } }
.bot-info-list { text-align: left; background: #f8f9fa; border-radius: 10px; padding: 15px 20px; margin-top: 15px; }
.bot-info-item { display: flex; justify-content: space-between; align-items: center; 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; }
.action-buttons { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; margin-top: 15px; }
</style>
</head>
<body>
<div class="share-card" id="shareCard">
<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>
<div class="action-buttons">
<button class="copy-btn" onclick="copyLink()">
<i class="fas fa-copy"></i> Copy Link
</button>
<button class="share-btn" onclick="shareCard()" id="shareBtn" style="display: none;">
<i class="fas fa-share-alt"></i> Share
</button>
</div>
}
<div class="reviews-section">
<div class="reviews-header">
<span class="star-rating">
@for (int i = 1; i <= 5; i++)
{
if (i <= Math.Floor(averageRating))
{
<i class="fas fa-star"></i>
}
else if (i - 0.5m <= averageRating)
{
<i class="fas fa-star-half-alt"></i>
}
else
{
<i class="far fa-star"></i>
}
}
</span>
<span class="rating-text">@averageRating.ToString("0.0")</span>
<span class="review-count">(@reviewCount reviews)</span>
</div>
<div class="review-ticker-container">
<div class="review-ticker">
<div class="review-item">
<div class="review-item-header">
<span class="review-item-stars"><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i></span>
<span class="review-item-name">Alex M.</span>
</div>
<div class="review-item-text">Super fast delivery and great communication. Highly recommended!</div>
</div>
<div class="review-item">
<div class="review-item-header">
<span class="review-item-stars"><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i></span>
<span class="review-item-name">Sarah K.</span>
</div>
<div class="review-item-text">Best bot I've used. Easy to order and always reliable.</div>
</div>
<div class="review-item">
<div class="review-item-header">
<span class="review-item-stars"><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="fas fa-star"></i><i class="far fa-star"></i></span>
<span class="review-item-name">Mike T.</span>
</div>
<div class="review-item-text">Great service, friendly and professional. Will use again!</div>
</div>
</div>
</div>
</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>
</div>
</div>
</div>
<script src="/js/qrcode.min.js"></script>
<script>
window.addEventListener('load', function() {
@if (hasLink)
{
<text>
var qrcodeContainer = document.getElementById('qrcode');
var canvas = document.createElement('canvas');
qrcodeContainer.appendChild(canvas);
QRCode.toCanvas(canvas, '@Html.Raw(telegramLink)', {
width: 200, margin: 2,
color: { dark: '#000000', light: '#ffffff' }
}, function (error) {
if (error) {
console.error('QR Generation failed:', error);
qrcodeContainer.innerHTML = '<p style="color: red; font-size: 0.9rem;">QR code generation failed</p>';
}
});
</text>
}
if (navigator.share) {
document.getElementById('shareBtn').style.display = 'inline-block';
}
});
function copyLink() {
navigator.clipboard.writeText('@Html.Raw(telegramLink)').then(function() {
var 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);
});
}
function shareCard() {
if (navigator.share) {
navigator.share({
title: '@Model.Name',
text: 'Check out @Model.Name on Telegram!',
url: '@Html.Raw(telegramLink)'
}).catch(console.error);
}
}
</script>
</body>
</html>