diff --git a/src/._firebase.js b/src/._firebase.js new file mode 100644 index 0000000..8d37cc0 Binary files /dev/null and b/src/._firebase.js differ diff --git a/src/Page/Camera/._Result.js b/src/Page/Camera/._Result.js new file mode 100644 index 0000000..e1b7145 Binary files /dev/null and b/src/Page/Camera/._Result.js differ diff --git a/src/Page/Camera/ModelSelect.js b/src/Page/Camera/ModelSelect.js index 38ac9f4..b68a5f7 100644 --- a/src/Page/Camera/ModelSelect.js +++ b/src/Page/Camera/ModelSelect.js @@ -1,89 +1,120 @@ -import React, { useState, useRef, useEffect, Suspense } from 'react' -import Result from './Result'; -import { useImage } from '../../Helper/ImageContext'; +import React, { useState, useRef, useEffect, Suspense } from "react"; +import Result from "./Result"; +import { useImage } from "../../Helper/ImageContext"; import { Link } from "react-router-dom"; -import { Button,Checkbox,Typography,Spinner } from "@material-tailwind/react"; -import { FaArrowLeft,FaCameraRetro,FaCheck } from "react-icons/fa"; +import { + Button, + Checkbox, + Typography, + Spinner, +} from "@material-tailwind/react"; +import { FaArrowLeft, FaCameraRetro, FaCheck } from "react-icons/fa"; import { GiCheckMark } from "react-icons/gi"; import { motion, AnimatePresence } from "framer-motion"; -import {getUsernameFromCookie,getRandomUniqueNumbers} from '../../Helper/Helper' +import { + getUsernameFromCookie, + getRandomUniqueNumbers, +} from "../../Helper/Helper"; import Resizer from "react-image-file-resizer"; -import TiltCard from '../Components/TiltCard'; -import JSZip from 'jszip'; +import TiltCard from "../Components/TiltCard"; +import JSZip from "jszip"; const bannerData = [ - {url:"https://r2.web.moonshine.tw/msweb/backto80s_ai/templates/template01.png" ,title:'MODULE 1', subtitle:"Introduction to module one",id:'1'}, - {url:"https://r2.web.moonshine.tw/msweb/backto80s_ai/templates/template02.png" ,title:'MODULE 2', subtitle:"Introduction to module two",id:'2'}, - {url:"https://r2.web.moonshine.tw/msweb/backto80s_ai/templates/template03.png" ,title:'MODULE 3', subtitle:"Introduction to module three",id:'3'}, - {url:"https://r2.web.moonshine.tw/msweb/backto80s_ai/templates/template04.png" ,title:'MODULE 4', subtitle:"Introduction to module four",id:'4'}, - - ] - let corsURL = 'https://mscors-anywhwere.kilokingw.workers.dev/?' - let apiurl = 'https://backto80s-api.rd-02f.workers.dev/' - let face_swap_url = apiurl + 'face_swap' - let getimages_url = apiurl + 'images/' + { + url: "https://r2.web.moonshine.tw/msweb/backto80s_ai/templates/template01.png", + title: "MODULE 1", + subtitle: "Introduction to module one", + id: "1", + }, + { + url: "https://r2.web.moonshine.tw/msweb/backto80s_ai/templates/template02.png", + title: "MODULE 2", + subtitle: "Introduction to module two", + id: "2", + }, + { + url: "https://r2.web.moonshine.tw/msweb/backto80s_ai/templates/template03.png", + title: "MODULE 3", + subtitle: "Introduction to module three", + id: "3", + }, + { + url: "https://r2.web.moonshine.tw/msweb/backto80s_ai/templates/template04.png", + title: "MODULE 4", + subtitle: "Introduction to module four", + id: "4", + }, +]; +let corsURL = "https://mscors-anywhwere.kilokingw.workers.dev/?"; +let apiurl = "https://backto80s-api.rd-02f.workers.dev/"; +let face_swap_url = apiurl + "face_swap"; +let getimages_url = apiurl + "images/"; function ModelSelect() { const storedUsername = getUsernameFromCookie(); const [isMobile, setIsMobile] = useState(window.innerWidth < 768); - const [isCompressing , setIsCompressing] = useState(false) - const [isResultComplete,setIsResultComplete]= useState(false) - const [sourceImage ,setSourceImage ] = useState(null) + const [isCompressing, setIsCompressing] = useState(false); + const [isResultComplete, setIsResultComplete] = useState(false); + const [sourceImage, setSourceImage] = useState(null); const [taskStatus, setTaskStatus] = useState([ { - status: 'ok', + status: "ok", id: null, - img: 'g', + img: "g", finished: 1, }, { - status: 'ok', + status: "ok", id: null, - img: '', + img: "", finished: 1, }, { - status: 'Waiting for Result...', + status: "Waiting for Result...", id: null, - img: '', + img: "", finished: 0, - } , + }, { - status: 'Waiting for Result...', + status: "Waiting for Result...", id: null, - img: '', + img: "", finished: 0, - } - ]); + }, + ]); const handleResize = () => { setIsMobile(window.innerWidth < 768); }; useEffect(() => { - window.addEventListener('resize', handleResize); - + window.addEventListener("resize", handleResize); + return () => { - window.removeEventListener('resize', handleResize); + window.removeEventListener("resize", handleResize); }; }, []); const { beforeImage } = useImage(); const [swiper, setSwiper] = useState(null); - const [currentId , setCurrentId] = useState('1') - const [currentIndex , setCurrentIndex] = useState(0) + const [currentId, setCurrentId] = useState("1"); + const [currentIndex, setCurrentIndex] = useState(0); // console.log(currentId) - const [msg,setMsg] = useState('') - - const [isRender , setIsRender] = useState(false) - const [renderedData, setRenderedData] = useState({}) - const [renderedResult, setRenderedResult] = useState({}) - const [showRender , setShowRender] = useState(false) + const [msg, setMsg] = useState(""); + + const [isRender, setIsRender] = useState(false); + const [renderedData, setRenderedData] = useState({}); + const [renderedResult, setRenderedResult] = useState({}); + const [showRender, setShowRender] = useState(false); const handleOpen = () => setShowRender(!showRender); - const handleImageClick = (index) =>{ - console.log(index) - setCurrentIndex(index) - } + const handleImageClick = (index) => { + console.log(index); + setCurrentIndex(index); + }; const needsCompression = (file, maxSize, maxDimension) => { - return file.size > maxSize || (file.width > maxDimension || file.height > maxDimension); - } + return ( + file.size > maxSize || + file.width > maxDimension || + file.height > maxDimension + ); + }; const getImageDimensions = (file) => { return new Promise((resolve) => { const reader = new FileReader(); @@ -99,175 +130,165 @@ function ModelSelect() { }; reader.readAsDataURL(file); }); - } + }; - const onBtnClick= async ()=>{ + const onBtnClick = async () => { //todo 四個數字 1-91 // getRandomUniqueNumbers(43,1,91) if (!beforeImage) { - setMsg('Error: Image must be taken or uploaded first.') - return + setMsg("Error: Image must be taken or uploaded first."); + return; } - if(!currentId){ + if (!currentId) { // console.log('no') - setMsg('Error: A mod must be selected.') - return + setMsg("Error: A mod must be selected."); + return; } - try{ - setMsg(null) - setMsg('Picture uploading…..') - setIsRender(true) - setIsResultComplete(false) + try { + setMsg(null); + setMsg("Picture uploading….."); + setIsRender(true); + setIsResultComplete(false); const imageUrls = getRandomUniqueNumbers(4, 1, 91).map( - (num) => `https://r2.web.moonshine.tw/msweb/backto80s_ai/template_80s/${num}.jpg` + (num) => + `https://r2.web.moonshine.tw/msweb/backto80s_ai/template_80s/${num}.jpg` ); //blob:https://web-r2.moonshine.tw/22837887-432d-4d0e-ac94-85ab193ba1b1 - console.log(imageUrls) + console.log(imageUrls); // setStartRender(true) //fetch API 上傳運算 //POST https://faceswap.rd-02f.workers.dev/images 上傳圖片 //GET https://faceswap.rd-02f.workers.dev/images/ 取得圖片 - var file = dataURLtoFile(beforeImage,'image.jpg') + var file = dataURLtoFile(beforeImage, "image.jpg"); const { width, height } = await getImageDimensions(file); - console.log(width, height) - + console.log(width, height); + //容量 尺寸 let compressFiles; - if(needsCompression(file, 1 * 1024 * 1024, 1200)) { - + if (needsCompression(file, 1 * 1024 * 1024, 1200)) { // console.log('需要壓縮') - setMsg('Compressing image.') + setMsg("Compressing image."); compressFiles = await resizeFile(file); - await setSourceImage(compressFiles) - }else{ - compressFiles = file - await setSourceImage(compressFiles) + await setSourceImage(compressFiles); + } else { + compressFiles = file; + await setSourceImage(compressFiles); } - await uploadAndAwaitResult(imageUrls,compressFiles) - - - }catch{ - - } - } - //TODO - const uploadAndAwaitResult = async (imageUrls,compressFiles)=>{ + await uploadAndAwaitResult(imageUrls, compressFiles); + } catch {} + }; + //TODO + const uploadAndAwaitResult = async (imageUrls, compressFiles) => { const statusList = imageUrls.map((imageUrl) => ({ - status: 'Uploading...', + status: "Uploading...", id: null, - img: '', + img: "", finished: 0, })); - let counterIndex = 0 - const uploadPromises = imageUrls.map((imageUrl,index) => { + let counterIndex = 0; + const uploadPromises = imageUrls.map((imageUrl, index) => { return new Promise(async (resolve, reject) => { try { // 上传图像的逻辑 const formData = new FormData(); - formData.append('source_image', compressFiles); - formData.append('swap_image_url', imageUrl); + formData.append("source_image", compressFiles); + formData.append("swap_image_url", imageUrl); var myHeaders = new Headers(); myHeaders.append("Authorization", process.env.REACT_APP_APITOKEN); var requestOptions = { - method: 'POST', + method: "POST", headers: myHeaders, body: formData, - redirect: 'follow', + redirect: "follow", }; - const response = await fetch(corsURL+face_swap_url,requestOptions); + const response = await fetch(face_swap_url, requestOptions); if (!response.ok) { - setMsg('Error:please upload the image again.') - reject(new Error('Image upload failed.')); + setMsg("Error:please upload the image again."); + reject(new Error("Image upload failed.")); return; } - + const responseData = await response.json(); console.log(responseData); - - + if (responseData.message) { - setMsg('Error:please upload the image again.') - reject(new Error('Image upload failed.')); + setMsg("Error:please upload the image again."); + reject(new Error("Image upload failed.")); return; } - if(responseData.id){ - counterIndex++ - setMsg(`Flow task ${counterIndex}/${imageUrls.length} uploaded`) + if (responseData.id) { + counterIndex++; + setMsg(`Flow task ${counterIndex}/${imageUrls.length} uploaded`); } // 上传成功,等待结果 await new Promise((innerResolve) => setTimeout(innerResolve, 300)); - - statusList[index].status = 'Waiting...'; + + statusList[index].status = "Waiting..."; statusList[index].id = responseData.id; resolve(responseData); } catch (error) { - setMsg('Error: Image upload failed.'); + setMsg("Error: Image upload failed."); reject(error); } }); }); - + try { setTaskStatus(statusList); const results = await Promise.all(uploadPromises); - console.log('All uploads completed:', results); + console.log("All uploads completed:", results); // 在这里处理所有上传任务完成后的逻辑 const taskStatusList = [...statusList]; - setShowRender(false) - // setIsRender(false) - console.log(statusList) + setShowRender(false); + // setIsRender(false) + console.log(statusList); for (let i = 0; i < results.length; i++) { const result = results[i]; const status = taskStatusList[i]; - + if (result && result.id) { // 调用 getResulImage,注意传入对应的 id 和 sourceImg // 不再直接更新状态,而是调用递归函数处理 processTasks(taskStatusList, i); } - } - + } } catch (error) { - console.error('Upload error:', error); + console.error("Upload error:", error); // 在这里处理上传过程中的错误 } - - } + }; // 送出時 一一上傳四張模組 等於有四個任務id 依序拿圖 - const processTasks = async (tasks, index,successCount=0) => { - - if(successCount === 4) { - setIsRender(false) - setShowRender(true) - return + const processTasks = async (tasks, index, successCount = 0) => { + if (successCount === 4) { + setIsRender(false); + setShowRender(true); + return; } if (index >= tasks.length) { // 所有任务都已处理完毕,退出递归 - console.log('完成了',tasks.length , index) + console.log("完成了", tasks.length, index); - setIsResultComplete(true) + setIsResultComplete(true); - setMsg(`Get result image..`) - console.log('getResulImage 完成的次数:', successCount); + setMsg(`Get result image..`); + console.log("getResulImage 完成的次数:", successCount); return; } - - const task = tasks[index]; const resultImage = await getResulImage(task.id); - if (resultImage.restarted>=2){ - setMsg('Timeout error, please upload the image again.') - return + if (resultImage.restarted >= 2) { + setMsg("Timeout error, please upload the image again."); + return; } if (resultImage.finished === 0) { // 如果沒有 source_image,一段時間再嘗試 @@ -280,144 +301,140 @@ function ModelSelect() { // 更新任務狀態 task.finished = 1; task.img = resultImage.generations[0].img; - task.status = 'Result received'; + task.status = "Result received"; setTaskStatus([...tasks]); successCount++; - + // 處理下一任務 - await processTasks(tasks, index + 1,successCount); + await processTasks(tasks, index + 1, successCount); } }; let source; - const getResulImage = async (id) =>{ + const getResulImage = async (id) => { try { - const response = await fetch(corsURL+getimages_url+ id, { - method: 'GET', + const response = await fetch(corsURL + getimages_url + id, { + method: "GET", headers: { - 'Authorization':process.env.REACT_APP_APITOKEN - } + Authorization: process.env.REACT_APP_APITOKEN, + }, }); const responseData = await response.json(); - + // 在此处返回 responseData return responseData; } catch (error) { console.error(error); // 返回一个错误或默认值,取决于你的需求 - return { error: 'An error occurred' }; + return { error: "An error occurred" }; } - - } - const updatedData = async (id,url,sourceImage)=>{ - console.log(sourceImage) + }; + const updatedData = async (id, url, sourceImage) => { + console.log(sourceImage); const formData = new FormData(); - formData.append('source_image', sourceImage); - formData.append('swap_image', url); + formData.append("source_image", sourceImage); + formData.append("swap_image", url); formData.append("horde_id", id); - formData.append("username", storedUsername ? storedUsername : ' '); + formData.append("username", storedUsername ? storedUsername : " "); formData.append("command_type", currentId); - await fetch(apiurl+'swap_data', { - method: 'POST', + await fetch(apiurl + "swap_data", { + method: "POST", body: formData, - redirect: 'follow', + redirect: "follow", headers: { - 'Authorization':process.env.REACT_APP_APITOKEN - } + Authorization: process.env.REACT_APP_APITOKEN, + }, }) - .then(response => { - if(response.status === 200){ - console.log('uploaded') - } - }) - .catch(error => { - console.error(error); - }); - } - const resizeFile = (file) => + .then((response) => { + if (response.status === 200) { + console.log("uploaded"); + } + }) + .catch((error) => { + console.error(error); + }); + }; + const resizeFile = (file) => new Promise((resolve) => { Resizer.imageFileResizer( file, 1000, // 設置圖像的最大寬度 1000, // 設置圖像的最大高度 - 'JPEG', // 設置圖像的格式 + "JPEG", // 設置圖像的格式 70, // 設置圖像的質量 0, // 設置圖像的旋轉角度 (uri) => { resolve(uri); }, - 'file' // 設置返回的圖像格式 + "file" // 設置返回的圖像格式 ); }); function dataURLtoFile(dataurl, filename) { - var arr = dataurl.split(','), - mime = arr[0].match(/:(.*?);/)[1], - bstr = atob(arr[arr.length - 1]), - n = bstr.length, - u8arr = new Uint8Array(n); - while(n--){ - u8arr[n] = bstr.charCodeAt(n); + var arr = dataurl.split(","), + mime = arr[0].match(/:(.*?);/)[1], + bstr = atob(arr[arr.length - 1]), + n = bstr.length, + u8arr = new Uint8Array(n); + while (n--) { + u8arr[n] = bstr.charCodeAt(n); } - return new File([u8arr], filename, {type:mime}); + return new File([u8arr], filename, { type: mime }); } useEffect(() => { // 這個 effect 會在 sourceImage 更新後執行 - console.log('sourceImage 更新了:', sourceImage); + console.log("sourceImage 更新了:", sourceImage); // 在這裡執行其他你想要的邏輯... }, [sourceImage]); // 設定 sourceImage 為 effect 的依賴 - //處理圖片打包下載壓縮檔 - const handleDownload = async()=>{ + const handleDownload = async () => { const addImagePromises = []; - setIsCompressing(true) + setIsCompressing(true); for (let index = 0; index < taskStatus.length; index++) { const task = taskStatus[index]; if (task.finished === 1 && task.img) { // 提取图片文件名,可以根据需要修改文件名 - const filename = `${'img'+index}.jpg`; + const filename = `${"img" + index}.jpg`; addImagePromises.push(fetchAndAddImageToZip(task.img, filename)); } } - console.log(taskStatus) - + console.log(taskStatus); try { // 使用 Promise.all 等待所有图片添加操作完成 await Promise.all(addImagePromises); - setIsCompressing(false) + setIsCompressing(false); // 所有图片已添加,现在可以生成 zip 文件 - const content = await zip.generateAsync({ type: 'blob' }); - + const content = await zip.generateAsync({ type: "blob" }); + // 创建一个可下载的链接 const url = window.URL.createObjectURL(content); - + // 创建一个下载链接元素 - const a = document.createElement('a'); + const a = document.createElement("a"); a.href = url; - a.download = 'images.zip'; // 设置下载文件的名称 - + a.download = "images.zip"; // 设置下载文件的名称 + // 模拟点击下载链接 a.click(); - + // 释放 URL 对象 window.URL.revokeObjectURL(url); } catch (error) { - console.error('Error while handling download:', error); + console.error("Error while handling download:", error); } - } + }; const zip = new JSZip(); const fetchAndAddImageToZip = async (imageUrl, filename) => { - try { const response = await fetch(imageUrl); if (response.ok) { const imageData = await response.blob(); const imageFile = new File([imageData], filename); - console.log(imageFile) + console.log(imageFile); zip.file(filename, imageFile); } else { console.error(`Failed to fetch image from ${imageUrl}`); @@ -426,159 +443,217 @@ function ModelSelect() { console.error(`Error fetching image from ${imageUrl}: ${error.message}`); } }; - useEffect(()=>{ - console.log('listen') - if(isRender === false && isResultComplete === false){ - onBtnClick() + useEffect(() => { + console.log("listen"); + if (isRender === false && isResultComplete === false) { + onBtnClick(); } - },[]) - + }, []); + return (
+
+ +
+ +
+
+
-
- -
- -
-
-
- - {isRender && - - -
-
- {msg && !msg.includes('Error') && ( - - {msg} - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
- - -
-
- )} - - { - msg && msg.includes('Error') && -
-
{msg}
-
Unsupported format or unclear image.
- Back +
+
+ {msg && !msg.includes("Error") && ( + + {msg} + +
+
+ +
- } - -
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + )} + + {msg && msg.includes("Error") && ( +
+
+ {msg} +
+
+ Unsupported format or unclear image. +
+ + Back + +
+ )} +
- - - - } + )} - {beforeImage? + {beforeImage ? ( Loading

}> - -
你的圖片:
+ +
你的圖片:
- Selected - + Selected
{/* 在这里可以进行图像上传或其他操作 */}
- : -
Remember to upload a photo
- } - - - - - - - - - -
- -
+ Remember to upload a photo +
+ )} + + +
- + + Home + + + + + +
+
+
- - {beforeImage? -
+
+ + {beforeImage ? ( +
+ ) : ( +
+
+ No images found for operation.
- : -
-
No images found for operation.
-
Please take a new photo or upload the image.
+
+ Please take a new photo or upload the image.
- } - - - - - - - - +
+ )} + +
- ) + ); } -export default ModelSelect \ No newline at end of file +export default ModelSelect; diff --git a/src/Page/Camera/Result.js b/src/Page/Camera/Result.js index 460c187..8f8a335 100644 --- a/src/Page/Camera/Result.js +++ b/src/Page/Camera/Result.js @@ -1,6 +1,6 @@ -import React,{Suspense} from 'react' +import React, { useState, Suspense } from "react"; import { Link } from "react-router-dom"; - +import { useNavigate } from "react-router-dom"; import { Button, Dialog, @@ -8,158 +8,237 @@ import { DialogBody, DialogFooter, Spinner, - IconButton + IconButton, } from "@material-tailwind/react"; import { motion, AnimatePresence } from "framer-motion"; import { FaExternalLinkAlt } from "react-icons/fa"; import { FaXmark } from "react-icons/fa6"; import { FiLoader } from "react-icons/fi"; -function Result({open ,handleOpen,taskStatus,handleDownload,isCompressing,isResultComplete}) { +function Result({ + open, + handleOpen, + taskStatus, + handleDownload, + isCompressing, + isResultComplete, +}) { + const navigate = useNavigate(); + const [selectedImage, setSelectedImage] = useState(null); + const [uploading, setUploading] = useState(false); + // 新增上傳函數 + const handleUpload = async () => { + if (!selectedImage) { + alert("請先選擇一張圖片"); + return; + } + + try { + setUploading(true); + + // 將 base64 圖片轉換為 Blob + const base64Response = await fetch(selectedImage); + const blob = await base64Response.blob(); + + // 準備上傳資料 + const formData = new FormData(); + formData.append("source_image", blob); + formData.append("prefix", "backto80"); + + // 發送上傳請求 + let apiurl = "https://backto80s-api.rd-02f.workers.dev/"; + const response = await fetch( + "https://backto80s-api.rd-02f.workers.dev/upload", + { + method: "POST", + headers: { + Authorization: `${process.env.REACT_APP_APITOKEN}`, + }, + body: formData, + } + ); + + if (!response.ok) throw new Error("上傳失敗"); + + const data = await response.json(); + console.log("上傳成功:", data.uri); + if (window.confirm("上傳成功!可以準備領卡片了?")) { + navigate("/"); // 導航到首頁 + } + } catch (error) { + console.error("上傳錯誤:", error); + alert("上傳失敗,請稍後再試"); + } finally { + setUploading(false); + } + }; const downloadImage = (imgurl) => { - const imageUrl = imgurl - const link = document.createElement('a'); + const imageUrl = imgurl; + const link = document.createElement("a"); link.href = imageUrl; - link.download = 'downloaded-image.jpg'; + link.download = "downloaded-image.jpg"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }; const downloadImageBlob = (imgurl) => { const imageUrl = imgurl; - + fetch(imageUrl) .then((response) => response.blob()) .then((blob) => { - const link = document.createElement('a'); + const link = document.createElement("a"); link.href = URL.createObjectURL(blob); - link.download = 'downloaded-image.jpg'; + link.download = "downloaded-image.jpg"; document.body.appendChild(link); link.click(); document.body.removeChild(link); }) - .catch((error) => console.error('下載失敗:', error)); + .catch((error) => console.error("下載失敗:", error)); }; return (
- - - + + + - -
- + +
{Object.keys(taskStatus).length > 0 && ( -
- }> -
Press and hold to save photo↓
-
- {Object.keys(taskStatus).length > 0 ? - - taskStatus.map((item,index)=>{ - if(item.finished === 0){ - return( -
-
-
-
-
+
+ }> +
+ Press and hold to save photo↓ +
+
+ {Object.keys(taskStatus).length > 0 ? ( + taskStatus.map((item, index) => { + if (item.finished === 0) { + return ( +
+
+
+
- {item.status}
- ) - }else{ - return( - -
- -
- - - setSelectedImage(item.img)} + > + + {item.status} - -
- ) +
+ ); } - }) - : + ) : (
no result or fail
- } + )} +
+
+
你可以選擇一張喜歡的圖片列印成卡片
+
+ {selectedImage ? "已選擇 1 張圖片" : "尚未選擇圖片"} +
+ {selectedImage && ( + + )}
- - - -
)} - - -
-
- - {isCompressing &&
Compressing..
} -
- {/*
+
+
+ + {isCompressing && ( +
+ + Compressing..{" "} +
+ )} +
+ {/*
*/} -
- - - -
- +
+ + +
- +
- - - -
- ) + ); } -export default Result \ No newline at end of file +export default Result; diff --git a/src/firebase.js b/src/firebase.js index f2aa2ce..bf967c3 100644 --- a/src/firebase.js +++ b/src/firebase.js @@ -5,6 +5,7 @@ import { getFunctions, connectFunctionsEmulator } from "firebase/functions"; // https://firebase.google.com/docs/emulator-suite/connect_firestore#android_apple_platforms_and_web_sdks import { getFirestore, connectFirestoreEmulator } from "firebase/firestore"; +import { getStorage } from "firebase/storage"; const firebase = initializeApp({ projectId: process.env.REACT_APP_FIREBASE_PROJECT_ID, @@ -16,7 +17,7 @@ const firebase = initializeApp({ }); const functions = getFunctions(firebase); const firestore = getFirestore(firebase); - +const storage = getStorage(firebase); if (window.location.hostname === "localhost") { console.log( "Testing locally: hitting local functions and firestore emulators." @@ -25,4 +26,4 @@ if (window.location.hostname === "localhost") { connectFirestoreEmulator(firestore, "127.0.0.1", 8080); } -export { functions, firestore }; \ No newline at end of file +export { functions, firestore, storage };