-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve user navigation in the list and between lists #39
Changes from all commits
94dd004
a42213f
a44dd2b
275ca7c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import { useState } from 'react'; | ||
import { addItem } from '../api'; | ||
|
||
export function AddItem({ data, listPath }) { | ||
const [formNewItem, setFormNewItem] = useState({ | ||
name: '', | ||
nextPurchase: 0, | ||
}); | ||
const [messageItem, setMessageItem] = useState(''); | ||
|
||
const handleNewItemChange = (e) => { | ||
const { name, value } = e.target; | ||
setFormNewItem((prevForm) => { | ||
return { | ||
...prevForm, | ||
[name]: value, | ||
}; | ||
}); | ||
}; | ||
|
||
const handleNewItemSubmit = async (e) => { | ||
e.preventDefault(); | ||
const { name, nextPurchase } = formNewItem; | ||
|
||
if (!name || !nextPurchase) { | ||
setMessageItem('Please fill out all fields'); | ||
return; | ||
} | ||
try { | ||
const normalizedName = (name) => { | ||
return name | ||
.toLowerCase() | ||
.replace(/[^\w\s]|_/g, '') | ||
.replace(/\s+/g, ''); | ||
}; | ||
|
||
const itemExists = data.some( | ||
(item) => normalizedName(item.name) === normalizedName(name), | ||
); | ||
|
||
if (itemExists) { | ||
alert(`${normalizedName(name)} is already in the list`); | ||
return; | ||
} | ||
|
||
await addItem(listPath, { | ||
itemName: name, | ||
daysUntilNextPurchase: nextPurchase, | ||
}); | ||
alert(`${name} has been successfully added to the list`); | ||
setFormNewItem({ | ||
name: '', | ||
nextPurchase: 0, | ||
}); | ||
} catch (error) { | ||
console.log('Failed to add the item: ', error); | ||
alert(`Failed to add ${name} to the list. Please try again!`); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
<form onSubmit={handleNewItemSubmit}> | ||
<label htmlFor="name">Item name</label> | ||
<input | ||
id="name" | ||
type="text" | ||
placeholder="Item" | ||
value={formNewItem.name} | ||
onChange={handleNewItemChange} | ||
name="name" | ||
required | ||
/> | ||
|
||
<label htmlFor="nextPurchase">When is your next purchase</label> | ||
<select | ||
name="nextPurchase" | ||
id="nextPurchase" | ||
onChange={handleNewItemChange} | ||
value={formNewItem.nextPurchase} | ||
required | ||
> | ||
<option value="">---</option> | ||
<option value={7}>Soon</option> | ||
<option value={14}>Kind of soon</option> | ||
<option value={30}>Not soon</option> | ||
</select> | ||
|
||
<button>Add Item</button> | ||
|
||
<p>{messageItem}</p> | ||
</form> | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { useState } from 'react'; | ||
import { useNavigate } from 'react-router-dom'; | ||
import { createList } from '../api/firebase'; | ||
|
||
export function AddList({ setListPath, userId, userEmail }) { | ||
const [listName, setListName] = useState(''); | ||
|
||
const navigate = useNavigate(); | ||
|
||
const handleCreateListButton = async (e) => { | ||
e.preventDefault(); | ||
|
||
try { | ||
await createList(userId, userEmail, listName); | ||
alert(`${listName} list was successfully created.`); | ||
|
||
const createListPath = `${userId}/${listName}}`; | ||
setListPath(createListPath); | ||
navigate('/list'); | ||
} catch (error) { | ||
console.error('error creating a list', error); | ||
alert('Failed to create the list. Please try again!'); | ||
} | ||
}; | ||
|
||
return ( | ||
<> | ||
<form onSubmit={handleCreateListButton}> | ||
<label htmlFor="listName">List Name:</label> | ||
<input | ||
type="text" | ||
id="listName" | ||
value={listName} | ||
onChange={(e) => setListName(e.target.value)} | ||
placeholder="Enter the name of your new list" | ||
required | ||
/> | ||
<button type="submit" className="button"> | ||
Create list | ||
</button> | ||
</form> | ||
</> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,19 +1,19 @@ | ||
import { ListItem } from '../components'; | ||
|
||
import { useEffect, useState } from 'react'; | ||
import { Link } from 'react-router-dom'; | ||
import { ListItem } from '../components'; | ||
import { AddItem } from '../components/AddItem'; | ||
import { | ||
comparePurchaseUrgency, | ||
updateItem, | ||
deleteItem, | ||
} from '../api/firebase'; | ||
|
||
import { Link } from 'react-router-dom'; | ||
|
||
export function List({ data, listPath, lists }) { | ||
const [searchItem, setSearchItem] = useState(''); | ||
const [errorMsg, setErrorMsg] = useState(''); | ||
|
||
const [items, setItems] = useState([]); | ||
|
||
const listTitle = listPath.split('/')[1]; | ||
|
||
useEffect(() => { | ||
const fetchItems = async () => { | ||
const sortedItems = await comparePurchaseUrgency(data); | ||
|
@@ -55,18 +55,15 @@ export function List({ data, listPath, lists }) { | |
const handleDelete = async (itemId) => { | ||
try { | ||
await deleteItem(listPath, itemId); | ||
setErrorMsg(''); | ||
} catch (error) { | ||
console.error(error.message, error); | ||
setErrorMsg('Failed to delete the item. Please try again!'); | ||
alert('Failed to delete the item. Please try again!'); | ||
} | ||
}; | ||
|
||
return ( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A further improvement for this view could be displaying the list name so users can always know which list they are interacting with. |
||
<> | ||
<p> | ||
Hello from the <code>/list</code> page! | ||
</p> | ||
<h2>{listTitle}</h2> | ||
{lists.length === 0 && ( | ||
<p> | ||
It looks like you don't have any shopping lists yet. Head to the{' '} | ||
|
@@ -75,14 +72,15 @@ export function List({ data, listPath, lists }) { | |
</p> | ||
)} | ||
{lists.length > 0 && data.length === 0 && ( | ||
<p> | ||
Your list is currently empty. To add items, visit{' '} | ||
<Link to="/manage-list">manage list</Link> and start building your | ||
shopping list! | ||
</p> | ||
<> | ||
<AddItem data={data} listPath={listPath} /> | ||
<p>Your list is currently empty.</p> | ||
</> | ||
)} | ||
{lists.length > 0 && data.length > 0 && ( | ||
<> | ||
<AddItem data={data} listPath={listPath} /> | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think having the form here makes perfect sense in terms of usability and navigation within the app. However, I noticed that when I’m on a list with no items, I can’t see the form for adding items in either the ManageList or List views: Screen.Recording.2024-09-26.at.17.33.14.mov |
||
<form onSubmit={handleSearch}> | ||
<div> | ||
<label htmlFor="search-item-in-list"> Search items:</label> | ||
|
@@ -101,6 +99,7 @@ export function List({ data, listPath, lists }) { | |
)} | ||
</div> | ||
</form> | ||
|
||
{searchItem ? ( | ||
<ul> | ||
{filterItems.map((item) => ( | ||
|
@@ -136,8 +135,6 @@ export function List({ data, listPath, lists }) { | |
))} | ||
</ul> | ||
)} | ||
|
||
{errorMsg && <p>{errorMsg}</p>} | ||
</> | ||
)} | ||
</> | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once this PR is merged, you may want to integrate the changes introduced, so item names are capitalized before being saved in Firestore.