Compare commits
17 Commits
49921a26a9
...
feat/navba
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d051276b1 | ||
| a4456631fb | |||
|
|
391df3bb41 | ||
| cf3b4ea476 | |||
|
|
04675dfd37 | ||
|
|
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
|
||||
# Copy to .env.local and update with your MinIO credentials
|
||||
# Local Development Configuration
|
||||
# Copy to .env.local and fill in your values.
|
||||
|
||||
# MinIO credentials (get from your production server)
|
||||
MINIO_KEY=secret_key
|
||||
# ── MinIO credentials ─────────────────────────────────────────────────────────
|
||||
MINIO_KEY=your_minio_root_user
|
||||
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
|
||||
# Copy to .env.local and update with your MinIO credentials
|
||||
# Local Development Configuration
|
||||
# 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_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)
|
||||
FROM php:7.0-apache
|
||||
# Use PHP 7.2 with Apache (mcrypt available via PECL, compatible with Laravel 5.0)
|
||||
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 \
|
||||
&& 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
|
||||
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 \
|
||||
libjpeg62-turbo-dev \
|
||||
openssh-client \
|
||||
libzip-dev \
|
||||
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
|
||||
&& docker-php-ext-install -j$(nproc) gd
|
||||
|
||||
# Install PHP extensions (mcrypt is built-in for PHP 7.0)
|
||||
RUN docker-php-ext-install pdo_mysql mbstring exif pcntl bcmath mcrypt tokenizer zip
|
||||
# Install mcrypt via PECL (removed from core in PHP 7.2)
|
||||
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
|
||||
RUN a2enmod rewrite
|
||||
|
||||
# Install Composer (version 1.x for better compatibility with Laravel 5.0)
|
||||
COPY --from=composer:1.10 /usr/bin/composer /usr/bin/composer
|
||||
# Install Composer 2.2 (LTS version supporting PHP 7.2+)
|
||||
COPY --from=composer:2.2 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Set working directory
|
||||
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
|
||||
RUN if [ ! -f .env ]; then cp .env.example .env; fi
|
||||
|
||||
# Install PHP dependencies (Laravel 5.0 compatible)
|
||||
RUN composer install --no-dev --no-interaction --prefer-dist
|
||||
# Install PHP dependencies (--no-plugins skips kylekatarnls/update-helper which is Composer 1 only)
|
||||
RUN composer install --no-dev --no-interaction --prefer-dist --no-plugins
|
||||
|
||||
# Generate application key
|
||||
RUN php artisan key:generate || true
|
||||
@@ -60,11 +67,7 @@ RUN php artisan key:generate || true
|
||||
# Run Laravel 5.0 optimization
|
||||
RUN php artisan clear-compiled && php artisan optimize
|
||||
|
||||
# Note: yakpro-po obfuscation requires PHP 7.1+, incompatible with PHP 7.0
|
||||
# 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)
|
||||
# Note: PHP 7.2 is compatible with Laravel 5.0 and yakpro-po obfuscation
|
||||
|
||||
# Configure Apache DocumentRoot to point to Laravel's public directory
|
||||
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);
|
||||
// var_dump($thumbs);
|
||||
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);
|
||||
Storage::disk('minio')->put('images/' . $thumbnail, file_get_contents($request->file('imgupload')[$i]->getRealPath()));
|
||||
}
|
||||
|
||||
$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);
|
||||
Storage::disk('sftp')->put($thumbnail, fopen($request->file('upload_images')[$i], 'r+')); //live
|
||||
//Storage::disk('localdir')->put($thumbnail, fopen($request->file('upload_images')[$i], 'r+'));
|
||||
|
||||
Storage::disk('minio')->put('images/' . $thumbnail, file_get_contents($request->file('upload_images')[$i]->getRealPath()));
|
||||
}
|
||||
|
||||
|
||||
@@ -826,9 +821,8 @@ class UserController extends Controller
|
||||
$id = $request->thumb_id;
|
||||
$UserModel = new UserModel;
|
||||
|
||||
$storagePath = Storage::disk('sftp')->getDriver()->getAdapter()->getPathPrefix();
|
||||
if (file_exists($storagePath . $file)) {
|
||||
unlink($storagePath . $file);
|
||||
if (Storage::disk('minio')->exists('images/' . $file)) {
|
||||
Storage::disk('minio')->delete('images/' . $file);
|
||||
}
|
||||
|
||||
$i = $UserModel->deleteImageThumb('Id', $id);
|
||||
@@ -973,11 +967,11 @@ class UserController extends Controller
|
||||
// var_dump($res);
|
||||
// if($res){
|
||||
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) {
|
||||
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(
|
||||
|
||||
@@ -4,6 +4,8 @@ use Illuminate\Support\ServiceProvider;
|
||||
use Storage;
|
||||
use League\Flysystem\Filesystem;
|
||||
use League\Flysystem\Sftp\SftpAdapter;
|
||||
use League\Flysystem\AwsS3v3\AwsS3Adapter as AwsS3v3Adapter;
|
||||
use Aws\S3\S3Client;
|
||||
|
||||
class AppServiceProvider extends ServiceProvider {
|
||||
|
||||
@@ -27,6 +29,21 @@ class AppServiceProvider extends ServiceProvider {
|
||||
Storage::extend('sftp', function ($app, $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()
|
||||
{
|
||||
// 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(
|
||||
'Illuminate\Contracts\Auth\Registrar',
|
||||
'App\Services\Registrar'
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
"google/recaptcha": "~1.1",
|
||||
"spatie/laravel-analytics": "^1.4",
|
||||
"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": {
|
||||
"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",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "9ad9cbf7c7c319c392284bef379f0004",
|
||||
"content-hash": "0320d93525d3aeed0db29b492fe6f3cc",
|
||||
"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",
|
||||
"version": "1.4.0",
|
||||
@@ -365,6 +510,190 @@
|
||||
],
|
||||
"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",
|
||||
"version": "1.1.1",
|
||||
@@ -907,6 +1236,71 @@
|
||||
],
|
||||
"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",
|
||||
"version": "1.0.22",
|
||||
@@ -1071,6 +1465,72 @@
|
||||
],
|
||||
"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",
|
||||
"version": "1.39.1",
|
||||
@@ -1409,6 +1869,59 @@
|
||||
],
|
||||
"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",
|
||||
"version": "1.1.2",
|
||||
@@ -1527,6 +2040,50 @@
|
||||
],
|
||||
"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",
|
||||
"version": "v2.7.1",
|
||||
@@ -2158,6 +2715,91 @@
|
||||
],
|
||||
"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",
|
||||
"version": "v1.12.0",
|
||||
@@ -3909,5 +4551,6 @@
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": []
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.2.0"
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ return [
|
||||
],
|
||||
|
||||
'minio' => [
|
||||
'driver' => 's3',
|
||||
'driver' => 'minio',
|
||||
'key' => env('MINIO_KEY'),
|
||||
'secret' => env('MINIO_SECRET'),
|
||||
'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:
|
||||
db:
|
||||
image: mariadb:10.6
|
||||
@@ -29,11 +45,7 @@ services:
|
||||
- APP_DEBUG=true
|
||||
- APP_URL=http://localhost:8082
|
||||
- DB_CONNECTION=mysql
|
||||
- DB_HOST=db
|
||||
- DB_PORT=3306
|
||||
- DB_DATABASE=crewsportswear
|
||||
- DB_USERNAME=crewsportswear
|
||||
- DB_PASSWORD=secret
|
||||
- DB_PORT=${DB_PORT:-3306}
|
||||
- PROD_PRIVATE=http://localhost:8082
|
||||
- IMAGES_URL=http://localhost:8082
|
||||
- UPLOAD_URL=http://localhost:8082/uploads/
|
||||
@@ -56,15 +68,65 @@ services:
|
||||
- MINIO_REGION=us-east-1
|
||||
- MINIO_USE_PATH_STYLE=false
|
||||
- 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:
|
||||
- ./:/var/www/html
|
||||
- ./storage:/var/www/html/storage
|
||||
- ./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:
|
||||
- db
|
||||
networks:
|
||||
- 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:
|
||||
image: arm64v8/phpmyadmin
|
||||
platform: linux/arm64
|
||||
@@ -87,3 +149,4 @@ networks:
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
vendor_local:
|
||||
|
||||
@@ -28,6 +28,14 @@ services:
|
||||
- ANALYTICS_SITE_ID=${ANALYTICS_SITE_ID}
|
||||
- ANALYTICS_CLIENT_ID=${ANALYTICS_CLIENT_ID}
|
||||
- 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:
|
||||
- ./storage:/var/www/html/storage
|
||||
- ./public/uploads:/var/www/html/public/uploads
|
||||
|
||||
@@ -13,5 +13,11 @@ mkdir -p bootstrap/cache
|
||||
chown -R www-data:www-data 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
|
||||
exec "$@"
|
||||
|
||||
@@ -2,58 +2,168 @@
|
||||
|
||||
@section('content')
|
||||
<style>
|
||||
.error{
|
||||
color: red;
|
||||
html,
|
||||
body {
|
||||
height: 100%;
|
||||
background: #ffffff;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
.form-wrapper{
|
||||
margin-top: 20%;
|
||||
|
||||
.navbar-custom {
|
||||
display: block;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.login-wrapper {
|
||||
height: calc(100vh - 100px);
|
||||
height: calc(100dvh - 100px);
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eceff3;
|
||||
border-radius: 12px;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.login-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 8px;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.login-subtitle {
|
||||
font-size: 14px;
|
||||
margin: 0 0 24px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.login-card .form-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.login-card label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.login-card .form-control {
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: #ffffff;
|
||||
font-size: 14px;
|
||||
padding: 0 12px;
|
||||
box-shadow: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.login-card .form-control:focus {
|
||||
border-color: #9ca3af;
|
||||
box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.login-remember-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin: 6px 0 18px;
|
||||
}
|
||||
|
||||
.login-remember-row label {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
color: #4b5563;
|
||||
}
|
||||
|
||||
.login-remember-row a {
|
||||
font-size: 13px;
|
||||
color: #4b5563;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.login-remember-row a:hover {
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.btn-login {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #111827;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn-login:hover {
|
||||
opacity: 0.92;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.login-footer {
|
||||
margin-top: 18px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.login-footer a {
|
||||
color: #111827;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.login-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#login-response-msg .alert {
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="form-wrapper">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="text-center">S I G N - I N</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div class="login-wrapper">
|
||||
<div class="login-card">
|
||||
<h1 class="login-title">Sign in</h1>
|
||||
<p class="login-subtitle">Welcome back. Please enter your details.</p>
|
||||
<div id="login-response-msg"></div>
|
||||
<form role="form" id="frm-login">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">Email Address</label>
|
||||
<input type="email" class="form-control" name="email" value="{{ old('email') }}" title="Please enter your email address" placeholder="example@gmail.com">
|
||||
<span class="help-block"></span>
|
||||
<label for="email">Email Address</label>
|
||||
<input type="email" class="form-control" name="email" id="email" value="{{ old('email') }}" placeholder="you@example.com" title="Please enter your email address">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">Password</label>
|
||||
<input type="password" class="form-control" name="password" placeholder="Password" title="Please enter your password">
|
||||
<span class="help-block"></span>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" name="password" id="password" placeholder="Enter your password" title="Please enter your password">
|
||||
</div>
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
<input type="checkbox" name="remember"> Remember login
|
||||
</label>
|
||||
<p class="help-block">(if this is a private computer)</p>
|
||||
<div class="login-remember-row">
|
||||
<label><input type="checkbox" name="remember"> Remember me</label>
|
||||
<a href="{{ url('/password/email') }}">Forgot password?</a>
|
||||
</div>
|
||||
<button type="submit" id="btn-login" class="btn btn-success btn-block"><i class="fa fa-sign-in"></i> Sign in</button>
|
||||
<a href="{{ url('/password/email') }}" class="btn btn-link btn-block">Forgot Your Password?</a>
|
||||
<button type="submit" id="btn-login" class="btn-login">Sign in</button>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<hr />
|
||||
<p class="text-center">Don't have an account? Register Now!</p>
|
||||
<a href="{{ url('/auth/register') }}" type="submit" id="btn-login" class="btn btn-primary btn-block">Register</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p class="login-footer">Don’t have an account? <a href="{{ url('/auth/register') }}">Create one</a></p>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -2,23 +2,127 @@
|
||||
|
||||
@section('content')
|
||||
<style>
|
||||
.error{
|
||||
color: red;
|
||||
body {
|
||||
background: #ffffff;
|
||||
margin: 0;
|
||||
}
|
||||
.form-wrapper{
|
||||
margin-top: 20%;
|
||||
|
||||
.navbar-custom {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.auth-wrapper {
|
||||
min-height: calc(100vh - 100px);
|
||||
min-height: calc(100dvh - 100px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eceff3;
|
||||
border-radius: 12px;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 8px;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.auth-subtitle {
|
||||
font-size: 14px;
|
||||
margin: 0 0 20px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.auth-card .form-group {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.auth-card label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.auth-card .form-control {
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: #ffffff;
|
||||
font-size: 14px;
|
||||
padding: 0 12px;
|
||||
box-shadow: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.auth-card .form-control:focus {
|
||||
border-color: #9ca3af;
|
||||
box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-auth {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #111827;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn-auth:hover {
|
||||
opacity: 0.92;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.auth-footer a {
|
||||
color: #111827;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.auth-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.auth-card .alert {
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.auth-card .alert ul {
|
||||
padding-left: 18px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="form-wrapper">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="text-center">R E S E T P A S S W O R D</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div class="auth-wrapper">
|
||||
<div class="auth-card">
|
||||
<h1 class="auth-title">Forgot password</h1>
|
||||
<p class="auth-subtitle">Enter your email and we’ll send you a reset link.</p>
|
||||
|
||||
@if (session('status'))
|
||||
<div class="alert alert-success">
|
||||
{{ session('status') }}
|
||||
@@ -35,21 +139,17 @@
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form role="form" method="POST" action="{{ url('/password/email') }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||
<div class="form-group">
|
||||
<label for="username" class="control-label">Email Address</label>
|
||||
<input type="email" class="form-control" name="email" value="{{ old('email') }}" title="Please enter your email address" placeholder="example@gmail.com">
|
||||
<span class="help-block"></span>
|
||||
<label for="email">Email Address</label>
|
||||
<input type="email" class="form-control" name="email" id="email" value="{{ old('email') }}" title="Please enter your email address" placeholder="you@example.com">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default btn-block">Send Password Reset Link</button>
|
||||
<button type="submit" class="btn-auth">Send reset link</button>
|
||||
</form>
|
||||
<br><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="auth-footer"><a href="{{ url('/auth/login') }}">Back to sign in</a></p>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -2,74 +2,194 @@
|
||||
|
||||
@section('content')
|
||||
<style>
|
||||
.error {
|
||||
color: red;
|
||||
body {
|
||||
background: #ffffff;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar-custom {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.auth-wrapper {
|
||||
min-height: calc(100vh - 100px);
|
||||
min-height: calc(100dvh - 100px);
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
padding: 24px 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 560px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eceff3;
|
||||
border-radius: 12px;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 8px;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.auth-subtitle {
|
||||
font-size: 14px;
|
||||
margin: 0 0 20px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.auth-section-title {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: #6b7280;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin: 8px 0 12px;
|
||||
}
|
||||
|
||||
.auth-card .form-group {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.auth-card label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.auth-card .form-control {
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: #ffffff;
|
||||
font-size: 14px;
|
||||
padding: 0 12px;
|
||||
box-shadow: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.auth-card .form-control:focus {
|
||||
border-color: #9ca3af;
|
||||
box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.auth-card select.form-control {
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.g-recaptcha {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.auth-terms {
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.auth-terms a {
|
||||
color: #111827;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.auth-terms a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.btn-register-modern {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #111827;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn-register-modern:hover {
|
||||
opacity: 0.92;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.auth-footer a {
|
||||
color: #111827;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.auth-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#register-response-msg .alert {
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="form-wrapper">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="text-center">R E G I S T E R</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div class="auth-wrapper">
|
||||
<div class="auth-card">
|
||||
<h1 class="auth-title">Create account</h1>
|
||||
<p class="auth-subtitle">Fill in your details to get started.</p>
|
||||
<div id="register-response-msg"></div>
|
||||
<form role="form" id="frm-register">
|
||||
<div class="form-group text-center">
|
||||
<h5>Personal Information</h5>
|
||||
</div>
|
||||
<div class="auth-section-title">Personal Information</div>
|
||||
<input type="hidden" name="redirect" value="{{ Request::get('redirectUrl') }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">First name</label>
|
||||
<label>First name</label>
|
||||
<input type="text" class="form-control" name="firstname" placeholder="First name">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Last name</label>
|
||||
<label>Last name</label>
|
||||
<input type="text" class="form-control" name="lastname" placeholder="Last name">
|
||||
</div>
|
||||
|
||||
<!-- <div class="form-group">
|
||||
<label class="control-label">Username</label>
|
||||
<input type="text" class="form-control" name="username" value="{{ old('username') }}" placeholder="Username">
|
||||
</div> -->
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Email Address</label>
|
||||
<input type="email" class="form-control" name="email" value="{{ old('email') }}" placeholder="Email Address">
|
||||
<label>Email Address</label>
|
||||
<input type="email" class="form-control" name="email" value="{{ old('email') }}" placeholder="you@example.com">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Phone Number</label>
|
||||
<label>Phone Number</label>
|
||||
<input type="text" class="form-control" name="mobilenumber" placeholder="Phone Number">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Password</label>
|
||||
<label>Password</label>
|
||||
<input type="password" class="form-control" name="password" id="password" required placeholder="Password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Confirm Password</label>
|
||||
<label>Confirm Password</label>
|
||||
<input type="password" class="form-control" name="password_confirmation" placeholder="Confirm Password" data-rule-equalTo="#password" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group text-center">
|
||||
<h5>Address Information</h5>
|
||||
</div>
|
||||
<div class="auth-section-title">Address Information</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Select Country</label>
|
||||
<label>Select Country</label>
|
||||
<select name="countryCode" id="select_country" class="form-control" onchange="selectCountry(this)">
|
||||
<option value="">Select Country</option>
|
||||
<option value="US">United States</option>
|
||||
@@ -78,60 +198,46 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">State / Province</label>
|
||||
<label></label>
|
||||
<label>State / Province</label>
|
||||
<select class="form-control" name="state" id="lst-states">
|
||||
<option value="">Select State</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">City</label>
|
||||
<label>City</label>
|
||||
<select class="form-control" name="city" id="lst-cities">
|
||||
<option value="">Select City</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Address 1</label>
|
||||
<label>Address 1</label>
|
||||
<input type="text" class="form-control" name="address" placeholder="Address 1">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Address 2</label>
|
||||
<label>Address 2</label>
|
||||
<input type="text" class="form-control" name="address2" placeholder="Address 2">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Zip Code</label>
|
||||
<label>Zip Code</label>
|
||||
<input type="text" class="form-control" name="zipcode" placeholder="Please enter your zip code">
|
||||
</div>
|
||||
|
||||
|
||||
<div class="form-group">
|
||||
<div class="g-recaptcha text-center" data-sitekey="{{ env('CAPTCHA_SITE_KEY') }}"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<p>By clicking Register, you agree to our <a href="#">Terms of Use</a> and that you have read our <a href="#">Privacy Policy</a>.</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button type="submit" id="btn-register" class="btn btn-primary btn-block">Register</button>
|
||||
</div>
|
||||
|
||||
<p class="auth-terms">By clicking Register, you agree to our <a href="#">Terms of Use</a> and that you have read our <a href="#">Privacy Policy</a>.</p>
|
||||
|
||||
<div class="form-group">
|
||||
<button type="submit" id="btn-register" class="btn-register-modern">Register</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<hr />
|
||||
<p class="text-center">Have already an account?</p>
|
||||
<a href="{{ url('/auth/login') }}" type="submit" id="btn-login" class="btn btn-success btn-block">Login here</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="auth-footer">Already have an account? <a href="{{ url('/auth/login') }}">Sign in</a></p>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -3,23 +3,127 @@
|
||||
@section('content')
|
||||
|
||||
<style>
|
||||
.error{
|
||||
color: red;
|
||||
body {
|
||||
background: #ffffff;
|
||||
margin: 0;
|
||||
}
|
||||
.form-wrapper{
|
||||
margin-top: 20%;
|
||||
|
||||
.navbar-custom {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.auth-wrapper {
|
||||
min-height: calc(100vh - 100px);
|
||||
min-height: calc(100dvh - 100px);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px 16px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.auth-card {
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
background: #ffffff;
|
||||
border: 1px solid #eceff3;
|
||||
border-radius: 12px;
|
||||
padding: 28px;
|
||||
}
|
||||
|
||||
.auth-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
line-height: 1.2;
|
||||
margin: 0 0 8px;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.auth-subtitle {
|
||||
font-size: 14px;
|
||||
margin: 0 0 20px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.auth-card .form-group {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.auth-card label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
margin-bottom: 6px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.auth-card .form-control {
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #d1d5db;
|
||||
background: #ffffff;
|
||||
font-size: 14px;
|
||||
padding: 0 12px;
|
||||
box-shadow: none;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.auth-card .form-control:focus {
|
||||
border-color: #9ca3af;
|
||||
box-shadow: 0 0 0 3px rgba(156, 163, 175, 0.2);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-auth {
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #111827;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.btn-auth:hover {
|
||||
opacity: 0.92;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.auth-footer {
|
||||
margin-top: 16px;
|
||||
text-align: center;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.auth-footer a {
|
||||
color: #111827;
|
||||
text-decoration: none;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.auth-footer a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.auth-card .alert {
|
||||
border-radius: 8px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.auth-card .alert ul {
|
||||
padding-left: 18px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
<div class="col-md-4 col-md-offset-4">
|
||||
<div class="form-wrapper">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="text-center">R E S E T P A S S W O R D</h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div class="auth-wrapper">
|
||||
<div class="auth-card">
|
||||
<h1 class="auth-title">Reset password</h1>
|
||||
<p class="auth-subtitle">Set a new password for your account.</p>
|
||||
|
||||
@if (count($errors) > 0)
|
||||
<div class="alert alert-danger">
|
||||
<strong>Whoops!</strong> There were some problems with your input.<br><br>
|
||||
@@ -30,33 +134,27 @@
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<form role="form" method="POST" action="{{ url('/password/reset') }}">
|
||||
<input type="hidden" name="_token" value="{{ csrf_token() }}">
|
||||
<input type="hidden" name="token" value="{{ $token }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label class="control-label">Email Address</label>
|
||||
<input type="email" class="form-control" name="email" value="{{ old('email') }}" title="Please enter your email address" placeholder="example@gmail.com">
|
||||
<span class="help-block"></span>
|
||||
<label for="email">Email Address</label>
|
||||
<input type="email" class="form-control" name="email" id="email" value="{{ old('email') }}" title="Please enter your email address" placeholder="you@example.com">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">Password</label>
|
||||
<input type="password" class="form-control" name="password" placeholder="Password">
|
||||
<span class="help-block"></span>
|
||||
<label for="password">Password</label>
|
||||
<input type="password" class="form-control" name="password" id="password" placeholder="New password">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="control-label">Confirm Password</label>
|
||||
<input type="password" class="form-control" name="password_confirmation" placeholder="Confirm Password">
|
||||
<span class="help-block"></span>
|
||||
<label for="password_confirmation">Confirm Password</label>
|
||||
<input type="password" class="form-control" name="password_confirmation" id="password_confirmation" placeholder="Confirm password">
|
||||
</div>
|
||||
<button type="submit" class="btn btn-default btn-block">Reset Password</button>
|
||||
<button type="submit" class="btn-auth">Reset password</button>
|
||||
</form>
|
||||
<br><br>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="auth-footer"><a href="{{ url('/auth/login') }}">Back to sign in</a></p>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@@ -21,24 +21,22 @@
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="nav navbar-nav navbar-right navbar-nav-custom">
|
||||
@if (Request::segment(2) == "hi-five-sports-club" || Request::segment(2) == "hi-five-sports-zone")
|
||||
<li style="font-size: 14px;"></li>
|
||||
<li class="nav-item-main"></li>
|
||||
@else
|
||||
<li style="font-size: 14px;">
|
||||
|
||||
<a href="{{ url('teamstore') }}"><span style="text-transform:uppercase;">Team Store</span></a>
|
||||
|
||||
<li class="nav-item-main">
|
||||
<a href="{{ url('teamstore') }}" class="nav-link-main"><span>Team Store</span></a>
|
||||
</li>
|
||||
@endif
|
||||
|
||||
<li style="font-size: 14px;">
|
||||
<a href="{{ url('cart') }}"><span style="text-transform:uppercase;">My Cart</span> <i class="fa fa-shopping-cart"></i>
|
||||
<li class="nav-item-main">
|
||||
<a href="{{ url('cart') }}" class="nav-link-main"><span>My Cart</span> <i class="fa fa-shopping-cart"></i>
|
||||
<span class="badge" id="my-cart-count">{{ \App\Http\Controllers\MainController::getCountCart() }}</span>
|
||||
</a>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="dropdown-toggle user-profile" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-bars" style="font-size: 21px;"></i></a>
|
||||
<ul class="dropdown-menu">
|
||||
<a href="#" class="dropdown-toggle user-profile nav-menu-toggle" data-toggle="dropdown" role="button" aria-expanded="false"><i class="fa fa-bars"></i></a>
|
||||
<ul class="dropdown-menu navbar-menu-dropdown">
|
||||
@if (Auth::guest())
|
||||
|
||||
@if(Request::segment(1) == "designer")
|
||||
@@ -88,6 +86,155 @@
|
||||
</nav>
|
||||
|
||||
<style>
|
||||
.navbar-custom {
|
||||
min-height: 78px;
|
||||
background: #ffffff;
|
||||
border: 0;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
box-shadow: 0 2px 8px rgba(15, 23, 42, 0.08);
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.navbar-custom .container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
height: 78px;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.navbar-brand>img {
|
||||
height: 58px;
|
||||
padding: 0;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.navbar-nav-custom > li > a.nav-link-main {
|
||||
color: #1f2937 !important;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.45px;
|
||||
border-radius: 8px;
|
||||
padding: 8px 12px;
|
||||
margin-top: 0;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.navbar-nav-custom > li > a.nav-link-main:hover,
|
||||
.navbar-nav-custom > li > a.nav-link-main:focus {
|
||||
color: #111827 !important;
|
||||
background: #f3f4f6 !important;
|
||||
}
|
||||
|
||||
.nav-menu-toggle {
|
||||
margin-top: 0 !important;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: flex !important;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #374151 !important;
|
||||
transition: background-color 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.nav-menu-toggle:hover,
|
||||
.nav-menu-toggle:focus {
|
||||
background: #f3f4f6 !important;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
.nav-menu-toggle i {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.navbar-menu-dropdown {
|
||||
margin-top: 8px;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 24px rgba(15, 23, 42, 0.14);
|
||||
}
|
||||
|
||||
.navbar-menu-dropdown > li > a {
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.navbar-menu-dropdown > li > a:hover {
|
||||
background: #f8fafc;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.navbar-menu-dropdown .dropdown-header {
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
color: #334155;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 11px;
|
||||
min-width: 24px;
|
||||
height: 24px;
|
||||
line-height: 24px;
|
||||
border-radius: 999px;
|
||||
background: #16a34a;
|
||||
padding: 0 7px;
|
||||
margin-left: 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.navbar-toggle {
|
||||
margin-top: 22px;
|
||||
border: 1px solid #d1d5db;
|
||||
}
|
||||
|
||||
.navbar-toggle .icon-bar {
|
||||
background: #374151 !important;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.navbar-nav-custom {
|
||||
min-height: 78px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.navbar-nav-custom > li {
|
||||
float: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px){
|
||||
.navbar-default .navbar-collapse,
|
||||
.navbar-default .navbar-form {
|
||||
margin-top: 12px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
background: #ffffff;
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.navbar-nav-custom > li > a.nav-link-main {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.nav-menu-toggle {
|
||||
margin-top: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 768px){
|
||||
.navbar-default .navbar-nav>li>a {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#nav{
|
||||
list-style:none;
|
||||
margin-bottom:10px;
|
||||
@@ -143,41 +290,8 @@
|
||||
background:#3d4248;
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
padding: 0px; /* firefox bug fix */
|
||||
}
|
||||
.navbar-brand>img {
|
||||
height: 200%;
|
||||
padding: 15px; /* firefox bug fix */
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.navbar-custom{
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
@media (max-width: 768px){
|
||||
.navbar-default .navbar-collapse, .navbar-default .navbar-form{
|
||||
margin-top: 50px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (min-width: 768px){
|
||||
.navbar-default .navbar-nav>li>a{
|
||||
margin-top: 23px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.badge{
|
||||
font-size: 14px;
|
||||
width:35px;
|
||||
border-radius: 0px;
|
||||
}
|
||||
|
||||
.navbar-default .navbar-nav>li>a:hover {
|
||||
background-color: #777;
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.modal {
|
||||
@@ -198,6 +312,158 @@
|
||||
text-align: left;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
#about_us_modal .modal-content {
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 14px 34px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
#about_us_modal .modal-header {
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
#about_us_modal .modal-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.about-us-content {
|
||||
color: #334155;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.about-us-intro {
|
||||
margin: 0 0 14px;
|
||||
color: #475569;
|
||||
}
|
||||
|
||||
.about-us-block {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
margin-bottom: 12px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.about-us-block h5 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.3px;
|
||||
}
|
||||
|
||||
.about-us-block p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.about-us-list {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.about-us-list li {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
#about_us_modal .modal-footer {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
background: #f8fafc;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.about-us-block {
|
||||
padding: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
#contact_us_modal .modal-content {
|
||||
border: 0;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 14px 34px rgba(0, 0, 0, 0.22);
|
||||
}
|
||||
|
||||
#contact_us_modal .modal-header {
|
||||
background: #f8fafc;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
padding: 16px 20px;
|
||||
}
|
||||
|
||||
#contact_us_modal .modal-title {
|
||||
font-size: 20px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
#contact_us_modal .modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.contact-modal-subtitle {
|
||||
margin: 0 0 16px;
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.contact-card {
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
padding: 14px;
|
||||
background: #fff;
|
||||
min-height: 140px;
|
||||
}
|
||||
|
||||
.contact-card-title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.4px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.contact-card-value {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
line-height: 1.6;
|
||||
color: #111827;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.contact-card-value a {
|
||||
color: #1d4ed8;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.contact-card-value a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
#contact_us_modal .modal-footer {
|
||||
border-top: 1px solid #e5e7eb;
|
||||
background: #f8fafc;
|
||||
padding: 12px 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
#contact_us_modal .modal-body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.contact-card {
|
||||
min-height: 0;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<!-- Privacy Modal -->
|
||||
@@ -248,7 +514,31 @@
|
||||
<h4 class="modal-title">About Us</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<p>Coming soon.</p>
|
||||
<div class="about-us-content">
|
||||
<p class="about-us-intro">Crew Sportswear helps teams, schools, and organizations bring their identity to life through high-quality custom apparel and team store experiences.</p>
|
||||
|
||||
<div class="about-us-block">
|
||||
<h5>Who We Are</h5>
|
||||
<p>We are a team focused on custom uniforms, spirit wear, and branded gear built for athletes, students, and supporters. Our goal is to make ordering team apparel simple, consistent, and reliable.</p>
|
||||
</div>
|
||||
|
||||
<div class="about-us-block">
|
||||
<h5>What We Do</h5>
|
||||
<ul class="about-us-list">
|
||||
<li>Custom team uniforms and apparel across multiple sports</li>
|
||||
<li>Print-on-demand production for flexible ordering and fulfillment</li>
|
||||
<li>Design-your-own apparel with our online designer tool</li>
|
||||
<li>Private or public online team stores for easy ordering</li>
|
||||
<li>Batch and individual order workflows depending on program needs</li>
|
||||
<li>Support for team branding, sizing, and product selection</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="about-us-block">
|
||||
<h5>Our Commitment</h5>
|
||||
<p>We’re committed to clear communication, dependable production quality, and a smooth customer experience from store launch through fulfillment.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
@@ -266,15 +556,28 @@
|
||||
<button type="button" class="close" data-dismiss="modal">×</button>
|
||||
<h4 class="modal-title">Contact Us</h4>
|
||||
</div>
|
||||
<div class="modal-body text-center">
|
||||
<h1>Contact Us</h1>
|
||||
<hr>
|
||||
<h3>Email Address: <small><a href="#">customer-service@crewsportswear.com</a></small></h3>
|
||||
<h3>Address: <small><a href="#">1281 Humbracht Circle
|
||||
Suite J
|
||||
Bartlett, Illinois
|
||||
60103</a></small>
|
||||
</h3>
|
||||
<div class="modal-body">
|
||||
<p class="contact-modal-subtitle">We’re here to help. Reach out through any of the contact options below.</p>
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="contact-card">
|
||||
<h5 class="contact-card-title">Email Address</h5>
|
||||
<p class="contact-card-value">
|
||||
<a href="mailto:customer-service@crewsportswear.com">customer-service@crewsportswear.com</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm-6">
|
||||
<div class="contact-card">
|
||||
<h5 class="contact-card-title">Address</h5>
|
||||
<p class="contact-card-value">
|
||||
1281 Humbracht Circle<br>
|
||||
Suite J<br>
|
||||
Bartlett, Illinois 60103
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
|
||||
|
||||
@@ -1,96 +1,171 @@
|
||||
<div>
|
||||
<div>Privacy Policy</div>
|
||||
<br />
|
||||
<div>Your privacy is important to us. It is Crew Sportswear's policy to respect your privacy regarding any information we may collect from you across our website, http://crewsportswear.com, and other sites we own and operate.</div>
|
||||
<br />
|
||||
<div>1. Information we collect</div>
|
||||
<br />
|
||||
<div>Log data</div>
|
||||
<br />
|
||||
<div>When you visit our website, our servers may automatically log the standard data provided by your web browser. It may include your computer’s Internet Protocol (IP) address, your browser type and version, the pages you visit, the time and date of your visit, the time spent on each page, and other details.</div>
|
||||
<br /><br />
|
||||
<div>Personal information</div>
|
||||
<br />
|
||||
<div>We may ask for personal information, such as your:</div>
|
||||
<br /><br />
|
||||
<div>NameEmailSocial media profilesDate of birthPhone/mobile numberHome/Mailing addressWork addressPayment information</div>
|
||||
<br />
|
||||
<div>2. Legal bases for processing</div>
|
||||
<br />
|
||||
<div>We will process your personal information lawfully, fairly and in a transparent manner. We collect and process information about you only where we have legal bases for doing so.</div>
|
||||
<div> </div>
|
||||
<div>These legal bases depend on the services you use and how you use them, meaning we collect and use your information only where:</div>
|
||||
<div> </div>
|
||||
<br />
|
||||
<div> it’s necessary for the performance of a contract to which you are a party or to take steps at your request before entering into such a contract (for example, when we provide a service you request from us);</div>
|
||||
<div> it satisfies a legitimate interest (which is not overridden by your data protection interests), such as for research and development, to market and promote our services, and to protect our legal rights and interests;</div>
|
||||
<div> you give us consent to do so for a specific purpose (for example, you might consent to us sending you our newsletter); or</div>
|
||||
<div> we need to process your data to comply with a legal obligation.</div>
|
||||
<br />
|
||||
<div> </div>
|
||||
<div>Where you consent to our use of information about you for a specific purpose, you have the right to change your mind at any time (but this will not affect any processing that has already taken place).</div>
|
||||
<div> </div>
|
||||
<div>We don’t keep personal information for longer than is necessary. While we retain this information, we will protect it within commercially acceptable means to prevent loss and theft, as well as unauthorised access, disclosure, copying, use or modification. That said, we advise that no method of electronic transmission or storage is 100% secure and cannot guarantee absolute data security. If necessary, we may retain your personal information for our compliance with a legal obligation or in order to protect your vital interests or the vital interests of another natural person.</div>
|
||||
<br />
|
||||
<div>3. Collection and use of information</div>
|
||||
<br />
|
||||
<div>We may collect, hold, use and disclose information for the following purposes and personal information will not be further processed in a manner that is incompatible with these purposes:</div>
|
||||
<br /><br />
|
||||
<div>to enable you to customise or personalise your experience of our website;to enable you to access and use our website, associated applications and associated social media platforms;to contact and communicate with you;for internal record keeping and administrative purposes;for analytics, market research and business development, including to operate and improve our website, associated applications and associated social media platforms;to run competitions and/or offer additional benefits to you;for advertising and marketing, including to send you promotional information about our products and services and information about third parties that we consider may be of interest to you; andto comply with our legal obligations and resolve any disputes that we may have.</div>
|
||||
<br />
|
||||
<div>4. Disclosure of personal information to third parties</div>
|
||||
<br />
|
||||
<div>We may disclose personal information to:</div>
|
||||
<br /><br />
|
||||
<div>third party service providers for the purpose of enabling them to provide their services, including (without limitation) IT service providers, data storage, hosting and server providers, ad networks, analytics, error loggers, debt collectors, maintenance or problem-solving providers, marketing or advertising providers, professional advisors and payment systems operators;our employees, contractors and/or related entities; andcredit reporting agencies, courts, tribunals and regulatory authorities, in the event you fail to pay for goods or services we have provided to you.</div>
|
||||
<br />
|
||||
<div>5. International transfers of personal information</div>
|
||||
<br />
|
||||
<div>The personal information we collect is stored and processed in United States, or where we or our partners, affiliates and third-party providers maintain facilities. By providing us with your personal information, you consent to the disclosure to these overseas third parties.</div>
|
||||
<br />
|
||||
<div>We will ensure that any transfer of personal information from countries in the European Economic Area (EEA) to countries outside the EEA will be protected by appropriate safeguards, for example by using standard data protection clauses approved by the European Commission, or the use of binding corporate rules or other legally accepted means.</div>
|
||||
<br />
|
||||
<div>Where we transfer personal information from a non-EEA country to another country, you acknowledge that third parties in other jurisdictions may not be subject to similar data protection laws to the ones in our jurisdiction. There are risks if any such third party engages in any act or practice that would contravene the data privacy laws in our jurisdiction and this might mean that you will not be able to seek redress under our jurisdiction’s privacy laws.</div>
|
||||
<br />
|
||||
<div>6. Your rights and controlling your personal information</div>
|
||||
<br />
|
||||
<div>Choice and consent: By providing personal information to us, you consent to us collecting, holding, using and disclosing your personal information in accordance with this privacy policy. If you are under 16 years of age, you must have, and warrant to the extent permitted by law to us, that you have your parent or legal guardian’s permission to access and use the website and they (your parents or guardian) have consented to you providing us with your personal information. You do not have to provide personal information to us, however, if you do not, it may affect your use of this website or the products and/or services offered on or through it.</div>
|
||||
<br />
|
||||
<div>Information from third parties: If we receive personal information about you from a third party, we will protect it as set out in this privacy policy. If you are a third party providing personal information about somebody else, you represent and warrant that you have such person’s consent to provide the personal information to us.</div>
|
||||
<br />
|
||||
<div>Restrict: You may choose to restrict the collection or use of your personal information. If you have previously agreed to us using your personal information for direct marketing purposes, you may change your mind at any time by contacting us using the details below. If you ask us to restrict or limit how we process your personal information, we will let you know how the restriction affects your use of our website or products and services.</div>
|
||||
<br />
|
||||
<div>Access and data portability: You may request details of the personal information that we hold about you. You may request a copy of the personal information we hold about you. Where possible, we will provide this information in CSV format or other easily readable machine format. You may request that we erase the personal information we hold about you at any time. You may also request that we transfer this personal information to another third party.</div>
|
||||
<br />
|
||||
<div>Correction: If you believe that any information we hold about you is inaccurate, out of date, incomplete, irrelevant or misleading, please contact us using the details below. We will take reasonable steps to correct any information found to be inaccurate, incomplete, misleading or out of date.</div>
|
||||
<br />
|
||||
<div>Notification of data breaches: We will comply laws applicable to us in respect of any data breach.</div>
|
||||
<br />
|
||||
<div>Complaints: If you believe that we have breached a relevant data protection law and wish to make a complaint, please contact us using the details below and provide us with full details of the alleged breach. We will promptly investigate your complaint and respond to you, in writing, setting out the outcome of our investigation and the steps we will take to deal with your complaint. You also have the right to contact a regulatory body or data protection authority in relation to your complaint.</div>
|
||||
<br />
|
||||
<div>Unsubscribe: To unsubscribe from our e-mail database or opt-out of communications (including marketing communications), please contact us using the details below or opt-out using the opt-out facilities provided in the communication.</div>
|
||||
<br />
|
||||
<div>7. Cookies</div>
|
||||
<br />
|
||||
<div>We use “cookies” to collect information about you and your activity across our site. A cookie is a small piece of data that our website stores on your computer, and accesses each time you visit, so we can understand how you use our site. This helps us serve you content based on preferences you have specified. Please refer to our Cookie Policy for more information.</div>
|
||||
<br />
|
||||
<div>8. Business transfers</div>
|
||||
<br />
|
||||
<div>If we or our assets are acquired, or in the unlikely event that we go out of business or enter bankruptcy, we would include data among the assets transferred to any parties who acquire us. You acknowledge that such transfers may occur, and that any parties who acquire us may continue to use your personal information according to this policy.</div>
|
||||
<br />
|
||||
<div>9. Limits of our policy</div>
|
||||
<br />
|
||||
<div>Our website may link to external sites that are not operated by us. Please be aware that we have no control over the content and policies of those sites, and cannot accept responsibility or liability for their respective privacy practices.</div>
|
||||
<br />
|
||||
<div>10. Changes to this policy</div>
|
||||
<br />
|
||||
<div>At our discretion, we may change our privacy policy to reflect current acceptable practices. We will take reasonable steps to let users know about changes via our website. Your continued use of this site after any changes to this policy will be regarded as acceptance of our practices around privacy and personal information.</div>
|
||||
<br />
|
||||
<div>If we make a significant change to this privacy policy, for example changing a lawful basis on which we process your personal information, we will ask you to re-consent to the amended privacy policy.</div>
|
||||
<br /><br />
|
||||
<div>Crew Sportswear Data Controller</div>
|
||||
<div>Angelo Garcia</div>
|
||||
<div>angelo@crewsportswear.com</div>
|
||||
<br /><br /><br />
|
||||
<div>This policy is effective as of 23 February 2019.</div>
|
||||
<style>
|
||||
.privacy-policy {
|
||||
color: #334155;
|
||||
font-size: 14px;
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.privacy-title {
|
||||
margin: 0 0 6px;
|
||||
font-size: 26px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.privacy-intro {
|
||||
margin: 0 0 18px;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.privacy-section {
|
||||
background: #ffffff;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 10px;
|
||||
padding: 14px 16px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.privacy-section h5 {
|
||||
margin: 0 0 8px;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
|
||||
.privacy-section p {
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
.privacy-section p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.privacy-section ul {
|
||||
margin: 0 0 10px 18px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.privacy-section li {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.privacy-footnote {
|
||||
margin-top: 12px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
padding-top: 12px;
|
||||
font-size: 13px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.privacy-contact {
|
||||
background: #f8fafc;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="privacy-policy">
|
||||
<h3 class="privacy-title">Privacy Policy</h3>
|
||||
<p class="privacy-intro">Your privacy is important to us. It is Crew Sportswear's policy to respect your privacy regarding any information we may collect from you across our website, http://crewsportswear.com, and other sites we own and operate.</p>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>1. Information we collect</h5>
|
||||
<p><strong>Log data:</strong> When you visit our website, our servers may automatically log the standard data provided by your web browser. It may include your computer’s Internet Protocol (IP) address, your browser type and version, the pages you visit, the time and date of your visit, the time spent on each page, and other details.</p>
|
||||
<p><strong>Personal information:</strong> We may ask for personal information, such as your:</p>
|
||||
<ul>
|
||||
<li>Name</li>
|
||||
<li>Email</li>
|
||||
<li>Social media profiles</li>
|
||||
<li>Date of birth</li>
|
||||
<li>Phone/mobile number</li>
|
||||
<li>Home/Mailing address</li>
|
||||
<li>Work address</li>
|
||||
<li>Payment information</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>2. Legal bases for processing</h5>
|
||||
<p>We will process your personal information lawfully, fairly and in a transparent manner. We collect and process information about you only where we have legal bases for doing so.</p>
|
||||
<p>These legal bases depend on the services you use and how you use them, meaning we collect and use your information only where:</p>
|
||||
<ul>
|
||||
<li>it’s necessary for the performance of a contract to which you are a party or to take steps at your request before entering into such a contract;</li>
|
||||
<li>it satisfies a legitimate interest (which is not overridden by your data protection interests), such as research and development, marketing and legal protection;</li>
|
||||
<li>you give us consent to do so for a specific purpose; or</li>
|
||||
<li>we need to process your data to comply with a legal obligation.</li>
|
||||
</ul>
|
||||
<p>Where you consent to our use of information about you for a specific purpose, you have the right to change your mind at any time (but this will not affect any processing that has already taken place).</p>
|
||||
<p>We don’t keep personal information for longer than is necessary. While we retain this information, we will protect it within commercially acceptable means to prevent loss and theft, as well as unauthorised access, disclosure, copying, use or modification. That said, we advise that no method of electronic transmission or storage is 100% secure and cannot guarantee absolute data security.</p>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>3. Collection and use of information</h5>
|
||||
<p>We may collect, hold, use and disclose information for the following purposes and personal information will not be further processed in a manner that is incompatible with these purposes:</p>
|
||||
<ul>
|
||||
<li>to enable you to customise or personalise your experience of our website;</li>
|
||||
<li>to enable you to access and use our website, associated applications and associated social media platforms;</li>
|
||||
<li>to contact and communicate with you;</li>
|
||||
<li>for internal record keeping and administrative purposes;</li>
|
||||
<li>for analytics, market research and business development;</li>
|
||||
<li>to run competitions and/or offer additional benefits;</li>
|
||||
<li>for advertising and marketing communications; and</li>
|
||||
<li>to comply with legal obligations and resolve disputes.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>4. Disclosure of personal information to third parties</h5>
|
||||
<p>We may disclose personal information to:</p>
|
||||
<ul>
|
||||
<li>third party service providers (including IT, hosting, analytics, marketing, advisors, and payment providers);</li>
|
||||
<li>our employees, contractors and/or related entities; and</li>
|
||||
<li>credit reporting agencies, courts, tribunals and regulatory authorities where required.</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>5. International transfers of personal information</h5>
|
||||
<p>The personal information we collect is stored and processed in United States, or where we or our partners, affiliates and third-party providers maintain facilities. By providing us with your personal information, you consent to disclosure to these overseas third parties.</p>
|
||||
<p>We will ensure that any transfer of personal information from countries in the European Economic Area (EEA) to countries outside the EEA is protected by appropriate safeguards.</p>
|
||||
<p>Where we transfer personal information from a non-EEA country to another country, you acknowledge that third parties in other jurisdictions may not be subject to similar data protection laws.</p>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>6. Your rights and controlling your personal information</h5>
|
||||
<p><strong>Choice and consent:</strong> By providing personal information to us, you consent to us collecting, holding, using and disclosing your personal information in accordance with this privacy policy.</p>
|
||||
<p><strong>Information from third parties:</strong> If we receive personal information about you from a third party, we will protect it as set out in this privacy policy.</p>
|
||||
<p><strong>Restrict:</strong> You may choose to restrict the collection or use of your personal information.</p>
|
||||
<p><strong>Access and data portability:</strong> You may request details or a copy of the personal information we hold about you, and request transfer to another third party where possible.</p>
|
||||
<p><strong>Correction:</strong> If you believe any information we hold is inaccurate or out of date, please contact us so we can correct it.</p>
|
||||
<p><strong>Notification of data breaches:</strong> We will comply with laws applicable to us in respect of any data breach.</p>
|
||||
<p><strong>Complaints:</strong> If you believe we have breached a relevant data protection law, please contact us with full details and we will promptly investigate.</p>
|
||||
<p><strong>Unsubscribe:</strong> To unsubscribe from communications, contact us using the details below or use opt-out facilities provided in communications.</p>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>7. Cookies</h5>
|
||||
<p>We use “cookies” to collect information about you and your activity across our site. A cookie is a small piece of data that our website stores on your computer and accesses each time you visit. Please refer to our Cookie Policy for more information.</p>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>8. Business transfers</h5>
|
||||
<p>If we or our assets are acquired, or if we go out of business or enter bankruptcy, data may be transferred to parties who acquire us. Any acquiring parties may continue to use your personal information according to this policy.</p>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>9. Limits of our policy</h5>
|
||||
<p>Our website may link to external sites that are not operated by us. We have no control over the content and policies of those sites and cannot accept responsibility or liability for their respective privacy practices.</p>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section">
|
||||
<h5>10. Changes to this policy</h5>
|
||||
<p>At our discretion, we may change our privacy policy to reflect current acceptable practices. We will take reasonable steps to let users know about changes via our website. Your continued use of this site after changes to this policy will be regarded as acceptance of our practices around privacy and personal information.</p>
|
||||
<p>If we make a significant change to this privacy policy, such as changing a lawful basis on which we process your personal information, we will ask you to re-consent to the amended privacy policy.</p>
|
||||
</div>
|
||||
|
||||
<div class="privacy-section privacy-contact">
|
||||
<h5>Crew Sportswear Data Controller</h5>
|
||||
<p>Angelo Garcia</p>
|
||||
<p><a href="mailto:angelo@crewsportswear.com">angelo@crewsportswear.com</a></p>
|
||||
</div>
|
||||
|
||||
<p class="privacy-footnote">This policy is effective as of 23 February 2019.</p>
|
||||
</div>
|
||||
@@ -7,14 +7,17 @@
|
||||
@endif
|
||||
|
||||
<style>
|
||||
body {
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
h2 {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 40px;
|
||||
|
||||
margin-top: 34px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
/* h2:after {
|
||||
display: inline-block;
|
||||
@@ -41,6 +44,15 @@
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.featured-title {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.6px;
|
||||
color: #111827;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.price{
|
||||
font-size: 25px;
|
||||
margin: 0 auto;
|
||||
@@ -51,19 +63,107 @@
|
||||
border-bottom: 2px solid #4B8E4B;
|
||||
}
|
||||
.thumbnail{
|
||||
/* opacity:0.70; */
|
||||
-webkit-transition: all 0.5s;
|
||||
transition: all 0.5s;
|
||||
}
|
||||
.thumbnail:hover{
|
||||
opacity:1.00;
|
||||
box-shadow: 0px 0px 10px #4bc6ff;
|
||||
}
|
||||
.line{
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.thumbnail>img{
|
||||
height:201.84px;
|
||||
.products-grid {
|
||||
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) {
|
||||
.right{
|
||||
@@ -127,6 +227,164 @@
|
||||
overflow: hidden;
|
||||
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>
|
||||
|
||||
<div class="container">
|
||||
@@ -147,19 +405,99 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<h2>FEATURED PRODUCTS</h2>
|
||||
<h2 class="featured-title">Featured Products</h2>
|
||||
</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)
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert-info">
|
||||
<p><b>Shop Announcements:</b></p>
|
||||
{!! nl2br(e($announcement->Announcement)) !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@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)
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert-warning">
|
||||
<p><b>Please read:</b></p>
|
||||
@@ -172,58 +510,172 @@
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@else
|
||||
<div class="col-md-12">
|
||||
<div class="alert alert-warning">
|
||||
<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>
|
||||
2. Store payments are processed through PayPal. A PayPal account is not required to make a purchase.<br>
|
||||
@if($store_array[0]->Id == 222)
|
||||
3. Orders will be batch processed on a monthly basis, please allow 4-6 weeks for delivery.<br>
|
||||
@else
|
||||
3. Orders will be batch processed on a weekly basis, please allow 2-3 weeks for delivery.<br>
|
||||
@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 }}" >
|
||||
{{-- ── Product grid ── --}}
|
||||
<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>
|
||||
<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 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 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>
|
||||
@endif
|
||||
@endforeach
|
||||
<!-- END PRODUCTS -->
|
||||
</div>{{-- /ts-app --}}
|
||||
|
||||
</div>
|
||||
</div> <!-- cotainer -->
|
||||
<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
|
||||
|
||||
@@ -7,6 +7,45 @@
|
||||
@endif
|
||||
<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{
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
@@ -30,7 +69,8 @@
|
||||
box-shadow: 0px 0px 10px #4bc6ff;
|
||||
} */
|
||||
.line{
|
||||
margin-bottom: 5px;
|
||||
margin: 14px 0;
|
||||
border-color: #e5e7eb;
|
||||
}
|
||||
@media screen and (max-width: 770px) {
|
||||
.right{
|
||||
@@ -117,18 +157,45 @@
|
||||
border: none;
|
||||
}
|
||||
|
||||
.carousel-control.left {
|
||||
margin-left: -35px;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.carousel-control.right {
|
||||
margin-right: -35px;
|
||||
margin-top: 7px;
|
||||
#myCarousel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.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,
|
||||
@@ -139,11 +206,127 @@
|
||||
|
||||
.hide-bullets {
|
||||
list-style:none;
|
||||
margin-left: -40px;
|
||||
margin-top:20px;
|
||||
margin-left: 0;
|
||||
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{
|
||||
margin-top: 40px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
.roster-input{
|
||||
border-radius: 0px;
|
||||
@@ -208,12 +391,49 @@
|
||||
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>
|
||||
|
||||
<div class="container">
|
||||
<div class="container product-page">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<nav aria-label="breadcrumb" class="product-breadcrumb">
|
||||
<ol class="breadcrumb">
|
||||
<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>
|
||||
@@ -224,23 +444,23 @@
|
||||
<div class="row">
|
||||
|
||||
<div class="col-md-5 col-sm-5">
|
||||
<div class="row">
|
||||
<div class="col-sm-12" id="carousel-bounding-box">
|
||||
<div class="main-gallery-card">
|
||||
<div id="carousel-bounding-box">
|
||||
<div class="main-image-stage">
|
||||
<div class="carousel slide" id="myCarousel" data-interval="false">
|
||||
<!-- Carousel items -->
|
||||
<div class="carousel-inner">
|
||||
@define $i = 0
|
||||
@foreach($thumbnails_array as $thumbnail)
|
||||
@if($thumbnail->ImageClass == 'active')
|
||||
<div class="active item text-center" data-slide-number="{{ $i }}">
|
||||
<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>
|
||||
</div>
|
||||
@else
|
||||
<div class="item text-center" data-slide-number="{{ $i }}">
|
||||
<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>
|
||||
</div>
|
||||
@endif
|
||||
@@ -248,7 +468,6 @@
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<!-- Carousel nav -->
|
||||
<a class="left carousel-control" href="#myCarousel" role="button" data-slide="prev">
|
||||
<span class="glyphicon glyphicon-chevron-left"></span>
|
||||
</a>
|
||||
@@ -257,14 +476,11 @@
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
|
||||
<hr class="line">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<ul class="hide-bullets">
|
||||
<ul class="hide-bullets row">
|
||||
@define $j = 0
|
||||
@foreach($thumbnails_array as $thumbnail)
|
||||
<li class="col-sm-3 col-xs-3">
|
||||
@@ -276,16 +492,14 @@
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="col-md-7 col-sm-7">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h1>{{ $product_array[0]->ProductName }}</h1> <p class="price">{{ $product_array[0]->ProductPrice }} <small> {{ $store_array[0]->StoreCurrency }} </small></p>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="product-info-card">
|
||||
<h1 class="product-title">{{ $product_array[0]->ProductName }}</h1>
|
||||
<p class="price">{{ $product_array[0]->ProductPrice }} <small> {{ $store_array[0]->StoreCurrency }} </small></p>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<form id="frm-order-list">
|
||||
@@ -300,13 +514,18 @@
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="description-card">
|
||||
<h3 class="description-title">Description</h3>
|
||||
<p class="description-text">{{ $product_array[0]->ProductDescription }}</p>
|
||||
</div>
|
||||
|
||||
<div class="spacer-top"></div>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#productDescription" aria-controls="productDescription" role="tab" data-toggle="tab">Description</a></li>
|
||||
<li role="presentation" class="active"><a href="#productDescription" aria-controls="productDescription" role="tab" data-toggle="tab">More Details</a></li>
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane active" id="productDescription">
|
||||
<div class="row">
|
||||
@@ -320,7 +539,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div> <!-- container -->
|
||||
</div>
|
||||
|
||||
@@ -2,64 +2,188 @@
|
||||
@section('content')
|
||||
|
||||
<style>
|
||||
a.thumbnail>img {
|
||||
/* height: 250px; */
|
||||
body {
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
.hide-bullets {
|
||||
list-style:none;
|
||||
margin-left: -40px;
|
||||
margin-top:20px;
|
||||
position: relative;
|
||||
.store-page {
|
||||
padding-top: 14px;
|
||||
padding-bottom: 26px;
|
||||
}
|
||||
.thumbnail{
|
||||
border: none;
|
||||
display: unset;
|
||||
background-color: transparent;
|
||||
|
||||
.store-header {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.li-custom{
|
||||
padding:10px;
|
||||
|
||||
.store-title {
|
||||
margin: 0;
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: #111827;
|
||||
}
|
||||
.store-logo{
|
||||
/* height: 250px;
|
||||
width: 250px;
|
||||
|
||||
.store-subtitle {
|
||||
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;
|
||||
object-fit: contain; */
|
||||
/* cursor: pointer; */
|
||||
height: 100%;
|
||||
transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
a.thumbnail>img{
|
||||
height: 150px
|
||||
}
|
||||
</style>
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="container store-page">
|
||||
<div class="row store-header">
|
||||
<div class="col-lg-12">
|
||||
<h2 style="font-size: 20px; font-weight: bold; ">TEAM STORES</h2>
|
||||
<hr>
|
||||
<h2 class="store-title">Team Stores</h2>
|
||||
<p class="store-subtitle">Browse and open your team store.</p>
|
||||
</div>
|
||||
</div><!-- /row -->
|
||||
</div>
|
||||
|
||||
<div class="store-toolbar">
|
||||
<form role="search" id="frm_search_store">
|
||||
<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="col-md-8 col-sm-7">
|
||||
<label>Search Store</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Search Store" value="{{ $keyword }}" name="q">
|
||||
<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>
|
||||
<div class="col-md-4 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>
|
||||
@@ -68,42 +192,47 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
<!-- </div> -->
|
||||
<!-- </div> -->
|
||||
</div>
|
||||
<div class="row" id="slider-thumbs">
|
||||
<!-- Bottom switcher of slider -->
|
||||
<ul class="hide-bullets">
|
||||
|
||||
<div id="slider-thumbs">
|
||||
<ul class="store-grid row">
|
||||
@foreach ($stores_array as $store)
|
||||
<li class="li-custom col-lg-3 col-md-3 col-sm-4 col-xs-12">
|
||||
<div style="border: 1px solid #dddddd; padding: 5px;">
|
||||
@if($store->Password != null )
|
||||
<a class="thumbnail password-protected" href="#" data-store-id="{{ $store->Id }}" data-store-url="{{ $store->StoreUrl }}">
|
||||
<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>
|
||||
<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 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="thumbnail" href="{{ url('teamstore') . '/' . $store->StoreUrl }}">
|
||||
<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>
|
||||
<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>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
<div class="row">
|
||||
|
||||
<div class="row store-pagination">
|
||||
<div class="col-sm-12">
|
||||
<div class="text-center">
|
||||
{!! $stores_array->render() !!}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div><!-- /container -->
|
||||
</div>
|
||||
|
||||
|
||||
<div id="team-store-login" class="modal fade" role="dialog">
|
||||
|
||||
Reference in New Issue
Block a user