Web Technologies

Firebaseセットアップ|Firebaseのセットアップ、ログイン機能全般とファイルアップロードまで

概要

良い視点ですね!👍
Firebaseのセットアップ手順を整理する前に、使用するライブラリと機能を明確にしておきましょう!✨


1. 使用するFirebaseライブラリ

**「ArchiSkill Studio」**は React + Firebase で開発するため、
JavaScript (modular) を選択しましょう!
理由:モダンで軽量な構成が可能&現在の公式推奨)

📌 公式ドキュメントの選択手順:
FirebaseリファレンスJavaScript (modular) を選択
→ 最新の構成に対応していて、記述も簡潔です!


2. 利用するFirebaseの機能

以下の機能を使用します:

🔑 ① Authentication(認証)
ログイン、ログアウト、アカウント作成、アカウント削除 に使用
メールとパスワード認証を利用予定

💾 ② Cloud Firestore(NoSQLデータベース)
→ 将来的なユーザーデータの保存や学習履歴に使用可能
ただし、初期構築では不要

📂 ③ Storage(ストレージ)
ファイルアップロード、ダウンロード、削除に使用
→ RhinocerosやTwinmotionのファイルなどを保存

🌐 ④ Hosting(ホスティング)
→ アプリをFirebase上にデプロイする場合に使用
MVP開発段階では不要
→ ローカル開発で進める方針で問題なし


3. 初期段階で必要なもの

🔥 MVPでは以下の2つで十分です:

  1. Authentication → 認証機能
  2. Storage → ファイル管理機能

💡 Cloud FirestoreHostingは、将来的にデータ管理や公開時に必要になったら追加しましょう!


まとめ

使用するライブラリ:

  • JavaScript (modular)

使用するFirebase機能:

  • MVP → Authentication(認証) + Storage(ストレージ)
  • 将来的 → Cloud Firestore(データ保存) + Hosting(デプロイ)

これで、開発の方向性がクリアになりますね!💻🚀

CORS(Cross-Origin Resource Sharing)は不要

1️⃣ CORSとは?

**CORS(クロスオリジンリソース共有)**は、
異なるドメイン間でリソースをやり取りする場合に必要な仕組みです。

🔥 「オリジン(origin)」とは?

  • オリジン = プロトコル + ドメイン + ポート
  • 例:
    • https://example.com:3000 → オリジン①
    • https://example.com:4000 → オリジン②(ポートが異なるため別オリジン)
    • http://example.com → オリジン③(プロトコルが異なるため別オリジン)

2️⃣ CORSが不要なケース(同一オリジンの場合)

同じオリジン内で通信する場合は、CORS設定は不要です。

📌 例①: ローカル開発の場合

  • localhost:3000(Reactアプリ)
  • localhost:3000(Firebase Storageへのアクセス)
    → 同一オリジンなのでCORS設定は不要

📌 例②: 同一ドメインでデプロイする場合

  • https://archiskill.web.app(アプリ)
  • https://firebasestorage.googleapis.com/v0/b/archiskill.appspot.com/o/...
    → Firebaseは同一オリジン扱いで自動的に許可
    CORS設定は不要

結論:
同一オリジンの場合はCORS設定不要です!


🚫 3️⃣ CORSが必要なケース(異なるオリジンの場合)

異なるドメイン間で通信する場合は、CORSが必要です。

📌 例①: 異なるドメインにデプロイする場合

  • アプリ → https://archiskill.com
  • Storage → https://firebasestorage.googleapis.com/...
    異なるオリジンなので、CORS設定が必要

📌 例②: 外部アプリからファイルを取得する場合

  • ArchiSkillのデータを別のサイトから取得
  • 例:fetch("https://firebasestorage.googleapis.com/...")
    → 外部からアクセスするため、CORSエラーが発生
    CORS設定が必要

📌 例③: CDNなどを利用する場合

  • ファイルをCDN経由で配信する
  • CDNとFirebaseは異なるオリジンのためCORSが必要

結論:
異なるオリジンからアクセスする場合はCORSが必要です!


4️⃣ 図解で理解

📌 CORS不要なケース(同一オリジン)

┌────────────────────────────┐
│ Reactアプリ                 │
│ https://archiskill.web.app   │
│  → Firebase Storageにアクセス│  
│  → ✅ CORS設定不要           │  
└────────────────────────────┘

同一オリジンなので問題なし!

📌 CORSが必要なケース(異なるオリジン)

┌────────────────────────────┐
│ ArchiSkillアプリ             │
│ https://archiskill.com       │
│                              │
└────────────────────────────┘
           │  
           │ 異なるオリジン(外部からアクセス)
           ▼  
┌────────────────────────────┐
│ Firebase Storage              │
│ https://firebasestorage.googleapis.com │
│                              │
│ → 🚫 CORSエラー発生!        │  
└────────────────────────────┘

異なるオリジンなのでCORSが必要!


5️⃣ まとめ

💡 CORS設定が不要なケース:

  • 同一オリジンでアクセスする場合
  • ローカル開発(localhostで同一ポート)
  • FirebaseアプリとStorageが同じプロジェクトの場合

🚫 CORS設定が必要なケース:

  • 異なるドメインからアクセスする場合
  • 別ドメインでデプロイ&ファイルアクセス
  • 外部アプリからファイル取得
  • CDN経由でファイルを配信する場合

今回のArchiSkill Studioでは、ローカル開発時やFirebase Hostingにデプロイする場合は同一オリジンになるため、CORS設定は不要です!
→ ただし、将来的に別ドメインで公開する場合は、CORS設定が必要になります!✅

Firebase CLIを使用した初期化は不要

理由

1️⃣ FirebaseのセットアップはすべてWebコンソールで完結
→ Firebaseのプロジェクト作成、Authentication、Firestore、Storageの設定はコンソール上で完了できます。
→ CLIを使わなくても必要な機能はすべて使えます!

2️⃣ CORS設定も不要
同一オリジンでアクセスするため、CORS設定は不要。
→ CLIでのgsutilコマンドによるCORS設定は必要ありません。


手順の確認

🔥 今回の手順は以下の流れです:

Firebaseコンソールでプロジェクト作成

  • Authentication、Firestore、Storageを有効化
  • ホスティングは使わないので無効のままでOK

Firebase設定ファイルをReactに追加

  • firebase-config.jsでAPIキーと設定を記載
  • ReactからFirebase SDKで接続

ローカル開発 & Firebase連携

  • npm startでローカル開発
  • ReactからFirebaseにファイルアップロードや認証を実装
    CLIは不要!

CLIを使う場合はこんなとき!

将来的に以下のケースでFirebase CLIを使う可能性があります:

📌 ① 本番環境にデプロイする場合

  • Firebase Hostingでデプロイする場合は、Firebase CLIでデプロイ作業が必要です。
  • firebase deployでデプロイ可能

📌 ② CORS設定が必要になった場合

  • 将来的に別ドメインからStorageにアクセスする場合は、Firebase CLIでCORS設定が必要です。
  • gsutil cors set cors.json gs://<バケット名> でCORS設定

結論

🚀 今回の構成では、CLIや初期化は一切不要!

  • FirebaseコンソールとReact側のSDK接続のみで完結します!👍

Reactセットアップ

npx create-react-app rhino-share

ディレクトリ構成

archiskill-studio
└─ src
├─ pages
│ └─ Home.js → 全機能のボタンを配置
├─ utils
│ ├─ firebase-config.js → Firebaseの初期設定
│ └─ auth.js → 認証関連の処理
├─ App.js
└─ index.js

ReactからFirebase SDKで接続する手順

1️⃣ 必要な手順の流れ

1️⃣ Firebaseコンソールでプロジェクト作成と設定
2️⃣ Firebase SDKをReactにインストール
3️⃣ Firebase設定ファイルを作成
4️⃣ ReactでFirebaseを初期化して接続


2️⃣ Firebaseの設定手順

🔥 ① Firebaseコンソールでプロジェクト作成

1️⃣ Firebaseコンソールにアクセス
2️⃣ 「プロジェクトを作成」をクリック
3️⃣ 名前を入力(例:archiskill-studio
4️⃣ 「続行」をクリック
5️⃣ GoogleアナリティクスはオフでOK


🔥 ② プロジェクトに必要なサービスを有効化

次に以下を有効化します:

📌 ① Authentication(認証)

  • メニュー →「ビルド」→「Authentication」
  • 「開始する」をクリック
  • サインイン方法で「メール/パスワード」を有効化
  • 保存

📌 ② Firestore(データベース)

  • メニュー →「ビルド」→「Firestore データベース」
  • 「データベースの作成」をクリック
  • モードはテストモードを選択
  • 次へ → 有効

Firestoreデータベース作成時は以下で設定しましょう!

  • データベースID → デフォルトの(default)でOK!
  • ロケーション → asia-northeast1(東京)がおすすめ!

📌 ③ Storage(ファイル保存)

  • メニュー →「ビルド」→「Storage」
  • 「開始する」をクリック
  • セキュリティルールはテストモードで開始
  • 完了

🔥 ③ Firebaseの設定ファイルを取得

1️⃣ メニュー → 「プロジェクトの設定」
2️⃣ 「アプリを追加」→「Web」を選択
3️⃣ アプリ名archiskill-webなど適当な名前を入力
4️⃣ Firebase構成コードが表示されます
→ このコードを後で使用します!


3️⃣ ReactでFirebase SDKを接続

🔥 ① Firebase SDKをインストール

ターミナルで以下を実行して、Firebaseライブラリをインストールします:

npm install firebase

✅ これでReactアプリでFirebaseを使えるようになります!


🔥 ② Firebase設定ファイルを作成

/src/utils/firebase-config.jsを作成し、Firebaseの構成を記載します。

📌 firebase-config.js

// Import the Firebase SDK
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";
import { getStorage } from "firebase/storage";

// Firebase構成情報(Firebaseコンソールから取得した情報に置き換え)
const firebaseConfig = {
  apiKey: "YOUR_API_KEY",
  authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
  projectId: "YOUR_PROJECT_ID",
  storageBucket: "YOUR_PROJECT_ID.appspot.com",
  messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
  appId: "YOUR_APP_ID"
};

// Firebaseアプリを初期化
const app = initializeApp(firebaseConfig);

// 各サービスをエクスポート
export const auth = getAuth(app);        // 認証機能
export const db = getFirestore(app);     // Firestore
export const storage = getStorage(app);  // Storage

ポイント:

  • getAuth() → 認証機能
  • getFirestore() → Firestoreデータベース
  • getStorage() → Storage(ファイルアップロード)
  • export で各サービスを外部で使用できるようにしています

🔥 ③ FirebaseをReactで接続

App.jsでFirebaseに接続します。

📌 App.js

import React from "react";
import { auth, db, storage } from "./utils/firebase-config";

function App() {
  console.log("Firebase Auth:", auth);
  console.log("Firebase Firestore:", db);
  console.log("Firebase Storage:", storage);

  return (
    <div>
      <h1>ArchiSkill Studio</h1>
      <p>Firebaseに接続完了!</p>
    </div>
  );
}

export default App;

ポイント

  • console.log()で接続確認
  • アプリを起動したときにFirebaseのオブジェクトが表示されていれば接続成功!

🔥 ④ アプリを起動して動作確認

以下のコマンドでアプリを起動します:

npm start

📌 ブラウザでhttp://localhost:3000にアクセス
→ コンソールに以下のようなログが表示されれば成功!

Firebase Auth: Object { ... }
Firebase Firestore: Object { ... }
Firebase Storage: Object { ... }

4️⃣ これで接続完了!

🔥 これで、ReactからFirebaseに接続できました!
→ あとは、認証・ファイルアップロード・ダウンロードなどの機能を実装していけばOKです!💡


🚀 ✨ 次のステップ

ファイルアップロード機能認証機能を実装する際は、
このfirebase-config.jsを使ってFirestoreやStorageと接続できます!👍

🚀 1️⃣ 認証機能(ログイン・ログアウト・アカウント作成・削除)を実装


💡 実装の流れ

以下の手順で進めます:

1️⃣ FirebaseのAuth設定と関数を作成
2️⃣ Homeページにボタンと入力フォームを設置
3️⃣ ログイン・ログアウト・アカウント作成・削除の処理を実装
4️⃣ 動作確認


1️⃣ FirebaseのAuth関数を作成

まずはFirebaseの認証関連関数をauth.jsに作成します。

📌 ディレクトリ構成

/src  
 ├── /pages  
 │    └── Home.js  
 ├── /utils  
 │    ├── firebase-config.js  
 │    └── auth.js    ← ✅ここに作成  
 ├── App.js  
 ├── index.js

📌 src/utils/auth.js

import { createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, deleteUser } from "firebase/auth";
import { auth } from "./firebase-config";

// ✅ 新規アカウント作成
export const signup = async (email, password) => {
  try {
    const userCredential = await createUserWithEmailAndPassword(auth, email, password);
    console.log("アカウント作成成功:", userCredential.user);
    return userCredential.user;
  } catch (error) {
    console.error("アカウント作成失敗:", error.message);
    throw error;
  }
};

// ✅ ログイン
export const login = async (email, password) => {
  try {
    const userCredential = await signInWithEmailAndPassword(auth, email, password);
    console.log("ログイン成功:", userCredential.user);
    return userCredential.user;
  } catch (error) {
    console.error("ログイン失敗:", error.message);
    throw error;
  }
};

// ✅ ログアウト
export const logout = async () => {
  try {
    await signOut(auth);
    console.log("ログアウト成功");
  } catch (error) {
    console.error("ログアウト失敗:", error.message);
    throw error;
  }
};

// ✅ アカウント削除
export const deleteAccount = async () => {
  const user = auth.currentUser;  // 現在のユーザーを取得

  if (user) {
    try {
      await deleteUser(user);
      console.log("アカウント削除成功");
    } catch (error) {
      console.error("アカウント削除失敗:", error.message);
      throw error;
    }
  } else {
    console.error("ユーザーがログインしていません");
    throw new Error("ユーザーがログインしていません");
  }
};

3️⃣ Firebase Authにメール/パスワードログインを有効化

1️⃣ Firebaseコンソールにアクセス
2️⃣ 左側メニューから**「Authentication」** → **「Sign-in method」**を選択
3️⃣ **「メール/パスワード」**を有効にする
4️⃣ 保存


ライブラリインストール

npm install react-router-dom

Material-UIインストール

npm install @mui/material @emotion/react @emotion/styled

ディレクトリ構成

/src
├── /pages
│ ├── Home.js → ホーム画面(ボタン配置)
│ ├── Login.js → ログインページ
│ ├── Signup.js → アカウント作成ページ
│ └── Dashboard.js → ログイン後のホーム画面
├── /utils
│ ├── firebase-config.js
│ └── auth.js
├── App.js → ルーティング管理
├── index.js

2️⃣ Homeページにフォームとボタンを設置

次に、Home.jsにログイン・アカウント作成・ログアウト・削除用のボタンと入力フォームを追加します。

📌 src/pages/Home.js

import React from "react";
import { Container, Typography, Button, Box } from "@mui/material";
import { useNavigate } from "react-router-dom";

const Home = () => {
  const navigate = useNavigate();

  return (
    <Container maxWidth="sm" sx={{ textAlign: "center", mt: 8 }}>
      <Typography variant="h3" gutterBottom>
        ArchiSkill Studio
      </Typography>
      <Typography variant="body1" sx={{ mb: 4 }}>
        建築・インテリア業界向けの学習ツール
      </Typography>
      <Box sx={{ display: "flex", justifyContent: "space-around" }}>
        <Button
          variant="contained"
          color="primary"
          onClick={() => navigate("/signup")}
        >
          アカウント作成
        </Button>
        <Button
          variant="outlined"
          color="secondary"
          onClick={() => navigate("/login")}
        >
          ログイン
        </Button>
      </Box>
    </Container>
  );
};

export default Home;

ログインページ

import React, { useState } from "react";
import { Container, TextField, Button, Typography, Box, Alert } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { login } from "../utils/auth";  // Firebase認証関数をインポート

const Login = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const navigate = useNavigate();

  const handleLogin = async (e) => {
    e.preventDefault();
    setError("");

    try {
      await login(email, password);
      navigate("/dashboard");  // ログイン後にダッシュボードへ
    } catch (error) {
      setError(error.message);
    }
  };

  return (
    <Container maxWidth="xs" sx={{ mt: 8 }}>
      <Typography variant="h4" align="center" gutterBottom>
        ログイン
      </Typography>
      {error && <Alert severity="error">{error}</Alert>}
      <form onSubmit={handleLogin}>
        <TextField
          label="メールアドレス"
          type="email"
          fullWidth
          margin="normal"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        <TextField
          label="パスワード"
          type="password"
          fullWidth
          margin="normal"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
        <Box sx={{ mt: 2 }}>
          <Button type="submit" variant="contained" color="primary" fullWidth>
            ログイン
          </Button>
        </Box>
      </form>
    </Container>
  );
};

export default Login;

アカウント作成ページ

import React, { useState } from "react";
import { Container, TextField, Button, Typography, Box, Alert } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { signup } from "../utils/auth";  // Firebase認証関数をインポート

const Signup = () => {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [error, setError] = useState("");
  const navigate = useNavigate();

  const handleSignup = async (e) => {
    e.preventDefault();
    setError("");

    try {
      await signup(email, password);
      navigate("/dashboard");  // 登録後にダッシュボードへ
    } catch (error) {
      setError(error.message);
    }
  };

  return (
    <Container maxWidth="xs" sx={{ mt: 8 }}>
      <Typography variant="h4" align="center" gutterBottom>
        アカウント作成
      </Typography>
      {error && <Alert severity="error">{error}</Alert>}
      <form onSubmit={handleSignup}>
        <TextField
          label="メールアドレス"
          type="email"
          fullWidth
          margin="normal"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
          required
        />
        <TextField
          label="パスワード"
          type="password"
          fullWidth
          margin="normal"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
          required
        />
        <Box sx={{ mt: 2 }}>
          <Button type="submit" variant="contained" color="primary" fullWidth>
            アカウント作成
          </Button>
        </Box>
      </form>
    </Container>
  );
};

export default Signup;

ダッシュボード画面(ログイン後のホーム画面)

import React, { useState } from "react";
import { Container, Typography, Button, Alert, Box, CircularProgress } from "@mui/material";
import { useNavigate } from "react-router-dom";
import { logout, deleteAccount } from "../utils/auth";  // アカウント削除関数をインポート

const Dashboard = () => {
  const navigate = useNavigate();
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);

  // ✅ ログアウト処理
  const handleLogout = async () => {
    try {
      await logout();
      navigate("/");
    } catch (error) {
      console.error("ログアウト失敗:", error.message);
      setError("ログアウトに失敗しました");
    }
  };

  // ✅ アカウント削除処理
  const handleDeleteAccount = async () => {
    const confirmDelete = window.confirm("本当にアカウントを削除しますか?この操作は取り消せません!");
    if (!confirmDelete) return;

    setLoading(true);
    setError("");

    try {
      await deleteAccount();
      alert("アカウントが削除されました");
      navigate("/");
    } catch (error) {
      console.error("アカウント削除失敗:", error.message);
      setError("アカウント削除に失敗しました。再度ログインしてください。");
    } finally {
      setLoading(false);
    }
  };

  return (
    <Container maxWidth="md" sx={{ mt: 8, textAlign: "center" }}>
      <Typography variant="h4" gutterBottom>
        ダッシュボード
      </Typography>
      <Typography variant="body1" sx={{ mb: 4 }}>
        ログイン済みユーザー用画面です。
      </Typography>

      {error && <Alert severity="error">{error}</Alert>}

      <Box sx={{ display: "flex", justifyContent: "center", gap: 2, mt: 4 }}>
        <Button
          variant="contained"
          color="secondary"
          onClick={handleLogout}
          disabled={loading}
        >
          {loading ? <CircularProgress size={24} /> : "ログアウト"}
        </Button>

        <Button
          variant="contained"
          color="error"
          onClick={handleDeleteAccount}
          disabled={loading}
        >
          {loading ? <CircularProgress size={24} /> : "アカウント削除"}
        </Button>
      </Box>
    </Container>
  );
};

export default Dashboard;

App.jsでルーティング

import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import Dashboard from "./pages/Dashboard";

const App = () => {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/login" element={<Login />} />
        <Route path="/signup" element={<Signup />} />
        <Route path="/dashboard" element={<Dashboard />} />
      </Routes>
    </Router>
  );
};

export default App;

4️⃣ 動作確認

📌 開発サーバーを再起動

npm start

📌 動作確認手順 1️⃣ メールアドレスとパスワードを入力
2️⃣ 「アカウント作成」ボタン → Firebaseにユーザーが作成される
3️⃣ 「ログイン」ボタン → ログイン成功
4️⃣ 「ログアウト」ボタン → ログアウト
5️⃣ 「アカウント削除」ボタン → Firebaseからユーザーが削除される

ブラウザで確認

http://localhost:3000

挙動

  • ホーム画面でボタンをクリックすると、ログインとアカウント作成ページに遷移
  • ダッシュボード画面が表示される

🎯 🔥 成功!

これでFirebase Authenticationを使ったログイン・ログアウト・アカウント作成・削除が完成しました!🎉


🚀 次のステップ

次は「ファイルアップロード・ダウンロード・削除機能」の実装に進みましょうか?💾🔥

🚀 ✅ 🔥 ダッシュボードに「ファイルアップロード・削除機能」を追加


1️⃣ 機能概要

  • ファイルアップロード
    • ダッシュボードでファイルを選択 → Firebase Storage にアップロード
    • Firestore にファイル情報(名前・URL)を保存
  • ファイル削除
    • アップロード済みファイルをリスト表示
    • 選択したファイルを Firebase Storage & Firestore から削除

2️⃣ Firebase FirestoreとStorageの初期設定

アップロードしたファイル情報を保存するために Firestore、ファイルデータは Storage を利用します。


🔥 Firestoreに「files」コレクションを作成

  1. Firebase コンソール にアクセス
  2. Firestore Database → 「データベースの作成」
  3. ルールをテストモードで開始
  4. 「ファイル情報を保存する」ために以下のコレクションを作成:
    • コレクション名 → files
    • フィールド →
      • name → 文字列型
      • url → 文字列型

🔥 Storageのルール設定

  1. Firebaseコンソール → Storage → ルール
  2. 以下のルールに変更 → 保存
rules_version = '2';
service firebase.storage {
  match /b/{bucket}/o {
    match /{allPaths=**} {
      allow read, write: if request.auth != null;  // 認証ユーザーのみ許可
    }
  }
}

ライブラリインストール

アイコンライブラリをインストールします。

npm install @mui/icons-material

3️⃣ Firebase関連の関数を作成

🔥 /src/utils/storage.js → ファイルアップロードと削除処理を作成

import { storage, db } from "./firebase-config";
import { ref, uploadBytes, getDownloadURL, deleteObject } from "firebase/storage";
import { collection, addDoc, getDocs, deleteDoc, doc } from "firebase/firestore";

// ✅ ファイルをアップロード
export const uploadFile = async (file) => {
  if (!file) throw new Error("ファイルが選択されていません");

  const storageRef = ref(storage, `uploads/${file.name}`);

  try {
    // Storageにファイルをアップロード
    await uploadBytes(storageRef, file);
    const url = await getDownloadURL(storageRef);

    // Firestoreにファイル情報を保存
    await addDoc(collection(db, "files"), {
      name: file.name,
      url: url,
    });

    console.log("ファイルアップロード成功:", file.name);
    return { name: file.name, url };
  } catch (error) {
    console.error("ファイルアップロード失敗:", error);
    throw error;
  }
};

// ✅ Firestoreからファイル情報を取得
export const getFiles = async () => {
  try {
    const querySnapshot = await getDocs(collection(db, "files"));
    return querySnapshot.docs.map((doc) => ({
      id: doc.id,
      ...doc.data(),
    }));
  } catch (error) {
    console.error("ファイル取得失敗:", error);
    throw error;
  }
};

// ✅ ファイル削除処理
export const deleteFile = async (id, fileName) => {
  const storageRef = ref(storage, `uploads/${fileName}`);

  try {
    // Storageからファイル削除
    await deleteObject(storageRef);

    // Firestoreからファイル情報を削除
    await deleteDoc(doc(db, "files", id));

    console.log("ファイル削除成功:", fileName);
  } catch (error) {
    console.error("ファイル削除失敗:", error);
    throw error;
  }
};

4️⃣ ダッシュボードに「ファイルアップロード・削除機能」を追加

🔥 Dashboard.js → ファイルアップロードと削除機能を追加

import React, { useState, useEffect } from "react";
import {
  Container,
  Typography,
  Button,
  Box,
  CircularProgress,
  List,
  ListItem,
  ListItemText,
  IconButton,
  Alert
} from "@mui/material";
import DeleteIcon from "@mui/icons-material/Delete";
import { uploadFile, getFiles, deleteFile } from "../utils/storage";
import { logout, deleteAccount } from "../utils/auth";
import { useNavigate } from "react-router-dom";

const Dashboard = () => {
  const [file, setFile] = useState(null);
  const [files, setFiles] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");
  const navigate = useNavigate();

  // ✅ ファイル一覧を取得
  useEffect(() => {
    const fetchFiles = async () => {
      try {
        const fileList = await getFiles();
        setFiles(fileList);
      } catch (error) {
        setError("ファイル取得に失敗しました");
      }
    };
    fetchFiles();
  }, []);

  // ✅ ファイル選択時
  const handleFileChange = (event) => {
    setFile(event.target.files[0]);
  };

  // ✅ ファイルアップロード
  const handleUpload = async () => {
    if (!file) return;

    setLoading(true);
    setError("");

    try {
      await uploadFile(file);
      alert("ファイルがアップロードされました");
      setFile(null);

      // 再読み込み
      const fileList = await getFiles();
      setFiles(fileList);
    } catch (error) {
      setError("ファイルアップロードに失敗しました");
    } finally {
      setLoading(false);
    }
  };

  // ✅ ファイル削除
  const handleDelete = async (id, fileName) => {
    const confirmDelete = window.confirm("このファイルを削除しますか?");
    if (!confirmDelete) return;

    try {
      await deleteFile(id, fileName);
      alert("ファイルが削除されました");

      // 削除後に再読み込み
      const fileList = await getFiles();
      setFiles(fileList);
    } catch (error) {
      setError("ファイル削除に失敗しました");
    }
  };

  // ✅ ログアウト
  const handleLogout = async () => {
    await logout();
    navigate("/");
  };

  return (
    <Container maxWidth="md" sx={{ mt: 8 }}>
      <Typography variant="h4" gutterBottom>
        ダッシュボード
      </Typography>

      {error && <Alert severity="error">{error}</Alert>}

      <Box sx={{ mt: 4, mb: 2 }}>
        <input type="file" onChange={handleFileChange} />
        <Button
          variant="contained"
          color="primary"
          onClick={handleUpload}
          disabled={loading || !file}
          sx={{ ml: 2 }}
        >
          {loading ? <CircularProgress size={24} /> : "アップロード"}
        </Button>
      </Box>

      <List>
        {files.map((file) => (
          <ListItem key={file.id}>
            <ListItemText primary={file.name} secondary={file.url} />
            <IconButton
              edge="end"
              aria-label="delete"
              onClick={() => handleDelete(file.id, file.name)}
            >
              <DeleteIcon />
            </IconButton>
          </ListItem>
        ))}
      </List>

      <Button variant="contained" color="secondary" onClick={handleLogout} sx={{ mt: 4 }}>
        ログアウト
      </Button>
    </Container>
  );
};

export default Dashboard;

5️⃣ 動作確認

📌 サーバー起動

npm start

📌 ブラウザで確認

http://localhost:3000

🚀 ✅ 完了!

これでダッシュボード画面に「ファイルアップロード・削除機能」が追加されました!🔥
次は「ファイルダウンロード機能」を実装しましょうか? 🚀

-Web Technologies