> For the complete documentation index, see [llms.txt](https://jesus-gimenez.gitbook.io/xex-scripts/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jesus-gimenez.gitbook.io/xex-scripts/premium-mods/business-details.md).

# Business Details

***

<figure><img src="/files/IGYtMWZeX7GCqLEPtIVP" alt=""><figcaption></figcaption></figure>

## Overview

XeX BusinessDetails is a premium, full-featured business directory and review system for FiveM servers. It provides a modern glassmorphism NUI interface where players can browse businesses, view their open/closed status, set GPS waypoints, and leave star ratings with reviews. Business owners can toggle their open/close status with cooldown protection, and admins can moderate reviews. Built with multi-framework support and optimized for performance.

### Supported Frameworks

| Framework  | Version | Status         |
| ---------- | ------- | -------------- |
| ESX Legacy | 1.6.0+  | ✅ Full Support |
| QBCore     | Latest  | ✅ Full Support |
| QBox       | Latest  | ✅ Full Support |

***

## Features Summary

### Core Features

| Category        | Feature                | Description                                                               |
| --------------- | ---------------------- | ------------------------------------------------------------------------- |
| **Directory**   | Business Listing       | Configurable list of businesses with images, descriptions, and categories |
| **Directory**   | Real-Time Status       | Open/closed status updated in real-time across all players                |
| **Directory**   | GPS Waypoint           | Click to set map waypoint to any business location                        |
| **Directory**   | Category Tabs          | Filter businesses by configurable categories                              |
| **Directory**   | Search                 | Instant text search by business name with debounced input (150ms)         |
| **Directory**   | Filter Chips           | All / Open / Closed / My Business filter buttons                          |
| **Reviews**     | Star Ratings           | 1-5 star rating system per business, stored in MySQL                      |
| **Reviews**     | Written Reviews        | Text reviews with configurable min/max character limits                   |
| **Reviews**     | Average Ratings        | Cached average rating displayed per business card                         |
| **Reviews**     | Owner Replies          | Business owners can reply to reviews on their business                    |
| **Reviews**     | Admin Moderation       | Admins can delete reviews and reply to any review                         |
| **Reviews**     | Self-Review Protection | Players cannot review their own business                                  |
| **Reviews**     | Duplicate Protection   | Configurable max reviews per player per business                          |
| **Toggle**      | Open/Close             | Business employees can toggle their business open/close                   |
| **Toggle**      | Cooldown               | Configurable cooldown between toggles (skip cooldown when closing)        |
| **Toggle**      | Grade Restriction      | Minimum job grade required per business                                   |
| **Announce**    | Chat Messages          | Automatic chat announcements when businesses open/close                   |
| **Announce**    | Periodic Reminders     | Configurable interval to remind players of open businesses                |
| **Announce**    | Discord Webhooks       | Rich embeds sent to Discord on status changes                             |
| **UI**          | Glassmorphism          | Modern dark translucent glass-effect interface                            |
| **UI**          | Responsive             | Auto-fill card grid with responsive breakpoint at 900px                   |
| **UI**          | Animations             | Container, card, review panel, and toast animations                       |
| **System**      | Multi-Framework        | ESX, QBCore, QBox with auto-detection                                     |
| **System**      | ox\_lib Support        | Optional; used for notifications and callbacks                            |
| **System**      | Auto Updater           | JSON-based version checker via GitHub Gist                                |
| **System**      | Auto DB                | Reviews table auto-created on first start                                 |
| **I18n**        | 2 Languages            | English, Spanish                                                          |
| **Integration** | lb-phone               | Client exports for phone app integration                                  |
| **Integration** | External Resources     | Full export API for third-party integrations                              |

***

## Installation

### Requirements

* FiveM Server Build 5181+
* [oxmysql](https://github.com/overextended/oxmysql)
* Framework: ESX Legacy, QBCore, or QBox
* Optional: [ox\_lib](https://github.com/overextended/ox_lib) (for enhanced notifications)

### Quick Start

{% stepper %}
{% step %}
Place the resource folder in your server resources:

* Put `xex_businessdetails` in your resources folder.
  {% endstep %}

{% step %}
Add the resource to your server configuration:

* In `server.cfg` add:
  * `ensure xex_businessdetails`
    {% endstep %}

{% step %}
Configure resource files:

* Edit `config.lua` (categories, businesses, language)
* Edit `svconfig.lua` (webhooks, reviews, admin groups)
* Add business images to `web/img/`
  {% endstep %}

{% step %}
Restart your server:

* Restart server; the database table is created automatically on first start.
  {% endstep %}
  {% endstepper %}

### Recommended Resource Order

```cfg
# server.cfg
ensure oxmysql
ensure ox_lib          # Optional but recommended
ensure es_extended     # or qb-core / qbox
ensure xex_businessdetails
```

***

## Configuration

### Framework Settings

```lua
-- config.lua
Config.Framework = 'auto'                       -- 'auto' | 'esx' | 'qb' | 'qbox'
Config.AutoDetectOrder = {'qbox', 'qb', 'esx'}  -- Detection priority
```

| Value    | Behavior                                      |
| -------- | --------------------------------------------- |
| `'auto'` | Detects framework automatically (recommended) |
| `'esx'`  | Force ESX Legacy                              |
| `'qb'`   | Force QBCore                                  |
| `'qbox'` | Force QBox                                    |

### Language Settings

```lua
Config.Language = 'en'  -- 'en' | 'es'
```

### General Settings

```lua
Config.BusinessCommand = 'business'       -- Command to open business directory
Config.CooldownSeconds = 1800             -- 30 min cooldown between open/close toggles
Config.ReminderIntervalMinutes = 15       -- Interval for chat reminders (0 = disabled)
```

| Option                           | Default      | Description                                                   |
| -------------------------------- | ------------ | ------------------------------------------------------------- |
| `Config.BusinessCommand`         | `'business'` | Chat command to open the directory UI                         |
| `Config.CooldownSeconds`         | `1800`       | Seconds between open toggles (closing has no cooldown)        |
| `Config.ReminderIntervalMinutes` | `15`         | Minutes between open businesses chat reminders (0 = disabled) |

### Categories

Define the category tabs displayed in the UI:

```lua
Config.Categories = {
    { id = 'restaurant',  label = 'Restaurants',   icon = 'fas fa-utensils',   color = '#F59E0B' },
    { id = 'bar',         label = 'Bars & Clubs',  icon = 'fas fa-cocktail',   color = '#8B5CF6' },
    { id = 'cafe',        label = 'Cafes',         icon = 'fas fa-mug-hot',    color = '#06B6D4' },
    { id = 'dealer',      label = 'Dealerships',   icon = 'fas fa-car',        color = '#3B82F6' },
    { id = 'other',       label = 'Other',         icon = 'fas fa-store',      color = '#6B7280' },
}
```

| Field   | Type   | Description                                          |
| ------- | ------ | ---------------------------------------------------- |
| `id`    | string | Unique identifier (matches `category` in businesses) |
| `label` | string | Display name in the UI tab                           |
| `icon`  | string | FontAwesome 6 icon class                             |
| `color` | string | Hex color for the category badge                     |

### Business Directory

Each business entry in `Config.Businesses`:

```lua
Config.Businesses = {
    {
        label = 'Bahama Mamas',             -- Display name
        name = 'miner',                     -- ESX/QB job name (for toggle permission)
        category = 'bar',                   -- Category ID
        minGradeToManipulate = 0,           -- Minimum job grade to toggle (0 = all grades)
        description = 'A tropical refuge...', -- Description shown in UI card
        img = 'bahamas.png',                -- Image filename (in web/img/)
        coords = vector3(-1402.82, -609.8, 29.32), -- Map coords (for GPS waypoint)
        open = false,                       -- Default open state on resource start
    },
    -- ... more businesses
}
```

| Field                  | Type    | Required | Description                                 |
| ---------------------- | ------- | -------- | ------------------------------------------- |
| `label`                | string  | ✅        | Display name                                |
| `name`                 | string  | ✅        | Job name (used for toggle permission check) |
| `category`             | string  | ❌        | Category ID (default: `'other'`)            |
| `minGradeToManipulate` | number  | ❌        | Min job grade to toggle (default: `0`)      |
| `description`          | string  | ❌        | Card description text                       |
| `img`                  | string  | ❌        | Image filename relative to `web/img/`       |
| `coords`               | vector3 | ✅        | Map coordinates for GPS waypoint            |
| `open`                 | boolean | ❌        | Default open state (default: `false`)       |

***

## Server Configuration

### svconfig.lua

#### Update System

```lua
ConfigSv.CheckForUpdates = true  -- Check for new versions on resource start
```

#### Notification System

```lua
ConfigSv.NotificationSystem = 'auto'  -- 'auto' | 'ox_lib' | 'esx' | 'qb' | 'native'
```

| Value      | Behavior                                      |
| ---------- | --------------------------------------------- |
| `'auto'`   | ox\_lib → Framework → Native (fallback chain) |
| `'ox_lib'` | Force ox\_lib notifications                   |
| `'esx'`    | Force ESX notifications                       |
| `'qb'`     | Force QBCore notifications                    |
| `'native'` | Force GTA V native notifications              |

#### Discord Webhooks

```lua
ConfigSv.Webhook = nil                   -- Discord webhook URL (nil = disabled)
ConfigSv.WebhookName = 'XeX Business'    -- Bot display name
ConfigSv.WebhookAvatar = nil             -- Bot avatar URL
ConfigSv.WebhookColor = 16753920         -- Embed color (orange)
```

Webhook sends a rich embed when a business is opened or closed, including:

* Player name who toggled
* Business name
* Open/Closed status
* Timestamp

#### Chat Announcements

```lua
ConfigSv.AnnounceChatOnToggle = true  -- Send chat message when a business opens/closes
ConfigSv.ReminderEnabled = true       -- Enable periodic open businesses reminder
```

#### Reviews System

```lua
ConfigSv.ReviewsEnabled = true         -- Enable/disable the entire review system
ConfigSv.MaxReviewsPerPlayer = 1       -- Max reviews per player per business
ConfigSv.ReviewMinLength = 10          -- Minimum comment characters
ConfigSv.ReviewMaxLength = 500         -- Maximum comment characters
ConfigSv.ReplyMaxLength = 300          -- Maximum reply characters
```

#### Admin Groups

```lua
ConfigSv.AdminGroups = {'admin', 'superadmin', 'mod', 'owner', 'god'}

-- Optional: Custom admin check function (overrides AdminGroups)
ConfigSv.CustomAdminCheck = nil
-- Example:
-- ConfigSv.CustomAdminCheck = function(source)
--     return IsPlayerAceAllowed(source, 'command.admin')
-- end
```

***

## Database

The `business_reviews` table is **automatically created** on first start. No manual SQL import needed.

If you prefer to create it manually:

```sql
CREATE TABLE IF NOT EXISTS `business_reviews` (
    `id` INT NOT NULL AUTO_INCREMENT,
    `business_id` INT NOT NULL,
    `identifier` VARCHAR(90) NOT NULL,
    `author_name` VARCHAR(60) NOT NULL DEFAULT 'Anonymous',
    `rating` TINYINT NOT NULL DEFAULT 5,
    `comment` TEXT NOT NULL,
    `reply` TEXT DEFAULT NULL,
    `reply_author` VARCHAR(60) DEFAULT NULL,
    `reply_date` DATETIME DEFAULT NULL,
    `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
    PRIMARY KEY (`id`),
    INDEX `idx_business` (`business_id`),
    INDEX `idx_identifier` (`identifier`),
    INDEX `idx_rating` (`rating`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
```

***

## How It Works

### Complete Flow

{% stepper %}
{% step %}
Player types /business → Server sends business data + player job/grade → Client opens NUI with glassmorphism UI
{% endstep %}

{% step %}
Player can:

* Browse categories (tabs)
* Search by name
* Filter: All / Open / Closed / My Business
* Click GPS → sets waypoint on map
* Click Toggle → opens/closes their business (if permitted)
* Click Stars → opens review panel
  {% endstep %}

{% step %}
Within the review panel:

* Read reviews
* Submit review (1-5 stars + comment)
* Delete own review
* Reply (owner/admin)
* Delete review (admin)
  {% endstep %}

{% step %}
Press ESC/Backspace → closes UI
{% endstep %}
{% endstepper %}

### Business Open/Close Toggle

{% stepper %}
{% step %}
Player clicks the toggle button on their business card.
{% endstep %}

{% step %}
Client sends `toggleBusiness` callback to server via Bridge.
{% endstep %}

{% step %}
Server validates:

* Business exists
* Player's job matches `business.name`
* Player's grade ≥ `business.minGradeToManipulate`
* Cooldown has elapsed (only for opening, closing skips cooldown)
  {% endstep %}

{% step %}
Server toggles state and broadcasts to all clients. Optional: chat message and Discord webhook sent.
{% endstep %}
{% endstepper %}

### Cooldown System

* Default: **30 minutes** (`Config.CooldownSeconds = 1800`)
* Cooldown applies **only when opening** a business
* Closing a business has **no cooldown** (can close immediately)
* Cooldown is per-business, tracked server-side

### Permission System

| Check           | Description                                               |
| --------------- | --------------------------------------------------------- |
| Job Match       | Player's current job must match the business `name` field |
| Grade Check     | Player's job grade must be ≥ `minGradeToManipulate`       |
| Admin (Reviews) | Admin groups or custom check for review moderation        |
| Self-Review     | Players cannot review their own business                  |

### Reviews & Ratings

* **Submit:** Players can leave 1-5 star rating + text comment
* **Character limits:** Configurable min (10) / max (500) characters
* **Duplicate limit:** Max 1 review per player per business (configurable)
* **Average rating:** Calculated via SQL `AVG(rating)` and displayed on cards
* **Replies:** Business owners and admins can reply to reviews
* **Moderation:** Admins or the review author can delete reviews
* **Identification:** Reviews track player identifier and character name

### Chat Reminders

When `ConfigSv.ReminderEnabled = true` and `Config.ReminderIntervalMinutes > 0`:

* Every N minutes, a chat message lists all currently open businesses
* Uses `[BUSINESS]` label with orange color formatting

***

## NUI Interface

### Main Directory View

* Header with search bar, open business count, and close button
* Dynamic category tabs (auto-generated from `Config.Categories`)
* Filter chips: All / Open / Closed / My Business
* Business card grid with responsive layout

### Category Tabs

* Auto-generated from config with icon and label
* Click to filter by category
* "All" tab shows everything

### Filter Chips

| Filter      | Behavior                                            |
| ----------- | --------------------------------------------------- |
| All         | Shows all businesses in current category            |
| Open        | Shows only businesses with `open = true`            |
| Closed      | Shows only businesses with `open = false`           |
| My Business | Shows only businesses matching player's current job |

### Business Cards

Each card displays:

* Business image (from `web/img/`)
* Status badge (OPEN = green, CLOSED = red)
* Category badge with color
* Star rating (clickable to open review panel)
* Business name and description
* GPS button (set map waypoint)
* Toggle button (only visible to business employees)

### Review Panel

Slide-in panel from the right showing:

* Business name and average rating
* Star input (hover highlight + click to select)
* Text area with character counter
* Submit button
* List of existing reviews with:
  * Author name and date (relative time format)
  * Star rating display
  * Comment text
  * Reply section (if replied)
  * Reply button (owner/admin only)
  * Delete button (author/admin only)
* Reply modal dialog

***

## Exports & Events

### Client Exports

```lua
-- Open the business directory UI (triggers server data fetch)
exports['xex_businessdetails']:OpenBusinessMenu()

-- Close the business directory UI
exports['xex_businessdetails']:CloseBusinessMenu()

-- Check if the business directory is currently open
local isOpen = exports['xex_businessdetails']:IsMenuOpen()

-- Get current business data (includes open/closed status)
local businesses = exports['xex_businessdetails']:GetBusinesses()
-- Returns: table of { id, label, name, category, description, img, coords, open, ... }
```

### Server Exports

```lua
-- Get all businesses with current status
local allBusinesses = exports['xex_businessdetails']:GetAllBusinesses()

-- Get only currently open businesses
local openBusinesses = exports['xex_businessdetails']:GetOpenBusinesses()

-- Get the framework bridge object
local bridge = exports['xex_businessdetails']:GetBridge()

-- Trigger manual update check
exports['xex_businessdetails']:CheckForUpdates()
```

### Shared Exports

```lua
-- Get the Bridge object (works from both client and server)
local bridge = exports['xex_businessdetails']:GetBridge()
```

### Server Callbacks (Bridge)

| Callback                                | Parameters                        | Returns                | Description                     |
| --------------------------------------- | --------------------------------- | ---------------------- | ------------------------------- |
| `xex_businessdetails:toggleBusiness`    | `businessId`                      | `boolean`              | Toggle business open/close      |
| `xex_businessdetails:getReviews`        | `businessId`                      | `table`                | Get reviews for a business      |
| `xex_businessdetails:getAverageRatings` | —                                 | `table`                | Get average rating per business |
| `xex_businessdetails:submitReview`      | `{ businessId, rating, comment }` | `{ success, message }` | Submit a review                 |
| `xex_businessdetails:deleteReview`      | `{ reviewId }`                    | `{ success, message }` | Delete a review                 |
| `xex_businessdetails:replyReview`       | `{ reviewId, reply }`             | `{ success, message }` | Reply to a review               |

### Net Events

| Event                                | Direction            | Parameters           | Description             |
| ------------------------------------ | -------------------- | -------------------- | ----------------------- |
| `xex_businessdetails:showMenu`       | Server → Client      | `data, job, grade`   | Opens the business UI   |
| `xex_businessdetails:toggleBusiness` | Server → All Clients | `businessId, isOpen` | Syncs open/close status |
| `xex_businessdetails:syncAll`        | Server → Client      | `data`               | Full business data sync |
| `xex_businessdetails:requestSync`    | Client → Server      | —                    | Request business data   |
| `xex_businessdetails:openMenu`       | Client → Server      | —                    | Open menu via export    |

### NUI Callbacks

| Callback            | Data                              | Description               |
| ------------------- | --------------------------------- | ------------------------- |
| `close`             | —                                 | Close the UI              |
| `toggleBusiness`    | `{ businessId }`                  | Toggle business state     |
| `markOnMap`         | `{ businessId }`                  | Set GPS waypoint          |
| `getReviews`        | `{ businessId }`                  | Fetch reviews list        |
| `getAverageRatings` | —                                 | Fetch all average ratings |
| `submitReview`      | `{ businessId, rating, comment }` | Submit a review           |
| `deleteReview`      | `{ reviewId }`                    | Delete a review           |
| `replyReview`       | `{ reviewId, reply }`             | Reply to a review         |

### Commands

| Command                  | Context        | Description                                         |
| ------------------------ | -------------- | --------------------------------------------------- |
| `/{BusinessCommand}`     | Client         | Opens the business directory (default: `/business`) |
| `businessdetails_update` | Server Console | Manual update check                                 |

***

## lb-phone Integration

The resource exposes client exports designed for easy integration with **lb-phone** and other phone systems.

### Quick Setup

You can open the business directory from any lb-phone app, custom contact, or launcher button using:

```lua
exports['xex_businessdetails']:OpenBusinessMenu()
```

### Custom LB-Phone App Example

-Copy / paste this code replacing ExecuteCommand('business') with your Config.BusinessCommand.

```
    ["businessapp"] = { -- do not have any spaces in this name
        name = "Business", -- the name of the app
        description = "Details about business in the city.", -- the description of the app
        developer = "XeX", -- OPTIONAL the developer of the app
        defaultApp = true, -- OPTIONAL if set to true, app should be added without having to download it,
        size = 59812, -- OPTIONAL in kB
        images = {"https://i.imgur.com/iCvCepD.jpg"}, -- OPTIONAL array of images for the app on the app store
        ui = "", -- OPTIONAL
        icon = "https://i.imgur.com/iCvCepD.jpg", -- OPTIONAL app icon
        price = 0, -- OPTIONAL, Make players pay with in-game money to download the app
        landscape = false, -- OPTIONAL, if set to true, the app will be displayed in landscape mode
        onUse = function() -- OPTIONAL function to be called when the app is opened
            -- do something
            ExecuteCommand('business')
        end
    }
```

To open with exports:

```lua
-- In your lb-phone app (e.g., apps/businessdirectory.lua)

-- Open the directory when the app is launched
exports['xex_businessdetails']:OpenBusinessMenu()
```

Or from a **client script** triggered by lb-phone:

```lua
-- Register an event that lb-phone calls when the app is opened
RegisterNetEvent('phone:app:businessdirectory', function()
    exports['xex_businessdetails']:OpenBusinessMenu()
end)
```

### Reading Business Data

You can also read business data from lb-phone to show info in the phone UI:

```lua
-- Get all businesses and their status
local businesses = exports['xex_businessdetails']:GetBusinesses()

for _, business in ipairs(businesses) do
    print(business.label, business.open and 'OPEN' or 'CLOSED')
end

-- Check if directory is open before triggering
if not exports['xex_businessdetails']:IsMenuOpen() then
    exports['xex_businessdetails']:OpenBusinessMenu()
end
```

**Server-side** (for phone server scripts):

```lua
-- Get all open businesses from server
local openBusinesses = exports['xex_businessdetails']:GetOpenBusinesses()

-- Get full list from server
local allBusinesses = exports['xex_businessdetails']:GetAllBusinesses()
```

***

## Bridge System

The Bridge is a framework abstraction layer that provides unified API across ESX, QBCore, and QBox.

### Framework Detection

{% stepper %}
{% step %}
Checks `Config.Framework` setting.
{% endstep %}

{% step %}
If `'auto'`, tries frameworks in `Config.AutoDetectOrder` sequence.
{% endstep %}

{% step %}
Default order: QBox → QBCore → ESX.
{% endstep %}

{% step %}
Retries initialization up to 50 times.
{% endstep %}
{% endstepper %}

### Client Bridge API

```lua
-- Check if bridge is ready
Bridge.IsReady() -- returns boolean

-- Get detected framework name
Bridge.GetFramework() -- returns 'esx' | 'qb' | 'qbox'

-- Get framework object
Bridge.GetFrameworkObject() -- returns ESX/QBCore/exports table

-- Check ox_lib availability
Bridge.HasOxLib() -- returns boolean

-- Trigger a server callback
Bridge.TriggerCallback(name, cb, ...) -- custom event-based callback with 15s timeout

-- Send a notification
Bridge.Notify(message, type) -- ox_lib → ESX → QB → QBox fallback chain
```

### Server Bridge API

```lua
-- Register a server callback
Bridge.RegisterCallback(name, handler) -- handler receives (source, cb, ...)

-- Get player job info
Bridge.GetPlayerJob(source) -- returns jobName, jobGrade

-- Get player character name
Bridge.GetPlayerName(source) -- returns string

-- Get player identifier
Bridge.GetPlayerIdentifier(source) -- ESX identifier / QB citizenid / license fallback

-- Check if player is admin
Bridge.IsPlayerAdmin(source) -- checks CustomAdminCheck → AdminGroups
```

***

## Localization

### Supported Languages

| Code | Language |
| ---- | -------- |
| `en` | English  |
| `es` | Español  |

### Locale Keys Reference

| Key                       | English                                    | Description         |
| ------------------------- | ------------------------------------------ | ------------------- |
| `status_changed`          | Business status updated successfully!      | Toggle success      |
| `too_early`               | You must wait before changing status again | Cooldown active     |
| `business_open_generic`   | is now OPEN! Use /%s to find us.           | Chat on open        |
| `business_closed_generic` | is now CLOSED.                             | Chat on close       |
| `view_business`           | Open businesses:                           | Reminder prefix     |
| `any_open`                | None                                       | No businesses open  |
| `business_chat_label`     | \[BUSINESS]                                | Chat label tag      |
| `webhook_opened`          | opened                                     | Webhook embed       |
| `webhook_closed`          | closed                                     | Webhook embed       |
| `webhook_title`           | Business Status Change                     | Webhook embed title |
| `review_submitted`        | Your review has been submitted!            | Review success      |
| `review_deleted`          | Review deleted successfully                | Delete success      |
| `review_reply_sent`       | Reply sent successfully                    | Reply success       |
| `review_already_exists`   | You already reviewed this business         | Duplicate review    |
| `review_too_short`        | Review is too short (min %d characters)    | Validation          |
| `review_too_long`         | Review is too long (max %d characters)     | Validation          |
| `review_invalid_rating`   | Rating must be between 1 and 5             | Validation          |
| `review_not_found`        | Review not found                           | Not found           |
| `review_no_permission`    | You do not have permission to do that      | Unauthorized        |
| `review_own_business`     | You cannot review your own business        | Self-review         |

### Adding Languages

Add a new entry in `locales.lua`:

```lua
Locales['fr'] = {
    ['status_changed'] = 'Statut mis à jour avec succès !',
    ['too_early'] = 'Vous devez attendre avant de changer le statut',
    -- ... all keys
}
```

Then set `Config.Language = 'fr'` in `config.lua`.

***

## File Structure

```
xex_businessdetails/
├── client/
│   └── main.lua              # Client-side logic, NUI callbacks, exports
├── server/
│   ├── main.lua              # Server-side logic, commands, webhooks, exports
│   ├── reviews.lua           # Review CRUD operations (auto-creates DB table)
│   └── updater.lua           # Version checker
├── shared/
│   ├── bridge.lua            # Multi-framework abstraction layer
│   └── lib_loader.lua        # Safe ox_lib loader with fallbacks
├── web/
│   ├── index.html            # NUI page structure
│   ├── style.css             # Glassmorphism styles (1366 lines)
│   ├── app.js                # Application logic (784 lines)
│   └── img/                  # Business images (21 images)
├── config.lua                # Client configuration
├── svconfig.lua              # Server configuration
├── locales.lua               # Translations (en/es)
├── business_reviews.sql      # Database schema (optional manual import)
├── fxmanifest.lua            # Resource manifest
└── DOCS.md                   # This documentation
```

***

## Security

| Measure                | Description                                                           |
| ---------------------- | --------------------------------------------------------------------- |
| Server-side toggle     | Business open/close validated on server (job, grade, cooldown)        |
| Server-side reviews    | All review operations validated server-side                           |
| Cooldown enforcement   | Cooldown tracked server-side, cannot be bypassed by client            |
| Permission checks      | Job match + grade verification for toggle; admin check for moderation |
| Self-review prevention | Server checks if player's job matches business before allowing review |
| Duplicate prevention   | Server checks existing reviews before allowing new submission         |
| Input validation       | Rating range (1-5), comment length limits enforced server-side        |
| Reply permissions      | Only business owners and admins can reply to reviews                  |
| Delete permissions     | Only review author and admins can delete reviews                      |
| Bridge callbacks       | 15-second timeout on client callbacks to prevent hanging              |
| SQL injection          | Uses parameterized queries via oxmysql                                |

***

## Performance

| Aspect        | Detail                                                           |
| ------------- | ---------------------------------------------------------------- |
| Idle cost     | 0.00ms when UI is closed                                         |
| Active cost   | Minimal — only NUI framerate when open                           |
| ESC thread    | `Wait(0)` loop only active when `isMenuOpen = true`              |
| Review cache  | Average ratings cached on client, updated on submit/delete       |
| Business sync | Initial sync on player connect, broadcast updates only on toggle |
| Search        | 150ms debounced input to reduce re-renders                       |

***

## Troubleshooting

<details>

<summary>UI Doesn't Open</summary>

* Verify resource is started: `ensure xex_businessdetails`
* Check for errors in server console (F8)
* Verify framework detection: check console for `[XeX BusinessDetails] Framework:`
* Ensure `oxmysql` is started before this resource

</details>

<details>

<summary>Reviews Not Working</summary>

* Check `ConfigSv.ReviewsEnabled = true`
* Check server console for database errors
* Verify oxmysql connection works
* The `business_reviews` table is auto-created — check for creation errors

</details>

<details>

<summary>Open/Close Not Working</summary>

* Verify player's job matches `business.name` in config
* Verify player's grade ≥ `minGradeToManipulate`
* Check if cooldown is active (30 min by default)
* Check server console for permission denial messages

</details>

<details>

<summary>Webhook Not Sending</summary>

* Verify `ConfigSv.Webhook` is set (not `nil`)
* Test webhook URL in Discord settings
* Check server console for HTTP errors

</details>

<details>

<summary>Framework Not Detected</summary>

```lua
-- Force framework instead of auto
Config.Framework = 'esx' -- or 'qb' / 'qbox'
```

</details>

***

## Changelog

### v2.0.0

* Complete rewrite with Bridge architecture
* Multi-framework support (ESX, QBCore, QBox)
* Auto framework detection
* Full review system with star ratings, comments, replies, moderation
* Category tabs with configurable icons and colors
* Search and filter system (All/Open/Closed/My Business)
* Modern glassmorphism NUI with animations
* Client exports for lb-phone and external resource integration
* Server exports for business data access
* Discord webhook integration with rich embeds
* Periodic chat reminder system
* Configurable cooldown with close-skip
* ox\_lib optional integration with full fallback chain
* Auto database table creation
* JSON-based auto-updater
* English and Spanish localization
* Performance optimized (0.00ms idle)

***

## Support

| Channel | Link                                                   |
| ------- | ------------------------------------------------------ |
| Discord | [discord.gg/9yY3Jxnhh8](https://discord.gg/9yY3Jxnhh8) |

***
