| 
 | 1 | +interface ChangelogEntry {  | 
 | 2 | +  author: string;  | 
 | 3 | +  description: string;  | 
 | 4 | +  id: string;  | 
 | 5 | +  publishedAt: string;  | 
 | 6 | +  title: string;  | 
 | 7 | +  url: string;  | 
 | 8 | +  filesChanged?: {  | 
 | 9 | +    added: string[];  | 
 | 10 | +    modified: string[];  | 
 | 11 | +    removed: string[];  | 
 | 12 | +  };  | 
 | 13 | +}  | 
 | 14 | + | 
 | 15 | +async function getChangelogEntries(): Promise<ChangelogEntry[]> {  | 
 | 16 | +  try {  | 
 | 17 | +    const res = await fetch('https://sentry-content-dashboard.sentry.dev/api/docs', {  | 
 | 18 | +      next: {revalidate: 3600}, // Cache for 1 hour  | 
 | 19 | +    });  | 
 | 20 | + | 
 | 21 | +    if (!res.ok) {  | 
 | 22 | +      throw new Error(`Failed to fetch changelog: ${res.status} ${res.statusText}`);  | 
 | 23 | +    }  | 
 | 24 | + | 
 | 25 | +    return res.json();  | 
 | 26 | +  } catch (error) {  | 
 | 27 | +    // eslint-disable-next-line no-console  | 
 | 28 | +    console.error('Error fetching changelog:', error);  | 
 | 29 | +    // Error fetching changelog - return empty array  | 
 | 30 | +    return [];  | 
 | 31 | +  }  | 
 | 32 | +}  | 
 | 33 | + | 
 | 34 | +export async function DocsChangelog() {  | 
 | 35 | +  const entries = await getChangelogEntries();  | 
 | 36 | + | 
 | 37 | +  if (entries.length === 0) {  | 
 | 38 | +    return (  | 
 | 39 | +      <div className="rounded-lg border border-yellow-200 bg-yellow-50 p-4 text-yellow-800 dark:border-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-200">  | 
 | 40 | +        <p className="font-semibold">No changelog entries available</p>  | 
 | 41 | +        <p className="text-sm">Check back later for updates.</p>  | 
 | 42 | +      </div>  | 
 | 43 | +    );  | 
 | 44 | +  }  | 
 | 45 | + | 
 | 46 | +  // Show only the 20 most recent entries  | 
 | 47 | +  const recentEntries = entries.slice(0, 20);  | 
 | 48 | + | 
 | 49 | +  return (  | 
 | 50 | +    <div className="space-y-8">  | 
 | 51 | +      {recentEntries.map(entry => {  | 
 | 52 | +        const date = new Date(entry.publishedAt);  | 
 | 53 | +        const totalFiles =  | 
 | 54 | +          (entry.filesChanged?.added?.length || 0) +  | 
 | 55 | +          (entry.filesChanged?.modified?.length || 0) +  | 
 | 56 | +          (entry.filesChanged?.removed?.length || 0);  | 
 | 57 | + | 
 | 58 | +        return (  | 
 | 59 | +          <article key={entry.id} className="border-b border-gray-200 pb-8 last:border-0">  | 
 | 60 | +            <header className="mb-3">  | 
 | 61 | +              <h3 className="mb-2 text-xl font-semibold">  | 
 | 62 | +                <a  | 
 | 63 | +                  href={entry.url}  | 
 | 64 | +                  target="_blank"  | 
 | 65 | +                  rel="noopener noreferrer"  | 
 | 66 | +                  className="text-primary hover:underline"  | 
 | 67 | +                >  | 
 | 68 | +                  {entry.title.replace('Docs Update: ', '')}  | 
 | 69 | +                </a>  | 
 | 70 | +              </h3>  | 
 | 71 | +              <div className="flex flex-wrap items-center gap-3 text-sm text-gray-600 dark:[color:rgb(210,199,218)]">  | 
 | 72 | +                <time dateTime={entry.publishedAt}>  | 
 | 73 | +                  {date.toLocaleDateString('en-US', {  | 
 | 74 | +                    year: 'numeric',  | 
 | 75 | +                    month: 'long',  | 
 | 76 | +                    day: 'numeric',  | 
 | 77 | +                  })}  | 
 | 78 | +                </time>  | 
 | 79 | +                {totalFiles > 0 && <span>•</span>}  | 
 | 80 | +                {totalFiles > 0 && (  | 
 | 81 | +                  <span>  | 
 | 82 | +                    {totalFiles} file{totalFiles !== 1 ? 's' : ''} changed  | 
 | 83 | +                  </span>  | 
 | 84 | +                )}  | 
 | 85 | +              </div>  | 
 | 86 | +            </header>  | 
 | 87 | + | 
 | 88 | +            <p className="mb-4 text-gray-700 dark:[color:rgb(210,199,218)]">  | 
 | 89 | +              {entry.description}  | 
 | 90 | +            </p>  | 
 | 91 | + | 
 | 92 | +            {entry.filesChanged && totalFiles > 0 && (  | 
 | 93 | +              <details className="text-sm">  | 
 | 94 | +                <summary className="cursor-pointer text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-200">  | 
 | 95 | +                  View changed files  | 
 | 96 | +                </summary>  | 
 | 97 | +                <div className="mt-2 space-y-2 rounded-md bg-gray-50 p-3 dark:bg-gray-800">  | 
 | 98 | +                  {entry.filesChanged.added && entry.filesChanged.added.length > 0 && (  | 
 | 99 | +                    <div>  | 
 | 100 | +                      <span className="font-semibold text-green-700 dark:text-green-400">  | 
 | 101 | +                        Added:  | 
 | 102 | +                      </span>  | 
 | 103 | +                      <ul className="ml-4 mt-1 list-inside list-disc">  | 
 | 104 | +                        {entry.filesChanged.added.map(file => (  | 
 | 105 | +                          <li key={file} className="text-gray-700 dark:text-gray-300">  | 
 | 106 | +                            {file}  | 
 | 107 | +                          </li>  | 
 | 108 | +                        ))}  | 
 | 109 | +                      </ul>  | 
 | 110 | +                    </div>  | 
 | 111 | +                  )}  | 
 | 112 | +                  {entry.filesChanged.modified &&  | 
 | 113 | +                    entry.filesChanged.modified.length > 0 && (  | 
 | 114 | +                      <div>  | 
 | 115 | +                        <span className="font-semibold text-blue-700 dark:text-blue-400">  | 
 | 116 | +                          Modified:  | 
 | 117 | +                        </span>  | 
 | 118 | +                        <ul className="ml-4 mt-1 list-inside list-disc">  | 
 | 119 | +                          {entry.filesChanged.modified.map(file => (  | 
 | 120 | +                            <li key={file} className="text-gray-700 dark:text-gray-300">  | 
 | 121 | +                              {file}  | 
 | 122 | +                            </li>  | 
 | 123 | +                          ))}  | 
 | 124 | +                        </ul>  | 
 | 125 | +                      </div>  | 
 | 126 | +                    )}  | 
 | 127 | +                  {entry.filesChanged.removed &&  | 
 | 128 | +                    entry.filesChanged.removed.length > 0 && (  | 
 | 129 | +                      <div>  | 
 | 130 | +                        <span className="font-semibold text-red-700 dark:text-red-400">  | 
 | 131 | +                          Removed:  | 
 | 132 | +                        </span>  | 
 | 133 | +                        <ul className="ml-4 mt-1 list-inside list-disc">  | 
 | 134 | +                          {entry.filesChanged.removed.map(file => (  | 
 | 135 | +                            <li key={file} className="text-gray-700 dark:text-gray-300">  | 
 | 136 | +                              {file}  | 
 | 137 | +                            </li>  | 
 | 138 | +                          ))}  | 
 | 139 | +                        </ul>  | 
 | 140 | +                      </div>  | 
 | 141 | +                    )}  | 
 | 142 | +                </div>  | 
 | 143 | +              </details>  | 
 | 144 | +            )}  | 
 | 145 | +          </article>  | 
 | 146 | +        );  | 
 | 147 | +      })}  | 
 | 148 | +      {entries.length > 20 && (  | 
 | 149 | +        <div className="mt-8 rounded-lg border border-gray-200 bg-gray-50 p-4 text-center dark:border-gray-700 dark:bg-gray-800">  | 
 | 150 | +          <p className="text-sm text-gray-600 dark:[color:rgb(210,199,218)]">  | 
 | 151 | +            Showing the 20 most recent updates. View the{' '}  | 
 | 152 | +            <a  | 
 | 153 | +              href="https://sentry-content-dashboard.sentry.dev/"  | 
 | 154 | +              target="_blank"  | 
 | 155 | +              rel="noopener noreferrer"  | 
 | 156 | +              className="text-primary hover:underline"  | 
 | 157 | +            >  | 
 | 158 | +              full content dashboard  | 
 | 159 | +            </a>{' '}  | 
 | 160 | +            or subscribe to the{' '}  | 
 | 161 | +            <a  | 
 | 162 | +              href="https://sentry-content-dashboard.sentry.dev/api/docs/feed"  | 
 | 163 | +              target="_blank"  | 
 | 164 | +              rel="noopener noreferrer"  | 
 | 165 | +              className="text-primary hover:underline"  | 
 | 166 | +            >  | 
 | 167 | +              RSS feed  | 
 | 168 | +            </a>  | 
 | 169 | +            .  | 
 | 170 | +          </p>  | 
 | 171 | +        </div>  | 
 | 172 | +      )}  | 
 | 173 | +    </div>  | 
 | 174 | +  );  | 
 | 175 | +}  | 
0 commit comments