merge dev #16
+39
-1
@@ -403,10 +403,47 @@ def confirm_account_action(token: str) -> dict[str, Any]:
|
|||||||
raise HTTPException(status_code=422, detail="Token is required")
|
raise HTTPException(status_code=422, detail="Token is required")
|
||||||
|
|
||||||
with get_connection() as conn:
|
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"])
|
action = str(confirmation["action"])
|
||||||
confirmed_user_id = str(confirmation["user_id"])
|
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":
|
if action == "register_confirm":
|
||||||
conn.execute(
|
conn.execute(
|
||||||
"UPDATE user_profiles SET email_confirmed = 1, updated_at = ? WHERE user_id = ?",
|
"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)
|
ensure_order_exists(order_id)
|
||||||
|
|
||||||
with get_connection() as conn:
|
with get_connection() as conn:
|
||||||
|
upsert_user_order_tokens(conn, clean_id, order_id)
|
||||||
auth = get_user_order_tokens(conn, clean_id, order_id)
|
auth = get_user_order_tokens(conn, clean_id, order_id)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
+2
-2
@@ -4,7 +4,7 @@ services:
|
|||||||
context: backend
|
context: backend
|
||||||
dockerfile: Containerfile
|
dockerfile: Containerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ../.data:/app/data
|
- ./.data:/app/data
|
||||||
- ../config.yaml:/app/config.yaml:ro
|
- ../config.yaml:/app/config.yaml:ro
|
||||||
- ./backend/app:/app/app
|
- ./backend/app:/app/app
|
||||||
environment:
|
environment:
|
||||||
@@ -28,7 +28,7 @@ services:
|
|||||||
- ./frontend/vite.config.ts:/app/vite.config.ts
|
- ./frontend/vite.config.ts:/app/vite.config.ts
|
||||||
- ./frontend/tsconfig.json:/app/tsconfig.json
|
- ./frontend/tsconfig.json:/app/tsconfig.json
|
||||||
- ./frontend/tsconfig.node.json:/app/tsconfig.node.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
|
command: npm run dev -- --host 0.0.0.0 --port 8000
|
||||||
|
|
||||||
mailpit:
|
mailpit:
|
||||||
|
|||||||
Generated
+17
@@ -9,6 +9,8 @@
|
|||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.1.0",
|
"@ant-design/icons": "^6.1.0",
|
||||||
|
"@mdi/js": "^7.4.47",
|
||||||
|
"@mdi/react": "^1.6.1",
|
||||||
"antd": "^5.27.3",
|
"antd": "^5.27.3",
|
||||||
"dompurify": "^3.3.3",
|
"dompurify": "^3.3.3",
|
||||||
"marked": "^16.0.0",
|
"marked": "^16.0.0",
|
||||||
@@ -930,6 +932,21 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@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": {
|
"node_modules/@rc-component/async-validator": {
|
||||||
"version": "5.1.0",
|
"version": "5.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.1.0.tgz",
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ant-design/icons": "^6.1.0",
|
"@ant-design/icons": "^6.1.0",
|
||||||
|
"@mdi/js": "^7.4.47",
|
||||||
|
"@mdi/react": "^1.6.1",
|
||||||
"antd": "^5.27.3",
|
"antd": "^5.27.3",
|
||||||
"dompurify": "^3.3.3",
|
"dompurify": "^3.3.3",
|
||||||
"marked": "^16.0.0",
|
"marked": "^16.0.0",
|
||||||
|
|||||||
@@ -416,6 +416,7 @@ export default function App() {
|
|||||||
message.success("Registration email sent. Open the link in your inbox to finish setup.");
|
message.success("Registration email sent. Open the link in your inbox to finish setup.");
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error?.message || "Could not create account.");
|
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.");
|
message.success("Migration email sent. Open the link in your inbox to complete migration.");
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
message.error(error?.message || "Could not migrate account.");
|
message.error(error?.message || "Could not migrate account.");
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import {
|
|||||||
formatEstimatedTotal,
|
formatEstimatedTotal,
|
||||||
stripPriceDecorations,
|
stripPriceDecorations,
|
||||||
} from "../../lib/orderFormatting";
|
} from "../../lib/orderFormatting";
|
||||||
|
import Icon from "@mdi/react";
|
||||||
|
import { mdiFood } from "@mdi/js";
|
||||||
|
|
||||||
const { Text } = Typography;
|
const { Text } = Typography;
|
||||||
|
|
||||||
@@ -40,6 +42,12 @@ function getEstimatedTotalText(rawValue: unknown): string | null {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getOrderIconStyle(isClosed: boolean) {
|
||||||
|
return {
|
||||||
|
opacity: isClosed ? 0.25 : 0.6,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export default function HomeOrdersTable({
|
export default function HomeOrdersTable({
|
||||||
orders,
|
orders,
|
||||||
selectedState,
|
selectedState,
|
||||||
@@ -81,31 +89,40 @@ export default function HomeOrdersTable({
|
|||||||
item.submission?.estimated_total,
|
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 (
|
return (
|
||||||
<Space direction="vertical" size={0}>
|
<Space size={16}>
|
||||||
<Space size={8} align="center">
|
<Icon path={mdiFood} size={1} style={getOrderIconStyle(isClosed)} />
|
||||||
<Text strong>{item.title || item.id}</Text>
|
<Space direction="vertical" size={0}>
|
||||||
{item.is_owner ? <Tag color="geekblue">Owner</Tag> : null}
|
<Space size={8} align="center">
|
||||||
{state === "paid" ? <Tag color="green">Paid</Tag> : null}
|
<Text strong>{item.title || item.id}</Text>
|
||||||
{state === "unpaid" ? (
|
{item.is_owner ? <Tag color="geekblue">Owner</Tag> : null}
|
||||||
<Tag color="volcano">Unpaid</Tag>
|
{state === "paid" ? <Tag color="green">Paid</Tag> : null}
|
||||||
) : null}
|
{state === "unpaid" ? (
|
||||||
{state === "pending" ? <Tag color="blue">Pending</Tag> : null}
|
<Tag color="volcano">Unpaid</Tag>
|
||||||
|
) : null}
|
||||||
|
{state === "pending" ? <Tag color="blue">Pending</Tag> : null}
|
||||||
|
</Space>
|
||||||
|
<Text type="secondary">
|
||||||
|
{<Tooltip title={`Order is ${closedStr.toLocaleLowerCase()}`}>{closedStr}</Tooltip>}{" • "}
|
||||||
|
{<Tooltip title={`Created at: ${dateStr}`}>{dateStr}</Tooltip>}
|
||||||
|
{item.is_participant && formatted && (
|
||||||
|
<>
|
||||||
|
{" • "}
|
||||||
|
<Tooltip title="Your order">
|
||||||
|
{formatted}
|
||||||
|
</Tooltip>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Text>
|
||||||
</Space>
|
</Space>
|
||||||
<Text type="secondary">
|
|
||||||
{item.created_at
|
|
||||||
? new Date(item.created_at).toLocaleString()
|
|
||||||
: "Unknown creation time"}
|
|
||||||
{item.is_participant && formatted && ` • ${formatted}`}
|
|
||||||
{item.is_participant && estimateText && (
|
|
||||||
<>
|
|
||||||
{" • "}
|
|
||||||
<Tooltip title="Rough estimate based on menu prices. Final amount may differ.">
|
|
||||||
{estimateText}
|
|
||||||
</Tooltip>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
</Space>
|
</Space>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -124,7 +141,7 @@ export default function HomeOrdersTable({
|
|||||||
navigateTo(`/order/${item.id}/admin`);
|
navigateTo(`/order/${item.id}/admin`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit
|
Manage
|
||||||
</Button>
|
</Button>
|
||||||
) : null,
|
) : null,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ export default function ParticipantView({ orderId }: { orderId: string }) {
|
|||||||
navigateTo(`/order/${orderId}/admin`);
|
navigateTo(`/order/${orderId}/admin`);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Edit order
|
Manage order
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
}
|
}
|
||||||
@@ -454,7 +454,7 @@ export default function ParticipantView({ orderId }: { orderId: string }) {
|
|||||||
type="primary"
|
type="primary"
|
||||||
onClick={() => setIsEditingSubmittedOrder(true)}
|
onClick={() => setIsEditingSubmittedOrder(true)}
|
||||||
>
|
>
|
||||||
Edit Order
|
Manage order
|
||||||
</Button>
|
</Button>
|
||||||
) : (
|
) : (
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
Reference in New Issue
Block a user