# Delete Account (حذف الحساب) — Frontend Guide

Everything the **user mobile/web app** needs to let a learner delete their own account, plus what the **admin dashboard** and **trainer app** will see afterwards for records that belonged to a deleted user.

- **Audience:** frontend developers (user app — mobile & web; admin dashboard; trainer app)
- **Base path:** `{{base_url}}` = `/api/v1`
- **Related docs:** [user.md](./user.md#profile) (auto-generated endpoint reference), [README.md](./README.md)

Common headers on every call: `Accept: application/json`, `Content-Type: application/json`, `Accept-Language: en|ar`. Responses use the standard envelope `{ "status", "message", "data" }`.

---

## 1. TL;DR

| | |
|---|---|
| **Endpoint** | `POST /user/profile/delete-account` |
| **Auth** | `Authorization: Bearer <token>` (required) |
| **Who can call it** | Accounts with `user_type = user` (learners) only. Trainers and admins get `403`. |
| **Body** | `{ "password": "<current password>" }` — required |
| **Success** | `200` with `data: null` — the account is gone and **the token you just used is already invalid** |
| **Reversible from the app?** | **No.** There is no undo / restore endpoint and no grace period. |

> The method is `POST` (not `DELETE`) on purpose: many mobile HTTP clients drop the body of a `DELETE`, and this call needs the password in the body.

---

## 2. The endpoint

```
POST {{base_url}}/user/profile/delete-account
Authorization: Bearer {{user_token}}
Content-Type: application/json

{ "password": "MyCurrentPassword1" }
```

### Request body

| Field | Type | Required | Notes |
|---|---|---|---|
| `password` | string | **yes** | The user's **current** password. Used as a re-authentication step for a destructive action. Sent as plain text over HTTPS — never log it. |

There is no `password_confirmation`, no reason field and no `device_token` field.

### Responses

| HTTP | When | `message` (en) | `message` (ar) | `data` |
|---|---|---|---|---|
| **200** | Account deleted | `Account deleted successfully` | `تم حذف الحساب بنجاح` | `null` |
| **422** | `password` missing | `The Password field is required.` | `حقل كلمة المرور مطلوب.` | `null` |
| **422** | `password` wrong | `The password is incorrect.` | `كلمة المرور غير صحيحة.` | `null` |
| **401** | No / invalid / expired / already-invalidated token | `Unauthenticated, you have to login first` | same English text (this message is **not** translated by the backend) | `null` |
| **403** | Token belongs to a trainer/admin, **or** the account is deactivated | `This action is not available for your account type.` (or the deactivation message) | localized | `null` |

Real success body:

```json
{
  "status": "success",
  "message": "Account deleted successfully",
  "data": null
}
```

Real error body (`422`, wrong password):

```json
{
  "status": "fail",
  "message": "The password is incorrect.",
  "data": null
}
```

**Important — validation errors are a single string.** This API does **not** return a per-field `errors` map. The `422` `message` is the first validation error as a plain string, and `data` is `null`. Show it under the password field (there is only one field, so no mapping is needed).

---

## 3. Recommended UX flow

```
Settings ▸ Account ▸ "Delete my account"
        │
        ▼
Confirmation screen — explain what is lost (see §5), red destructive button
        │
        ▼
Password field  ──►  POST /user/profile/delete-account
        │
        ├─ 200 ─► clear session locally ─► go to Login / Welcome screen
        ├─ 422 ─► keep the dialog open, show `message` under the field, let them retry
        ├─ 401 ─► session already dead → treat as logged out → go to Login
        └─ 403 ─► show `message` (should never happen for a learner; hide the button for other roles)
```

### On `200` — do all of these client-side

The server has already invalidated the JWT, deleted the user's device tokens and deactivated the account. The app must now clean up its own state:

1. **Do not call `POST /user/auth/logout`.** The token is already dead, so it would return `401`. Just discard it.
2. Delete the stored access token (and refresh/session data, cached profile, cached cart, cached notifications).
3. **Close the notification SSE stream** (`GET /user/notifications/stream`) if it is open — reconnecting with the dead token returns `401`.
4. Stop / unregister the push-notification token locally (the server already removed the device row).
5. Navigate to the login / welcome screen and show a neutral confirmation such as the returned `message`. Do not auto-retry any queued request — they would all `401`.

### On any `401` from a *different* request afterwards

That is expected once the account is deleted (for example another tab / device still holding the old token). Handle it with your normal "session expired → go to login" path. A deleted account's token cannot be refreshed either.

### Who should see the button

Only show "Delete account" to learners (`user_type = user`). The profile response (`GET /user/profile`) tells you the type. Trainers manage their accounts through the admin team and get `403`.

---

## 4. Code samples

### cURL

```bash
curl -X POST "$BASE_URL/user/profile/delete-account" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json" \
  -H "Accept-Language: en" \
  -H "Content-Type: application/json" \
  -d '{"password":"MyCurrentPassword1"}'
```

### TypeScript (fetch)

```ts
type Envelope<T> = { status: 'success' | 'fail'; message: string; data: T | null };

export async function deleteAccount(password: string, token: string): Promise<void> {
  const res = await fetch(`${BASE_URL}/user/profile/delete-account`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${token}`,
      Accept: 'application/json',
      'Content-Type': 'application/json',
      'Accept-Language': locale, // 'en' | 'ar'
    },
    body: JSON.stringify({ password }),
  });

  const body = (await res.json()) as Envelope<null>;

  if (res.ok) {
    await clearSession();      // token, profile cache, cart cache, push token…
    closeNotificationStream(); // SSE
    navigateToLogin();
    return;
  }

  if (res.status === 401) {    // already logged out
    await clearSession();
    navigateToLogin();
    return;
  }

  // 422 (missing / wrong password) and 403: show the server's message
  throw new Error(body.message);
}
```

---

## 5. What actually happens to the account (so your copy is accurate)

Deleting an account is a **soft delete**. Nothing that other people or the business rely on is destroyed.

| Thing | Result |
|---|---|
| **Login** | Immediately impossible. Logging in with the old email or phone returns the normal `422` *"Invalid email or password, please try again!"* — the app cannot tell "deleted" apart from "wrong credentials" (by design). |
| **Existing tokens (all devices)** | All become invalid at once → `401` on every call. |
| **Push notification devices** | Removed. |
| **Profile, notifications, cart, addresses, reading progress, annotations** | Not shown anywhere any more (the account no longer exists for the app). |
| **Orders, payments, invoices, enrollments, certificates, attendance, consultation bookings** | **Kept** for accounting / the trainers' records. They still exist and stay linked to the (now hidden) account. |
| **Email & phone** | Rewritten to `deleted_<internal id>_<original value>` and the account is marked inactive. This frees the original email/phone so **the same person can register again** later as a brand-new account. |
| **Old data after re-registering** | **Not restored.** The new account starts empty — purchases, progress and certificates stay with the old, deleted account. Tell users this in the confirmation copy. |
| **Restore / undo** | Not available through the API. |

Suggested confirmation copy (adapt to your design system):

- **en:** "Delete your account? You will be signed out on all devices and won't be able to log in again. Your purchases, course progress and certificates will not be available if you sign up again. This can't be undone."
- **ar:** "هل تريد حذف حسابك؟ سيتم تسجيل خروجك من جميع الأجهزة ولن تتمكن من تسجيل الدخول مرة أخرى. لن تظل مشترياتك وتقدمك في الدورات وشهاداتك متاحة إذا سجّلت من جديد. لا يمكن التراجع عن هذا الإجراء."

### Signing up again with the same email / phone

`POST /user/auth/register` accepts the previously used email and phone (it returns `201` and sends the verification code as usual). The user row is created when the code is verified via `POST /user/auth/verify`, exactly like any new sign-up. No special handling is needed in the app.

---

## 6. Admin dashboard & trainer app — what changes

The account is hidden from user management, but historical records still need to render. The backend was updated so these **keep working** instead of erroring:

| Screen / endpoint area | Behavior for a deleted user |
|---|---|
| **Admin ▸ Users list / show / update / delete** | The user no longer appears; `GET /admin/users/{uuid}` for that user returns `404` (`Resource not found`). |
| **Admin ▸ Book orders** (list + details) | Order still listed. `user.id` (uuid) and `user.name` are still returned. Details show `user.email` / `user.phone` as `deleted_<id>_…` values. |
| **Admin ▸ Enrollments / enrolled users, attendance, certificates, consultation bookings** | Records still listed with the user's name; email/phone appear in the `deleted_<id>_…` form. |
| **Trainer ▸ consultation bookings & attendance** | Same — the learner's name still shows, contact fields look like `deleted_<id>_…`. |
| **Reviews** (admin) | Null-safe, but **not** linked to the deleted account any more: the review is still listed, with `user.id` and `user.name` (and `email`/`phone` in details) returned as `null`. Render a "Deleted user" placeholder when `user.id` is `null`. |

Frontend guidance for those screens:

- Treat an email or phone that **starts with `deleted_`** (or, on reviews, a `null` user) as "deleted account": render a muted "Deleted user" badge and **hide mailto:/tel: links and "message user" actions** for it.
- Don't assume `is_active = true` for these users; a deleted account always has `is_active = false`.
- Never rely on the deleted user's email/phone being unique or valid in forms — they are not real contact details any more.

---

## 7. Edge cases & FAQ

| Question | Answer |
|---|---|
| User calls it twice quickly | The first succeeds (`200`); the second gets `401` (token already invalidated). Disable the button after the first tap and treat a `401` as success-equivalent. |
| User forgot their password | The endpoint needs the current password. Send them through the normal *Forgot password* flow first (`POST /user/auth/forgot-password` → `reset-password`), then delete. |
| Account was created without a password | Not possible through the normal sign-up (password is mandatory), so it cannot happen for learners. If it ever does, the call returns `422` "The password is incorrect." |
| Does deleting cancel active courses / bookings / orders? | No. Nothing is cancelled or refunded automatically. Enrollments, bookings and orders remain as they were; refunds/cancellations are an admin/business process. Warn users if they have an upcoming consultation booking or an open order (you already have those lists in the app). |
| Any rate limit / lockout on wrong passwords? | No dedicated limiter on this endpoint. Add a client-side attempt counter if you want to discourage guessing. |
| Language of `message` | Follows `Accept-Language` (`en` / `ar`). Send the header on every call. |
| Is `message` safe to show directly? | Yes for `200` / `422` / `403`. For `401` use your own localized "session expired" copy — the backend sends it in English only. |

---

## 8. QA checklist

- [ ] Wrong password → `422`, dialog stays open, message shown, account still works.
- [ ] Empty password → `422` (`The Password field is required.`), no request should even be sent if you validate client-side.
- [ ] Correct password → `200`; app returns to login; old token no longer works (`GET /user/profile` → `401`).
- [ ] Logging in with the old email **and** with the old phone number fails with the generic invalid-credentials `422`.
- [ ] Same email/phone can start a new registration (`POST /user/auth/register` → `201`) and complete verification.
- [ ] Push notifications stop arriving on the device; SSE stream is closed.
- [ ] Trainer account → button hidden; a forced call returns `403`.
- [ ] Both `Accept-Language: en` and `ar` show localized messages.
- [ ] Admin dashboard: book-orders list/details, enrollments, attendance, certificates and consultation bookings for a deleted user render without errors and show the "Deleted user" treatment.
