Hi, I’m using React frontend and Nodejs Backend.
I want to add a progress bar on my frontend.
Here is my apolloClient setup:
Blockquote
import { ApolloClient, InMemoryCache } from ‘@apollo/client’;
import createUploadLink from ‘apollo-upload-client/createUploadLink.mjs’;
const uploadLink = createUploadLink({
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT, // Use the environment variable
});
const client = new ApolloClient({
link: uploadLink,
cache: new InMemoryCache(),
});
export default client;
Blockquote
and here is my component:
Blockquote
import React, { useState } from ‘react’;
import { useParams } from ‘react-router-dom’;
import { useQuery, useMutation, gql } from ‘@apollo/client’;
import MerchandiseItem from ‘…/components/MerchandiseItem’;
const GET_MERCH_PAGE = gql`
query GetMerchPage($input: MerchInput!) {
getMerchPage(input: $input) {
id
title
description
ownerId
items {
id
title
description
price
attributes
mainImage
merchandisePageId
}
}
}
`;
const CREATE_MERCH_ITEM = gql mutation Mutation($input: MerchandiseItemInput!) { CreateMerchandiseItem(input: $input) { id title description merchandisePageId mainImage } }
;
const UPDATE_MERCH_ITEM = gql mutation UpdateMerchItem($id: ID!, $input: MerchItemInput!) { updateMerchItem(id: $id, input: $input) { id title description price attributes mainImage additionalImages merchandisePageId } }
;
const DELETE_MERCH_ITEM = gql mutation DeleteMerchItem($id: ID!) { deleteMerchItem(id: $id) { id } }
;
// Your existing GraphQL queries and mutations here…
const MerchandiseDashboard = () => {
const { merchPageId } = useParams();
const token = sessionStorage.getItem(‘token’);
const MerchInput = merchPageId;
const [progress, setProgress] = useState(0);
const { loading, error, data, refetch } = useQuery(GET_MERCH_PAGE, {
variables: { input: { id: merchPageId } },
context: {
headers: {
Authorization: Bearer ${token}
,
},
},
});
const [createMerchItem] = useMutation(CREATE_MERCH_ITEM,{
context: {
fetchOptions: {
onUploadProgress: (event) => {
if (event.lengthComputable) {
const percentCompleted = Math.round((event.loaded * 100) / event.total);
setProgress(percentCompleted); // Update the progress state
}
},
},
},
});
const [updateMerchItem] = useMutation(UPDATE_MERCH_ITEM);
const [deleteMerchItem] = useMutation(DELETE_MERCH_ITEM);
const [editingItem, setEditingItem] = useState(null);
const [newItem, setNewItem] = useState({ title: ‘’, description: ‘’, price: 0, mainImageFile: null });
const [imageFile, setImageFile] = useState(null); // State to store the selected file
if (loading) return
Loading…
;if (error) return
Error: {error.message}
;const { title, description, items } = data.getMerchPage;
const backgroundimage = https://gigabout.com.au/merchimages/${merchPageId}/background.jpg
;
// Handle image file change
const handleImageChange = async (e) => {
console.log(“we made it into handleImageChange”)
const file = e.target.files[0]; // Get the selected file
if (file) {
const fileSizeInMB = file.size / (1024 * 1024); // Convert size to MB
if (fileSizeInMB > 6) {
alert(“File size exceeds 6 MB. Please choose a smaller file.”);
return;
}
try {
console.log(file.name)
setNewItem({ ...newItem, mainImageFile: file }); // Update newItem with base64 image
} catch (error) {
console.error('Error:', error);
}
}
};
const handleCreateOrUpdate = async () => {
if (editingItem) {
await updateMerchItem({ variables: { id: editingItem.id, input: newItem } });
} else {
console.log(newItem)
await createMerchItem({
variables: {
input: {
title: newItem.title,
description: newItem.description,
price: newItem.price,
mainImageFile: newItem.mainImageFile, // Pass the file directly
merchandisePageId: merchPageId
}
}
});
}
refetch();
setNewItem({ title: '', description: '', price: 0, mainImage: '', additionalImages: [] });
setEditingItem(null);
};
const handleEdit = (item) => {
setEditingItem(item);
setNewItem(item);
};
const handleDelete = async (id) => {
await deleteMerchItem({ variables: { id } });
refetch();
};
return (
<div
style={{
position: ‘relative’,
minHeight: ‘100vh’,
backgroundImage: url(${backgroundimage})
,
backgroundRepeat: ‘repeat’,
backgroundSize: ‘cover’,
}}
>
<div
style={{
position: ‘absolute’,
top: 0,
left: 0,
width: ‘100%’,
height: ‘100%’,
backgroundColor: ‘rgba(255, 255, 255, 0.886)’,
zIndex: 1,
}}
/>
<div style={{ position: ‘relative’, zIndex: 2 }}>
{title}
<p>{description}</p>
{/* Merchandise Item Form */}
<div className="merchandise-form">
<input
type="text"
placeholder="Title"
value={newItem.title}
onChange={(e) => setNewItem({ ...newItem, title: e.target.value })}
/>
<textarea
placeholder="Description"
value={newItem.description}
onChange={(e) => setNewItem({ ...newItem, description: e.target.value })}
/>
<input
type="number"
placeholder="Price"
value={newItem.price}
onChange={(e) => setNewItem({ ...newItem, price: parseFloat(e.target.value) })}
/>
{/* Image Picker */}
<input
type="file"
accept="image/png, image/jpeg"
onChange={handleImageChange}
/>
<button onClick={handleCreateOrUpdate}>{editingItem ? 'Update Item' : 'Create Item'}</button>
{editingItem && <button onClick={() => setEditingItem(null)}>Cancel Edit</button>}
{/* Display the progress */}
{progress > 0 && <div>Upload progress: {progress}%</div>}
</div>
{/* Progress Bar */}
{progress > 0 && (
<div style={{ margin: '10px 0' }}>
<div style={{
width: '100%',
height: '10px',
backgroundColor: '#e0e0e0',
borderRadius: '5px',
}}>
<div style={{
width: `${progress}%`,
height: '100%',
backgroundColor: progress < 100 ? '#3b82f6' : '#4caf50', // Blue during upload, green when complete
borderRadius: '5px',
transition: 'width 0.3s ease',
}} />
</div>
<p>{progress}%</p> {/* Optional to show the percentage as text */}
</div>
)}
{/* Merchandise Items List */}
<h2>Manage Products</h2>
<div style={{ paddingLeft: 20, paddingRight: 20 }}>
<div
style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
gap: '16px',
backgroundColor: 'transparent',
}}
>
{items.map((item) => (
<div key={item.id}>
<MerchandiseItem item={item} />
<button onClick={() => handleEdit(item)}>Edit</button>
<button onClick={() => handleDelete(item.id)}>Delete</button>
</div>
))}
</div>
</div>
</div>
</div>
);
};
export default MerchandiseDashboard;