ifnex/03_WordPress/wp-admin/css/login-rtl.css
Kazem Alghasi f1d5667fe8 feat(deploy): preflight + smoke-test + UAT checklist (phases 3.9.1–3.11)
Prepare the project for production deployment and final client
acceptance testing. Adds preflight fail-fast command, hardened FX rate
importer, currency seeder, deploy scripts, E2E smoke test, and the
43-item UAT checklist document with sign-off form.

Key changes:

- Phase 3.9.1 — hotfix (AddressBookEntry):
  - Add $attributes defaults (use_count=0, is_default=false) so
    instance values are populated right after create() without
    refresh(). Resolves the red test
    `it_increments_use_count_and_sets_last_used_at`.

- Phase 3.10 — deploy infrastructure (Laravel):
  - Add `ifnex:preflight` command: fail-fast audit of production
    environment (APP_ENV, APP_DEBUG, APP_KEY, IFNEX_API_KEY,
    IFNEX_BRIDGE_API_KEY, ZARINPAL_SANDBOX, DB_CONNECTION, CORS,
    currencies seeded, PHP extensions, storage writable). Exits 1
    if any check fails; supports --skip-db for pre-migrate runs.
  - Add `ifnex:import-fx-rates` command: replacement for
    `ifnex:update-rates`. Fetches USD/AED from Exir.io + EUR/CNY
    from FreeCurrencyAPI. Supports --auto-approve (cron use) and
    --threshold (max change % for auto-approve). Logs critical on
    HTTP failure. URL configurable via EXIR_API_URL env.
  - Add `CurrencySeeder`: creates 5 base currencies (USD/AED/EUR/
    CNY/IRR) with rate=0 (IRR=1). Idempotent — does not overwrite
    real rates already set by admin or cron.
  - Update `DatabaseSeeder`: skip super_admin with password
    'password' in production (still created in local/testing).
  - Fix `bootstrap/app.php`: remove dead `use ApiKeyAuth` import
    (actual class is ApiKeyMiddleware). Latent fatal if api_key
    alias was used.
  - Add `config/ifnex.php` exir block + preflight config.
  - Harden `.env.example`: MySQL default, EXIR_API_URL,
    SANCTUM_STATEFUL_DOMAINS, APP_TIMEZONE, LOG_DAILY_DAYS, fix
    duplicate CURRENCY_API_KEY.

- Phase 3.10 — deploy scripts (Linux):
  - Add `scripts/deploy.sh`: idempotent 13-step deploy (composer
    install → key:generate → preflight → migrate → seed → FX import
    → cache → chown). Fail-fast with set -e.
  - Add `scripts/cron-exir-rates.sh`: daily cron wrapper for
    `ifnex:import-fx-rates --source=all --auto-approve`.

- Phase 3.10.1 — preflight hotfix:
  - Make expected DB connection configurable
    (ifnex.preflight.expected_db_connection) instead of hardcoded
    'mysql'. Prevents cascade test failure when tests would have to
    mutate database.default to mysql in a sqlite test environment
    (Unknown database ':memory:' → broken connection pool → 22
    previously-green tests fail with "There is already an active
    transaction").
  - Rewrite IfnexPreflightTest to use config() rather than mutating
    database.default.

- Phase 3.11 — UAT:
  - Add `ifnex:smoke-test` command: E2E post-deploy automated test
    (25+ checks: DB connection, core tables, currencies with
    non-zero rates, countries count, roles, admin user, system
    settings, storage writable, config/route cache, 4 API
    endpoints: /up, /countries, /track/{invalid}, /customer/profile
    without token).
  - Add 9 feature tests for smoke-test (PASS + 6 FAIL + custom URL
    + 422/500 handling + auth bypass detection).
  - Add `UAT-3.11.md`: 43-item Persian checklist for client
    acceptance testing across 15 feature areas. Includes sign-off
    form for both parties (client + VernaSoft).
  - Update `CLIENT_DELIVERY.md` to record phases 3.10 and 3.11.

Tests:
- Total: 51 → 82 tests, 150 → 220+ assertions, all green.
- New: IfnexPreflightTest (9), IfnexImportFxRatesTest (9),
  CurrencySeederTest (4), IfnexSmokeTestTest (9) = 31 new tests.

Documentation:
- DEPLOYMENT_HANDOFF.md §2.4: add CurrencySeeder + import-fx-rates.
- DEPLOYMENT_HANDOFF.md §2.8: update cron to use cron-exir-rates.sh.
- UAT-3.11.md: new 43-item client acceptance checklist + sign-off.
- CLIENT_DELIVERY.md: record phases 3.10 and 3.11 completion.
2026-10-31 22:18:15 +03:30

517 lines
8.6 KiB
CSS

/*! This file is auto-generated */
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
body {
background: #f0f0f1;
min-width: 0;
color: #3c434a;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
font-size: 13px;
line-height: 1.4;
}
a {
color: var(--wp-admin-theme-color-darker-10);
transition-property: border, background, color;
transition-duration: .05s;
transition-timing-function: ease-in-out;
text-underline-position: under;
}
a {
outline: 0;
}
a:hover,
a:active {
color: var(--wp-admin-theme-color-darker-20);
}
a:focus {
color: var(--wp-admin-theme-color-darker-20);
box-shadow: 0 0 0 var(--wp-admin-border-width-focus, 1.5px) var(--wp-admin-theme-color, #3858e9);
/* Only visible in Windows High Contrast mode */
outline: 2px solid transparent;
}
p {
line-height: 1.5;
}
.login .message,
.login .notice,
.login .success {
border-right: 4px solid #3858e9;
padding: 8px 12px;
margin-top: 0;
margin-right: 0;
margin-bottom: 20px;
background-color: #fff;
word-wrap: break-word;
}
.login .message p,
.login .notice p,
.login .success p {
font-size: 13px;
line-height: 1.54;
margin: 0.5em 0;
}
.login .success {
border-right-color: #4ab866;
background-color: #eff9f1;
}
/* Match border color from common.css */
.login .notice-error {
border-right-color: #cc1818;
background-color: #fcf0f0;
}
.login .login-error-list {
list-style: none;
}
.login .login-error-list li + li {
margin-top: 4px;
}
#loginform p.submit,
.login-action-lostpassword p.submit {
border: none;
margin: -10px 0 20px; /* May want to revisit this */
}
.login * {
margin: 0;
padding: 0;
}
.login .input::-ms-clear {
display: none;
}
.login .pw-weak {
margin-bottom: 15px;
}
/* rtl:ignore */
.login .button.wp-hide-pw {
background: transparent;
border: 1px solid transparent;
box-shadow: none;
font-size: 14px;
line-height: normal;
width: 2.5rem;
height: 2.5rem;
min-width: 40px;
min-height: 40px;
margin: 0;
padding: 5px 9px;
position: absolute;
right: 0;
top: 0;
}
.login .button.wp-hide-pw:hover {
background: transparent;
}
.login .button.wp-hide-pw:focus {
background: transparent;
border-color: var(--wp-admin-theme-color);
box-shadow: 0 0 0 1px var(--wp-admin-theme-color);
/* Only visible in Windows High Contrast mode */
outline: 2px solid transparent;
}
.login .button.wp-hide-pw:active {
background: transparent;
box-shadow: none;
transform: none;
}
.login .button.wp-hide-pw .dashicons {
width: 1.25rem;
height: 1.25rem;
}
.login .wp-pwd {
position: relative;
}
.no-js .hide-if-no-js {
display: none;
}
.login form {
margin: 24px 0;
padding: 26px 24px;
font-weight: 400;
overflow: hidden;
background: #fff;
border: 1px solid #c3c4c7;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
}
.login form.shake {
animation: shake 0.2s cubic-bezier(.19,.49,.38,.79) both;
animation-iteration-count: 3;
transform: translateX(0);
}
@keyframes shake {
25% {
transform: translateX(20px);
}
75% {
transform: translateX(-20px);
}
100% {
transform: translateX(0);
}
}
@media (prefers-reduced-motion: reduce) {
.login form.shake {
animation: none;
transform: none;
}
}
.login-action-confirm_admin_email #login {
width: 60vw;
max-width: 650px;
margin-top: -2vh;
}
@media screen and (max-width: 782px) {
.login-action-confirm_admin_email #login {
box-sizing: border-box;
margin-top: 0;
padding-right: 4vw;
padding-left: 4vw;
width: 100vw;
}
}
.login form .forgetmenot {
display: flex;
align-items: center;
gap: 4px;
/* Matches the height of the Log In button beside it, so the row centers on it. */
min-height: 40px;
font-weight: 400;
float: right;
margin-bottom: 0;
}
/* Centering aligns margin boxes, so margins left from the inline layout would offset these. */
.login form .forgetmenot input[type="checkbox"],
.login form .forgetmenot label {
margin-top: 0;
margin-bottom: 0;
}
.login .button-primary {
float: left;
}
.login .reset-pass-submit {
display: flex;
flex-flow: row wrap;
justify-content: space-between;
}
.login .reset-pass-submit .button {
display: inline-block;
float: none;
margin-bottom: 6px;
}
.login .admin-email-confirm-form .submit {
text-align: center;
}
.admin-email__later {
text-align: right;
}
.login form p.admin-email__details {
margin: 1.1em 0;
}
.login .admin-email__heading {
border-bottom: 1px #f0f0f1 solid;
color: #50575e;
font-weight: normal;
padding-bottom: 0.5em;
text-align: right;
}
.admin-email__actions div {
padding-top: 1.5em;
}
.login .admin-email__actions .button-primary {
float: none;
margin-right: 0.25em;
margin-left: 0.25em;
}
#login form p {
margin-bottom: 0;
}
#login form .indicator-hint,
#login #reg_passmail {
margin-bottom: 16px;
}
#login form p.submit {
margin: 0;
padding: 0;
}
.login label {
font-size: 14px;
line-height: 1.5;
display: inline-block;
margin-bottom: 3px;
}
.login .forgetmenot label,
.login .pw-weak label {
line-height: 1.5;
vertical-align: baseline;
}
.login h1 {
text-align: center;
}
.login h1 a {
background-image: url(../images/w-logo-gray.png?ver=20260303);
background-image: none, url(../images/wordpress-logo-gray.svg?ver=20260303);
background-size: 84px;
background-position: center top;
background-repeat: no-repeat;
color: #3c434a;
height: 84px;
font-size: 20px;
font-weight: 400;
line-height: 1.3;
margin: 0 auto 24px;
padding: 0;
text-decoration: none;
width: 84px;
text-indent: -9999px;
outline: none;
overflow: hidden;
display: block;
}
#login {
width: 320px;
padding: 5% 0 0;
margin: auto;
}
.login #nav,
.login #backtoblog {
font-size: 13px;
padding: 0 24px;
}
.login #nav {
margin: 24px 0 0;
}
#backtoblog {
margin: 16px 0;
word-wrap: break-word;
}
.login #nav a,
.login #backtoblog a {
color: #50575e;
}
.login #nav a:hover,
.login #backtoblog a:hover,
.login h1 a:hover {
color: var(--wp-admin-theme-color-darker-20);
}
.login #nav a:focus,
.login #backtoblog a:focus,
.login h1 a:focus {
color: var(--wp-admin-theme-color-darker-20);
}
.login .privacy-policy-page-link {
text-align: center;
width: 100%;
margin: 3em 0 2em;
}
.login form .input,
.login input[type="text"],
.login input[type="password"] {
font-size: 24px;
line-height: 1.33333333; /* 32px */
width: 100%;
border-width: 0.0625rem;
padding: 0.1875rem 0.3125rem; /* 3px 5px */
margin: 0 0 16px 6px;
min-height: 40px;
max-height: none;
}
.login input.password-input {
font-family: Consolas, Monaco, monospace;
}
/* rtl:ignore */
.js.login input.password-input {
padding-right: 2.5rem;
}
.js.login-action-resetpass input[type="text"],
.js.login-action-resetpass input[type="password"],
.js.login-action-rp input[type="text"],
.js.login-action-rp input[type="password"] {
margin-bottom: 0;
}
.login #pass-strength-result {
font-weight: 600;
margin: -1px 0 16px 5px;
padding: 6px 5px;
text-align: center;
width: 100%;
}
body.interim-login {
height: auto;
}
.interim-login #login {
padding: 0;
margin: 5px auto 20px;
}
.interim-login.login h1 a {
width: auto;
}
.interim-login #login_error,
.interim-login.login .message {
margin: 0 0 16px;
}
.interim-login.login form {
margin: 0;
}
/* Hide visually but not from screen readers */
.screen-reader-text,
.screen-reader-text span {
border: 0;
clip-path: inset(50%);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
/* Many screen reader and browser combinations announce broken words as they would appear visually. */
word-wrap: normal !important;
word-break: normal !important;
}
/* Hide the Edge "reveal password" native button */
input::-ms-reveal {
display: none;
}
#language-switcher {
padding: 0;
overflow: visible;
background: none;
border: none;
box-shadow: none;
}
#language-switcher select {
margin-left: 0.25em;
}
.language-switcher {
margin: 0 auto;
padding: 0 0 24px;
text-align: center;
}
.language-switcher form {
display: inline-block;
}
.language-switcher label {
display: block;
margin-bottom: 0.5em;
text-align: right;
}
.language-switcher label .dashicons {
margin-right: 0.5em;
width: auto;
height: auto;
}
.login .language-switcher .button {
margin-bottom: 0;
}
@media screen and (max-height: 550px) {
#login {
padding: 20px 0;
}
#language-switcher {
margin-top: 0;
}
}
@media screen and (max-width: 782px) {
.interim-login input[type=checkbox] {
width: 1rem;
height: 1rem;
}
.interim-login input[type=checkbox]:checked:before {
width: 1.3125rem;
height: 1.3125rem;
margin: -0.1875rem -0.25rem 0 0;
}
#language-switcher label,
#language-switcher select {
margin-left: 0;
}
}
@media screen and (max-width: 400px) {
.login .language-switcher .button {
display: block;
margin: 5px auto 0;
}
}