libra/docs/stories/story-2.4-account-lifecycle...

7.8 KiB

Story 2.4: Account Lifecycle Management

Epic Reference

Epic 2: User Management System

User Story

As an admin, I want to deactivate, reactivate, and permanently delete client accounts, So that I can manage the full lifecycle of client relationships.

Story Context

Existing System Integration

  • Integrates with: Users table, consultations, timelines, notifications
  • Technology: Livewire Volt, confirmation modals
  • Follows pattern: Soft deactivation, hard deletion with cascade
  • Touch points: User model, all related models

Acceptance Criteria

Deactivate Account

  • "Deactivate" button on user profile and list
  • Confirmation dialog explaining consequences
  • Effects of deactivation:
    • User cannot log in
    • All data retained (consultations, timelines)
    • Status changes to 'deactivated'
    • Can be reactivated by admin
  • Visual indicator in user list (grayed out, badge)
  • Audit log entry created

Reactivate Account

  • "Reactivate" button on deactivated profiles
  • Confirmation dialog
  • Effects of reactivation:
    • Restore login ability
    • Status changes to 'active'
    • All data intact
  • Email notification sent to user
  • Audit log entry created

Delete Account (Permanent)

  • "Delete" button (with danger styling)
  • Confirmation dialog with strong warning:
    • "This action cannot be undone"
    • Lists what will be deleted
    • Requires typing confirmation (e.g., user email)
  • Effects of deletion:
    • User record permanently removed
    • Cascades to: consultations, timelines, timeline_updates, notifications
    • Cannot be recovered
  • Audit log entry preserved (for audit trail)
  • No email sent (user no longer exists)

Password Reset

  • "Reset Password" action on user profile
  • Options:
    • Generate random password
    • Set specific password manually
  • Email new credentials to client
  • Force password change on next login (optional)
  • Audit log entry created

Quality Requirements

  • All actions logged in admin_logs table
  • Bilingual confirmation messages
  • Clear visual states for account status
  • Tests for all lifecycle operations

Technical Notes

User Status Management

// User model
public function isActive(): bool
{
    return $this->status === 'active';
}

public function isDeactivated(): bool
{
    return $this->status === 'deactivated';
}

public function deactivate(): void
{
    $this->update(['status' => 'deactivated']);
}

public function reactivate(): void
{
    $this->update(['status' => 'active']);
}

Authentication Check

// In FortifyServiceProvider or custom auth
Fortify::authenticateUsing(function (Request $request) {
    $user = User::where('email', $request->email)->first();

    if ($user &&
        $user->isActive() &&
        Hash::check($request->password, $user->password)) {
        return $user;
    }

    // Optionally: different error for deactivated
    return null;
});

Cascade Deletion

// In User model
protected static function booted(): void
{
    static::deleting(function (User $user) {
        // Log before deletion
        AdminLog::create([
            'admin_id' => auth()->id(),
            'action_type' => 'delete',
            'target_type' => 'user',
            'target_id' => $user->id,
            'old_values' => $user->toArray(),
            'ip_address' => request()->ip(),
        ]);

        // Cascade delete (or use foreign key CASCADE)
        $user->consultations()->delete();
        $user->timelines->each(function ($timeline) {
            $timeline->updates()->delete();
            $timeline->delete();
        });
        $user->notifications()->delete();
    });
}

Volt Component for Lifecycle Actions

<?php

use App\Models\User;
use Livewire\Volt\Component;
use Illuminate\Support\Str;
use Illuminate\Support\Facades\Hash;

new class extends Component {
    public User $user;
    public string $deleteConfirmation = '';
    public bool $showDeleteModal = false;

    public function deactivate(): void
    {
        $this->user->deactivate();

        AdminLog::create([
            'admin_id' => auth()->id(),
            'action_type' => 'deactivate',
            'target_type' => 'user',
            'target_id' => $this->user->id,
            'old_values' => ['status' => 'active'],
            'new_values' => ['status' => 'deactivated'],
            'ip_address' => request()->ip(),
        ]);

        session()->flash('success', __('messages.user_deactivated'));
    }

    public function reactivate(): void
    {
        $this->user->reactivate();

        // Send notification
        $this->user->notify(new AccountReactivatedNotification());

        // Log action
        AdminLog::create([...]);

        session()->flash('success', __('messages.user_reactivated'));
    }

    public function delete(): void
    {
        if ($this->deleteConfirmation !== $this->user->email) {
            $this->addError('deleteConfirmation', __('validation.email_confirmation'));
            return;
        }

        $this->user->delete();

        session()->flash('success', __('messages.user_deleted'));
        $this->redirect(route('admin.users.index'));
    }

    public function resetPassword(): void
    {
        $newPassword = Str::random(12);

        $this->user->update([
            'password' => Hash::make($newPassword),
        ]);

        // Send new credentials email
        $this->user->notify(new PasswordResetByAdminNotification($newPassword));

        // Log action
        AdminLog::create([
            'admin_id' => auth()->id(),
            'action_type' => 'password_reset',
            'target_type' => 'user',
            'target_id' => $this->user->id,
            'ip_address' => request()->ip(),
        ]);

        session()->flash('success', __('messages.password_reset_sent'));
    }
};

Delete Confirmation Modal

<flux:modal wire:model="showDeleteModal">
    <flux:heading>{{ __('messages.confirm_delete') }}</flux:heading>

    <flux:callout variant="danger">
        {{ __('messages.delete_warning') }}
    </flux:callout>

    <p>{{ __('messages.will_be_deleted') }}</p>
    <ul class="list-disc ps-5 mt-2">
        <li>{{ __('messages.all_consultations') }}</li>
        <li>{{ __('messages.all_timelines') }}</li>
        <li>{{ __('messages.all_notifications') }}</li>
    </ul>

    <flux:field>
        <flux:label>{{ __('messages.type_email_to_confirm', ['email' => $user->email]) }}</flux:label>
        <flux:input wire:model="deleteConfirmation" />
        <flux:error name="deleteConfirmation" />
    </flux:field>

    <div class="flex gap-3 mt-4">
        <flux:button wire:click="$set('showDeleteModal', false)">
            {{ __('messages.cancel') }}
        </flux:button>
        <flux:button variant="danger" wire:click="delete">
            {{ __('messages.delete_permanently') }}
        </flux:button>
    </div>
</flux:modal>

Definition of Done

  • Deactivate prevents login but preserves data
  • Reactivate restores login ability
  • Delete permanently removes all user data
  • Delete requires email confirmation
  • Password reset sends new credentials
  • Visual indicators show account status
  • Audit logging for all actions
  • Email notifications sent appropriately
  • Bilingual support complete
  • Tests for all lifecycle states
  • Code formatted with Pint

Dependencies

  • Story 2.1: Individual client management
  • Story 2.2: Company client management
  • Epic 3: Consultations (cascade delete)
  • Epic 4: Timelines (cascade delete)

Risk Assessment

  • Primary Risk: Accidental permanent deletion
  • Mitigation: Strong confirmation dialog, email confirmation, audit log
  • Rollback: Not possible for delete - warn user clearly

Estimation

Complexity: Medium Estimated Effort: 4-5 hours