From 636b0323075225c584b62719ed51e75521bb7ffb Mon Sep 17 00:00:00 2001
From: aura <nw@moneybot.cc>
Date: Tue, 17 Feb 2026 22:39:42 +0100
Subject: push source

---
 moneyjsx/src/upgrade.tsx | 206 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 206 insertions(+)
 create mode 100644 moneyjsx/src/upgrade.tsx

(limited to 'moneyjsx/src/upgrade.tsx')

diff --git a/moneyjsx/src/upgrade.tsx b/moneyjsx/src/upgrade.tsx
new file mode 100644
index 0000000..ab29e34
--- /dev/null
+++ b/moneyjsx/src/upgrade.tsx
@@ -0,0 +1,206 @@
+import * as JSX from './jsx';
+import * as user from './user';
+import * as api from './api';
+import $ from 'jquery';
+
+import { Page, GroupBox, Spinner } from './components';
+
+let stripe = null;
+let card = null;
+let selected_product = 2;
+
+async function loadStripe() {
+  return new Promise( function (resolve, reject ) {
+    const s = document.createElement( 'script' );
+    s.src = 'https://js.stripe.com/v3/';
+    s.onload = resolve;
+    s.onerror = reject;
+    document.head.appendChild( s );
+  } );
+}
+
+function showError( text: string ) {
+  $( "#payment-error" ).show();
+  $( "#payment-error" ).text( text );
+}
+
+async function onFormSubmit( e: Event ) {
+  e.preventDefault();
+  const { paymentMethod, error } = await stripe.createPaymentMethod( {
+    type: 'card',
+    card
+  } );
+
+  if( error )
+    return showError( error.message );
+
+  const res = await api.post( 'create-payment-intent', {
+    token: localStorage.getItem( 'session' ),
+    paymentMethodId: paymentMethod.id,
+    subLength: selected_product
+  } );
+
+  if( res.status == 'ok' ) {
+    const intent = res.paymentIntent;
+    if( intent.status == 'succeeded' )
+      return JSX.navigateParams( '/payment-success', {} );
+    else
+      return showError( "Payment failed. Contact support if the issue persists." );
+  }
+  else if( res.msg )
+    return showError( "Error: " + res.msg );
+  else
+    return showError( "Payment failed. Contact support if the issue persists." );
+}
+
+function waitForStripe() {
+  const iframes = $( 'iframe' );
+  if( !iframes.length )
+    return setTimeout( waitForStripe, 50 );
+
+  for( let iframe of iframes ) {
+    if( iframe.attributes.getNamedItem( 'name' ).value == 'hcaptcha-invisible' ) {
+      $( '#upgrade-loading' ).remove();
+      $( '#upgrade-inner' ).show();
+
+      return;
+    }
+  }
+
+  setTimeout( waitForStripe, 50 );
+}
+
+async function initStripe() {
+  stripe = window['Stripe']( 'pk_live_51Q9JG602rmv2yeiGYNNfwFkqp5ntpXIDIMIoiwDoEOrB5IsC75WDy3XOqekvuwY6PecviSSo5ERt0umrdBdUtqhd00FyYZe5p3' );
+  const font = user.settings.site_prefs.font;
+
+  const params = {
+    mode: 'billing',
+    style: {
+      base: {
+        color: '#fff',
+        fontFamily: font,
+        fontSize: '15px',
+        '::placeholder': {
+          color: '#ccc',
+        },
+      },
+    },
+  }
+
+  const el = getComputedStyle( document.body );
+  const back = el.getPropertyValue( '--back' );
+  const front = el.getPropertyValue( '--front' );
+  const appearance = {
+    theme: 'stripe',
+    disableAnimations: true,
+
+    variables: {
+      colorPrimary: front,
+      colorBackground: back,
+      colorText: '#fff',
+      fontFamily: font,
+      fontSizeBase: '15px',
+      fontSizeSm: '13px',
+      spacingUnit: '2px',
+      borderRadius: '0px',
+    },
+    rules: {
+      ".Input": {
+        border: 'none'
+      }
+    }
+  }
+
+  const elements = stripe.elements( { appearance } );
+  const name = elements.create( 'address', { mode: 'billing', appearance } );
+  name.mount( '#name-element' );
+
+  card = elements.create( 'card', params );
+  card.mount( '#card-element' );
+}
+
+function onProductChange( e: Event ) {
+  selected_product = parseInt( (e.target as HTMLInputElement).value );
+}
+
+function SelectionRadio( params: any ) {
+  return <div style="display: flex">
+    { params.checked &&
+      <input type="radio"
+        name="product"
+        value={ params.value }
+        onChange={ onProductChange }
+        checked="checked"
+      />
+    }
+    { !params.checked &&
+      <input type="radio"
+        name="product"
+        value={ params.value }
+        onChange={ onProductChange }
+      />
+    }
+    <label class="radio-label" style="margin-left: 5px">
+      { params.text } <span style="color: var(--front)">-- ${ params.price }</span>
+    </label>
+  </div>
+}
+
+function UpgradeLoaded() {
+  return <GroupBox
+    title="Account upgrade"
+    style="width: 90%; max-width: 550px; display: none"
+    innerStyle="width: calc( 100% - 10px )"
+    id="upgrade-inner"
+  >
+    <div style="border-bottom: 1px solid var(--front);">
+      <h2 style="padding: 8px 0px; margin: 0px; font-size: 28px">Summary</h2>
+      <div style="width: 60%">
+        <h3 style="margin: 5px 0;">
+          Product:
+        </h3>
+        <h4 style="margin: 5px 0">
+          <span id="product-name">Axonbox premium account upgrade</span><br />
+          <SelectionRadio text="1 week" price="2.70" value="1"/>
+          <SelectionRadio text="1 month" price="10" value="2" checked="checked"/>
+          <SelectionRadio text="1 year" price="100" value="3"/>
+        </h4>
+      </div>
+    </div>
+    <div>
+      <h3 style="margin: 5px 0">
+        Payment information
+      </h3>
+    </div>
+    <form id="upgrade-form" method="POST" action="" onsubmit={ onFormSubmit }>
+      <div>
+        <label for="name-element">Contact information</label>
+        <div id="name-element"></div>
+        <label for="card-element">Credit or debit card</label>
+        <div id="card-element" style="padding: 10px 0"></div>
+        <div id="card-errors" role="alert"></div>
+      </div>
+      <div>
+        <span style="display:none" id='payment-error'></span>
+      </div>
+      <button type="submit">Submit</button>
+    </form>
+  </GroupBox>
+}
+
+export default function Upgrade() {
+  setTimeout( () => {
+    loadStripe().then( () => {
+      $( <UpgradeLoaded /> ).insertAfter( "#upgrade-loading" );
+      initStripe();
+      waitForStripe();
+    } );
+  } );
+
+  return <Page>
+    <GroupBox title="Account upgrade" id="upgrade-loading">
+      <span>Loading... &nbsp;<Spinner style="display: inline" /></span>
+    </GroupBox>
+  </Page>
+}
-- 
cgit v1.2.3

