Merge branch 'main' into cron-minutely

This commit is contained in:
mplorentz 2026-08-27 15:23:24 -04:00
commit bb1a7982dc
5 changed files with 60 additions and 19 deletions

View file

@ -39,6 +39,7 @@ const sample = {
}, },
], ],
unsubscribeUrl: 'https://app.flotilla.social/unsubscribe?token=abc123', unsubscribeUrl: 'https://app.flotilla.social/unsubscribe?token=abc123',
settingsUrl: 'https://app.flotilla.social/settings/alerts',
} }
const source = readFileSync(templatePath, 'utf8') const source = readFileSync(templatePath, 'utf8')

View file

@ -104,6 +104,7 @@ export class Digest {
for (const pk of pubkeys) { for (const pk of pubkeys) {
try { try {
await loadProfile(pk) await loadProfile(pk)
await waitForProfile(pk)
} catch { } catch {
// pass // pass
} }
@ -176,3 +177,17 @@ const getFormatter = () => {
timeZone: 'UTC', timeZone: 'UTC',
}) })
} }
// Poll until the profile lands in the repository or hits a timeout. The load
// promise can resolve before sharedLoad writes the event to the store, so only
// the profile's presence here guarantees its avatar/name.
const waitForProfile = async (pubkey: string, timeoutMs = 5000) => {
const deadline = Date.now() + timeoutMs
while (Date.now() < deadline) {
if (profilesByPubkey.get().get(pubkey)) {
return
}
await new Promise(r => setTimeout(r, 200))
}
}

View file

@ -19,6 +19,7 @@
.event-stats { margin-top: 8px; color: #64748b; font-size: 13px; } .event-stats { margin-top: 8px; color: #64748b; font-size: 13px; }
.stat-item { display: inline-flex; align-items: center; margin-right: 12px; } .stat-item { display: inline-flex; align-items: center; margin-right: 12px; }
.footer { font-family: Inter, Helvetica, Arial, sans-serif; color: #94a3b8; font-size: 12px; line-height: 1.5; } .footer { font-family: Inter, Helvetica, Arial, sans-serif; color: #94a3b8; font-size: 12px; line-height: 1.5; }
.footer a { color: #7161FF; text-decoration: underline; }
.logo { max-width: 48px; max-height: 48px; } .logo { max-width: 48px; max-height: 48px; }
a { text-decoration: none; } a { text-decoration: none; }
</mj-style> </mj-style>
@ -57,7 +58,7 @@
<span class="event-timestamp">at {{Timestamp}}</span> <span class="event-timestamp">at {{Timestamp}}</span>
</div> </div>
<div class="event-link"> <div class="event-link">
<a href="{{Link}}">View in {{brandName}}</a> <a href="{{Link}}">Open</a>
</div> </div>
</div> </div>
<div class="event-content">{{{Content}}}</div> <div class="event-content">{{{Content}}}</div>
@ -81,11 +82,8 @@
<mj-divider border-color="#e2e8f0" /> <mj-divider border-color="#e2e8f0" />
<mj-text css-class="footer"> <mj-text css-class="footer">
You're receiving this email because you subscribed to {{brandName}} notifications. You're receiving this email because you subscribed to {{brandName}} notifications.
We'll send you updates based on your subscription preferences. You can manage your subscriptions <a href="{{settingsUrl}}">here</a> or <a href="{{unsubscribeUrl}}">unsubscribe</a>.
</mj-text> </mj-text>
<mj-button background-color="{{brandAccent}}" color="#ffffff" href="{{unsubscribeUrl}}" padding-top="16px">
Unsubscribe
</mj-button>
</mj-column> </mj-column>
</mj-section> </mj-section>
</mj-body> </mj-body>

View file

@ -86,6 +86,7 @@ export const sendDigest = async (sub: Subscription, variables: Record<string, an
brandName: BRAND_NAME, brandName: BRAND_NAME,
brandAccent: BRAND_ACCENT, brandAccent: BRAND_ACCENT,
brandLogo: BRAND_LOGO, brandLogo: BRAND_LOGO,
settingsUrl: `${EVENT_VIEWER_URL.replace(/\/$/, '')}/settings/alerts`,
}), }),
}) })
.catch(error => { .catch(error => {

View file

@ -1,36 +1,62 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Confirmation Successful</title> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Email Confirmed</title>
<style> <style>
* { box-sizing: border-box; }
body { body {
font-family: system-ui, sans-serif; font-family: 'Inter', system-ui, -apple-system, sans-serif;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-height: 100vh; min-height: 100vh;
margin: 0; margin: 0;
background: #f5f5f5; background: #f0f2f5;
color: #1e293b;
} }
.container { .container {
width: 100%;
max-width: 480px;
margin: 16px;
text-align: center; text-align: center;
padding: 2rem; padding: 40px 32px;
background: white; background: #ffffff;
border-radius: 8px; border-radius: 12px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1); border: 1px solid #e2e8f0;
}
.logo { height: 40px; width: auto; margin: 0 auto 16px; display: block; }
.brand {
font-size: 24px;
font-weight: 700;
margin: 0 0 28px;
color: {{brandAccent}};
}
.title {
font-size: 20px;
font-weight: 700;
margin: 0 0 12px;
color: #1e293b;
} }
.message { .message {
margin-top: 1rem; font-size: 15px;
color: #2ecc71; line-height: 1.6;
color: #64748b;
margin: 0;
} }
.message a { color: {{brandAccent}}; text-decoration: none; font-weight: 600; }
</style> </style>
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h2>Email Alert Confirmed</h2> {{#brandLogo}}<img class="logo" src="{{brandLogo}}" alt="{{brandName}}" />{{/brandLogo}}
<div class="message"> <div class="brand">{{brandName}}</div>
Your alert has been successfully confirmed. You will now receive notifications. <h1 class="title">Email confirmed</h1>
</div> <p class="message">
Your email has been successfully confirmed. You will start receiving notifications shortly.
Visit <a href="{{settingsUrl}}">{{brandName}}</a> to manage your notification settings.
</p>
</div> </div>
</body> </body>
</html> </html>