Diff -- /var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/signup.cgi
Diff

/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com/signup.cgi

added on local at 2026-07-01 13:47:42

Added
+209
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 490caa895590
Restore this content →
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.
1#!/usr/bin/perl
2#======================================================================
3# WebSTLs — signup
4# Page body lives in TEMPLATES/webstls_signup.html
5#
6# GET : show the signup form
7# POST : create user, mint session, set cookie, 302 to /dashboard.cgi
8# on failure, re-render the form with an error
9#======================================================================
10use strict;
11use warnings;
12
13use lib '/var/www/vhosts/3dshawn.com/affiliate.3dshawn.com';
14use CGI;
15use MODS::Template;
16use MODS::DBConnect;
17use MODS::Login;
18use MODS::AffSoft::Config;
19use MODS::AffSoft::Wrapper;
20use MODS::AffSoft::Billing;
21use MODS::AffSoft::EmailVerify;
22
23my $q = CGI->new;
24my $form = $q->Vars;
25my $auth = MODS::Login->new;
26my $wrap = MODS::AffSoft::Wrapper->new;
27my $tfile = MODS::Template->new;
28my $db = MODS::DBConnect->new;
29my $cfg = MODS::AffSoft::Config->new;
30my $bill = MODS::AffSoft::Billing->new;
31my $DB = $cfg->settings('database_name');
32
33$|=1;
34
35if (my $existing = $auth->login_verify()) {
36 print "Status: 302 Found\nLocation: /dashboard.cgi\n\n";
37 exit;
38}
39
40my $error_msg = '';
41my $email_val = '';
42my $name_val = '';
43# Default to the plan slug passed via ?plan= from the pricing cards.
44my $plan_val = $form->{plan} || $form->{plan_tier} || 'pro';
45$plan_val =~ s/[^a-z0-9_\-]//gi;
46$plan_val = lc $plan_val;
47
48if (($ENV{REQUEST_METHOD} || '') eq 'POST') {
49 my $email = $form->{email} || '';
50 my $pass = $form->{password} || '';
51 my $name = $form->{display_name} || '';
52 my $plan = $form->{plan_tier} || 'free';
53
54 $email_val = _h($email);
55 $name_val = _h($name);
56 $plan_val = _h($plan);
57
58 if ($email !~ /^[^\@\s]+\@[^\@\s]+\.[^\@\s]+$/) {
59 $error_msg = 'Please enter a valid email address.';
60 } elsif (length($pass) < 8) {
61 $error_msg = 'Password must be at least 8 characters.';
62 } else {
63 my $user_id = $auth->signup($email, $pass, $name, $plan);
64 if ($user_id) {
65 # Seed a sample affiliate program so the dashboard isn't empty.
66 eval {
67 require MODS::AffSoft::Onboarding;
68 my $dbh3 = $db->db_connect();
69 MODS::AffSoft::Onboarding->new->seed_sample_data($db, $dbh3, $DB, $user_id);
70 $db->db_disconnect($dbh3);
71 };
72
73 # Fire-and-forget the verification email. The user is
74 # signed in immediately either way (we don't gate the
75 # platform behind verification); a bounce or mailer-down
76 # condition just means they'll see a "Resend verification"
77 # button on /profile.cgi until they confirm. Wrapped in
78 # eval so a SMTP outage never blocks signup.
79 eval {
80 my $dbh2 = $db->db_connect();
81 MODS::AffSoft::EmailVerify->send_for_user($db, $dbh2, $DB, {
82 id => $user_id,
83 email => $email,
84 display_name => $name,
85 });
86 $db->db_disconnect($dbh2);
87 };
88
89 my ($userinfo, $token) = $auth->login_exec($email, $pass);
90 if ($userinfo && $token) {
91 print $auth->set_cookie_header($token);
92 # Brand-new accounts land on /dashboard.cgi -- the
93 # in-app landing with KPIs, programs, and activity.
94 print "Status: 302 Found\nLocation: /dashboard.cgi\n\n";
95 exit;
96 }
97 $error_msg = 'Account created — please sign in.';
98 } else {
99 $error_msg = 'That email is already in use, or an account error occurred.';
100 }
101 }
102}
103
104print "Content-Type: text/html; charset=utf-8\nCache-Control: no-store, no-cache, must-revalidate, max-age=0\nPragma: no-cache\nExpires: 0\n\n";
105
106# Build the plan <option> list from billing_plans (admin-managed). One
107# entry per unique tier; we prefer the monthly price for the label.
108# Falls back to a built-in set when the schema isn't migrated.
109my @plan_options = _build_plan_options($db, $bill, $plan_val);
110
111my $tvars = {
112 error_msg => $error_msg,
113 email_val => $email_val,
114 name_val => $name_val,
115 plan_options => \@plan_options,
116};
117
118my $body = join('', $tfile->template('webstls_signup.html', $tvars, undef));
119
120
121# === Maintenance lockdown overlay (auto-injected 2026-06-23) ===
122# When maintenance_locked=1, render a modal over the login form.
123# X-close stays visible so an admin can sign in. Non-admin login
124# attempts fail and the page reloads, restoring the overlay.
125{
126 my $_mcfg = MODS::AffSoft::Config->new;
127 if ($_mcfg->settings('maintenance_locked')) {
128 my $_mmsg = $_mcfg->settings('maintenance_message') || '<p>The system is in maintenance mode.</p>';
129 my $_overlay = qq{<div id="maint-overlay" style="position:fixed;inset:0;background:rgba(0,0,0,0.88);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);z-index:9999;display:flex;align-items:center;justify-content:center;padding:24px"><div style="position:relative;background:#0f0f0f;border:1px solid rgba(217,119,6,0.6);border-radius:12px;max-width:680px;width:100%;padding:48px 40px;box-shadow:0 30px 90px rgba(0,0,0,0.6),0 0 0 1px rgba(217,119,6,0.15)"><button type="button" aria-label="Close" onclick="document.getElementById('maint-overlay').style.display='none'" style="position:absolute;top:14px;right:14px;width:36px;height:36px;background:transparent;border:1px solid rgba(255,255,255,0.12);color:#aaa;font-size:22px;line-height:1;border-radius:8px;cursor:pointer">&times;</button>$_mmsg</div><script>document.addEventListener('keydown',function(e){if(e.key==='Escape'){var o=document.getElementById('maint-overlay');if(o)o.style.display='none';}});</script></div>};
130 $body = $_overlay . $body;
131 }
132}
133# === End maintenance lockdown overlay ===
134$wrap->render({
135 userinfo => undef,
136 title => 'Start your trial',
137 body => $body,
138});
139
140#----------------------------------------------------------------------
141sub _h {
142 my $s = shift // '';
143 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g;
144 $s =~ s/"/&quot;/g; $s =~ s/'/&#39;/g;
145 return $s;
146}
147
148# Build the <option> list for the plan dropdown. Reads billing_plans
149# (preferring the monthly row of each tier for the label price), and
150# pre-marks the matching tier with selected_attr. Connection-safe:
151# opens its own connection so signup pages on databases without the
152# billing schema still render via the fallback list.
153sub _build_plan_options {
154 my ($db, $bill, $current_tier) = @_;
155 $current_tier = '' unless defined $current_tier;
156 my @out;
157
158 my $dbh = eval { $db->db_connect() };
159 if ($dbh && $bill->schema_ready($db, $dbh, $DB)) {
160 my @rows = $bill->list_plans($db, $dbh, $DB);
161 my %seen_tier;
162 # Prefer monthly row per tier (annual is implicit on the pricing card).
163 my @monthly = grep { ($_->{cadence}||'') eq 'monthly' } @rows;
164 my @backup = grep { ($_->{cadence}||'') ne 'monthly' } @rows;
165 foreach my $r (@monthly, @backup) {
166 next if $seen_tier{ $r->{tier} }++;
167 my $tier = $r->{tier};
168 my $label = ($r->{display_name} || ucfirst($tier))
169 . ' &mdash; '
170 . _signup_price_label($r->{price_cents})
171 . ($r->{tagline} ? (' &middot; ' . $r->{tagline}) : '');
172 push @out, {
173 value => $tier,
174 label => $label,
175 selected_attr => ($tier eq $current_tier) ? ' selected' : '',
176 };
177 }
178 $db->db_disconnect($dbh);
179 return @out if scalar @out;
180 }
181 $db->db_disconnect($dbh) if $dbh;
182
183 # Fallback for un-migrated databases. Must match pricing.cgi cards +
184 # index.cgi pricing block + comparison-table claims so prospects see
185 # the same offer everywhere ([[feedback-pricing-packaging-sync]]).
186 foreach my $r (
187 { v=>'free', l=>'Free &mdash; 1 program, 5 affiliates, manual payouts' },
188 { v=>'starter', l=>'Starter &mdash; $9/mo, 3 programs, 50 affiliates' },
189 { v=>'pro', l=>'Pro &mdash; $29/mo, 10 programs, 500 affiliates, Stripe Connect payouts' },
190 { v=>'studio', l=>'Studio &mdash; $99/mo, unlimited programs &amp; affiliates, team seats' },
191 ) {
192 push @out, {
193 value => $r->{v},
194 label => $r->{l},
195 selected_attr => ($r->{v} eq $current_tier) ? ' selected' : '',
196 };
197 }
198 return @out;
199}
200
201sub _signup_price_label {
202 my ($cents) = @_;
203 $cents = 0 unless defined $cents;
204 return 'free forever' if $cents == 0;
205 if ($cents % 100 == 0) {
206 return '$' . int($cents / 100) . '/mo';
207 }
208 return '$' . sprintf('%.2f', $cents / 100) . '/mo';
209}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help