Home
+
Manage
Settings
{role === "admin" &&
Admin}
diff --git a/frontend/src/components/manage/CreateJoinHousehold.jsx b/frontend/src/components/manage/CreateJoinHousehold.jsx
new file mode 100644
index 0000000..fdfc42f
--- /dev/null
+++ b/frontend/src/components/manage/CreateJoinHousehold.jsx
@@ -0,0 +1,130 @@
+import { useContext, useState } from "react";
+import { createHousehold, joinHousehold } from "../../api/households";
+import { HouseholdContext } from "../../context/HouseholdContext";
+import "../../styles/components/manage/CreateJoinHousehold.css";
+
+export default function CreateJoinHousehold({ onClose }) {
+ const { loadHouseholds } = useContext(HouseholdContext);
+ const [mode, setMode] = useState("create"); // "create" or "join"
+ const [householdName, setHouseholdName] = useState("");
+ const [inviteCode, setInviteCode] = useState("");
+ const [loading, setLoading] = useState(false);
+ const [error, setError] = useState("");
+
+ const handleCreate = async (e) => {
+ e.preventDefault();
+ if (!householdName.trim()) return;
+
+ setLoading(true);
+ setError("");
+
+ try {
+ await createHousehold(householdName);
+ await loadHouseholds();
+ onClose();
+ } catch (err) {
+ console.error("Failed to create household:", err);
+ setError(err.response?.data?.message || "Failed to create household");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleJoin = async (e) => {
+ e.preventDefault();
+ if (!inviteCode.trim()) return;
+
+ setLoading(true);
+ setError("");
+
+ try {
+ await joinHousehold(inviteCode);
+ await loadHouseholds();
+ onClose();
+ } catch (err) {
+ console.error("Failed to join household:", err);
+ setError(err.response?.data?.message || "Failed to join household");
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
e.stopPropagation()}>
+
+
Household
+
+
+
+
+
+
+
+
+ {error &&
{error}
}
+
+ {mode === "create" ? (
+
+ ) : (
+
+ )}
+
+
+ );
+}
diff --git a/frontend/src/components/manage/ManageHousehold.jsx b/frontend/src/components/manage/ManageHousehold.jsx
new file mode 100644
index 0000000..3a63f7b
--- /dev/null
+++ b/frontend/src/components/manage/ManageHousehold.jsx
@@ -0,0 +1,229 @@
+import { useContext, useEffect, useState } from "react";
+import {
+ deleteHousehold,
+ getHouseholdMembers,
+ refreshInviteCode,
+ removeMember,
+ updateHousehold,
+ updateMemberRole
+} from "../../api/households";
+import { AuthContext } from "../../context/AuthContext";
+import { HouseholdContext } from "../../context/HouseholdContext";
+import "../../styles/components/manage/ManageHousehold.css";
+
+export default function ManageHousehold() {
+ const { userId } = useContext(AuthContext);
+ const { activeHousehold, refreshHouseholds } = useContext(HouseholdContext);
+ const [members, setMembers] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [editingName, setEditingName] = useState(false);
+ const [newName, setNewName] = useState("");
+ const [showInviteCode, setShowInviteCode] = useState(false);
+
+ const isAdmin = activeHousehold?.role === "admin";
+
+ useEffect(() => {
+ loadMembers();
+ }, [activeHousehold?.id]);
+
+ const loadMembers = async () => {
+ if (!activeHousehold?.id) return;
+ setLoading(true);
+ try {
+ const response = await getHouseholdMembers(activeHousehold.id);
+ setMembers(response.data);
+ } catch (error) {
+ console.error("Failed to load members:", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleUpdateName = async () => {
+ if (!newName.trim() || newName === activeHousehold.name) {
+ setEditingName(false);
+ return;
+ }
+
+ try {
+ console.log('Updating household:', activeHousehold.id, 'with name:', newName);
+ const response = await updateHousehold(activeHousehold.id, newName);
+ console.log('Update response:', response);
+ await refreshHouseholds();
+ setEditingName(false);
+ } catch (error) {
+ console.error("Failed to update household name:", error);
+ console.error("Error response:", error.response?.data);
+ alert(`Failed to update household name: ${error.response?.data?.error || error.message}`);
+ }
+ };
+
+ const handleRefreshInvite = async () => {
+ if (!confirm("Generate a new invite code? The old code will no longer work.")) return;
+
+ try {
+ const response = await refreshInviteCode(activeHousehold.id);
+ await refreshHouseholds();
+ alert(`New invite code: ${response.data.inviteCode}`);
+ } catch (error) {
+ console.error("Failed to refresh invite code:", error);
+ alert("Failed to refresh invite code");
+ }
+ };
+
+ const handleUpdateRole = async (userId, currentRole) => {
+ const newRole = currentRole === "admin" ? "member" : "admin";
+
+ try {
+ await updateMemberRole(activeHousehold.id, userId, newRole);
+ await loadMembers();
+ } catch (error) {
+ console.error("Failed to update role:", error);
+ alert("Failed to update member role");
+ }
+ };
+
+ const handleRemoveMember = async (userId, username) => {
+ if (!confirm(`Remove ${username} from this household?`)) return;
+
+ try {
+ await removeMember(activeHousehold.id, userId);
+ await loadMembers();
+ } catch (error) {
+ console.error("Failed to remove member:", error);
+ alert("Failed to remove member");
+ }
+ };
+
+ const handleDeleteHousehold = async () => {
+ if (!confirm(`Delete "${activeHousehold.name}"? This will delete all lists and data. This cannot be undone.`)) return;
+ if (!confirm("Are you absolutely sure? Type DELETE to confirm.")) return;
+
+ try {
+ await deleteHousehold(activeHousehold.id);
+ await refreshHouseholds();
+ } catch (error) {
+ console.error("Failed to delete household:", error);
+ alert("Failed to delete household");
+ }
+ };
+
+ const copyInviteCode = () => {
+ navigator.clipboard.writeText(activeHousehold.invite_code);
+ alert("Invite code copied to clipboard!");
+ };
+
+ return (
+
+ {/* Household Name Section */}
+
+
+ {/* Invite Code Section */}
+ {isAdmin && (
+
+ Invite Code
+
+ Share this code with others to invite them to your household.
+
+
+
+ {showInviteCode && (
+ <>
+ {activeHousehold.invite_code}
+
+ >
+ )}
+
+
+
+ )}
+
+ {/* Members Section */}
+
+ Members ({members.length})
+ {loading ? (
+ Loading members...
+ ) : (
+
+ {members.map((member) => (
+
+
+
+ {member.username}
+ {member.user_id === parseInt(userId) && " (You)"}
+
+
+ {member.role}
+
+
+ {isAdmin && member.user_id !== parseInt(userId) && (
+
+
+
+
+ )}
+
+ ))}
+
+ )}
+
+
+ {/* Danger Zone */}
+ {isAdmin && (
+
+ Danger Zone
+
+ Deleting a household is permanent and will delete all lists, items, and history.
+
+
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/manage/ManageStores.jsx b/frontend/src/components/manage/ManageStores.jsx
new file mode 100644
index 0000000..9d2b38b
--- /dev/null
+++ b/frontend/src/components/manage/ManageStores.jsx
@@ -0,0 +1,157 @@
+import { useContext, useEffect, useState } from "react";
+import {
+ addStoreToHousehold,
+ getAllStores,
+ removeStoreFromHousehold,
+ setDefaultStore
+} from "../../api/stores";
+import { HouseholdContext } from "../../context/HouseholdContext";
+import { StoreContext } from "../../context/StoreContext";
+import "../../styles/components/manage/ManageStores.css";
+
+export default function ManageStores() {
+ const { activeHousehold } = useContext(HouseholdContext);
+ const { stores: householdStores, loadStores } = useContext(StoreContext);
+ const [allStores, setAllStores] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [showAddStore, setShowAddStore] = useState(false);
+
+ const isAdmin = activeHousehold?.role === "admin";
+
+ useEffect(() => {
+ loadAllStores();
+ }, []);
+
+ const loadAllStores = async () => {
+ setLoading(true);
+ try {
+ const response = await getAllStores();
+ setAllStores(response.data);
+ } catch (error) {
+ console.error("Failed to load stores:", error);
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ const handleAddStore = async (storeId) => {
+ try {
+ await addStoreToHousehold(activeHousehold.id, storeId, false);
+ await loadStores();
+ setShowAddStore(false);
+ } catch (error) {
+ console.error("Failed to add store:", error);
+ alert("Failed to add store");
+ }
+ };
+
+ const handleRemoveStore = async (storeId, storeName) => {
+ if (!confirm(`Remove ${storeName} from this household?`)) return;
+
+ try {
+ await removeStoreFromHousehold(activeHousehold.id, storeId);
+ await loadStores();
+ } catch (error) {
+ console.error("Failed to remove store:", error);
+ alert("Failed to remove store");
+ }
+ };
+
+ const handleSetDefault = async (storeId) => {
+ try {
+ await setDefaultStore(activeHousehold.id, storeId);
+ await loadStores();
+ } catch (error) {
+ console.error("Failed to set default store:", error);
+ alert("Failed to set default store");
+ }
+ };
+
+ const availableStores = allStores.filter(
+ store => !householdStores.some(hs => hs.id === store.id)
+ );
+
+ return (
+
+ {/* Current Stores Section */}
+
+ Your Stores ({householdStores.length})
+ {householdStores.length === 0 ? (
+ No stores added yet.
+ ) : (
+
+ {householdStores.map((store) => (
+
+
+
{store.name}
+ {store.location &&
{store.location}
}
+ {store.is_default &&
Default}
+
+ {isAdmin && (
+
+ {!store.is_default && (
+
+ )}
+
+
+ )}
+
+ ))}
+
+ )}
+
+
+ {/* Add Store Section */}
+ {isAdmin && (
+
+ Add Store
+ {!showAddStore ? (
+
+ ) : (
+
+
+ {loading ? (
+
Loading stores...
+ ) : availableStores.length === 0 ? (
+
All available stores have been added.
+ ) : (
+
+ {availableStores.map((store) => (
+
+
+
{store.name}
+ {store.location &&
{store.location}
}
+
+
+
+ ))}
+
+ )}
+
+ )}
+
+ )}
+
+ );
+}
diff --git a/frontend/src/components/manage/index.js b/frontend/src/components/manage/index.js
new file mode 100644
index 0000000..5ce1ee0
--- /dev/null
+++ b/frontend/src/components/manage/index.js
@@ -0,0 +1,4 @@
+export { default as CreateJoinHousehold } from './CreateJoinHousehold';
+export { default as ManageHousehold } from './ManageHousehold';
+export { default as ManageStores } from './ManageStores';
+
diff --git a/frontend/src/context/AuthContext.jsx b/frontend/src/context/AuthContext.jsx
index e660f4b..046babd 100644
--- a/frontend/src/context/AuthContext.jsx
+++ b/frontend/src/context/AuthContext.jsx
@@ -2,6 +2,7 @@ import { createContext, useState } from 'react';
export const AuthContext = createContext({
token: null,
+ userId: null,
role: null,
username: null,
login: () => { },
@@ -10,14 +11,17 @@ export const AuthContext = createContext({
export const AuthProvider = ({ children }) => {
const [token, setToken] = useState(localStorage.getItem('token') || null);
+ const [userId, setUserId] = useState(localStorage.getItem('userId') || null);
const [role, setRole] = useState(localStorage.getItem('role') || null);
const [username, setUsername] = useState(localStorage.getItem('username') || null);
const login = (data) => {
localStorage.setItem('token', data.token);
+ localStorage.setItem('userId', data.userId);
localStorage.setItem('role', data.role);
localStorage.setItem('username', data.username);
setToken(data.token);
+ setUserId(data.userId);
setRole(data.role);
setUsername(data.username);
};
@@ -26,12 +30,14 @@ export const AuthProvider = ({ children }) => {
localStorage.clear();
setToken(null);
+ setUserId(null);
setRole(null);
setUsername(null);
};
const value = {
token,
+ userId,
role,
username,
login,
diff --git a/frontend/src/pages/Manage.jsx b/frontend/src/pages/Manage.jsx
new file mode 100644
index 0000000..5e55947
--- /dev/null
+++ b/frontend/src/pages/Manage.jsx
@@ -0,0 +1,51 @@
+import { useContext, useState } from "react";
+import ManageHousehold from "../components/manage/ManageHousehold";
+import ManageStores from "../components/manage/ManageStores";
+import { HouseholdContext } from "../context/HouseholdContext";
+import "../styles/pages/Manage.css";
+
+export default function Manage() {
+ const { activeHousehold } = useContext(HouseholdContext);
+ const [activeTab, setActiveTab] = useState("household");
+
+ if (!activeHousehold) {
+ return (
+
+
+
Manage
+
+ Loading household...
+
+
+
+ );
+ }
+
+ return (
+
+
+
Manage {activeHousehold.name}
+
+
+
+
+
+
+
+ {activeTab === "household" && }
+ {activeTab === "stores" && }
+
+
+
+ );
+}
diff --git a/frontend/src/styles/components/HouseholdSwitcher.css b/frontend/src/styles/components/HouseholdSwitcher.css
index ce1469e..d05a5b6 100644
--- a/frontend/src/styles/components/HouseholdSwitcher.css
+++ b/frontend/src/styles/components/HouseholdSwitcher.css
@@ -8,18 +8,18 @@
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
- background: var(--surface-color);
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- color: var(--text-color);
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ color: var(--text-primary);
font-size: 1rem;
cursor: pointer;
transition: all 0.2s ease;
}
.household-switcher-toggle:hover {
- background: var(--hover-color);
- border-color: var(--primary-color);
+ background: var(--card-hover);
+ border-color: var(--primary);
}
.household-switcher-toggle:disabled {
@@ -53,11 +53,11 @@
position: absolute;
top: calc(100% + 0.5rem);
left: 0;
- min-width: 200px;
- background: var(--surface-color);
- border: 1px solid var(--border-color);
- border-radius: var(--border-radius);
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ min-width: 220px;
+ background: var(--card-bg);
+ border: 2px solid var(--border);
+ border-radius: 8px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3);
z-index: 1000;
overflow: hidden;
}
@@ -67,15 +67,15 @@
align-items: center;
justify-content: space-between;
width: 100%;
- padding: 0.75rem 1rem;
- background: transparent;
+ padding: 0.875rem 1rem;
+ background: var(--card-bg);
border: none;
- border-bottom: 1px solid var(--border-color);
- color: var(--text-color);
+ border-bottom: 1px solid var(--border);
+ color: var(--text-primary);
font-size: 1rem;
text-align: left;
cursor: pointer;
- transition: background 0.2s ease;
+ transition: all 0.2s ease;
}
.household-option:last-child {
@@ -83,16 +83,33 @@
}
.household-option:hover {
- background: var(--hover-color);
+ background: var(--card-hover);
+ border-color: var(--primary);
}
.household-option.active {
- background: var(--primary-color-light);
- color: var(--primary-color);
- font-weight: 500;
+ background: rgba(30, 144, 255, 0.15);
+ color: var(--primary);
+ font-weight: 600;
}
.check-mark {
- color: var(--primary-color);
+ color: var(--primary);
font-weight: bold;
+ font-size: 1.1rem;
+}
+
+.household-divider {);
+ margin: 0.25rem 0;
+}
+
+.create-household-btn {
+ color: var(--primary);
+ font-weight: 600;
+}
+
+.create-household-btn:hover {
+ background: rgba(30, 144, 255, 0.15
+.create-household-btn:hover {
+ background: var(--primary-color-light);
}
diff --git a/frontend/src/styles/components/manage/CreateJoinHousehold.css b/frontend/src/styles/components/manage/CreateJoinHousehold.css
new file mode 100644
index 0000000..5103595
--- /dev/null
+++ b/frontend/src/styles/components/manage/CreateJoinHousehold.css
@@ -0,0 +1,165 @@
+/* Create/Join Household Modal */
+.create-join-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 0, 0, 0.6);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 1000;
+ padding: 1rem;
+}
+
+.create-join-modal {
+ background: var(--card-bg);
+ border-radius: 12px;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
+ max-width: 500px;
+ width: 100%;
+ max-height: 90vh;
+ overflow-y: auto;
+}
+
+.modal-header {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1.5rem;
+ border-bottom: 1px solid var(--border);
+}
+
+.modal-header h2 {
+ font-size: 1.5rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin: 0;
+}
+
+.close-btn {
+ background: none;
+ border: none;
+ font-size: 2rem;
+ color: var(--text-secondary);
+ cursor: pointer;
+ padding: 0;
+ width: 2rem;
+ height: 2rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: all 0.2s;
+}
+
+.close-btn:hover {
+ background: var(--card-hover);
+ color: var(--text-primary);
+}
+
+/* Mode Tabs */
+.mode-tabs {
+ display: flex;
+ border-bottom: 2px solid var(--border);
+}
+
+.mode-tab {
+ flex: 1;
+ padding: 1rem;
+ background: none;
+ border: none;
+ border-bottom: 3px solid transparent;
+ color: var(--text-secondary);
+ font-size: 1rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.mode-tab:hover {
+ color: var(--text-primary);
+ background: var(--card-hover);
+}
+
+.mode-tab.active {
+ color: var(--primary);
+ border-bottom-color: var(--primary);
+}
+
+/* Form */
+.household-form {
+ padding: 1.5rem;
+}
+
+.form-group {
+ margin-bottom: 1.5rem;
+}
+
+.form-group label {
+ display: block;
+ font-weight: 500;
+ color: var(--text-primary);
+ margin-bottom: 0.5rem;
+}
+
+.form-group input {
+ width: 100%;
+ padding: 0.75rem;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ font-size: 1rem;
+ background: var(--background);
+ color: var(--text-primary);
+ transition: border-color 0.2s;
+}
+
+.form-group input:focus {
+ outline: none;
+ border-color: var(--primary);
+}
+
+.form-hint {
+ margin-top: 0.5rem;
+ font-size: 0.85rem;
+ color: var(--text-secondary);
+}
+
+.error-message {
+ margin: 1rem 1.5rem;
+ padding: 0.75rem;
+ background: var(--danger-light, rgba(220, 53, 69, 0.1));
+ border: 1px solid var(--danger);
+ border-radius: 6px;
+ color: var(--danger);
+ font-size: 0.9rem;
+}
+
+.form-actions {
+ display: flex;
+ gap: 0.75rem;
+ justify-content: flex-end;
+}
+
+.form-actions button {
+ min-width: 100px;
+}
+
+/* Responsive */
+@media (max-width: 600px) {
+ .create-join-modal {
+ margin: 0;
+ border-radius: 0;
+ max-height: 100vh;
+ height: 100%;
+ }
+
+ .form-actions {
+ flex-direction: column-reverse;
+ }
+
+ .form-actions button {
+ width: 100%;
+ }
+}
diff --git a/frontend/src/styles/components/manage/ManageHousehold.css b/frontend/src/styles/components/manage/ManageHousehold.css
new file mode 100644
index 0000000..d7caeed
--- /dev/null
+++ b/frontend/src/styles/components/manage/ManageHousehold.css
@@ -0,0 +1,252 @@
+/* Manage Household Component */
+.manage-household {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ max-width: 800px;
+ margin: 0 auto;
+ width: 100%;
+}
+
+/* Section Styling */
+.manage-section {
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 2rem;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.manage-section h2 {
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 1rem;
+}
+
+.section-description {
+ color: var(--text-secondary);
+ font-size: 0.9rem;
+ margin-bottom: 1rem;
+}
+
+/* Household Name Section */
+.name-display {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 1rem;
+}
+
+.name-display h3 {
+ font-size: 1.5rem;
+ color: var(--text-primary);
+ margin: 0;
+}
+
+.edit-name-form {
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.edit-name-form input {
+ flex: 1;
+ min-width: 200px;
+ padding: 0.75rem;
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ font-size: 1rem;
+ background: var(--background);
+ color: var(--text-primary);
+}
+
+/* Invite Code Section */
+.invite-actions {
+ display: flex;
+ gap: 0.75rem;
+ align-items: center;
+ flex-wrap: wrap;
+}
+
+.invite-code {
+ background: var(--background);
+ padding: 0.75rem 1rem;
+ border-radius: 6px;
+ font-family: 'Courier New', monospace;
+ font-size: 1rem;
+ color: var(--primary);
+ border: 2px solid var(--border);
+ font-weight: 600;
+ letter-spacing: 0.5px;
+}
+
+/* Members Section */
+.members-list {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.member-card {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 1.25rem;
+ background: var(--background);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ transition: all 0.2s;
+ gap: 1rem;
+}
+
+.member-card:hover {
+ background: var(--card-hover);
+ border-color: var(--primary);
+}
+
+.member-info {
+ display: flex;
+ flex-direction: column;
+ gap: 0.25rem;
+}
+
+.member-name {
+ font-weight: 500;
+ color: var(--text-primary);
+ font-size: 1rem;
+}
+
+.member-role {
+ font-size: 0.85rem;
+ padding: 0.2rem 0.5rem;
+ border-radius: 4px;
+ width: fit-content;
+ text-transform: capitalize;
+}
+
+.member-role.admin {
+ background: var(--primary-light, rgba(0, 122, 255, 0.1));
+ color: var(--primary);
+}
+
+.member-role.member {
+ background: var(--text-secondary-light, rgba(128, 128, 128, 0.1));
+ color: var(--text-secondary);
+}
+
+.member-actions {
+ display: flex;
+ gap: 0.75rem;
+ flex-wrap: wrap;
+}
+
+/* Danger Zone */
+.danger-zone {
+ border-color: var(--danger);
+}
+
+.danger-zone h2 {
+ color: var(--danger);
+}
+
+/* Buttons */
+.btn-primary,
+.btn-secondary,
+.btn-danger {
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 6px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.btn-primary,
+.btn-secondary {
+ background: var(--primary);
+ color: white;
+}
+
+.btn-primary:hover,
+.btn-secondary:hover {
+ background: var(--primary-dark, #0056b3);
+}
+
+.btn-danger {
+ background: var(--danger);
+ color: white;
+}
+
+.btn-danger:hover {
+ background: var(--danger-dark, #c82333);
+}
+
+.btn-small {
+ padding: 0.4rem 0.75rem;
+ font-size: 0.85rem;
+}
+
+.btn-danger:disabled {
+ background: var(--text-secondary);
+ opacity: 0.5;
+ cursor: not-allowed;
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .manage-section {
+ padding: 1.25rem;
+ }
+
+ .name-display {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .edit-name-form {
+ flex-direction: column;
+ width: 100%;
+ align-items: stretch;
+ }
+
+ .edit-name-form input {
+ width: 100%;
+ min-width: unset;
+ }
+
+ .edit-name-form button {
+ width: 100%;
+ }
+
+ .member-card {
+ flex-direction: column;
+ align-items: flex-start;
+ gap: 1rem;
+ }
+
+ .member-actions {
+ width: 100%;
+ }
+
+ .member-actions button {
+ flex: 1;
+ }
+
+ .invite-actions {
+ flex-direction: column;
+ align-items: stretch;
+ }
+
+ .invite-actions button {
+ width: 100%;
+ }
+
+ .invite-code {
+ text-align: center;
+ width: 100%;
+ }
+}
diff --git a/frontend/src/styles/components/manage/ManageStores.css b/frontend/src/styles/components/manage/ManageStores.css
new file mode 100644
index 0000000..2023aa8
--- /dev/null
+++ b/frontend/src/styles/components/manage/ManageStores.css
@@ -0,0 +1,161 @@
+/* Manage Stores Component */
+.manage-stores {
+ display: flex;
+ flex-direction: column;
+ gap: 1.5rem;
+ max-width: 800px;
+ margin: 0 auto;
+ width: 100%;
+}
+
+/* Section Styling */
+.manage-section {
+ background: var(--card-bg);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 2rem;
+ width: 100%;
+ box-sizing: border-box;
+}
+
+.manage-section h2 {
+ font-size: 1.3rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin-bottom: 1rem;
+}
+
+/* Stores List */
+.stores-list {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1rem;
+}
+
+.store-card {
+ background: var(--background);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 1.25rem;
+ transition: all 0.2s;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+}
+
+.store-card:hover {
+ border-color: var(--primary);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.store-info h3 {
+ font-size: 1.1rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin: 0 0 0.5rem 0;
+}
+
+.store-location {
+ color: var(--text-secondary);
+ font-size: 0.9rem;
+ margin: 0;
+}
+
+.default-badge {
+ display: inline-block;
+ background: var(--primary);
+ color: white;
+ padding: 0.25rem 0.5rem;
+ border-radius: 4px;
+ font-size: 0.75rem;
+ font-weight: 600;
+ text-transform: uppercase;
+ margin-top: 0.5rem;
+}
+
+.store-actions {
+ display: flex;
+ gap: 0.5rem;
+ flex-wrap: wrap;
+}
+
+/* Add Store Panel */
+.add-store-panel {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ margin-top: 1rem;
+}
+
+.available-stores {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 1rem;
+}
+
+.available-store-card {
+ background: var(--background);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 1.25rem;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 1rem;
+ transition: all 0.2s;
+}
+
+.available-store-card:hover {
+ border-color: var(--primary);
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.available-store-card .store-info {
+ flex: 1;
+}
+
+.available-store-card h3 {
+ font-size: 1rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ margin: 0 0 0.25rem 0;
+}
+
+.available-store-card .store-location {
+ color: var(--text-secondary);
+ font-size: 0.85rem;
+ margin: 0;
+}
+
+/* Empty State */
+.empty-message {
+ color: var(--text-secondary);
+ text-align: center;
+ padding: 2rem;
+ font-style: italic;
+}
+
+/* Responsive */
+@media (max-width: 600px) {
+ .stores-list,
+ .available-stores {
+ grid-template-columns: 1fr;
+ }
+
+ .store-actions {
+ width: 100%;
+ }
+
+ .store-actions button {
+ flex: 1;
+ }
+
+ .available-store-card {
+ flex-direction: column;
+ align-items: flex-start;
+ }
+
+ .available-store-card button {
+ width: 100%;
+ }
+}
diff --git a/frontend/src/styles/pages/Manage.css b/frontend/src/styles/pages/Manage.css
new file mode 100644
index 0000000..ebf742b
--- /dev/null
+++ b/frontend/src/styles/pages/Manage.css
@@ -0,0 +1,119 @@
+/* Manage Page Layout */
+.manage-body {
+ min-height: 100vh;
+ background: var(--background);
+ padding: 2rem 1rem;
+ display: flex;
+ justify-content: center;
+}
+
+.manage-container {
+ max-width: 850px;
+ width: 100%;
+ margin: 0 auto;
+ padding: 0 1rem;
+}
+
+.manage-title {
+ font-size: 2rem;
+ font-weight: 600;
+ color: var(--text-primary);
+ text-align: center;
+ margin-bottom: 2rem;
+}
+
+/* Tabs */
+.manage-tabs {
+ display: flex;
+ gap: 0.5rem;
+ border-bottom: 2px solid var(--border);
+ margin-bottom: 2rem;
+}
+
+.manage-tab {
+ padding: 0.75rem 1.5rem;
+ background: none;
+ border: none;
+ border-bottom: 3px solid transparent;
+ color: var(--text-secondary);
+ font-size: 1rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+}
+
+.manage-tab:hover {
+ color: var(--text-primary);
+ background: var(--card-hover);
+}
+
+.manage-tab.active {
+ color: var(--primary);
+ border-bottom-color: var(--primary);
+}
+
+/* Content Area */
+.manage-content {
+ animation: fadeIn 0.3s ease-in;
+}
+
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+/* Button Styles */
+.btn-primary,
+.btn-secondary {
+ padding: 0.5rem 1rem;
+ border: none;
+ border-radius: 6px;
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ transition: all 0.2s;
+ background: var(--primary);
+ color: white;
+}
+
+.btn-primary:hover,
+.btn-secondary:hover {
+ background: var(--primary-dark, #0056b3);
+}
+
+/* Responsive */
+@media (max-width: 768px) {
+ .manage-body {
+ padding: 1rem 0.75rem;
+ }
+
+ .manage-title {
+ font-size: 1.75rem;
+ }
+
+ .manage-tab {
+ padding: 0.65rem 1rem;
+ font-size: 0.9rem;
+ }
+}
+
+@media (max-width: 480px) {
+ .manage-body {
+ padding: 1rem 0.5rem;
+ }
+
+ .manage-title {
+ font-size: 1.5rem;
+ }
+
+ .manage-tab {
+ padding: 0.5rem 0.75rem;
+ font-size: 0.85rem;
+ }
+}
diff --git a/frontend/src/styles/theme.css b/frontend/src/styles/theme.css
index c9d7254..75ba26f 100644
--- a/frontend/src/styles/theme.css
+++ b/frontend/src/styles/theme.css
@@ -14,10 +14,10 @@
============================================ */
/* Primary Colors */
- --color-primary: #007bff;
- --color-primary-hover: #0056b3;
+ --color-primary: dodgerblue;
+ --color-primary-hover: #0066cc;
--color-primary-light: #e7f3ff;
- --color-primary-dark: #0067d8;
+ --color-primary-dark: #0056b3;
/* Secondary Colors */
--color-secondary: #6c757d;
@@ -187,6 +187,20 @@
--modal-border-radius: var(--border-radius-lg);
--modal-padding: var(--spacing-lg);
--modal-max-width: 500px;
+
+ /* ============================================
+ SIMPLIFIED ALIASES (for component convenience)
+ ============================================ */
+ --primary: var(--color-primary);
+ --primary-dark: var(--color-primary-dark);
+ --primary-light: var(--color-primary-light);
+ --danger: var(--color-danger);
+ --danger-dark: var(--color-danger-hover);
+ --text-primary: var(--color-text-primary);
+ --text-secondary: var(--color-text-secondary);
+ --background: var(--color-bg-body);
+ --border: var(--color-border-light);
+ --card-hover: var(--color-bg-hover);
}
diff --git a/frontend/src/styles/utilities.css b/frontend/src/styles/utilities.css
index e02a4e4..b92d9da 100644
--- a/frontend/src/styles/utilities.css
+++ b/frontend/src/styles/utilities.css
@@ -109,12 +109,12 @@
}
.btn-secondary {
- background: var(--color-secondary);
+ background: var(--color-primary);
color: var(--color-text-inverse);
}
.btn-secondary:hover:not(:disabled) {
- background: var(--color-secondary-hover);
+ background: var(--color-primary-hover);
}
.btn-danger {