Skip to content
  1. Extras
  2. MiniShop3
  3. Frontend interface
  4. Customer account
  5. Customer profile

Customer profile ​

An authenticated customer edits name, email, phone and sees email verification status. The page is built with msCustomer and service=profile.

A guest on the same page sees login and registration.

Page structure ​

ComponentFilePurpose
Base layouttpl.msCustomer.baseCommon wrapper with sidebar
Sidebartpl.msCustomer.sidebarAccount navigation
Profiletpl.msCustomer.profileEdit form

Snippet call ​

fenom
{'!msCustomer' | snippet : [
    'service' => 'profile'
]}

Parameters ​

ParameterDefaultDescription
serviceprofileService type
tpltpl.msCustomer.profileProfile chunk
unauthorizedTpltpl.msCustomer.unauthorizedChunk for guests
returntplFormat: tpl or data

Chunk architecture ​

Customer account chunks use inheritance:

tpl.msCustomer.base          # base layout
├── {include 'tpl.msCustomer.sidebar'}  # sidebar
└── {block 'content'}        # content area
    └── tpl.msCustomer.profile # profile form

Placeholders ​

In tpl.msCustomer.profile ​

PlaceholderTypeDescription
{$customer}arrayCustomer data
{$customer.id}intCustomer ID
{$customer.email}stringEmail
{$customer.first_name}stringFirst name
{$customer.last_name}stringLast name
{$customer.phone}stringPhone
{$email_verified}boolEmail verified
{$email_verified_at}stringEmail verification date
{$phone_verified}boolAlways false: phone verification is planned (#138)
{$phone_verified_at}stringAlways empty, for the same reason
{$errors}arrayValidation errors
{$success}boolSave succeeded

Profile chunk ​

fenom
{* tpl.msCustomer.profile *}
{extends 'tpl.msCustomer.base'}

{block 'content'}
<div class="ms3-customer-profile">
    <div class="card shadow-sm">
        <div class="card-header bg-primary text-white">
            <h5 class="mb-0">{'ms3_customer_profile_title' | lexicon}</h5>
        </div>
        <div class="card-body">
            {if $success?}
            <div class="alert alert-success" role="alert">
                {'ms3_customer_profile_updated' | lexicon}
            </div>
            {/if}

            <form class="ms3_form ms3-customer-profile-form" method="post" action="">
                <input type="hidden" name="ms3_action" value="customer/update-profile">

                <div class="row">
                    {* First name *}
                    <div class="col-md-6 mb-3">
                        <label for="first_name" class="form-label">
                            {'ms3_customer_first_name' | lexicon} <span class="text-danger">*</span>
                        </label>
                        <input type="text"
                               class="form-control {if $errors.first_name?}is-invalid{/if}"
                               id="first_name"
                               name="first_name"
                               value="{$customer.first_name}"
                               required>
                        {if $errors.first_name?}
                        <div class="invalid-feedback">{$errors.first_name}</div>
                        {/if}
                    </div>

                    {* Last name *}
                    <div class="col-md-6 mb-3">
                        <label for="last_name" class="form-label">
                            {'ms3_customer_last_name' | lexicon} <span class="text-danger">*</span>
                        </label>
                        <input type="text"
                               class="form-control {if $errors.last_name?}is-invalid{/if}"
                               id="last_name"
                               name="last_name"
                               value="{$customer.last_name}"
                               required>
                        {if $errors.last_name?}
                        <div class="invalid-feedback">{$errors.last_name}</div>
                        {/if}
                    </div>
                </div>

                {* Email with verification status *}
                <div class="mb-3">
                    <label for="email" class="form-label">
                        {'ms3_customer_email' | lexicon} <span class="text-danger">*</span>
                    </label>
                    <div class="input-group">
                        <input type="email"
                               class="form-control {if $errors.email?}is-invalid{/if}"
                               id="email"
                               name="email"
                               value="{$customer.email}"
                               required>
                        {if $email_verified}
                        <span class="input-group-text bg-success text-white">
                            {'ms3_customer_email_verified' | lexicon}
                        </span>
                        {else}
                        <button class="btn btn-outline-warning" type="button"
                                id="resend-verification-email"
                                data-customer-id="{$customer.id}">
                            {'ms3_customer_email_send_verification' | lexicon}
                        </button>
                        {/if}
                    </div>
                    {if $errors.email?}
                    <div class="invalid-feedback d-block">{$errors.email}</div>
                    {/if}
                </div>

                {* Phone *}
                <div class="mb-3">
                    <label for="phone" class="form-label">
                        {'ms3_customer_phone' | lexicon} <span class="text-danger">*</span>
                    </label>
                    <input type="tel"
                           class="form-control {if $errors.phone?}is-invalid{/if}"
                           id="phone"
                           name="phone"
                           value="{$customer.phone}"
                           required>
                    {if $errors.phone?}
                    <div class="invalid-feedback">{$errors.phone}</div>
                    {/if}
                </div>

                {* Save button *}
                <div class="d-flex justify-content-end gap-2 mt-4">
                    <button type="submit" class="btn btn-primary ms3_link">
                        {'ms3_customer_profile_save' | lexicon}
                    </button>
                </div>
            </form>
        </div>
    </div>
</div>
{/block}

Email verification ​

The profile shows whether email is verified:

  • Verified: green badge with checkmark
  • Not verified: button to send the email again
fenom
{if $email_verified}
    <span class="input-group-text bg-success text-white">
        {'ms3_customer_email_verified' | lexicon}
    </span>
{else}
    <button class="btn btn-outline-warning" type="button"
            id="resend-verification-email"
            data-customer-id="{$customer.id}">
        {'ms3_customer_email_send_verification' | lexicon}
    </button>
{/if}

Form handling ​

The form is submitted via POST with action customer/update-profile:

html
<form class="ms3_form ms3-customer-profile-form" method="post">
    <input type="hidden" name="ms3_action" value="customer/update-profile">
    <!-- form fields -->
    <button type="submit" class="btn btn-primary ms3_link">
        Save
    </button>
</form>

MiniShop3 JavaScript intercepts form submission for forms with the ms3_form class and processes it through the API.

Errors ​

The {$errors} array arrives after failed validation:

fenom
<input type="text"
       class="form-control {if $errors.first_name?}is-invalid{/if}"
       name="first_name"
       value="{$customer.first_name}">
{if $errors.first_name?}
<div class="invalid-feedback">{$errors.first_name}</div>
{/if}

System settings ​

SettingDescription
ms3_customer_profile_page_idProfile page ID

Customization ​

Custom profile chunk ​

fenom
{'!msCustomer' | snippet : [
    'service' => 'profile',
    'tpl' => 'tpl.myProfile'
]}

Adding fields ​

To add extra profile fields:

  1. Add fields to the msCustomer model (via migration)
  2. Add fields to the profile chunk
  3. Handle the new fields in the API endpoint

CSS classes ​

ClassElement
.ms3-customer-profileProfile container
.ms3-customer-profile-formProfile form
.ms3_formMiniShop3 form (for JS)
.ms3_linkSubmit button