···
last_used_at: number | null;
30
+
interface Subscription {
33
+
current_period_start: number | null;
34
+
current_period_end: number | null;
35
+
cancel_at_period_end: number;
36
+
canceled_at: number | null;
type SettingsPage = "account" | "sessions" | "passkeys" | "billing" | "danger";
@customElement("user-settings")
···
@state() user: User | null = null;
@state() sessions: Session[] = [];
@state() passkeys: Passkey[] = [];
46
+
@state() subscription: Subscription | null = null;
@state() loadingSessions = true;
@state() loadingPasskeys = true;
50
+
@state() loadingSubscription = true;
@state() showDeleteConfirm = false;
@state() currentPage: SettingsPage = "account";
···
206
+
background: var(--primary);
208
+
border-color: var(--primary);
211
+
.btn-affirmative:hover:not(:disabled) {
212
+
background: transparent;
213
+
color: var(--primary);
217
+
background: var(--success);
219
+
border-color: var(--success);
222
+
.btn-success:hover:not(:disabled) {
223
+
background: transparent;
224
+
color: var(--success);
···
this.passkeySupported = isPasskeySupported();
await this.loadSessions();
451
+
await this.loadSubscription();
if (this.passkeySupported) {
await this.loadPasskeys();
···
498
+
async loadSubscription() {
500
+
const response = await fetch("/api/billing/subscription");
503
+
const data = await response.json();
504
+
this.subscription = data.subscription;
507
+
this.loadingSubscription = false;
async handleAddPasskey() {
this.addingPasskey = true;
···
const { url } = await response.json();
676
-
window.location.href = url;
723
+
window.open(url, "_blank");
this.error = "Failed to create checkout session";
···
731
+
async handleOpenPortal() {
732
+
this.loading = true;
736
+
const response = await fetch("/api/billing/portal", {
738
+
headers: { "Content-Type": "application/json" },
741
+
if (!response.ok) {
742
+
const data = await response.json();
743
+
this.error = data.error || "Failed to open customer portal";
747
+
const { url } = await response.json();
748
+
window.open(url, "_blank");
750
+
this.error = "Failed to open customer portal";
752
+
this.loading = false;
// Generate a random string for the avatar
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
···
1152
+
if (this.loadingSubscription) {
1154
+
<div class="content-inner">
1155
+
<div class="section">
1156
+
<div class="loading">Loading subscription...</div>
1162
+
const hasActiveSubscription = this.subscription && (
1163
+
this.subscription.status === "active" ||
1164
+
this.subscription.status === "trialing"
1167
+
if (this.subscription && !hasActiveSubscription) {
1168
+
// Has a subscription but it's not active (canceled, expired, etc.)
1169
+
const statusColor =
1170
+
this.subscription.status === "canceled" ? "var(--accent)" :
1171
+
"var(--secondary)";
1174
+
<div class="content-inner">
1175
+
<div class="section">
1176
+
<h2 class="section-title">Subscription</h2>
1178
+
<div class="field-group">
1179
+
<label class="field-label">Status</label>
1180
+
<div style="display: flex; align-items: center; gap: 0.75rem;">
1182
+
display: inline-block;
1183
+
padding: 0.25rem 0.75rem;
1184
+
border-radius: 4px;
1185
+
background: ${statusColor};
1186
+
color: var(--white);
1187
+
font-size: 0.875rem;
1189
+
text-transform: uppercase;
1191
+
${this.subscription.status}
1196
+
${this.subscription.canceled_at ? html`
1197
+
<div class="field-group">
1198
+
<label class="field-label">Canceled At</label>
1199
+
<div class="field-value" style="color: var(--accent);">
1200
+
${this.formatDate(this.subscription.canceled_at)}
1205
+
<div class="field-group" style="margin-top: 2rem;">
1207
+
class="btn btn-success"
1208
+
@click=${this.handleCreateCheckout}
1209
+
?disabled=${this.loading}
1211
+
${this.loading ? "Loading..." : "Activate Your Subscription"}
1213
+
<p class="field-description" style="margin-top: 0.75rem;">
1214
+
Reactivate your subscription to unlock unlimited transcriptions.
1218
+
${this.error ? html`<p class="error" style="margin-top: 1rem;">${this.error}</p>` : ""}
1224
+
if (hasActiveSubscription) {
1226
+
<div class="content-inner">
1227
+
<div class="section">
1228
+
<h2 class="section-title">Subscription</h2>
1230
+
<div class="field-group">
1231
+
<label class="field-label">Status</label>
1232
+
<div style="display: flex; align-items: center; gap: 0.75rem;">
1234
+
display: inline-block;
1235
+
padding: 0.25rem 0.75rem;
1236
+
border-radius: 4px;
1237
+
background: var(--success);
1238
+
color: var(--white);
1239
+
font-size: 0.875rem;
1241
+
text-transform: uppercase;
1243
+
${this.subscription.status}
1245
+
${this.subscription.cancel_at_period_end ? html`
1246
+
<span style="color: var(--accent); font-size: 0.875rem;">
1247
+
(Cancels at end of period)
1253
+
${this.subscription.current_period_start && this.subscription.current_period_end ? html`
1254
+
<div class="field-group">
1255
+
<label class="field-label">Current Period</label>
1256
+
<div class="field-value">
1257
+
${this.formatDate(this.subscription.current_period_start)} -
1258
+
${this.formatDate(this.subscription.current_period_end)}
1263
+
<div class="field-group" style="margin-top: 2rem;">
1265
+
class="btn btn-affirmative"
1266
+
@click=${this.handleOpenPortal}
1267
+
?disabled=${this.loading}
1269
+
${this.loading ? "Loading..." : "Manage Subscription"}
1271
+
<p class="field-description" style="margin-top: 0.75rem;">
1272
+
Opens the customer portal where you can update payment methods, view invoices, and manage your subscription.
1276
+
${this.error ? html`<p class="error" style="margin-top: 1rem;">${this.error}</p>` : ""}
<div class="content-inner">
<h2 class="section-title">Billing & Subscription</h2>
<p class="field-description" style="margin-bottom: 1.5rem;">
1085
-
Manage your subscription and billing information.
1287
+
Activate your subscription to unlock unlimited transcriptions. Note: We currently offer a single subscription tier.
1088
-
class="btn btn-affirmative"
1290
+
class="btn btn-success"
@click=${this.handleCreateCheckout}
?disabled=${this.loading}
1092
-
${this.loading ? "Loading..." : "Subscribe to Premium"}
1294
+
${this.loading ? "Loading..." : "Activate Your Subscription"}
${this.error ? html`<p class="error" style="margin-top: 1rem;">${this.error}</p>` : ""}