ChatGPTを使用したアプリ開発記【ニュースページの追加】

## APIの取得
(1)アカウントを作成してAPIを取得

https://gnews.io/dashboard

(2).envファイルでパスを管理
```.env
REACT_APP_GNEWS_API_KEY=〇〇〇〇〇〇〇〇〇〇
```

## パスの設定
```AppRouter.js
//NewsPageをインポート
import NewsPage from './NewsPage'

//パスの設定
<Route path="/news" element={<NewsPage />} />
```

## NewsPage.jsの作成
```NewsPage.js
import React, { useEffect, useState } from 'react';
//axios: ニュースAPIへのHTTPリクエスト用。
import axios from 'axios';
//Header: 上部に表示されるナビゲーション。
import Header from './Header';
//auth, db: Firebase認証とデータベース。
import { auth, db } from './FireBase';
import { doc, getDoc } from 'firebase/firestore';

function NewsPage() {
	//articles: ニュース記事を格納。
  const [articles, setArticles] = useState([]);
  	//loading: 読み込み中かどうか。
  const [loading, setLoading] = useState(true);
  	//profile: ユーザーのプロフィール情報（名前と画像URL）。
  const [profile, setProfile] = useState({ name: '', photoURL: '' });

	//初回レンダリング時に fetchNews() と fetchProfile() を実行。
  useEffect(() => {
    const fetchNews = async () => {
    //.env からAPIキーを取得。
      const API_KEY = process.env.REACT_APP_GNEWS_API_KEY;
      const endpoint = `https://gnews.io/api/v4/top-headlines?lang=ja&country=jp&max=10&token=${API_KEY}`;
      
      console.log("APIキー:", API_KEY);
      console.log("エンドポイント:", endpoint);

      try {
      	//axiosでGETリクエストを実行し、レスポンスの articles 配列をステートに保存。
        const response = await axios.get(endpoint);
        console.log("レスポンス:", response.data);
        setArticles(response.data.articles);
      } catch (error) {
        console.error("ニュースの取得に失敗しました:", error.response?.data || error.message);
      } finally {
        setLoading(false);
      }
    };

    const fetchProfile = async () => {
      const user = auth.currentUser;
      if (user) {
      	//ログイン中のユーザーのプロフィールをFirestoreの users/{uid} から取得。
        const docRef = doc(db, 'users', user.uid);
        //名前とプロフィール写真を profile ステートに設定。
        const docSnap = await getDoc(docRef);
        if (docSnap.exists()) {
          const data = docSnap.data();
          setProfile({
            name: data.name || '',
            photoURL: data.photoURL || '',
          });
        }
      }
    };

    fetchNews();
    fetchProfile();
  }, []);

  return (
    <div className="max-w-4xl mx-auto pt-20 p-4">
    //Header にプロフィール情報を渡す。
      <Header profileName={profile.name} profilePhotoURL={profile.photoURL} />
      <h1 className="text-2xl font-bold mb-4">最新ニュース（日本）</h1>

      //ローディング中 → 「読み込み中…」を表示。
	  //ニュースが0件 → 「ニュースが見つかりませんでした。」を表示。
	  //ニュースがある → 記事リストを表示。
      {loading ? (
        <p>読み込み中...</p>
      ) : articles.length === 0 ? (
        <p>ニュースが見つかりませんでした。</p>
      ) : (
        <ul className="space-y-4">

	//記事タイトルをリンクとして表示。
	//画像がある場合は表示。
	//記事の説明・公開日時も表示。
          {articles.map((article, idx) => (
            <li key={idx} className="bg-white p-4 rounded shadow">
              <a href={article.url} target="_blank" rel="noopener noreferrer" className="text-lg font-semibold text-blue-600 hover:underline">
                {article.title}
              </a>
              {article.image && (
                <img src={article.image} alt="" className="my-2 rounded max-h-60 object-cover w-full" />
              )}
              <p className="text-sm text-gray-700">{article.description}</p>
              <p className="text-xs text-gray-500 mt-2">{new Date(article.publishedAt).toLocaleString()}</p>
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default NewsPage;
```
## ヘッダーにニュースボタンを作成
```Header.js
            <button
            //クリックでニュースページに遷移
              onClick={() => navigate('/news')}
              //ボタンの装飾
              className="bg-blue-500 text-white text-sm px-4 py-2 rounded-xl hover:bg-blue-600 transition shadow"
            >
              📰 ニュース
            </button>
```

## UI

![スクリーンショット 2025-05-28 10.22.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1058366/9a7faa6e-e3af-4614-af4e-249a0d6bb36c.png)