From 2b42033c70c0761dd450e15b1ee1fb5802e36ce7 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 7 Apr 2026 21:27:06 +0200 Subject: [PATCH] merge dev (#16) - Closes #15 - Other fixes and improvements here and there Co-authored-by: fhs52267 Reviewed-on: https://git.sgruber.at/lunchtime/lunchtime-web/pulls/16 --- src/backend/app/main.py | 40 +++++++++++- src/compose.yml | 4 +- src/frontend/package-lock.json | 17 +++++ src/frontend/package.json | 2 + src/frontend/src/App.tsx | 2 + .../src/components/views/HomeOrdersTable.tsx | 65 ++++++++++++------- src/frontend/src/views/ParticipantView.tsx | 4 +- 7 files changed, 105 insertions(+), 29 deletions(-) diff --git a/src/backend/app/main.py b/src/backend/app/main.py index 3ea3bef..3f2ae06 100644 --- a/src/backend/app/main.py +++ b/src/backend/app/main.py @@ -403,10 +403,47 @@ def confirm_account_action(token: str) -> dict[str, Any]: raise HTTPException(status_code=422, detail="Token is required") with get_connection() as conn: - confirmation = consume_confirmation_token(conn, token.strip()) + normalized_token = token.strip() + + try: + confirmation = consume_confirmation_token(conn, normalized_token) + already_consumed = False + except HTTPException as exc: + if exc.status_code != 409 or str(exc.detail) != "Confirmation token already used": + raise + + confirmation = conn.execute( + """ + SELECT token, user_id, action, process_id, email, new_email, new_user_id, created_at, expires_at, consumed_at + FROM account_confirmation_tokens + WHERE token = ? + """, + (normalized_token,), + ).fetchone() + + if not confirmation: + raise HTTPException(status_code=404, detail="Confirmation token not found") + + already_consumed = True + action = str(confirmation["action"]) confirmed_user_id = str(confirmation["user_id"]) + if already_consumed: + if action == "user_id_change_confirm": + migrated_user_id = str(confirmation["new_user_id"] or "").strip() or confirmed_user_id + return { + "status": "already_confirmed", + "action": action, + "user_id": migrated_user_id, + } + + return { + "status": "already_confirmed", + "action": action, + "user_id": confirmed_user_id, + } + if action == "register_confirm": conn.execute( "UPDATE user_profiles SET email_confirmed = 1, updated_at = ? WHERE user_id = ?", @@ -676,6 +713,7 @@ def get_my_order_access(order_id: str, user_id: str = Depends(get_existing_user_ ensure_order_exists(order_id) with get_connection() as conn: + upsert_user_order_tokens(conn, clean_id, order_id) auth = get_user_order_tokens(conn, clean_id, order_id) return { diff --git a/src/compose.yml b/src/compose.yml index 6d08385..bf3ec7d 100644 --- a/src/compose.yml +++ b/src/compose.yml @@ -4,7 +4,7 @@ services: context: backend dockerfile: Containerfile volumes: - - ../.data:/app/data + - ./.data:/app/data - ../config.yaml:/app/config.yaml:ro - ./backend/app:/app/app environment: @@ -28,7 +28,7 @@ services: - ./frontend/vite.config.ts:/app/vite.config.ts - ./frontend/tsconfig.json:/app/tsconfig.json - ./frontend/tsconfig.node.json:/app/tsconfig.node.json - - /app/node_modules + - /app/node_modules # Reset node_modules by running with '--renew-anon-volumes' command: npm run dev -- --host 0.0.0.0 --port 8000 mailpit: diff --git a/src/frontend/package-lock.json b/src/frontend/package-lock.json index 100dd09..dcac043 100644 --- a/src/frontend/package-lock.json +++ b/src/frontend/package-lock.json @@ -9,6 +9,8 @@ "version": "1.0.0", "dependencies": { "@ant-design/icons": "^6.1.0", + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", "antd": "^5.27.3", "dompurify": "^3.3.3", "marked": "^16.0.0", @@ -930,6 +932,21 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@mdi/js": { + "version": "7.4.47", + "resolved": "https://registry.npmjs.org/@mdi/js/-/js-7.4.47.tgz", + "integrity": "sha512-KPnNOtm5i2pMabqZxpUz7iQf+mfrYZyKCZ8QNz85czgEt7cuHcGorWfdzUMWYA0SD+a6Hn4FmJ+YhzzzjkTZrQ==", + "license": "Apache-2.0" + }, + "node_modules/@mdi/react": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@mdi/react/-/react-1.6.1.tgz", + "integrity": "sha512-4qZeDcluDFGFTWkHs86VOlHkm6gnKaMql13/gpIcUQ8kzxHgpj31NuCkD8abECVfbULJ3shc7Yt4HJ6Wu6SN4w==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.7.2" + } + }, "node_modules/@rc-component/async-validator": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz", diff --git a/src/frontend/package.json b/src/frontend/package.json index 2ebca02..c5ac74e 100644 --- a/src/frontend/package.json +++ b/src/frontend/package.json @@ -10,6 +10,8 @@ }, "dependencies": { "@ant-design/icons": "^6.1.0", + "@mdi/js": "^7.4.47", + "@mdi/react": "^1.6.1", "antd": "^5.27.3", "dompurify": "^3.3.3", "marked": "^16.0.0", diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index a186b5a..5405155 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -416,6 +416,7 @@ export default function App() { message.success("Registration email sent. Open the link in your inbox to finish setup."); } catch (error: any) { message.error(error?.message || "Could not create account."); + throw error; } }; @@ -430,6 +431,7 @@ export default function App() { message.success("Migration email sent. Open the link in your inbox to complete migration."); } catch (error: any) { message.error(error?.message || "Could not migrate account."); + throw error; } }; diff --git a/src/frontend/src/components/views/HomeOrdersTable.tsx b/src/frontend/src/components/views/HomeOrdersTable.tsx index 917941e..655bba7 100644 --- a/src/frontend/src/components/views/HomeOrdersTable.tsx +++ b/src/frontend/src/components/views/HomeOrdersTable.tsx @@ -6,6 +6,8 @@ import { formatEstimatedTotal, stripPriceDecorations, } from "../../lib/orderFormatting"; +import Icon from "@mdi/react"; +import { mdiFood } from "@mdi/js"; const { Text } = Typography; @@ -40,6 +42,12 @@ function getEstimatedTotalText(rawValue: unknown): string | null { return null; } +function getOrderIconStyle(isClosed: boolean) { + return { + opacity: isClosed ? 0.25 : 0.6, + }; +} + export default function HomeOrdersTable({ orders, selectedState, @@ -81,31 +89,40 @@ export default function HomeOrdersTable({ item.submission?.estimated_total, ); + const isClosed = !!item.closed; + const closedStr = isClosed ? " Closed" : "Open"; + + const dateStr = + item.created_at + ? new Date(item.created_at).toLocaleString() + : "Unknown creation time"; + return ( - - - {item.title || item.id} - {item.is_owner ? Owner : null} - {state === "paid" ? Paid : null} - {state === "unpaid" ? ( - Unpaid - ) : null} - {state === "pending" ? Pending : null} + + + + + {item.title || item.id} + {item.is_owner ? Owner : null} + {state === "paid" ? Paid : null} + {state === "unpaid" ? ( + Unpaid + ) : null} + {state === "pending" ? Pending : null} + + + {{closedStr}}{" • "} + {{dateStr}} + {item.is_participant && formatted && ( + <> + {" • "} + + {formatted} + + + )} + - - {item.created_at - ? new Date(item.created_at).toLocaleString() - : "Unknown creation time"} - {item.is_participant && formatted && ` • ${formatted}`} - {item.is_participant && estimateText && ( - <> - {" • "} - - {estimateText} - - - )} - ); }, @@ -124,7 +141,7 @@ export default function HomeOrdersTable({ navigateTo(`/order/${item.id}/admin`); }} > - Edit + Manage ) : null, }, diff --git a/src/frontend/src/views/ParticipantView.tsx b/src/frontend/src/views/ParticipantView.tsx index 7a2c8b5..07de547 100644 --- a/src/frontend/src/views/ParticipantView.tsx +++ b/src/frontend/src/views/ParticipantView.tsx @@ -428,7 +428,7 @@ export default function ParticipantView({ orderId }: { orderId: string }) { navigateTo(`/order/${orderId}/admin`); }} > - Edit order + Manage order } @@ -454,7 +454,7 @@ export default function ParticipantView({ orderId }: { orderId: string }) { type="primary" onClick={() => setIsEditingSubmittedOrder(true)} > - Edit Order + Manage order ) : (