# Business Details

***

<figure><img src="https://3626236831-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fa8uHYewwKFFVyUWKAUtL%2Fuploads%2FMml86wr6xjHnPmEyLkLT%2F1315b4c71302c9cb39d6c3900ba10d41a5de8a68.jpg?alt=media&#x26;token=15361615-420d-4d5b-88be-7428ee7c2baa" 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) |

***


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jesus-gimenez.gitbook.io/xex-scripts/premium-mods/business-details.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
