diff --git a/.gitignore b/.gitignore
index 502ba03..b11967b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -23,4 +23,6 @@ dist-ssr
*.sln
*.sw?
-/backend/.env
\ No newline at end of file
+/backend/.env
+.vercel
+.env*.local
diff --git a/src/components/PeopleManager/AddPersonModal.tsx b/src/components/PeopleManager/AddPersonModal.tsx
new file mode 100644
index 0000000..31df103
--- /dev/null
+++ b/src/components/PeopleManager/AddPersonModal.tsx
@@ -0,0 +1,67 @@
+import Modal from "react-bootstrap/Modal";
+import { useState } from "react";
+import { DEFAULT_COLORS } from "./usePeople";
+
+interface Props {
+ show: boolean;
+ onClose: () => void;
+ onSubmit: (name: string, color: string) => void;
+}
+
+export default function AddPersonModal({ show, onClose, onSubmit }: Props) {
+ const [name, setName] = useState("");
+ const [color, setColor] = useState(DEFAULT_COLORS[0]);
+
+ const handleAdd = () => {
+ if (name.trim()) {
+ onSubmit(name.trim(), color);
+ setName("");
+ setColor(DEFAULT_COLORS[0]);
+ onClose();
+ }
+ };
+
+ return (
+
+
+ Add person
+
+
+
+
+ setName(e.target.value)}
+ placeholder="Enter name"
+ style={{ width: "100%", marginBottom: 16 }}
+ />
+
+
+
+ {DEFAULT_COLORS.map((c) => (
+
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/PeopleManager/PeopleChips.tsx b/src/components/PeopleManager/PeopleChips.tsx
new file mode 100644
index 0000000..d3b28b7
--- /dev/null
+++ b/src/components/PeopleManager/PeopleChips.tsx
@@ -0,0 +1,34 @@
+import styles from "./styles.module.css";
+import type { Person } from "./usePeople";
+
+interface Props {
+ people: Person[];
+ onAddClick: () => void;
+ onRemovePerson: (id: string) => void;
+}
+
+export default function PeopleChips({ people, onAddClick, onRemovePerson }: Props) {
+ return (
+
+ {people.map((p) => (
+
+ ))}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/PeopleManager/index.ts b/src/components/PeopleManager/index.ts
new file mode 100644
index 0000000..123cb9c
--- /dev/null
+++ b/src/components/PeopleManager/index.ts
@@ -0,0 +1,3 @@
+export { usePeople } from "./usePeople";
+export { default as PeopleChips } from "./PeopleChips";
+export { default as AddPersonModal } from "./AddPersonModal";
\ No newline at end of file
diff --git a/src/components/PeopleManager/styles.module.css b/src/components/PeopleManager/styles.module.css
new file mode 100644
index 0000000..0d2c908
--- /dev/null
+++ b/src/components/PeopleManager/styles.module.css
@@ -0,0 +1,71 @@
+/* Container for chips */
+.chipsRow {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 10px;
+ margin-bottom: 1.5rem;
+}
+
+/* Person chip */
+.personChip {
+ position: relative;
+ padding: 6px 14px;
+ border-radius: 20px;
+ border: none;
+ font-size: 0.9rem;
+ font-weight: 600;
+ cursor: pointer;
+ color: #fff;
+ text-transform: capitalize;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.personChip:hover {
+ opacity: 0.9;
+}
+
+.personChip:hover span {
+ visibility: hidden;
+}
+
+.personChip::after {
+ position: absolute;
+ inset: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ pointer-events: none;
+ opacity: 0;
+}
+
+.personChip:hover::after {
+ content: "✕";
+ opacity: 1;
+}
+
+
+/* Add person button */
+.addChip {
+ padding: 8px 14px;
+ border-radius: 20px;
+ font-size: 0.9rem;
+ font-weight: 600;
+ cursor: pointer;
+ background-color: transparent;
+ color: var(--primary-pink);
+ border: 2px solid var(--primary-pink);
+ transition: background-color 0.2s ease, color 0.2s ease;
+}
+
+.addChip:hover {
+ background-color: var(--primary-pink);
+ color: #fff;
+}
+
+.personChip:focus,
+.addChip:focus {
+ outline: 3px solid rgba(198, 0, 90, 0.3);
+ outline-offset: 2px;
+}
\ No newline at end of file
diff --git a/src/components/PeopleManager/usePeople.ts b/src/components/PeopleManager/usePeople.ts
new file mode 100644
index 0000000..420b9b8
--- /dev/null
+++ b/src/components/PeopleManager/usePeople.ts
@@ -0,0 +1,56 @@
+import { useState } from "react";
+
+export type Person = {
+ id: string;
+ name: string;
+ color: string;
+ total: number;
+};
+
+export const DEFAULT_COLORS = [
+ "#C6005A",
+ "#1F3B57",
+ "#2EC4B6",
+ "#FF9F1C",
+ "#8AC926",
+ "#FF595E",
+ "#6A4C93",
+ "#6F1D1B",
+ "#D4A373",
+ "#6B7280"
+];
+
+export function usePeople(initial?: Person[]) {
+ const [people, setPeople] = useState(initial || []);
+
+ const addPerson = (name: string, color: string) => {
+ const id =
+ typeof crypto !== "undefined" && "randomUUID" in crypto
+ ? crypto.randomUUID()
+ : Date.now().toString();
+
+ setPeople(prev => [...prev, { id, name, color, total: 0 }]);
+ };
+
+ const removePerson = (id: string) => {
+ setPeople(prev => prev.filter(p => p.id !== id));
+ };
+
+ const updateTotal = (id: string, amount: number) => {
+ setPeople(prev =>
+ prev.map(p =>
+ p.id === id ? { ...p, total: amount } : p
+ )
+ );
+ };
+
+ const grandTotal = people.reduce((sum, p) => sum + p.total, 0);
+
+ return {
+ people,
+ addPerson,
+ removePerson,
+ updateTotal,
+ grandTotal
+ };
+}
\ No newline at end of file
diff --git a/src/pages/Review.tsx b/src/pages/Review.tsx
index a3695d9..74d8de9 100644
--- a/src/pages/Review.tsx
+++ b/src/pages/Review.tsx
@@ -1,9 +1,10 @@
-import { useLocation } from "react-router-dom";
+import { useLocation, useNavigate } from "react-router-dom";
import { useState, useEffect } from "react";
import { parseItems, type Item } from "../utils/parseReceipt";
const Review = () => {
const ocrText = useLocation().state?.ocrText || "";
+ const navigate = useNavigate();
const [items, setItems] = useState- ([]);
useEffect(() => {
@@ -24,6 +25,12 @@ const Review = () => {
});
};
+ const handleContinue = () => {
+ // Only continue if there’s at least one item
+ if (!items.length) return;
+ navigate("/Split", { state: { items } });
+ };
+
return (
Review and Edit Items
@@ -52,6 +59,13 @@ const Review = () => {
/>
))}
+
);
};
diff --git a/src/pages/Split.module.css b/src/pages/Split.module.css
new file mode 100644
index 0000000..cddbcc4
--- /dev/null
+++ b/src/pages/Split.module.css
@@ -0,0 +1,58 @@
+.desktopPage {
+ display: flex;
+ flex-direction: row;
+ gap: 2rem;
+ align-items: center;
+ height: 100%;
+ width: 100%;
+ max-width: 1200px;
+ margin: 0 auto;
+}
+
+.mobilePage {
+ display: flex;
+ flex-direction: column;
+ gap: 2rem;
+}
+
+.itemsContainer {
+ width: 60%;
+}
+
+.itemsContainer button {
+ font-weight: 600;
+ font-size: 1rem;
+ padding: 0.5rem 1rem;
+ transition: background-color 0.2s ease;
+}
+
+.resultsContainer {
+ width: 40%;
+ background-color: var(--primary-pink);
+ min-width: 300px;
+ border-radius: 1rem;
+ color: white;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ gap: 1.5rem;
+ padding: 5%;
+
+ @media (min-width: 768px) {
+ min-height: 30px;
+ }
+}
+
+.resultsHeader {
+ font-size: 1.8rem;
+ font-weight: 700;
+ margin-bottom: 0.5rem;
+ border-bottom: 2px solid rgba(255, 255, 255, 0.2);
+ padding-bottom: 0.5rem;
+}
+
+.total {
+ margin-top: 0.5rem;
+ border-top: 2px solid rgba(255, 255, 255, 0.2);
+ padding-top: 0.5rem;
+}
\ No newline at end of file
diff --git a/src/pages/Split.tsx b/src/pages/Split.tsx
index ef880a2..4c66b1d 100644
--- a/src/pages/Split.tsx
+++ b/src/pages/Split.tsx
@@ -1,7 +1,58 @@
+import { useState } from "react";
+import { useLocation } from "react-router-dom";
+import type { Item } from "../utils/parseReceipt";
+import useIsMobile from "../hooks/useIsMobile";
+import { usePeople, PeopleChips, AddPersonModal } from "../components/PeopleManager";
+
+import styles from './Split.module.css'
+
const Split = () => {
- const text = "hi"
+ const isMobile = useIsMobile();
+ const { state } = useLocation() as { state?: { items?: Item[] } };
+ const { people, addPerson, removePerson, grandTotal } = usePeople([]);
+ const [showModal, setShowModal] = useState(false);
+ const items = state?.items || [];
- return text;
+ return (
+ <>
+
+
+
setShowModal(true)}
+ onRemovePerson={removePerson}
+ />
+
+ {items.map((item, i) => (
+
+ | {item.quantity} |
+ {item.name} |
+ ${item.price.toFixed(2)} |
+ |
+
+ ))}
+
+ setShowModal(false)}
+ onSubmit={(name, color) => addPerson(name, color)}
+ />
+
+
+
Results
+
+ {people.map((p) => (
+
+ | {p.name}: |
+ ${p.total.toFixed(2)} |
+
+ ))}
+
+
Total: ${grandTotal.toFixed(2)}
+
+
+ >
+ );
}
export default Split;
\ No newline at end of file