fix: self-hosted useSend support + purchase-first activate UX
- Pass USESEND_BASE_URL to UseSend SDK for self-hosted instances - Add debug logging for email sends - Redesign activate page: prominent Purchase Now button, collapsible license key input
This commit is contained in:
@@ -8,6 +8,7 @@ import * as schema from './db/schema';
|
||||
import { sendEmail } from './email';
|
||||
|
||||
export const auth = betterAuth({
|
||||
baseURL: process.env.BETTER_AUTH_URL || 'http://localhost:5173',
|
||||
database: drizzleAdapter(db, {
|
||||
provider: 'pg',
|
||||
schema
|
||||
@@ -15,14 +16,16 @@ export const auth = betterAuth({
|
||||
plugins: [sveltekitCookies(getRequestEvent), apiKey()],
|
||||
emailVerification: {
|
||||
sendVerificationEmail: async ({ user, url }) => {
|
||||
console.log('[Email] sendVerificationEmail called for:', user.email, 'url:', url);
|
||||
try {
|
||||
await sendEmail({
|
||||
const result = await sendEmail({
|
||||
to: user.email,
|
||||
subject: 'Verify your DroidClaw email',
|
||||
text: `Hi ${user.name || 'there'},\n\nClick the link below to verify your email:\n\n${url}\n\nThis link expires in 1 hour.\n\n-- DroidClaw`
|
||||
});
|
||||
console.log('[Email] sendEmail result:', JSON.stringify(result));
|
||||
} catch (err) {
|
||||
console.error('Failed to send verification email:', err);
|
||||
console.error('[Email] Failed to send verification email:', err);
|
||||
}
|
||||
},
|
||||
sendOnSignUp: true,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import { env } from '$env/dynamic/private';
|
||||
import { UseSend } from 'usesend-js';
|
||||
|
||||
if (!env.USESEND_API_KEY) throw new Error('USESEND_API_KEY is not set');
|
||||
|
||||
const usesend = new UseSend(env.USESEND_API_KEY);
|
||||
|
||||
const EMAIL_FROM = 'noreply@app.droidclaw.ai';
|
||||
|
||||
function getClient() {
|
||||
if (!env.USESEND_API_KEY) throw new Error('USESEND_API_KEY is not set');
|
||||
return new UseSend(env.USESEND_API_KEY, env.USESEND_BASE_URL);
|
||||
}
|
||||
|
||||
export async function sendEmail({
|
||||
to,
|
||||
subject,
|
||||
@@ -16,7 +17,8 @@ export async function sendEmail({
|
||||
subject: string;
|
||||
text: string;
|
||||
}) {
|
||||
return usesend.emails.send({
|
||||
console.log('[Email] API key prefix:', env.USESEND_API_KEY?.slice(0, 15) + '...');
|
||||
return getClient().emails.send({
|
||||
to,
|
||||
from: EMAIL_FROM,
|
||||
subject,
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import { LICENSE_ACTIVATE_CHECKOUT, LICENSE_ACTIVATE_MANUAL, LICENSE_PURCHASE_CLICK } from '$lib/analytics/events';
|
||||
|
||||
const checkoutId = page.url.searchParams.get('checkout_id');
|
||||
|
||||
let showKeyInput = $state(false);
|
||||
</script>
|
||||
|
||||
{#if checkoutId}
|
||||
@@ -69,18 +71,42 @@
|
||||
</form>
|
||||
</div>
|
||||
{:else}
|
||||
<!-- Manual activation (no checkout ID) -->
|
||||
<!-- Purchase-first flow -->
|
||||
<div class="mx-auto max-w-md pt-20">
|
||||
<div class="mb-8 text-center">
|
||||
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-neutral-100">
|
||||
<Icon icon="ph:seal-check-duotone" class="h-6 w-6 text-neutral-600" />
|
||||
<div class="mx-auto mb-4 flex h-12 w-12 items-center justify-center rounded-xl bg-neutral-900">
|
||||
<Icon icon="ph:robot-duotone" class="h-6 w-6 text-white" />
|
||||
</div>
|
||||
<h2 class="text-2xl font-bold">Activate your license</h2>
|
||||
<p class="mt-1 text-neutral-500">
|
||||
Enter the license key you received after purchasing DroidClaw.
|
||||
<h2 class="text-2xl font-bold">Get started with DroidClaw</h2>
|
||||
<p class="mt-2 text-neutral-500">
|
||||
Unlock AI-powered Android device control.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
href="https://sandbox-api.polar.sh/v1/checkout-links/polar_cl_5pGavRIJJhM8ge6p0UaeaadT2bCiqL04CYXgW3bwVac/redirect"
|
||||
data-umami-event={LICENSE_PURCHASE_CLICK}
|
||||
class="flex w-full items-center justify-center gap-2 rounded-xl bg-neutral-900 px-4 py-3 text-base font-medium text-white hover:bg-neutral-800"
|
||||
>
|
||||
<Icon icon="ph:credit-card-duotone" class="h-5 w-5" />
|
||||
Purchase Now
|
||||
</a>
|
||||
|
||||
<div class="mt-10">
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => showKeyInput = !showKeyInput}
|
||||
class="flex w-full items-center justify-center gap-1.5 text-sm text-neutral-400 hover:text-neutral-600"
|
||||
>
|
||||
Already have a license key?
|
||||
<Icon
|
||||
icon="ph:caret-down"
|
||||
class="h-3.5 w-3.5 transition-transform {showKeyInput ? 'rotate-180' : ''}"
|
||||
/>
|
||||
</button>
|
||||
|
||||
{#if showKeyInput}
|
||||
<div class="mt-4">
|
||||
<form {...activateLicense} class="space-y-4">
|
||||
<label class="block">
|
||||
<span class="flex items-center gap-1.5 text-sm font-medium text-neutral-700">
|
||||
@@ -100,18 +126,14 @@
|
||||
<button
|
||||
type="submit"
|
||||
data-umami-event={LICENSE_ACTIVATE_MANUAL}
|
||||
class="flex w-full items-center justify-center gap-2 rounded-xl bg-neutral-900 px-4 py-2.5 font-medium text-white hover:bg-neutral-800"
|
||||
class="flex w-full items-center justify-center gap-2 rounded-xl border border-neutral-300 px-4 py-2.5 text-sm font-medium text-neutral-700 hover:bg-neutral-50"
|
||||
>
|
||||
<Icon icon="ph:seal-check-duotone" class="h-4 w-4" />
|
||||
Activate
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="mt-6 text-center text-sm text-neutral-400">
|
||||
Don't have a key?
|
||||
<a href="https://sandbox-api.polar.sh/v1/checkout-links/polar_cl_5pGavRIJJhM8ge6p0UaeaadT2bCiqL04CYXgW3bwVac/redirect" data-umami-event={LICENSE_PURCHASE_CLICK} class="font-medium text-neutral-700 underline hover:text-neutral-900">
|
||||
Purchase here
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user