Merge branch 'dev'
CI/CD / deploy (push) Failing after 2m8s
Details
CI/CD / deploy (push) Failing after 2m8s
Details
This commit is contained in:
commit
2e9dfef39f
|
|
@ -83,14 +83,14 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="icon" type="image/png" href="/favicon.png">
|
||||
<link rel="apple-touch-icon" href="/logo.png">
|
||||
<title>rSpace — Own Your Community Infrastructure</title>
|
||||
<meta name="description" content="rSpace is a local-first platform where communities own their tools, data, and governance. ${modules.length} composable apps — from voting to budgets to maps — encrypted, interoperable, and yours.">
|
||||
<title>rSpace — Reclaim (you)rSpace on the Internet</title>
|
||||
<meta name="description" content="rSpace is a local-first platform where groups coordinate around what they care about — without stitching together a dozen corporate apps. ${modules.length} composable tools, encrypted and yours.">
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://rspace.online">
|
||||
<meta property="og:title" content="rSpace — Own Your Community Infrastructure">
|
||||
<meta property="og:description" content="rSpace is a local-first platform where communities own their tools, data, and governance. ${modules.length} composable apps — from voting to budgets to maps — encrypted, interoperable, and yours.">
|
||||
<meta property="og:title" content="rSpace — Reclaim (you)rSpace on the Internet">
|
||||
<meta property="og:description" content="rSpace is a local-first platform where groups coordinate around what they care about — without stitching together a dozen corporate apps. ${modules.length} composable tools, encrypted and yours.">
|
||||
<meta property="og:image" content="https://rspace.online/og-image.png">
|
||||
<meta property="og:image:width" content="1200">
|
||||
<meta property="og:image:height" content="630">
|
||||
|
|
@ -98,8 +98,8 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
|
||||
<!-- Twitter -->
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="rSpace — Own Your Community Infrastructure">
|
||||
<meta name="twitter:description" content="Local-first community platform. ${modules.length} composable apps — voting, budgets, maps, payments, identity — encrypted and self-sovereign.">
|
||||
<meta name="twitter:title" content="rSpace — Reclaim (you)rSpace on the Internet">
|
||||
<meta name="twitter:description" content="One place for your group to plan, decide, fund, and build together. ${modules.length} composable tools — encrypted, local-first, yours.">
|
||||
<meta name="twitter:image" content="https://rspace.online/og-image.png">
|
||||
|
||||
<script>(function(){var t=localStorage.getItem('canvas-theme');if(!t)t=matchMedia('(prefers-color-scheme:light)').matches?'light':'dark';document.documentElement.setAttribute('data-theme',t)})()</script>
|
||||
|
|
@ -131,10 +131,10 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
<div class="lp-hero__orb lp-hero__orb--indigo" aria-hidden="true"></div>
|
||||
<div class="lp-hero__grid" aria-hidden="true"></div>
|
||||
<div class="lp-hero__content">
|
||||
<span class="rl-tagline">Local-first community platform</span>
|
||||
<span class="rl-tagline">Reclaim (you)<span style="color:#f97316">r</span><span style="color:#14b8a6">Space</span> on the internet</span>
|
||||
<h1 class="lp-wordmark"><span class="lp-wordmark__r">r</span><span class="lp-wordmark__space">Space</span></h1>
|
||||
<p class="lp-hero__tagline">
|
||||
One platform. ${modules.length} apps. All your community’s tools talking to each other.
|
||||
Coordinate around what you care about — without stitching together a dozen corporate apps.
|
||||
</p>
|
||||
<div class="lp-hero__ctas">
|
||||
<a href="${demoUrl}" class="lp-btn lp-btn--primary" id="ml-primary">Start your Space →</a>
|
||||
|
|
@ -161,9 +161,9 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
<!-- 3. Flow Stories -->
|
||||
<section class="rl-section">
|
||||
<div class="rl-container">
|
||||
<h2 class="lp-section-heading">One Platform. Every Tool Connected.</h2>
|
||||
<h2 class="lp-section-heading">Your Group. One Shared Workspace.</h2>
|
||||
<p class="rl-subtext" style="text-align:center">
|
||||
rApps share one sync layer. Data flows automatically — no import/export rituals.
|
||||
Everything your group needs lives in one place. Data flows between tools automatically — no copy-pasting between apps.
|
||||
</p>
|
||||
<div class="lp-flows">
|
||||
<div class="lp-flow">
|
||||
|
|
@ -293,10 +293,10 @@ export function renderMainLanding(modules: ModuleInfo[]): string {
|
|||
<!-- 7. Final CTA -->
|
||||
<section class="lp-final-cta">
|
||||
<div class="rl-container" style="max-width:720px;text-align:center">
|
||||
<h2 class="lp-final-cta__heading">Your community. Your rules. Your data.</h2>
|
||||
<h2 class="lp-final-cta__heading">Reclaim (you)<span style="color:#f97316;-webkit-text-fill-color:#f97316">r</span><span style="color:#14b8a6;-webkit-text-fill-color:#14b8a6">Space</span>.</h2>
|
||||
<p class="rl-subtext" style="font-size:1.15rem;line-height:1.7;text-align:center">
|
||||
No algorithms deciding what you see. No ads. No data harvesting.
|
||||
Just tools that work for you, run by you, owned by you.
|
||||
Just one place for your group to plan, decide, fund, and build together.
|
||||
</p>
|
||||
<div class="lp-hero__ctas">
|
||||
<a href="${demoUrl}" class="lp-btn lp-btn--primary">Start your Space →</a>
|
||||
|
|
@ -609,8 +609,8 @@ body {
|
|||
}
|
||||
.lp-wordmark__r {
|
||||
font-weight: 400;
|
||||
color: var(--rs-text-primary);
|
||||
-webkit-text-fill-color: unset;
|
||||
color: #f97316;
|
||||
-webkit-text-fill-color: #f97316;
|
||||
}
|
||||
.lp-wordmark__space {
|
||||
background: var(--rs-gradient-brand);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* Welcome Email — sent once when a user first connects their email address.
|
||||
*/
|
||||
|
||||
import { getSmtpTransport } from "./notification-service";
|
||||
|
||||
export async function sendWelcomeEmail(email: string, username: string): Promise<void> {
|
||||
const transport = await getSmtpTransport();
|
||||
if (!transport) {
|
||||
console.warn("[welcome-email] No SMTP transport available");
|
||||
return;
|
||||
}
|
||||
|
||||
const displayName = username || "there";
|
||||
const demoUrl = "https://demo.rspace.online";
|
||||
const createUrl = "https://rspace.online/create";
|
||||
|
||||
const html = `
|
||||
<div style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 520px; margin: 0 auto; padding: 24px;">
|
||||
<div style="background: #1e293b; border-radius: 12px; padding: 28px; color: #e2e8f0;">
|
||||
<h1 style="margin: 0 0 4px; font-size: 22px; color: #f1f5f9;">
|
||||
Welcome to <span style="color: #f97316;">r</span><span style="color: #14b8a6;">Space</span>, ${escapeHtml(displayName)}!
|
||||
</h1>
|
||||
<p style="margin: 0 0 24px; font-size: 15px; color: #94a3b8;">
|
||||
Reclaim (you)<span style="color: #f97316;">r</span><span style="color: #14b8a6;">Space</span> on the internet — one place for your group to coordinate around what you care about.
|
||||
</p>
|
||||
|
||||
<div style="background: #0f172a; border-radius: 8px; padding: 16px; margin-bottom: 20px;">
|
||||
<p style="margin: 0 0 12px; font-size: 14px; color: #e2e8f0; line-height: 1.6;">
|
||||
Instead of scattering your group across Slack, Google Docs, Trello, Zoom, Splitwise, and a dozen other apps —
|
||||
<strong style="color: #14b8a6;">(you)rSpace puts it all in one shared workspace</strong> that your group actually owns.
|
||||
</p>
|
||||
<p style="margin: 0; font-size: 14px; color: #94a3b8; line-height: 1.6;">
|
||||
Plan together. Decide together. Fund together. Build together. No corporate middlemen.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p style="margin: 0 0 12px; font-size: 13px; color: #94a3b8; text-transform: uppercase; letter-spacing: 0.05em;">How groups use rSpace</p>
|
||||
<table style="width: 100%; border-collapse: collapse; margin-bottom: 20px;">
|
||||
<tr>
|
||||
<td style="padding: 8px; font-size: 14px; color: #e2e8f0; line-height: 1.5; border-bottom: 1px solid #334155;">
|
||||
<strong style="color: #f1f5f9;">Plan & Decide</strong><br>
|
||||
<span style="color: #94a3b8; font-size: 13px;">Schedule, vote, set priorities — decisions flow into tasks automatically</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; font-size: 14px; color: #e2e8f0; line-height: 1.5; border-bottom: 1px solid #334155;">
|
||||
<strong style="color: #f1f5f9;">Create & Share</strong><br>
|
||||
<span style="color: #94a3b8; font-size: 13px;">Docs, maps, data — all synced, all encrypted, all yours</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; font-size: 14px; color: #e2e8f0; line-height: 1.5; border-bottom: 1px solid #334155;">
|
||||
<strong style="color: #f1f5f9;">Fund & Sustain</strong><br>
|
||||
<span style="color: #94a3b8; font-size: 13px;">Shared wallets, resource flows, transparent budgets</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px; font-size: 14px; color: #e2e8f0; line-height: 1.5;">
|
||||
<strong style="color: #f1f5f9;">Stay Connected</strong><br>
|
||||
<span style="color: #94a3b8; font-size: 13px;">Chat, meet, coordinate — without giving your conversations to ad companies</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div style="background: #0f172a; border-radius: 8px; padding: 12px 16px; margin-bottom: 20px;">
|
||||
<p style="margin: 0; font-size: 13px; color: #94a3b8; line-height: 1.5;">
|
||||
🔐 <strong style="color: #e2e8f0;">Your identity is yours.</strong>
|
||||
One passkey — no passwords, no seeds, no email loops. Works everywhere, owned by nobody but you.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<a href="${demoUrl}" style="display: inline-block; padding: 10px 22px; background: linear-gradient(135deg, #14b8a6, #0d9488); color: white; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 600; margin-right: 8px;">Explore the Demo Space</a>
|
||||
<a href="${createUrl}" style="display: inline-block; padding: 10px 22px; background: transparent; color: #14b8a6; text-decoration: none; border-radius: 6px; font-size: 14px; font-weight: 600; border: 1px solid #14b8a6;">Create Your Space</a>
|
||||
</div>
|
||||
</div>
|
||||
<p style="margin: 14px 0 0; font-size: 11px; color: #64748b; text-align: center;">
|
||||
You can manage your email in profile settings at any time.
|
||||
</p>
|
||||
</div>`;
|
||||
|
||||
try {
|
||||
await transport.sendMail({
|
||||
from: "rSpace <hello@rspace.online>",
|
||||
to: email,
|
||||
subject: `Welcome to rSpace, ${displayName} — your group's new home`,
|
||||
html,
|
||||
replyTo: "hello@rspace.online",
|
||||
});
|
||||
console.log(`[welcome-email] Sent to ${email}`);
|
||||
} catch (err: any) {
|
||||
console.error(`[welcome-email] Failed to send to ${email}:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(s: string): string {
|
||||
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
||||
}
|
||||
|
|
@ -160,6 +160,7 @@ import {
|
|||
aliasExists,
|
||||
} from './mailcow.js';
|
||||
import { notify } from '../../server/notification-service';
|
||||
import { sendWelcomeEmail } from '../../server/welcome-email';
|
||||
import { startTrustEngine } from './trust-engine.js';
|
||||
import { provisionSpaceAlias, syncSpaceAlias, deprovisionSpaceAlias, provisionAgentMailbox, deprovisionAgentMailbox } from './space-alias-service.js';
|
||||
|
||||
|
|
@ -1231,9 +1232,15 @@ app.put('/api/user/profile', async (c) => {
|
|||
if (body.profileEmailIsRecovery !== undefined) updates.profileEmailIsRecovery = body.profileEmailIsRecovery;
|
||||
if (body.walletAddress !== undefined) updates.walletAddress = body.walletAddress;
|
||||
|
||||
const oldProfile = await getUserProfile(claims.sub as string);
|
||||
const profile = await updateUserProfile(claims.sub as string, updates);
|
||||
if (!profile) return c.json({ error: 'User not found' }, 404);
|
||||
|
||||
// Send welcome email on first email connect
|
||||
if (updates.profileEmail && !oldProfile?.profileEmail) {
|
||||
sendWelcomeEmail(updates.profileEmail, profile.username).catch(() => {});
|
||||
}
|
||||
|
||||
// If profile email changed and forwarding is active, update/disable the alias
|
||||
if (updates.profileEmail !== undefined && isMailcowConfigured()) {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in New Issue