update some minor ux issues
This commit is contained in:
@@ -38,7 +38,6 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
|
|
||||||
// Fam-user session: pb_token JWT → authRefresh → locals.user.
|
// Fam-user session: pb_token JWT → authRefresh → locals.user.
|
||||||
const token = event.cookies.get(SESSION_COOKIE);
|
const token = event.cookies.get(SESSION_COOKIE);
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
const pb = createPbClient(token);
|
const pb = createPbClient(token);
|
||||||
try {
|
try {
|
||||||
@@ -50,7 +49,6 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
// name/role/famId, since PB doesn't embed custom fields in the
|
// name/role/famId, since PB doesn't embed custom fields in the
|
||||||
// token itself.
|
// token itself.
|
||||||
const { record, token: freshToken } = await pb.collection('users').authRefresh();
|
const { record, token: freshToken } = await pb.collection('users').authRefresh();
|
||||||
|
|
||||||
event.locals.user = {
|
event.locals.user = {
|
||||||
id: record.id,
|
id: record.id,
|
||||||
name: record.name || record.username || '',
|
name: record.name || record.username || '',
|
||||||
|
|||||||
@@ -13,15 +13,9 @@
|
|||||||
|
|
||||||
let { famName = '', session = null, isParent = false, role = 'child' } = $props();
|
let { famName = '', session = null, isParent = false, role = 'child' } = $props();
|
||||||
|
|
||||||
let collapsed = $state(false);
|
|
||||||
|
|
||||||
let famSlug = $derived(page.data.famSlug ?? page.params.fam);
|
let famSlug = $derived(page.data.famSlug ?? page.params.fam);
|
||||||
let memberName = $derived(session?.memberName || page.params.username || '');
|
let memberName = $derived(session?.memberName || page.params.username || '');
|
||||||
|
|
||||||
function toggle() {
|
|
||||||
collapsed = !collapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
let showChildItems = $derived(!!memberName);
|
let showChildItems = $derived(!!memberName);
|
||||||
|
|
||||||
let navItems = $derived(
|
let navItems = $derived(
|
||||||
@@ -49,28 +43,24 @@
|
|||||||
? [{ href: `/${famSlug}/${memberName}/settings`, label: 'Settings', icon: settingsIcon }]
|
? [{ href: `/${famSlug}/${memberName}/settings`, label: 'Settings', icon: settingsIcon }]
|
||||||
: []),
|
: []),
|
||||||
{
|
{
|
||||||
href: session ? '/logout' : '/login',
|
href: session ? '/logout' : role === 'child' ? `/${famSlug}/join` : '/login',
|
||||||
label: session ? 'Log out' : 'Log in',
|
label: session ? 'Log out' : 'Log in',
|
||||||
icon: logoutIcon
|
icon: logoutIcon
|
||||||
}
|
}
|
||||||
]);
|
]);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<aside class="sidebar" class:collapsed>
|
<aside class="sidebar">
|
||||||
<button class="toggle-btn" onclick={toggle}>
|
|
||||||
{collapsed ? '☰' : '✕'}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div class="sidebar-header">
|
<div class="sidebar-header">
|
||||||
<span class="app-icon">✦</span>
|
<span class="app-icon">✦</span>
|
||||||
{#if !collapsed}<span class="app-name">FamChore</span>{/if}
|
<span class="app-name">FamChore</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<nav class="sidebar-nav">
|
<nav class="sidebar-nav">
|
||||||
{#each navItems as item}
|
{#each navItems as item}
|
||||||
<a href={item.href} class="nav-item" class:active={page.url.pathname === item.href}>
|
<a href={item.href} class="nav-item" class:active={page.url.pathname === item.href}>
|
||||||
{@html item.icon}
|
{@html item.icon}
|
||||||
{#if !collapsed}<span class="nav-label">{item.label}</span>{/if}
|
<span class="nav-label">{item.label}</span>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</nav>
|
</nav>
|
||||||
@@ -79,7 +69,7 @@
|
|||||||
{#each footerItems as item}
|
{#each footerItems as item}
|
||||||
<a href={item.href} class="nav-item">
|
<a href={item.href} class="nav-item">
|
||||||
{@html item.icon}
|
{@html item.icon}
|
||||||
{#if !collapsed}<span class="nav-label">{item.label}</span>{/if}
|
<span class="nav-label">{item.label}</span>
|
||||||
</a>
|
</a>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
@@ -100,21 +90,7 @@
|
|||||||
z-index: 100;
|
z-index: 100;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
.sidebar.collapsed {
|
|
||||||
width: 56px;
|
|
||||||
}
|
|
||||||
.toggle-btn {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.5rem;
|
|
||||||
right: 0.5rem;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: #a5b4fc;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.25rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
.sidebar-header {
|
.sidebar-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ export const choresIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill=
|
|||||||
export const rewardsIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="6"/><path d="M15.477 12.89L17 22l-5-3-5 3 1.523-9.11"/></svg>'
|
export const rewardsIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="8" r="6"/><path d="M15.477 12.89L17 22l-5-3-5 3 1.523-9.11"/></svg>'
|
||||||
export const moneyIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>'
|
export const moneyIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6"/></svg>'
|
||||||
export const coinsIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v6c0 1.66 3.58 3 8 3s8-1.34 8-3V6"/><path d="M4 12v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"/></svg>'
|
export const coinsIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="6" rx="8" ry="3"/><path d="M4 6v6c0 1.66 3.58 3 8 3s8-1.34 8-3V6"/><path d="M4 12v6c0 1.66 3.58 3 8 3s8-1.34 8-3v-6"/></svg>'
|
||||||
|
export const walletIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12V7H5a2 2 0 0 1 0-4h14v4"/><path d="M3 5v14a2 2 0 0 0 2 2h16v-5"/><path d="M18 12a2 2 0 0 0 0 4h4v-4Z"/></svg>'
|
||||||
|
export const circleIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/></svg>'
|
||||||
export const handCoinsIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 15h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 17"/><path d="m7 21 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"/><path d="M2 16l1 1h2"/><path d="M4 12l1 1h2"/><path d="M6 8l1 1h2"/><path d="M2 20l1.5-.5"/><circle cx="16" cy="8" r="2"/></svg>'
|
export const handCoinsIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 15h2a2 2 0 1 0 0-4h-3c-.6 0-1.1.2-1.4.6L3 17"/><path d="m7 21 1.6-1.4c.3-.4.8-.6 1.4-.6h4c1.1 0 2.1-.4 2.8-1.2l4.6-4.4a2 2 0 0 0-2.75-2.91l-4.2 3.9"/><path d="M2 16l1 1h2"/><path d="M4 12l1 1h2"/><path d="M6 8l1 1h2"/><path d="M2 20l1.5-.5"/><circle cx="16" cy="8" r="2"/></svg>'
|
||||||
export const gemIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 3h12l4 6-10 13L2 9Z"/><path d="M11 3 8 9l4 13 4-13-3-6"/><path d="M2 9h20"/></svg>'
|
export const gemIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6 3h12l4 6-10 13L2 9Z"/><path d="M11 3 8 9l4 13 4-13-3-6"/><path d="M2 9h20"/></svg>'
|
||||||
export const archiveIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"/><path d="M10 12h4"/></svg>'
|
export const archiveIcon = '<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect width="20" height="5" x="2" y="3" rx="1"/><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"/><path d="M10 12h4"/></svg>'
|
||||||
|
|||||||
@@ -21,8 +21,12 @@
|
|||||||
let disabled = $derived(!!data.famAccess?.disabled);
|
let disabled = $derived(!!data.famAccess?.disabled);
|
||||||
let accessReason = $derived(data.famAccess?.reason || '');
|
let accessReason = $derived(data.famAccess?.reason || '');
|
||||||
// Settings stays usable while paused so admins can apply a code / manage billing.
|
// Settings stays usable while paused so admins can apply a code / manage billing.
|
||||||
|
let hasAuth = $derived(!!data.session);
|
||||||
let locked = $derived(disabled && !page.url.pathname.endsWith('/settings'));
|
let locked = $derived(disabled && !page.url.pathname.endsWith('/settings'));
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
console.log(data, page.url.pathname.includes('/join'), locked);
|
||||||
|
});
|
||||||
// ── Post-checkout activation (event-driven) ──
|
// ── Post-checkout activation (event-driven) ──
|
||||||
// Landing with ?checkout=return: if the webhook has already landed we show
|
// Landing with ?checkout=return: if the webhook has already landed we show
|
||||||
// the welcome notice; otherwise show an "activating" state and wait for the
|
// the welcome notice; otherwise show an "activating" state and wait for the
|
||||||
@@ -89,16 +93,22 @@
|
|||||||
|
|
||||||
function accessMessage(reason: string, parent: boolean) {
|
function accessMessage(reason: string, parent: boolean) {
|
||||||
const map: Record<string, string> = {
|
const map: Record<string, string> = {
|
||||||
code_expired: parent ? 'Access paused — your code has expired. Add a new one.' : 'Access paused.',
|
code_expired: parent
|
||||||
|
? 'Access paused — your code has expired. Add a new one.'
|
||||||
|
: 'Access paused.',
|
||||||
code_disabled: parent ? 'Access paused — your access code was disabled.' : 'Access paused.',
|
code_disabled: parent ? 'Access paused — your access code was disabled.' : 'Access paused.',
|
||||||
subscription_inactive: parent ? 'Access paused — check your subscription payment.' : 'Access paused.',
|
subscription_inactive: parent
|
||||||
canceled: parent ? 'Access paused — renew your subscription or add a code.' : 'Access paused.',
|
? 'Access paused — check your subscription payment.'
|
||||||
|
: 'Access paused.',
|
||||||
|
canceled: parent
|
||||||
|
? 'Access paused — renew your subscription or add a code.'
|
||||||
|
: 'Access paused.',
|
||||||
no_access: parent ? 'Access paused — add an access code to get started.' : 'Access paused.'
|
no_access: parent ? 'Access paused — add an access code to get started.' : 'Access paused.'
|
||||||
};
|
};
|
||||||
return map[reason] || 'Access paused.';
|
return map[reason] || 'Access paused.';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Claim toast watcher (admin only)
|
// Claim toast watcher (admin only)
|
||||||
let claimToast = $state('');
|
let claimToast = $state('');
|
||||||
let prevClaimedIds = new Set<string>();
|
let prevClaimedIds = new Set<string>();
|
||||||
let mounted = false;
|
let mounted = false;
|
||||||
@@ -173,25 +183,28 @@
|
|||||||
</TopNav>
|
</TopNav>
|
||||||
<main class="app-main">
|
<main class="app-main">
|
||||||
<div class="page-wrap">
|
<div class="page-wrap">
|
||||||
<div class="page-content" class:locked>{@render children()}</div>
|
<div class="page-content" class:locked>{@render children()}</div>
|
||||||
{#if locked}
|
|
||||||
<div class="disabled-overlay">
|
{#if locked}
|
||||||
<div class="disabled-card">
|
<div class="disabled-overlay">
|
||||||
{#if activating}
|
<div class="disabled-card">
|
||||||
<div class="spinner" aria-hidden="true"></div>
|
{#if !hasAuth}
|
||||||
<strong>Activating your subscription…</strong>
|
<h2>You need to be logged in to view this page</h2>
|
||||||
<span>Payment received — this usually only takes a few seconds.</span>
|
{:else if activating}
|
||||||
{:else}
|
<div class="spinner" aria-hidden="true"></div>
|
||||||
<strong>Access paused</strong>
|
<strong>Activating your subscription…</strong>
|
||||||
<span>
|
<span>Payment received — this usually only takes a few seconds.</span>
|
||||||
{isParent
|
{:else}
|
||||||
? 'Add an access code or resume your subscription to keep using FamChore.'
|
<strong>Access paused</strong>
|
||||||
: 'Your family access is paused.'}
|
<span>
|
||||||
</span>
|
{isParent
|
||||||
{/if}
|
? 'Add an access code or resume your subscription to keep using FamChore.'
|
||||||
|
: 'Your family access is paused.'}
|
||||||
|
</span>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{/if}
|
||||||
{/if}
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{#if claimToast}
|
{#if claimToast}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export async function load(event) {
|
|||||||
s.crud.list('members', famId),
|
s.crud.list('members', famId),
|
||||||
s.crud.list('chore-templates', famId),
|
s.crud.list('chore-templates', famId),
|
||||||
s.crud.list('assigned-chores', famId),
|
s.crud.list('assigned-chores', famId),
|
||||||
s.fam.weeklySummary(famId),
|
s.fam.weeklySummary(famId)
|
||||||
]);
|
]);
|
||||||
return { members, templates, assigned, summary };
|
return { members, templates, assigned, summary };
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
import {
|
import {
|
||||||
checkCircleIcon,
|
checkCircleIcon,
|
||||||
revokeIcon,
|
revokeIcon,
|
||||||
todoIcon,
|
circleIcon,
|
||||||
targetIcon,
|
targetIcon,
|
||||||
sunIcon,
|
sunIcon,
|
||||||
calendarIcon,
|
calendarIcon,
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
handCoinsIcon,
|
handCoinsIcon,
|
||||||
moneyIcon,
|
moneyIcon,
|
||||||
coinsIcon,
|
coinsIcon,
|
||||||
|
walletIcon,
|
||||||
chevronLeftIcon,
|
chevronLeftIcon,
|
||||||
chevronRightIcon
|
chevronRightIcon
|
||||||
} from '$lib/components/icons';
|
} from '$lib/components/icons';
|
||||||
@@ -578,6 +579,9 @@
|
|||||||
function headDay(): number {
|
function headDay(): number {
|
||||||
return headDate().getDate();
|
return headDate().getDate();
|
||||||
}
|
}
|
||||||
|
function headWeekday(): string {
|
||||||
|
return headDate().toLocaleDateString(undefined, { weekday: 'long' });
|
||||||
|
}
|
||||||
function headMonth(): string {
|
function headMonth(): string {
|
||||||
const d = headDate();
|
const d = headDate();
|
||||||
return `${MONTHS[d.getMonth()]} ${d.getFullYear()}`;
|
return `${MONTHS[d.getMonth()]} ${d.getFullYear()}`;
|
||||||
@@ -1078,6 +1082,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="hero-date">
|
<div class="hero-date">
|
||||||
<span class="hero-day">{headDay()}</span>
|
<span class="hero-day">{headDay()}</span>
|
||||||
|
<span class="hero-weekday">{headWeekday()}</span>
|
||||||
<span class="hero-month">{headMonth()}</span>
|
<span class="hero-month">{headMonth()}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -1259,7 +1264,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{#each dailyPending as chore}
|
{#each dailyPending as chore}
|
||||||
<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
|
<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
|
||||||
<span class="checkbox empty"></span>
|
<span class="checkbox">{@html circleIcon}</span>
|
||||||
<span class="chore-name">{choreName(chore)}</span>
|
<span class="chore-name">{choreName(chore)}</span>
|
||||||
<span class="chore-value">{chore.value} {chore.type}</span>
|
<span class="chore-value">{chore.value} {chore.type}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -1276,7 +1281,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
{#each weeklyPending as chore}
|
{#each weeklyPending as chore}
|
||||||
<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
|
<button class="chore" onclick={() => toggle(chore)} disabled={!!togglingIds}>
|
||||||
<span class="checkbox empty"></span>
|
<span class="checkbox">{@html circleIcon}</span>
|
||||||
<span class="chore-name">{choreName(chore)}</span>
|
<span class="chore-name">{choreName(chore)}</span>
|
||||||
<span class="chore-value">{chore.value} {chore.type}</span>
|
<span class="chore-value">{chore.value} {chore.type}</span>
|
||||||
</button>
|
</button>
|
||||||
@@ -1284,28 +1289,30 @@
|
|||||||
{#each memberTodos.filter((t) => !isTodoDone(t.id)) as todo}
|
{#each memberTodos.filter((t) => !isTodoDone(t.id)) as todo}
|
||||||
{@const urgency = todoUrgency(todo)}
|
{@const urgency = todoUrgency(todo)}
|
||||||
{#if todo.type === 'emoji'}
|
{#if todo.type === 'emoji'}
|
||||||
<div
|
<button
|
||||||
class="chore todo-chore todo-display"
|
class="chore todo-chore"
|
||||||
class:todo-blue={urgency === 'blue'}
|
class:todo-blue={urgency === 'blue'}
|
||||||
class:todo-green={urgency === 'green'}
|
class:todo-green={urgency === 'green'}
|
||||||
class:todo-orange={urgency === 'orange'}
|
class:todo-orange={urgency === 'orange'}
|
||||||
class:todo-red={urgency === 'red'}
|
class:todo-red={urgency === 'red'}
|
||||||
class:todo-black={urgency === 'black'}
|
class:todo-black={urgency === 'black'}
|
||||||
|
onclick={() => toggle(todo)}
|
||||||
|
disabled={!!togglingIds}
|
||||||
>
|
>
|
||||||
<span class="checkbox">{@html todoIcon}</span>
|
<span class="checkbox">{@html circleIcon}</span>
|
||||||
<span class="todo-main">
|
<span class="todo-main">
|
||||||
<span class="chore-name">{todo.customName || 'Todo'}</span>
|
<span class="chore-name">{todo.customName || 'Todo'}</span>
|
||||||
{#if todo.completeBy}
|
{#if todo.completeBy}
|
||||||
{#if todo.completeBy === todayChild}
|
{#if todo.completeBy === todayChild}
|
||||||
<span class="todo-due due-today">DUE TODAY!</span>
|
<span class="todo-due due-today">DUE TODAY!</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="todo-due">due {formatHumanDate(todo.completeBy)}</span>
|
<span class="todo-due">due {formatHumanDate(todo.completeBy)}</span>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
</span>
|
</span>
|
||||||
<span class="todo-label">todo</span>
|
<span class="todo-label">todo</span>
|
||||||
<span class="chore-value">{todo.emoji || '🎯'}</span>
|
<span class="chore-value">{todo.emoji || '🎯'}</span>
|
||||||
</div>
|
</button>
|
||||||
{:else}
|
{:else}
|
||||||
<button
|
<button
|
||||||
class="chore todo-chore"
|
class="chore todo-chore"
|
||||||
@@ -1317,16 +1324,16 @@
|
|||||||
onclick={() => toggle(todo)}
|
onclick={() => toggle(todo)}
|
||||||
disabled={!!togglingIds}
|
disabled={!!togglingIds}
|
||||||
>
|
>
|
||||||
<span class="checkbox">{@html todoIcon}</span>
|
<span class="checkbox">{@html circleIcon}</span>
|
||||||
<span class="todo-main">
|
<span class="todo-main">
|
||||||
<span class="chore-name">{todo.customName || 'Todo'}</span>
|
<span class="chore-name">{todo.customName || 'Todo'}</span>
|
||||||
{#if todo.completeBy}
|
{#if todo.completeBy}
|
||||||
{#if todo.completeBy === todayChild}
|
{#if todo.completeBy === todayChild}
|
||||||
<span class="todo-due due-today">DUE TODAY!</span>
|
<span class="todo-due due-today">DUE TODAY!</span>
|
||||||
{:else}
|
{:else}
|
||||||
<span class="todo-due">due {formatHumanDate(todo.completeBy)}</span>
|
<span class="todo-due">due {formatHumanDate(todo.completeBy)}</span>
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{/if}
|
|
||||||
</span>
|
</span>
|
||||||
<span class="todo-label">todo</span>
|
<span class="todo-label">todo</span>
|
||||||
<span class="chore-value">
|
<span class="chore-value">
|
||||||
@@ -1350,7 +1357,11 @@
|
|||||||
<button class="chore done" onclick={() => toggle(chore)} disabled={!!togglingIds}>
|
<button class="chore done" onclick={() => toggle(chore)} disabled={!!togglingIds}>
|
||||||
<span class="checkbox">{@html checkCircleIcon}</span>
|
<span class="checkbox">{@html checkCircleIcon}</span>
|
||||||
<span class="chore-name">{choreName(chore)}</span>
|
<span class="chore-name">{choreName(chore)}</span>
|
||||||
<span class="chore-value">{chore.value} {chore.type}</span>
|
{#if chore.type === 'emoji'}
|
||||||
|
<span class="chore-value">{chore.emoji || '🎯'}</span>
|
||||||
|
{:else}
|
||||||
|
<span class="chore-value">{chore.value} {chore.type}</span>
|
||||||
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
{/if}
|
{/if}
|
||||||
{/each}
|
{/each}
|
||||||
@@ -1362,7 +1373,7 @@
|
|||||||
<!-- WALLET / CLAIMS -->
|
<!-- WALLET / CLAIMS -->
|
||||||
<div class="wallet">
|
<div class="wallet">
|
||||||
<div class="wallet-top">
|
<div class="wallet-top">
|
||||||
<span class="wallet-lbl">{@html moneyIcon} Wallet</span>
|
<span class="wallet-lbl">{@html walletIcon} Wallet</span>
|
||||||
{#if allTimeCash > 0 || allTimePoints > 0}
|
{#if allTimeCash > 0 || allTimePoints > 0}
|
||||||
<span class="wallet-total">
|
<span class="wallet-total">
|
||||||
<span class="wallet-cash">£{allTimeCash.toFixed(2)}</span>
|
<span class="wallet-cash">£{allTimeCash.toFixed(2)}</span>
|
||||||
@@ -1906,6 +1917,11 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: rgba(255, 255, 255, 0.85);
|
color: rgba(255, 255, 255, 0.85);
|
||||||
}
|
}
|
||||||
|
.hero-weekday {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: rgba(255, 255, 255, 0.85);
|
||||||
|
}
|
||||||
.hero-progress {
|
.hero-progress {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -2018,8 +2034,9 @@
|
|||||||
}
|
}
|
||||||
.hero-nav-btn.cur {
|
.hero-nav-btn.cur {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background: #fff;
|
background: transparent;
|
||||||
color: #6d28d9;
|
color: #fff;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ── Kanban scroll for parent view cards ── */
|
/* ── Kanban scroll for parent view cards ── */
|
||||||
@@ -2334,17 +2351,6 @@
|
|||||||
opacity: 0.55;
|
opacity: 0.55;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.todo-display {
|
|
||||||
cursor: default;
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
.todo-display:hover {
|
|
||||||
transform: none;
|
|
||||||
box-shadow: none;
|
|
||||||
border-top-color: #e5e7eb;
|
|
||||||
border-right-color: #e5e7eb;
|
|
||||||
border-bottom-color: #e5e7eb;
|
|
||||||
}
|
|
||||||
.chore:disabled {
|
.chore:disabled {
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
@@ -2364,14 +2370,6 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: -0.2em;
|
vertical-align: -0.2em;
|
||||||
}
|
}
|
||||||
.checkbox.empty {
|
|
||||||
display: inline-block;
|
|
||||||
width: 1.15em;
|
|
||||||
height: 1.15em;
|
|
||||||
border: 2px solid currentColor;
|
|
||||||
border-radius: 4px;
|
|
||||||
vertical-align: -0.2em;
|
|
||||||
}
|
|
||||||
.chore-name {
|
.chore-name {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
|
|||||||
@@ -16,9 +16,7 @@ export const actions = {
|
|||||||
|
|
||||||
let authResult: { token: string; record: any };
|
let authResult: { token: string; record: any };
|
||||||
try {
|
try {
|
||||||
authResult = await createPbClient()
|
authResult = await createPbClient().collection('users').authWithPassword(email, password);
|
||||||
.collection('users')
|
|
||||||
.authWithPassword(email, password);
|
|
||||||
} catch {
|
} catch {
|
||||||
return fail(400, { error: 'Invalid email or password', email });
|
return fail(400, { error: 'Invalid email or password', email });
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,8 @@
|
|||||||
|
|
||||||
// use:enhance is two-stage: this factory receives submit params (no result
|
// use:enhance is two-stage: this factory receives submit params (no result
|
||||||
// yet), and the RESOLVE function it returns receives { result, formData }.
|
// yet), and the RESOLVE function it returns receives { result, formData }.
|
||||||
const handleChoose = () =>
|
const handleChoose =
|
||||||
|
() =>
|
||||||
async ({ result, formData }: any) => {
|
async ({ result, formData }: any) => {
|
||||||
if (result?.type === 'success' && result.data) {
|
if (result?.type === 'success' && result.data) {
|
||||||
const d = result.data as { clientSecret?: string };
|
const d = result.data as { clientSecret?: string };
|
||||||
@@ -59,7 +60,9 @@
|
|||||||
|
|
||||||
<div class="shell">
|
<div class="shell">
|
||||||
<header class="topbar">
|
<header class="topbar">
|
||||||
<a class="brand" href="/"><span class="brand-icon">{@html homeIcon}</span> <strong>FamChore</strong></a>
|
<a class="brand" href="/"
|
||||||
|
><span class="brand-icon">{@html homeIcon}</span> <strong>FamChore</strong></a
|
||||||
|
>
|
||||||
<nav>
|
<nav>
|
||||||
<a href="/login" class="nav-link">Log in</a>
|
<a href="/login" class="nav-link">Log in</a>
|
||||||
<a href="/signup" class="nav-cta">Get started</a>
|
<a href="/signup" class="nav-cta">Get started</a>
|
||||||
@@ -78,13 +81,17 @@
|
|||||||
<div class="checkout-card">
|
<div class="checkout-card">
|
||||||
<div class="checkout-head">
|
<div class="checkout-head">
|
||||||
<h2>Checkout — {checkoutTitle}</h2>
|
<h2>Checkout — {checkoutTitle}</h2>
|
||||||
<Button variant="ghost" size="sm" onclick={resetCheckout}>{@html chevronLeftIcon} Back to plans</Button>
|
<Button variant="ghost" size="sm" onclick={resetCheckout}
|
||||||
|
>{@html chevronLeftIcon} Back to plans</Button
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div bind:this={checkoutEl} class="checkout-host"></div>
|
<div bind:this={checkoutEl} class="checkout-host"></div>
|
||||||
<p class="hint">
|
<p class="hint">
|
||||||
You can close and go to your dashboard any time — access unlocks once payment completes.
|
You can close and go to your dashboard any time — access unlocks once payment completes.
|
||||||
</p>
|
</p>
|
||||||
<a href={data.famSlug ? `/${data.famSlug}` : '/'} class="dash-link">Go to dashboard {@html chevronRightIcon}</a>
|
<a href={data.famSlug ? `/${data.famSlug}` : '/'} class="dash-link"
|
||||||
|
>Go to dashboard {@html chevronRightIcon}</a
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
Reference in New Issue
Block a user