Compare commits
13 Commits
49921a26a9
...
feat/teams
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
391df3bb41 | ||
|
|
a29bca1931 | ||
|
|
bc5da01735 | ||
|
|
914e276026 | ||
|
|
2e44012b8c | ||
|
|
0fe2e2bae6 | ||
|
|
c6518e81c9 | ||
|
|
289e11f3c5 | ||
| 8eef632ebb | |||
| ef88a6b69b | |||
|
|
a410208c62 | ||
|
|
d4a6028599 | ||
|
|
4888f93eac |
28
.env.local
28
.env.local
@@ -1,6 +1,26 @@
|
|||||||
# Local Development MinIO Configuration
|
# Local Development Configuration
|
||||||
# Copy to .env.local and update with your MinIO credentials
|
# Copy to .env.local and fill in your values.
|
||||||
|
|
||||||
# MinIO credentials (get from your production server)
|
# ── MinIO credentials ─────────────────────────────────────────────────────────
|
||||||
MINIO_KEY=secret_key
|
MINIO_KEY=your_minio_root_user
|
||||||
MINIO_SECRET=your_minio_root_password
|
MINIO_SECRET=your_minio_root_password
|
||||||
|
|
||||||
|
# ── SSH tunnel (remote DB) ────────────────────────────────────────────────────
|
||||||
|
# Only needed when starting with --profile ssh-db.
|
||||||
|
# NOTE: no inline comments allowed after values — Docker reads them literally.
|
||||||
|
|
||||||
|
SSH_HOST=136.114.183.15
|
||||||
|
SSH_PORT=22
|
||||||
|
SSH_USER=webmaster
|
||||||
|
# Must be an absolute path — Docker Compose does NOT expand ~
|
||||||
|
SSH_KEY_PATH=/Users/webmaster/.ssh/id_ed25519_crew_webmaster
|
||||||
|
|
||||||
|
SSH_DB_REMOTE_HOST=127.0.0.1
|
||||||
|
SSH_DB_REMOTE_PORT=3306
|
||||||
|
|
||||||
|
# Tell the app to route DB traffic through the tunnel container:
|
||||||
|
DB_HOST=db-tunnel
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=custom_designs
|
||||||
|
DB_USERNAME=root
|
||||||
|
DB_PASSWORD=VeryStrongRootPass2025!
|
||||||
|
|||||||
@@ -1,6 +1,26 @@
|
|||||||
# Local Development MinIO Configuration
|
# Local Development Configuration
|
||||||
# Copy to .env.local and update with your MinIO credentials
|
# Copy to .env.local and fill in your values.
|
||||||
|
|
||||||
# MinIO credentials (get from your production server)
|
# ── MinIO credentials ─────────────────────────────────────────────────────────
|
||||||
MINIO_KEY=your_minio_root_user
|
MINIO_KEY=your_minio_root_user
|
||||||
MINIO_SECRET=your_minio_root_password
|
MINIO_SECRET=your_minio_root_password
|
||||||
|
|
||||||
|
# ── SSH tunnel (remote DB) ────────────────────────────────────────────────────
|
||||||
|
# Only needed when starting with --profile ssh-db.
|
||||||
|
# IMPORTANT: no inline comments after values — Docker Compose reads them literally.
|
||||||
|
# IMPORTANT: SSH_KEY_PATH must be an absolute path — ~ is NOT expanded by Docker Compose.
|
||||||
|
#
|
||||||
|
SSH_HOST=your.server.ip.or.hostname
|
||||||
|
SSH_PORT=22
|
||||||
|
SSH_USER=root
|
||||||
|
SSH_KEY_PATH=/absolute/path/to/your/private/key
|
||||||
|
#
|
||||||
|
SSH_DB_REMOTE_HOST=127.0.0.1 # DB host as seen from the SSH server
|
||||||
|
SSH_DB_REMOTE_PORT=3306 # DB port as seen from the SSH server
|
||||||
|
#
|
||||||
|
# Tell the app to route DB traffic through the tunnel container:
|
||||||
|
DB_HOST=db-tunnel
|
||||||
|
DB_PORT=3306
|
||||||
|
DB_DATABASE=your_remote_db_name
|
||||||
|
DB_USERNAME=your_remote_db_user
|
||||||
|
DB_PASSWORD=your_remote_db_password
|
||||||
|
|||||||
33
Dockerfile
33
Dockerfile
@@ -1,10 +1,10 @@
|
|||||||
# Use PHP 7.0 with Apache (has native mcrypt support for Laravel 5.0)
|
# Use PHP 7.2 with Apache (mcrypt available via PECL, compatible with Laravel 5.0)
|
||||||
FROM php:7.0-apache
|
FROM php:7.2-apache
|
||||||
|
|
||||||
# Update to use archived Debian repositories
|
# Redirect to archived Debian Buster repositories (EOL)
|
||||||
RUN sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list \
|
RUN sed -i 's|deb.debian.org|archive.debian.org|g' /etc/apt/sources.list \
|
||||||
&& sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list \
|
&& sed -i 's|security.debian.org|archive.debian.org|g' /etc/apt/sources.list \
|
||||||
&& sed -i '/stretch-updates/d' /etc/apt/sources.list
|
&& sed -i '/buster-updates/d' /etc/apt/sources.list
|
||||||
|
|
||||||
# Install system dependencies
|
# Install system dependencies
|
||||||
RUN apt-get update && apt-get install -y --allow-unauthenticated \
|
RUN apt-get update && apt-get install -y --allow-unauthenticated \
|
||||||
@@ -18,17 +18,24 @@ RUN apt-get update && apt-get install -y --allow-unauthenticated \
|
|||||||
libfreetype6-dev \
|
libfreetype6-dev \
|
||||||
libjpeg62-turbo-dev \
|
libjpeg62-turbo-dev \
|
||||||
openssh-client \
|
openssh-client \
|
||||||
|
libzip-dev \
|
||||||
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
|
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
|
||||||
&& docker-php-ext-install -j$(nproc) gd
|
&& docker-php-ext-install -j$(nproc) gd
|
||||||
|
|
||||||
# Install PHP extensions (mcrypt is built-in for PHP 7.0)
|
# Install mcrypt via PECL (removed from core in PHP 7.2)
|
||||||
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath mcrypt tokenizer zip
|
RUN pecl install mcrypt-1.0.4 && docker-php-ext-enable mcrypt
|
||||||
|
|
||||||
|
# Suppress E_DEPRECATED so PECL mcrypt functions don't trigger ErrorException in Laravel 5.0
|
||||||
|
RUN echo "error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT" > /usr/local/etc/php/conf.d/suppress-deprecated.ini
|
||||||
|
|
||||||
|
# Install PHP extensions
|
||||||
|
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath tokenizer zip
|
||||||
|
|
||||||
# Enable Apache mod_rewrite
|
# Enable Apache mod_rewrite
|
||||||
RUN a2enmod rewrite
|
RUN a2enmod rewrite
|
||||||
|
|
||||||
# Install Composer (version 1.x for better compatibility with Laravel 5.0)
|
# Install Composer 2.2 (LTS version supporting PHP 7.2+)
|
||||||
COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer
|
COPY --from=composer:2.2 /usr/bin/composer /usr/bin/composer
|
||||||
|
|
||||||
# Set working directory
|
# Set working directory
|
||||||
WORKDIR /var/www/html
|
WORKDIR /var/www/html
|
||||||
@@ -51,8 +58,8 @@ RUN chown -R www-data:www-data /var/www/html \
|
|||||||
# Create .env file if it doesn't exist
|
# Create .env file if it doesn't exist
|
||||||
RUN if [ ! -f .env ]; then cp .env.example .env; fi
|
RUN if [ ! -f .env ]; then cp .env.example .env; fi
|
||||||
|
|
||||||
# Install PHP dependencies (Laravel 5.0 compatible)
|
# Install PHP dependencies (--no-plugins skips kylekatarnls/update-helper which is Composer 1 only)
|
||||||
RUN composer install --no-dev --no-interaction --prefer-dist
|
RUN composer install --no-dev --no-interaction --prefer-dist --no-plugins
|
||||||
|
|
||||||
# Generate application key
|
# Generate application key
|
||||||
RUN php artisan key:generate || true
|
RUN php artisan key:generate || true
|
||||||
@@ -60,11 +67,7 @@ RUN php artisan key:generate || true
|
|||||||
# Run Laravel 5.0 optimization
|
# Run Laravel 5.0 optimization
|
||||||
RUN php artisan clear-compiled && php artisan optimize
|
RUN php artisan clear-compiled && php artisan optimize
|
||||||
|
|
||||||
# Note: yakpro-po obfuscation requires PHP 7.1+, incompatible with PHP 7.0
|
# Note: PHP 7.2 is compatible with Laravel 5.0 and yakpro-po obfuscation
|
||||||
# For code protection with PHP 7.0, consider:
|
|
||||||
# 1. ionCube Encoder (commercial, most secure)
|
|
||||||
# 2. Keeping source code private and using proper access controls
|
|
||||||
# 3. Using --optimize flag in composer (already done above)
|
|
||||||
|
|
||||||
# Configure Apache DocumentRoot to point to Laravel's public directory
|
# Configure Apache DocumentRoot to point to Laravel's public directory
|
||||||
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
|
ENV APACHE_DOCUMENT_ROOT=/var/www/html/public
|
||||||
|
|||||||
17
Makefile
Normal file
17
Makefile
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
COMPOSE_LOCAL = docker compose -f docker-compose.local.yml
|
||||||
|
|
||||||
|
# ── Local stack (local MariaDB) ───────────────────────────────────────────────
|
||||||
|
up:
|
||||||
|
$(COMPOSE_LOCAL) up --build
|
||||||
|
|
||||||
|
down:
|
||||||
|
$(COMPOSE_LOCAL) down
|
||||||
|
|
||||||
|
# ── Local stack with SSH tunnel to remote DB ──────────────────────────────────
|
||||||
|
up-ssh:
|
||||||
|
$(COMPOSE_LOCAL) --env-file .env.local --profile ssh-db up --build
|
||||||
|
|
||||||
|
down-ssh:
|
||||||
|
$(COMPOSE_LOCAL) --env-file .env.local --profile ssh-db down
|
||||||
|
|
||||||
|
.PHONY: up down up-ssh down-ssh
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
# SSH Keys Setup Guide
|
|
||||||
|
|
||||||
## Security Notice
|
|
||||||
|
|
||||||
SSH private keys (.ppk, .pem, id_rsa, etc.) should **NEVER** be:
|
|
||||||
- Stored in the application directory
|
|
||||||
- Committed to git repositories
|
|
||||||
- Placed in web-accessible locations
|
|
||||||
|
|
||||||
## Recommended Setup
|
|
||||||
|
|
||||||
### 1. Create Secure Keys Directory on Server
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# On your production server
|
|
||||||
sudo mkdir -p /var/crew-keys
|
|
||||||
sudo chmod 700 /var/crew-keys
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Place Your SSH Key
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Copy your key to the secure location
|
|
||||||
sudo cp /path/to/your/root.ppk /var/crew-keys/
|
|
||||||
sudo chmod 600 /var/crew-keys/root.ppk
|
|
||||||
sudo chown root:root /var/crew-keys/root.ppk
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Verify Permissions
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ls -la /var/crew-keys/
|
|
||||||
# Should show: drwx------ (700) for directory
|
|
||||||
# Should show: -rw------- (600) for key file
|
|
||||||
```
|
|
||||||
|
|
||||||
## Docker Configuration
|
|
||||||
|
|
||||||
The `docker-compose.prod.yml` and `docker-compose.dev.yml` files are configured to mount `/var/crew-keys` as a **read-only** volume:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
volumes:
|
|
||||||
- /var/crew-keys:/var/keys:ro
|
|
||||||
```
|
|
||||||
|
|
||||||
The `:ro` flag ensures the container can only read the keys, not modify them.
|
|
||||||
|
|
||||||
## Application Configuration
|
|
||||||
|
|
||||||
The [config/filesystems.php](config/filesystems.php) references the key at:
|
|
||||||
|
|
||||||
```php
|
|
||||||
'privateKey' => '/var/keys/root.ppk',
|
|
||||||
```
|
|
||||||
|
|
||||||
This path is inside the container and maps to `/var/crew-keys/root.ppk` on the host.
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
To verify the SFTP connection works:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker exec crewsportswear_app_prod php -r "
|
|
||||||
use League\Flysystem\Sftp\SftpAdapter;
|
|
||||||
try {
|
|
||||||
\$adapter = new SftpAdapter([
|
|
||||||
'host' => '35.232.234.8',
|
|
||||||
'port' => 22,
|
|
||||||
'username' => 'root',
|
|
||||||
'privateKey' => '/var/keys/root.ppk',
|
|
||||||
'root' => '/var/www/html/images',
|
|
||||||
'timeout' => 10,
|
|
||||||
]);
|
|
||||||
echo 'SFTP connection: SUCCESS';
|
|
||||||
} catch (Exception \$e) {
|
|
||||||
echo 'SFTP connection failed: ' . \$e->getMessage();
|
|
||||||
}
|
|
||||||
"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Permission Denied
|
|
||||||
|
|
||||||
If you get permission errors:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Fix directory permissions
|
|
||||||
sudo chmod 700 /var/crew-keys
|
|
||||||
|
|
||||||
# Fix key file permissions
|
|
||||||
sudo chmod 600 /var/crew-keys/root.ppk
|
|
||||||
```
|
|
||||||
|
|
||||||
### Key Format Issues
|
|
||||||
|
|
||||||
PuTTY keys (.ppk) may need conversion for Linux/PHP:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Convert .ppk to OpenSSH format
|
|
||||||
puttygen root.ppk -O private-openssh -o /var/crew-keys/root.pem
|
|
||||||
chmod 600 /var/crew-keys/root.pem
|
|
||||||
```
|
|
||||||
|
|
||||||
Then update `filesystems.php`:
|
|
||||||
```php
|
|
||||||
'privateKey' => '/var/keys/root.pem',
|
|
||||||
```
|
|
||||||
|
|
||||||
## Security Best Practices
|
|
||||||
|
|
||||||
✅ **DO:**
|
|
||||||
- Store keys outside application directory
|
|
||||||
- Use restrictive permissions (600 for files, 700 for directories)
|
|
||||||
- Mount as read-only in Docker
|
|
||||||
- Keep keys out of version control
|
|
||||||
- Use SSH key authentication instead of passwords
|
|
||||||
- Rotate keys regularly
|
|
||||||
|
|
||||||
❌ **DON'T:**
|
|
||||||
- Commit keys to git
|
|
||||||
- Store in web-accessible directories
|
|
||||||
- Use world-readable permissions
|
|
||||||
- Share keys across multiple services
|
|
||||||
- Use password-protected keys without proper passphrase management
|
|
||||||
@@ -761,10 +761,7 @@ class UserController extends Controller
|
|||||||
);
|
);
|
||||||
|
|
||||||
$u = $UserModel->insertNewProductThumbnails($thumbs);
|
$u = $UserModel->insertNewProductThumbnails($thumbs);
|
||||||
// var_dump($thumbs);
|
Storage::disk('minio')->put('images/' . $thumbnail, file_get_contents($request->file('imgupload')[$i]->getRealPath()));
|
||||||
Storage::disk('sftp')->put($thumbnail, fopen($request->file('imgupload')[$i], 'r+')); //live
|
|
||||||
//Storage::disk('localdir')->put($thumbnail, fopen($request->file('imgupload')[$i], 'r+'));
|
|
||||||
// var_dump($s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$prod_code = array('ProductCode' => $getYear . '-' . str_pad($id, 10, '0', STR_PAD_LEFT));
|
$prod_code = array('ProductCode' => $getYear . '-' . str_pad($id, 10, '0', STR_PAD_LEFT));
|
||||||
@@ -808,9 +805,7 @@ class UserController extends Controller
|
|||||||
);
|
);
|
||||||
|
|
||||||
$u = $UserModel->insertNewProductThumbnails($thumbs);
|
$u = $UserModel->insertNewProductThumbnails($thumbs);
|
||||||
Storage::disk('sftp')->put($thumbnail, fopen($request->file('upload_images')[$i], 'r+')); //live
|
Storage::disk('minio')->put('images/' . $thumbnail, file_get_contents($request->file('upload_images')[$i]->getRealPath()));
|
||||||
//Storage::disk('localdir')->put($thumbnail, fopen($request->file('upload_images')[$i], 'r+'));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -826,9 +821,8 @@ class UserController extends Controller
|
|||||||
$id = $request->thumb_id;
|
$id = $request->thumb_id;
|
||||||
$UserModel = new UserModel;
|
$UserModel = new UserModel;
|
||||||
|
|
||||||
$storagePath = Storage::disk('sftp')->getDriver()->getAdapter()->getPathPrefix();
|
if (Storage::disk('minio')->exists('images/' . $file)) {
|
||||||
if (file_exists($storagePath . $file)) {
|
Storage::disk('minio')->delete('images/' . $file);
|
||||||
unlink($storagePath . $file);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$i = $UserModel->deleteImageThumb('Id', $id);
|
$i = $UserModel->deleteImageThumb('Id', $id);
|
||||||
@@ -973,11 +967,11 @@ class UserController extends Controller
|
|||||||
// var_dump($res);
|
// var_dump($res);
|
||||||
// if($res){
|
// if($res){
|
||||||
if ($request->file('store_logo') != null) {
|
if ($request->file('store_logo') != null) {
|
||||||
Storage::disk('uploads')->put('/teamstore/' . $orig_store_url . '/' . $store_logo_name, fopen($request->file('store_logo'), 'r+'));
|
Storage::disk('minio')->put('uploads/images/teamstore/' . $orig_store_url . '/' . $store_logo_name, file_get_contents($request->file('store_logo')->getRealPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($request->file('store_banner') != null) {
|
if ($request->file('store_banner') != null) {
|
||||||
Storage::disk('uploads')->put('/teamstore/' . $orig_store_url . '/' . $store_banner_name, fopen($request->file('store_banner'), 'r+'));
|
Storage::disk('minio')->put('uploads/images/teamstore/' . $orig_store_url . '/' . $store_banner_name, file_get_contents($request->file('store_banner')->getRealPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return response()->json(array(
|
return response()->json(array(
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ use Illuminate\Support\ServiceProvider;
|
|||||||
use Storage;
|
use Storage;
|
||||||
use League\Flysystem\Filesystem;
|
use League\Flysystem\Filesystem;
|
||||||
use League\Flysystem\Sftp\SftpAdapter;
|
use League\Flysystem\Sftp\SftpAdapter;
|
||||||
|
use League\Flysystem\AwsS3v3\AwsS3Adapter as AwsS3v3Adapter;
|
||||||
|
use Aws\S3\S3Client;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider {
|
class AppServiceProvider extends ServiceProvider {
|
||||||
|
|
||||||
@@ -27,6 +29,21 @@ class AppServiceProvider extends ServiceProvider {
|
|||||||
Storage::extend('sftp', function ($app, $config) {
|
Storage::extend('sftp', function ($app, $config) {
|
||||||
return new Filesystem(new SftpAdapter($config));
|
return new Filesystem(new SftpAdapter($config));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Storage::extend('minio', function ($app, $config) {
|
||||||
|
$client = new S3Client([
|
||||||
|
'credentials' => [
|
||||||
|
'key' => $config['key'],
|
||||||
|
'secret' => $config['secret'],
|
||||||
|
],
|
||||||
|
'region' => $config['region'],
|
||||||
|
'version' => 'latest',
|
||||||
|
'endpoint' => $config['endpoint'],
|
||||||
|
'use_path_style_endpoint' => filter_var($config['use_path_style_endpoint'] ?? true, FILTER_VALIDATE_BOOLEAN),
|
||||||
|
]);
|
||||||
|
$adapter = new AwsS3v3Adapter($client, $config['bucket']);
|
||||||
|
return new Filesystem($adapter);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,6 +57,10 @@ class AppServiceProvider extends ServiceProvider {
|
|||||||
*/
|
*/
|
||||||
public function register()
|
public function register()
|
||||||
{
|
{
|
||||||
|
// Laravel's HandleExceptions sets error_reporting(-1) which causes PECL mcrypt
|
||||||
|
// deprecation notices to become ErrorExceptions. Override it here to suppress E_DEPRECATED.
|
||||||
|
error_reporting(E_ALL & ~E_DEPRECATED & ~E_STRICT);
|
||||||
|
|
||||||
$this->app->bind(
|
$this->app->bind(
|
||||||
'Illuminate\Contracts\Auth\Registrar',
|
'Illuminate\Contracts\Auth\Registrar',
|
||||||
'App\Services\Registrar'
|
'App\Services\Registrar'
|
||||||
|
|||||||
@@ -12,7 +12,10 @@
|
|||||||
"google/recaptcha": "~1.1",
|
"google/recaptcha": "~1.1",
|
||||||
"spatie/laravel-analytics": "^1.4",
|
"spatie/laravel-analytics": "^1.4",
|
||||||
"league/flysystem-sftp": "^1.0",
|
"league/flysystem-sftp": "^1.0",
|
||||||
"aws/aws-sdk-php": "~3.0"
|
"league/flysystem-aws-s3-v3": "~1.0",
|
||||||
|
"aws/aws-sdk-php": "~3.0",
|
||||||
|
"psr/http-message": "^1.0",
|
||||||
|
"guzzlehttp/psr7": "^1.4"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"phpunit/phpunit": "~4.0",
|
"phpunit/phpunit": "~4.0",
|
||||||
|
|||||||
647
composer.lock
generated
647
composer.lock
generated
@@ -4,8 +4,153 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "9ad9cbf7c7c319c392284bef379f0004",
|
"content-hash": "0320d93525d3aeed0db29b492fe6f3cc",
|
||||||
"packages": [
|
"packages": [
|
||||||
|
{
|
||||||
|
"name": "aws/aws-crt-php",
|
||||||
|
"version": "v1.2.7",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/awslabs/aws-crt-php.git",
|
||||||
|
"reference": "d71d9906c7bb63a28295447ba12e74723bd3730e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e",
|
||||||
|
"reference": "d71d9906c7bb63a28295447ba12e74723bd3730e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.5"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "^4.8.35||^5.6.3||^9.5",
|
||||||
|
"yoast/phpunit-polyfills": "^1.0"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality."
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"classmap": [
|
||||||
|
"src/"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "AWS SDK Common Runtime Team",
|
||||||
|
"email": "aws-sdk-common-runtime@amazon.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "AWS Common Runtime for PHP",
|
||||||
|
"homepage": "https://github.com/awslabs/aws-crt-php",
|
||||||
|
"keywords": [
|
||||||
|
"amazon",
|
||||||
|
"aws",
|
||||||
|
"crt",
|
||||||
|
"sdk"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/awslabs/aws-crt-php/issues",
|
||||||
|
"source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7"
|
||||||
|
},
|
||||||
|
"time": "2024-10-18T22:15:13+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "aws/aws-sdk-php",
|
||||||
|
"version": "3.226.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/aws/aws-sdk-php.git",
|
||||||
|
"reference": "d76d4fe0fa603ddc3f5c54d9664438dc1a808859"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/d76d4fe0fa603ddc3f5c54d9664438dc1a808859",
|
||||||
|
"reference": "d76d4fe0fa603ddc3f5c54d9664438dc1a808859",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"aws/aws-crt-php": "^1.0.2",
|
||||||
|
"ext-json": "*",
|
||||||
|
"ext-pcre": "*",
|
||||||
|
"ext-simplexml": "*",
|
||||||
|
"guzzlehttp/guzzle": "^5.3.3 || ^6.2.1 || ^7.0",
|
||||||
|
"guzzlehttp/promises": "^1.4.0",
|
||||||
|
"guzzlehttp/psr7": "^1.7.0 || ^2.1.1",
|
||||||
|
"mtdowling/jmespath.php": "^2.6",
|
||||||
|
"php": ">=5.5"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"andrewsville/php-token-reflection": "^1.4",
|
||||||
|
"aws/aws-php-sns-message-validator": "~1.0",
|
||||||
|
"behat/behat": "~3.0",
|
||||||
|
"doctrine/cache": "~1.4",
|
||||||
|
"ext-dom": "*",
|
||||||
|
"ext-openssl": "*",
|
||||||
|
"ext-pcntl": "*",
|
||||||
|
"ext-sockets": "*",
|
||||||
|
"nette/neon": "^2.3",
|
||||||
|
"paragonie/random_compat": ">= 2",
|
||||||
|
"phpunit/phpunit": "^4.8.35 || ^5.6.3",
|
||||||
|
"psr/cache": "^1.0",
|
||||||
|
"psr/simple-cache": "^1.0",
|
||||||
|
"sebastian/comparator": "^1.2.3"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"aws/aws-php-sns-message-validator": "To validate incoming SNS notifications",
|
||||||
|
"doctrine/cache": "To use the DoctrineCacheAdapter",
|
||||||
|
"ext-curl": "To send requests using cURL",
|
||||||
|
"ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages",
|
||||||
|
"ext-sockets": "To use client-side monitoring"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "3.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Aws\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"Apache-2.0"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Amazon Web Services",
|
||||||
|
"homepage": "http://aws.amazon.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project",
|
||||||
|
"homepage": "http://aws.amazon.com/sdkforphp",
|
||||||
|
"keywords": [
|
||||||
|
"amazon",
|
||||||
|
"aws",
|
||||||
|
"cloud",
|
||||||
|
"dynamodb",
|
||||||
|
"ec2",
|
||||||
|
"glacier",
|
||||||
|
"s3",
|
||||||
|
"sdk"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
|
||||||
|
"issues": "https://github.com/aws/aws-sdk-php/issues",
|
||||||
|
"source": "https://github.com/aws/aws-sdk-php/tree/3.226.0"
|
||||||
|
},
|
||||||
|
"time": "2022-06-16T18:14:10+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "classpreloader/classpreloader",
|
"name": "classpreloader/classpreloader",
|
||||||
"version": "1.4.0",
|
"version": "1.4.0",
|
||||||
@@ -365,6 +510,190 @@
|
|||||||
],
|
],
|
||||||
"time": "2019-10-30T09:32:00+00:00"
|
"time": "2019-10-30T09:32:00+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "guzzlehttp/promises",
|
||||||
|
"version": "1.5.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/guzzle/promises.git",
|
||||||
|
"reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e",
|
||||||
|
"reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.5"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"symfony/phpunit-bridge": "^4.4 || ^5.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions_include.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"GuzzleHttp\\Promise\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Michael Dowling",
|
||||||
|
"email": "mtdowling@gmail.com",
|
||||||
|
"homepage": "https://github.com/mtdowling"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com",
|
||||||
|
"homepage": "https://github.com/Nyholm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Schultze",
|
||||||
|
"email": "webmaster@tubo-world.de",
|
||||||
|
"homepage": "https://github.com/Tobion"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Guzzle promises library",
|
||||||
|
"keywords": [
|
||||||
|
"promise"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/guzzle/promises/issues",
|
||||||
|
"source": "https://github.com/guzzle/promises/tree/1.5.3"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/GrahamCampbell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Nyholm",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-05-21T12:31:43+00:00"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "guzzlehttp/psr7",
|
||||||
|
"version": "1.9.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/guzzle/psr7.git",
|
||||||
|
"reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b",
|
||||||
|
"reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.4.0",
|
||||||
|
"psr/http-message": "~1.0",
|
||||||
|
"ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"psr/http-message-implementation": "1.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-zlib": "*",
|
||||||
|
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/functions_include.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"GuzzleHttp\\Psr7\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Michael Dowling",
|
||||||
|
"email": "mtdowling@gmail.com",
|
||||||
|
"homepage": "https://github.com/mtdowling"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "George Mponos",
|
||||||
|
"email": "gmponos@gmail.com",
|
||||||
|
"homepage": "https://github.com/gmponos"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Nyholm",
|
||||||
|
"email": "tobias.nyholm@gmail.com",
|
||||||
|
"homepage": "https://github.com/Nyholm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Márk Sági-Kazár",
|
||||||
|
"email": "mark.sagikazar@gmail.com",
|
||||||
|
"homepage": "https://github.com/sagikazarmark"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Tobias Schultze",
|
||||||
|
"email": "webmaster@tubo-world.de",
|
||||||
|
"homepage": "https://github.com/Tobion"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "PSR-7 message implementation that also provides common utility methods",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"message",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response",
|
||||||
|
"stream",
|
||||||
|
"uri",
|
||||||
|
"url"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/guzzle/psr7/issues",
|
||||||
|
"source": "https://github.com/guzzle/psr7/tree/1.9.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://github.com/GrahamCampbell",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/Nyholm",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2023-04-17T16:00:37+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "guzzlehttp/ringphp",
|
"name": "guzzlehttp/ringphp",
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
@@ -907,6 +1236,71 @@
|
|||||||
],
|
],
|
||||||
"time": "2019-10-16T21:01:05+00:00"
|
"time": "2019-10-16T21:01:05+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "league/flysystem-aws-s3-v3",
|
||||||
|
"version": "1.0.30",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git",
|
||||||
|
"reference": "af286f291ebab6877bac0c359c6c2cb017eb061d"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/af286f291ebab6877bac0c359c6c2cb017eb061d",
|
||||||
|
"reference": "af286f291ebab6877bac0c359c6c2cb017eb061d",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"aws/aws-sdk-php": "^3.20.0",
|
||||||
|
"league/flysystem": "^1.0.40",
|
||||||
|
"php": ">=5.5.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"henrikbjorn/phpspec-code-coverage": "~1.0.1",
|
||||||
|
"phpspec/phpspec": "^2.0.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"League\\Flysystem\\AwsS3v3\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Frank de Jonge",
|
||||||
|
"email": "info@frenky.net"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Flysystem adapter for the AWS S3 SDK v3.x",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues",
|
||||||
|
"source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/1.0.30"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://offset.earth/frankdejonge",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/frankdejonge",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/league/flysystem",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2022-07-02T13:51:38+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "league/flysystem-sftp",
|
"name": "league/flysystem-sftp",
|
||||||
"version": "1.0.22",
|
"version": "1.0.22",
|
||||||
@@ -1071,6 +1465,72 @@
|
|||||||
],
|
],
|
||||||
"time": "2017-01-23T04:29:33+00:00"
|
"time": "2017-01-23T04:29:33+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "mtdowling/jmespath.php",
|
||||||
|
"version": "2.8.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/jmespath/jmespath.php.git",
|
||||||
|
"reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/a2a865e05d5f420b50cc2f85bb78d565db12a6bc",
|
||||||
|
"reference": "a2a865e05d5f420b50cc2f85bb78d565db12a6bc",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2.5 || ^8.0",
|
||||||
|
"symfony/polyfill-mbstring": "^1.17"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"composer/xdebug-handler": "^3.0.3",
|
||||||
|
"phpunit/phpunit": "^8.5.33"
|
||||||
|
},
|
||||||
|
"bin": [
|
||||||
|
"bin/jp.php"
|
||||||
|
],
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "2.8-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/JmesPath.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"JmesPath\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Graham Campbell",
|
||||||
|
"email": "hello@gjcampbell.co.uk",
|
||||||
|
"homepage": "https://github.com/GrahamCampbell"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Michael Dowling",
|
||||||
|
"email": "mtdowling@gmail.com",
|
||||||
|
"homepage": "https://github.com/mtdowling"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Declaratively specify how to extract elements from a JSON document",
|
||||||
|
"keywords": [
|
||||||
|
"json",
|
||||||
|
"jsonpath"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/jmespath/jmespath.php/issues",
|
||||||
|
"source": "https://github.com/jmespath/jmespath.php/tree/2.8.0"
|
||||||
|
},
|
||||||
|
"time": "2024-09-04T18:46:31+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "nesbot/carbon",
|
"name": "nesbot/carbon",
|
||||||
"version": "1.39.1",
|
"version": "1.39.1",
|
||||||
@@ -1409,6 +1869,59 @@
|
|||||||
],
|
],
|
||||||
"time": "2019-09-17T03:41:22+00:00"
|
"time": "2019-09-17T03:41:22+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "psr/http-message",
|
||||||
|
"version": "1.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/php-fig/http-message.git",
|
||||||
|
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||||
|
"reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": "^7.2 || ^8.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "1.1.x-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Psr\\Http\\Message\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "PHP-FIG",
|
||||||
|
"homepage": "http://www.php-fig.org/"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Common interface for HTTP messages",
|
||||||
|
"homepage": "https://github.com/php-fig/http-message",
|
||||||
|
"keywords": [
|
||||||
|
"http",
|
||||||
|
"http-message",
|
||||||
|
"psr",
|
||||||
|
"psr-7",
|
||||||
|
"request",
|
||||||
|
"response"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/php-fig/http-message/tree/1.1"
|
||||||
|
},
|
||||||
|
"time": "2023-04-04T09:50:52+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "psr/log",
|
"name": "psr/log",
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
@@ -1527,6 +2040,50 @@
|
|||||||
],
|
],
|
||||||
"time": "2015-03-26T18:43:54+00:00"
|
"time": "2015-03-26T18:43:54+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "ralouphie/getallheaders",
|
||||||
|
"version": "3.0.3",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/ralouphie/getallheaders.git",
|
||||||
|
"reference": "120b605dfeb996808c31b6477290a714d356e822"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
|
||||||
|
"reference": "120b605dfeb996808c31b6477290a714d356e822",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=5.6"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"php-coveralls/php-coveralls": "^2.1",
|
||||||
|
"phpunit/phpunit": "^5 || ^6.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"src/getallheaders.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Ralph Khattar",
|
||||||
|
"email": "ralph.khattar@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "A polyfill for getallheaders.",
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/ralouphie/getallheaders/issues",
|
||||||
|
"source": "https://github.com/ralouphie/getallheaders/tree/develop"
|
||||||
|
},
|
||||||
|
"time": "2019-03-08T08:55:37+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "react/promise",
|
"name": "react/promise",
|
||||||
"version": "v2.7.1",
|
"version": "v2.7.1",
|
||||||
@@ -2158,6 +2715,91 @@
|
|||||||
],
|
],
|
||||||
"time": "2019-08-06T08:03:45+00:00"
|
"time": "2019-08-06T08:03:45+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "symfony/polyfill-mbstring",
|
||||||
|
"version": "v1.36.0",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/symfony/polyfill-mbstring.git",
|
||||||
|
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||||
|
"reference": "6a21eb99c6973357967f6ce3708cd55a6bec6315",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-iconv": "*",
|
||||||
|
"php": ">=7.2"
|
||||||
|
},
|
||||||
|
"provide": {
|
||||||
|
"ext-mbstring": "*"
|
||||||
|
},
|
||||||
|
"suggest": {
|
||||||
|
"ext-mbstring": "For best performance"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"thanks": {
|
||||||
|
"url": "https://github.com/symfony/polyfill",
|
||||||
|
"name": "symfony/polyfill"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"files": [
|
||||||
|
"bootstrap.php"
|
||||||
|
],
|
||||||
|
"psr-4": {
|
||||||
|
"Symfony\\Polyfill\\Mbstring\\": ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nicolas Grekas",
|
||||||
|
"email": "p@tchwork.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Symfony Community",
|
||||||
|
"homepage": "https://symfony.com/contributors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Symfony polyfill for the Mbstring extension",
|
||||||
|
"homepage": "https://symfony.com",
|
||||||
|
"keywords": [
|
||||||
|
"compatibility",
|
||||||
|
"mbstring",
|
||||||
|
"polyfill",
|
||||||
|
"portable",
|
||||||
|
"shim"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"source": "https://github.com/symfony/polyfill-mbstring/tree/v1.36.0"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://symfony.com/sponsor",
|
||||||
|
"type": "custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/fabpot",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://github.com/nicolas-grekas",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
|
||||||
|
"type": "tidelift"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2026-04-10T17:25:58+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "symfony/polyfill-php56",
|
"name": "symfony/polyfill-php56",
|
||||||
"version": "v1.12.0",
|
"version": "v1.12.0",
|
||||||
@@ -3909,5 +4551,6 @@
|
|||||||
"prefer-stable": false,
|
"prefer-stable": false,
|
||||||
"prefer-lowest": false,
|
"prefer-lowest": false,
|
||||||
"platform": [],
|
"platform": [],
|
||||||
"platform-dev": []
|
"platform-dev": [],
|
||||||
|
"plugin-api-version": "2.2.0"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ return [
|
|||||||
],
|
],
|
||||||
|
|
||||||
'minio' => [
|
'minio' => [
|
||||||
'driver' => 's3',
|
'driver' => 'minio',
|
||||||
'key' => env('MINIO_KEY'),
|
'key' => env('MINIO_KEY'),
|
||||||
'secret' => env('MINIO_SECRET'),
|
'secret' => env('MINIO_SECRET'),
|
||||||
'region' => env('MINIO_REGION', 'us-east-1'),
|
'region' => env('MINIO_REGION', 'us-east-1'),
|
||||||
|
|||||||
@@ -1,3 +1,19 @@
|
|||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
# Local development stack
|
||||||
|
#
|
||||||
|
# Default (local MariaDB):
|
||||||
|
# docker compose -f docker-compose.local.yml up --build
|
||||||
|
#
|
||||||
|
# Remote DB via SSH private-key tunnel:
|
||||||
|
# 1. Set SSH_HOST, SSH_USER, SSH_KEY_PATH, SSH_DB_REMOTE_HOST,
|
||||||
|
# SSH_DB_REMOTE_PORT (and DB_* creds) in .env.local
|
||||||
|
# 2. docker compose -f docker-compose.local.yml --profile ssh-db up --build
|
||||||
|
# The app will talk to db-tunnel (port 3306) instead of the local db.
|
||||||
|
#
|
||||||
|
# App: http://localhost:8082
|
||||||
|
# phpMyAdmin: http://localhost:8083
|
||||||
|
# ─────────────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
services:
|
services:
|
||||||
db:
|
db:
|
||||||
image: mariadb:10.6
|
image: mariadb:10.6
|
||||||
@@ -29,11 +45,7 @@ services:
|
|||||||
- APP_DEBUG=true
|
- APP_DEBUG=true
|
||||||
- APP_URL=http://localhost:8082
|
- APP_URL=http://localhost:8082
|
||||||
- DB_CONNECTION=mysql
|
- DB_CONNECTION=mysql
|
||||||
- DB_HOST=db
|
- DB_PORT=${DB_PORT:-3306}
|
||||||
- DB_PORT=3306
|
|
||||||
- DB_DATABASE=crewsportswear
|
|
||||||
- DB_USERNAME=crewsportswear
|
|
||||||
- DB_PASSWORD=secret
|
|
||||||
- PROD_PRIVATE=http://localhost:8082
|
- PROD_PRIVATE=http://localhost:8082
|
||||||
- IMAGES_URL=http://localhost:8082
|
- IMAGES_URL=http://localhost:8082
|
||||||
- UPLOAD_URL=http://localhost:8082/uploads/
|
- UPLOAD_URL=http://localhost:8082/uploads/
|
||||||
@@ -56,15 +68,65 @@ services:
|
|||||||
- MINIO_REGION=us-east-1
|
- MINIO_REGION=us-east-1
|
||||||
- MINIO_USE_PATH_STYLE=false
|
- MINIO_USE_PATH_STYLE=false
|
||||||
- MINIO_URL=https://minio.crewsportswear.app
|
- MINIO_URL=https://minio.crewsportswear.app
|
||||||
|
# DB_HOST defaults to local container; set to db-tunnel in .env.local for SSH mode
|
||||||
|
- DB_HOST=${DB_HOST:-db}
|
||||||
|
- DB_DATABASE=${DB_DATABASE:-crewsportswear}
|
||||||
|
- DB_USERNAME=${DB_USERNAME:-crewsportswear}
|
||||||
|
- DB_PASSWORD=${DB_PASSWORD:-secret}
|
||||||
|
env_file:
|
||||||
|
- path: .env.local
|
||||||
|
required: false
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/var/www/html
|
- ./:/var/www/html
|
||||||
- ./storage:/var/www/html/storage
|
- ./storage:/var/www/html/storage
|
||||||
- ./public/uploads:/var/www/html/public/uploads
|
- ./public/uploads:/var/www/html/public/uploads
|
||||||
|
# Keep the vendor/ directory from the image — prevents the bind-mount
|
||||||
|
# from wiping out the composer install done during docker build.
|
||||||
|
- vendor_local:/var/www/html/vendor
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
networks:
|
networks:
|
||||||
- crewsportswear-local
|
- crewsportswear-local
|
||||||
|
|
||||||
|
# ── SSH tunnel to a remote database ────────────────────────────────────────
|
||||||
|
# Activated only with: --profile ssh-db
|
||||||
|
# Requires SSH_HOST, SSH_USER, SSH_KEY_PATH (and optionally SSH_PORT,
|
||||||
|
# SSH_DB_REMOTE_HOST, SSH_DB_REMOTE_PORT) set in .env.local.
|
||||||
|
db-tunnel:
|
||||||
|
profiles:
|
||||||
|
- ssh-db
|
||||||
|
image: alpine:3.19
|
||||||
|
container_name: crewsportswear_db_tunnel
|
||||||
|
restart: unless-stopped
|
||||||
|
environment:
|
||||||
|
- SSH_HOST=${SSH_HOST}
|
||||||
|
- SSH_PORT=${SSH_PORT:-22}
|
||||||
|
- SSH_USER=${SSH_USER:-root}
|
||||||
|
- SSH_DB_REMOTE_HOST=${SSH_DB_REMOTE_HOST:-127.0.0.1}
|
||||||
|
- SSH_DB_REMOTE_PORT=${SSH_DB_REMOTE_PORT:-3306}
|
||||||
|
volumes:
|
||||||
|
# Mount your private key read-only; path configured in .env.local
|
||||||
|
- ${SSH_KEY_PATH:-~/.ssh/id_rsa}:/ssh/id_rsa:ro
|
||||||
|
command:
|
||||||
|
- sh
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
apk add --no-cache openssh-client
|
||||||
|
cp /ssh/id_rsa /tmp/id_rsa
|
||||||
|
chmod 600 /tmp/id_rsa
|
||||||
|
echo "Starting SSH tunnel to $$SSH_HOST..."
|
||||||
|
exec ssh -N \
|
||||||
|
-o StrictHostKeyChecking=no \
|
||||||
|
-o ServerAliveInterval=30 \
|
||||||
|
-o ServerAliveCountMax=3 \
|
||||||
|
-o ExitOnForwardFailure=yes \
|
||||||
|
-i /tmp/id_rsa \
|
||||||
|
-L "0.0.0.0:3306:$$SSH_DB_REMOTE_HOST:$$SSH_DB_REMOTE_PORT" \
|
||||||
|
-p "$$SSH_PORT" \
|
||||||
|
"$$SSH_USER@$$SSH_HOST"
|
||||||
|
networks:
|
||||||
|
- crewsportswear-local
|
||||||
|
|
||||||
phpmyadmin:
|
phpmyadmin:
|
||||||
image: arm64v8/phpmyadmin
|
image: arm64v8/phpmyadmin
|
||||||
platform: linux/arm64
|
platform: linux/arm64
|
||||||
@@ -87,3 +149,4 @@ networks:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
db_data:
|
db_data:
|
||||||
|
vendor_local:
|
||||||
|
|||||||
@@ -28,6 +28,14 @@ services:
|
|||||||
- ANALYTICS_SITE_ID=${ANALYTICS_SITE_ID}
|
- ANALYTICS_SITE_ID=${ANALYTICS_SITE_ID}
|
||||||
- ANALYTICS_CLIENT_ID=${ANALYTICS_CLIENT_ID}
|
- ANALYTICS_CLIENT_ID=${ANALYTICS_CLIENT_ID}
|
||||||
- ANALYTICS_SERVICE_EMAIL=${ANALYTICS_SERVICE_EMAIL}
|
- ANALYTICS_SERVICE_EMAIL=${ANALYTICS_SERVICE_EMAIL}
|
||||||
|
# MinIO S3 Storage
|
||||||
|
- MINIO_ENDPOINT=${MINIO_ENDPOINT:-http://crew-minio-prod:9000}
|
||||||
|
- MINIO_KEY=${MINIO_KEY}
|
||||||
|
- MINIO_SECRET=${MINIO_SECRET}
|
||||||
|
- MINIO_BUCKET=${MINIO_BUCKET:-crewsportswear}
|
||||||
|
- MINIO_REGION=${MINIO_REGION:-us-east-1}
|
||||||
|
- MINIO_USE_PATH_STYLE=${MINIO_USE_PATH_STYLE:-true}
|
||||||
|
- MINIO_URL=${MINIO_URL:-https://minio.crewsportswear.app}
|
||||||
volumes:
|
volumes:
|
||||||
- ./storage:/var/www/html/storage
|
- ./storage:/var/www/html/storage
|
||||||
- ./public/uploads:/var/www/html/public/uploads
|
- ./public/uploads:/var/www/html/public/uploads
|
||||||
|
|||||||
@@ -13,5 +13,11 @@ mkdir -p bootstrap/cache
|
|||||||
chown -R www-data:www-data storage bootstrap/cache
|
chown -R www-data:www-data storage bootstrap/cache
|
||||||
chmod -R 775 storage bootstrap/cache
|
chmod -R 775 storage bootstrap/cache
|
||||||
|
|
||||||
|
# Install/update Composer dependencies if vendor is missing or composer.json changed
|
||||||
|
if [ ! -f vendor/autoload.php ]; then
|
||||||
|
echo "vendor/autoload.php not found — running composer install..."
|
||||||
|
composer install --no-interaction --prefer-dist
|
||||||
|
fi
|
||||||
|
|
||||||
# Execute the main command
|
# Execute the main command
|
||||||
exec "$@"
|
exec "$@"
|
||||||
|
|||||||
@@ -7,14 +7,17 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
body {
|
||||||
|
background: #f4f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
h2 {
|
h2 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
margin-top: 40px;
|
margin-top: 34px;
|
||||||
margin-bottom: 40px;
|
margin-bottom: 24px;
|
||||||
|
|
||||||
}
|
}
|
||||||
/* h2:after {
|
/* h2:after {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
@@ -41,6 +44,15 @@
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.featured-title {
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.6px;
|
||||||
|
color: #111827;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
.price{
|
.price{
|
||||||
font-size: 25px;
|
font-size: 25px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@@ -51,19 +63,107 @@
|
|||||||
border-bottom: 2px solid #4B8E4B;
|
border-bottom: 2px solid #4B8E4B;
|
||||||
}
|
}
|
||||||
.thumbnail{
|
.thumbnail{
|
||||||
/* opacity:0.70; */
|
-webkit-transition: all 0.5s;
|
||||||
-webkit-transition: all 0.5s;
|
|
||||||
transition: all 0.5s;
|
transition: all 0.5s;
|
||||||
}
|
}
|
||||||
.thumbnail:hover{
|
|
||||||
opacity:1.00;
|
|
||||||
box-shadow: 0px 0px 10px #4bc6ff;
|
|
||||||
}
|
|
||||||
.line{
|
.line{
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
.thumbnail>img{
|
.products-grid {
|
||||||
height:201.84px;
|
margin-top: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-col {
|
||||||
|
margin-bottom: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card {
|
||||||
|
height: 100%;
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||||
|
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: #d1d5db;
|
||||||
|
box-shadow: 0 10px 24px rgba(2, 6, 23, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 16px;
|
||||||
|
min-height: 240px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-image {
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 202px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-body {
|
||||||
|
padding: 12px 14px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-name-holder {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: #111827;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #eef2f7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 22px;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price small,
|
||||||
|
.price-currency {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view-details {
|
||||||
|
border-radius: 8px;
|
||||||
|
min-height: 36px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 12px;
|
||||||
|
padding: 8px 12px;
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
margin: 50px 0;
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 770px) {
|
@media screen and (max-width: 770px) {
|
||||||
.right{
|
.right{
|
||||||
@@ -127,6 +227,164 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Category nav ──────────────────────────────────────────────────────── */
|
||||||
|
.cat-nav {
|
||||||
|
background: #ffffff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
.cat-nav ul {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
.cat-nav > ul > li {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.cat-nav > ul > li > a {
|
||||||
|
display: block;
|
||||||
|
padding: 14px 20px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 13px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #222;
|
||||||
|
text-decoration: none;
|
||||||
|
letter-spacing: .5px;
|
||||||
|
border-bottom: 3px solid transparent;
|
||||||
|
transition: border-color .2s, color .2s;
|
||||||
|
}
|
||||||
|
.cat-nav > ul > li > a:hover,
|
||||||
|
.cat-nav > ul > li > a.active {
|
||||||
|
color: #4B8E4B;
|
||||||
|
border-bottom-color: #4B8E4B;
|
||||||
|
}
|
||||||
|
/* sub-category dropdown */
|
||||||
|
.cat-nav .sub-menu {
|
||||||
|
display: none;
|
||||||
|
position: absolute;
|
||||||
|
top: 100%;
|
||||||
|
left: 0;
|
||||||
|
min-width: 200px;
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-top: 3px solid #4B8E4B;
|
||||||
|
box-shadow: 0 4px 12px rgba(0,0,0,.12);
|
||||||
|
z-index: 999;
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 6px 0;
|
||||||
|
}
|
||||||
|
.cat-nav > ul > li:hover .sub-menu {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.cat-nav .sub-menu li a {
|
||||||
|
display: block;
|
||||||
|
padding: 9px 18px;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #333;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.cat-nav .sub-menu li a:hover,
|
||||||
|
.cat-nav .sub-menu li a.active {
|
||||||
|
background: #f0f9f0;
|
||||||
|
color: #4B8E4B;
|
||||||
|
}
|
||||||
|
.cat-count {
|
||||||
|
display: inline-block;
|
||||||
|
background: #ddd;
|
||||||
|
border-radius: 10px;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 1px 7px;
|
||||||
|
margin-left: 4px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ── League / conference pill filter (sub-sub-category) ─────────────────── */
|
||||||
|
.league-filter {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 10px 12px 14px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #fff;
|
||||||
|
align-items: center;
|
||||||
|
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||||
|
}
|
||||||
|
.league-filter .lf-label {
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 700;
|
||||||
|
text-transform: uppercase;
|
||||||
|
color: #888;
|
||||||
|
letter-spacing: .5px;
|
||||||
|
margin-right: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.league-filter a {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #333;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-decoration: none;
|
||||||
|
white-space: nowrap;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
transition: background .2s, color .2s, border-color .2s;
|
||||||
|
}
|
||||||
|
.league-filter a:hover {
|
||||||
|
background: #e0f2e0;
|
||||||
|
color: #4B8E4B;
|
||||||
|
border-color: #4B8E4B;
|
||||||
|
}
|
||||||
|
.league-filter a.active {
|
||||||
|
background: #4B8E4B;
|
||||||
|
color: #fff;
|
||||||
|
border-color: #4B8E4B;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 991px) {
|
||||||
|
.product-image-link {
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view-details {
|
||||||
|
padding: 7px 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 767px) {
|
||||||
|
h2 {
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.featured-title {
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-card-footer {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-view-details {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[v-cloak] { display: none; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@@ -147,83 +405,277 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<h2>FEATURED PRODUCTS</h2>
|
<h2 class="featured-title">Featured Products</h2>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
|
{{-- ── Vue category + product grid ─────────────────────────────────────── --}}
|
||||||
|
<script>
|
||||||
|
window._tsProducts = {!! json_encode(
|
||||||
|
collect($product_array)
|
||||||
|
->where('PrivacyStatus', 'public')
|
||||||
|
->map(function($p) use ($thumbnails) {
|
||||||
|
$thumbList = isset($thumbnails) ? $thumbnails : [];
|
||||||
|
$thumb = collect($thumbList)->filter(function($t) use ($p) {
|
||||||
|
return $t['product_id'] == $p->Id;
|
||||||
|
})->first();
|
||||||
|
return [
|
||||||
|
'id' => $p->Id,
|
||||||
|
'name' => $p->ProductName,
|
||||||
|
'price' => $p->ProductPrice,
|
||||||
|
'url' => $p->ProductURL,
|
||||||
|
'img' => $thumb ? $thumb['thumb'] : 'product-image-placeholder.png',
|
||||||
|
'folder'=> $thumb ? $thumb['folder'] : '',
|
||||||
|
];
|
||||||
|
})->values()->toArray(),
|
||||||
|
JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT
|
||||||
|
) !!};
|
||||||
|
window._tsStore = {
|
||||||
|
url : {!! json_encode($store_array[0]->StoreUrl, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) !!},
|
||||||
|
currency : {!! json_encode($store_array[0]->StoreCurrency, JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) !!},
|
||||||
|
minoBase : {!! json_encode(rtrim(config('filesystems.disks.minio.url', ''), '/') . '/' . env('MINIO_BUCKET', 'crewsportswear') . '/', JSON_HEX_TAG | JSON_HEX_APOS | JSON_HEX_AMP | JSON_HEX_QUOT) !!},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div id="ts-app" v-cloak>
|
||||||
|
|
||||||
|
{{-- ── Category nav ── --}}
|
||||||
|
<nav class="cat-nav" v-if="categories.length > 0">
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="#" :class="{active: activeCategory===null && activeSubCategory===null}"
|
||||||
|
@click.prevent="activeCategory=null; activeSubCategory=null">
|
||||||
|
All
|
||||||
|
<span class="cat-count">@{{ totalPublic }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li v-for="cat in categories" :key="cat.name">
|
||||||
|
<a href="#"
|
||||||
|
:class="{active: activeCategory===cat.name}"
|
||||||
|
@click.prevent="selectCategory(cat.name)">
|
||||||
|
@{{ cat.name }}
|
||||||
|
<span class="cat-count">@{{ cat.count }}</span>
|
||||||
|
</a>
|
||||||
|
<ul class="sub-menu" v-if="cat.subs.length > 0">
|
||||||
|
<li v-for="sub in cat.subs" :key="sub.name">
|
||||||
|
<a href="#"
|
||||||
|
:class="{active: activeCategory===cat.name && activeSubCategory===sub.name}"
|
||||||
|
@click.prevent="selectSub(cat.name, sub.name)">
|
||||||
|
@{{ sub.name }}
|
||||||
|
<span class="cat-count">@{{ sub.count }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
{{-- ── League / conference pill filter ── --}}
|
||||||
|
<div class="league-filter" v-if="isLeagueStore && activeCategory !== null && leagues.length > 0">
|
||||||
|
<span class="lf-label">League / Conf:</span>
|
||||||
|
<a href="#"
|
||||||
|
:class="{active: activeLeague === null}"
|
||||||
|
@click.prevent="activeLeague = null">All</a>
|
||||||
|
<a href="#"
|
||||||
|
v-for="lg in leagues" :key="lg.name"
|
||||||
|
:class="{active: activeLeague === lg.name}"
|
||||||
|
@click.prevent="activeLeague = lg.name">
|
||||||
|
@{{ lg.name }}
|
||||||
|
<span class="cat-count">@{{ lg.count }}</span>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- ── Announcements (kept outside loop) ── --}}
|
||||||
@if ($announcement->IsActive)
|
@if ($announcement->IsActive)
|
||||||
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
<p><b>Shop Announcements:</b></p>
|
<p><b>Shop Announcements:</b></p>
|
||||||
{!! nl2br(e($announcement->Announcement)) !!}
|
{!! nl2br(e($announcement->Announcement)) !!}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@if($store_array[0]->Id == 174 || $store_array[0]->Id == 175 || $store_array[0]->Id == 178 || $store_array[0]->Id == 179 || $store_array[0]->Id == 177 || $store_array[0]->Id == 189 || $store_array[0]->Id == 176 || $store_array[0]->Id == 190 || $store_array[0]->Id == 191 || $store_array[0]->Id == 192 || $store_array[0]->Id == 194)
|
@if($store_array[0]->Id == 174 || $store_array[0]->Id == 175 || $store_array[0]->Id == 178 || $store_array[0]->Id == 179 || $store_array[0]->Id == 177 || $store_array[0]->Id == 189 || $store_array[0]->Id == 176 || $store_array[0]->Id == 190 || $store_array[0]->Id == 191 || $store_array[0]->Id == 192 || $store_array[0]->Id == 194)
|
||||||
<div class="col-md-12">
|
<div class="row">
|
||||||
<div class="alert alert-warning">
|
<div class="col-md-12">
|
||||||
<p><b>Please read:</b></p>
|
<div class="alert alert-warning">
|
||||||
1. All orders will be batch shipped to your school for pick up.<br>
|
|
||||||
2. Orders will be batch processed on a weekly basis, please allow 2-3 weeks for delivery.<br>
|
|
||||||
3. Masks and gaiters sold on Crew are not intended for medical use. Crew does not make any medical or health claims.<br>
|
|
||||||
4. Refunds and exchanges are not allowed due to the hygenic nature of the product.<br>
|
|
||||||
@if($store_array[0]->Id == 175)
|
|
||||||
5. $1 from every item sold will benefit the 2020-2021 Maine South Schoolwide Fundraiser.
|
|
||||||
@endif
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@else
|
|
||||||
<div class="col-md-12">
|
|
||||||
<div class="alert alert-warning">
|
|
||||||
<p><b>Please read:</b></p>
|
<p><b>Please read:</b></p>
|
||||||
1. Items purchased are made on demand. Orders will be shipped based on dates set by your store administrator.<br>
|
1. All orders will be batch shipped to your school for pick up.<br>
|
||||||
2. Store payments are processed through PayPal. A PayPal account is not required to make a purchase.<br>
|
2. Orders will be batch processed on a weekly basis, please allow 2-3 weeks for delivery.<br>
|
||||||
@if($store_array[0]->Id == 222)
|
3. Masks and gaiters sold on Crew are not intended for medical use. Crew does not make any medical or health claims.<br>
|
||||||
3. Orders will be batch processed on a monthly basis, please allow 4-6 weeks for delivery.<br>
|
4. Refunds and exchanges are not allowed due to the hygenic nature of the product.<br>
|
||||||
@else
|
@if($store_array[0]->Id == 175)
|
||||||
3. Orders will be batch processed on a weekly basis, please allow 2-3 weeks for delivery.<br>
|
5. $1 from every item sold will benefit the 2020-2021 Maine South Schoolwide Fundraiser.
|
||||||
@endif
|
@endif
|
||||||
4. We are currently only shipping to US locations. For international orders, please contact <b>orders@crewsportswear.com</b> if you'd like to place an order.<br>
|
|
||||||
5. Expect shipping delays due to COVID-19.<br>
|
|
||||||
6. All sales are final. No returns or exchanges will be accepted.<br><br>
|
|
||||||
|
|
||||||
<b>DISCLAIMER:</b> Masks and gaiters sold by Crew Sportswear are not intended for medical use. Crew Sportswear does not make any medical or health claims.
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
<!-- BEGIN PRODUCTS -->
|
|
||||||
|
|
||||||
@foreach($product_array as $i => $product)
|
|
||||||
@if($product->PrivacyStatus == "public")
|
|
||||||
@foreach($thumbnails as $t => $thumb)
|
|
||||||
@if($thumb['product_id'] == $product->Id)
|
|
||||||
@define $storeFolder = $thumb['folder']
|
|
||||||
@define $filename = $thumb['thumb']
|
|
||||||
@endif
|
|
||||||
@endforeach
|
|
||||||
|
|
||||||
<div class="col-md-3 col-sm-6">
|
|
||||||
<span class="thumbnail">
|
|
||||||
<a href="{{ url('teamstore') }}/{{ $store_array[0]->StoreUrl }}/product/{{ $product->ProductURL }}">
|
|
||||||
<img style="height: 201.84px;" src="{{ minio_url('images/' . $filename) }}" alt="{{ $product->ProductName }}" >
|
|
||||||
</a>
|
|
||||||
<h4 class="text-center product-name-holder">{{ $product->ProductName }}</h4>
|
|
||||||
<hr class="line">
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-md-7 col-sm-7">
|
|
||||||
<p class="price">{{ $product->ProductPrice }} <small style="font-size: 15px;"> {{ $store_array[0]->StoreCurrency }}</small></p>
|
|
||||||
</div>
|
|
||||||
<div class="col-md-5 col-sm-5">
|
|
||||||
<a href="{{ url('teamstore') }}/{{ $store_array[0]->StoreUrl }}/product/{{ $product->ProductURL }}" class="btn btn-success right" > View Details</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
@endif
|
</div>
|
||||||
@endforeach
|
</div>
|
||||||
<!-- END PRODUCTS -->
|
@endif
|
||||||
|
|
||||||
</div>
|
{{-- ── Product grid ── --}}
|
||||||
</div> <!-- cotainer -->
|
<div class="row products-grid">
|
||||||
|
<div class="col-md-12 text-center" v-if="filtered.length === 0">
|
||||||
|
<p class="empty-state">No products found in this category.</p>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-3 col-sm-6 product-col" v-for="p in filtered" :key="p.id">
|
||||||
|
<div class="product-card">
|
||||||
|
<a :href="productUrl(p)" class="product-image-link">
|
||||||
|
<img class="product-image" :src="imgUrl(p)" :alt="p.name">
|
||||||
|
</a>
|
||||||
|
<div class="product-card-body">
|
||||||
|
<h4 class="text-center product-name-holder">@{{ p.name }}</h4>
|
||||||
|
<div class="product-card-footer">
|
||||||
|
<p class="price">@{{ p.price }} <small class="price-currency">@{{ store.currency }}</small></p>
|
||||||
|
<a :href="productUrl(p)" class="btn btn-success btn-view-details">View Details</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>{{-- /ts-app --}}
|
||||||
|
|
||||||
|
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
// ── sport & item-type keyword maps ──────────────────────────────────
|
||||||
|
const SPORTS = [
|
||||||
|
'Basketball','Football','Soccer','Baseball','Softball',
|
||||||
|
'Volleyball','Hockey','Lacrosse','Wrestling','Tennis',
|
||||||
|
'Swimming','Track','Cross Country','Golf','Cheerleading',
|
||||||
|
'Dance','Rugby','Bowling','Gymnastics','Cycling',
|
||||||
|
];
|
||||||
|
const ITEMS = [
|
||||||
|
'Jersey','T-Shirt','Tee','Hoodie','Sweatshirt','Jacket',
|
||||||
|
'Shorts','Pants','Tank','Top','Pullover','Zip-Up',
|
||||||
|
'Hat','Cap','Beanie','Polo','Uniform','Warmup','Pinnie',
|
||||||
|
];
|
||||||
|
// ── league / conference keyword map ──────────────────────────────────
|
||||||
|
// Order matters: more specific phrases first
|
||||||
|
const LEAGUES = [
|
||||||
|
{ key: 'WNBA', terms: ['\\bwnba\\b'] },
|
||||||
|
{ key: 'NBA', terms: ['\\bnba\\b'] },
|
||||||
|
{ key: 'NFL', terms: ['\\bnfl\\b'] },
|
||||||
|
{ key: 'MLB', terms: ['\\bmlb\\b'] },
|
||||||
|
{ key: 'NHL', terms: ['\\bnhl\\b'] },
|
||||||
|
{ key: 'MLS', terms: ['\\bmls\\b'] },
|
||||||
|
{ key: 'Big Ten', terms: ['\\bbig\\s*ten\\b','\\bbig\\s*10\\b','\\bbig10\\b'] },
|
||||||
|
{ key: 'ACC', terms: ['\\bacc\\b'] },
|
||||||
|
{ key: 'SEC', terms: ['\\bsec\\b'] },
|
||||||
|
{ key: 'Big 12', terms: ['\\bbig\\s*12\\b','\\bbig12\\b'] },
|
||||||
|
{ key: 'Pac-12', terms: ['\\bpac[-\\s]*12\\b','\\bpac12\\b'] },
|
||||||
|
{ key: 'AAC', terms: ['\\baac\\b'] },
|
||||||
|
{ key: 'CUSA', terms: ['\\bcusa\\b','\\bc-usa\\b'] },
|
||||||
|
{ key: 'MAC', terms: ['\\bmac\\b'] },
|
||||||
|
{ key: 'Sun Belt',terms: ['\\bsun\\s+belt\\b'] },
|
||||||
|
{ key: 'Mountain West', terms: ['\\bmountain\\s+west\\b','\\bmwc\\b'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
function classify(name) {
|
||||||
|
const n = name.toLowerCase();
|
||||||
|
const sport = SPORTS.find(s => n.includes(s.toLowerCase())) || 'Other';
|
||||||
|
const item = ITEMS.find(i => n.includes(i.toLowerCase())) || 'Other';
|
||||||
|
let league = null;
|
||||||
|
for (const lg of LEAGUES) {
|
||||||
|
if (lg.terms.some(t => {
|
||||||
|
try { return new RegExp(t, 'i').test(n); } catch(e) { return n.includes(t); }
|
||||||
|
})) {
|
||||||
|
league = lg.key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { sport, item, league };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { createApp } = Vue;
|
||||||
|
const store = window._tsStore || { url:'', currency:'', minoBase:'' };
|
||||||
|
const allProducts = Array.isArray(window._tsProducts) ? window._tsProducts : [];
|
||||||
|
|
||||||
|
createApp({
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
products : allProducts,
|
||||||
|
store : store,
|
||||||
|
activeCategory : null,
|
||||||
|
activeSubCategory: null,
|
||||||
|
activeLeague : null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
totalPublic() {
|
||||||
|
return this.products.length;
|
||||||
|
},
|
||||||
|
isLeagueStore() {
|
||||||
|
return this.store.url === 'hi-five-franchise-store';
|
||||||
|
},
|
||||||
|
// Build [ { name:'Basketball', count:N, subs:[{name:'Jersey',count:N},...] }, ... ]
|
||||||
|
categories() {
|
||||||
|
const map = {};
|
||||||
|
this.products.forEach(p => {
|
||||||
|
const { sport, item } = classify(p.name);
|
||||||
|
if (!map[sport]) map[sport] = {};
|
||||||
|
map[sport][item] = (map[sport][item] || 0) + 1;
|
||||||
|
});
|
||||||
|
return Object.entries(map)
|
||||||
|
.sort((a,b) => a[0].localeCompare(b[0]))
|
||||||
|
.map(([name, subs]) => ({
|
||||||
|
name,
|
||||||
|
count: Object.values(subs).reduce((a,b) => a+b, 0),
|
||||||
|
subs: Object.entries(subs)
|
||||||
|
.sort((a,b) => a[0].localeCompare(b[0]))
|
||||||
|
.map(([s, count]) => ({ name: s, count }))
|
||||||
|
.filter(s => s.name !== 'Other' || Object.keys(subs).length === 1),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
// Available leagues for the currently active category (+ optional sub)
|
||||||
|
// Only computed for hi-five-franchise-store
|
||||||
|
leagues() {
|
||||||
|
if (!this.isLeagueStore) return [];
|
||||||
|
const map = {};
|
||||||
|
this.products.forEach(p => {
|
||||||
|
const { sport, item, league } = classify(p.name);
|
||||||
|
if (!league) return;
|
||||||
|
if (this.activeCategory && sport !== this.activeCategory) return;
|
||||||
|
if (this.activeSubCategory && item !== this.activeSubCategory) return;
|
||||||
|
map[league] = (map[league] || 0) + 1;
|
||||||
|
});
|
||||||
|
return Object.entries(map)
|
||||||
|
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||||
|
.map(([name, count]) => ({ name, count }));
|
||||||
|
},
|
||||||
|
filtered() {
|
||||||
|
if (!this.activeCategory && !this.activeLeague) return this.products;
|
||||||
|
return this.products.filter(p => {
|
||||||
|
const { sport, item, league } = classify(p.name);
|
||||||
|
if (this.activeCategory && sport !== this.activeCategory) return false;
|
||||||
|
if (this.activeSubCategory && item !== this.activeSubCategory) return false;
|
||||||
|
if (this.isLeagueStore && this.activeLeague && league !== this.activeLeague) return false;
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
selectCategory(cat) {
|
||||||
|
this.activeCategory = cat;
|
||||||
|
this.activeSubCategory = null;
|
||||||
|
this.activeLeague = null;
|
||||||
|
},
|
||||||
|
selectSub(cat, sub) {
|
||||||
|
this.activeCategory = cat;
|
||||||
|
this.activeSubCategory = sub;
|
||||||
|
this.activeLeague = null;
|
||||||
|
},
|
||||||
|
productUrl(p) {
|
||||||
|
return '/teamstore/' + store.url + '/product/' + p.url;
|
||||||
|
},
|
||||||
|
imgUrl(p) {
|
||||||
|
return store.minoBase + 'images/' + p.img;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}).mount('#ts-app');
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</div>{{-- /container --}}
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -7,6 +7,45 @@
|
|||||||
@endif
|
@endif
|
||||||
<style>
|
<style>
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #f4f6f8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-page {
|
||||||
|
padding-top: 14px;
|
||||||
|
padding-bottom: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-breadcrumb {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-breadcrumb .breadcrumb {
|
||||||
|
margin: 0;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-breadcrumb .breadcrumb > li,
|
||||||
|
.product-breadcrumb .breadcrumb > li.active,
|
||||||
|
.product-breadcrumb .breadcrumb > li + li:before {
|
||||||
|
color: #6b7280;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-breadcrumb .breadcrumb > li > a {
|
||||||
|
color: #374151;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-breadcrumb .breadcrumb > li > a:hover {
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
p{
|
p{
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
@@ -30,7 +69,8 @@
|
|||||||
box-shadow: 0px 0px 10px #4bc6ff;
|
box-shadow: 0px 0px 10px #4bc6ff;
|
||||||
} */
|
} */
|
||||||
.line{
|
.line{
|
||||||
margin-bottom: 5px;
|
margin: 14px 0;
|
||||||
|
border-color: #e5e7eb;
|
||||||
}
|
}
|
||||||
@media screen and (max-width: 770px) {
|
@media screen and (max-width: 770px) {
|
||||||
.right{
|
.right{
|
||||||
@@ -117,18 +157,45 @@
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.carousel-control.left {
|
#myCarousel {
|
||||||
margin-left: -35px;
|
position: relative;
|
||||||
margin-top: 7px;
|
width: 100%;
|
||||||
}
|
|
||||||
|
|
||||||
.carousel-control.right {
|
|
||||||
margin-right: -35px;
|
|
||||||
margin-top: 7px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.carousel-control {
|
.carousel-control {
|
||||||
width: 0%;
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -17px;
|
||||||
|
opacity: 1;
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-control.left {
|
||||||
|
left: -18px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-control.right {
|
||||||
|
right: -18px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-control .glyphicon {
|
||||||
|
width: 34px;
|
||||||
|
height: 34px;
|
||||||
|
line-height: 34px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-control:hover .glyphicon {
|
||||||
|
background: #ffffff;
|
||||||
|
border-color: #9ca3af;
|
||||||
|
color: #111827;
|
||||||
}
|
}
|
||||||
|
|
||||||
.custom-chevron-left,
|
.custom-chevron-left,
|
||||||
@@ -139,11 +206,127 @@
|
|||||||
|
|
||||||
.hide-bullets {
|
.hide-bullets {
|
||||||
list-style:none;
|
list-style:none;
|
||||||
margin-left: -40px;
|
margin-left: 0;
|
||||||
margin-top:20px;
|
margin-top: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
.hide-bullets > li {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-gallery-card,
|
||||||
|
.product-info-card,
|
||||||
|
.description-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-gallery-card {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-image-stage {
|
||||||
|
border: 1px solid #eef0f3;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #fcfcfd;
|
||||||
|
min-height: 430px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-product-image {
|
||||||
|
max-height: 400px;
|
||||||
|
width: auto;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail {
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #d1d5db;
|
||||||
|
background: #fff;
|
||||||
|
margin-bottom: 0;
|
||||||
|
transition: border-color 0.2s, box-shadow 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumbnail:hover {
|
||||||
|
border-color: #9ca3af;
|
||||||
|
box-shadow: 0 3px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.a_thumbnail.active {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-thumbnails {
|
||||||
|
border-radius: 8px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-info-card {
|
||||||
|
padding: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 28px;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 30px;
|
||||||
|
margin: 8px 0 18px;
|
||||||
|
color: #0f172a;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price small {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #6b7280;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
#frm-order-list {
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-add-to-cart {
|
||||||
|
min-height: 44px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 9px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-card {
|
||||||
|
margin-top: 18px;
|
||||||
|
padding: 18px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-title {
|
||||||
|
margin: 0 0 10px;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #111827;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-text {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 1.65;
|
||||||
|
color: #4b5563;
|
||||||
|
}
|
||||||
|
|
||||||
.spacer-top{
|
.spacer-top{
|
||||||
margin-top: 40px;
|
margin-top: 24px;
|
||||||
}
|
}
|
||||||
.roster-input{
|
.roster-input{
|
||||||
border-radius: 0px;
|
border-radius: 0px;
|
||||||
@@ -208,12 +391,49 @@
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 991px) {
|
||||||
|
.product-info-card {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-image-stage {
|
||||||
|
min-height: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#btn-add-to-cart {
|
||||||
|
width: 100%;
|
||||||
|
float: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 768px) {
|
||||||
|
.main-image-stage {
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-control.left {
|
||||||
|
left: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-control.right {
|
||||||
|
right: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.product-title {
|
||||||
|
font-size: 23px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.price {
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container product-page">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<nav aria-label="breadcrumb">
|
<nav aria-label="breadcrumb" class="product-breadcrumb">
|
||||||
<ol class="breadcrumb">
|
<ol class="breadcrumb">
|
||||||
<li class="breadcrumb-item"><a href="{{ url('teamstore') }}/{{ $store_array[0]->StoreUrl }}">Home</a></li>
|
<li class="breadcrumb-item"><a href="{{ url('teamstore') }}/{{ $store_array[0]->StoreUrl }}">Home</a></li>
|
||||||
<li class="breadcrumb-item active" aria-current="page">{{ $product_array[0]->ProductName }}</li>
|
<li class="breadcrumb-item active" aria-current="page">{{ $product_array[0]->ProductName }}</li>
|
||||||
@@ -224,68 +444,62 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
|
|
||||||
<div class="col-md-5 col-sm-5">
|
<div class="col-md-5 col-sm-5">
|
||||||
<div class="row">
|
<div class="main-gallery-card">
|
||||||
<div class="col-sm-12" id="carousel-bounding-box">
|
<div id="carousel-bounding-box">
|
||||||
<div class="carousel slide" id="myCarousel" data-interval="false">
|
<div class="main-image-stage">
|
||||||
<!-- Carousel items -->
|
<div class="carousel slide" id="myCarousel" data-interval="false">
|
||||||
<div class="carousel-inner">
|
<div class="carousel-inner">
|
||||||
@define $i = 0
|
@define $i = 0
|
||||||
@foreach($thumbnails_array as $thumbnail)
|
@foreach($thumbnails_array as $thumbnail)
|
||||||
@if($thumbnail->ImageClass == 'active')
|
@if($thumbnail->ImageClass == 'active')
|
||||||
<div class="active item text-center" data-slide-number="{{ $i }}">
|
<div class="active item text-center" data-slide-number="{{ $i }}">
|
||||||
<span class="zoom img-zoom">
|
<span class="zoom img-zoom">
|
||||||
<img style="height:400px;" src="{{ minio_url('images/' . $thumbnail->Image) }}">
|
<img class="main-product-image" src="{{ minio_url('images/' . $thumbnail->Image) }}">
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="item text-center" data-slide-number="{{ $i }}">
|
<div class="item text-center" data-slide-number="{{ $i }}">
|
||||||
<span class="zoom img-zoom">
|
<span class="zoom img-zoom">
|
||||||
<img style="height:400px;" src="{{ minio_url('images/' . $thumbnail->Image) }}">
|
<img class="main-product-image" src="{{ minio_url('images/' . $thumbnail->Image) }}">
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@define $i++
|
@define $i++
|
||||||
@endforeach
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
|
||||||
|
<span class="glyphicon glyphicon-chevron-left"></span>
|
||||||
|
</a>
|
||||||
|
<a class="right carousel-control" href="#myCarousel" role="button" data-slide="next">
|
||||||
|
<span class="glyphicon glyphicon-chevron-right"></span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Carousel nav -->
|
|
||||||
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
|
|
||||||
<span class="glyphicon glyphicon-chevron-left"></span>
|
|
||||||
</a>
|
|
||||||
<a class="right carousel-control" href="#myCarousel" role="button" data-slide="next">
|
|
||||||
<span class="glyphicon glyphicon-chevron-right"></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- </div> -->
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<hr class="line">
|
<hr class="line">
|
||||||
|
|
||||||
<div class="row">
|
<ul class="hide-bullets row">
|
||||||
<div class="col-md-12">
|
@define $j = 0
|
||||||
<ul class="hide-bullets">
|
@foreach($thumbnails_array as $thumbnail)
|
||||||
@define $j = 0
|
<li class="col-sm-3 col-xs-3">
|
||||||
@foreach($thumbnails_array as $thumbnail)
|
<a class="thumbnail a_thumbnail {{ $thumbnail->ImageClass }}" id="carousel-selector-{{ $j }}">
|
||||||
<li class="col-sm-3 col-xs-3">
|
<img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ minio_url('images/' . $thumbnail->Image) }}"/>
|
||||||
<a class="thumbnail a_thumbnail {{ $thumbnail->ImageClass }}" id="carousel-selector-{{ $j }}">
|
</a>
|
||||||
<img class="img img-responsive product-center image-thumbnails" style="height: 59.45px;" src="{{ minio_url('images/' . $thumbnail->Image) }}"/>
|
</li>
|
||||||
</a>
|
@define $j++
|
||||||
</li>
|
@endforeach
|
||||||
@define $j++
|
</ul>
|
||||||
@endforeach
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-7 col-sm-7">
|
<div class="col-md-7 col-sm-7">
|
||||||
<div class="panel panel-default">
|
<div class="product-info-card">
|
||||||
<div class="panel-heading">
|
<h1 class="product-title">{{ $product_array[0]->ProductName }}</h1>
|
||||||
<h1>{{ $product_array[0]->ProductName }}</h1> <p class="price">{{ $product_array[0]->ProductPrice }} <small> {{ $store_array[0]->StoreCurrency }} </small></p>
|
<p class="price">{{ $product_array[0]->ProductPrice }} <small> {{ $store_array[0]->StoreCurrency }} </small></p>
|
||||||
</div>
|
|
||||||
<div class="panel-body">
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-md-12">
|
<div class="col-md-12">
|
||||||
<form id="frm-order-list">
|
<form id="frm-order-list">
|
||||||
@@ -300,24 +514,28 @@
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="spacer-top"></div>
|
|
||||||
<div class="row">
|
<div class="description-card">
|
||||||
<div class="col-md-12">
|
<h3 class="description-title">Description</h3>
|
||||||
<ul class="nav nav-tabs" role="tablist">
|
<p class="description-text">{{ $product_array[0]->ProductDescription }}</p>
|
||||||
<li role="presentation" class="active"><a href="#productDescription" aria-controls="productDescription" role="tab" data-toggle="tab">Description</a></li>
|
</div>
|
||||||
</ul>
|
|
||||||
<!-- Tab panes -->
|
<div class="spacer-top"></div>
|
||||||
<div class="tab-content">
|
<div class="row">
|
||||||
<div role="tabpanel" class="tab-pane active" id="productDescription">
|
<div class="col-md-12">
|
||||||
<div class="row">
|
<ul class="nav nav-tabs" role="tablist">
|
||||||
<div class="col-md-12">
|
<li role="presentation" class="active"><a href="#productDescription" aria-controls="productDescription" role="tab" data-toggle="tab">More Details</a></li>
|
||||||
<p>{{ $product_array[0]->ProductDescription }}</p>
|
</ul>
|
||||||
</div>
|
<div class="tab-content">
|
||||||
|
<div role="tabpanel" class="tab-pane active" id="productDescription">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<p>{{ $product_array[0]->ProductDescription }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -2,108 +2,237 @@
|
|||||||
@section('content')
|
@section('content')
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
a.thumbnail>img {
|
body {
|
||||||
/* height: 250px; */
|
background: #f4f6f8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hide-bullets {
|
.store-page {
|
||||||
list-style:none;
|
padding-top: 14px;
|
||||||
margin-left: -40px;
|
padding-bottom: 26px;
|
||||||
margin-top:20px;
|
|
||||||
position: relative;
|
|
||||||
}
|
}
|
||||||
.thumbnail{
|
|
||||||
border: none;
|
.store-header {
|
||||||
display: unset;
|
margin-bottom: 14px;
|
||||||
background-color: transparent;
|
|
||||||
}
|
}
|
||||||
.li-custom{
|
|
||||||
padding:10px;
|
.store-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
}
|
}
|
||||||
.store-logo{
|
|
||||||
/* height: 250px;
|
.store-subtitle {
|
||||||
width: 250px;
|
margin: 6px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
color: #6b7280;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-toolbar {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-toolbar label {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #4b5563;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 6px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-toolbar .form-control,
|
||||||
|
.store-toolbar .btn {
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-toolbar .form-control {
|
||||||
|
border-color: #d1d5db;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-toolbar .form-control:focus {
|
||||||
|
border-color: #9ca3af;
|
||||||
|
box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-grid {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
margin: 0 -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-grid > li {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-card {
|
||||||
|
background: #fff;
|
||||||
|
border: 1px solid #e5e7eb;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 1px 2px rgba(16, 24, 40, 0.04);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
object-fit: contain; */
|
height: 100%;
|
||||||
/* cursor: pointer; */
|
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
|
||||||
}
|
}
|
||||||
a.thumbnail>img{
|
|
||||||
height: 150px
|
.store-card:hover {
|
||||||
|
transform: translateY(-2px);
|
||||||
|
border-color: #d1d5db;
|
||||||
|
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-link {
|
||||||
|
display: block;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-link:hover,
|
||||||
|
.store-link:focus {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-logo-wrap {
|
||||||
|
height: 170px;
|
||||||
|
background: #f8fafc;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-logo {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
width: auto;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-meta {
|
||||||
|
padding: 12px;
|
||||||
|
min-height: 72px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-name {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.35;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #111827;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-lock {
|
||||||
|
color: #6b7280;
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-pagination {
|
||||||
|
margin-top: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-pagination .pagination {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.store-title {
|
||||||
|
font-size: 21px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-toolbar {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.store-logo-wrap {
|
||||||
|
height: 155px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<div class="container">
|
<div class="container store-page">
|
||||||
<div class="row">
|
<div class="row store-header">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<h2 style="font-size: 20px; font-weight: bold; ">TEAM STORES</h2>
|
<h2 class="store-title">Team Stores</h2>
|
||||||
<hr>
|
<p class="store-subtitle">Browse and open your team store.</p>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- /row -->
|
|
||||||
<div class="row">
|
|
||||||
<!-- <div class="col-sm-12"> -->
|
|
||||||
<!-- <div class="well"> -->
|
|
||||||
<form class="form-horizontal" role="search" id="frm_search_store">
|
|
||||||
<div class="col-lg-7">
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<label>Seach Store</label>
|
|
||||||
<div class="input-group">
|
|
||||||
<input type="text" class="form-control" placeholder="Search Store" value="{{ $keyword }}" name="q">
|
|
||||||
<div class="input-group-btn">
|
|
||||||
<button class="btn btn-default" type="submit"><i class="fa fa-search"></i></button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="col-lg-5">
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-7 control-label hidden-xs"> </label>
|
|
||||||
<div class="col-sm-5">
|
|
||||||
<label>Sort by:</label>
|
|
||||||
<select class="form-control" name="s" id="select_sort_stores">
|
|
||||||
<option @if($filter == "al-asc") selected @endif value="al-asc">Store Name A → Z</option>
|
|
||||||
<option @if($filter == "al-desc") selected @endif value="al-desc">Store Name Z → A</option>
|
|
||||||
<option @if($filter == "latest") selected @endif value="latest">Newest → Oldest</option>
|
|
||||||
<option @if($filter == "oldest") selected @endif value="oldest">Oldest → Newest</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
<div class="clearfix"></div>
|
|
||||||
<!-- </div> -->
|
|
||||||
<!-- </div> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="row" id="slider-thumbs">
|
|
||||||
<!-- Bottom switcher of slider -->
|
<div class="store-toolbar">
|
||||||
<ul class="hide-bullets">
|
<form role="search" id="frm_search_store">
|
||||||
@foreach ($stores_array as $store)
|
<div class="row">
|
||||||
<li class="li-custom col-lg-3 col-md-3 col-sm-4 col-xs-12">
|
<div class="col-md-8 col-sm-7">
|
||||||
<div style="border: 1px solid #dddddd; padding: 5px;">
|
<label>Search Store</label>
|
||||||
@if($store->Password != null )
|
<div class="input-group">
|
||||||
<a class="thumbnail password-protected" href="#" data-store-id="{{ $store->Id }}" data-store-url="{{ $store->StoreUrl }}">
|
<input type="text" class="form-control" placeholder="Search store" value="{{ $keyword }}" name="q">
|
||||||
<img class="store-logo" src="{{ minio_url('uploads/images/teamstore/' . $store->ImageFolder . '/' . $store->StoreLogo) }}">
|
<div class="input-group-btn">
|
||||||
</a>
|
<button class="btn btn-default" type="submit"><i class="fa fa-search"></i></button>
|
||||||
<h4 style="border-top: 1px solid #dddddd; padding: 10px; font-size: 16px; font-weight: bold; text-transform: uppercase;" class="text-center">{{ $store->StoreName }} <i class="fa fa-lock" title="This store is password protected."></i></h4>
|
</div>
|
||||||
@else
|
</div>
|
||||||
<a class="thumbnail" href="{{ url('teamstore') . '/' . $store->StoreUrl }}">
|
|
||||||
<img class="store-logo" src="{{ minio_url('uploads/images/teamstore/' . $store->ImageFolder . '/' . $store->StoreLogo) }}">
|
|
||||||
</a>
|
|
||||||
<h4 style="border-top: 1px solid #dddddd; padding: 10px; font-size: 16px; font-weight: bold; text-transform: uppercase;" class="text-center">{{ $store->StoreName }}</h4>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
</li>
|
<div class="col-md-4 col-sm-5">
|
||||||
@endforeach
|
<label>Sort by</label>
|
||||||
</ul>
|
<select class="form-control" name="s" id="select_sort_stores">
|
||||||
|
<option @if($filter == "al-asc") selected @endif value="al-asc">Store Name A → Z</option>
|
||||||
|
<option @if($filter == "al-desc") selected @endif value="al-desc">Store Name Z → A</option>
|
||||||
|
<option @if($filter == "latest") selected @endif value="latest">Newest → Oldest</option>
|
||||||
|
<option @if($filter == "oldest") selected @endif value="oldest">Oldest → Newest</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
|
||||||
|
<div id="slider-thumbs">
|
||||||
|
<ul class="store-grid row">
|
||||||
|
@foreach ($stores_array as $store)
|
||||||
|
<li class="col-lg-3 col-md-4 col-sm-6 col-xs-12">
|
||||||
|
<div class="store-card">
|
||||||
|
@if($store->Password != null)
|
||||||
|
<a class="store-link password-protected" href="#" data-store-id="{{ $store->Id }}" data-store-url="{{ $store->StoreUrl }}">
|
||||||
|
<div class="store-logo-wrap">
|
||||||
|
<img class="store-logo" src="{{ minio_url('uploads/images/teamstore/' . $store->ImageFolder . '/' . $store->StoreLogo) }}">
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
<div class="store-meta">
|
||||||
|
<h4 class="store-name">{{ $store->StoreName }} <i class="fa fa-lock store-lock" title="This store is password protected."></i></h4>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<a class="store-link" href="{{ url('teamstore') . '/' . $store->StoreUrl }}">
|
||||||
|
<div class="store-logo-wrap">
|
||||||
|
<img class="store-logo" src="{{ minio_url('uploads/images/teamstore/' . $store->ImageFolder . '/' . $store->StoreLogo) }}">
|
||||||
|
</div>
|
||||||
|
<div class="store-meta">
|
||||||
|
<h4 class="store-name">{{ $store->StoreName }}</h4>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
@endforeach
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row store-pagination">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
{!! $stores_array->render() !!}
|
{!! $stores_array->render() !!}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div><!-- /container -->
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div id="team-store-login" class="modal fade" role="dialog">
|
<div id="team-store-login" class="modal fade" role="dialog">
|
||||||
|
|||||||
Reference in New Issue
Block a user