Move files, complete PHP migration
The application works the same way as the F# version
This commit is contained in:
parent
9421bb2035
commit
20ad50928a
@ -551,9 +551,9 @@ let routes = [
|
|||||||
]
|
]
|
||||||
PATCH [
|
PATCH [
|
||||||
route "" Request.update // done
|
route "" Request.update // done
|
||||||
routef "/%s/cancel-snooze" Request.cancelSnooze
|
routef "/%s/cancel-snooze" Request.cancelSnooze // done
|
||||||
routef "/%s/prayed" Request.prayed // done
|
routef "/%s/prayed" Request.prayed // done
|
||||||
routef "/%s/show" Request.show
|
routef "/%s/show" Request.show // done
|
||||||
routef "/%s/snooze" Request.snooze // done
|
routef "/%s/snooze" Request.snooze // done
|
||||||
]
|
]
|
||||||
POST [
|
POST [
|
||||||
|
@ -15,7 +15,9 @@
|
|||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"MyPrayerJournal\\": "lib/"
|
"MyPrayerJournal\\": "lib/",
|
||||||
|
"MyPrayerJournal\\Domain\\": "lib/Domain",
|
||||||
|
"MyPrayerJournal\\UI\\": "lib/UI"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
use BitBadger\PDODocument\{Configuration, Custom, Definition, Document, Mode};
|
use BitBadger\PDODocument\{Configuration, Custom, Definition, Document, Mode};
|
||||||
use BitBadger\PDODocument\Mapper\ArrayMapper;
|
use BitBadger\PDODocument\Mapper\ArrayMapper;
|
||||||
use MyPrayerJournal\{History, Note, Recurrence, RecurrencePeriod, Request, RequestAction, Table};
|
use MyPrayerJournal\Domain\{History, Note, Recurrence, RecurrencePeriod, Request, RequestAction};
|
||||||
|
use MyPrayerJournal\Table;
|
||||||
|
|
||||||
require 'start.php';
|
require 'start.php';
|
||||||
|
|
||||||
|
@ -5,10 +5,19 @@ namespace MyPrayerJournal;
|
|||||||
use Auth0\SDK\Auth0;
|
use Auth0\SDK\Auth0;
|
||||||
use Auth0\SDK\Exception\ConfigurationException;
|
use Auth0\SDK\Exception\ConfigurationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* myPrayerJournal-specific authorization functions
|
||||||
|
*/
|
||||||
class Auth
|
class Auth
|
||||||
{
|
{
|
||||||
|
/** @var Auth0|null The Auth0 client to use for requests (initialized on first use) */
|
||||||
private static ?Auth0 $auth0 = null;
|
private static ?Auth0 $auth0 = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current Auth0 client
|
||||||
|
*
|
||||||
|
* @return Auth0 The current Auth0 client
|
||||||
|
*/
|
||||||
public static function client(): Auth0
|
public static function client(): Auth0
|
||||||
{
|
{
|
||||||
if (is_null(self::$auth0)) {
|
if (is_null(self::$auth0)) {
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace MyPrayerJournal;
|
namespace MyPrayerJournal\Domain;
|
||||||
|
|
||||||
use JsonSerializable;
|
use JsonSerializable;
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace MyPrayerJournal;
|
namespace MyPrayerJournal\Domain;
|
||||||
|
|
||||||
use BitBadger\PDODocument\DocumentException;
|
use BitBadger\PDODocument\DocumentException;
|
||||||
|
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace MyPrayerJournal;
|
namespace MyPrayerJournal\Domain;
|
||||||
|
|
||||||
use DateInterval;
|
use DateInterval;
|
||||||
use JsonSerializable;
|
use JsonSerializable;
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace MyPrayerJournal;
|
namespace MyPrayerJournal\Domain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The type of recurrence a request can have
|
* The type of recurrence a request can have
|
@ -1,11 +1,12 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace MyPrayerJournal;
|
namespace MyPrayerJournal\Domain;
|
||||||
|
|
||||||
use BitBadger\PDODocument\{Custom, DocumentException, DocumentList, Find, Mapper\DocumentMapper};
|
use BitBadger\PDODocument\{Custom, DocumentException, DocumentList, Find, Mapper\DocumentMapper};
|
||||||
use DateTimeImmutable;
|
use DateTimeImmutable;
|
||||||
use Exception;
|
use Exception;
|
||||||
use JsonSerializable;
|
use JsonSerializable;
|
||||||
|
use MyPrayerJournal\Table;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
/**
|
/**
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace MyPrayerJournal;
|
namespace MyPrayerJournal\Domain;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An action taken on a prayer request
|
* An action taken on a prayer request
|
238
src/lib/UI.php
238
src/lib/UI.php
@ -1,238 +0,0 @@
|
|||||||
<?php declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace MyPrayerJournal;
|
|
||||||
|
|
||||||
use BitBadger\PDODocument\{DocumentException, DocumentList};
|
|
||||||
use DateTimeImmutable;
|
|
||||||
use DateTimeZone;
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* User interface building blocks
|
|
||||||
*/
|
|
||||||
class UI
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Generate a material icon
|
|
||||||
*
|
|
||||||
* @param string $name The name of the material icon
|
|
||||||
* @return string The material icon wrapped in a `span` tag
|
|
||||||
*/
|
|
||||||
public static function icon(string $name): string {
|
|
||||||
return "<span class=material-icons>$name</span>";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the journal items for the current user
|
|
||||||
*
|
|
||||||
* @throws DocumentException If any is encountered
|
|
||||||
*/
|
|
||||||
public static function journal(): void
|
|
||||||
{
|
|
||||||
Layout::bareHead();
|
|
||||||
$reqs = Request::forJournal();
|
|
||||||
if ($reqs->hasItems()) { ?>
|
|
||||||
<section id=journalItems class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-3" hx-target=this
|
|
||||||
hx-swap=outerHTML aria-label="Prayer Requests"><?php
|
|
||||||
$spacer = '<span> </span>';
|
|
||||||
foreach ($reqs->items() as /** @var Request $req */ $req) { ?>
|
|
||||||
<div class=col>
|
|
||||||
<div class="card h-100">
|
|
||||||
<div class="card-header p-0 d-flex" role=toolbar><?php
|
|
||||||
self::pageLink("/request/edit?id=$req->id", self::icon('edit'),
|
|
||||||
['class' => 'btn btn-secondary', 'title' => 'Edit Request']); ?>
|
|
||||||
<?=$spacer?>
|
|
||||||
<button type=button class="btn btn-secondary" title="Add Notes" data-bs-toggle=modal
|
|
||||||
data-bs-target=#notesModal hx-get="/components/request/add-note?id=<?=$req->id?>"
|
|
||||||
hx-target=#notesBody hx-swap=innerHTML><?=self::icon('comment');?></button>
|
|
||||||
<?=$spacer?>
|
|
||||||
<button type=button class="btn btn-secondary" title="Snooze Request" data-bs-toggle=modal
|
|
||||||
data-bs-target=#snoozeModal hx-get="/components/request/snooze?id=<?=$req->id?>"
|
|
||||||
hx-target=#snoozeBody hx-swap=innerHTML><?=self::icon('schedule');?></button>
|
|
||||||
<div class=flex-grow-1></div>
|
|
||||||
<button type=button class="btn btn-success w-25" hx-patch="/request/prayed?id=<?=$req->id?>"
|
|
||||||
title="Mark as Prayed"><?=self::icon('done');?></button>
|
|
||||||
</div>
|
|
||||||
<div class=card-body>
|
|
||||||
<p class=request-text><?=htmlentities($req->currentText());?>
|
|
||||||
</div>
|
|
||||||
<div class="card-footer text-end text-muted px-1 py-0">
|
|
||||||
<em><?php
|
|
||||||
$lastPrayed = $req->lastPrayed();
|
|
||||||
echo 'last ' . (is_null($lastPrayed) ? 'activity': 'prayed') . ' ';
|
|
||||||
self::relativeDate($lastPrayed ?? $req->history[0]->asOf); ?>
|
|
||||||
</em>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div><?php
|
|
||||||
} ?>
|
|
||||||
</section><?php
|
|
||||||
} else {
|
|
||||||
UI::noResults('No Active Requests', '/request/edit?id=new', 'Add a Request', <<<'TEXT'
|
|
||||||
You have no requests to be shown; see the “Active” link above for snoozed or deferred
|
|
||||||
requests, and the “Answered” link for answered requests
|
|
||||||
TEXT);
|
|
||||||
}
|
|
||||||
Layout::bareFoot();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a card when there are no results found
|
|
||||||
*/
|
|
||||||
public static function noResults(string $heading, string $link, string $buttonText, string $text): void
|
|
||||||
{ ?>
|
|
||||||
<div class=card>
|
|
||||||
<h5 class=card-header><?=$heading?></h5>
|
|
||||||
<div class="card-body text-center">
|
|
||||||
<p class=card-text><?=$text?></p><?php
|
|
||||||
self::pageLink($link, $buttonText, ['class' => 'btn btn-primary']); ?>
|
|
||||||
</div>
|
|
||||||
</div><?php
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate a link to a page within myPrayerJournal
|
|
||||||
*
|
|
||||||
* @param string $href The URL for the link
|
|
||||||
* @param string $text The text for the link
|
|
||||||
* @param array $attrs Any additional attributes that should be placed on the `a` tag
|
|
||||||
*/
|
|
||||||
public static function pageLink(string $href, string $text, array $attrs = []): void
|
|
||||||
{ ?>
|
|
||||||
<a href="<?=$href?>" hx-get="<?=$href?>" hx-target=#top hx-swap=innerHTML hx-push-url=true<?php
|
|
||||||
foreach ($attrs as $key => $value) echo " $key=\"" . htmlspecialchars($value) . "\""; ?>><?=$text?></a><?php
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @throws Exception
|
|
||||||
*/
|
|
||||||
public static function relativeDate(string $date): void
|
|
||||||
{
|
|
||||||
$parsed = new DateTimeImmutable($date);
|
|
||||||
$inZone = $parsed->setTimezone(new DateTimeZone($_REQUEST['time_zone']));
|
|
||||||
echo '<span title="' . date_format($inZone, 'l, F j, Y \a\t g:ia T') . '">'
|
|
||||||
. self::formatDistance('now', $parsed) . '</span>';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Many thanks to date-fns (https://date-fns.org) for this logic
|
|
||||||
/**
|
|
||||||
* Format the distance between two dates
|
|
||||||
*
|
|
||||||
* @param string|DateTimeImmutable $from The starting date/time
|
|
||||||
* @param string|DateTimeImmutable $to The ending date/time
|
|
||||||
* @return string The distance between two dates
|
|
||||||
* @throws Exception If date/time objects cannot be created
|
|
||||||
*/
|
|
||||||
public static function formatDistance(string|DateTimeImmutable $from, string|DateTimeImmutable $to): string
|
|
||||||
{
|
|
||||||
$aDay = 1_440.0;
|
|
||||||
$almost2Days = 2_520.0;
|
|
||||||
$aMonth = 43_200.0;
|
|
||||||
$twoMonths = 86_400.0;
|
|
||||||
|
|
||||||
$dtFrom = is_string($from) ? new DateTimeImmutable($from) : $from;
|
|
||||||
$dtTo = is_string($to) ? new DateTimeImmutable($to) : $to;
|
|
||||||
$minutes = abs($dtFrom->getTimestamp() - $dtTo->getTimestamp()) / 60;
|
|
||||||
$months = round($minutes / $aMonth);
|
|
||||||
$years = round($months / 12);
|
|
||||||
|
|
||||||
$typeAndNumber = match (true) {
|
|
||||||
$minutes < 1.0 => [FormatDistanceToken::LessThanXMinutes, 1],
|
|
||||||
$minutes < 45.0 => [FormatDistanceToken::XMinutes, round($minutes)],
|
|
||||||
$minutes < 90.0 => [FormatDistanceToken::AboutXHours, 1],
|
|
||||||
$minutes < $aDay => [FormatDistanceToken::AboutXHours, round($minutes / 60)],
|
|
||||||
$minutes < $almost2Days => [FormatDistanceToken::XDays, 1],
|
|
||||||
$minutes < $aMonth => [FormatDistanceToken::XDays, round($minutes / $aDay)],
|
|
||||||
$minutes < $twoMonths => [FormatDistanceToken::AboutXMonths, round($minutes / $aMonth)],
|
|
||||||
$months < 12 => [FormatDistanceToken::XMonths, round($minutes / $aMonth)],
|
|
||||||
$months % 12 < 3 => [FormatDistanceToken::AboutXYears, $years],
|
|
||||||
$months % 12 < 9 => [FormatDistanceToken::OverXYears, $years],
|
|
||||||
default => [FormatDistanceToken::AlmostXYears, $years]
|
|
||||||
};
|
|
||||||
$format = match ($typeAndNumber[0]) {
|
|
||||||
FormatDistanceToken::LessThanXMinutes => ['less than a minute', 'less than %d minutes'],
|
|
||||||
FormatDistanceToken::XMinutes => ['a minute', '%d minutes'],
|
|
||||||
FormatDistanceToken::AboutXHours => ['about an hour', 'about %d hours'],
|
|
||||||
FormatDistanceToken::XHours => ['an hour', '%d hours'],
|
|
||||||
FormatDistanceToken::XDays => ['a day', '%d days'],
|
|
||||||
FormatDistanceToken::AboutXWeeks => ['about a week', 'about %d weeks'],
|
|
||||||
FormatDistanceToken::XWeeks => ['a week', '%d weeks'],
|
|
||||||
FormatDistanceToken::AboutXMonths => ['about a month', 'about %d months'],
|
|
||||||
FormatDistanceToken::XMonths => ['a month', '%d months'],
|
|
||||||
FormatDistanceToken::AboutXYears => ['about a year', 'about %d years'],
|
|
||||||
FormatDistanceToken::XYears => ['a year', '%d years'],
|
|
||||||
FormatDistanceToken::OverXYears => ['over a year', 'over %d years'],
|
|
||||||
FormatDistanceToken::AlmostXYears => ['almost a year', 'almost %d years']
|
|
||||||
};
|
|
||||||
$value = $typeAndNumber[1] == 1 ? $format[0] : sprintf($format[1], $typeAndNumber[1]);
|
|
||||||
return $dtFrom > $dtTo ? "$value ago" : "in $value";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static function requestItem(Request $req): void
|
|
||||||
{
|
|
||||||
$btnClass = "btn btn-light mx-2";
|
|
||||||
$restoreBtn = fn(string $id, string $link, string $title) =>
|
|
||||||
'<button class="' . $btnClass. '" hx-patch="/request/' . $link . '?id=' . $id
|
|
||||||
. '" title="' . htmlspecialchars($title) . '">' . self::icon('restore') . '</button>'; ?>
|
|
||||||
<div class="list-group-item px-0 d-flex flex-row align-items-start" hx-target=this
|
|
||||||
hx-swap=outerHTML><?php
|
|
||||||
self::pageLink("/request/full?id=$req->id", self::icon('description'),
|
|
||||||
['class' => $btnClass, 'title' => 'View Full Request']);
|
|
||||||
if (!$req->isAnswered()) {
|
|
||||||
self::pageLink("/request/edit?id=$req->id", self::icon('edit'),
|
|
||||||
['class' => $btnClass, 'title' => 'Edit Request']);
|
|
||||||
}
|
|
||||||
if ($req->isSnoozed()) {
|
|
||||||
echo $restoreBtn($req->id, 'cancel-snooze', 'Cancel Snooze');
|
|
||||||
} elseif ($req->isPending()) {
|
|
||||||
echo $restoreBtn($req->id, 'show', 'Show Now');
|
|
||||||
}
|
|
||||||
echo '<p class="request-text mb-0">' . $req->currentText();
|
|
||||||
if ($req->isSnoozed() || $req->isPending() || $req->isAnswered()) { ?>
|
|
||||||
<br>
|
|
||||||
<small class=text-muted><em><?php
|
|
||||||
switch (true) {
|
|
||||||
case $req->isSnoozed():
|
|
||||||
echo 'Snooze expires '; self::relativeDate($req->snoozedUntil);
|
|
||||||
break;
|
|
||||||
case $req->isPending():
|
|
||||||
echo 'Request appears next '; self::relativeDate($req->showAfter);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
echo 'Answered '; self::relativeDate($req->history[0]->asOf);
|
|
||||||
} ?>
|
|
||||||
</em></small><?php
|
|
||||||
} ?>
|
|
||||||
</div><?php
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Render the given list of requests
|
|
||||||
*
|
|
||||||
* @param DocumentList<Request> $reqs The list of requests to render
|
|
||||||
* @throws Exception If date/time instances are not valid
|
|
||||||
*/
|
|
||||||
public static function requestList(DocumentList $reqs): void
|
|
||||||
{
|
|
||||||
echo '<div class=list-group>';
|
|
||||||
foreach ($reqs->items() as $req) self::requestItem($req);
|
|
||||||
echo '</div>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum FormatDistanceToken
|
|
||||||
{
|
|
||||||
case LessThanXMinutes;
|
|
||||||
case XMinutes;
|
|
||||||
case AboutXHours;
|
|
||||||
case XHours;
|
|
||||||
case XDays;
|
|
||||||
case AboutXWeeks;
|
|
||||||
case XWeeks;
|
|
||||||
case AboutXMonths;
|
|
||||||
case XMonths;
|
|
||||||
case AboutXYears;
|
|
||||||
case XYears;
|
|
||||||
case OverXYears;
|
|
||||||
case AlmostXYears;
|
|
||||||
}
|
|
175
src/lib/UI/Component.php
Normal file
175
src/lib/UI/Component.php
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MyPrayerJournal\UI;
|
||||||
|
|
||||||
|
use BitBadger\PDODocument\{DocumentException, DocumentList};
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use DateTimeZone;
|
||||||
|
use Exception;
|
||||||
|
use MyPrayerJournal\Domain\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User interface building blocks
|
||||||
|
*/
|
||||||
|
class Component
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Generate a material icon
|
||||||
|
*
|
||||||
|
* @param string $name The name of the material icon
|
||||||
|
* @return string The material icon wrapped in a `span` tag
|
||||||
|
*/
|
||||||
|
public static function icon(string $name): string {
|
||||||
|
return "<span class=material-icons>$name</span>";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the journal items for the current user
|
||||||
|
*
|
||||||
|
* @throws DocumentException If any is encountered
|
||||||
|
*/
|
||||||
|
public static function journal(): void
|
||||||
|
{
|
||||||
|
Layout::bareHead();
|
||||||
|
$reqs = Request::forJournal();
|
||||||
|
if ($reqs->hasItems()) { ?>
|
||||||
|
<section id=journalItems class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 g-3" hx-target=this
|
||||||
|
hx-swap=outerHTML aria-label="Prayer Requests"><?php
|
||||||
|
$spacer = '<span> </span>';
|
||||||
|
foreach ($reqs->items() as /** @var Request $req */ $req) { ?>
|
||||||
|
<div class=col>
|
||||||
|
<div class="card h-100">
|
||||||
|
<div class="card-header p-0 d-flex" role=toolbar>
|
||||||
|
<?=self::pageLink("/request/edit?id=$req->id", self::icon('edit'),
|
||||||
|
['class' => 'btn btn-secondary', 'title' => 'Edit Request'])?>
|
||||||
|
<?=$spacer?>
|
||||||
|
<button type=button class="btn btn-secondary" title="Add Notes" data-bs-toggle=modal
|
||||||
|
data-bs-target=#notesModal hx-get="/components/request/add-note?id=<?=$req->id?>"
|
||||||
|
hx-target=#notesBody hx-swap=innerHTML><?=self::icon('comment');?></button>
|
||||||
|
<?=$spacer?>
|
||||||
|
<button type=button class="btn btn-secondary" title="Snooze Request" data-bs-toggle=modal
|
||||||
|
data-bs-target=#snoozeModal hx-get="/components/request/snooze?id=<?=$req->id?>"
|
||||||
|
hx-target=#snoozeBody hx-swap=innerHTML><?=self::icon('schedule');?></button>
|
||||||
|
<div class=flex-grow-1></div>
|
||||||
|
<button type=button class="btn btn-success w-25" hx-patch="/request/prayed?id=<?=$req->id?>"
|
||||||
|
title="Mark as Prayed"><?=self::icon('done');?></button>
|
||||||
|
</div>
|
||||||
|
<div class=card-body>
|
||||||
|
<p class=request-text><?=htmlentities($req->currentText());?>
|
||||||
|
</div>
|
||||||
|
<div class="card-footer text-end text-muted px-1 py-0">
|
||||||
|
<em><?php
|
||||||
|
$lastPrayed = $req->lastPrayed();
|
||||||
|
echo 'last ' . (is_null($lastPrayed) ? 'activity': 'prayed') . ' '
|
||||||
|
. self::relativeDate($lastPrayed ?? $req->history[0]->asOf); ?>
|
||||||
|
</em>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><?php
|
||||||
|
} ?>
|
||||||
|
</section><?php
|
||||||
|
} else {
|
||||||
|
Component::noResults('No Active Requests', '/request/edit?id=new', 'Add a Request', <<<'TEXT'
|
||||||
|
You have no requests to be shown; see the “Active” link above for snoozed or deferred
|
||||||
|
requests, and the “Answered” link for answered requests
|
||||||
|
TEXT);
|
||||||
|
}
|
||||||
|
Layout::bareFoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a card when there are no results found
|
||||||
|
*/
|
||||||
|
public static function noResults(string $heading, string $link, string $buttonText, string $text): void
|
||||||
|
{ ?>
|
||||||
|
<div class=card>
|
||||||
|
<h5 class=card-header><?=$heading?></h5>
|
||||||
|
<div class="card-body text-center">
|
||||||
|
<p class=card-text><?=$text?></p>
|
||||||
|
<?=self::pageLink($link, $buttonText, ['class' => 'btn btn-primary'])?>
|
||||||
|
</div>
|
||||||
|
</div><?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a link to a page within myPrayerJournal
|
||||||
|
*
|
||||||
|
* @param string $href The URL for the link
|
||||||
|
* @param string $text The text for the link
|
||||||
|
* @param array $attrs Any additional attributes that should be placed on the `a` tag
|
||||||
|
*/
|
||||||
|
public static function pageLink(string $href, string $text, array $attrs = []): string
|
||||||
|
{
|
||||||
|
$extraAttrs = array_reduce(array_keys($attrs),
|
||||||
|
fn($acc, $key) => $acc . sprintf(' %s="%s"', $key, $attrs[$key]), '');
|
||||||
|
return sprintf('<a href="%s" hx-get="%s" hx-target=#top hx-swap=innerHTML hx-push-url=true%s>%s</a>', $href,
|
||||||
|
$href, $extraAttrs, $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a relative date with a tooltip with the actual date/time
|
||||||
|
*
|
||||||
|
* @return string The HTML for the relative date
|
||||||
|
* @throws Exception If the date/time cannot be parsed
|
||||||
|
*/
|
||||||
|
public static function relativeDate(string $date): string
|
||||||
|
{
|
||||||
|
$parsed = new DateTimeImmutable($date);
|
||||||
|
$inZone = $parsed->setTimezone(new DateTimeZone($_REQUEST['time_zone']));
|
||||||
|
return sprintf('<span title="%s">%s</span>', date_format($inZone, 'l, F j, Y \a\t g:ia T'),
|
||||||
|
RelativeDate::between('now', $parsed));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a request list item
|
||||||
|
*
|
||||||
|
* @param Request $req The request on which the list items should be based
|
||||||
|
* @throws Exception If date/time values cannot be parsed
|
||||||
|
*/
|
||||||
|
public static function requestItem(Request $req): void
|
||||||
|
{
|
||||||
|
$btnClass = "btn btn-light mx-2";
|
||||||
|
$restoreBtn = fn(string $id, string $link, string $title) => sprintf(
|
||||||
|
'<button class="%s" hx-patch="/request/%s?id=%s" title="%s" hx-target=#req-%s hx-swap=outerHTML>%s</button>',
|
||||||
|
$btnClass, $link, $id, htmlspecialchars($title), $id, self::icon('restore')); ?>
|
||||||
|
<div class="list-group-item px-0 d-flex flex-row align-items-start" id=req-<?=$req->id?>><?php
|
||||||
|
echo self::pageLink("/request/full?id=$req->id", self::icon('description'),
|
||||||
|
['class' => $btnClass, 'title' => 'View Full Request']);
|
||||||
|
if (!$req->isAnswered()) {
|
||||||
|
echo self::pageLink("/request/edit?id=$req->id", self::icon('edit'),
|
||||||
|
['class' => $btnClass, 'title' => 'Edit Request']);
|
||||||
|
}
|
||||||
|
if ($req->isSnoozed()) {
|
||||||
|
echo $restoreBtn($req->id, 'cancel-snooze', 'Cancel Snooze');
|
||||||
|
} elseif ($req->isPending()) {
|
||||||
|
echo $restoreBtn($req->id, 'show', 'Show Now');
|
||||||
|
}
|
||||||
|
echo '<p class="request-text mb-0">' . $req->currentText();
|
||||||
|
if ($req->isSnoozed() || $req->isPending() || $req->isAnswered()) { ?>
|
||||||
|
<br>
|
||||||
|
<small class=text-muted><em><?php
|
||||||
|
if ($req->isSnoozed()) {
|
||||||
|
echo 'Snooze expires ' . self::relativeDate($req->snoozedUntil);
|
||||||
|
} elseif ($req->isPending()) {
|
||||||
|
echo 'Request appears next ' . self::relativeDate($req->showAfter);
|
||||||
|
} else {
|
||||||
|
echo 'Answered ' . self::relativeDate($req->history[0]->asOf);
|
||||||
|
} ?>
|
||||||
|
</em></small><?php
|
||||||
|
} ?>
|
||||||
|
</div><?php
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the given list of requests
|
||||||
|
*
|
||||||
|
* @param DocumentList<Request> $reqs The list of requests to render
|
||||||
|
* @throws Exception If date/time instances are not valid
|
||||||
|
*/
|
||||||
|
public static function requestList(DocumentList $reqs): void
|
||||||
|
{
|
||||||
|
echo '<div class=list-group>';
|
||||||
|
foreach ($reqs->items() as $req) self::requestItem($req);
|
||||||
|
echo '</div>';
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +1,10 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
namespace MyPrayerJournal;
|
namespace MyPrayerJournal\UI;
|
||||||
|
|
||||||
use BitBadger\PDODocument\Custom;
|
use BitBadger\PDODocument\Custom;
|
||||||
use BitBadger\PDODocument\Mapper\ExistsMapper;
|
use BitBadger\PDODocument\Mapper\ExistsMapper;
|
||||||
|
use MyPrayerJournal\Table;
|
||||||
|
|
||||||
class Layout
|
class Layout
|
||||||
{
|
{
|
||||||
@ -67,8 +68,7 @@ class Layout
|
|||||||
str_starts_with($_SERVER['PHP_SELF'], $url) => ['class' => 'is-active-route'],
|
str_starts_with($_SERVER['PHP_SELF'], $url) => ['class' => 'is-active-route'],
|
||||||
default => []
|
default => []
|
||||||
};
|
};
|
||||||
echo '<li class=nav-item>';
|
echo '<li class=nav-item>' . Component::pageLink($url, $text, $classAttr);
|
||||||
UI::pageLink($url, $text, $classAttr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -85,9 +85,10 @@ class Layout
|
|||||||
SQL, [':userId' => $_SESSION['user_id']], new ExistsMapper())
|
SQL, [':userId' => $_SESSION['user_id']], new ExistsMapper())
|
||||||
: false; ?>
|
: false; ?>
|
||||||
<nav class="navbar navbar-dark" role="navigation">
|
<nav class="navbar navbar-dark" role="navigation">
|
||||||
<div class=container-fluid><?php
|
<div class=container-fluid>
|
||||||
UI::pageLink('/', '<span class=m>my</span><span class=p>Prayer</span><span class=j>Journal</span>',
|
<?=Component::pageLink('/',
|
||||||
['class' => 'navbar-brand']); ?>
|
'<span class=m>my</span><span class=p>Prayer</span><span class=j>Journal</span>',
|
||||||
|
['class' => 'navbar-brand'])?>
|
||||||
<ul class="navbar-nav me-auto d-flex flex-row"><?php
|
<ul class="navbar-nav me-auto d-flex flex-row"><?php
|
||||||
if (key_exists('user_id', $_SESSION)) {
|
if (key_exists('user_id', $_SESSION)) {
|
||||||
self::navLink('/journal', 'Journal');
|
self::navLink('/journal', 'Journal');
|
||||||
@ -128,11 +129,9 @@ class Layout
|
|||||||
<footer class=container-fluid>
|
<footer class=container-fluid>
|
||||||
<p class="text-muted text-end">
|
<p class="text-muted text-end">
|
||||||
myPrayerJournal <?=self::displayVersion();?><br>
|
myPrayerJournal <?=self::displayVersion();?><br>
|
||||||
<em><small><?php
|
<em><small>
|
||||||
UI::pageLink('/legal/privacy-policy', 'Privacy Policy');
|
<?=Component::pageLink('/legal/privacy-policy', 'Privacy Policy')?> •
|
||||||
echo ' • ';
|
<?=Component::pageLink('/legal/terms-of-service', 'Terms of Service')?> •
|
||||||
UI::pageLink('/legal/terms-of-service', 'Terms of Service');
|
|
||||||
echo ' • '; ?>
|
|
||||||
<a href=https://git.bitbadger.solutions/bit-badger/myPrayerJournal target=_blank
|
<a href=https://git.bitbadger.solutions/bit-badger/myPrayerJournal target=_blank
|
||||||
rel=noopener>Developed</a> and hosted by
|
rel=noopener>Developed</a> and hosted by
|
||||||
<a href=https://bitbadger.solutions target=_blank rel=noopener>Bit Badger Solutions</a>
|
<a href=https://bitbadger.solutions target=_blank rel=noopener>Bit Badger Solutions</a>
|
66
src/lib/UI/RelativeDate.php
Normal file
66
src/lib/UI/RelativeDate.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace MyPrayerJournal\UI;
|
||||||
|
|
||||||
|
use DateTimeImmutable;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A type of relative date, along with the formatting strings
|
||||||
|
*/
|
||||||
|
enum RelativeDate: string
|
||||||
|
{
|
||||||
|
case LessThanXMinutes = 'less than a minute|less than %d minutes';
|
||||||
|
case XMinutes = 'a minute|%d minutes';
|
||||||
|
case AboutXHours = 'about an hour|about %d hours';
|
||||||
|
case XHours = 'an hour|%d hours';
|
||||||
|
case XDays = 'a day|%d days';
|
||||||
|
case AboutXWeeks = 'about a week|about %d weeks';
|
||||||
|
case XWeeks = 'a week|%d weeks';
|
||||||
|
case AboutXMonths = 'about a month|about %d months';
|
||||||
|
case XMonths = 'a month|%d months';
|
||||||
|
case AboutXYears = 'about a year|about %d years';
|
||||||
|
case XYears = 'a year|%d years';
|
||||||
|
case OverXYears = 'over a year|over %d years';
|
||||||
|
case AlmostXYears = 'almost a year|almost %d years';
|
||||||
|
|
||||||
|
// Many thanks to date-fns (https://date-fns.org) for this logic
|
||||||
|
/**
|
||||||
|
* Format the distance between two dates
|
||||||
|
*
|
||||||
|
* @param string|DateTimeImmutable $from The starting date/time
|
||||||
|
* @param string|DateTimeImmutable $to The ending date/time
|
||||||
|
* @return string The distance between two dates
|
||||||
|
* @throws Exception If date/time objects cannot be created
|
||||||
|
*/
|
||||||
|
public static function between(string|DateTimeImmutable $from, string|DateTimeImmutable $to): string
|
||||||
|
{
|
||||||
|
$aDay = 1_440.0;
|
||||||
|
$almost2Days = 2_520.0;
|
||||||
|
$aMonth = 43_200.0;
|
||||||
|
$twoMonths = 86_400.0;
|
||||||
|
|
||||||
|
$dtFrom = is_string($from) ? new DateTimeImmutable($from) : $from;
|
||||||
|
$dtTo = is_string($to) ? new DateTimeImmutable($to) : $to;
|
||||||
|
$minutes = abs($dtFrom->getTimestamp() - $dtTo->getTimestamp()) / 60;
|
||||||
|
$months = round($minutes / $aMonth);
|
||||||
|
$years = round($months / 12);
|
||||||
|
|
||||||
|
[$type, $number] = match (true) {
|
||||||
|
$minutes < 1.0 => [RelativeDate::LessThanXMinutes, 1],
|
||||||
|
$minutes < 45.0 => [RelativeDate::XMinutes, round($minutes)],
|
||||||
|
$minutes < 90.0 => [RelativeDate::AboutXHours, 1],
|
||||||
|
$minutes < $aDay => [RelativeDate::AboutXHours, round($minutes / 60)],
|
||||||
|
$minutes < $almost2Days => [RelativeDate::XDays, 1],
|
||||||
|
$minutes < $aMonth => [RelativeDate::XDays, round($minutes / $aDay)],
|
||||||
|
$minutes < $twoMonths => [RelativeDate::AboutXMonths, round($minutes / $aMonth)],
|
||||||
|
$months < 12 => [RelativeDate::XMonths, round($minutes / $aMonth)],
|
||||||
|
$months % 12 < 3 => [RelativeDate::AboutXYears, $years],
|
||||||
|
$months % 12 < 9 => [RelativeDate::OverXYears, $years],
|
||||||
|
default => [RelativeDate::AlmostXYears, $years]
|
||||||
|
};
|
||||||
|
[$singular, $plural] = explode('|', $type->value);
|
||||||
|
$value = $number == 1 ? $singular : sprintf($plural, $number);
|
||||||
|
return $dtFrom > $dtTo ? "$value ago" : "in $value";
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,11 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Auth, UI};
|
use MyPrayerJournal\Auth;
|
||||||
|
use MyPrayerJournal\UI\Component;
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
|
|
||||||
Auth::requireUser(false);
|
Auth::requireUser(false);
|
||||||
|
|
||||||
UI::journal();
|
Component::journal();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Auth, Layout, Request};
|
use MyPrayerJournal\UI\Layout;
|
||||||
|
|
||||||
require '../../../start.php';
|
require '../../../start.php';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Layout, UI};
|
use MyPrayerJournal\UI\{Component, Layout};
|
||||||
|
|
||||||
require '../../../start.php';
|
require '../../../start.php';
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ Layout::bareHead();?>
|
|||||||
<p class=text-center><strong>Prior Notes for This Request</strong><?php
|
<p class=text-center><strong>Prior Notes for This Request</strong><?php
|
||||||
if (sizeof($req->notes) > 0) {
|
if (sizeof($req->notes) > 0) {
|
||||||
foreach ($req->notes as $note) { ?>
|
foreach ($req->notes as $note) { ?>
|
||||||
<p><small class=text-muted><?php UI::relativeDate($note->asOf); ?></small><br>
|
<p><small class=text-muted><?=Component::relativeDate($note->asOf)?></small><br>
|
||||||
<?=htmlentities($note->text)?><?php
|
<?=htmlentities($note->text)?><?php
|
||||||
}
|
}
|
||||||
} else { ?>
|
} else { ?>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\Layout;
|
use MyPrayerJournal\UI\Layout;
|
||||||
|
|
||||||
require '../../../start.php';
|
require '../../../start.php';
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\Layout;
|
use MyPrayerJournal\UI\Layout;
|
||||||
|
|
||||||
require '../start.php';
|
require '../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\Layout;
|
use MyPrayerJournal\UI\Layout;
|
||||||
|
|
||||||
require '../start.php';
|
require '../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Auth, Layout, UI};
|
use MyPrayerJournal\Auth;
|
||||||
|
use MyPrayerJournal\UI\{Component, Layout};
|
||||||
|
|
||||||
require '../start.php';
|
require '../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
@ -12,9 +13,9 @@ $name = $user['given_name'] ?? 'Your';
|
|||||||
Layout::pageHead('Journal'); ?>
|
Layout::pageHead('Journal'); ?>
|
||||||
<article class="container-fluid mt-3">
|
<article class="container-fluid mt-3">
|
||||||
<h2 class=pb-3><?=$name?><?=$name == 'Your' ? '' : '’s'?> Prayer Journal</h2>
|
<h2 class=pb-3><?=$name?><?=$name == 'Your' ? '' : '’s'?> Prayer Journal</h2>
|
||||||
<p class="pb-3 text-center"><?php
|
<p class="pb-3 text-center">
|
||||||
UI::pageLink('/request/edit?id=new', UI::icon('add_box') . ' Add a Prayer Request',
|
<?=Component::pageLink('/request/edit?id=new', Component::icon('add_box') . ' Add a Prayer Request',
|
||||||
['class' => 'btn btn-primary']); ?>
|
['class' => 'btn btn-primary'])?>
|
||||||
<p hx-get=/components/journal-items hx-swap=outerHTML hx-trigger=load hx-target=this>
|
<p hx-get=/components/journal-items hx-swap=outerHTML hx-trigger=load hx-target=this>
|
||||||
Loading your prayer journal…
|
Loading your prayer journal…
|
||||||
<div id=notesModal class="modal fade" tabindex=-1 aria-labelledby=nodesModalLabel aria-hidden=true>
|
<div id=notesModal class="modal fade" tabindex=-1 aria-labelledby=nodesModalLabel aria-hidden=true>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\Layout;
|
use MyPrayerJournal\UI\Layout;
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Layout, UI};
|
use MyPrayerJournal\UI\{Component, Layout};
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
@ -24,7 +24,7 @@ Layout::pageHead('Terms of Service'); ?>
|
|||||||
myPrayerJournal is a service that allows individuals to enter and amend their prayer requests. It
|
myPrayerJournal is a service that allows individuals to enter and amend their prayer requests. It
|
||||||
requires no registration by itself, but access is granted based on a successful login with an
|
requires no registration by itself, but access is granted based on a successful login with an
|
||||||
external identity provider. See
|
external identity provider. See
|
||||||
<?php UI::pageLink('/legal/privacy-policy', 'our privacy policy'); ?> for details on how that
|
<?=Component::pageLink('/legal/privacy-policy', 'our privacy policy')?> for details on how that
|
||||||
information is accessed and stored.
|
information is accessed and stored.
|
||||||
</div>
|
</div>
|
||||||
<div class=list-group-item>
|
<div class=list-group-item>
|
||||||
@ -53,7 +53,7 @@ Layout::pageHead('Terms of Service'); ?>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<p class=pt-3>
|
<p class=pt-3>
|
||||||
You may also wish to review our <?php UI::pageLink('/legal/privacy-policy', 'privacy policy'); ?> to learn how
|
You may also wish to review our <?=Component::pageLink('/legal/privacy-policy', 'privacy policy')?> to learn how
|
||||||
we handle your data.
|
we handle your data.
|
||||||
</article><?php
|
</article><?php
|
||||||
Layout::pageFoot();
|
Layout::pageFoot();
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Layout, Table, UI};
|
|
||||||
use BitBadger\PDODocument\RemoveFields;
|
use BitBadger\PDODocument\RemoveFields;
|
||||||
|
use MyPrayerJournal\Table;
|
||||||
|
use MyPrayerJournal\UI\{Component, Layout};
|
||||||
|
|
||||||
require '../../../start.php';
|
require '../../start.php';
|
||||||
|
|
||||||
$req = validate_request($_GET['id'], ['GET'], false);
|
$req = validate_request($_GET['id'], ['PATCH'], false);
|
||||||
|
|
||||||
RemoveFields::byId(Table::REQUEST, $req->id, ['snoozedUntil']);
|
RemoveFields::byId(Table::REQUEST, $req->id, ['snoozedUntil']);
|
||||||
|
$req->snoozedUntil = null;
|
||||||
|
|
||||||
// TODO: message
|
// TODO: message
|
||||||
Layout::bareHead();
|
Layout::bareHead();
|
||||||
UI::requestItem($req);
|
Component::requestItem($req);
|
||||||
Layout::bareFoot();
|
Layout::bareFoot();
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Auth, Layout, RecurrencePeriod, Request, RequestAction, UI};
|
use MyPrayerJournal\Auth;
|
||||||
|
use MyPrayerJournal\Domain\{RecurrencePeriod, Request, RequestAction};
|
||||||
|
use MyPrayerJournal\UI\{Component, Layout};
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
@ -97,8 +99,9 @@ Layout::pageHead("$action Prayer Request");?>
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-end pt-3">
|
<div class="text-end pt-3">
|
||||||
<button class="btn btn-primary me-2" type=submit><?=UI::icon('save');?> Save</button><?php
|
<button class="btn btn-primary me-2" type=submit><?=Component::icon('save');?> Save</button>
|
||||||
UI::pageLink($cancelLink, UI::icon('arrow_back') . ' Cancel', ['class' => 'btn btn-secondary ms-2']); ?>
|
<?=Component::pageLink($cancelLink, Component::icon('arrow_back') . ' Cancel',
|
||||||
|
['class' => 'btn btn-secondary ms-2'])?>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</article><?php
|
</article><?php
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{History, Layout, Note, RequestAction, UI};
|
use MyPrayerJournal\Domain\{History, Note, RequestAction};
|
||||||
|
use MyPrayerJournal\UI\{Layout, RelativeDate};
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
|
|
||||||
@ -27,7 +28,7 @@ Layout::pageHead('Full Request');?>
|
|||||||
<h6 class="card-subtitle text-muted mb-2"><?php
|
<h6 class="card-subtitle text-muted mb-2"><?php
|
||||||
if (!is_null($answered)) { ?>
|
if (!is_null($answered)) { ?>
|
||||||
Answered <?=$answered->format('F j, Y')?>
|
Answered <?=$answered->format('F j, Y')?>
|
||||||
(<?=UI::formatDistance('now', $req->history[0]->asOf);?>) •<?php
|
(<?= RelativeDate::between('now', $req->history[0]->asOf);?>) •<?php
|
||||||
} ?>
|
} ?>
|
||||||
Prayed <?=number_format($prayed)?> times • Open <?=number_format($daysOpen)?> days
|
Prayed <?=number_format($prayed)?> times • Open <?=number_format($daysOpen)?> days
|
||||||
</h6>
|
</h6>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Note, Table};
|
|
||||||
use BitBadger\PDODocument\Patch;
|
use BitBadger\PDODocument\Patch;
|
||||||
|
use MyPrayerJournal\Domain\Note;
|
||||||
|
use MyPrayerJournal\Table;
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{History, RecurrencePeriod, RequestAction, Table, UI};
|
|
||||||
use BitBadger\PDODocument\Patch;
|
use BitBadger\PDODocument\Patch;
|
||||||
|
use MyPrayerJournal\Domain\{History, RecurrencePeriod, RequestAction};
|
||||||
|
use MyPrayerJournal\Table;
|
||||||
|
use MyPrayerJournal\UI\Component;
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
|
|
||||||
@ -16,4 +18,4 @@ if ($req->recurrence->period <> RecurrencePeriod::Immediate) {
|
|||||||
}
|
}
|
||||||
Patch::byId(Table::REQUEST, $req->id, $patch);
|
Patch::byId(Table::REQUEST, $req->id, $patch);
|
||||||
|
|
||||||
UI::journal();
|
Component::journal();
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Auth, History, Recurrence, RecurrencePeriod, Request, RequestAction, Table};
|
|
||||||
use BitBadger\PDODocument\{Document, Patch, RemoveFields};
|
use BitBadger\PDODocument\{Document, Patch, RemoveFields};
|
||||||
|
use MyPrayerJournal\{Auth, Table};
|
||||||
|
use MyPrayerJournal\Domain\{History, Recurrence, RecurrencePeriod, Request, RequestAction};
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'POST' && $_SERVER['REQUEST_METHOD'] <> 'PATCH') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'POST' && $_SERVER['REQUEST_METHOD'] <> 'PATCH') not_found();
|
||||||
|
17
src/public/request/show.php
Normal file
17
src/public/request/show.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
|
use BitBadger\PDODocument\RemoveFields;
|
||||||
|
use MyPrayerJournal\Table;
|
||||||
|
use MyPrayerJournal\UI\{Component, Layout};
|
||||||
|
|
||||||
|
require '../../start.php';
|
||||||
|
|
||||||
|
$req = validate_request($_GET['id'], ['PATCH'], false);
|
||||||
|
|
||||||
|
RemoveFields::byId(Table::REQUEST, $req->id, ['showAfter']);
|
||||||
|
$req->showAfter = null;
|
||||||
|
|
||||||
|
// TODO: message
|
||||||
|
Layout::bareHead();
|
||||||
|
Component::requestItem($req);
|
||||||
|
Layout::bareFoot();
|
@ -1,7 +1,8 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Table, UI};
|
|
||||||
use BitBadger\PDODocument\Patch;
|
use BitBadger\PDODocument\Patch;
|
||||||
|
use MyPrayerJournal\Table;
|
||||||
|
use MyPrayerJournal\UI\Component;
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
|
|
||||||
@ -14,4 +15,4 @@ Patch::byId(Table::REQUEST, $req->id, ['snoozedUntil' => $until->format('c')]);
|
|||||||
// TODO: message
|
// TODO: message
|
||||||
|
|
||||||
hide_modal('snooze');
|
hide_modal('snooze');
|
||||||
UI::journal();
|
Component::journal();
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Auth, Layout, Request, UI};
|
use MyPrayerJournal\Auth;
|
||||||
|
use MyPrayerJournal\Domain\Request;
|
||||||
|
use MyPrayerJournal\UI\{Component, Layout};
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
@ -13,9 +15,9 @@ Layout::pageHead('Active Requests'); ?>
|
|||||||
<article class="container mt-3">
|
<article class="container mt-3">
|
||||||
<h2 class=pb-3>Active Requests</h2><?php
|
<h2 class=pb-3>Active Requests</h2><?php
|
||||||
if ($reqs->hasItems()) {
|
if ($reqs->hasItems()) {
|
||||||
UI::requestList($reqs);
|
Component::requestList($reqs);
|
||||||
} else {
|
} else {
|
||||||
UI::noResults('No Active Requests', '/journal', 'Return to your journal',
|
Component::noResults('No Active Requests', '/journal', 'Return to your journal',
|
||||||
'Your prayer journal has no active requests');
|
'Your prayer journal has no active requests');
|
||||||
} ?>
|
} ?>
|
||||||
</article><?php
|
</article><?php
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Auth, Layout, Request, UI};
|
use MyPrayerJournal\Auth;
|
||||||
|
use MyPrayerJournal\Domain\Request;
|
||||||
|
use MyPrayerJournal\UI\{Component, Layout};
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
@ -13,9 +15,9 @@ Layout::pageHead('Answered Requests'); ?>
|
|||||||
<article class="container mt-3">
|
<article class="container mt-3">
|
||||||
<h2 class=pb-3>Answered Requests</h2><?php
|
<h2 class=pb-3>Answered Requests</h2><?php
|
||||||
if ($reqs->hasItems()) {
|
if ($reqs->hasItems()) {
|
||||||
UI::requestList($reqs);
|
Component::requestList($reqs);
|
||||||
} else {
|
} else {
|
||||||
UI::noResults('No Answered Requests', '/journal', 'Return to your journal', <<<'TEXT'
|
Component::noResults('No Answered Requests', '/journal', 'Return to your journal', <<<'TEXT'
|
||||||
Your prayer journal has no answered requests; once you have marked one as “Answered”, it will
|
Your prayer journal has no answered requests; once you have marked one as “Answered”, it will
|
||||||
appear here
|
appear here
|
||||||
TEXT);
|
TEXT);
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
<?php declare(strict_types=1);
|
<?php declare(strict_types=1);
|
||||||
|
|
||||||
use MyPrayerJournal\{Auth, Layout, Request, UI};
|
use MyPrayerJournal\Auth;
|
||||||
|
use MyPrayerJournal\Domain\Request;
|
||||||
|
use MyPrayerJournal\UI\{Component, Layout};
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
@ -13,9 +15,9 @@ Layout::pageHead('Snoozed Requests'); ?>
|
|||||||
<article class="container mt-3">
|
<article class="container mt-3">
|
||||||
<h2 class=pb-3>Snoozed Requests</h2><?php
|
<h2 class=pb-3>Snoozed Requests</h2><?php
|
||||||
if ($reqs->hasItems()) {
|
if ($reqs->hasItems()) {
|
||||||
UI::requestList($reqs);
|
Component::requestList($reqs);
|
||||||
} else {
|
} else {
|
||||||
UI::noResults('No Snoozed Requests', '/journal', 'Return to your journal',
|
Component::noResults('No Snoozed Requests', '/journal', 'Return to your journal',
|
||||||
'Your prayer journal has no snoozed requests');
|
'Your prayer journal has no snoozed requests');
|
||||||
} ?>
|
} ?>
|
||||||
</article><?php
|
</article><?php
|
||||||
|
@ -3,5 +3,6 @@
|
|||||||
use MyPrayerJournal\Auth;
|
use MyPrayerJournal\Auth;
|
||||||
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
|
|
||||||
Auth::logOff();
|
Auth::logOff();
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
use MyPrayerJournal\Auth;
|
use MyPrayerJournal\Auth;
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
|
||||||
require '../../start.php';
|
require '../../start.php';
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] <> 'GET') not_found();
|
||||||
|
|
||||||
Auth::logOn();
|
Auth::logOn();
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
use Auth0\SDK\Exception\ConfigurationException;
|
use Auth0\SDK\Exception\ConfigurationException;
|
||||||
use BitBadger\PDODocument\{Configuration, Definition, DocumentException, Mode};
|
use BitBadger\PDODocument\{Configuration, Definition, DocumentException, Mode};
|
||||||
use Dotenv\Dotenv;
|
use Dotenv\Dotenv;
|
||||||
use MyPrayerJournal\{Auth, Request, Table};
|
use MyPrayerJournal\{Auth, Table};
|
||||||
|
use MyPrayerJournal\Domain\Request;
|
||||||
|
|
||||||
require __DIR__ . '/vendor/autoload.php';
|
require __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user