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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user