))}
diff --git a/src/components/authoring/StepDetailsTab.js b/src/components/authoring/StepDetailsTab.js
index 990f561..cc008c2 100644
--- a/src/components/authoring/StepDetailsTab.js
+++ b/src/components/authoring/StepDetailsTab.js
@@ -1,6 +1,11 @@
import React, { useEffect } from 'react';
import { COMPONENTS, COLORS } from './shared/styles';
+// Chrome detection function
+const isChrome = () => {
+ return navigator.userAgent.includes('Chrome') && !navigator.userAgent.includes('Edg') && !navigator.userAgent.includes('Safari');
+};
+
const getAnnotationText = (annotation) => {
return annotation.component_name || annotation.data?.text || 'Untitled annotation';
};
@@ -22,6 +27,11 @@ const subCardStyle = {
marginTop: '1.25rem',
marginBottom: '1.25rem',
boxShadow: 'none',
+ ...(isChrome() && {
+ padding: '0.75rem',
+ marginTop: '1rem',
+ marginBottom: '1rem',
+ }),
};
const subCardTitleStyle = {
fontWeight: 600,
@@ -29,10 +39,17 @@ const subCardTitleStyle = {
fontSize: '1.05rem',
marginBottom: '0.5rem',
letterSpacing: '0.01em',
+ ...(isChrome() && {
+ fontSize: '0.95rem',
+ marginBottom: '0.4rem',
+ }),
};
const subCardTextStyle = {
color: '#E5E5E5',
fontSize: '0.97rem',
+ ...(isChrome() && {
+ fontSize: '0.9rem',
+ }),
};
const goldAccent = { color: '#F1C232', fontWeight: 500 };
const blueAccent = { color: '#3B82F6', fontWeight: 500 };
@@ -161,6 +178,11 @@ const StepDetailsTab = ({
color: '#F5F5F5',
fontSize: '0.97rem',
transition: 'box-shadow 0.15s',
+ ...(isChrome() && {
+ padding: '0.4rem 0.6rem',
+ marginBottom: '0.4rem',
+ fontSize: '0.9rem',
+ }),
}}
>
@@ -193,6 +215,11 @@ const StepDetailsTab = ({
fontWeight: 600,
marginLeft: '0.7rem',
transition: 'background 0.15s, color 0.15s',
+ ...(isChrome() && {
+ padding: '3px 8px',
+ fontSize: '0.85rem',
+ marginLeft: '0.5rem',
+ }),
}}
onMouseOver={e => { e.currentTarget.style.background = '#fff'; e.currentTarget.style.color = '#F1C232'; }}
onMouseOut={e => { e.currentTarget.style.background = '#F1C232'; e.currentTarget.style.color = '#18181b'; }}
@@ -219,6 +246,9 @@ const StepDetailsTab = ({
color: '#BDBDBD',
fontStyle: 'italic',
fontSize: '0.97rem',
+ ...(isChrome() && {
+ fontSize: '0.9rem',
+ }),
}}>
No annotations yet
@@ -226,6 +256,9 @@ const StepDetailsTab = ({
margin: '4px 0 0 0',
fontSize: '0.93rem',
color: '#BDBDBD',
+ ...(isChrome() && {
+ fontSize: '0.85rem',
+ }),
}}>
Use "Capture Frame" in the video section to add annotations
diff --git a/src/components/pages/CreateSteps.js b/src/components/pages/CreateSteps.js
index ca0bdd4..e564c11 100644
--- a/src/components/pages/CreateSteps.js
+++ b/src/components/pages/CreateSteps.js
@@ -287,17 +287,6 @@ const ProjectStepsPage = () => {
resultImageInputRef,
// Buy list data
projectBuyList,
- buyListItemName,
- setBuyListItemName,
- buyListItemQty,
- setBuyListItemQty,
- buyListItemSpec,
- setBuyListItemSpec,
- buyListItemLink,
- setBuyListItemLink,
- buyListItemImageFile,
- setBuyListItemImageFile,
- buyListImageInputRef,
// Add missing captured annotation frames
capturedAnnotationFrames,
setCapturedAnnotationFrames,
@@ -476,10 +465,19 @@ const ProjectStepsPage = () => {
return
{errorMessage} navigate(-1)} style={{...chromeStyles.backLink, marginLeft: '10px'}}>Go Back
;
}
+ // Chrome-specific layout adjustments
+ const isChrome = navigator.userAgent.includes('Chrome');
+
return (
{/* Header */}
@@ -599,27 +597,12 @@ const ProjectStepsPage = () => {
) : activeTab === 'repository' ? (
diff --git a/src/components/pages/Repository.styles.js b/src/components/pages/Repository.styles.js
index 7af91ce..51bedc2 100644
--- a/src/components/pages/Repository.styles.js
+++ b/src/components/pages/Repository.styles.js
@@ -63,7 +63,8 @@ export const repositoryStyles = {
actionsContainer: "flex justify-end space-x-2 pt-2 border-t border-[#D9D9D9] mt-2",
actionButton: "bg-[#18181b] border border-[#D9D9D9] text-[#D9D9D9] p-2 rounded-lg transition-all duration-200 hover:bg-[#222222] hover:border-[#F1C232] hover:text-[#F1C232] flex items-center justify-center",
editButton: "hover:bg-[#0000FF] hover:border-[#0000FF] hover:text-white",
- deleteButton: "hover:bg-red-600 hover:border-red-600 hover:text-white"
+ deleteButton: "hover:bg-red-600 hover:border-red-600 hover:text-white",
+ buyButton: "hover:bg-[#F1C232] hover:border-[#F1C232] hover:text-black"
}
},
diff --git a/src/components/pages/createsteps helpers/CreateStepsActions.js b/src/components/pages/createsteps helpers/CreateStepsActions.js
index be8abfb..a96e0a8 100644
--- a/src/components/pages/createsteps helpers/CreateStepsActions.js
+++ b/src/components/pages/createsteps helpers/CreateStepsActions.js
@@ -620,14 +620,72 @@ export const createStepActions = (state) => {
processedStepsPayload.push(stepPayload);
}
- // --- 3. VALIDATE AND SEND FINAL PAYLOAD TO THE API ---
+ // --- 3. PROCESS BUY LIST ITEMS ---
+ const processedBuyListPayload = [];
+ for (const item of projectBuyList) {
+ let itemImageUrl = null;
+ let itemImagePath = null;
+
+ if (item.imageFile) {
+ // New image file to upload
+ const uploaded = await uploadFileToFirebase(item.imageFile, `users/${currentUser.uid}/${projectId}/buy_list_images`, currentUser);
+ if (uploaded) {
+ itemImageUrl = uploaded.url;
+ itemImagePath = uploaded.path;
+ }
+ } else if (item.hasExistingImage && item.image_url && item.image_path) {
+ // Preserve existing image from database
+ itemImageUrl = item.image_url;
+ itemImagePath = item.image_path;
+ }
+
+ processedBuyListPayload.push({
+ name: item.name,
+ quantity: item.quantity,
+ specification: item.specification,
+ purchase_link: item.purchase_link,
+ image_url: itemImageUrl,
+ image_path: itemImagePath,
+ });
+ }
+
+ // If buy list is empty, explicitly send empty array to prevent backend auto-generation
+ console.log(`Finalizing project with ${processedBuyListPayload.length} buy list items (${projectBuyList ? projectBuyList.length : 0} in frontend)`);
+
+ // --- 4. SYNC BUY LIST TO DATABASE BEFORE FINALIZING ---
+ // Send the buy list data to the database to ensure it's saved
+ try {
+ const buyListSyncResponse = await fetch(`${getApiUrl()}/projects/${projectId}/buy_list?firebase_uid=${currentUser.uid}`, {
+ method: 'PATCH',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: JSON.stringify(processedBuyListPayload),
+ });
+
+ if (!buyListSyncResponse.ok) {
+ const errorData = await buyListSyncResponse.json();
+ console.warn("Failed to sync buy list to database:", errorData);
+ // Continue with finalization even if sync fails
+ } else {
+ const syncResult = await buyListSyncResponse.json();
+ console.log("Buy list synced to database:", syncResult.message);
+ }
+ } catch (syncError) {
+ console.warn("Error syncing buy list to database:", syncError);
+ // Continue with finalization even if sync fails
+ }
+
+ // --- 5. VALIDATE AND SEND FINAL PAYLOAD TO THE API ---
const finalPayload = {
project_name: projectName,
project_id: projectId,
user_id: currentUser.uid,
steps: processedStepsPayload,
- buy_list: projectBuyList || [],
+ buy_list: processedBuyListPayload,
thumbnail_path: uploadedThumbnailInfo ? uploadedThumbnailInfo.path : null,
+ // Explicitly indicate if buy list should be auto-generated from steps
+ auto_generate_buy_list: false, // Never auto-generate, only use what's explicitly provided
};
// Validate payload before sending
@@ -712,6 +770,11 @@ export const createStepActions = (state) => {
const result = await response.json();
console.log('API Response:', result);
+ // Clear the buy list state from localStorage since project is now finalized
+ localStorage.removeItem(`buyListState_${projectId}`);
+ // Also clear the old sessionStorage flag if it exists
+ sessionStorage.removeItem(`buyListCleared_${projectId}`);
+
setSuccessMessage('Project finalized successfully! Redirecting to projects...');
setTimeout(() => navigate('/my-projects'), 2000);
diff --git a/src/components/pages/createsteps helpers/CreateStepsHandlers.js b/src/components/pages/createsteps helpers/CreateStepsHandlers.js
index 2fe4a92..7642766 100644
--- a/src/components/pages/createsteps helpers/CreateStepsHandlers.js
+++ b/src/components/pages/createsteps helpers/CreateStepsHandlers.js
@@ -31,11 +31,6 @@ export const createStepHandlers = (
setCurrentStepResultImageFile,
setCurrentStepResultImage,
setProjectBuyList,
- setBuyListItemName,
- setBuyListItemQty,
- setBuyListItemSpec,
- setBuyListItemLink,
- setBuyListItemImageFile,
setProjectSteps,
setCurrentStepIndex,
setIsStepLoading,
@@ -45,7 +40,6 @@ export const createStepHandlers = (
toolImageInputRef,
materialImageInputRef,
supFileInputRef,
- buyListImageInputRef,
videoRef,
// State values
uploadedVideos,
@@ -67,11 +61,6 @@ export const createStepHandlers = (
currentStepMaterialImageFile,
currentStepMaterialPurchaseLink,
currentStepMaterialQuantity,
- buyListItemName,
- buyListItemQty,
- buyListItemSpec,
- buyListItemLink,
- buyListItemImageFile,
projectBuyList,
projectSteps,
currentStepIndex,
@@ -225,325 +214,6 @@ export const createStepHandlers = (
}
};
- const handleAddBuyListItem = () => {
- if (!buyListItemName.trim()) { alert("Item name is required for buy list."); return; }
- // Only store serializable data
- const safeImageFile = buyListItemImageFile instanceof File ? buyListItemImageFile : null;
- setProjectBuyList(prev => [...prev, {
- id: `buyitem_${uuidv4()}`, name: buyListItemName,
- quantity: parseInt(buyListItemQty, 10) || 1, specification: buyListItemSpec,
- purchase_link: buyListItemLink, imageFile: safeImageFile,
- hasExistingImage: false // Mark as new item for proper image display
- }]);
- setBuyListItemName(''); setBuyListItemQty(1); setBuyListItemSpec('');
- setBuyListItemLink(''); setBuyListItemImageFile(null);
- if (buyListImageInputRef.current) buyListImageInputRef.current.value = "";
- };
-
- const removeBuyListItem = (itemId) => {
- const removedItem = projectBuyList.find(item => item.id === itemId);
- if (removedItem) {
- // If this is a project-generated item, we should remember not to add it back
- if (removedItem.id.startsWith('buyitem_tool_') || removedItem.id.startsWith('buyitem_material_')) {
- // Store the removed item name in localStorage to prevent auto-adding back
- const removedItems = JSON.parse(localStorage.getItem('removedBuyListItems') || '[]');
- if (!removedItems.includes(removedItem.name.toLowerCase())) {
- removedItems.push(removedItem.name.toLowerCase());
- localStorage.setItem('removedBuyListItems', JSON.stringify(removedItems));
- }
- }
- }
- setProjectBuyList(prev => prev.filter(item => item.id !== itemId));
- };
-
- // New function to fetch repository items and auto-populate buy list
- const handleAutoPopulateBuyList = async () => {
- if (!projectSteps || projectSteps.length === 0) {
- setErrorMessage("No steps found in project. Please add some steps first.");
- return;
- }
-
- try {
- setIsLoading(true);
- setErrorMessage('');
-
- // Collect all tools and materials from all steps in the project
- let allProjectItems = [];
-
- projectSteps.forEach((step, stepIndex) => {
- // Add tools from this step
- if (step.tools && step.tools.length > 0) {
- step.tools.forEach(tool => {
- // Skip tools without a valid name
- if (!tool.name || typeof tool.name !== 'string') {
- console.warn('Skipping tool without valid name:', tool);
- return;
- }
-
- allProjectItems.push({
- id: `buyitem_tool_${stepIndex}_${tool.id || uuidv4()}`,
- name: tool.name,
- quantity: tool.quantity || 1, // Use actual quantity from step
- specification: tool.specification || '',
- purchase_link: tool.purchase_link || '',
- imageFile: tool.imageFile || null,
- image_url: tool.image_url || null,
- image_path: tool.image_path || null,
- sourceType: 'tool',
- sourceStep: stepIndex + 1,
- sourceStepName: step.name
- });
- });
- }
-
- // Add materials from this step
- if (step.materials && step.materials.length > 0) {
- step.materials.forEach(material => {
- // Skip materials without a valid name
- if (!material.name || typeof material.name !== 'string') {
- console.warn('Skipping material without valid name:', material);
- return;
- }
-
- allProjectItems.push({
- id: `buyitem_material_${stepIndex}_${material.id || uuidv4()}`,
- name: material.name,
- quantity: material.quantity || 1, // Use actual quantity from step
- specification: material.specification || '',
- purchase_link: material.purchase_link || '',
- imageFile: material.imageFile || null,
- image_url: material.image_url || null,
- image_path: material.image_path || null,
- sourceType: 'material',
- sourceStep: stepIndex + 1,
- sourceStepName: step.name
- });
- });
- }
- });
-
- if (allProjectItems.length === 0) {
- setSuccessMessage('No tools or materials found in any project steps. Please add some tools or materials to your steps first.');
- setTimeout(() => setSuccessMessage(''), 5000);
- return;
- }
-
- // Remove duplicates by name and aggregate quantities
- const aggregatedItems = {};
-
- allProjectItems.forEach(item => {
- // Skip items without a name
- if (!item.name || typeof item.name !== 'string') {
- console.warn('Skipping item without valid name:', item);
- return;
- }
-
- const key = item.name.toLowerCase();
- if (aggregatedItems[key]) {
- // If item already exists, add the quantities
- aggregatedItems[key].quantity += (item.quantity || 1);
- } else {
- // If item doesn't exist, add it
- aggregatedItems[key] = { ...item };
- }
- });
-
- const uniqueItems = Object.values(aggregatedItems);
-
- // Filter out items that are already in the buy list (by name to avoid duplicates)
- const existingItemNames = new Set(projectBuyList.map(item => item.name?.toLowerCase()).filter(Boolean));
-
- // Get list of items that were manually removed
- const removedItems = JSON.parse(localStorage.getItem('removedBuyListItems') || '[]');
- const removedItemsSet = new Set(removedItems);
-
- const newItems = uniqueItems.filter(item =>
- item.name &&
- !existingItemNames.has(item.name.toLowerCase()) &&
- !removedItemsSet.has(item.name.toLowerCase())
- );
-
- if (newItems.length === 0) {
- setSuccessMessage('All project tools and materials are already in the buy list or were previously removed.');
- setTimeout(() => setSuccessMessage(''), 3000);
- return;
- }
-
- // Add new items to the buy list (append to existing)
- setProjectBuyList(prev => [...prev, ...newItems]);
-
- setSuccessMessage(`Successfully added ${newItems.length} items from project steps to buy list!`);
- setTimeout(() => setSuccessMessage(''), 5000);
-
- } catch (error) {
- console.error('Error auto-populating buy list from project:', error);
- setErrorMessage(`Failed to populate buy list from project: ${error.message}`);
- } finally {
- setIsLoading(false);
- }
- };
-
- // New function to update buy list from current project state
- const handleUpdateBuyListFromProject = async () => {
- if (!projectSteps || projectSteps.length === 0) {
- setErrorMessage("No steps found in project. Please add some steps first.");
- return;
- }
-
- try {
- setIsLoading(true);
- setErrorMessage('');
-
- // Collect all tools and materials from all steps in the project
- let allProjectItems = [];
-
- projectSteps.forEach((step, stepIndex) => {
- // Add tools from this step
- if (step.tools && step.tools.length > 0) {
- step.tools.forEach(tool => {
- // Skip tools without a valid name
- if (!tool.name || typeof tool.name !== 'string') {
- console.warn('Skipping tool without valid name:', tool);
- return;
- }
-
- allProjectItems.push({
- id: `buyitem_tool_${stepIndex}_${tool.id || uuidv4()}`,
- name: tool.name,
- quantity: tool.quantity || 1, // Use actual quantity from step
- specification: tool.specification || '',
- purchase_link: tool.purchase_link || '',
- imageFile: tool.imageFile || null,
- image_url: tool.image_url || null,
- image_path: tool.image_path || null,
- sourceType: 'tool',
- sourceStep: stepIndex + 1,
- sourceStepName: step.name
- });
- });
- }
-
- // Add materials from this step
- if (step.materials && step.materials.length > 0) {
- step.materials.forEach(material => {
- // Skip materials without a valid name
- if (!material.name || typeof material.name !== 'string') {
- console.warn('Skipping material without valid name:', material);
- return;
- }
-
- allProjectItems.push({
- id: `buyitem_material_${stepIndex}_${material.id || uuidv4()}`,
- name: material.name,
- quantity: material.quantity || 1, // Use actual quantity from step
- specification: material.specification || '',
- purchase_link: material.purchase_link || '',
- imageFile: material.imageFile || null,
- image_url: material.image_url || null,
- image_path: material.image_path || null,
- sourceType: 'material',
- sourceStep: stepIndex + 1,
- sourceStepName: step.name
- });
- });
- }
- });
-
- if (allProjectItems.length === 0) {
- setSuccessMessage('No tools or materials found in any project steps. Please add some tools or materials to your steps first.');
- setTimeout(() => setSuccessMessage(''), 5000);
- return;
- }
-
- // Remove duplicates by name and aggregate quantities
- const aggregatedItems = {};
-
- allProjectItems.forEach(item => {
- // Skip items without a name
- if (!item.name || typeof item.name !== 'string') {
- console.warn('Skipping item without valid name:', item);
- return;
- }
-
- const key = item.name.toLowerCase();
- if (aggregatedItems[key]) {
- // If item already exists, add the quantities
- aggregatedItems[key].quantity += (item.quantity || 1);
- } else {
- // If item doesn't exist, add it
- aggregatedItems[key] = { ...item };
- }
- });
-
- const uniqueProjectItems = Object.values(aggregatedItems);
-
- // Separate manually added items from project-generated items
- const manuallyAddedItems = projectBuyList.filter(item =>
- !item.id.startsWith('buyitem_tool_') && !item.id.startsWith('buyitem_material_')
- );
-
- // Get list of items that were manually removed
- const removedItems = JSON.parse(localStorage.getItem('removedBuyListItems') || '[]');
- const removedItemsSet = new Set(removedItems);
-
- // Filter out items that were manually removed
- const filteredProjectItems = uniqueProjectItems.filter(item =>
- item.name && !removedItemsSet.has(item.name.toLowerCase())
- );
-
- // Combine manually added items with filtered project items
- const updatedBuyList = [...manuallyAddedItems, ...filteredProjectItems];
-
- setProjectBuyList(updatedBuyList);
-
- const addedCount = filteredProjectItems.length;
- const removedCount = removedItems.length;
-
- let message = `Buy list updated! `;
- if (addedCount > 0) {
- message += `Added ${addedCount} items from project steps. `;
- }
- if (removedCount > 0) {
- message += `Respected ${removedCount} previously removed items. `;
- }
- if (manuallyAddedItems.length > 0) {
- message += `Preserved ${manuallyAddedItems.length} manually added items.`;
- }
-
- setSuccessMessage(message);
- setTimeout(() => setSuccessMessage(''), 5000);
-
- } catch (error) {
- console.error('Error updating buy list from project:', error);
- setErrorMessage(`Failed to update buy list from project: ${error.message}`);
- } finally {
- setIsLoading(false);
- }
- };
-
- // New function to clear the entire buy list
- const handleClearBuyList = () => {
- if (projectBuyList.length === 0) {
- setSuccessMessage('Buy list is already empty.');
- setTimeout(() => setSuccessMessage(''), 3000);
- return;
- }
-
- const confirmed = window.confirm(`Are you sure you want to clear all ${projectBuyList.length} items from the buy list? This action cannot be undone.`);
- if (confirmed) {
- setProjectBuyList([]);
- // Also clear the removed items list so they can be added back if needed
- localStorage.removeItem('removedBuyListItems');
- setSuccessMessage('Buy list cleared successfully. Removed items can now be added back.');
- setTimeout(() => setSuccessMessage(''), 3000);
- }
- };
-
- // Function to replace buy list with new items (for auto-populate)
- const handleReplaceBuyList = (newItems) => {
- setProjectBuyList(newItems);
- };
-
const handleFinishProject = async (projectName) => {
setIsLoading(true);
setErrorMessage('');
@@ -695,6 +365,9 @@ export const createStepHandlers = (
// If buy list is empty, explicitly send empty array to prevent backend auto-generation
console.log(`Finalizing project with ${processedBuyListPayload.length} buy list items (${projectBuyList ? projectBuyList.length : 0} in frontend)`);
+ // If buy list is empty, explicitly send empty array to prevent backend auto-generation
+ console.log(`Finalizing project with ${processedBuyListPayload.length} buy list items (${projectBuyList ? projectBuyList.length : 0} in frontend)`);
+
const finalApiPayload = {
project_id: projectId,
project_name: projectName,
@@ -705,7 +378,8 @@ export const createStepHandlers = (
auto_generate_buy_list: false, // Never auto-generate, only use what's explicitly provided
};
- console.log("Final API Payload to send to backend:", JSON.stringify(finalApiPayload, null, 2));
+ // DEBUG: Log the payload being sent to the backend
+ console.log('Payload sent to /upload_steps:', JSON.stringify(finalApiPayload, null, 2));
// TODO: Replace with your actual API endpoint for finalizing project steps
const backendApiUrl = `${getApiUrl()}/upload_steps`;
@@ -728,6 +402,11 @@ export const createStepHandlers = (
const responseData = await response.json();
console.log("Backend response from finalize_steps:", responseData);
+ // Clear the buy list state from localStorage since project is now finalized
+ localStorage.removeItem(`buyListState_${projectId}`);
+ // Also clear the old sessionStorage flag if it exists
+ sessionStorage.removeItem(`buyListCleared_${projectId}`);
+
setSuccessMessage("Project finalized and all data saved successfully! Redirecting...");
navigate(`/videos`);
@@ -751,12 +430,6 @@ export const createStepHandlers = (
handleSupFileChange,
removeSupFileFromCurrentStep,
handleResultImageChange,
- handleAddBuyListItem,
- removeBuyListItem,
- handleAutoPopulateBuyList,
- handleUpdateBuyListFromProject,
- handleReplaceBuyList,
- handleClearBuyList,
handleFinishProject
};
};
\ No newline at end of file
diff --git a/src/components/pages/createsteps helpers/CreateStepsHooks.js b/src/components/pages/createsteps helpers/CreateStepsHooks.js
index 17170f4..e2cae6b 100644
--- a/src/components/pages/createsteps helpers/CreateStepsHooks.js
+++ b/src/components/pages/createsteps helpers/CreateStepsHooks.js
@@ -68,12 +68,7 @@ export const useCreateStepsState = () => {
const [currentStepResultImage, setCurrentStepResultImage] = useState(null); // For displaying image URL
// Buy list state
- const [projectBuyList, setProjectBuyList] = useState([]);
- const [buyListItemName, setBuyListItemName] = useState('');
- const [buyListItemQty, setBuyListItemQty] = useState(1);
- const [buyListItemSpec, setBuyListItemSpec] = useState('');
- const [buyListItemLink, setBuyListItemLink] = useState('');
- const [buyListItemImageFile, setBuyListItemImageFile] = useState(null);
+ const [projectBuyList, setProjectBuyList] = useState([]);
// Project steps and loading state
const [projectSteps, setProjectSteps] = useState([]);
@@ -100,7 +95,6 @@ export const useCreateStepsState = () => {
const materialImageInputRef = useRef(null);
const supFileInputRef = useRef(null);
const resultImageInputRef = useRef(null);
- const buyListImageInputRef = useRef(null);
// Tab validation function
const validateCurrentTab = (tabName) => {
@@ -262,16 +256,6 @@ export const useCreateStepsState = () => {
// Buy list state
projectBuyList,
setProjectBuyList,
- buyListItemName,
- setBuyListItemName,
- buyListItemQty,
- setBuyListItemQty,
- buyListItemSpec,
- setBuyListItemSpec,
- buyListItemLink,
- setBuyListItemLink,
- buyListItemImageFile,
- setBuyListItemImageFile,
// Project steps and loading state
projectSteps,
@@ -306,8 +290,7 @@ export const useCreateStepsState = () => {
toolImageInputRef,
materialImageInputRef,
supFileInputRef,
- resultImageInputRef,
- buyListImageInputRef
+ resultImageInputRef
};
};
@@ -327,7 +310,6 @@ export const useCreateStepsEffects = (state) => {
setSuccessMessage,
setExistingThumbnailUrl,
setProjectBuyList,
- projectBuyList,
setIsLargeScreen,
videoRef,
setVideoDimensions,
@@ -335,9 +317,6 @@ export const useCreateStepsEffects = (state) => {
activeVideoUrl
} = state;
- // Add a ref to track if we've already loaded the buy list
- const buyListLoadedRef = useRef(false);
-
// Expose setActiveTab globally for sidebar navigation
useEffect(() => {
window.setActiveTab = setActiveTab;
@@ -382,82 +361,91 @@ export const useCreateStepsEffects = (state) => {
setExistingThumbnailUrl(projectData.thumbnail_url);
}
- // Now fetch the project's primary video files and buy list
- const [stepsResponse, buyListResponse] = await Promise.all([
- fetch(`${getApiUrl()}/projects/${projectId}/steps`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
- }),
- fetch(`${getApiUrl()}/projects/${projectId}/buy_list`, {
- method: 'GET',
- headers: {
- 'Content-Type': 'application/json',
- },
- })
- ]);
+ // Check if there's a saved buy list state in localStorage
+ const savedBuyListState = localStorage.getItem(`buyListState_${projectId}`);
+
+ // Now fetch the project's primary video files
+ const stepsResponse = await fetch(`${getApiUrl()}/projects/${projectId}/steps`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
let videosFromAPI = [];
if (stepsResponse.ok) {
const stepsData = await stepsResponse.json();
- // Process and load existing buy list (only if not already loaded)
- if (buyListResponse.ok && !buyListLoadedRef.current) {
- const buyListData = await buyListResponse.json();
-
- // Transform buy list items to frontend format
- const transformedBuyList = buyListData.map(item => ({
- id: `buyitem_${item.item_id}`,
- name: item.name,
- quantity: item.quantity,
- specification: item.specification || '',
- purchase_link: item.purchase_link || '',
- imageFile: null,
- hasExistingImage: !!(item.image_file && item.image_file.file_url),
- image_url: item.image_file?.file_url || null,
- image_path: item.image_file?.file_key || null,
- sourceType: 'existing',
- sourceId: item.item_id
- }));
-
- setProjectBuyList(transformedBuyList);
- buyListLoadedRef.current = true; // Mark as loaded
-
- // Show success message if items were loaded
- if (transformedBuyList.length > 0) {
- setSuccessMessage(`Loaded existing buy list with ${transformedBuyList.length} item${transformedBuyList.length !== 1 ? 's' : ''}.`);
- setTimeout(() => setSuccessMessage(''), 3000);
- }
- } else if (!buyListLoadedRef.current) {
- // Fallback: create a placeholder if we can't get video files
- console.warn('Could not fetch steps data, using placeholder');
- setUploadedVideos([]);
-
- // Still try to load buy list even if steps failed (only if not already loaded)
- if (buyListResponse.ok && !buyListLoadedRef.current) {
- const buyListData = await buyListResponse.json();
+ // Check if this is an existing project (has steps) or a new project
+ const isExistingProject = stepsData && stepsData.length > 0;
+
+ if (savedBuyListState) {
+ // If there's a saved state, use it (handles both cleared and modified states)
+ try {
+ const savedBuyList = JSON.parse(savedBuyListState);
+ setProjectBuyList(savedBuyList);
- const transformedBuyList = buyListData.map(item => ({
- id: `buyitem_${item.item_id}`,
- name: item.name,
- quantity: item.quantity,
- specification: item.specification || '',
- purchase_link: item.purchase_link || '',
- imageFile: null,
- hasExistingImage: !!(item.image_file && item.image_file.file_url),
- image_url: item.image_file?.file_url || null,
- image_path: item.image_file?.file_key || null,
- sourceType: 'existing',
- sourceId: item.item_id
- }));
+ if (savedBuyList.length > 0) {
+ setSuccessMessage(`Restored buy list with ${savedBuyList.length} item${savedBuyList.length !== 1 ? 's' : ''}.`);
+ setTimeout(() => setSuccessMessage(''), 3000);
+ }
+ } catch (error) {
+ console.error('Error parsing saved buy list state:', error);
+ setProjectBuyList([]);
+ }
+ } else if (isExistingProject) {
+ // For existing projects without saved state, load from database
+ try {
+ const buyListResponse = await fetch(`${getApiUrl()}/projects/${projectId}/buy_list`, {
+ method: 'GET',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ });
- setProjectBuyList(transformedBuyList);
- buyListLoadedRef.current = true; // Mark as loaded
- } else if (!buyListLoadedRef.current) {
- buyListLoadedRef.current = true; // Mark as loaded even if empty
+ if (buyListResponse.ok) {
+ const buyListData = await buyListResponse.json();
+
+ // Transform buy list items to frontend format
+ const transformedBuyList = buyListData.map(item => ({
+ id: `buyitem_${item.item_id}`,
+ name: item.name,
+ quantity: item.quantity,
+ specification: item.specification || '',
+ purchase_link: item.purchase_link || '',
+ imageFile: null,
+ hasExistingImage: !!(item.image_file && item.image_file.file_url),
+ image_url: item.image_file?.file_url || null,
+ image_path: item.image_file?.file_key || null,
+ sourceType: 'existing',
+ sourceId: item.item_id
+ }));
+
+ setProjectBuyList(transformedBuyList);
+
+ // Save initial state to localStorage
+ localStorage.setItem(`buyListState_${projectId}`, JSON.stringify(transformedBuyList));
+
+ // Show success message if items were loaded
+ if (transformedBuyList.length > 0) {
+ setSuccessMessage(`Loaded existing buy list with ${transformedBuyList.length} item${transformedBuyList.length !== 1 ? 's' : ''}.`);
+ setTimeout(() => setSuccessMessage(''), 3000);
+ }
+ } else {
+ // If buy list fetch fails, start with empty
+ setProjectBuyList([]);
+ localStorage.setItem(`buyListState_${projectId}`, JSON.stringify([]));
+ }
+ } catch (error) {
+ console.error('Error loading buy list:', error);
+ setProjectBuyList([]);
+ localStorage.setItem(`buyListState_${projectId}`, JSON.stringify([]));
}
+ } else {
+ // For new projects, start with empty buy list
+ setProjectBuyList([]);
+ localStorage.setItem(`buyListState_${projectId}`, JSON.stringify([]));
}
// Extract unique video files from steps
@@ -542,29 +530,8 @@ export const useCreateStepsEffects = (state) => {
console.warn('Could not fetch steps data, using placeholder');
setUploadedVideos([]);
- // Still try to load buy list even if steps failed (only if not already loaded)
- if (buyListResponse.ok && !buyListLoadedRef.current) {
- const buyListData = await buyListResponse.json();
-
- const transformedBuyList = buyListData.map(item => ({
- id: `buyitem_${item.item_id}`,
- name: item.name,
- quantity: item.quantity,
- specification: item.specification || '',
- purchase_link: item.purchase_link || '',
- imageFile: null,
- hasExistingImage: !!(item.image_file && item.image_file.file_url),
- image_url: item.image_file?.file_url || null,
- image_path: item.image_file?.file_key || null,
- sourceType: 'existing',
- sourceId: item.item_id
- }));
-
- setProjectBuyList(transformedBuyList);
- buyListLoadedRef.current = true; // Mark as loaded
- } else if (!buyListLoadedRef.current) {
- buyListLoadedRef.current = true; // Mark as loaded even if empty
- }
+ // Initialize buy list as empty
+ setProjectBuyList([]);
}
if (videosFromAPI.length > 0 && videosFromAPI[0].url) {
@@ -578,10 +545,8 @@ export const useCreateStepsEffects = (state) => {
console.error('Error fetching project data:', error);
setErrorMessage("Failed to load project data. Please try again.");
- // If project data fetch fails completely, still try to initialize empty buy list
- if (projectBuyList.length === 0) {
- setProjectBuyList([]);
- }
+ // Initialize buy list as empty even if project data fetch fails
+ setProjectBuyList([]);
}
};
@@ -603,7 +568,7 @@ export const useCreateStepsEffects = (state) => {
} else {
setErrorMessage("Project ID not found. Please start from project creation.");
}
- }, [projectId, location.state, currentUser, navigate, setProjectName, setUploadedVideos, setActiveVideoUrl, setActiveVideoIndex, setErrorMessage, setProjectSteps, setCapturedAnnotationFrames, setSuccessMessage, setExistingThumbnailUrl, setProjectBuyList, projectBuyList.length]);
+ }, [projectId, location.state, currentUser, navigate, setProjectName, setUploadedVideos, setActiveVideoUrl, setActiveVideoIndex, setErrorMessage, setProjectSteps, setCapturedAnnotationFrames, setSuccessMessage, setExistingThumbnailUrl, setProjectBuyList]);
// Handle video metadata loading
useEffect(() => {
diff --git a/src/components/pages/createsteps helpers/CreateStepsStyles.js b/src/components/pages/createsteps helpers/CreateStepsStyles.js
index 76cf5c3..810fc5d 100644
--- a/src/components/pages/createsteps helpers/CreateStepsStyles.js
+++ b/src/components/pages/createsteps helpers/CreateStepsStyles.js
@@ -1,4 +1,10 @@
// CreateStepsStyles.js - All styles for the CreateSteps component
+
+// Chrome detection function
+const isChrome = () => {
+ return navigator.userAgent.includes('Chrome') && !navigator.userAgent.includes('Edg') && !navigator.userAgent.includes('Safari');
+};
+
export const styles = {
pageContainer: {
display: 'flex',
@@ -6,6 +12,10 @@ export const styles = {
fontFamily: "'Inter', sans-serif",
color: '#D9D9D9',
backgroundColor: '#000000',
+ ...(isChrome() && {
+ width: '100vw',
+ overflow: 'hidden',
+ }),
},
videoTimelineContainer: {
display: 'flex',
@@ -14,11 +24,20 @@ export const styles = {
fontFamily: "'Inter', sans-serif",
color: '#D9D9D9',
backgroundColor: '#000000',
+ ...(isChrome() && {
+ width: '100vw',
+ height: '100vh',
+ overflow: 'hidden',
+ }),
},
videoTimelineLayout: {
display: 'flex',
flex: 1,
overflow: 'hidden',
+ ...(isChrome() && {
+ width: '100%',
+ minWidth: '0',
+ }),
},
leftPanel: {
width: '40%',
@@ -26,18 +45,33 @@ export const styles = {
flexDirection: 'column',
backgroundColor: '#000000',
borderRight: '1px solid #D9D9D9',
+ ...(isChrome() && {
+ width: '25%',
+ minWidth: '260px',
+ maxWidth: '300px',
+ flexShrink: 0,
+ }),
},
rightPanel: {
width: '60%',
display: 'flex',
flexDirection: 'column',
backgroundColor: '#000000',
+ ...(isChrome() && {
+ width: '75%',
+ minWidth: '0',
+ flex: 1,
+ }),
},
mainContent: {
flex: 1,
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
+ ...(isChrome() && {
+ width: '100%',
+ minWidth: '0',
+ }),
},
header: {
display: 'flex',
@@ -47,11 +81,20 @@ export const styles = {
backgroundColor: '#000000',
borderBottom: '1px solid #D9D9D9',
boxShadow: '0 1px 3px rgba(0,0,0,0.3)',
+ ...(isChrome() && {
+ padding: '12px 16px',
+ width: '100%',
+ minWidth: '0',
+ boxSizing: 'border-box',
+ }),
},
pageTitle: {
fontSize: '1.8rem',
fontWeight: 'bold',
color: '#F1C232', // Gold for main title
+ ...(isChrome() && {
+ fontSize: '1.6rem',
+ }),
},
projectNameHighlight: {
color: '#F1C232',
@@ -144,6 +187,13 @@ export const styles = {
boxShadow: '0 4px 12px rgba(0,0,0,0.15)',
backgroundColor: '#000000',
marginBottom: '20px',
+ ...(isChrome() && {
+ padding: '8px',
+ marginBottom: '8px',
+ width: '100%',
+ minWidth: '0',
+ boxSizing: 'border-box',
+ }),
},
sectionTitle: {
fontSize: '1.4rem',
@@ -151,7 +201,13 @@ export const styles = {
color: '#F1C232', // Gold for section titles
marginBottom: '16px',
paddingBottom: '8px',
- borderBottom: '1px solid #D9D9D9'
+ borderBottom: '1px solid #D9D9D9',
+ ...(isChrome() && {
+ fontSize: '1.2rem',
+ marginBottom: '12px',
+ width: '100%',
+ minWidth: '0',
+ }),
},
inputLabel: {
display: 'block',
@@ -159,6 +215,10 @@ export const styles = {
fontWeight: '500',
color: '#D9D9D9',
marginBottom: '6px',
+ ...(isChrome() && {
+ fontSize: '0.8rem',
+ marginBottom: '4px',
+ }),
},
inputField: {
width: '100%',
@@ -171,6 +231,13 @@ export const styles = {
boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.15)',
transition: 'border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
boxSizing: 'border-box',
+ ...(isChrome() && {
+ padding: '6px 8px',
+ fontSize: '0.8rem',
+ width: '100%',
+ minWidth: '0',
+ boxSizing: 'border-box',
+ }),
},
textareaField: {
width: '100%',
@@ -184,6 +251,14 @@ export const styles = {
transition: 'border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out',
minHeight: '80px',
boxSizing: 'border-box',
+ ...(isChrome() && {
+ padding: '6px 8px',
+ fontSize: '0.8rem',
+ minHeight: '60px',
+ width: '100%',
+ minWidth: '0',
+ boxSizing: 'border-box',
+ }),
},
button: {
padding: '10px 18px',
@@ -194,6 +269,10 @@ export const styles = {
cursor: 'pointer',
transition: 'background-color 0.2s ease-in-out, transform 0.1s ease',
boxShadow: '0 2px 4px rgba(0,0,0,0.15)',
+ ...(isChrome() && {
+ width: 'auto',
+ minWidth: '0',
+ }),
},
buttonPrimary: {
backgroundColor: '#F1C232', // Gold for primary actions
@@ -236,6 +315,12 @@ export const styles = {
flex: 1,
padding: '20px',
overflowY: 'auto',
+ ...(isChrome() && {
+ padding: '8px',
+ width: '100%',
+ minWidth: '0',
+ boxSizing: 'border-box',
+ }),
},
finalizeContainer: {
flex: 1,
@@ -243,6 +328,12 @@ export const styles = {
overflowY: 'auto',
backgroundColor: '#000000',
minHeight: '100vh',
+ ...(isChrome() && {
+ padding: '16px',
+ width: '100%',
+ minWidth: '0',
+ boxSizing: 'border-box',
+ }),
},
tabNavigation: {
display: 'flex',
@@ -251,6 +342,12 @@ export const styles = {
borderTopLeftRadius: '8px',
borderTopRightRadius: '8px',
padding: '0 20px',
+ ...(isChrome() && {
+ padding: '0 8px',
+ width: '100%',
+ minWidth: '0',
+ boxSizing: 'border-box',
+ }),
},
tabButton: {
padding: '12px 16px',
@@ -265,6 +362,12 @@ export const styles = {
borderBottomStyle: 'solid',
borderBottomColor: 'transparent',
position: 'relative',
+ ...(isChrome() && {
+ padding: '8px 10px',
+ fontSize: '0.8rem',
+ flex: 1,
+ minWidth: '0',
+ }),
},
tabButtonActive: {
color: '#000000',
@@ -276,15 +379,28 @@ export const styles = {
videoSection: {
padding: '20px',
borderBottom: '1px solid #D9D9D9',
+ ...(isChrome() && {
+ padding: '12px',
+ width: '100%',
+ minWidth: '0',
+ boxSizing: 'border-box',
+ }),
},
videoContainer: {
marginBottom: '16px',
+ ...(isChrome() && {
+ marginBottom: '12px',
+ }),
},
videoSelection: {
display: 'flex',
gap: '8px',
marginBottom: '16px',
flexWrap: 'wrap',
+ ...(isChrome() && {
+ gap: '6px',
+ marginBottom: '12px',
+ }),
},
videoSelectButton: {
padding: '6px 12px',
@@ -307,6 +423,10 @@ export const styles = {
gap: '8px',
marginTop: '12px',
flexWrap: 'wrap',
+ ...(isChrome() && {
+ width: '100%',
+ minWidth: '0',
+ }),
},
noVideoMessage: {
padding: '40px',
@@ -319,17 +439,31 @@ export const styles = {
flex: 1,
padding: '20px',
overflowY: 'auto',
+ ...(isChrome() && {
+ padding: '12px',
+ width: '100%',
+ minWidth: '0',
+ boxSizing: 'border-box',
+ }),
},
stepsSectionHeader: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: '16px',
+ ...(isChrome() && {
+ marginBottom: '12px',
+ gap: '8px',
+ }),
},
stepsSectionTitle: {
fontSize: '1.1rem',
fontWeight: '600',
color: '#F1C232',
+ ...(isChrome() && {
+ width: '100%',
+ minWidth: '0',
+ }),
},
stepsList: {
display: 'flex',
@@ -343,6 +477,10 @@ export const styles = {
backgroundColor: '#000000',
cursor: 'pointer',
transition: 'all 0.2s',
+ ...(isChrome() && {
+ width: '100%',
+ minWidth: '0',
+ }),
},
stepItemActive: {
border: '1px solid #0000FF',