shopping: #shopping tag, help section, tests, client-side proxy
Three follow-ups:
- Accept #shopping as an alias for #einkauf. Either tag now makes
a card show up in the picker; the help blurb in the dialog says
so explicitly.
- Add a proper help-overlay section covering the trigger tags,
how to pick an active list, the phone URL, sync cadence, and
why phone is check-only.
- Add a client-side proxy in public/index.php for /api/shopping
and /api/shopping/check — shopping.js was calling api('api/...')
which went to hyprcrd, but only the relay had handlers. Without
this forwarder, push/pull just 404'd. Proxy mirrors the pattern
used by /api/relay/push.
- New shopping.test.js: asserts the proxy returns a JSON shape
with `ok` (works whether or not a relay is configured), the
hamburger menu toggles as expected, and the picker lists both
#einkauf + #shopping cards while excluding untagged ones.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
670f125627
commit
e086db0c2c
6 files changed
+143
-6
No files matched your search
@@ -1148,6 +1148,40 @@ if (str_starts_with($route, '/api/')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// ── Shopping list (proxy to relay) ───────────────────────────
|
||||
// Thin forwarder. The client (shopping.js) talks to us, we relay
|
||||
// to /api/shopping with the configured X-Relay-User/-Key headers.
|
||||
// Phone UI talks to the relay directly via session auth.
|
||||
if (($route === '/api/shopping' || $route === '/api/shopping/check') && in_array($method, ['GET', 'POST'])) {
|
||||
$relayUrl = $db->query("SELECT value FROM settings WHERE key = 'relay_url'")->fetchColumn();
|
||||
$relayKey = $db->query("SELECT value FROM settings WHERE key = 'relay_key'")->fetchColumn();
|
||||
$relayUser = $db->query("SELECT value FROM settings WHERE key = 'relay_username'")->fetchColumn();
|
||||
if (!$relayUrl || !$relayKey || !$relayUser) {
|
||||
echo json_encode(['ok' => false, 'error' => 'relay not configured']); exit;
|
||||
}
|
||||
$ch = curl_init(rtrim($relayUrl, '/') . $route);
|
||||
$opts = [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'X-Relay-User: ' . $relayUser,
|
||||
'X-Relay-Key: ' . $relayKey,
|
||||
],
|
||||
CURLOPT_TIMEOUT => 10,
|
||||
];
|
||||
if ($method === 'POST') {
|
||||
$opts[CURLOPT_POST] = true;
|
||||
$opts[CURLOPT_POSTFIELDS] = file_get_contents('php://input') ?: '{}';
|
||||
}
|
||||
curl_setopt_array($ch, $opts);
|
||||
$response = curl_exec($ch);
|
||||
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
http_response_code($status ?: 502);
|
||||
echo is_string($response) ? $response : json_encode(['ok' => false, 'error' => 'relay unreachable']);
|
||||
exit;
|
||||
}
|
||||
|
||||
http_response_code(404); echo json_encode(['error' => 'not found']); exit;
|
||||
}
|
||||
|
||||
@@ -1656,6 +1690,21 @@ if (str_starts_with($route, '/api/')) {
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="font-pixel text-xs text-t-accent mb-3 flex items-center gap-2"><i class="ph ph-shopping-cart text-sm"></i>shopping list (phone sync)</h3>
|
||||
<div class="text-xs leading-relaxed space-y-3 text-t-muted mb-3">
|
||||
<p>Tag a card with <span class="text-t-accent">#einkauf</span> or <span class="text-t-accent">#shopping</span> to mark it as a shopping-list candidate. Open the header's <i class="ph ph-list-dashes text-xs"></i> menu → <span class="text-t-bright font-pixel">Einkaufsliste</span> to pick which one is the currently <span class="text-t-bright">active</span> list — only one at a time is mirrored to the phone.</p>
|
||||
<p>The active card's body is pushed to the relay on every sync tick. On your phone, open <span class="text-t-accent"><your-relay>/shopping</span> and log in with your relay credentials. The page shows only the <span class="text-t-bright">unchecked</span> <code class="text-t-accent">- [ ]</code> items as tappable rows. Tapping an item flips it to <code class="text-t-accent">- [x]</code> on the relay in-place; the next client tick pulls that back and updates the local card.</p>
|
||||
<p>Editing stays in hyprcrd — the phone can only check/uncheck, never change text. That keeps conflict handling trivial (last-writer wins by timestamp).</p>
|
||||
</div>
|
||||
<table class="w-full text-xs">
|
||||
<tr><td class="py-1 text-t-dim w-40 font-pixel"><i class="ph ph-hash text-xs"></i> trigger tags</td><td><span class="text-t-accent">#einkauf</span> or <span class="text-t-accent">#shopping</span> — either counts</td></tr>
|
||||
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-list-dashes text-xs"></i> pick active</td><td>header menu → Einkaufsliste → radio select → Save</td></tr>
|
||||
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-device-mobile text-xs"></i> phone URL</td><td><code>https://<relay>/shopping</code> — session-auth, mobile layout</td></tr>
|
||||
<tr><td class="py-1 text-t-dim font-pixel"><i class="ph ph-arrows-clockwise text-xs"></i> sync cadence</td><td>piggybacks on the shared sync tick (see Settings → Relay)</td></tr>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 class="font-pixel text-xs text-t-accent mb-3 flex items-center gap-2"><i class="ph ph-download-simple text-sm"></i>import / export</h3>
|
||||
<table class="w-full text-xs">
|
||||
|
||||
Reference in new issue
Block a user