Créer un projet
Créer un projet
Créer un projet
Créer un projet
Créer un projet
Cliquez sur projet web
Créer un projet
Créer un projet
Créer un projet
mettre la config dans un fichier : firebase/config.ts
Installer firebase
npm i firebase --save
Importer firebase
import * as firebase from 'firebase/app';
Importer storage et firestore
import 'firebase/storage';
import 'firebase/firestore';
Inititaliser storage et firestore
const projectStorage = firebase.storage();
const projectFirestore = firebase.firestore();
export { projectStorage, projectFirestore };
Code
import * as firebase from 'firebase/app';
import 'firebase/storage';
import 'firebase/firestore';
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: 'AIzaSyDAmgRgchDRU0du9-WPfy7XFnkgk7r2mVw',
authDomain: 'image-upload-d2866.firebaseapp.com',
databaseURL: 'https://image-upload-d2866.firebaseio.com',
projectId: 'image-upload-d2866',
storageBucket: 'image-upload-d2866.appspot.com',
messagingSenderId: '993562178799',
appId: '1:993562178799:web:fdf294227df4f043eb571a',
};
// Initialize Firebase
firebase.initializeApp(firebaseConfig);
const projectStorage = firebase.storage();
const projectFirestore = firebase.firestore();
export { projectStorage, projectFirestore };
Créer une base de données
Créer une base de données
Créer une base de données
Créer une base de données
Créer un service storage
Créer un service storage
Créer un service storage
Créer un service storage
Changement des règles
allow read, write: if request.auth != null;
devient allow read, write
Changement des règles
Création de notre uploader
import React from 'react';
export default function Uploader() {
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
console.log(event.currentTarget.files);
}
return (
<form>
<input type="file" onChange={handleChange}/>
</form>
);
}
Création de notre uploader
Création de notre uploader
Gestion du state de l'uploader
import React, { useState } from 'react';
export default function Uploader() {
const [file, setFile] = useState<any>(undefined)
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
if(event.currentTarget.files === null){
return;
}
const selectedFile = event.currentTarget.files[0];
if(selectedFile){
setFile(selectedFile);
}
}
return (
<form>
<input type="file" onChange={handleChange}/>
</form>
);
}
Gestion des types
import React, { useState } from 'react';
export default function Uploader() {
const [file, setFile] = useState<any>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
const types = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'];
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
if (event.currentTarget.files === null) {
return;
}
const selectedFile = event.currentTarget.files[0];
if (selectedFile && types.includes(selectedFile.type)) {
setFile(selectedFile);
setError(undefined);
} else {
setFile(undefined);
setError('Le format du fichier est incorrect');
}
};
return (
<form>
<input type="file" onChange={handleChange} />
{error ? <p>{error}</p> : null}
</form>
);
}Test gestion des types
Création d'un hook useStorage
import { useState, useEffect } from 'react';
import { projectStorage } from '../../../firebase/config';
const useStorage = (file: any) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState<any | undefined>(undefined);
const [url, setUrl] = useState<any | undefined>(undefined);
useEffect(() => {
const storageRef = projectStorage.ref(file.name);
storageRef.put(file).on(
'state_changed',
(snap) => {
let percentage = Math.round((snap.bytesTransferred / snap.totalBytes) * 100);
setProgress(percentage);
},
(err) => {
setError(err);
},
async () => {
const url = await storageRef.getDownloadURL();
setUrl(url);
}
);
}, [file]);
return { progress, url, error };
};
export default useStorage;Création d'une progress bar
import React, { useEffect } from 'react';
import useStorage from './hooks/useStorage';
export default function ProgressBar({
file,
onFileUploadCompleted,
}: {
file: any;
onFileUploadCompleted: Function;
}) {
const { url, progress } = useStorage(file);
useEffect(() => {
if(url) {
onFileUploadCompleted(url);
}
}, [url, onFileUploadCompleted]);
return (
<div>
<p>{progress} %</p>
<div style={{ width: `${progress}%` }}></div>
</div>
);
}
Style de la progress bar
.progressBar {
height: 5px;
background: #2ecc71;
}style.module.scss
import styles from './styles.module.scss';
return (
<div>
<p>{progress} %</p>
<div className={styles.progressBar} style={{ width: `${progress}%` }}></div>
</div>
);progressBar.tsx
Test de notre composant
Le storage Firebase
Faire le lien entre le storage et la base de données
const getTimestamp = firebase.firestore.FieldValue.serverTimestamp;
export { projectStorage, projectFirestore, getTimestamp };
Nous avons besoin de cela dans notre fichier de configuration afin d'enregistrer la date de création de l'image dans la base de données
Faire le lien entre le storage et la base de données
import { useState, useEffect } from 'react';
import { projectStorage, projectFirestore, getTimestamp } from '../../../firebase/config';
const useStorage = (file: any) => {
const [progress, setProgress] = useState(0);
const [error, setError] = useState<any | undefined>(undefined);
const [url, setUrl] = useState<any | undefined>(undefined);
useEffect(() => {
const storageRef = projectStorage.ref(file.name);
const collectionRef = projectFirestore.collection('image');
storageRef.put(file).on(
'state_changed',
(snap) => {
let percentage = Math.round((snap.bytesTransferred / snap.totalBytes) * 100);
setProgress(percentage);
},
(err) => {
setError(err);
},
async () => {
const url = await storageRef.getDownloadURL();
collectionRef.add({ url, createdAt: getTimestamp() })
setUrl(url);
}
);
}, [file]);
return { progress, url, error };
};
export default useStorage;
Code uploader
import React, { useState } from 'react';
import ProgressBar from './ProgressBar';
export default function Uploader() {
const [file, setFile] = useState<any>(undefined);
const [error, setError] = useState<string | undefined>(undefined);
const types = ['image/png', 'image/jpg', 'image/jpeg', 'image/gif'];
const onFileUploadCompleted = (url: string) => {
setFile(undefined);
setError(undefined);
}
const handleChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
if (event.currentTarget.files === null) {
return;
}
const selectedFile = event.currentTarget.files[0];
if (selectedFile && types.includes(selectedFile.type)) {
setFile(selectedFile);
setError(undefined);
} else {
setFile(undefined);
setError('Le format du fichier est incorrect');
}
};
return (
<form>
<input type="file" onChange={handleChange} />
{error ? <p>{error}</p> : null}
{file && <ProgressBar file={file} onFileUploadCompleted={onFileUploadCompleted}/>}
</form>
);
}
Image grid component
import React from 'react';
import styles from './styles.module.scss';
export default function ImageGrid() {
return (
<div className={styles.imgGrid}>
images
</div>
)
}.imgGrid {}Créer useFirestore hook
import { useState, useEffect } from 'react';
import { projectFirestore } from '../../../firebase/config';
export default function useFirestore(collection: any) {
const [docs, setDocs] = useState<Array<any>>([]);
useEffect(() => {
const unsuscribe = projectFirestore
.collection(collection)
.orderBy('createdAt', 'desc')
.onSnapshot((snap) => {
let documents: Array<any> = [];
snap.forEach((doc) => documents.push({ ...doc.data(), id: doc.id }));
setDocs(documents);
});
return () => unsuscribe();
}, [collection]);
return { docs };
}
Utilisation du hook dans Image grid
import React from 'react';
import styles from './styles.module.scss';
import useFirestore from '../../components/Uploader/hooks/useFirestore';
export default function ImageGrid({ onSelectedImage}: {onSelectedImage: Function}) {
const { docs } = useFirestore('image');
return (
<div className={styles.imgGrid}>
{docs &&
docs.map((doc) => {
return (
<div key={doc.id} className={styles.imgWrapper}>
<img src={doc.url} onClick={() => onSelectedImage(doc.url)} />
</div>
);
})}
</div>
);
}
Style du composant image grid
.imgGrid {
margin: 20px auto;
display: grid;
grid-template-columns: 1fr 1fr 1fr;
grid-gap: 25px;
}
.imgWrapper {
overflow: hidden;
height: 0;
padding: 50% 0;
position: relative;
img {
min-width: 100%;
min-height: 100%;
position: absolute;
top: 0;
left: 0;
max-width: 150%;
}
}
Resultat
Création d'une modal
import React from 'react';
import styles from './styles.module.scss';
export default function Modal({
image,
onBackdropClick,
}: {
image: string | undefined;
onBackdropClick: any;
}) {
return (
<div className={styles.backdrop} onClick={onBackdropClick}>
<img src={image} />
</div>
);
}
Style de la modal
.backdrop {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(30, 30, 30, 0.5);
z-index: 9999;
img {
margin: 60px auto;
display: block;
max-width: 70%;
max-height: 70%;
}
}
Interaction entre nos composants
import React, { useState } from 'react';
import styles from './styles.module.scss';
import Topbar from '../../components/Topbar';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faChevronLeft } from '@fortawesome/free-solid-svg-icons';
import { Link } from 'react-router-dom';
import Uploader from '../../components/Uploader';
import ImageGrid from '../../components/ImageGrid';
import Modal from '../../components/Modal';
export default function Dashboard() {
const [selectedImage, setSelectedImage] = useState<string | undefined>(
undefined
);
const handleSelectedImage = (image: string) => {
setSelectedImage(image);
};
const handleOnCloseBackdrop = () => {
setSelectedImage(undefined);
}
return (
<div>
<Topbar>
<Link to="/" className={styles.backButton}>
<FontAwesomeIcon icon={faChevronLeft} />
</Link>
</Topbar>
<div className="mt-4 p-4">
<Uploader />
</div>
<div className="mt-4 p-4">
<ImageGrid onSelectedImage={handleSelectedImage} />
</div>
{selectedImage ? <Modal image={selectedImage} onBackdropClick={handleOnCloseBackdrop} /> : null}
</div>
);
}
Résultat