diff --git a/src/components/discussions/DiscussionCard.tsx b/src/components/discussions/DiscussionCard.tsx new file mode 100644 index 00000000..1bee236e --- /dev/null +++ b/src/components/discussions/DiscussionCard.tsx @@ -0,0 +1,148 @@ +import React from "react"; +import { motion } from "framer-motion"; +import { MessageCircle, ThumbsUp, Calendar, Tag, User } from "lucide-react"; + +export interface Discussion { + id: string; + title: string; + body: string; + author: { + login: string; + avatar_url: string; + html_url: string; + }; + category: { + name: string; + emoji: string; + }; + created_at: string; + updated_at: string; + comments: number; + reactions: { + total_count: number; + }; + html_url: string; + labels: Array<{ + name: string; + color: string; + }>; +} + +interface DiscussionCardProps { + discussion: Discussion; + index: number; +} + +export default function DiscussionCard({ + discussion, + index, +}: DiscussionCardProps): JSX.Element { + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }); + }; + + const truncateText = (text: string, maxLength: number) => { + if (!text || text.length <= maxLength) return text; + return text.substring(0, maxLength).trim() + "..."; + }; + + const formatCount = (count: number) => { + if (count >= 1000) { + return (count / 1000).toFixed(1) + "k"; + } + return count.toString(); + }; + + return ( + +
+
+ + {discussion.category.name} +
+
+ + {formatDate(discussion.created_at)} +
+
+ +
+

+ + {discussion.title} + +

+ + {discussion.body && ( +

+ {truncateText( + discussion.body.replace(/[#*`\[\]]/g, "").replace(/\n/g, " "), + 150 + )} +

+ )} + + {discussion.labels.length > 0 && ( +
+ {discussion.labels.slice(0, 4).map((label, idx) => ( + + {label.name} + + ))} +
+ )} +
+ +
+
+ {discussion.author.avatar_url ? ( + {discussion.author.login} { + const target = e.currentTarget; + target.style.display = 'none'; + const fallback = target.nextElementSibling; + if (fallback) fallback.style.display = 'flex'; + }} + /> + ) : null} +
+ {discussion.author.login.charAt(0).toUpperCase()} +
+ {discussion.author.login} +
+ +
+
+ + {formatCount(discussion.comments)} +
+
+ + {formatCount(discussion.reactions.total_count)} +
+
+
+
+ ); +} diff --git a/src/components/discussions/discussions.css b/src/components/discussions/discussions.css new file mode 100644 index 00000000..3da696e9 --- /dev/null +++ b/src/components/discussions/discussions.css @@ -0,0 +1,1068 @@ +/* Enhanced Discussion Card Styles */ +.discussion-card { + background: var(--ifm-card-background-color); + border-radius: 16px; + padding: 1.5rem; + border: 1px solid var(--ifm-color-emphasis-200); + transition: all 0.2s ease; + cursor: pointer; + position: relative; + overflow: hidden; +} + +.discussion-card::before { + content: ""; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient( + 90deg, + var(--ifm-color-primary), + var(--ifm-color-primary-dark) + ); + transform: scaleX(0); + transition: transform 0.2s ease; +} + +.discussion-card:hover { + border-color: var(--ifm-color-primary-light); + box-shadow: 0 8px 25px var(--ifm-color-primary-lightest); +} + +.discussion-card:hover::before { + transform: scaleX(1); +} + +.discussion-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + margin-bottom: 1rem; + gap: 1rem; +} + +.discussion-category { + display: inline-flex; + align-items: center; + gap: 0.5rem; + background: var(--ifm-color-primary-lightest); + color: var(--ifm-color-primary-dark); + padding: 0.375rem 0.875rem; + border-radius: 25px; + font-size: 0.8rem; + font-weight: 600; + border: 1px solid var(--ifm-color-primary-light); + flex-shrink: 0; +} + +.category-emoji { + font-size: 0.9rem; +} + +.discussion-date { + color: var(--ifm-color-emphasis-600); + font-size: 0.8rem; + font-weight: 500; + white-space: nowrap; +} + +.discussion-content { + margin-bottom: 1rem; +} + +.discussion-title { + margin: 0 0 0.75rem 0; + font-size: 1.2rem; + font-weight: 700; + line-height: 1.3; +} + +.discussion-title a { + color: var(--ifm-color-content); + text-decoration: none; + transition: color 0.2s ease; + display: -webkit-box; + -webkit-line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.discussion-title a:hover { + color: var(--ifm-color-primary); +} + +.discussion-body { + color: var(--ifm-color-emphasis-700); + line-height: 1.5; + margin: 0 0 1rem 0; + font-size: 0.9rem; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.discussion-labels { + display: flex; + flex-wrap: wrap; + gap: 0.375rem; + margin-top: 0.75rem; +} + +.discussion-label { + padding: 0.25rem 0.625rem; + border-radius: 15px; + font-size: 0.7rem; + font-weight: 600; + color: white; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.discussion-label-more { + padding: 0.25rem 0.625rem; + border-radius: 15px; + font-size: 0.7rem; + font-weight: 600; + background: var(--ifm-color-emphasis-200); + color: var(--ifm-color-emphasis-700); + border: 1px solid var(--ifm-color-emphasis-300); +} + +.discussion-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 0; +} + +.discussion-author { + display: flex; + align-items: center; + gap: 0.5rem; + flex: 1; + min-width: 0; +} + +.author-avatar { + width: 28px; + height: 28px; + border-radius: 50%; + border: 1px solid var(--ifm-color-emphasis-200); + flex-shrink: 0; +} + +.author-name { + font-weight: 600; + color: var(--ifm-color-emphasis-800); + font-size: 0.9rem; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.discussion-stats { + display: flex; + gap: 1.25rem; + flex-shrink: 0; +} + +.stat-item { + border: none; + display: flex; + align-items: center; + justify-content: center; + gap: 0.375rem; + font-size: 0.85rem; + font-weight: 500; + pointer-events: none; +} + +.stat-icon { + color: var(--ifm-color-emphasis-600) !important; + border: none !important; + background: none !important; + outline: none !important; + box-shadow: none !important; + padding: 0 !important; + margin: 0 !important; + stroke: currentColor !important; + fill: none !important; +} + +.stat-icon * { + border: none !important; + background: none !important; + outline: none !important; + box-shadow: none !important; +} + +/* Discussion Layout Styles */ +.discussion-container { + width: 100%; + max-width: 100%; + padding: 0 1rem; + box-sizing: border-box; + overflow-x: hidden; +} + +.discussion-header { + margin-bottom: 2rem; + width: 100%; + max-width: 100%; +} + +.header-content { + display: flex; + justify-content: space-between; + align-items: flex-start; + gap: 2rem; +} + +.header-text h1 { + margin: 0 0 0.5rem 0; + font-size: 2.5rem; + font-weight: 700; +} + +.header-text p { + margin: 0; + color: var(--ifm-color-emphasis-700); + font-size: 1.1rem; +} + +.discussion-tabs { + display: flex; + justify-content: space-between; + align-items: center; + gap: 0.5rem; + margin-bottom: 1.5rem; + width: 100%; + max-width: 100%; + box-sizing: border-box; +} + +.tabs-left { + display: flex; + gap: 0.5rem; +} + +.new-discussion-btn { + background: linear-gradient(135deg, #10b981 0%, #059669 50%, #047857 100%); + color: white; + border: none; + padding: 0.75rem 1.25rem; + border-radius: 12px; + font-weight: 600; + font-size: 0.9rem; + cursor: pointer; + transition: all 0.2s ease; + white-space: nowrap; + box-shadow: 0 4px 12px rgba(16, 185, 129, 0.3); + flex-shrink: 0; +} + +.new-discussion-btn:hover { + background: linear-gradient(135deg, #059669 0%, #047857 50%, #065f46 100%); + transform: translateY(-1px); + box-shadow: 0 6px 20px rgba(16, 185, 129, 0.4); +} + +.tab-btn { + background: var(--ifm-card-background-color); + border: 2px solid var(--ifm-color-emphasis-200); + color: var(--ifm-color-content); + padding: 0.75rem 1.25rem; + border-radius: 12px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + font-size: 0.9rem; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.tab-btn:hover { + border-color: var(--ifm-color-primary); + color: var(--ifm-color-primary); +} + +.tab-btn.active { + background: var(--ifm-color-primary); + border-color: var(--ifm-color-primary); + color: white; +} + +@media (max-width: 768px) { + .discussion-container { + padding: 0 0.75rem; + } + + .header-content { + flex-direction: column; + align-items: stretch; + gap: 1rem; + padding: 0; + } + + .discussion-tabs { + flex-direction: column; + gap: 1rem; + align-items: stretch; + padding: 0; + } + + .tabs-left { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + justify-content: flex-start; + width: 100%; + } + + .tab-btn { + font-size: 0.85rem; + padding: 0.625rem 1rem; + flex: 0 0 auto; + } + + .new-discussion-btn { + width: 100%; + padding: 1rem 1.5rem; + font-size: 1rem; + border-radius: 25px; + margin-top: 0.5rem; + box-sizing: border-box; + } + + .search-sort-container { + padding: 0; + margin-left: 0; + margin-right: 0; + } + + .dashboard-main .category-filters { + padding: 0; + margin-left: 0; + margin-right: 0; + } +} + +/* Dashboard Integration Styles */ +.dashboard-main .discussions-grid { + display: flex; + flex-direction: column; + gap: 1rem; + margin-top: 1.5rem; +} + +/* New Discussion Card Styles */ +.discussion-card-new { + width: 100%; + background: var(--ifm-card-background-color); + border: 1px solid var(--ifm-color-emphasis-200); + border-radius: 16px; + padding: 1.5rem; + transition: all 0.3s ease; + cursor: pointer; + position: relative; + overflow: hidden; +} + +.discussion-card-new::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 3px; + background: linear-gradient(90deg, var(--ifm-color-primary), var(--ifm-color-primary-light)); + opacity: 0; + transition: opacity 0.3s ease; +} + +.discussion-card-new:hover { + border-color: var(--ifm-color-primary-light); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); + transform: translateY(-2px); +} + +.discussion-card-new:hover::before { + opacity: 1; +} + +[data-theme='dark'] .discussion-card-new:hover { + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); +} + +.discussion-card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 1rem; +} + +.discussion-category-badge, +.discussion-date-badge { + display: flex; + align-items: center; + gap: 0.375rem; + padding: 0.375rem 0.75rem; + border-radius: 20px; + font-size: 0.8rem; + font-weight: 500; +} + +.discussion-category-badge { + background: linear-gradient(135deg, var(--ifm-color-primary-lightest), var(--ifm-color-primary-lighter)); + color: var(--ifm-color-primary-dark); + border: 1px solid var(--ifm-color-primary-light); +} + +.discussion-date-badge { + background: linear-gradient(135deg, #fff3e0, #ffe0b2); + color: #e65100; + border: 1px solid #ffcc02; +} + +[data-theme='dark'] .discussion-category-badge { + background: linear-gradient(135deg, rgba(var(--ifm-color-primary-rgb), 0.2), rgba(var(--ifm-color-primary-rgb), 0.3)); + color: var(--ifm-color-primary-light); + border-color: rgba(var(--ifm-color-primary-rgb), 0.4); +} + +[data-theme='dark'] .discussion-date-badge { + background: rgba(255, 152, 0, 0.2); + color: #ffb74d; + border-color: rgba(255, 152, 0, 0.4); +} + +.discussion-main-content { + margin-bottom: 1.25rem; +} + +.discussion-title-new { + margin: 0 0 0.75rem 0; + font-size: 1.25rem; + font-weight: 600; + line-height: 1.4; +} + +.discussion-title-new a { + color: var(--ifm-color-emphasis-800); + text-decoration: none; + transition: color 0.2s ease; +} + +.discussion-title-new a:hover { + color: var(--ifm-color-primary); +} + +.discussion-excerpt { + margin: 0 0 1rem 0; + color: var(--ifm-color-emphasis-700); + font-size: 0.95rem; + line-height: 1.5; +} + +.discussion-keywords { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; +} + +.keyword-tag { + padding: 0.25rem 0.625rem; + background: var(--ifm-color-emphasis-100); + color: var(--ifm-color-emphasis-700); + border-radius: 16px; + font-size: 0.75rem; + font-weight: 500; + border: 1px solid var(--ifm-color-emphasis-200); + transition: all 0.2s ease; + position: relative; +} + +.keyword-tag:nth-child(1) { + background: linear-gradient(135deg, #e3f2fd, #bbdefb); + color: #1565c0; + border-color: #90caf9; +} + +.keyword-tag:nth-child(2) { + background: linear-gradient(135deg, #f3e5f5, #e1bee7); + color: #7b1fa2; + border-color: #ce93d8; +} + +.keyword-tag:nth-child(3) { + background: linear-gradient(135deg, #e8f5e8, #c8e6c9); + color: #2e7d32; + border-color: #a5d6a7; +} + +.keyword-tag:nth-child(4) { + background: linear-gradient(135deg, #fff3e0, #ffcc02); + color: #ef6c00; + border-color: #ffb74d; +} + +[data-theme='dark'] .keyword-tag { + background: var(--ifm-color-emphasis-200); + color: var(--ifm-color-emphasis-800); + border-color: var(--ifm-color-emphasis-300); +} + +[data-theme='dark'] .keyword-tag:nth-child(1) { + background: rgba(33, 150, 243, 0.2); + color: #90caf9; + border-color: rgba(33, 150, 243, 0.4); +} + +[data-theme='dark'] .keyword-tag:nth-child(2) { + background: rgba(156, 39, 176, 0.2); + color: #ce93d8; + border-color: rgba(156, 39, 176, 0.4); +} + +[data-theme='dark'] .keyword-tag:nth-child(3) { + background: rgba(76, 175, 80, 0.2); + color: #a5d6a7; + border-color: rgba(76, 175, 80, 0.4); +} + +[data-theme='dark'] .keyword-tag:nth-child(4) { + background: rgba(255, 152, 0, 0.2); + color: #ffb74d; + border-color: rgba(255, 152, 0, 0.4); +} + +.discussion-card-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding-top: 1rem; + border-top: 1px solid var(--ifm-color-emphasis-200); + margin-top: 1rem; +} + +[data-theme='dark'] .discussion-card-footer { + border-top-color: var(--ifm-color-emphasis-300); +} + +.discussion-author-info { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.author-avatar-small { + width: 24px; + height: 24px; + border-radius: 50%; + border: 2px solid var(--ifm-color-primary-light); + object-fit: cover; + transition: all 0.2s ease; +} + +.author-avatar-small:hover { + border-color: var(--ifm-color-primary); + transform: scale(1.1); +} + +.author-avatar-fallback { + width: 24px; + height: 24px; + border-radius: 50%; + border: 2px solid var(--ifm-color-primary-light); + background: linear-gradient(135deg, var(--ifm-color-primary), var(--ifm-color-primary-light)); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 10px; + font-weight: 600; + transition: all 0.2s ease; +} + +.author-avatar-fallback:hover { + border-color: var(--ifm-color-primary); + transform: scale(1.1); +} + +[data-theme='dark'] .author-avatar-small { + border-color: var(--ifm-color-primary-dark); +} + +[data-theme='dark'] .author-avatar-fallback { + border-color: var(--ifm-color-primary-dark); + background: linear-gradient(135deg, var(--ifm-color-primary-dark), var(--ifm-color-primary)); +} + +.author-name-new { + font-weight: 500; + color: var(--ifm-color-emphasis-800); + font-size: 0.9rem; + transition: color 0.2s ease; +} + +.discussion-author-info:hover .author-name-new { + color: var(--ifm-color-primary); +} + +.discussion-author-info { + transition: all 0.2s ease; + padding: 0.25rem 0.5rem; + border-radius: 12px; + margin-left: -0.5rem; +} + +.discussion-author-info:hover { + background: var(--ifm-color-emphasis-100); +} + +[data-theme='dark'] .discussion-author-info:hover { + background: var(--ifm-color-emphasis-200); +} + +.discussion-engagement { + display: flex; + gap: 1rem; +} + +.engagement-item { + display: flex; + align-items: center; + gap: 0.375rem; + color: var(--ifm-color-emphasis-600); + font-size: 0.85rem; + font-weight: 500; + padding: 0.25rem 0.5rem; + border-radius: 12px; + transition: all 0.2s ease; +} + +.engagement-item:hover { + background: var(--ifm-color-emphasis-100); + color: var(--ifm-color-primary); +} + +.engagement-item svg { + color: var(--ifm-color-emphasis-500); + transition: color 0.2s ease; +} + +.engagement-item:hover svg { + color: var(--ifm-color-primary); +} + +[data-theme='dark'] .engagement-item:hover { + background: var(--ifm-color-emphasis-200); +} + +.search-sort-container { + display: flex; + align-items: center; + gap: 1rem; + margin-top: 1rem; + margin-bottom: 1.5rem; + width: 100%; + max-width: 100%; + box-sizing: border-box; + border: none !important; + outline: none !important; + box-shadow: none !important; + padding: 0 !important; + margin-left: 0 !important; + margin-right: 0 !important; +} + +.search-wrapper { + position: relative; + flex: 1; + max-width: 800px; + border: none !important; + outline: none !important; + box-shadow: none !important; + padding: 0 !important; + margin: 0 !important; +} + +.search-wrapper::before, +.search-wrapper::after { + display: none !important; +} + +.search-wrapper:focus-within { + border: none !important; + outline: none !important; + box-shadow: none !important; +} + +.search-field { + width: 100%; + padding: 1rem 1rem 1rem 2.75rem; + border: 1px solid var(--ifm-color-emphasis-300) !important; + border-radius: 12px; + font-size: 1rem; + background: var(--ifm-color-emphasis-100); + color: var(--ifm-color-content); + transition: all 0.2s ease; + outline: none !important; + box-shadow: none !important; +} + +.search-field:focus { + outline: none !important; + border: 1px solid var(--ifm-color-primary) !important; + box-shadow: none !important; + background: var(--ifm-card-background-color); +} + +.search-field:focus-visible { + outline: none !important; + border: 1px solid var(--ifm-color-primary) !important; + box-shadow: none !important; +} + +.search-icon { + position: absolute; + left: 0.875rem; + top: 30%; + transform: translateY(-50%); + color: var(--ifm-color-emphasis-600); + pointer-events: none; + z-index: 1; +} + +.sort-wrapper { + flex-shrink: 0; +} + +.sort-select { + padding: 1rem 2.5rem 1rem 1rem; + border: 1px solid var(--ifm-color-emphasis-300); + border-radius: 12px; + font-size: 1rem; + background: var(--ifm-color-emphasis-100); + color: var(--ifm-color-content); + cursor: pointer; + min-width: 160px; + appearance: none; + background-image: url('data:image/svg+xml;charset=US-ASCII,'); + background-repeat: no-repeat; + background-position: right 1rem center; + background-size: 0.75rem; +} + +.sort-select:focus { + outline: none; + border-color: var(--ifm-color-primary); + background-color: var(--ifm-card-background-color); +} + +.dashboard-main .category-filters { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; + width: 100%; + max-width: 100%; + box-sizing: border-box; + margin-bottom: 1rem; +} + +.dashboard-main .category-filter { + padding: 0.5rem 1rem; + border: 2px solid var(--ifm-color-emphasis-200); + background: var(--ifm-card-background-color); + color: var(--ifm-color-content); + border-radius: 25px; + cursor: pointer; + font-weight: 500; + font-size: 0.85rem; + transition: all 0.2s ease; + text-transform: capitalize; + display: flex; + align-items: center; + gap: 0.5rem; +} + +.dashboard-main .category-filter:hover { + border-color: var(--ifm-color-primary); + color: var(--ifm-color-primary); +} + +.dashboard-main .category-filter.active { + background: var(--ifm-color-primary); + border-color: var(--ifm-color-primary); + color: white; +} + +.dashboard-main .discussions-count { + margin: 1rem 0; + color: var(--ifm-color-emphasis-600); + font-weight: 500; + font-size: 0.9rem; +} + +.dashboard-main .discussions-loading, +.dashboard-main .discussions-error, +.dashboard-main .no-discussions { + text-align: center; + padding: 4rem 2rem; + color: var(--ifm-color-emphasis-600); + background: var(--ifm-card-background-color); + border-radius: 16px; + border: 1px solid var(--ifm-color-emphasis-200); +} + +.dashboard-main .loading-spinner { + width: 48px; + height: 48px; + border: 4px solid var(--ifm-color-emphasis-200); + border-top: 4px solid var(--ifm-color-primary); + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto 1.5rem; +} + +@keyframes spin { + 0% { + transform: rotate(0deg); + } + 100% { + transform: rotate(360deg); + } +} + +.dashboard-main .retry-button, +.dashboard-main .start-discussion-btn { + background: var(--ifm-color-primary); + color: white; + border: none; + padding: 0.875rem 2rem; + border-radius: 12px; + cursor: pointer; + font-weight: 600; + text-decoration: none; + display: inline-flex; + align-items: center; + gap: 0.5rem; + margin-top: 1.5rem; + transition: all 0.2s ease; + font-size: 0.9rem; +} + +.dashboard-main .retry-button:hover, +.dashboard-main .start-discussion-btn:hover { + background: var(--ifm-color-primary-dark); + color: white; + transform: translateY(-1px); + box-shadow: 0 4px 12px var(--ifm-color-primary-lightest); +} + +/* Responsive Design */ +@media (max-width: 996px) { + .dashboard-main .discussions-grid { + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 1.25rem; + padding: 0; + margin: 0; + } + + .discussion-container { + padding: 0 1rem; + } +} + +@media (max-width: 768px) { + .discussion-card { + padding: 1.25rem; + } + + .discussion-header { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .discussion-date { + align-self: flex-end; + margin-top: -0.5rem; + } + + .dashboard-main .discussions-grid { + grid-template-columns: 1fr; + gap: 1rem; + } + + .search-sort-container { + flex-direction: row; + gap: 1rem; + margin-top: 0.5rem; + } + + .search-wrapper { + flex: 2; + max-width: none; + } + + .sort-wrapper { + flex: 1; + min-width: 140px; + } + + .search-field { + padding: 0.875rem 0.875rem 0.875rem 2.5rem; + font-size: 0.95rem; + } + + .search-icon { + left: 0.75rem; + } + + .sort-select { + width: 100%; + min-width: auto; + padding: 0.875rem 2rem 0.875rem 0.875rem; + font-size: 0.95rem; + } + + .dashboard-main .category-filters { + justify-content: center; + } + + .discussion-footer { + flex-direction: column; + gap: 1rem; + align-items: flex-start; + } + + .discussion-stats { + align-self: flex-end; + width: auto; + } +} + +@media (max-width: 480px) { + .discussion-container { + padding: 0 0.5rem; + } + + .discussion-card { + padding: 1rem; + margin: 0; + } + + .discussion-title { + font-size: 1.1rem; + } + + .discussion-body { + font-size: 0.85rem; + } + + .dashboard-main .category-filter { + font-size: 0.8rem; + padding: 0.4rem 0.8rem; + } + + .tabs-left { + justify-content: center; + width: 100%; + } + + .tab-btn { + font-size: 0.8rem; + padding: 0.5rem 0.875rem; + } + + .new-discussion-btn { + font-size: 0.95rem; + padding: 0.875rem 1.25rem; + } + + .header-text h1 { + font-size: 2rem; + } + + .header-text p { + font-size: 1rem; + } + + .search-sort-container { + flex-direction: column; + gap: 1rem; + padding: 0; + } + + .search-wrapper { + flex: none; + width: 100%; + } + + .sort-wrapper { + flex: none; + min-width: auto; + width: 100%; + } + + .sort-select { + width: 100%; + } + + .dashboard-main .discussions-grid { + padding: 0; + margin: 0; + } +} + +/* New Card Responsive Styles */ +@media (max-width: 768px) { + .discussion-card-new { + padding: 1.25rem; + } + + .discussion-card-header { + flex-direction: column; + align-items: flex-start; + gap: 0.75rem; + } + + .discussion-date-badge { + align-self: flex-end; + margin-top: -0.5rem; + } +} + +@media (max-width: 480px) { + .discussion-card-new { + padding: 1rem; + } + + .discussion-title-new { + font-size: 1.1rem; + } + + .discussion-excerpt { + font-size: 0.85rem; + } + + .discussion-card-footer { + flex-direction: column; + gap: 1rem; + align-items: flex-start; + } + + .discussion-engagement { + align-self: flex-end; + } + + .keyword-tag { + font-size: 0.7rem; + padding: 0.2rem 0.5rem; + } +} \ No newline at end of file diff --git a/src/components/discussions/index.ts b/src/components/discussions/index.ts new file mode 100644 index 00000000..99591d63 --- /dev/null +++ b/src/components/discussions/index.ts @@ -0,0 +1,2 @@ +export { default as DiscussionCard } from './DiscussionCard'; +export type { Discussion } from './DiscussionCard'; \ No newline at end of file diff --git a/src/css/custom.css b/src/css/custom.css index faf5c725..92dcaedb 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -19,24 +19,56 @@ body { transition: background-color 0.15s ease, color 0.15s ease !important; } -/* Instant transitions for theme-sensitive elements */ -*, -*::before, -*::after { +/* Instant transitions for theme-sensitive elements - exclude stat icons */ +*:not(.stat-icon):not(.stat-icon *):not(svg.lucide):not(svg.lucide *), +*:not(.stat-icon):not(.stat-icon *):not(svg.lucide):not(svg.lucide *)::before, +*:not(.stat-icon):not(.stat-icon *):not(svg.lucide):not(svg.lucide *)::after { transition: background-color 0.15s ease, color 0.15s ease, border-color 0.15s ease, box-shadow 0.15s ease !important; } -/* Very fast transitions for interactive elements */ -button, -.button, -a, -input, -select, -textarea { +/* Completely disable styling for stat icons and Lucide SVGs */ +.stat-icon, +.stat-icon *, +.stat-icon svg, +.stat-icon svg *, +svg.lucide, +svg.lucide *, +[class*="lucide"], +[class*="lucide"] * { + transition: none !important; + border: 0 !important; + outline: 0 !important; + box-shadow: none !important; + background: transparent !important; + padding: 0 !important; + margin: 0 !important; +} + +/* Very fast transitions for interactive elements - exclude SVG icons */ +button:not(.stat-icon):not(.stat-icon *), +.button:not(.stat-icon):not(.stat-icon *), +a:not(.stat-icon):not(.stat-icon *), +input:not(.stat-icon):not(.stat-icon *), +select:not(.stat-icon):not(.stat-icon *), +textarea:not(.stat-icon):not(.stat-icon *) { transition: all 0.1s ease !important; } +/* Disable all transitions and borders for stat icons */ +.stat-icon, +.stat-icon *, +.stat-icon svg, +.stat-icon svg *, +svg.lucide, +svg.lucide * { + transition: none !important; + border: none !important; + outline: none !important; + box-shadow: none !important; + background: transparent !important; +} + /* Navbar styling and alignment */ .navbar, .navbar__inner, @@ -1558,10 +1590,6 @@ html { pointer-events: auto !important; } - /* STAR section border fixes for dark mode */ - [data-theme="dark"] .star-border-red { - border-color: #ef4444 !important; - } /* Fix: Remove extra box/space above dropdown nav items in sidebar */ .navbar-sidebar__item, diff --git a/src/pages/dashboard/giveaway/index.tsx b/src/pages/dashboard/giveaway/index.tsx index 4cebdb19..038e6d6b 100644 --- a/src/pages/dashboard/giveaway/index.tsx +++ b/src/pages/dashboard/giveaway/index.tsx @@ -11,10 +11,10 @@ const GiveawayPage: React.FC = () => { const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false); const [timeLeft, setTimeLeft] = useState({ - days: '--', - hours: '--', - minutes: '--', - seconds: '--', + days: "--", + hours: "--", + minutes: "--", + seconds: "--", }); const countdownTarget = new Date('2025-08-15T23:59:59').getTime(); @@ -28,18 +28,22 @@ const GiveawayPage: React.FC = () => { if (distance <= 0) { clearInterval(interval); setTimeLeft({ - days: '00', - hours: '00', - minutes: '00', - seconds: '00', + days: "00", + hours: "00", + minutes: "00", + seconds: "00", }); return; } setTimeLeft({ days: String(Math.floor(distance / (1000 * 60 * 60 * 24))), - hours: String(Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60))), - minutes: String(Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60))), + hours: String( + Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) + ), + minutes: String( + Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)) + ), seconds: String(Math.floor((distance % (1000 * 60)) / 1000)), }); }, 1000); @@ -50,7 +54,7 @@ const GiveawayPage: React.FC = () => { // Confetti Effect useEffect(() => { const runConfetti = async () => { - const module = await import('canvas-confetti'); + const module = await import("canvas-confetti"); const confetti = module.default as typeof confettiType; confetti({ diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx index a6eeb417..456b47b7 100644 --- a/src/pages/dashboard/index.tsx +++ b/src/pages/dashboard/index.tsx @@ -8,28 +8,19 @@ import { CommunityStatsProvider, } from "@site/src/lib/statsProvider"; import SlotCounter from "react-slot-counter"; -import Giscus from "@giscus/react"; import { useLocation, useHistory } from "@docusaurus/router"; +import { + githubService, + GitHubDiscussion, +} from "@site/src/services/githubService"; +import DiscussionCard from "@site/src/components/discussions/DiscussionCard"; +import { Megaphone, Lightbulb, HelpCircle, Star, MessageCircle, Search, TrendingUp } from "lucide-react"; +import "@site/src/components/discussions/discussions.css"; import "./dashboard.css"; type DiscussionTab = "discussions" | "trending" | "unanswered"; type SortOption = "most_popular" | "latest" | "oldest"; -type Category = "all" | "react" | "typescript" | "nodejs" | "python" | "ai_ml"; - -interface Discussion { - id: string; - title: string; - content: string; - author: { - name: string; - avatar: string; - }; - createdAt: string; - likes: number; - comments: number; - tags: string[]; - isPinned?: boolean; -} +type Category = "all" | "announcements" | "ideas" | "q-a" | "show-and-tell" | "general"; interface LeaderboardEntry { rank: number; @@ -65,49 +56,13 @@ interface RateLimitInfo { limit?: number; } -// Helper function to parse CSV data from Google Sheets -const parseCSVToJSON = (csvText: string): any[] => { - const lines = csvText.trim().split("\n"); - if (lines.length < 2) return []; - - // Get headers from first line (remove quotes) - const headers = lines[0] - .split(",") - .map((header) => header.replace(/"/g, "").trim()); - console.log("📋 CSV Headers found:", headers); - - // Parse data rows - const data: any[] = []; - - for (let i = 1; i < lines.length; i++) { - const values = lines[i] - .split(",") - .map((value) => value.replace(/"/g, "").trim()); - const row: any = {}; - - headers.forEach((header, index) => { - if (values[index]) { - row[header] = values[index]; - } - }); - - // Only add rows that have meaningful data - if (row[headers[0]] && row[headers[0]] !== "") { - data.push(row); - } - } - - console.log("📊 Parsed CSV data:", data); - return data; -}; - const categories: Category[] = [ "all", - "react", - "typescript", - "nodejs", - "python", - "ai_ml", + "announcements", + "ideas", + "q-a", + "show-and-tell", + "general" ]; const DashboardContent: React.FC = () => { @@ -123,65 +78,9 @@ const DashboardContent: React.FC = () => { const [selectedCategory, setSelectedCategory] = useState("all"); const [sortBy, setSortBy] = useState("most_popular"); const [searchQuery, setSearchQuery] = useState(""); - const [discussions, setDiscussions] = useState([ - { - id: "1", - title: "Best practices for React component optimization", - content: - "I've been working on a large React application and noticed some performance issues. What are the most effective ways to optimize React components for better performance? I'm particularly interested in memo, useMemo, and useCallback usage patterns.", - author: { - name: "Sarah Chen", - avatar: "/img/default-avatar.png", - }, - createdAt: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), - likes: 24, - comments: 8, - tags: ["react", "performance", "frontend"], - isPinned: true, - }, - { - id: "2", - title: "Building scalable microservices with Node.js", - content: - "Looking for advice on architecting microservices using Node.js. What patterns and tools do you recommend?", - author: { - name: "Mike Rodriguez", - avatar: "/img/default-avatar.png", - }, - createdAt: new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString(), - likes: 31, - comments: 14, - tags: ["nodejs", "microservices", "architecture"], - }, - { - id: "3", - title: "How to use AI/ML in Python for sentiment analysis?", - content: - "I'm new to AI/ML and want to build a sentiment analysis tool in Python. Where should I start? What libraries are recommended?", - author: { - name: "Alex Doe", - avatar: "/img/default-avatar.png", - }, - createdAt: new Date(Date.now() - 2 * 24 * 60 * 60 * 1000).toISOString(), - likes: 15, - comments: 3, - tags: ["python", "ai_ml", "sentiment-analysis"], - }, - { - id: "4", - title: "Getting started with TypeScript in a React project", - content: - "What are the benefits of using TypeScript with React? I'm looking for a simple guide to set it up.", - author: { - name: "Jane Smith", - avatar: "/img/default-avatar.png", - }, - createdAt: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), - likes: 45, - comments: 0, - tags: ["react", "typescript"], - }, - ]); + const [discussions, setDiscussions] = useState([]); + const [discussionsLoading, setDiscussionsLoading] = useState(true); + const [discussionsError, setDiscussionsError] = useState(null); const [isSidebarCollapsed, setIsSidebarCollapsed] = useState(false); const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false); const [leaderboardData, setLeaderboardData] = useState( @@ -211,6 +110,29 @@ const DashboardContent: React.FC = () => { } }, [location]); + // Fetch discussions when discuss tab is active + useEffect(() => { + if (activeTab === "discuss") { + fetchDiscussions(); + } + }, [activeTab]); + + const fetchDiscussions = async () => { + try { + setDiscussionsLoading(true); + setDiscussionsError(null); + const discussionsData = await githubService.fetchDiscussions(20); + setDiscussions(discussionsData); + } catch (error) { + console.error("Failed to fetch discussions:", error); + setDiscussionsError( + error instanceof Error ? error.message : "Failed to load discussions" + ); + } finally { + setDiscussionsLoading(false); + } + }; + // Fetch leaderboard data when leaderboard tab is active useEffect(() => { if (activeTab === "leaderboard") { @@ -227,6 +149,30 @@ const DashboardContent: React.FC = () => { setSelectedCategory(category); }; + const getCategoryIcon = (category: string) => { + const iconMap = { + 'all': null, + 'announcements': , + 'ideas': , + 'q-a': , + 'show-and-tell': , + 'general': + }; + return iconMap[category] || null; + }; + + const getCategoryDisplayName = (category: string) => { + const categoryMap = { + 'all': 'All', + 'announcements': 'Announcements', + 'ideas': 'Ideas', + 'q-a': 'Q&A', + 'show-and-tell': 'Show & Tell', + 'general': 'General' + }; + return categoryMap[category] || category; + }; + const handleSortChange = (event: React.ChangeEvent) => { setSortBy(event.target.value as SortOption); }; @@ -236,18 +182,20 @@ const DashboardContent: React.FC = () => { }; const handleNewDiscussion = () => { - // This could open a modal or navigate to a new discussion form - alert("New discussion feature coming soon!"); + window.open( + "https://github.com/recodehive/recode-website/discussions/new", + "_blank" + ); }; // Filter discussions based on current state and tab - const getFilteredDiscussions = (discussions: Discussion[]) => { + const getFilteredDiscussions = (discussions: GitHubDiscussion[]) => { return discussions .filter((discussion) => { // First apply tab filter switch (activeDiscussionTab) { case "trending": - return discussion.likes > 20; // Show discussions with more than 20 likes + return discussion.reactions.total_count > 5; // Show discussions with more than 5 reactions case "unanswered": return discussion.comments === 0; default: @@ -257,9 +205,27 @@ const DashboardContent: React.FC = () => { .filter((discussion) => { // Then apply category filter if (selectedCategory !== "all") { - return discussion.tags.some( - (tag) => tag.toLowerCase() === selectedCategory - ); + const categoryName = discussion.category.name.toLowerCase(); + const selectedCat = selectedCategory.toLowerCase(); + + // Map GitHub discussion categories to our filter categories + if (selectedCat === 'q-a' && (categoryName.includes('q&a') || categoryName.includes('question'))) { + return true; + } + if (selectedCat === 'show-and-tell' && categoryName.includes('show')) { + return true; + } + if (selectedCat === 'announcements' && categoryName.includes('announcement')) { + return true; + } + if (selectedCat === 'ideas' && categoryName.includes('idea')) { + return true; + } + if (selectedCat === 'general' && (categoryName.includes('general') || categoryName.includes('discussion'))) { + return true; + } + + return categoryName.includes(selectedCat); } return true; }) @@ -270,28 +236,26 @@ const DashboardContent: React.FC = () => { discussion.title .toLowerCase() .includes(searchQuery.toLowerCase()) || - discussion.content.toLowerCase().includes(searchQuery.toLowerCase()) + discussion.body.toLowerCase().includes(searchQuery.toLowerCase()) ); } return true; }) .sort((a, b) => { - // Pinned discussions first - if (a.isPinned && !b.isPinned) return -1; - if (!a.isPinned && b.isPinned) return 1; - // Finally sort the results switch (sortBy) { case "latest": return ( - new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime() + new Date(b.created_at).getTime() - + new Date(a.created_at).getTime() ); case "oldest": return ( - new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() + new Date(a.created_at).getTime() - + new Date(b.created_at).getTime() ); default: - return b.likes - a.likes; // most_popular + return b.reactions.total_count - a.reactions.total_count; // most_popular } }); }; @@ -461,11 +425,6 @@ const DashboardContent: React.FC = () => { } const repos = await reposResponse.json(); - // console.log( - // "📊 GitHub Repos Response:", - // repos.length, - // "repositories found" - // ); if (!Array.isArray(repos)) { throw new Error("Invalid GitHub API response format"); @@ -490,8 +449,6 @@ const DashboardContent: React.FC = () => { .sort((a, b) => b.stargazers_count - a.stargazers_count) .slice(0, 10); // Limit to top 10 repos to reduce API calls - // console.log(`📊 Processing top ${topRepos.length} repositories...`); - // Fetch contributors for each repository with delay to avoid rate limits for (let i = 0; i < topRepos.length; i++) { const repo = topRepos[i]; @@ -580,25 +537,16 @@ const DashboardContent: React.FC = () => { .sort((a, b) => b.contributions - a.contributions) // Sort by contributions descending .map((item, index) => ({ ...item, rank: index + 1 })); // Update ranks after sorting - // console.log( - // "✅ Successfully processed RecodeHive contributors data:", - // transformedData.length, - // "contributors" - // ); setLeaderboardData(transformedData); } catch (error) { console.error("❌ Error fetching RecodeHive contributors data:", error); setLeaderboardError(error.message); // Load fallback demo data - console.log("📝 Loading demo data as fallback..."); console.warn( "Using fallback leaderboard data due to GitHub API limitations" ); setLeaderboardError("GitHub API rate limit reached. Showing demo data."); - - // Fallback demo data with similar structure - console.log("📝 Loading demo data as fallback..."); const demoData: LeaderboardEntry[] = [ { rank: 1, @@ -839,59 +787,20 @@ const DashboardContent: React.FC = () => { topContributors: [], }); - // Mock data for demonstration - const mockLeaderboardData: LeaderboardEntry[] = [ - { - rank: 1, - name: "Vansh Codes", - avatar: "https://avatars.githubusercontent.com/u/79542825?v=4", - contributions: 982, - repositories: 8, - achievements: ["🚀 Rising Star", "💡 Bug Hunter", "📚 Documentation"], - github_url: "https://github.com/vansh-codes", - }, - { - rank: 2, - name: "Community Member", - avatar: "https://avatars.githubusercontent.com/u/79542825?v=4", - contributions: 756, - repositories: 6, - achievements: ["🎨 UI/UX Expert", "🔧 Feature Builder"], - github_url: "https://github.com/example", - }, - { - rank: 3, - name: "Open Source Dev", - avatar: "https://avatars.githubusercontent.com/u/79542825?v=4", - contributions: 523, - repositories: 4, - achievements: ["🌟 First Timer", "👥 Collaborator"], - github_url: "https://github.com/example2", - }, - { - rank: 4, - name: "Code Contributor", - avatar: "https://avatars.githubusercontent.com/u/79542825?v=4", - contributions: 401, - repositories: 3, - achievements: ["🏅 Consistent", "🔍 Code Reviewer"], - github_url: "https://github.com/example3", - }, - ]; - useEffect(() => { setDashboardStats({ totalContributors: githubContributorsCount, totalRepositories: githubReposCount, totalStars: githubStarCount, totalForks: githubForksCount, - topContributors: mockLeaderboardData, + topContributors: leaderboardData.slice(0, 4), }); }, [ githubContributorsCount, githubReposCount, githubStarCount, githubForksCount, + leaderboardData, ]); const StatCard: React.FC<{ @@ -978,15 +887,19 @@ const DashboardContent: React.FC = () => { ); return ( - + RecodeHive | Dashboard -
+
{/* Mobile Menu Button */} -
-
+
{categories.map((category) => ( -
handleCategoryChange(category)} > - {category.charAt(0).toUpperCase() + category.slice(1)} -
+ {getCategoryIcon(category)} + {getCategoryDisplayName(category)} + ))}
-
-
- - - +
+
+
-
- @@ -1536,55 +1448,331 @@ const DashboardContent: React.FC = () => {
-
- {filteredDiscussions.map((discussion) => ( -
+ + {filteredDiscussions.length} discussion + {filteredDiscussions.length !== 1 ? "s" : ""} found + +
+ + {discussionsLoading ? ( +
+
+

Loading discussions...

+
+ ) : discussionsError ? ( +
+
⚠️
+

Unable to load discussions

+

{discussionsError}

+ +
+ ) : ( + <> +
+ {filteredDiscussions.length > 0 ? ( + filteredDiscussions.map((discussion, index) => ( + + )) + ) : ( +
+
💬
+

No discussions found

+

+ {searchQuery || selectedCategory !== "all" + ? "Try adjusting your filters or search terms." + : "Be the first to start a discussion!"} +

+ + Start a Discussion + +
+ )} +
+ + {/* Interactive Community Engagement Section */} + -
- {`${discussion.author.name}'s + {/* Animated Background Elements */} +
+ {[...Array(6)].map((_, i) => ( + + ))}
-
-
-

{discussion.title}

- {discussion.isPinned && ( - 📌 Pinned - )} -
-
-

{discussion.content}

-
-
-
- {discussion.tags.map((tag) => ( - - {tag} - - ))} -
-
- - by {discussion.author.name} - - - {new Date( - discussion.createdAt - ).toLocaleDateString()} - - 👍 {discussion.likes} - 💬 {discussion.comments} -
-
+ + + Ready to Join the Conversation? + + + + Share your thoughts, ask questions, or help others in our community. + + +
+ {[ + { emoji: "❓", text: "Ask a Question", url: "https://github.com/recodehive/recode-website/discussions/new?category=q-a", gradient: "linear-gradient(135deg, #ff6b6b, #ff8e8e)", shadow: "#ff6b6b" }, + { emoji: "💡", text: "Share an Idea", url: "https://github.com/recodehive/recode-website/discussions/new?category=ideas", gradient: "linear-gradient(135deg, #4ecdc4, #44a08d)", shadow: "#4ecdc4" }, + { emoji: "🎉", text: "Show Your Work", url: "https://github.com/recodehive/recode-website/discussions/new?category=show-and-tell", gradient: "linear-gradient(135deg, #45b7d1, #96c93d)", shadow: "#45b7d1" } + ].map((item, index) => ( + + + {/* Animated gradient overlay */} + + + {/* Floating particles */} +
+ {[...Array(3)].map((_, i) => ( + + ))} +
+ + {/* Animated emoji */} + + {item.emoji} + + + {/* Text with glow effect */} + + {item.text} + + + {/* Shimmer effect */} + +
+
+ ))}
-
- ))} -
+ + + + Join thousands of developers sharing knowledge and building together 🚀 + + +
+ + )} ) : ( // Giveaway tab content diff --git a/src/services/githubService.ts b/src/services/githubService.ts index a7d50d10..733dd015 100644 --- a/src/services/githubService.ts +++ b/src/services/githubService.ts @@ -30,6 +30,32 @@ export interface GitHubOrganization { following: number; } +export interface GitHubDiscussion { + id: string; + title: string; + body: string; + author: { + login: string; + avatar_url: string; + html_url: string; + }; + category: { + name: string; + emoji: string; + }; + created_at: string; + updated_at: string; + comments: number; + reactions: { + total_count: number; + }; + html_url: string; + labels: Array<{ + name: string; + color: string; + }>; +} + class GitHubService { private readonly ORG_NAME = 'recodehive'; private readonly CACHE_KEY = 'github_org_stats'; @@ -322,6 +348,190 @@ class GitHubService { return { cached: true, age, expiresIn }; } + + // Fetch GitHub Discussions using GraphQL API + async fetchDiscussions(limit: number = 20, signal?: AbortSignal): Promise { + const query = ` + query GetDiscussions($owner: String!, $name: String!, $first: Int!) { + repository(owner: $owner, name: $name) { + discussions(first: $first, orderBy: {field: UPDATED_AT, direction: DESC}) { + nodes { + id + title + body + createdAt + updatedAt + url + author { + login + avatarUrl + url + } + category { + name + emoji + } + comments { + totalCount + } + reactions { + totalCount + } + labels(first: 10) { + nodes { + name + color + } + } + } + } + } + } + `; + + const variables = { + owner: this.ORG_NAME, + name: 'recode-website', // Main repository for discussions + first: limit + }; + + try { + const response = await fetch('https://api.github.com/graphql', { + method: 'POST', + headers: { + ...this.getHeaders(), + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ query, variables }), + signal, + }); + + if (!response.ok) { + throw new Error(`GraphQL request failed: ${response.status}`); + } + + const data = await response.json(); + + if (data.errors) { + console.error('GraphQL errors:', data.errors); + throw new Error('GraphQL query failed'); + } + + const discussions = data.data?.repository?.discussions?.nodes || []; + + return discussions.map((discussion: any): GitHubDiscussion => ({ + id: discussion.id, + title: discussion.title, + body: discussion.body || '', + author: { + login: discussion.author?.login || 'Unknown', + avatar_url: discussion.author?.avatarUrl || '', + html_url: discussion.author?.url || '', + }, + category: { + name: discussion.category?.name || 'General', + emoji: discussion.category?.emoji || '💬', + }, + created_at: discussion.createdAt, + updated_at: discussion.updatedAt, + comments: discussion.comments?.totalCount || 0, + reactions: { + total_count: discussion.reactions?.totalCount || 0, + }, + html_url: discussion.url, + labels: discussion.labels?.nodes?.map((label: any) => ({ + name: label.name, + color: label.color, + })) || [], + })); + } catch (error) { + console.error('Error fetching discussions:', error); + + // Return mock data for development/fallback + return this.getMockDiscussions(); + } + } + + // Mock discussions for development/fallback + private getMockDiscussions(): GitHubDiscussion[] { + return [ + { + id: '1', + title: 'Welcome to RecodeHive Discussions!', + body: 'This is where we discuss ideas, share knowledge, and help each other grow. Feel free to ask questions, share your projects, or just say hello!', + author: { + login: 'recodehive', + avatar_url: 'https://avatars.githubusercontent.com/u/your-org-id?v=4', + html_url: 'https://github.com/recodehive', + }, + category: { + name: 'Announcements', + emoji: '📢', + }, + created_at: new Date(Date.now() - 86400000).toISOString(), + updated_at: new Date(Date.now() - 3600000).toISOString(), + comments: 12, + reactions: { + total_count: 25, + }, + html_url: 'https://github.com/recodehive/recode-website/discussions', + labels: [ + { name: 'welcome', color: '0075ca' }, + { name: 'community', color: '7057ff' }, + ], + }, + { + id: '2', + title: 'How to contribute to open source projects?', + body: 'I\'m new to open source and would love to learn how to make my first contribution. Any tips or resources would be greatly appreciated!', + author: { + login: 'newcontributor', + avatar_url: 'https://avatars.githubusercontent.com/u/example?v=4', + html_url: 'https://github.com/newcontributor', + }, + category: { + name: 'Q&A', + emoji: '❓', + }, + created_at: new Date(Date.now() - 172800000).toISOString(), + updated_at: new Date(Date.now() - 7200000).toISOString(), + comments: 8, + reactions: { + total_count: 15, + }, + html_url: 'https://github.com/recodehive/recode-website/discussions', + labels: [ + { name: 'question', color: 'd876e3' }, + { name: 'beginner', color: '0e8a16' }, + ], + }, + { + id: '3', + title: 'Feature Request: Dark Mode for Documentation', + body: 'It would be great to have a dark mode option for the documentation pages. This would be easier on the eyes during late-night coding sessions.', + author: { + login: 'darkmode-lover', + avatar_url: 'https://avatars.githubusercontent.com/u/example2?v=4', + html_url: 'https://github.com/darkmode-lover', + }, + category: { + name: 'Ideas', + emoji: '💡', + }, + created_at: new Date(Date.now() - 259200000).toISOString(), + updated_at: new Date(Date.now() - 10800000).toISOString(), + comments: 5, + reactions: { + total_count: 22, + }, + html_url: 'https://github.com/recodehive/recode-website/discussions', + labels: [ + { name: 'enhancement', color: 'a2eeef' }, + { name: 'ui/ux', color: 'f9d0c4' }, + ], + }, + ]; + } } export const githubService = new GitHubService();