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

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

added on local at 2026-07-01 21:47:18

Added
+241
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to a3b67ef5f85f
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# RePricer — signup
4# Page body lives in TEMPLATES/repricer_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/repricer.3dshawn.com';
14use CGI;
15use MODS::Template;
16use MODS::DBConnect;
17use MODS::Login;
18use MODS::RePricer::Config;
19use MODS::RePricer::Wrapper;
20use MODS::RePricer::Billing;
21use MODS::RePricer::EmailVerify;
22
23my $q = CGI->new;
24my $form = $q->Vars;
25my $auth = MODS::Login->new;
26my $wrap = MODS::RePricer::Wrapper->new;
27my $tfile = MODS::Template->new;
28my $db = MODS::DBConnect->new;
29my $cfg = MODS::RePricer::Config->new;
30my $bill = MODS::RePricer::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} || 'pro';
53
54 # Plan v3: only pro/business/scale are valid tiers. Coerce anything
55 # else (a legacy slug from a stale form cache, or a tampered POST)
56 # into the default Pro plan so signup never errors out on a now-
57 # removed ENUM value.
58 my %tier_ok = map { $_ => 1 } qw(pro business scale);
59 $plan = 'pro' unless $tier_ok{$plan};
60
61 $email_val = _h($email);
62 $name_val = _h($name);
63 $plan_val = _h($plan);
64
65 if ($email !~ /^[^\@\s]+\@[^\@\s]+\.[^\@\s]+$/) {
66 $error_msg = 'Please enter a valid email address.';
67 } elsif (length($pass) < 8) {
68 $error_msg = 'Password must be at least 8 characters.';
69 } else {
70 my $user_id = $auth->signup($email, $pass, $name, $plan);
71 if ($user_id) {
72 # ---- Start the 14-day trial -----------------------------
73 # Every new account starts in trial regardless of which paid
74 # tier they picked. Cap SKUs at 10 during trial; the chosen
75 # tier's normal marketplace defaults apply so the seller can
76 # see exactly what that plan unlocks. The card-required gate
77 # is enforced when trial_ends_at <= NOW() by the billing
78 # worker / dashboard upgrade banner -- NOT here. Wrapped in
79 # eval so a transient DB hiccup never blocks the signup
80 # (worst case: defaults apply, admin can repair).
81 eval {
82 my $marketplaces =
83 $plan eq 'scale' ? 'amazon,walmart,ebay' :
84 $plan eq 'business' ? 'amazon,walmart' :
85 'amazon';
86 my $uid = $user_id; $uid =~ s/[^0-9]//g;
87 my $dbh_t = $db->db_connect();
88 $db->db_readwrite($dbh_t, qq~
89 UPDATE ${DB}.users
90 SET trial_ends_at = DATE_ADD(NOW(), INTERVAL 14 DAY),
91 trial_sku_cap = 10,
92 marketplaces_enabled = '$marketplaces'
93 WHERE id='$uid'
94 ~, $ENV{SCRIPT_NAME}, __LINE__);
95 $db->db_disconnect($dbh_t);
96 };
97
98 # Seed 3 sample products + a sample rule so the dashboard isn't empty.
99 eval {
100 require MODS::RePricer::Onboarding;
101 my $dbh3 = $db->db_connect();
102 MODS::RePricer::Onboarding->new->seed_sample_data($db, $dbh3, $DB, $user_id);
103 $db->db_disconnect($dbh3);
104 };
105
106 # Fire-and-forget the verification email. The user is
107 # signed in immediately either way (we don't gate the
108 # platform behind verification); a bounce or mailer-down
109 # condition just means they'll see a "Resend verification"
110 # button on /settings.cgi until they confirm. Wrapped in
111 # eval so a SMTP outage never blocks signup.
112 eval {
113 my $dbh2 = $db->db_connect();
114 MODS::RePricer::EmailVerify->send_for_user($db, $dbh2, $DB, {
115 id => $user_id,
116 email => $email,
117 display_name => $name,
118 });
119 $db->db_disconnect($dbh2);
120 };
121
122 my ($userinfo, $token) = $auth->login_exec($email, $pass);
123 if ($userinfo && $token) {
124 print $auth->set_cookie_header($token);
125 # Brand-new accounts land on /dashboard.cgi -- the
126 # getting-started console with checklist + plan summary.
127 # The dashboard is the right home AFTER they're set up,
128 # not on day zero.
129 print "Status: 302 Found\nLocation: /dashboard.cgi\n\n";
130 exit;
131 }
132 $error_msg = 'Account created — please sign in.';
133 } else {
134 $error_msg = 'That email is already in use, or an account error occurred.';
135 }
136 }
137}
138
139print "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";
140
141# Build the plan <option> list from billing_plans (admin-managed). One
142# entry per unique tier; we prefer the monthly price for the label.
143# Falls back to a built-in set when the schema isn't migrated.
144my @plan_options = _build_plan_options($db, $bill, $plan_val);
145
146my $tvars = {
147 error_msg => $error_msg,
148 email_val => $email_val,
149 name_val => $name_val,
150 plan_options => \@plan_options,
151};
152
153my $body = join('', $tfile->template('repricer_signup.html', $tvars, undef));
154
155
156# === Maintenance lockdown overlay (auto-injected 2026-06-23) ===
157# When maintenance_locked=1, render a modal over the login form.
158# X-close stays visible so an admin can sign in. Non-admin login
159# attempts fail and the page reloads, restoring the overlay.
160{
161 my $_mcfg = MODS::RePricer::Config->new;
162 if ($_mcfg->settings('maintenance_locked')) {
163 my $_mmsg = $_mcfg->settings('maintenance_message') || '<p>The system is in maintenance mode.</p>';
164 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>};
165 $body = $_overlay . $body;
166 }
167}
168# === End maintenance lockdown overlay ===
169$wrap->render({
170 userinfo => undef,
171 title => 'Start your trial',
172 body => $body,
173});
174
175#----------------------------------------------------------------------
176sub _h {
177 my $s = shift // '';
178 $s =~ s/&/&amp;/g; $s =~ s/</&lt;/g; $s =~ s/>/&gt;/g;
179 $s =~ s/"/&quot;/g; $s =~ s/'/&#39;/g;
180 return $s;
181}
182
183# Build the <option> list for the plan dropdown. Reads billing_plans
184# (preferring the monthly row of each tier for the label price), and
185# pre-marks the matching tier with selected_attr. Connection-safe:
186# opens its own connection so signup pages on databases without the
187# billing schema still render via the fallback list.
188sub _build_plan_options {
189 my ($db, $bill, $current_tier) = @_;
190 $current_tier = '' unless defined $current_tier;
191 my @out;
192
193 my $dbh = eval { $db->db_connect() };
194 if ($dbh && $bill->schema_ready($db, $dbh, $DB)) {
195 my @rows = $bill->list_plans($db, $dbh, $DB);
196 my %seen_tier;
197 # Prefer monthly row per tier (annual is implicit on the pricing card).
198 my @monthly = grep { ($_->{cadence}||'') eq 'monthly' } @rows;
199 my @backup = grep { ($_->{cadence}||'') ne 'monthly' } @rows;
200 foreach my $r (@monthly, @backup) {
201 next if $seen_tier{ $r->{tier} }++;
202 my $tier = $r->{tier};
203 my $label = ($r->{display_name} || ucfirst($tier))
204 . ' &mdash; '
205 . _signup_price_label($r->{price_cents})
206 . ($r->{tagline} ? (' &middot; ' . $r->{tagline}) : '');
207 push @out, {
208 value => $tier,
209 label => $label,
210 selected_attr => ($tier eq $current_tier) ? ' selected' : '',
211 };
212 }
213 $db->db_disconnect($dbh);
214 return @out if scalar @out;
215 }
216 $db->db_disconnect($dbh) if $dbh;
217
218 # Fallback for un-migrated databases.
219 foreach my $r (
220 { v=>'pro', l=>'Pro &mdash; $49/mo &middot; 1,000 SKUs, Amazon' },
221 { v=>'business', l=>'Business &mdash; $149/mo &middot; 10,000 SKUs, Amazon + Walmart' },
222 { v=>'scale', l=>'Scale &mdash; $349/mo &middot; Unlimited, all marketplaces, SLA' },
223 ) {
224 push @out, {
225 value => $r->{v},
226 label => $r->{l},
227 selected_attr => ($r->{v} eq $current_tier) ? ' selected' : '',
228 };
229 }
230 return @out;
231}
232
233sub _signup_price_label {
234 my ($cents) = @_;
235 $cents = 0 unless defined $cents;
236 return 'free forever' if $cents == 0;
237 if ($cents % 100 == 0) {
238 return '$' . int($cents / 100) . '/mo';
239 }
240 return '$' . sprintf('%.2f', $cents / 100) . '/mo';
241}
Keyboard: j next diff k previous diff g top G bottom r restore c compile-check ? help