Round on the web stuff.

master
Kaitlyn Parkhurst 3 years ago
parent 58d8bd5df9
commit 8a49af1ff2
  1. 7
      Web/cpanfile
  2. 21
      Web/lib/MJB/Web.pm
  3. 178
      Web/lib/MJB/Web/Controller/Auth.pm
  4. 135
      Web/lib/MJB/Web/Controller/UserSettings.pm
  5. 68
      Web/templates/_base/bootstrap-datepicker.html.ep
  6. 10
      Web/templates/_base/error_window.html.ep
  7. 7
      Web/templates/_base/form/input.html.ep
  8. 30
      Web/templates/_base/head.html.ep
  9. 70
      Web/templates/_base/sidebar.html.ep
  10. 15
      Web/templates/_base/topbar.html.ep
  11. 35
      Web/templates/auth/forgot.html.ep
  12. 32
      Web/templates/auth/login.html.ep
  13. 45
      Web/templates/auth/register.html.ep
  14. 38
      Web/templates/auth/reset.html.ep
  15. 6
      Web/templates/layouts/standard.html.ep
  16. 9
      Web/templates/root/about.html.ep
  17. 10
      Web/templates/root/contact.html.ep
  18. 4
      Web/templates/root/index.html.ep
  19. 73
      Web/templates/root/pricing.html.ep
  20. 45
      Web/templates/user_settings/change_password.html.ep
  21. 43
      Web/templates/user_settings/profile.html.ep

@ -1,4 +1,11 @@
requires 'Mojolicious';
requires 'Mojo::Pg';
requires 'Mojo::File';
requires 'DateTime';
requires 'DateTime::Format::Pg';
requires 'Try::Tiny';
# Remove when moving mail to its own package.
requires 'Email::Sender::Simple';
requires 'Email::Sender::Transport::SMTP';
requires 'Email::MIME::Kit';

@ -18,7 +18,7 @@ sub startup ($self) {
push @{$self->commands->namespaces}, 'MJB::Web::Command';
$self->helper( db => sub {
return state $db = WeightGrapher::DB->connect($config->{database}->{mjb});
return state $db = MJB::DB->connect($config->{database}->{mjb});
});
# Standard router.
@ -67,6 +67,25 @@ sub startup ($self) {
$r->get ( '/pricing' )->to( 'Root#pricing' )->name('show_pricing' );
$r->get ( '/contact' )->to( 'Root#contact' )->name('show_contact' );
# User registration, login, and logout.
$r->get ( '/register' )->to( 'Auth#register' )->name('show_register' );
$r->post ( '/register' )->to( 'Auth#do_register' )->name('do_register' );
$r->get ( '/login' )->to( 'Auth#login' )->name('show_login' );
$r->post ( '/login' )->to( 'Auth#do_login' )->name('do_login' );
$auth->get( '/logout' )->to( 'Auth#do_logout' )->name('do_logout' );
# User Forgot Password Workflow.
$r->get ( '/forgot' )->to('Auth#forgot' )->name('show_forgot' );
$r->post( '/forgot' )->to('Auth#do_forgot' )->name('do_forgot' );
$r->get ( '/reset/:token' )->to('Auth#reset' )->name('show_reset' );
$r->post( '/reset/:token' )->to('Auth#do_reset' )->name('do_reset' );
# User setting changes when logged in
$auth->get ( '/profile' )->to('UserSettings#profile' )->name('show_profile' );
$auth->post( '/profile' )->to('UserSettings#do_profile' )->name('do_profile' );
$auth->get ( '/password' )->to('UserSettings#change_password' )->name('show_change_password' );
$auth->post( '/password' )->to('UserSettings#do_change_password' )->name('do_change_password' );
}

@ -0,0 +1,178 @@
package MJB::Web::Controller::Auth;
use Mojo::Base 'Mojolicious::Controller', -signatures;
use Try::Tiny;
use DateTime;
use Email::Sender::Simple qw( sendmail );
use Email::Sender::Transport::SMTP;
use Email::MIME::Kit;
sub show_register ( $c ) {
}
sub do_register ( $c ) {
$c->stash->{template} = 'auth/register';
my $name = $c->stash->{form_name} = $c->param('name');
my $email = $c->stash->{form_email} = $c->param('email');
my $password = $c->stash->{form_password} = $c->param('password');
my $p_confirm = $c->stash->{form_password_confirm} = $c->param('password_confirm');
push @{$c->stash->{errors}}, "Name is required" unless $name;
push @{$c->stash->{errors}}, "Email is required" unless $email;
push @{$c->stash->{errors}}, "Password is required" unless $password;
push @{$c->stash->{errors}}, "Confirm Password is required" unless $p_confirm;
return if $c->stash->{errors};
push @{$c->stash->{errors}}, "Password and confirm password must match"
unless $p_confirm eq $password;
push @{$c->stash->{errors}}, "Password must be at least 8 characters"
unless length($password) >= 8;
return if $c->stash->{errors};
my $person = try {
$c->db->storage->schema->txn_do( sub {
my $person = $c->db->resultset('Person')->create({
email => $c->param('email'),
name => $c->param('name'),
});
$person->new_related('auth_password', {})->set_password($c->param('password'));
return $person;
});
} catch {
push @{$c->stash->{errors}}, "Account could not be created: $_";
};
return if $c->stash->{errors};
$c->session->{uid} = $person->id;
$c->redirect_to( $c->url_for( 'dashboard' ) );
}
sub login ( $c ) {
if ( $c->stash->{person} ) {
$c->redirect_to( $c->url_for( 'show_dashboard' ) );
}
}
sub do_login ( $c ) {
$c->stash->{template} = 'auth/login';
my $email = $c->stash->{form_email} = $c->param('email');
my $password = $c->stash->{form_password} = $c->param('password');
my $person = $c->db->resultset('Person')->find( { email => $email } )
or push @{$c->stash->{errors}}, "Invalid email address or password.";
return 0 if $c->stash->{errors};
$person->auth_password->check_password( $password )
or push @{$c->stash->{errors}}, "Invalid email address or password.";
return 0 if $c->stash->{errors};
$c->stash->{person} = $person;
$c->session->{uid} = $person->id;
$c->redirect_to( $c->url_for( 'show_dashboard' ) );
}
sub do_logout ( $c ) {
undef $c->session->{uid};
$c->redirect_to( $c->url_for( 'show_login' ) );
}
sub show_forgot ( $c ) { }
sub do_forgot ( $c ) {
$c->stash->{template} = 'auth/forgot';
my $email = $c->stash->{form_email} = $c->param('email');
my $person = $c->db->resultset('Person')->find( { email => $email } )
or push @{$c->stash->{errors}}, "There is no account with that email address.";
return 0 if $c->stash->{errors};
# Make a token & send the email TODO
my $token = $person->create_auth_token( 'forgot' );
my $mkit_path = $c->config->{mkit_path};
my $transport = Email::Sender::Transport::SMTP->new(%{$c->config->{smtp}});
my $kit = Email::MIME::Kit->new({ source => "$mkit_path/forgot_password.mkit" } );
my $message = $kit->assemble( {
send_to => $email,
link => 'https://' . $c->config->{domain} . "/reset/$token"
});
sendmail( $message, { transport => $transport } );
# Let the user know the next steps.
$c->stash->{success} = 1;
$c->stash->{success_message} = 'Please check your email for a password reset link.';;
# Clear the form.
$c->stash->{form_email} = '';
}
sub show_reset ( $c ) { }
sub do_reset ( $c ) {
$c->stash->{template} = 'auth/reset';
my $token = $c->param('token');
my $password = $c->stash->{form_password} = $c->param('password');
my $confirm = $c->stash->{form_password_confirm} = $c->param('password_confirm');
push @{$c->stash->{errors}}, "Password is required" unless $password;
push @{$c->stash->{errors}}, "Confirm Password is required" unless $confirm;
return if $c->stash->{errors};
push @{$c->stash->{errors}}, "Password and confirm password must match"
unless $confirm eq $password;
push @{$c->stash->{errors}}, "Password must be at least 8 characters"
unless length($password) >= 8;
return if $c->stash->{errors};
my $lower_time = DateTime->now;
$lower_time->subtract( minutes => 60 );
my $record = $c->db->auth_tokens->search( {
token => $token,
scope => 'forgot',
'me.created_at' => { '>=', $lower_time },
}, { prefetch => 'person' })->first;
push @{$c->stash->{errors}}, "This token is not valid."
unless $record;
return 0 if $c->stash->{errors};
# Change the user's password.
$record->person->auth_password->update_password( $password );
# Log the user into the account
$c->session->{uid} = $record->person->id;
# Delete this token.
$record->delete;
# Send them to the dashboard.
$c->redirect_to( $c->url_for( 'show_dashboard' ) );
}
1;

@ -0,0 +1,135 @@
package MJB::Web::Controller::UserSettings;
use Mojo::Base 'Mojolicious::Controller', -signatures;
sub profile ( $c ) {
$c->stash->{form_name} = $c->stash->{person}->name;
$c->stash->{form_email} = $c->stash->{person}->email;
}
sub do_profile ( $c ) {
$c->stash->{template} = 'user_settings/profile';
my $name = $c->stash->{form_name} = $c->param('name');
my $email = $c->stash->{form_email} = $c->param('email');
my $password = $c->stash->{form_password} = $c->param('password');
# Populate errors if we don't have values.
push @{$c->{stash}->{errors}}, "You must enter your name" unless $name;
push @{$c->{stash}->{errors}}, "You must enter your email" unless $email;
push @{$c->{stash}->{errors}}, "You must enter your password" unless $password;
# Bail out if we have errors now.
return 0 if $c->stash->{errors};
$c->stash->{person}->auth_password->check_password( $password )
or push @{$c->stash->{errors}}, "You must enter your current login password correctly.";
# Bail out if we have errors now.
return 0 if $c->stash->{errors};
$c->stash->{person}->name( $name );
$c->stash->{person}->email( $email );
$c->stash->{person}->update;
# Let the user know the action was successful.
$c->stash->{success} = 1;
$c->stash->{success_message} = "Your records were updated.";
}
sub change_password ( $c ) { }
sub do_change_password ( $c ) {
$c->stash->{template} = 'user_settings/change_password';
# Get the values the user gave for the password change.
my $password = $c->stash->{form_password} = $c->param('password');
my $new_pass = $c->stash->{form_new_password} = $c->param('new_password');
my $confirm = $c->stash->{form_password_confirm} = $c->param('password_confirm');
# Populate errors if we don't have values.
push @{$c->{stash}->{errors}}, "You must enter your current password" unless $password;
push @{$c->{stash}->{errors}}, "You must enter your new password" unless $new_pass;
push @{$c->{stash}->{errors}}, "You must enter your new password again to confirm" unless $confirm;
# Bail out if we have errors now.
return 0 if $c->stash->{errors};
$c->stash->{person}->auth_password->check_password( $password )
or push @{$c->stash->{errors}}, "You must enter your current login password correctly.";
# Bail out if we have errors now.
return 0 if $c->stash->{errors};
push @{$c->stash->{errors}}, "Password and confirm password must match"
unless $new_pass eq $confirm;
push @{$c->stash->{errors}}, "Password must be at least 8 characters"
unless length($new_pass) >= 8;
# Bail out if we have errors now.
return if $c->stash->{errors};
# We can update the password now.
$c->stash->{person}->auth_password->update_password($new_pass);
# Let the user know the action was successful.
$c->stash->{success} = 1;
$c->stash->{success_message} = "Your password was updated.";
# Clear the form values on success.
$c->stash->{form_password} = "";
$c->stash->{form_new_password} = "";
$c->stash->{form_password_confirm} = "";
}
sub subscription ($c) {
my $status = $c->param('status');
# No status=, the user themself requested this page.
if ( ! $status ) {
return;
}
# Status isn't successful, tell the user they could try agan.
if ( $status ne 'success' ) {
push @{$c->stash->{errors}}, "Subscription wasn't successful.";
return;
}
my $session_id = $c->param('session_id');
my $customer_id = $c->ua->get( $c->config->{stripe_backend} . '/stripe/session-to-customer?session_id=' . $session_id )->result->json->{customer_id};
# Store the customer id along side the user in the DB.
if ( $customer_id ) {
$c->db->storage->schema->txn_do( sub {
$c->stash->{person}->stripe_customer_id( $customer_id );
$c->stash->{person}->is_subscribed( 1 );
$c->stash->{person}->update;
});
}
$c->stash(
success => 1,
success_message => 'Thank you for signing up!',
);
}
# Send to stripe to signup for the subscription
sub do_subscription ($c) {
my $lookup_key = $c->param('lookup_key');
my $url = $c->ua->get( $c->config->{stripe_backend} . '/stripe/get-checkout-link?lookup_key=' . $lookup_key )->result->json->{url};
$c->redirect_to( $url );
}
# Send to stripe to manage the subscription
sub do_subscription_manage ($c) {
my $url = $c->ua->get( $c->config->{stripe_backend} . '/stripe/get-portal-link?customer_id=' . $c->stash->{person}->stripe_customer_id )->result->json->{url};
$c->redirect_to( $url );
}
1;

@ -0,0 +1,68 @@
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js"></script>
<link href="/assets/bootstrap-datepicker/css/bootstrap-datepicker3.min.css" rel="stylesheet">
<script src="/assets/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
<script src="/assets/bootstrap-datepicker/js/bootstrap-datepicker.min.js"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.ar.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.az.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.bg.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.bs.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.ca.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.cs.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.cy.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.da.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.de.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.el.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.en-GB.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.es.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.et.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.eu.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.fa.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.fi.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.fo.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.fr.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.fr-CH.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.gl.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.he.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.hr.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.hu.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.hy.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.id.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.is.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.it.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.it-CH.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.ja.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.ka.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.km.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.kk.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.ko.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.lt.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.lv.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.mk.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.ms.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.nl.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.nl-BE.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.no.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.pl.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.pt-BR.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.pt.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.ro.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.sr.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.sr-latin.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.ru.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.sk.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.sl.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.sq.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.sr.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.sr-latin.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.sv.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.sw.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.th.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.tr.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.uk.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.vi.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.zh-CN.min.js" charset="UTF-8"></script>
<script src="/assets/bootstrap-datepicker/locales/bootstrap-datepicker.zh-TW.min.js" charset="UTF-8"></script>

@ -0,0 +1,10 @@
% if ( $c->stash->{errors} ) {
<div style="margin-top: 2em" class="alert alert-danger" role="alert">
There were errors with your request that could not be resolved:
<ul>
% for my $error ( @{$c->stash->{errors}} ) {
<li><%= $error %></li>
% }
</ul>
</div>
% }

@ -0,0 +1,7 @@
<!-- RUN _forms/input -->
<div class="mb-3">
<label for="form_<%= $name %>" class="form-label"><%= $title %></label>
<input type="<%= $type %>" class="form-control" id="form_<%= $name %>" name="<%= $name %>" value="<%= $value %>" aria-describedby="form_<%= $name %>_help">
<div id="form_<%= $name %>_help" class="form-text"><%= $help %></div>
</div>
<!-- END _forms/input.tx -->

@ -0,0 +1,30 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TodayChecklist<%= $title ? " - " . $title : "" %></title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-+0n0xVW2eSR5OomGNYDnhzAbDsOXxcvSN1TPprVMTNDbiYZCxYbOOl7+AMvyTG2x"
crossorigin="anonymous"
>
<!-- Favicons -->
<link rel="apple-touch-icon" href="/docs/5.0/assets/img/favicons/apple-touch-icon.png" sizes="180x180">
<link rel="icon" href="/docs/5.0/assets/img/favicons/favicon-32x32.png" sizes="32x32" type="image/png">
<link rel="icon" href="/docs/5.0/assets/img/favicons/favicon-16x16.png" sizes="16x16" type="image/png">
<link rel="manifest" href="/docs/5.0/assets/img/favicons/manifest.json">
<link rel="mask-icon" href="/docs/5.0/assets/img/favicons/safari-pinned-tab.svg" color="#7952b3">
<link rel="icon" href="/docs/5.0/assets/img/favicons/favicon.ico">
<meta name="theme-color" content="#7952b3">
<!-- Custom styles for this template -->
<link href="/assets/css/dashboard.css" rel="stylesheet">
<!-- ChartJS -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.7.1/dist/chart.min.js"></script>
%= include '_base/bootstrap-datepicker'

@ -0,0 +1,70 @@
<!-- BEGIN _base/sidebar.html.ep -->
<nav id="sidebarMenu" class="col-md-3 col-lg-2 d-md-block bg-light sidebar collapse">
<div class="position-sticky pt-3">
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "dashboard" ? "active" : "" %>" aria-current="page" href="<%= $c->url_for( 'show_dashboard' ) %>">
<span data-feather="home"></span>
Dashboard
</a>
</li>
</ul>
<ul class="nav flex-column">
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "about" ? "active" : "" %>" aria-current="page" href="<%= $c->url_for( 'show_about' ) %>">
<span data-feather="info"></span>
About
</a>
</li>
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "contact" ? "active" : "" %>" aria-current="page" href="<%= $c->url_for( 'show_contact' ) %>">
<span data-feather="mail"></span>
Contact
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>Create New</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "document" ? "active" : "" %>" href="<%= $c->url_for( 'show_documents' ) %>">
<span data-feather="file-plus"></span>
Document
</a>
</li>
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "template" ? "active" : "" %>" href="<%= $c->url_for( 'show_template_create' ) %>">
<span data-feather="file-text"></span>
Template
</a>
</li>
</ul>
<h6 class="sidebar-heading d-flex justify-content-between align-items-center px-3 mt-4 mb-1 text-muted">
<span>My Info</span>
</h6>
<ul class="nav flex-column mb-2">
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "subscription" ? "active" : "" %>" href="<%= $c->url_for( 'show_subscription' ) %>">
<span data-feather="credit-card"></span>
Subscription
</a>
</li>
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "profile" ? "active" : "" %>" href="<%= $c->url_for( 'show_profile' ) %>">
<span data-feather="user"></span>
Profile
</a>
</li>
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "password" ? "active" : "" %>" href="<%= $c->url_for( 'show_change_password' ) %>">
<span data-feather="lock"></span>
Change Password
</a>
</li>
</ul>
</div>
</nav>
<!-- END _base/sidebar.html.ep -->

@ -0,0 +1,15 @@
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="<%= $c->url_for( 'show_dashboard' ) %>">TodayChecklist</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button"
data-bs-toggle="collapse"
data-bs-target="#sidebarMenu"
aria-controls="sidebarMenu"
aria-expanded="false"
aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<ul class="navbar-nav px-3">
<li class="nav-item text-nowrap"><a class="nav-link" href="<%= $c->url_for('do_logout') %>">Sign out</a></li>
</ul>
</header>

@ -0,0 +1,35 @@
% layout 'standard', title => 'Forgot Password', sb_active => 'forgot';
<h2 class="mt-5 display-6 mb-4">Reset Password</h2>
<p>If you have forgotten the password for your account, please enter the email address below and you will get a link to reset your password.</p>
% if ( $c->stash->{success} ) {
<div style="margin-top: 2em" class="alert alert-success" role="alert">
<%= $c->stash->{success_message} %>
</div>
% }
% if ( $c->stash->{errors} ) {
<div style="margin-top: 2em" class="alert alert-danger" role="alert">
There were errors with your request that could not be resolved:
<ul>
% for my $error ( @{$c->stash->{errors}} ) {
<li><%= $error %></li>
% }
</ul>
</div>
% }
<form autocomplete="off" style="margin-top: 1.5em" method="POST" action="<%= $c->url_for( 'do_forgot' ) %>">
<%= include '_base/form/input', type => 'email', name => 'email',
title => 'Email Address',
help => '',
value => $c->stash->{form_email}
%>
<button type="submit" class="btn btn-primary float-end">Reset Password</button>
</form>

@ -0,0 +1,32 @@
% layout 'standard', title => 'Register', sb_active => 'login';
<h2 class="mt-5 display-6 mb-4">Login</h2>
% if ( $c->stash->{errors} ) {
<div style="margin-top: 2em" class="alert alert-danger" role="alert">
There were errors with your request that could not be resolved:
<ul>
% for my $error ( @{$c->stash->{errors}} ) {
<li><%= $error %></li>
% }
</ul>
</div>
% }
<form autocomplete="off" style="margin-top: 1.5em" method="POST" action="<%= $c->url_for( 'do_login' ) %>">
<%= include '_base/form/input', type => 'email', name => 'email',
title => 'Email Address',
help => '',
value => $c->stash->{form_email}
%>
<%= include '_base/form/input', type => 'password', name => 'password',
title => 'Password',
help => '',
value => $c->stash->{form_password}
%>
<button type="submit" class="btn btn-primary float-end">Login</button>
</form>

@ -0,0 +1,45 @@
% layout 'standard', title => 'Register', sb_active => 'register';
<h2 class="mt-5 display-6 mb-4">Create an account</h2>
% if ( $c->stash->{errors} ) {
<div style="margin-top: 2em" class="alert alert-danger" role="alert">
There were errors with your request that could not be resolved:
<ul>
% for my $error ( @{$c->stash->{errors}} ) {
<li><%= $error %></li>
% }
</ul>
</div>
% }
<form autocomplete="off" style="margin-top: 1.5em" method="POST" action="<%= $c->url_for( 'do_register' ) %>">
<%= include '_base/form/input', type => 'text', name => 'name',
title => 'Your name',
help => '',
value => $c->stash->{form_name}
%>
<%= include '_base/form/input', type => 'email', name => 'email',
title => 'Email Address',
help => '',
value => $c->stash->{form_email}
%>
<%= include '_base/form/input', type => 'password', name => 'password',
title => 'Password',
help => '',
value => $c->stash->{form_password}
%>
<%= include '_base/form/input', type => 'password', name => 'password_confirm',
title => 'Confirm Password',
help => '',
value => $c->stash->{form_password_confirm}
%>
<button type="submit" class="btn btn-primary float-end">Create Account</button>
</form>

@ -0,0 +1,38 @@
% layout 'standard', title => 'Reset Password', sb_active => 'forgot';
<h2 class="mt-5 display-6 mb-4">Reset Password</h2>
% if ( $c->stash->{success} ) {
<div style="margin-top: 2em" class="alert alert-success" role="alert">
<%= $c->stash->{success_message} %>
</div>
% }
% if ( $c->stash->{errors} ) {
<div style="margin-top: 2em" class="alert alert-danger" role="alert">
There were errors with your request that could not be resolved:
<ul>
% for my $error ( @{$c->stash->{errors}} ) {
<li><%= $error %></li>
% }
</ul>
</div>
% }
<form autocomplete="off" style="margin-top: 1.5em" method="POST" action="<%= $c->url_for() %>">
<%= include '_base/form/input', type => 'password', name => 'password',
title => 'Enter your new password',
help => '',
value => $c->stash->{form_password}
%>
<%= include '_base/form/input', type => 'password', name => 'password_confirm',
title => 'Confirm your new password',
help => '',
value => $c->stash->{form_password_confirm}
%>
<button type="submit" class="btn btn-primary float-end">Reset Password</button>
</form>

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>TodayChecklist<%= $title ? " - " . $title : "" %></title>
<title>MyJekyllBlog<%= $title ? " - " . $title : "" %></title>
<!-- Bootstrap core CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.1/dist/css/bootstrap.min.css"
@ -33,7 +33,7 @@
</head>
<body>
<header class="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="/">WeightGrapher</a>
<a class="navbar-brand col-md-3 col-lg-2 me-0 px-3" href="/">MyJekyllBlog</a>
<button class="navbar-toggler position-absolute d-md-none collapsed" type="button"
data-bs-toggle="collapse"
data-bs-target="#sidebarMenu"
@ -123,12 +123,14 @@
<span>My Info</span>
</h6>
<ul class="nav flex-column mb-2">
<!--
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "subscription" ? "active" : "" %>" href="<%= $c->url_for( 'show_subscription' ) %>">
<span data-feather="credit-card"></span>
Subscription
</a>
</li>
-->
<li class="nav-item">
<a class="nav-link <%= $sb_active eq "profile" ? "active" : "" %>" href="<%= $c->url_for( 'show_profile' ) %>">
<span data-feather="user"></span>

@ -0,0 +1,9 @@
% layout 'standard', title => 'About', sb_active => 'about';
<h2 class="mt-5 display-6 mb-4">About This Site</h2>
<p class="fs-5 text-muted">Hello Wonderful Internet Person!</p>
<p class="fs-5 text-muted">I’m Kate and I wrote MyJekyllBlog.</p>
<p class="fs-5 text-muted">Thanks for reading!</p>

@ -0,0 +1,10 @@
% layout 'standard', title => 'Contact', sb_active => 'contact';
<header>
<div class="pricing-header mt-5 mb-3 p-3 pb-md-4 mx-auto text-center">
<h1 class="display-4 fw-normal mb-3">Let's Chat</h1>
<p class="fs-5 text-muted">
hello@myjekyllblog.com
</p>
</div>
</header>

@ -2,9 +2,9 @@
<header>
<div class="pricing-header mt-5 mb-3 p-3 pb-md-4 mx-auto text-center">
<h1 class="display-4 fw-normal mb-3">Welcome To WeightGrapher</h1>
<h1 class="display-4 fw-normal mb-3">Welcome To MyJekyllBlog</h1>
<p class="fs-5 text-muted">
WeightGrapher gives you the insight to understand how your activity impacts your weight
MyJekyllBlog gives you a content editor and web hosting for your Jekyll blog.
</p>
</div>
</header>

@ -0,0 +1,73 @@
% layout 'standard', title => 'Pricing', sb_active => 'pricing';
<header>
<div class="pricing-header mt-5 mb-3 p-3 pb-md-4 mx-auto text-center">
<h1 class="display-4 fw-normal mb-3">Plan Pricing</h1>
<p class="fs-5 text-muted">
After you've registered, you can choose to subscribe to the personal plan for unlimited documents and templates.
</p>
<p class="fs-5 text-muted">
Free accounts otherwise have access to all of the same functionality.
</p>
</div>
</header>
<main>
<div class="row row-cols-1 row-cols-md-2 mb-3 text-center">
<div class="col">
<div class="card mb-4 rounded-3 shadow-sm">
<div class="card-header py-3">
<h4 class="my-0 fw-normal">Free</h4>
</div>
<div class="card-body">
<h1 class="card-title pricing-card-title">$0<small class="text-muted fw-light"></small></h1>
<ul class="list-unstyled mt-3 mb-4">
<li>7 Documents</li>
<li>5 Templates</li>
</ul>
<a href="<%= $c->url_for( 'show_register' ) %>" class="button w-100 btn btn-lg btn-outline-primary">Register</a>
</div>
</div>
</div>
<div class="col">
<div class="card mb-4 rounded-3 shadow-sm border-primary">
<div class="card-header py-3 text-white bg-primary border-primary">
<h4 class="my-0 fw-normal">Personal</h4>
</div>
<div class="card-body">
<h1 class="card-title pricing-card-title">$4<small class="text-muted fw-light">/mo</small></h1>
<ul class="list-unstyled mt-3 mb-4">
<li>Unlimited Documents</li>
<li>Unlimited Templates</li>
</ul>
<a href="<%= $c->url_for( 'show_register' ) %>" class="button w-100 btn btn-lg btn-primary">Register</a>
</div>
</div>
</div>
<!--
<div class="col">
<div class="card mb-4 rounded-3 shadow-sm">
<div class="card-header py-3">
<h4 class="my-0 fw-normal">Hosted</h4>
</div>
<div class="card-body">
<h1 class="card-title pricing-card-title">$97<small class="text-muted fw-light">/mo</small></h1>
<ul class="list-unstyled mt-3 mb-4">
<li>Your own TodayChecklist on your own domain</li>
<li>Unlimited Users/Documents/Templates</li>
<li>Create Default Templates</li>
</ul>
<a href="<%= $c->url_for( 'show_contact' ) %>" class="button w-100 btn btn-lg btn-outline-primary">Contact</a>
</div>
</div>
</div>
-->
</div>
<div class="pricing-header mt-5 p-3 pb-md-4 mx-auto text-center">
<h2 class="display-6 text-center mb-4">Run It Yourself</h2>
<p class="fs-5 text-muted">
TodayChecklist is an open-source application. You can find the source and documentation <a target="_blank" href="https://github.com/symkat/TodayChecklist">on GitHub.</a>
</p>
</div>
</main>

@ -0,0 +1,45 @@
% layout 'standard', title => 'Change Password', sb_active => 'password';
<h2 class="mt-5 display-6 mb-4">Change your password</h2>
% if ( $c->stash->{success} ) {
<div style="margin-top: 2em" class="alert alert-success" role="alert">
<%= $c->stash->{success_message} %>
</div>
% }
% if ( $c->stash->{errors} ) {
<div style="margin-top: 2em" class="alert alert-danger" role="alert">
There were errors with your request that could not be resolved:
<ul>
% for my $error ( @{$c->stash->{errors}} ) {
<li><%= $error %></li>
% }
</ul>
</div>
% }
<form style="margin-top: 1.5em" method="POST" action="<%= $c->url_for( 'do_change_password' ) %>">
<%= include '_base/form/input', type => 'password', name => 'password',
title => 'Your current password',
help => '',
value => $c->stash->{form_password}
%>
<%= include '_base/form/input', type => 'password', name => 'new_password',
title => 'Your new password',
help => '',
value => $c->stash->{form_new_password}
%>
<%= include '_base/form/input', type => 'password', name => 'password_confirm',
title => 'Confirm your new password',
help => '',
value => $c->stash->{form_password_confirm}
%>
<button type="submit" class="btn btn-primary float-end">Change Password</button>
</form>

@ -0,0 +1,43 @@
% layout 'standard', title => 'Profile', sb_active => 'profile';
<h2 class="mt-5 display-6 mb-4">Change your name or email</h2>
% if ( $c->stash->{success} ) {
<div style="margin-top: 2em" class="alert alert-success" role="alert">
<%= $c->stash->{success_message} %>
</div>
% }
% if ( $c->stash->{errors} ) {
<div style="margin-top: 2em" class="alert alert-danger" role="alert">
There were errors with your request that could not be resolved:
<ul>
% for my $error ( @{$c->stash->{errors}} ) {
<li><%= $error %></li>
% }
</ul>
</div>
% }
<form style="margin-top: 1.5em" method="POST" action="<%= $c->url_for( 'do_profile' ) %>">
<%= include '_base/form/input', type => 'text', name => 'name',
title => 'Your name',
help => '',
value => $c->stash->{form_name},
%>
<%= include '_base/form/input', type => 'email', name => 'email',
title => 'Email Address',
help => '',
value => $c->stash->{form_email},
%>
<%= include '_base/form/input', type => 'password', name => 'password',
title => 'Your password (required for these changes)',
help => '',
value => $c->stash->{form_password}
%>
<button type="submit" class="btn btn-primary float-end">Update Profile</button>
</form>
Loading…
Cancel
Save