parent
58d8bd5df9
commit
8a49af1ff2
21 changed files with 876 additions and 5 deletions
@ -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'; |
||||
|
||||
@ -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> |
||||
@ -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> |
||||
@ -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…
Reference in new issue