From 6c5e9cc05e3d2cbe74238e202504192dd05efc07 Mon Sep 17 00:00:00 2001 From: Cobi Druxerman Date: Tue, 21 Oct 2025 15:21:34 -0400 Subject: [PATCH 1/3] feat: create custom Linear roadmap component - Replace iframe with React component that fetches from Linear API - Display projects from 2025 Roadmap initiative in three columns: Planned, In Progress, Completed - Filter to only show projects with 'Public Roadmap' label - Show platform labels with Linear colors - Include modal for detailed project view - Progress bars only for in-progress projects - Responsive design with dark mode support --- .gitignore | 1 + docs/product-roadmap.mdx | 4 +- docusaurus.config.js | 1 + .../LinearRoadmap/LinearRoadmap.css | 640 ++++++++++++++++++ .../LinearRoadmap/LinearRoadmap.jsx | 505 ++++++++++++++ src/components/LinearRoadmap/index.js | 1 + 6 files changed, 1151 insertions(+), 1 deletion(-) create mode 100644 src/components/LinearRoadmap/LinearRoadmap.css create mode 100644 src/components/LinearRoadmap/LinearRoadmap.jsx create mode 100644 src/components/LinearRoadmap/index.js diff --git a/.gitignore b/.gitignore index b3ad9e33..8e81fb98 100644 --- a/.gitignore +++ b/.gitignore @@ -48,3 +48,4 @@ website/i18n/**/* package-lock.json .vercel .env +.env.local diff --git a/docs/product-roadmap.mdx b/docs/product-roadmap.mdx index 9fa2dd34..248c3e29 100644 --- a/docs/product-roadmap.mdx +++ b/docs/product-roadmap.mdx @@ -4,4 +4,6 @@ hide_title: true hide_table_of_contents: true --- - \ No newline at end of file +import LinearRoadmap from '@site/src/components/LinearRoadmap' + + diff --git a/docusaurus.config.js b/docusaurus.config.js index d5c596c4..7c0d11b7 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -377,6 +377,7 @@ const config = { 'The DevCycle documentation site includes guides and API documentation for the complete platform including the management dashboard, management APIs, SDKs, and more. If you need help along the way feel free to reach out to support and if you don’t have an account yet, you can create a free account now.', }, DEVCYCLE_CLIENT_SDK_KEY: process.env.DEVCYCLE_CLIENT_SDK_KEY, + LINEAR_API_KEY: process.env.LINEAR_API_KEY, }, url: process.env.CF_PAGES ? 'https://docs.devcycle.com' diff --git a/src/components/LinearRoadmap/LinearRoadmap.css b/src/components/LinearRoadmap/LinearRoadmap.css new file mode 100644 index 00000000..60758ca5 --- /dev/null +++ b/src/components/LinearRoadmap/LinearRoadmap.css @@ -0,0 +1,640 @@ +.roadmap-container { + max-width: 1200px; + margin: 0 auto; + padding: 2rem; + font-family: var(--ifm-font-family-base); +} + +.roadmap-header { + text-align: center; + margin-bottom: 3rem; +} + +.roadmap-header h1 { + color: var(--ifm-color-emphasis-800); + font-size: 2.5rem; + margin-bottom: 0.5rem; + font-weight: 700; +} + +.roadmap-header p { + color: var(--ifm-color-emphasis-600); + font-size: 1.125rem; + margin: 0; +} + +.roadmap-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 1.5rem; + margin-top: 2rem; +} + +@media (min-width: 768px) { + .roadmap-grid { + grid-template-columns: repeat(3, 1fr); + } +} + +.roadmap-column { + background: #f8f9fa; + border-radius: 12px; + padding: 1.5rem; + min-height: 400px; + border: 1px solid #e9ecef; + transition: box-shadow 0.2s ease; +} + +.roadmap-column:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +.column-header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 1.5rem; + padding-bottom: 1rem; + border-bottom: 2px solid var(--ifm-color-emphasis-200); +} + +.column-title { + font-size: 1.25rem; + font-weight: 600; + margin: 0; + color: var(--ifm-color-emphasis-800); +} + +.column-count { + background: var(--ifm-color-emphasis-300); + color: var(--ifm-color-emphasis-700); + padding: 0.25rem 0.5rem; + border-radius: 12px; + font-size: 0.875rem; + font-weight: 500; +} + +.column-planned .column-header { + border-bottom-color: #2563eb; +} + +.column-planned .column-title { + color: #2563eb; +} + +.column-in-progress .column-header { + border-bottom-color: #059669; +} + +.column-in-progress .column-title { + color: #059669; +} + +.column-completed .column-header { + border-bottom-color: #64748b; +} + +.column-completed .column-title { + color: #64748b; +} + +.column-content { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.initiative-card { + background: #ffffff; + border: 1px solid #e9ecef; + border-radius: 8px; + padding: 1rem; + transition: transform 0.2s ease, box-shadow 0.2s ease; + cursor: pointer; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1); +} + +.initiative-card:hover { + transform: translateY(-2px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); + border-color: var(--ifm-color-primary); +} + +.initiative-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 0.5rem; + margin-bottom: 0.75rem; +} + +.initiative-title { + font-size: 1rem; + font-weight: 600; + margin: 0; + color: var(--ifm-color-emphasis-800); + line-height: 1.3; + flex: 1; +} + +.initiative-team { + background: var(--ifm-color-primary-lightest); + color: var(--ifm-color-primary-darkest); + padding: 0.125rem 0.5rem; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 500; + white-space: nowrap; + flex-shrink: 0; +} + +.initiative-description { + color: var(--ifm-color-emphasis-600); + font-size: 0.875rem; + line-height: 1.4; + margin: 0 0 1rem 0; + display: -webkit-box; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.initiative-meta { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.initiative-date { + font-size: 0.8125rem; + color: var(--ifm-color-emphasis-600); + display: flex; + align-items: center; + gap: 0.25rem; +} + +.initiative-progress { + display: flex; + align-items: center; + gap: 0.5rem; +} + +.progress-bar { + flex: 1; + height: 6px; + background: var(--ifm-color-emphasis-200); + border-radius: 3px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: linear-gradient( + 90deg, + var(--ifm-color-primary), + var(--ifm-color-primary-dark) + ); + transition: width 0.3s ease; +} + +.progress-text { + font-size: 0.75rem; + font-weight: 600; + color: var(--ifm-color-primary); + min-width: 35px; + text-align: right; +} + +.initiative-projects { + font-size: 0.8125rem; + color: var(--ifm-color-emphasis-600); + font-weight: 500; +} + +.empty-column { + text-align: center; + color: var(--ifm-color-emphasis-500); + font-style: italic; + padding: 2rem 1rem; +} + +.loading, +.error { + text-align: center; + padding: 3rem 1rem; +} + +.loading { + color: var(--ifm-color-emphasis-600); + font-size: 1.125rem; +} + +.error { + color: var(--ifm-color-danger); +} + +.error h3 { + margin: 0 0 1rem 0; + color: var(--ifm-color-danger); +} + +.error p { + margin: 0; + color: var(--ifm-color-emphasis-600); +} + +/* Modal Styles */ +.modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.75); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + padding: 2rem; + backdrop-filter: blur(4px); +} + +.modal-content { + background: #ffffff; + border-radius: 16px; + width: 100%; + max-width: 700px; + max-height: 85vh; + overflow: hidden; + box-shadow: 0 25px 50px rgba(0, 0, 0, 0.4); + animation: modalSlideIn 0.3s cubic-bezier(0.34, 1.56, 0.64, 1); + border: 1px solid #e5e7eb; + display: flex; + flex-direction: column; +} + +@keyframes modalSlideIn { + from { + opacity: 0; + transform: translateY(-30px) scale(0.9); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } +} + +.modal-header { + display: flex; + justify-content: space-between; + align-items: flex-start; + padding: 2rem 2rem 1.5rem; + border-bottom: 1px solid #e5e7eb; + flex-shrink: 0; + background: #f8fafc; +} + +.modal-title-section { + flex: 1; + min-width: 0; +} + +.modal-title { + font-size: 1.75rem; + font-weight: 700; + color: #111827; + margin: 0 0 0.75rem 0; + line-height: 1.2; + letter-spacing: -0.02em; +} + +.modal-status-badge { + padding: 0.5rem 1rem; + border-radius: 24px; + font-size: 0.85rem; + font-weight: 600; + display: inline-block; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.modal-status-planned { + background: #dbeafe; + color: #1e40af; +} + +.modal-status-in-progress { + background: #dcfce7; + color: #166534; +} + +.modal-status-completed { + background: #f1f5f9; + color: #475569; +} + +[data-theme='dark'] .modal-status-planned { + background: #1e3a8a; + color: #bfdbfe; +} + +[data-theme='dark'] .modal-status-in-progress { + background: #166534; + color: #bbf7d0; +} + +[data-theme='dark'] .modal-status-completed { + background: #475569; + color: #cbd5e1; +} + +.modal-close { + background: #f3f4f6; + border: 1px solid #d1d5db; + font-size: 1.25rem; + cursor: pointer; + color: #6b7280; + padding: 0; + margin-left: 1.5rem; + border-radius: 8px; + width: 40px; + height: 40px; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + transition: all 0.2s ease; +} + +.modal-close:hover { + background: #e5e7eb; + color: #374151; + border-color: #9ca3af; + transform: scale(1.05); +} + +.modal-body { + padding: 2rem; + flex: 1; + overflow-y: auto; + background: #ffffff; +} + +.modal-labels { + margin-bottom: 2rem; +} + +.modal-labels h4 { + margin: 0 0 1rem 0; + font-size: 0.875rem; + font-weight: 600; + color: #374151; + text-transform: uppercase; + letter-spacing: 0.1em; +} + +.label-list { + display: flex; + flex-wrap: wrap; + gap: 0.75rem; +} + +.modal-label { + padding: 0.5rem 1rem; + border-radius: 20px; + font-size: 0.85rem; + font-weight: 600; + border: 1px solid rgba(0, 0, 0, 0.1); +} + +.modal-description { + margin-bottom: 2rem; +} + +.modal-description h4 { + margin: 0 0 1rem 0; + font-size: 0.875rem; + font-weight: 600; + color: #374151; + text-transform: uppercase; + letter-spacing: 0.1em; +} + +.modal-description p { + margin: 0; + line-height: 1.7; + color: #1f2937; + font-size: 1rem; +} + +.modal-details { + display: flex; + flex-direction: column; + gap: 1.5rem; + background: #f8fafc; + padding: 1.5rem; + border-radius: 12px; + border: 1px solid #e5e7eb; +} + +.detail-row { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1.5rem; +} + +.detail-item h4 { + margin: 0 0 0.75rem 0; + font-size: 0.8rem; + font-weight: 600; + color: #6b7280; + text-transform: uppercase; + letter-spacing: 0.1em; +} + +.detail-item p { + margin: 0; + color: #111827; + font-weight: 600; + font-size: 1rem; +} + +.modal-progress { + display: flex; + align-items: center; + gap: 1rem; +} + +.modal-progress .progress-bar { + flex: 1; + height: 10px; + background: #e5e7eb; + border-radius: 6px; + overflow: hidden; + border: 1px solid #d1d5db; +} + +.modal-progress .progress-fill { + height: 100%; + background: linear-gradient( + 90deg, + var(--ifm-color-primary), + var(--ifm-color-primary-dark) + ); + transition: width 0.4s cubic-bezier(0.4, 0, 0.2, 1); +} + +.modal-progress .progress-text { + font-size: 1rem; + font-weight: 700; + color: var(--ifm-color-primary); + min-width: 45px; + text-align: right; +} + +/* Dark mode adjustments */ +[data-theme='dark'] .roadmap-column { + background: var(--ifm-color-emphasis-200); + border-color: var(--ifm-color-emphasis-300); +} + +[data-theme='dark'] .initiative-card { + background: #1a202c; + border-color: #4b5563; + box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); +} + +[data-theme='dark'] .initiative-card:hover { + border-color: var(--ifm-color-primary-light); +} + +[data-theme='dark'] .column-count { + background: var(--ifm-color-emphasis-400); + color: var(--ifm-color-emphasis-100); +} + +/* Dark mode modal adjustments */ +[data-theme='dark'] .modal-overlay { + background: rgba(0, 0, 0, 0.85); +} + +[data-theme='dark'] .modal-content { + background: #1f2937; + border-color: #4b5563; +} + +[data-theme='dark'] .modal-header { + background: #111827; + border-bottom-color: #4b5563; +} + +[data-theme='dark'] .modal-body { + background: #1f2937; +} + +[data-theme='dark'] .modal-title { + color: #f9fafb; +} + +[data-theme='dark'] .modal-labels h4, +[data-theme='dark'] .modal-description h4 { + color: #d1d5db; +} + +[data-theme='dark'] .modal-description p { + color: #e5e7eb; +} + +[data-theme='dark'] .modal-details { + background: #111827; + border-color: #4b5563; +} + +[data-theme='dark'] .detail-item h4 { + color: #9ca3af; +} + +[data-theme='dark'] .detail-item p { + color: #f3f4f6; +} + +[data-theme='dark'] .modal-close { + background: #374151; + border-color: #4b5563; + color: #9ca3af; +} + +[data-theme='dark'] .modal-close:hover { + background: #4b5563; + color: #d1d5db; + border-color: #6b7280; +} + +[data-theme='dark'] .modal-progress .progress-bar { + background: #374151; + border-color: #4b5563; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .roadmap-container { + padding: 1rem; + } + + .roadmap-header h1 { + font-size: 2rem; + } + + .roadmap-grid { + grid-template-columns: 1fr; + gap: 1rem; + } + + .roadmap-column { + padding: 1rem; + } + + .initiative-header { + flex-direction: column; + align-items: flex-start; + gap: 0.5rem; + } + + .initiative-team { + align-self: flex-start; + } + + .modal-overlay { + padding: 1rem; + } + + .modal-content { + max-height: 90vh; + } + + .modal-header { + padding: 1.5rem 1.5rem 1rem; + } + + .modal-body { + padding: 1.5rem; + } + + .modal-title { + font-size: 1.375rem; + } + + .modal-close { + width: 36px; + height: 36px; + } + + .modal-details { + padding: 1rem; + } + + .detail-row { + grid-template-columns: 1fr; + gap: 1rem; + } +} diff --git a/src/components/LinearRoadmap/LinearRoadmap.jsx b/src/components/LinearRoadmap/LinearRoadmap.jsx new file mode 100644 index 00000000..77189d08 --- /dev/null +++ b/src/components/LinearRoadmap/LinearRoadmap.jsx @@ -0,0 +1,505 @@ +import React, { useState, useEffect } from 'react' +import useDocusaurusContext from '@docusaurus/useDocusaurusContext' +import './LinearRoadmap.css' + +const LinearRoadmap = () => { + const [initiatives, setInitiatives] = useState([]) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [selectedProject, setSelectedProject] = useState(null) + const [isModalOpen, setIsModalOpen] = useState(false) + + const { siteConfig } = useDocusaurusContext() + const LINEAR_API_KEY = siteConfig.customFields?.LINEAR_API_KEY + const LINEAR_API_URL = 'https://api.linear.app/graphql' + + const fetchInitiatives = async () => { + if (!LINEAR_API_KEY) { + setError( + 'Linear API key not found. Please set LINEAR_API_KEY environment variable.', + ) + setLoading(false) + return + } + + // First, find the 2025 Roadmap initiative + const findInitiativeQuery = ` + query { + initiatives(first: 20) { + nodes { + id + name + } + } + } + ` + + try { + // Find the 2025 Roadmap initiative ID + const findResponse = await fetch(LINEAR_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: LINEAR_API_KEY, + }, + body: JSON.stringify({ + query: findInitiativeQuery, + }), + }) + + const findData = await findResponse.json() + const roadmapInitiative = findData.data.initiatives.nodes.find( + (init) => init.name === '2025 Roadmap', + ) + + if (!roadmapInitiative) { + throw new Error('2025 Roadmap initiative not found') + } + + // Now fetch the full initiative with projects + const fullQuery = ` + query GetInitiativeProjects($id: String!) { + initiative(id: $id) { + id + name + description + projects { + nodes { + id + name + description + status { + name + type + } + state + progress + targetDate + completedAt + createdAt + teams { + nodes { + name + } + } + labels { + nodes { + name + color + parent { + name + } + } + } + } + } + } + } + ` + + // Fetch the full initiative with projects + const response = await fetch(LINEAR_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: LINEAR_API_KEY, + }, + body: JSON.stringify({ + query: fullQuery, + variables: { id: roadmapInitiative.id }, + }), + }) + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`) + } + + const data = await response.json() + + if (data.errors) { + throw new Error(data.errors[0].message) + } + + // Set the projects from the 2025 Roadmap initiative as our "initiatives" for display + const projects = data.data.initiative.projects.nodes + + // Filter to only show projects with "Public Roadmap" label + const publicRoadmapProjects = projects.filter((project) => { + if (!project.labels || !project.labels.nodes) return false + return project.labels.nodes.some( + (label) => label.name.toLowerCase() === 'public roadmap', + ) + }) + + setInitiatives(publicRoadmapProjects) + setLoading(false) + } catch (err) { + console.error('Error fetching initiatives:', err) + setError(err.message) + setLoading(false) + } + } + + useEffect(() => { + fetchInitiatives() + }, []) + + const openModal = (project) => { + setSelectedProject(project) + setIsModalOpen(true) + } + + const closeModal = () => { + setSelectedProject(null) + setIsModalOpen(false) + } + + const categorizeProject = (project) => { + // Check if project is completed + if ( + project.completedAt || + (project.state && project.state.toLowerCase().includes('completed')) || + (project.state && project.state.toLowerCase().includes('complete')) || + (project.status && + project.status.name && + project.status.name.toLowerCase().includes('complete')) + ) { + return 'completed' + } + + // Check if project is currently active/in progress + if ( + project.state && + (project.state.toLowerCase().includes('started') || + project.state.toLowerCase().includes('progress') || + project.state.toLowerCase().includes('active') || + project.state.toLowerCase().includes('in progress')) + ) { + return 'in-progress' + } + + // Everything else is planned + return 'planned' + } + + // Sort planned projects to put items with target dates first + const sortPlannedProjects = (projects) => { + return projects.sort((a, b) => { + // Items with target dates come first + if (a.targetDate && !b.targetDate) return -1 + if (!a.targetDate && b.targetDate) return 1 + + // If both have target dates, sort by date (earliest first) + if (a.targetDate && b.targetDate) { + return new Date(a.targetDate) - new Date(b.targetDate) + } + + // If neither has target dates, maintain original order + return 0 + }) + } + + const groupedProjects = { + planned: sortPlannedProjects( + initiatives.filter((project) => categorizeProject(project) === 'planned'), + ), + inProgress: initiatives.filter( + (project) => categorizeProject(project) === 'in-progress', + ), + completed: initiatives + .filter((project) => categorizeProject(project) === 'completed') + .slice(0, 8), // Show more completed items + } + + const calculateProgress = (project) => { + if (!project.progress) return 0 + + // Linear returns progress as a decimal (0.0 to 1.0), convert to percentage + const progressValue = project.progress + + // If it's already a percentage (> 1), return as is + if (progressValue > 1) { + return Math.round(progressValue) + } + + // If it's a decimal (0.0 to 1.0), convert to percentage + return Math.round(progressValue * 100) + } + + const formatDate = (dateString) => { + if (!dateString) return null + return new Date(dateString).toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + year: 'numeric', + }) + } + + const getPlatformLabels = (project) => { + const labels = [] + // Projects have labels directly - filter to only show Platform labels, exclude Public Roadmap + if (project.labels && project.labels.nodes) { + project.labels.nodes.forEach((label) => { + // Skip the "Public Roadmap" label (used only for filtering) + if (label.name.toLowerCase() === 'public roadmap') { + return + } + + // Only include labels that are in the "Platforms" group + if (label.parent && label.parent.name.toLowerCase() === 'platforms') { + labels.push({ + name: label.name, + color: label.color, + }) + } + }) + } + return labels + } + + const ProjectCard = ({ project }) => { + const progress = calculateProgress(project) + const platformLabels = getPlatformLabels(project) + const projectCategory = categorizeProject(project) + const isInProgress = projectCategory === 'in-progress' + + return ( +
openModal(project)}> +
+

{project.name}

+ {platformLabels.length > 0 && ( +
+ {platformLabels.slice(0, 2).map((label) => ( + + {label.name} + + ))} + {platformLabels.length > 2 && ( + + +{platformLabels.length - 2} + + )} +
+ )} +
+ + {project.description && ( +

{project.description}

+ )} + +
+ {project.targetDate && projectCategory !== 'completed' && ( +
+ πŸ“… Target: {formatDate(project.targetDate)} +
+ )} + + {project.completedAt && ( +
+ βœ… Completed: {formatDate(project.completedAt)} +
+ )} + + {isInProgress && progress > 0 && ( +
+
+
+
+ {progress}% +
+ )} +
+
+ ) + } + + const ProjectModal = ({ project, isOpen, onClose }) => { + if (!isOpen || !project) return null + + const progress = calculateProgress(project) + const platformLabels = getPlatformLabels(project) + const projectCategory = categorizeProject(project) + const isInProgress = projectCategory === 'in-progress' + + return ( +
+
e.stopPropagation()}> +
+
+

{project.name}

+
+ {projectCategory === 'in-progress' + ? 'In Progress' + : projectCategory === 'planned' + ? 'Planned' + : 'Completed'} +
+
+ +
+ +
+ {platformLabels.length > 0 && ( +
+

Platform Labels

+
+ {platformLabels.map((label) => ( + + {label.name} + + ))} +
+
+ )} + + {project.description && ( +
+

Description

+

{project.description}

+
+ )} + +
+
+
+

Created

+

{formatDate(project.createdAt)}

+
+
+ + {project.targetDate && ( +
+

Target Date

+

πŸ“… {formatDate(project.targetDate)}

+
+ )} + + {project.completedAt && ( +
+

Completed

+

βœ… {formatDate(project.completedAt)}

+
+ )} + + {isInProgress && ( +
+

Progress

+
+
+
+
+ {progress}% +
+
+ )} + + {project.state && ( +
+

Current Status

+

{project.state}

+
+ )} +
+
+
+
+ ) + } + + const RoadmapColumn = ({ title, projects, className }) => ( +
+
+

{title}

+ ({projects.length}) +
+
+ {projects.map((project) => ( + + ))} + {projects.length === 0 && ( +
No projects
+ )} +
+
+ ) + + if (loading) { + return ( +
+
Loading roadmap...
+
+ ) + } + + if (error) { + return ( +
+
+

Error loading roadmap

+

{error}

+
+
+ ) + } + + return ( +
+
+

2025 Roadmap Projects

+

Projects from our 2025 Roadmap initiative organized by status

+
+ +
+ + + +
+ + +
+ ) +} + +export default LinearRoadmap diff --git a/src/components/LinearRoadmap/index.js b/src/components/LinearRoadmap/index.js new file mode 100644 index 00000000..392d4a61 --- /dev/null +++ b/src/components/LinearRoadmap/index.js @@ -0,0 +1 @@ +export { default } from './LinearRoadmap' From 3dea2305bb10e7cce9763e861c8c9dfd73052579 Mon Sep 17 00:00:00 2001 From: Cobi Druxerman Date: Tue, 21 Oct 2025 16:15:16 -0400 Subject: [PATCH 2/3] testing a small change --- src/components/LinearRoadmap/LinearRoadmap.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LinearRoadmap/LinearRoadmap.jsx b/src/components/LinearRoadmap/LinearRoadmap.jsx index 77189d08..c047505a 100644 --- a/src/components/LinearRoadmap/LinearRoadmap.jsx +++ b/src/components/LinearRoadmap/LinearRoadmap.jsx @@ -16,7 +16,7 @@ const LinearRoadmap = () => { const fetchInitiatives = async () => { if (!LINEAR_API_KEY) { setError( - 'Linear API key not found. Please set LINEAR_API_KEY environment variable.', + 'An error occurred while fetching the roadmap projects. Please try again later.', ) setLoading(false) return From 7311f4013744496cf1e78164b167604a568b2c30 Mon Sep 17 00:00:00 2001 From: Cobi Druxerman Date: Tue, 21 Oct 2025 16:37:03 -0400 Subject: [PATCH 3/3] testing a new deploy --- src/components/LinearRoadmap/LinearRoadmap.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/LinearRoadmap/LinearRoadmap.jsx b/src/components/LinearRoadmap/LinearRoadmap.jsx index c047505a..e880990a 100644 --- a/src/components/LinearRoadmap/LinearRoadmap.jsx +++ b/src/components/LinearRoadmap/LinearRoadmap.jsx @@ -16,7 +16,7 @@ const LinearRoadmap = () => { const fetchInitiatives = async () => { if (!LINEAR_API_KEY) { setError( - 'An error occurred while fetching the roadmap projects. Please try again later.', + 'An error occurred while fetching the roadmap projects. Please try again sooner or later.', ) setLoading(false) return