added on local at 2026-07-01 21:47:06
| 1 | #!/usr/bin/perl | |
| 2 | #====================================================================== | |
| 3 | # RePricer -- /email_preferences.cgi | |
| 4 | # | |
| 5 | # Logged-in user-facing email preference center. | |
| 6 | # - Shows per-category toggle (transactional is always on, can't toggle) | |
| 7 | # - Shows recent emails sent to this user (last 10) | |
| 8 | # - POST act=save updates user_email_preferences rows | |
| 9 | #====================================================================== | |
| 10 | use strict; | |
| 11 | use warnings; | |
| 12 | ||
| 13 | use lib '/var/www/vhosts/3dshawn.com/repricer.3dshawn.com'; | |
| 14 | use CGI; | |
| 15 | use MODS::DBConnect; | |
| 16 | use MODS::RePricer::Wrapper; | |
| 17 | use MODS::RePricer::Config; | |
| 18 | use MODS::Login; | |
| 19 | ||
| 20 | my $q = CGI->new; | |
| 21 | my $form = $q->Vars; | |
| 22 | my $wrap = MODS::RePricer::Wrapper->new; | |
| 23 | my $db = MODS::DBConnect->new; | |
| 24 | my $cfg = MODS::RePricer::Config->new; | |
| 25 | my $DB = $cfg->settings('database_name'); | |
| 26 | ||
| 27 | my $u = MODS::RePricer::PM::require_login(); | |
| 28 | my $uid = $u->{user_id} + 0; | |
| 29 | ||
| 30 | my $dbh = $db->db_connect; | |
| 31 | my $flash = ''; | |
| 32 | ||
| 33 | my @CATS = ( | |
| 34 | { key => 'lifecycle', label => 'Lifecycle emails', description => 'Trial reminders, plan changes, downgrade notices, winback. We recommend keeping these on.', default_on => 1 }, | |
| 35 | { key => 'product', label => 'Product updates', description => 'New features, release notes, what shipped this month. Once or twice a month.', default_on => 0 }, | |
| 36 | { key => 'marketing', label => 'Tips & marketing', description => 'Onboarding tips, customer stories, occasional promotions.', default_on => 0 }, | |
| 37 | ); | |
| 38 | ||
| 39 | if (($ENV{REQUEST_METHOD} || '') eq 'POST' && ($form->{act} || '') eq 'save') { | |
| 40 | foreach my $c (@CATS) { | |
| 41 | my $sub = $form->{"pref_$c->{key}"} ? 1 : 0; | |
| 42 | $db->db_readwrite($dbh, qq~ | |
| 43 | INSERT INTO ${DB}.user_email_preferences (user_id, category, is_subscribed) | |
| 44 | VALUES ('$uid', '$c->{key}', '$sub') | |
| 45 | ON DUPLICATE KEY UPDATE is_subscribed='$sub' | |
| 46 | ~, $0, __LINE__); | |
| 47 | } | |
| 48 | $flash = 'Preferences saved.'; | |
| 49 | } | |
| 50 | ||
| 51 | my %prefs; | |
| 52 | { | |
| 53 | my @rows = $db->db_readwrite_multiple($dbh, qq~ | |
| 54 | SELECT category, is_subscribed FROM ${DB}.user_email_preferences WHERE user_id='$uid' | |
| 55 | ~, $0, __LINE__); | |
| 56 | foreach my $r (@rows) { $prefs{$r->{category}} = $r->{is_subscribed} ? 1 : 0; } | |
| 57 | } | |
| 58 | ||
| 59 | my @recent = $db->db_readwrite_multiple($dbh, qq~ | |
| 60 | SELECT template_slug, subject, status, created_at | |
| 61 | FROM ${DB}.email_sends_log | |
| 62 | WHERE user_id='$uid' | |
| 63 | ORDER BY id DESC LIMIT 10 | |
| 64 | ~, $0, __LINE__); | |
| 65 | ||
| 66 | $db->db_disconnect($dbh); | |
| 67 | ||
| 68 | print "Content-Type: text/html; charset=utf-8\r\nCache-Control: no-store\r\n\r\n"; | |
| 69 | ||
| 70 | my $flash_html = $flash ? qq~<div style="padding:10px 14px;background:rgba(34,197,94,.12);border:1px solid rgba(34,197,94,.3);border-radius:8px;color:#86efac;font-size:13px;margin-bottom:18px">$flash</div>~ : ''; | |
| 71 | ||
| 72 | my $rows = ''; | |
| 73 | foreach my $c (@CATS) { | |
| 74 | my $sub = defined($prefs{$c->{key}}) ? $prefs{$c->{key}} : $c->{default_on}; | |
| 75 | my $checked = $sub ? 'checked' : ''; | |
| 76 | $rows .= qq~ | |
| 77 | <label style="display:flex;align-items:flex-start;gap:14px;padding:16px;border:1px solid rgba(255,255,255,.08);border-radius:10px;margin-bottom:10px;cursor:pointer;background:rgba(20,20,30,.4)"> | |
| 78 | <input type="checkbox" name="pref_$c->{key}" value="1" $checked style="margin-top:3px;width:18px;height:18px"> | |
| 79 | <div> | |
| 80 | <div style="font-weight:600;color:#e2e8f0;font-size:14px">$c->{label}</div> | |
| 81 | <div style="color:#94a3b8;font-size:13px;line-height:1.5;margin-top:4px">$c->{description}</div> | |
| 82 | </div> | |
| 83 | </label>~; | |
| 84 | } | |
| 85 | ||
| 86 | my $recent_html = ''; | |
| 87 | foreach my $r (@recent) { | |
| 88 | my $color = $r->{status} eq 'sent' ? '#86efac' : ($r->{status} =~ /^suppressed/ ? '#fbbf24' : '#fca5a5'); | |
| 89 | my $sj = _h($r->{subject}); | |
| 90 | $recent_html .= qq~ | |
| 91 | <div style="display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid rgba(255,255,255,.05);font-size:12px"> | |
| 92 | <div><span style="color:#cbd5e1">$sj</span><div style="color:#64748b;font-size:11px;font-family:ui-monospace,Consolas,Menlo,monospace">$r->{template_slug}</div></div> | |
| 93 | <div><span style="color:$color;font-size:11px;text-transform:uppercase;letter-spacing:.05em">$r->{status}</span><div style="color:#94a3b8;font-size:11px;text-align:right">$r->{created_at}</div></div> | |
| 94 | </div>~; | |
| 95 | } | |
| 96 | ||
| 97 | my $body = qq~ | |
| 98 | <div class="page-pad" style="max-width:720px"> | |
| 99 | <h1 style="margin:0 0 6px;font-size:28px;color:#fff">Email preferences</h1> | |
| 100 | <p style="color:#94a3b8;margin:0 0 24px;font-size:14px">Choose which emails you'd like to receive from RePricer. Transactional emails (password resets, invite acceptance, billing receipts) are always sent regardless.</p> | |
| 101 | $flash_html | |
| 102 | ||
| 103 | <form method="POST" action="/email_preferences.cgi"> | |
| 104 | <input type="hidden" name="act" value="save"> | |
| 105 | $rows | |
| 106 | <div style="margin-top:18px"><button type="submit" class="btn btn-primary btn-lg">Save preferences</button></div> | |
| 107 | </form> | |
| 108 | ||
| 109 | <h2 style="font-size:16px;color:#fff;margin:32px 0 12px">Recent emails sent to you</h2> | |
| 110 | <div style="border:1px solid rgba(255,255,255,.08);border-radius:10px;padding:8px 16px;background:rgba(20,20,30,.4)">$recent_html</div> | |
| 111 | </div>~; | |
| 112 | ||
| 113 | $wrap->render({ userinfo => $u, page_key => 'email_preferences', title => 'Email preferences', body => $body }); | |
| 114 | ||
| 115 | sub _h { my $s = shift // ''; $s =~ s/&/&/g; $s =~ s/</</g; $s =~ s/>/>/g; $s =~ s/"/"/g; $s =~ s/'/'/g; return $s; } |