Add an account block showing who is signed in #156

Merged
thatguygriff merged 2 commits from feature/142-account-block into main 2026-07-30 01:53:31 +00:00
9 changed files with 7 additions and 84 deletions
Showing only changes of commit ab5212282d - Show all commits
+1 -1
View File
@@ -14,7 +14,7 @@ each change under the current top section as you work.
## [1.3.1]
### Added
- An **Account** block (`[us_account]`) showing who is signed in — their name, their email, the students they book for if that is anyone besides themselves — and a **Sign out** link. Signing out returns to the login page chosen in the block, or to the page the visitor was already on when none is set, so putting it in a site header does not also move people somewhere. To a signed-out visitor it shows a **Sign in** link when a login page is chosen, and nothing at all when one is not: a panel about who is signed in has nothing to tell a stranger, and a notice they cannot act on is just clutter in a header.
- An **Account** block (`[us_account]`) showing who is signed in — their name and their email — and a **Sign out** link. Signing out returns to the login page chosen in the block, or to the page the visitor was already on when none is set, so putting it in a site header does not also move people somewhere. To a signed-out visitor it shows a **Sign in** link when a login page is chosen, and nothing at all when one is not: a panel about who is signed in has nothing to tell a stranger, and a notice they cannot act on is just clutter in a header.
### Security
- Signup now checks the password properly. The form scores it as you type with the same zxcvbn meter wp-admin uses and will not submit a weak one, and the server refuses — regardless of what the browser allowed — anything shorter than 8 characters, one of the well-known leaked passwords, one built from barely any distinct characters, or one containing your own name or email address. Composition rules ("must contain a symbol") are deliberately not imposed: they mostly produce predictable substitutions. Email addresses are validated on the server on every signup path, with a clear message when one is already registered.
+1 -2
View File
@@ -562,8 +562,7 @@
font-weight: 600;
}
.us-account-email,
.us-account-students {
.us-account-email {
display: block;
font-size: 0.9em;
opacity: 0.75;
+1 -1
View File
@@ -310,7 +310,7 @@
{
name: 'us-scheduler/account',
title: __('Account', 'unsupervised-schedular'),
description: __('Shows who is signed in, who they book for, and a sign out link. Renders nothing for signed-out visitors unless a login page is chosen.', 'unsupervised-schedular'),
description: __('Shows the name and email of whoever is signed in, with a sign out link. Renders nothing for signed-out visitors unless a login page is chosen.', 'unsupervised-schedular'),
icon: 'admin-users',
keywords: ['account', 'sign out', 'log out', 'signed in', 'profile'],
shortcode: 'us_account',
+2 -3
View File
@@ -142,7 +142,6 @@ header is noise that cannot be acted on, so:
- **Login page chosen** → a single **Sign in** link.
Signed in, it shows the display name (`Auth\UserName::format()`, so a username
is never exposed), the account email, a **Sign out** link, and — only on an
account that books for someone other than itself — the students it books for.
Signing out returns to the chosen login page, or to the current page when there
is never exposed), the account email, and a **Sign out** link — deliberately
nothing else. Signing out returns to the chosen login page, or to the current page when there
is none, so a header sign-out does not also navigate the visitor somewhere.
-13
View File
@@ -3,7 +3,6 @@ declare(strict_types=1);
namespace Unsupervised\Schedular\Auth;
use Unsupervised\Schedular\Guardian\GuardianService;
use Unsupervised\Schedular\Val;
/**
@@ -15,8 +14,6 @@ use Unsupervised\Schedular\Val;
*/
class AccountPage {
public function __construct( private GuardianService $guardians ) {}
/**
* Renders the account shortcode/block output.
*
@@ -55,16 +52,6 @@ class AccountPage {
$name = UserName::format( $user, get_current_user_id() );
$email = $user->user_email;
// Whose lessons this account books, when that is more than just their own.
// A parent's first question on seeing "signed in as Grace" is whether this
// is the account their children's lessons are on.
$students = [];
foreach ( $this->guardians->bookableStudents( get_current_user_id() ) as $student ) {
if ( ! $student['is_self'] ) {
$students[] = $student['name'];
}
}
// Back to where they were, so signing out of a header link does not also
// navigate them somewhere. The login page is the better landing spot when
// one is configured, since the current page may be members-only.
-8
View File
@@ -220,18 +220,10 @@ class BlockPreview {
'<div class="us-account">%s'
. '<p class="us-account-who"><span class="us-account-name">%s</span>'
. '<span class="us-account-email">%s</span></p>'
. '<p class="us-account-students">%s</p>'
. '<p class="us-account-actions"><a class="us-account-signout" href="#">%s</a></p></div>',
self::note( __( 'Editor preview — each visitor sees their own account here.', 'unsupervised-schedular' ) ),
esc_html__( 'Grace Hopper', 'unsupervised-schedular' ),
esc_html__( '[email protected]', 'unsupervised-schedular' ),
esc_html(
sprintf(
/* translators: %s: comma-separated list of the students this account books for. */
__( 'Booking for %s', 'unsupervised-schedular' ),
__( 'Ada, Alan', 'unsupervised-schedular' )
)
),
esc_html__( 'Sign out', 'unsupervised-schedular' )
);
}
+1 -1
View File
@@ -100,7 +100,7 @@ class Plugin {
$registrationPage = new RegistrationPage( $invites, $policies, $policyVersions, $acceptances, $settings, $registrationMailer, $questions, $answers, $groupAccess, $guardians );
$groupClassPage = new GroupClassPage( $guardians );
$familyPage = new FamilyPage( $guardians, $questions, $answers );
$accountPage = new AccountPage( $guardians );
$accountPage = new AccountPage();
( new ScheduledBillingRunner( $paymentService, $bookings, $enrollments, $offerings, new PaymentDueMailer(), $guardians ) )->register();
-13
View File
@@ -8,7 +8,6 @@ if (! defined('ABSPATH')) {
/**
* @var string $name Display name of the signed-in visitor.
* @var string $email Their account email.
* @var list<string> $students Names this account books for, excluding themselves; empty for a plain student account.
* @var string $logoutUrl Nonced sign-out URL, already carrying its redirect.
*/
?>
@@ -20,18 +19,6 @@ if (! defined('ABSPATH')) {
<?php endif; ?>
</p>
<?php if (! empty($students)) : ?>
<p class="us-account-students">
<?php
printf(
/* translators: %s: comma-separated list of the students this account books for. */
esc_html__('Booking for %s', 'unsupervised-schedular'),
esc_html(implode(', ', $students))
);
?>
</p>
<?php endif; ?>
<p class="us-account-actions">
<a class="us-account-signout" href="<?php echo esc_url($logoutUrl); ?>"><?php esc_html_e('Sign out', 'unsupervised-schedular'); ?></a>
</p>
+1 -42
View File
@@ -6,20 +6,17 @@ namespace Unsupervised\Schedular\Tests\Unit\Auth;
use Brain\Monkey\Functions;
use Mockery;
use Unsupervised\Schedular\Auth\AccountPage;
use Unsupervised\Schedular\Guardian\GuardianService;
use Unsupervised\Schedular\Tests\Unit\TestCase;
class AccountPageTest extends TestCase
{
private GuardianService&Mockery\MockInterface $guardians;
private AccountPage $page;
protected function setUp(): void
{
parent::setUp();
$this->guardians = Mockery::mock(GuardianService::class);
$this->page = new AccountPage($this->guardians);
$this->page = new AccountPage();
Functions\when('is_user_logged_in')->justReturn(true);
Functions\when('get_current_user_id')->justReturn(5);
@@ -47,16 +44,8 @@ class AccountPageTest extends TestCase
return $user;
}
/** @param list<array{id: int, name: string, is_self: bool}> $students */
private function bookable(array $students): void
{
$this->guardians->shouldReceive('bookableStudents')->with(5)->andReturn($students);
}
public function testShowsTheSignedInNameAndEmail(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
$html = $this->page->render([]);
self::assertStringContainsString('Grace Hopper', $html);
@@ -64,32 +53,8 @@ class AccountPageTest extends TestCase
self::assertStringContainsString('Sign out', $html);
}
public function testNamesTheStudentsTheAccountBooksFor(): void
{
$this->bookable([
['id' => 42, 'name' => 'Ada', 'is_self' => false],
['id' => 43, 'name' => 'Alan', 'is_self' => false],
['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true],
]);
self::assertStringContainsString('Booking for Ada, Alan', $this->page->render([]));
}
/**
* An account that only books for itself has nothing to add — "Booking for
* Grace Hopper" under "Grace Hopper" is noise.
*/
public function testSaysNothingAboutStudentsOnAPlainAccount(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
self::assertStringNotContainsString('Booking for', $this->page->render([]));
}
public function testSigningOutReturnsToTheConfiguredLoginPage(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
self::assertStringContainsString(
rawurlencode('https://studio.test/sign-in/'),
$this->page->render(['loginPageId' => 9])
@@ -102,8 +67,6 @@ class AccountPageTest extends TestCase
*/
public function testSigningOutReturnsToTheCurrentPageWhenNoLoginPageIsSet(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
self::assertStringContainsString(
rawurlencode('https://studio.test/current/'),
$this->page->render([])
@@ -112,8 +75,6 @@ class AccountPageTest extends TestCase
public function testTheShortcodeAttributeNameIsAccepted(): void
{
$this->bookable([['id' => 5, 'name' => 'Grace Hopper', 'is_self' => true]]);
self::assertStringContainsString(
rawurlencode('https://studio.test/sign-in/'),
$this->page->render(['login_page_id' => 9])
@@ -127,7 +88,6 @@ class AccountPageTest extends TestCase
public function testRendersNothingForASignedOutVisitorWithNoLoginPage(): void
{
Functions\when('is_user_logged_in')->justReturn(false);
$this->guardians->shouldNotReceive('bookableStudents');
self::assertSame('', $this->page->render([]));
}
@@ -135,7 +95,6 @@ class AccountPageTest extends TestCase
public function testOffersASignInLinkToASignedOutVisitorWhenAPageIsChosen(): void
{
Functions\when('is_user_logged_in')->justReturn(false);
$this->guardians->shouldNotReceive('bookableStudents');
$html = $this->page->render(['loginPageId' => 9]);