SMS gateway api service for android application
This commit is contained in:
719
SAAS_INTEGRATION_GUIDE.md
Normal file
719
SAAS_INTEGRATION_GUIDE.md
Normal file
@@ -0,0 +1,719 @@
|
||||
# SaaS Application Integration Guide
|
||||
|
||||
## Overview
|
||||
|
||||
This guide shows how to integrate MySMSAPio into your SaaS application to send SMS and OTP messages.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. **MySMSAPio Server**: Running and accessible (e.g., `http://192.168.18.66:3005`)
|
||||
2. **API Key**: Client API key (starts with `api_live_...`)
|
||||
3. **Gateway Device**: At least one Android gateway online
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Create an API Key
|
||||
|
||||
**Via Admin Interface**:
|
||||
1. Go to `http://your-server:3005/admin/api_keys`
|
||||
2. Click "New API Key"
|
||||
3. Enter name (e.g., "My SaaS App")
|
||||
4. Enable permissions: `send_sms`, `receive_sms`
|
||||
5. Copy the API key (shown only once!)
|
||||
|
||||
**Via Rails Console**:
|
||||
```ruby
|
||||
result = ApiKey.generate!(
|
||||
name: "My SaaS App",
|
||||
permissions: {
|
||||
send_sms: true,
|
||||
receive_sms: true,
|
||||
send_otp: true,
|
||||
verify_otp: true
|
||||
}
|
||||
)
|
||||
puts result[:raw_key]
|
||||
# Save this key: api_live_abc123...
|
||||
```
|
||||
|
||||
### 2. Test Connection
|
||||
|
||||
```bash
|
||||
curl -X GET http://your-server:3005/up \
|
||||
-H "Authorization: Bearer api_live_your_key_here"
|
||||
```
|
||||
|
||||
Expected: `200 OK`
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Base URL
|
||||
```
|
||||
http://your-server:3005/api/v1
|
||||
```
|
||||
|
||||
### Authentication
|
||||
All requests require the `Authorization` header:
|
||||
```
|
||||
Authorization: Bearer api_live_your_key_here
|
||||
```
|
||||
|
||||
## Sending SMS
|
||||
|
||||
### Endpoint
|
||||
```
|
||||
POST /api/v1/sms/send
|
||||
```
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"to": "+959123456789",
|
||||
"message": "Your message here"
|
||||
}
|
||||
```
|
||||
|
||||
### Response (Success - 202 Accepted)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message_id": "msg_abc123def456...",
|
||||
"status": "queued"
|
||||
}
|
||||
```
|
||||
|
||||
### Response (Error - 422 Unprocessable Entity)
|
||||
```json
|
||||
{
|
||||
"error": "Invalid phone number format"
|
||||
}
|
||||
```
|
||||
|
||||
### Example: cURL
|
||||
```bash
|
||||
curl -X POST http://your-server:3005/api/v1/sms/send \
|
||||
-H "Authorization: Bearer api_live_your_key_here" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"to": "+959123456789",
|
||||
"message": "Hello from My SaaS App!"
|
||||
}'
|
||||
```
|
||||
|
||||
### Example: JavaScript/Node.js
|
||||
```javascript
|
||||
const axios = require('axios');
|
||||
|
||||
async function sendSMS(phoneNumber, message) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
'http://your-server:3005/api/v1/sms/send',
|
||||
{
|
||||
to: phoneNumber,
|
||||
message: message
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Authorization': 'Bearer api_live_your_key_here',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('SMS sent!');
|
||||
console.log('Message ID:', response.data.message_id);
|
||||
console.log('Status:', response.data.status);
|
||||
|
||||
return response.data.message_id;
|
||||
} catch (error) {
|
||||
console.error('Failed to send SMS:', error.response?.data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
sendSMS('+959123456789', 'Welcome to our service!')
|
||||
.then(messageId => console.log('Sent:', messageId))
|
||||
.catch(err => console.error('Error:', err));
|
||||
```
|
||||
|
||||
### Example: Python
|
||||
```python
|
||||
import requests
|
||||
|
||||
def send_sms(phone_number, message):
|
||||
url = "http://your-server:3005/api/v1/sms/send"
|
||||
headers = {
|
||||
"Authorization": "Bearer api_live_your_key_here",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
data = {
|
||||
"to": phone_number,
|
||||
"message": message
|
||||
}
|
||||
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
|
||||
if response.status_code == 202:
|
||||
result = response.json()
|
||||
print(f"SMS sent! Message ID: {result['message_id']}")
|
||||
return result['message_id']
|
||||
else:
|
||||
print(f"Failed: {response.json()}")
|
||||
raise Exception(response.json().get('error', 'Unknown error'))
|
||||
|
||||
# Usage
|
||||
message_id = send_sms("+959123456789", "Welcome to our service!")
|
||||
```
|
||||
|
||||
### Example: PHP
|
||||
```php
|
||||
<?php
|
||||
function sendSMS($phoneNumber, $message) {
|
||||
$url = 'http://your-server:3005/api/v1/sms/send';
|
||||
|
||||
$data = [
|
||||
'to' => $phoneNumber,
|
||||
'message' => $message
|
||||
];
|
||||
|
||||
$options = [
|
||||
'http' => [
|
||||
'header' => [
|
||||
"Authorization: Bearer api_live_your_key_here",
|
||||
"Content-Type: application/json"
|
||||
],
|
||||
'method' => 'POST',
|
||||
'content' => json_encode($data)
|
||||
]
|
||||
];
|
||||
|
||||
$context = stream_context_create($options);
|
||||
$result = file_get_contents($url, false, $context);
|
||||
|
||||
if ($result === FALSE) {
|
||||
throw new Exception('Failed to send SMS');
|
||||
}
|
||||
|
||||
$response = json_decode($result, true);
|
||||
echo "SMS sent! Message ID: " . $response['message_id'] . "\n";
|
||||
|
||||
return $response['message_id'];
|
||||
}
|
||||
|
||||
// Usage
|
||||
$messageId = sendSMS("+959123456789", "Welcome to our service!");
|
||||
?>
|
||||
```
|
||||
|
||||
### Example: Ruby
|
||||
```ruby
|
||||
require 'httparty'
|
||||
|
||||
def send_sms(phone_number, message)
|
||||
response = HTTParty.post(
|
||||
'http://your-server:3005/api/v1/sms/send',
|
||||
headers: {
|
||||
'Authorization' => 'Bearer api_live_your_key_here',
|
||||
'Content-Type' => 'application/json'
|
||||
},
|
||||
body: {
|
||||
to: phone_number,
|
||||
message: message
|
||||
}.to_json
|
||||
)
|
||||
|
||||
if response.code == 202
|
||||
puts "SMS sent! Message ID: #{response['message_id']}"
|
||||
response['message_id']
|
||||
else
|
||||
puts "Failed: #{response['error']}"
|
||||
raise StandardError, response['error']
|
||||
end
|
||||
end
|
||||
|
||||
# Usage
|
||||
message_id = send_sms("+959123456789", "Welcome to our service!")
|
||||
```
|
||||
|
||||
## Sending OTP
|
||||
|
||||
### Endpoint
|
||||
```
|
||||
POST /api/v1/otp/send
|
||||
```
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"phone_number": "+959123456789"
|
||||
}
|
||||
```
|
||||
|
||||
### Response (Success - 200 OK)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "OTP sent successfully",
|
||||
"expires_in": 300
|
||||
}
|
||||
```
|
||||
|
||||
### Response (Error - Rate Limited)
|
||||
```json
|
||||
{
|
||||
"error": "Rate limit exceeded. Maximum 3 OTP per hour for this number"
|
||||
}
|
||||
```
|
||||
|
||||
### Example: JavaScript
|
||||
```javascript
|
||||
async function sendOTP(phoneNumber) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
'http://your-server:3005/api/v1/otp/send',
|
||||
{
|
||||
phone_number: phoneNumber
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Authorization': 'Bearer api_live_your_key_here',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('OTP sent!');
|
||||
console.log('Expires in:', response.data.expires_in, 'seconds');
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Failed to send OTP:', error.response?.data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
sendOTP('+959123456789')
|
||||
.then(() => console.log('OTP sent successfully'))
|
||||
.catch(err => console.error('Error:', err));
|
||||
```
|
||||
|
||||
### Example: Python
|
||||
```python
|
||||
def send_otp(phone_number):
|
||||
url = "http://your-server:3005/api/v1/otp/send"
|
||||
headers = {
|
||||
"Authorization": "Bearer api_live_your_key_here",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
data = {
|
||||
"phone_number": phone_number
|
||||
}
|
||||
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
result = response.json()
|
||||
print(f"OTP sent! Expires in {result['expires_in']} seconds")
|
||||
return True
|
||||
else:
|
||||
print(f"Failed: {response.json()}")
|
||||
raise Exception(response.json().get('error', 'Unknown error'))
|
||||
|
||||
# Usage
|
||||
send_otp("+959123456789")
|
||||
```
|
||||
|
||||
## Verifying OTP
|
||||
|
||||
### Endpoint
|
||||
```
|
||||
POST /api/v1/otp/verify
|
||||
```
|
||||
|
||||
### Request
|
||||
```json
|
||||
{
|
||||
"phone_number": "+959123456789",
|
||||
"code": "123456"
|
||||
}
|
||||
```
|
||||
|
||||
### Response (Success - 200 OK)
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "OTP verified successfully"
|
||||
}
|
||||
```
|
||||
|
||||
### Response (Error - Invalid)
|
||||
```json
|
||||
{
|
||||
"error": "Invalid or expired OTP"
|
||||
}
|
||||
```
|
||||
|
||||
### Example: JavaScript
|
||||
```javascript
|
||||
async function verifyOTP(phoneNumber, code) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
'http://your-server:3005/api/v1/otp/verify',
|
||||
{
|
||||
phone_number: phoneNumber,
|
||||
code: code
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
'Authorization': 'Bearer api_live_your_key_here',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('OTP verified!');
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('OTP verification failed:', error.response?.data || error.message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const isValid = await verifyOTP('+959123456789', '123456');
|
||||
if (isValid) {
|
||||
console.log('User verified!');
|
||||
} else {
|
||||
console.log('Invalid OTP');
|
||||
}
|
||||
```
|
||||
|
||||
### Example: Python
|
||||
```python
|
||||
def verify_otp(phone_number, code):
|
||||
url = "http://your-server:3005/api/v1/otp/verify"
|
||||
headers = {
|
||||
"Authorization": "Bearer api_live_your_key_here",
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
data = {
|
||||
"phone_number": phone_number,
|
||||
"code": code
|
||||
}
|
||||
|
||||
response = requests.post(url, json=data, headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
print("OTP verified successfully!")
|
||||
return True
|
||||
else:
|
||||
print(f"Verification failed: {response.json()}")
|
||||
return False
|
||||
|
||||
# Usage
|
||||
if verify_otp("+959123456789", "123456"):
|
||||
print("User authenticated!")
|
||||
else:
|
||||
print("Authentication failed")
|
||||
```
|
||||
|
||||
## Checking Message Status
|
||||
|
||||
### Endpoint
|
||||
```
|
||||
GET /api/v1/sms/status/:message_id
|
||||
```
|
||||
|
||||
### Response
|
||||
```json
|
||||
{
|
||||
"message_id": "msg_abc123def456...",
|
||||
"status": "delivered",
|
||||
"sent_at": "2025-10-22T10:30:00Z",
|
||||
"delivered_at": "2025-10-22T10:30:05Z",
|
||||
"failed_at": null,
|
||||
"error_message": null
|
||||
}
|
||||
```
|
||||
|
||||
### Status Values
|
||||
- `queued` - Waiting for gateway
|
||||
- `pending` - No gateway available, will retry
|
||||
- `sent` - Sent to gateway device
|
||||
- `delivered` - Successfully delivered to recipient
|
||||
- `failed` - Failed to send
|
||||
|
||||
### Example: JavaScript
|
||||
```javascript
|
||||
async function checkMessageStatus(messageId) {
|
||||
try {
|
||||
const response = await axios.get(
|
||||
`http://your-server:3005/api/v1/sms/status/${messageId}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': 'Bearer api_live_your_key_here'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
console.log('Status:', response.data.status);
|
||||
console.log('Sent at:', response.data.sent_at);
|
||||
console.log('Delivered at:', response.data.delivered_at);
|
||||
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error('Failed to check status:', error.response?.data || error.message);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
const status = await checkMessageStatus('msg_abc123def456');
|
||||
```
|
||||
|
||||
## Complete Integration Example
|
||||
|
||||
### User Registration with OTP (JavaScript)
|
||||
|
||||
```javascript
|
||||
const MySMSAPI = {
|
||||
baseURL: 'http://your-server:3005/api/v1',
|
||||
apiKey: 'api_live_your_key_here',
|
||||
|
||||
headers() {
|
||||
return {
|
||||
'Authorization': `Bearer ${this.apiKey}`,
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
},
|
||||
|
||||
async sendSMS(phoneNumber, message) {
|
||||
const response = await axios.post(
|
||||
`${this.baseURL}/sms/send`,
|
||||
{ to: phoneNumber, message },
|
||||
{ headers: this.headers() }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async sendOTP(phoneNumber) {
|
||||
const response = await axios.post(
|
||||
`${this.baseURL}/otp/send`,
|
||||
{ phone_number: phoneNumber },
|
||||
{ headers: this.headers() }
|
||||
);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
async verifyOTP(phoneNumber, code) {
|
||||
try {
|
||||
const response = await axios.post(
|
||||
`${this.baseURL}/otp/verify`,
|
||||
{ phone_number: phoneNumber, code },
|
||||
{ headers: this.headers() }
|
||||
);
|
||||
return response.data.success;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
async checkStatus(messageId) {
|
||||
const response = await axios.get(
|
||||
`${this.baseURL}/sms/status/${messageId}`,
|
||||
{ headers: this.headers() }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
|
||||
// Usage Example: User Registration Flow
|
||||
async function registerUser(phoneNumber, userData) {
|
||||
try {
|
||||
// Step 1: Send OTP
|
||||
console.log('Sending OTP...');
|
||||
await MySMSAPI.sendOTP(phoneNumber);
|
||||
console.log('OTP sent! Check your phone.');
|
||||
|
||||
// Step 2: Wait for user to enter OTP (in real app, this would be a form)
|
||||
const userEnteredCode = '123456'; // Get from user input
|
||||
|
||||
// Step 3: Verify OTP
|
||||
console.log('Verifying OTP...');
|
||||
const isValid = await MySMSAPI.verifyOTP(phoneNumber, userEnteredCode);
|
||||
|
||||
if (isValid) {
|
||||
console.log('✅ Phone verified!');
|
||||
|
||||
// Step 4: Create user account
|
||||
const user = await createUserInDatabase(phoneNumber, userData);
|
||||
|
||||
// Step 5: Send welcome SMS
|
||||
const result = await MySMSAPI.sendSMS(
|
||||
phoneNumber,
|
||||
`Welcome to our service, ${userData.name}! Your account is now active.`
|
||||
);
|
||||
|
||||
console.log('Welcome SMS sent:', result.message_id);
|
||||
|
||||
return { success: true, user };
|
||||
} else {
|
||||
console.log('❌ Invalid OTP');
|
||||
return { success: false, error: 'Invalid OTP' };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Registration failed:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// Example usage
|
||||
registerUser('+959123456789', { name: 'John Doe', email: 'john@example.com' });
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Common Errors
|
||||
|
||||
| Status | Error | Cause | Solution |
|
||||
|--------|-------|-------|----------|
|
||||
| 401 | Unauthorized | Invalid API key | Check API key is correct |
|
||||
| 422 | Unprocessable Entity | Invalid phone number | Use international format (+959...) |
|
||||
| 429 | Too Many Requests | Rate limit exceeded | Wait before retrying |
|
||||
| 404 | Not Found | Message ID not found | Check message ID is correct |
|
||||
| 503 | Service Unavailable | No gateway online | Ensure gateway is connected |
|
||||
|
||||
### Rate Limits
|
||||
|
||||
- **SMS Send**: 100 per minute per API key
|
||||
- **OTP Send**: 3 per hour per phone number
|
||||
- **OTP Verify**: 3 attempts per OTP code
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Store API Key Securely
|
||||
```javascript
|
||||
// ❌ Don't hardcode in frontend
|
||||
const apiKey = 'api_live_abc123...';
|
||||
|
||||
// ✅ Use environment variables
|
||||
const apiKey = process.env.SMS_API_KEY;
|
||||
|
||||
// ✅ Store in backend config
|
||||
const apiKey = config.get('sms.apiKey');
|
||||
```
|
||||
|
||||
### 2. Handle Errors Gracefully
|
||||
```javascript
|
||||
async function sendSMSWithRetry(phoneNumber, message, maxRetries = 3) {
|
||||
for (let i = 0; i < maxRetries; i++) {
|
||||
try {
|
||||
return await MySMSAPI.sendSMS(phoneNumber, message);
|
||||
} catch (error) {
|
||||
if (i === maxRetries - 1) throw error;
|
||||
await sleep(1000 * (i + 1)); // Exponential backoff
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Validate Phone Numbers
|
||||
```javascript
|
||||
function validatePhoneNumber(phone) {
|
||||
// Myanmar phone numbers
|
||||
const myanmarRegex = /^\+959\d{8,9}$/;
|
||||
return myanmarRegex.test(phone);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Monitor Message Status
|
||||
```javascript
|
||||
async function sendAndTrack(phoneNumber, message) {
|
||||
const result = await MySMSAPI.sendSMS(phoneNumber, message);
|
||||
|
||||
// Poll status until delivered
|
||||
const maxAttempts = 30; // 5 minutes (30 * 10 seconds)
|
||||
for (let i = 0; i < maxAttempts; i++) {
|
||||
await sleep(10000); // Wait 10 seconds
|
||||
|
||||
const status = await MySMSAPI.checkStatus(result.message_id);
|
||||
|
||||
if (status.status === 'delivered') {
|
||||
console.log('✅ Message delivered!');
|
||||
return status;
|
||||
} else if (status.status === 'failed') {
|
||||
console.log('❌ Message failed:', status.error_message);
|
||||
throw new Error(status.error_message);
|
||||
}
|
||||
}
|
||||
|
||||
console.log('⏱️ Delivery timeout');
|
||||
return null;
|
||||
}
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
### Test API Key
|
||||
Use the API Tester in the admin interface:
|
||||
```
|
||||
http://your-server:3005/admin/api_tester
|
||||
```
|
||||
|
||||
### Test from Command Line
|
||||
```bash
|
||||
# Send SMS
|
||||
curl -X POST http://your-server:3005/api/v1/sms/send \
|
||||
-H "Authorization: Bearer api_live_your_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"to":"+959123456789","message":"Test"}'
|
||||
|
||||
# Send OTP
|
||||
curl -X POST http://your-server:3005/api/v1/otp/send \
|
||||
-H "Authorization: Bearer api_live_your_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"phone_number":"+959123456789"}'
|
||||
|
||||
# Verify OTP
|
||||
curl -X POST http://your-server:3005/api/v1/otp/verify \
|
||||
-H "Authorization: Bearer api_live_your_key" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"phone_number":"+959123456789","code":"123456"}'
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Messages Stuck in "pending"
|
||||
**Cause**: No gateway online
|
||||
**Solution**: Ensure at least one Android gateway is connected
|
||||
|
||||
### "Invalid phone number" error
|
||||
**Cause**: Wrong format
|
||||
**Solution**: Use international format: `+[country_code][number]`
|
||||
|
||||
### "Rate limit exceeded"
|
||||
**Cause**: Too many requests
|
||||
**Solution**: Implement exponential backoff and caching
|
||||
|
||||
### "Unauthorized" error
|
||||
**Cause**: Invalid or expired API key
|
||||
**Solution**: Generate new API key in admin interface
|
||||
|
||||
## Support
|
||||
|
||||
- **API Documentation**: See `API_DOCUMENTATION.md`
|
||||
- **Admin Interface**: `http://your-server:3005/admin`
|
||||
- **API Tester**: `http://your-server:3005/admin/api_tester`
|
||||
- **Logs**: `http://your-server:3005/admin/logs`
|
||||
|
||||
## Summary
|
||||
|
||||
✅ **Create API Key** → Get `api_live_...` key
|
||||
✅ **Send SMS** → `POST /api/v1/sms/send`
|
||||
✅ **Send OTP** → `POST /api/v1/otp/send`
|
||||
✅ **Verify OTP** → `POST /api/v1/otp/verify`
|
||||
✅ **Check Status** → `GET /api/v1/sms/status/:id`
|
||||
|
||||
Your SaaS app is now ready to send SMS and OTP! 🚀
|
||||
@@ -1,9 +1,12 @@
|
||||
module Admin
|
||||
class LogsController < BaseController
|
||||
def index
|
||||
items_per_page = params[:items]&.to_i || 50
|
||||
items_per_page = 50 unless [10, 25, 50, 100].include?(items_per_page)
|
||||
|
||||
@pagy, @messages = pagy(
|
||||
apply_filters(SmsMessage).order(created_at: :desc),
|
||||
items: 50
|
||||
items: items_per_page
|
||||
)
|
||||
|
||||
respond_to do |format|
|
||||
|
||||
@@ -128,7 +128,7 @@
|
||||
<%= msg.phone_number %>
|
||||
</td>
|
||||
<td class="px-6 py-4 text-sm text-gray-700">
|
||||
<div class="max-w-xs truncate"><%= msg.message_body %></div>
|
||||
<div class="max-w-md whitespace-pre-wrap break-words"><%= msg.message_body %></div>
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-6 py-4 text-sm">
|
||||
<% if msg.direction == "outbound" %>
|
||||
@@ -209,8 +209,71 @@
|
||||
<!-- Pagination -->
|
||||
<% if @pagy.pages > 1 %>
|
||||
<div class="border-t border-gray-200 px-6 py-4 bg-gray-50">
|
||||
<div class="flex items-center justify-center">
|
||||
<%== pagy_nav(@pagy) %>
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- Page info -->
|
||||
<div class="text-sm text-gray-700">
|
||||
Showing
|
||||
<span class="font-semibold"><%= @pagy.from %></span>
|
||||
to
|
||||
<span class="font-semibold"><%= @pagy.to %></span>
|
||||
of
|
||||
<span class="font-semibold"><%= @pagy.count %></span>
|
||||
results
|
||||
</div>
|
||||
|
||||
<!-- Pagination controls -->
|
||||
<nav class="isolate inline-flex -space-x-px rounded-md shadow-sm" aria-label="Pagination">
|
||||
<% if @pagy.prev %>
|
||||
<%= link_to admin_logs_path(params.permit!.merge(page: @pagy.prev)),
|
||||
class: "relative inline-flex items-center rounded-l-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 transition-colors" do %>
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
<span class="sr-only">Previous</span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="relative inline-flex items-center rounded-l-md px-3 py-2 text-sm font-semibold text-gray-400 ring-1 ring-inset ring-gray-300 cursor-not-allowed">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</span>
|
||||
<% end %>
|
||||
|
||||
<% @pagy.series.each do |item| %>
|
||||
<% if item.is_a?(Integer) %>
|
||||
<% if item == @pagy.page %>
|
||||
<span class="relative z-10 inline-flex items-center bg-blue-600 px-4 py-2 text-sm font-semibold text-white ring-1 ring-inset ring-blue-600"><%= item %></span>
|
||||
<% else %>
|
||||
<%= link_to item, admin_logs_path(params.permit!.merge(page: item)),
|
||||
class: "relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 transition-colors" %>
|
||||
<% end %>
|
||||
<% elsif item == :gap %>
|
||||
<span class="relative inline-flex items-center px-4 py-2 text-sm font-semibold text-gray-700 ring-1 ring-inset ring-gray-300">...</span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if @pagy.next %>
|
||||
<%= link_to admin_logs_path(params.permit!.merge(page: @pagy.next)),
|
||||
class: "relative inline-flex items-center rounded-r-md px-3 py-2 text-sm font-semibold text-gray-900 ring-1 ring-inset ring-gray-300 hover:bg-gray-50 transition-colors" do %>
|
||||
<span class="sr-only">Next</span>
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<span class="relative inline-flex items-center rounded-r-md px-3 py-2 text-sm font-semibold text-gray-400 ring-1 ring-inset ring-gray-300 cursor-not-allowed">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</span>
|
||||
<% end %>
|
||||
</nav>
|
||||
|
||||
<!-- Page size selector -->
|
||||
<div class="text-sm text-gray-700">
|
||||
<%= form_with url: admin_logs_path, method: :get, local: true, class: "inline-flex items-center gap-2" do |f| %>
|
||||
<label for="items" class="font-medium">Per page:</label>
|
||||
<%= select_tag :items,
|
||||
options_for_select([10, 25, 50, 100], params[:items] || 50),
|
||||
onchange: "this.form.submit()",
|
||||
class: "rounded-md border-gray-300 text-sm focus:border-blue-500 focus:ring-blue-500" %>
|
||||
<% params.except(:items, :authenticity_token).each do |key, value| %>
|
||||
<%= hidden_field_tag key, value %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
Reference in New Issue
Block a user