Loading...

This is taking longer than expected.

Your account area

What a signed-in person can change about themselves under /account, and what each screen needs from you to work.

Every signed-in person has an account area at /account: their profile, their notifications, the announcements you publish and, when each module is on, their referrals, their support tickets, their live chats and their plans. This guide walks through each screen, what it writes to the users table, and the configuration that decides which parts appear. You need it when a client asks where to change their password, and when you decide which fields your product lets people edit.

The screens

All of them are defined in routes/auth.php behind the auth and security middleware: the person must be signed in, must not be blocked, and must not have a pending forced password change.

URL Route name Component Exists when
/account auth.profile App\Livewire\Auth\MyProfile Always
/account/notifications auth.notifications.center App\Livewire\Auth\Notifications\Index Always
/account/referrals auth.referrals App\Livewire\Auth\Referrals\Index FEATURE_REFERRALS_ENABLED=true, otherwise 404
/account/announcements auth.announcements.index App\Livewire\Auth\Announcements\Index FEATURE_ANNOUNCEMENTS=true, otherwise 404
/account/announcements/{announcement} auth.announcements.show App\Livewire\Auth\Announcements\Show Same flag; the URL carries the slug
/account/tickets auth.tickets.index App\Livewire\Shared\Tickets\Index FEATURE_TICKETS_ENABLED=true, otherwise 404
/account/tickets/create auth.tickets.create App\Livewire\Shared\Tickets\Create Same flag
/account/tickets/{ticket} auth.tickets.show App\Livewire\Shared\Tickets\Show Same flag, and only for the person's own ticket; 404 otherwise
/account/chat auth.chat App\Livewire\Shared\Chats\Chat FEATURE_CHAT_ENABLED=true, otherwise 404
/account/chats auth.chats.index App\Livewire\Shared\Chats\Index Same flag: the person's closed chats
/account/chats/{chat} auth.chats.show App\Livewire\Shared\Chats\Show Same flag, and only for the person's own chat once it is closed; 404 otherwise
/account/plans auth.plans WeblaborMx\BillingCore\Livewire\Auth\Plans Registered by the billing package; 403 unless the person may manage the billing

The billing package registers the rest of its account screens under the same prefix: /account/add-ons (with each add-on and meter below it), /account/billing, /account/payment-methods and /account/debt. Billing as a whole is in Turn on plans and billing.

The sidebar of the whole area is resources/views/components/project/layouts/sidebars/account.blade.php. The layout picks the sidebar from the first segment of the URL, so anything you add under /account gets it too. It lists My Profile, Notifications, Referrals and Announcements (the last two only when their flags are on), My plans when billing is available and the person may see subscriptions, Tickets at /account/tickets when the support tickets module is on, Chat at /account/chat when the live chat is on, whether or not the person's plan includes it, and Help, which opens the help centre in the person's language through help_url(). A Go to App button at the top returns to route('app.dashboard'). Tickets and Chat are separate entries, each following its own switch: from Tickets the person reaches New ticket and their tickets, and from Chat the chat and its Chat history. Tickets also says how soon a new ticket is answered under the person's plan. The support team's screens (the inbox, the team's chats and the assistant) live in the admin panel under /admin/support, in the Support group of its sidebar, which whoever answers support opens without the admin role. The ticket and chat screens are shared components (App\Livewire\Shared) that build their links from the panel that mounts them, so a product can mount them in another panel too. How tickets and the live chat work is in the guides «Support tickets» and «Live support chat», which the help centre lists only while their module is on.

The profile page

/account is one Livewire component, App\Livewire\Auth\MyProfile, laid out as cards. Each card has its own Save button and its own method, so saving the password does not revalidate the name and the other way round.

The profile page, laid out as cards

Profile picture

The picture is an <x-image-uploader> bound with wire:model.live to photo: choosing a file uploads it at once with a progress bar, and updatedPhoto() saves the change through User::saveAvatarChanges(), so there is no Save button. The uploader takes any image up to 5120 KB; the caption on screen only names JPG, GIF and PNG. The file lands in the Profile folder of the account's own media library (created with the first picture) as a Media record, with the conversions the library generates for every upload, and users.avatar_media_id points at it. When a picture is already there, a new one does not replace it until the person answers the question in a dialog over the page: Keep it in the media library unlinks the old picture and leaves it in the library, Delete it from the media library deletes it unless another record uses it, and Cancel throws the new upload away, as closing the dialog with its X, with Escape or by clicking outside it does. Nothing is saved before that answer. A product whose people have no media library to look at can drop the question with :confirm-replacement="false" on the uploader: the new picture then replaces the old one at once, and the old one is deleted when the person may delete it. The option is in the media library notes of the developer documentation. Taking the current one off offers Remove from this record, which keeps it in the library, or Delete from the media library, which deletes it. Because the file belongs to the account like anything else in its library, it counts against the account's disk-space quota. The pipeline and what it needs from your storage are in /help/files-images-and-storage.

Until a person uploads a picture, $user->avatar is a placeholder generated by api.dicebear.com from the MD5 of their email ($user->default_avatar).

The card uses the uploader's compact presentation: the picture, 96 px, on the left, and the title, the caption and a one-line drop box on its right; on a phone the picture goes on top. Without an uploaded picture it shows that placeholder; with one, the picture takes its place with its two buttons over the corner, and its file name and size go under the box. The card hides the field that adds a library file by its ID (hide-id-field), so the picture can only be chosen or dropped.

General information

Name, the identity fields, and country, state and city. Which identity fields exist follows LOGIN_IDENTITIES (config('auth.login_identities')): an Email field when email is in the list, Phone when phone is, Username when username is. Each identity must be unique across users; the username is letters, digits, dashes and underscores, up to 255 characters. Saving also runs the check in App\Livewire\Traits\ValidatesLoginIdentities: at least one identity must be filled, and when more than one is configured a username alone is not enough, an email or a phone must be there too. The error opens a dialog and nothing is saved.

Country, state and city are stored in the extra_data JSON column, not in columns of their own.

State and city wait for the field above them. While the states of a new country, or the cities of a new state, are loading, the field cannot be opened and says Loading options… under it. Changing the country clears the state and the city, and changing the state clears the city, because they no longer belong to it. A list that arrived is kept for a day, so opening the profile again does not ask for it again, and when the service fails the field keeps showing the last list it had. When a country has no states, or a state has no cities, the field says No options. When the options cannot be loaded and there is no earlier list, the field says The options could not be loaded with a Retry link that asks again; if the service asked to wait, the field says for how many seconds and Retry does not ask before then. Nothing else on the page is lost. A field whose parent is still empty stays disabled. How the selects do it is in docs/weblabor-base/country-detection.md.

While AUTH_ENABLE_VALIDATION is on, the email field and the phone field each show a Verified or Not verified badge and a Verify now link that sends a code, and Save refuses an identity that was changed and not confirmed: the field itself says the value still needs its code. An identity nobody touched is never asked again, so saving the name or the country needs no code. Emptying an identity is allowed and clears its verification with it, as long as the check above still finds a usable identity left.

When a code is confirmed, onIdentityVerified() writes that identity and its timestamp at once, without pressing Save, and writes nothing else: everything else on the page waits for Save and its validation. A phone is stored in international format, and the badge compares numbers rather than strings, so the stored number typed without its prefix is still the same number. With the switch off neither field shows the verification row. How the codes travel is in /help/verify-email-and-phone.

Password

Two fields: the new password and its confirmation. The rule is at least 8 characters and both fields equal (changePassword(), required|min:8|confirmed). The current password is not asked. Nothing signs the other sessions out; the Connected devices card below does that.

Language and time

Two selects. The language offers the keys of config('app.languages'), es and en out of the box; the timezone offers PHP's full list of identifiers. Both are required to save, and they write users.locale and users.timezone. The locale is the language of the interface for that account, the timezone is how every date is shown to them. See /help/languages-and-translations and /help/timezones-and-dates.

Notifications

One toggle, Email notification, bound to users.send_mail. Off drops the mail channel from every notification the base class sends; the bell and the notification centre keep receiving everything. When APP_PWA=true and the VAPID keys are configured, a second toggle, Push notification, subscribes or unsubscribes the current browser, and a Test button appears while it is subscribed. Test calls testWebPushNotification(), which sends App\Notifications\TestWebPushNotification to the person's push subscriptions, or warns instead when push is not configured or this browser has no subscription. See /help/send-notifications and /help/web-push-and-the-pwa.

Security PIN

The card exists only when AUTH_ENABLE_PIN=true. It embeds App\Livewire\Auth\ManagePin: four one-digit boxes for the PIN and four for its confirmation, plus four for the current PIN once one exists, which must match before anything changes. The PIN is exactly four digits and is stored hashed in users.pin.

While the flag is on and the person has no PIN, App\Http\Middleware\SystemValidations flashes a warning on every request, and <x-design::system-warnings/> renders it at the top of every page of the application layout: "Need to configure a PIN for your account." with a Configure PIN link to /account. An administrator can clear a user's PIN from the user's page in the admin panel. What the PIN protects is in /help/the-security-pin.

Connected devices

The card lists the devices of the account, built by App\Models\Session::devicesOf(): the rows of the sessions table whose user_id is the person and whose last_activity is within the last session.devices_recent_days days (30, in config/session.php) and never past SESSION_LIFETIME (525600 minutes, a year, by default). Rows with the same browser, operating system and IP are grouped into one line, which shows the latest activity of the group; the most recently used line comes first. Older rows are not deleted: they only stop being listed and counted. Each line shows the browser and the operating system read from the user agent (Edge, Opera, Samsung Internet, Firefox, Chrome or Safari, including the iPhone and iPad builds of Chrome, Firefox, Edge and Opera; Windows, macOS, Android, iOS or Linux), the IP, the country, and how long ago it was last active; the line that holds the current session is marked This device and has no Disconnect button. Disconnect deletes every row of its line, and only the person's own; when there is more than one line, Disconnect all other devices asks for confirmation (confirmDisconnectAll()) and deletes every row of the person except the current one, old ones included. The password does not change.

The devices limit of the plan (App\Classes\PlanLimits::devices()) counts these same lines, so the meter and the card never show different numbers.

Two requirements. The list reads the database, so it only works with SESSION_DRIVER=database, which .env.example sets, and the sessions table from the default migrations; with the file driver the card says No connected devices found. The country comes from App\Services\CountryDetectionService::detectFromIp(): a private IP, which is every request in local development, returns COUNTRY_CODE_FALLBACK; a public IP is looked up at ipapi.co with a two-second timeout, and a failed lookup leaves the location empty.

Delete account

Delete opens a confirmation dialog (confirmDeletion()). Accepting runs deleteAccount(): it signs the person out, deletes the user and redirects to /. The User model uses SoftDeletes, so the row stays with deleted_at set even though the dialog announces a permanent deletion, and nothing else is removed: notifications, sessions and related records keep pointing at the id. Decide what your product has to do with them and do it in an observer or in deleteAccount().

A person whose subscription can still charge them cannot delete their account. Before signing anyone out, deleteAccount() asks User::hasActiveSubscription(); when it is true the account stays, the session stays, and a dialog tells them to cancel the subscription in Plans, with a button that goes there. The same rule lives in UserObserver::deleting(), so deleting the user from the admin panel, to the trash or for good, is refused too, with the error This user has an active subscription. Cancel it before deleting the user. A user already in the trash is restored first, and the subscription cancelled, before it can be deleted for good. Nothing is cancelled automatically. A subscription blocks when it is active, in trial, past due, unpaid or incomplete, has no cancellation requested (ends_at empty) and has at least one paid price: a free plan, a cancelled subscription or one ending at the end of its period does not. Without the billing package, or with it turned off, nothing blocks.

The notification centre

/account/notifications lists the notifications of the account, twenty per page, unread first and then newest, which is how User::notifications() orders them. Each row is a link to the notification's url (# when it has none) with its image, its icon, its title, its description and a relative date; unread rows have an amber background.

The notification centre, unread rows first

Rows are App\Models\DatabaseNotification, Laravel's model plus the senderUser relation and five accessors: sender_id (the sender_id the notification stored, when a person sent it), is_read (read_at is set), image, url and target (_self by default). image follows the sender: a notification sent by a person shows that person's current photo, so a photo changed or deleted later never leaves a broken image, their generated placeholder when they have none, and the config('app.icon') icon when the account no longer exists. A notification without a sender shows the image it stored, or that icon when there is none.

Opening the centre marks nothing as read. That happens from the bell in the top bar, App\Livewire\Auth\Notifications\Notification: it shows the unread count, the ten first notifications grouped by day, and clicking one marks it read while Mark as Read marks every unread one. How notifications are created and which channels they take is in /help/send-notifications.

Announcements

With FEATURE_ANNOUNCEMENTS=true, the header shows a megaphone, App\Livewire\Auth\Announcements\Dropdown, with the count of unread announcements (9+ past nine) and the ten latest published. /account/announcements lists them ten per page with the first 150 characters of each, and the announcement page renders the full Markdown with its publication date.

The announcements list at /account/announcements

Read state is one timestamp, users.last_announcement_read_at: an announcement is unread when it was published after it. Opening any announcement page sets it to now, and so does Mark as Read in the dropdown, so both mark everything as read at once; the list page changes nothing. Writing and publishing announcements is in /help/announcements.

Referrals

With FEATURE_REFERRALS_ENABLED=true, /account/referrals shows the person's referral link with a Copy link button, the registration reward and the paid subscription reward percentage, the virtual money they have generated, and a table of the users who registered with their link. The program, its rewards and its cookie are in /help/referrals.

Back