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

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

added on local at 2026-07-01 22:09:45

Added
+208
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 360ef4c6778f
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# ShopCart — signup
4# Page body lives in TEMPLATES/shopcart_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/shop.3dshawn.com';
14use CGI;
15use MODS::Template;
16use MODS::DBConnect;
17use MODS::Login;
18use MODS::ShopCart::Config;
19use MODS::ShopCart::Wrapper;
20use MODS::ShopCart::Billing;
21use MODS::ShopCart::EmailVerify;
22
23my $q = CGI->new;
24my $form = $q->Vars;
25my $auth = MODS::Login->new;
26my $wrap = MODS::ShopCart::Wrapper->new;
27my $tfile = MODS::Template->new;
28my $db = MODS::DBConnect->new;
29my $cfg = MODS::ShopCart::Config->new;
30my $bill = MODS::ShopCart::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 sample storefront + products so the dashboard isn't empty.
66 eval {
67 require MODS::ShopCart::Onboarding;
68 my $dbh3 = $db->db_connect();
69 MODS::ShopCart::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::ShopCart::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 # getting-started console with checklist + plan summary.
94 # The dashboard is the right home AFTER they're set up,
95 # not on day zero.
96 print "Status: 302 Found\nLocation: /dashboard.cgi\n\n";
97 exit;
98 }
99 $error_msg = 'Account created — please sign in.';
100 } else {
101 $error_msg = 'That email is already in use, or an account error occurred.';
102 }
103 }
104}
105
106print "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";
107
108# Build the plan <option> list from billing_plans (admin-managed). One
109# entry per unique tier; we prefer the monthly price for the label.
110# Falls back to a built-in set when the schema isn't migrated.
111my @plan_options = _build_plan_options($db, $bill, $plan_val);
112
113my $tvars = {
114 error_msg => $error_msg,
115 email_val => $email_val,
116 name_val => $name_val,
117 plan_options => \@plan_options,
118};
119
120my $body = join('', $tfile->template('shopcart_signup.html', $tvars, undef));
121
122
123# === Maintenance lockdown overlay (auto-injected 2026-06-23) ===
124# When maintenance_locked=1, render a modal over the login form.
125# X-close stays visible so an admin can sign in. Non-admin login
126# attempts fail and the page reloads, restoring the overlay.
127{
128 my $_mcfg = MODS::ShopCart::Config->new;
129 if ($_mcfg->settings('maintenance_locked')) {
130 my $_mmsg = $_mcfg->settings('maintenance_message') || '<p>The system is in maintenance mode.</p>';
131 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>};
132 $body = $_overlay . $body;
133 }
134}
135# === End maintenance lockdown overlay ===
136$wrap->render({
137 userinfo => undef,
138 title => 'Start your trial',
139 body => $body,
140});
141
142#----------------------------------------------------------------------
143sub _h {
144 my $s = shift // '';
145 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g;
146 $s =~ s/"/&quot;/g; $s =~ s/'/&#39;/g;
147 return $s;
148}
149
150# Build the <option> list for the plan dropdown. Reads billing_plans
151# (preferring the monthly row of each tier for the label price), and
152# pre-marks the matching tier with selected_attr. Connection-safe:
153# opens its own connection so signup pages on databases without the
154# billing schema still render via the fallback list.
155sub _build_plan_options {
156 my ($db, $bill, $current_tier) = @_;
157 $current_tier = '' unless defined $current_tier;
158 my @out;
159
160 my $dbh = eval { $db->db_connect() };
161 if ($dbh && $bill->schema_ready($db, $dbh, $DB)) {
162 my @rows = $bill->list_plans($db, $dbh, $DB);
163 my %seen_tier;
164 # Prefer monthly row per tier (annual is implicit on the pricing card).
165 my @monthly = grep { ($_->{cadence}||'') eq 'monthly' } @rows;
166 my @backup = grep { ($_->{cadence}||'') ne 'monthly' } @rows;
167 foreach my $r (@monthly, @backup) {
168 next if $seen_tier{ $r->{tier} }++;
169 my $tier = $r->{tier};
170 my $label = ($r->{display_name} || ucfirst($tier))
171 . ' &mdash; '
172 . _signup_price_label($r->{price_cents})
173 . ($r->{tagline} ? (' &middot; ' . $r->{tagline}) : '');
174 push @out, {
175 value => $tier,
176 label => $label,
177 selected_attr => ($tier eq $current_tier) ? ' selected' : '',
178 };
179 }
180 $db->db_disconnect($dbh);
181 return @out if scalar @out;
182 }
183 $db->db_disconnect($dbh) if $dbh;
184
185 # Fallback for un-migrated databases.
186 foreach my $r (
187 { v=>'free', l=>'Free &mdash; up to 50 products, 0% platform fee' },
188 { v=>'pro', l=>'Pro &mdash; $29/mo, unlimited products + A/B' },
189 { v=>'business', l=>'Business &mdash; $79/mo, unlimited everything' },
190 ) {
191 push @out, {
192 value => $r->{v},
193 label => $r->{l},
194 selected_attr => ($r->{v} eq $current_tier) ? ' selected' : '',
195 };
196 }
197 return @out;
198}
199
200sub _signup_price_label {
201 my ($cents) = @_;
202 $cents = 0 unless defined $cents;
203 return 'free forever' if $cents == 0;
204 if ($cents % 100 == 0) {
205 return '$' . int($cents / 100) . '/mo';
206 }
207 return '$' . sprintf('%.2f', $cents / 100) . '/mo';
208}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help