diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000000..d238581275
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,183 @@
+name: Deploy to Vapor and Frontend
+
+on:
+ push:
+ branches:
+ - main
+ - develop
+ workflow_dispatch:
+ inputs:
+ environment:
+ description: 'Environment to deploy to'
+ required: true
+ default: 'staging'
+ type: choice
+ options:
+ - staging
+ - production
+ test_mode:
+ description: 'Run in test mode (no actual deployment)'
+ required: false
+ default: false
+ type: boolean
+
+jobs:
+ backend:
+ name: Deploy Backend
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.3
+ tools: composer:v2
+ coverage: none
+
+ - name: Prepare Laravel Environment
+ working-directory: ./backend
+ run: |
+ mkdir -p bootstrap/cache
+ chmod -R 775 bootstrap/cache
+
+ - name: Prepare HTMLPurifier Cache Directory
+ working-directory: ./backend
+ run: |
+ mkdir -p storage/app/htmlpurifier
+ chmod -R 775 storage/app/htmlpurifier
+
+ - name: Install Dependencies
+ working-directory: ./backend
+ run: composer install --no-dev --no-progress --no-scripts --optimize-autoloader
+
+ - name: Install Vapor CLI
+ run: composer global require laravel/vapor-cli
+
+ - name: Set Deployment Environment
+ run: |
+ if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
+ echo "VAPOR_ENV=${{ github.event.inputs.environment }}" >> "$GITHUB_ENV"
+ echo "TEST_MODE=${{ github.event.inputs.test_mode }}" >> "$GITHUB_ENV"
+ elif [[ "${{ github.ref_name }}" == "develop" ]]; then
+ echo "VAPOR_ENV=staging" >> "$GITHUB_ENV"
+ echo "TEST_MODE=false" >> "$GITHUB_ENV"
+ else
+ echo "VAPOR_ENV=production" >> "$GITHUB_ENV"
+ echo "TEST_MODE=false" >> "$GITHUB_ENV"
+ fi
+
+ - name: Log Branch and Environment
+ run: |
+ echo "🚀 Deploying branch ${{ github.ref_name }} to Vapor environment: ${{ env.VAPOR_ENV }}"
+ echo "🧪 Test mode: ${{ env.TEST_MODE }}"
+
+ - name: Validate Deployment Configuration
+ working-directory: ./backend
+ run: |
+ if [[ "${{ env.TEST_MODE }}" == "true" ]]; then
+ echo "✅ TEST MODE: Would deploy to ${{ env.VAPOR_ENV }} environment"
+ echo "vapor deploy ${{ env.VAPOR_ENV }} --dry-run"
+ exit 0
+ fi
+
+ - name: Deploy to Vapor
+ working-directory: ./backend
+ run: vapor deploy ${{ env.VAPOR_ENV }}
+ env:
+ VAPOR_API_TOKEN: ${{ secrets.VAPOR_API_TOKEN }}
+
+ frontend:
+ name: Deploy Frontend
+ runs-on: ubuntu-latest
+ needs: backend
+
+ steps:
+ - uses: actions/checkout@v3
+
+ - name: Set Deployment Environment
+ run: |
+ if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
+ if [[ "${{ github.event.inputs.environment }}" == "staging" ]]; then
+ echo "DO_APP_ID=${{ secrets.DIGITALOCEAN_STAGING_APP_ID }}" >> "$GITHUB_ENV"
+ else
+ echo "DO_APP_ID=${{ secrets.DIGITALOCEAN_PRODUCTION_APP_ID }}" >> "$GITHUB_ENV"
+ fi
+ echo "TEST_MODE=${{ github.event.inputs.test_mode }}" >> "$GITHUB_ENV"
+ elif [[ "${{ github.ref_name }}" == "develop" ]]; then
+ echo "DO_APP_ID=${{ secrets.DIGITALOCEAN_STAGING_APP_ID }}" >> "$GITHUB_ENV"
+ echo "TEST_MODE=false" >> "$GITHUB_ENV"
+ else
+ echo "DO_APP_ID=${{ secrets.DIGITALOCEAN_PRODUCTION_APP_ID }}" >> "$GITHUB_ENV"
+ echo "TEST_MODE=false" >> "$GITHUB_ENV"
+ fi
+
+ - name: Log Environment Settings
+ run: |
+ echo "🚀 Deploying frontend to DigitalOcean App: ${{ env.DO_APP_ID }}"
+ echo "🧪 Test mode: ${{ env.TEST_MODE }}"
+
+ - name: Validate Deployment Configuration (Test Mode)
+ if: env.TEST_MODE == 'true'
+ run: |
+ echo "✅ TEST MODE: Would trigger deployment for DigitalOcean App: ${{ env.DO_APP_ID }}"
+ echo "curl -X POST 'https://api.digitalocean.com/v2/apps/${{ env.DO_APP_ID }}/deployments'"
+ exit 0
+
+ - name: Trigger Deployment on DigitalOcean
+ if: env.TEST_MODE != 'true'
+ id: trigger_deployment
+ run: |
+ RESPONSE=$(curl -s -o response.json -w "%{http_code}" -X POST "https://api.digitalocean.com/v2/apps/$DO_APP_ID/deployments" \
+ -H "Authorization: Bearer ${{ secrets.DIGITALOCEAN_API_TOKEN }}" \
+ -H "Content-Type: application/json")
+
+ if [ "$RESPONSE" -ne 201 ] && [ "$RESPONSE" -ne 200 ]; then
+ ERROR_MSG=$(jq -r '.message // "Unknown error occurred."' response.json)
+ echo "❌ Failed to trigger deployment. HTTP Status: $RESPONSE. Error: $ERROR_MSG"
+ exit 1
+ fi
+
+ DEPLOYMENT_ID=$(jq -r '.deployment.id' response.json)
+ if [ "$DEPLOYMENT_ID" == "null" ]; then
+ echo "❌ Failed to extract deployment ID."
+ exit 1
+ fi
+
+ echo "::add-mask::$DEPLOYMENT_ID"
+ echo "✅ Deployment triggered successfully."
+
+ echo "deployment_id=$DEPLOYMENT_ID" >> "$GITHUB_ENV"
+
+ - name: Poll Deployment Status
+ if: env.TEST_MODE != 'true'
+ run: |
+ MAX_RETRIES=60
+ SLEEP_TIME=10
+ COUNTER=0
+
+ while [ $COUNTER -lt $MAX_RETRIES ]; do
+ RESPONSE=$(curl -s -X GET "https://api.digitalocean.com/v2/apps/$DO_APP_ID/deployments/${{ env.deployment_id }}" \
+ -H "Authorization: Bearer ${{ secrets.DIGITALOCEAN_API_TOKEN }}" \
+ -H "Content-Type: application/json")
+
+ STATUS=$(echo "$RESPONSE" | jq -r '.deployment.phase')
+
+ echo "🔄 Deployment Status: $STATUS"
+
+ if [ "$STATUS" == "ACTIVE" ]; then
+ echo "✅ Deployment completed successfully."
+ exit 0
+ elif [[ "$STATUS" == "FAILED" || "$STATUS" == "CANCELLED" ]]; then
+ echo "❌ Deployment failed or was cancelled."
+ exit 1
+ fi
+
+ COUNTER=$((COUNTER + 1))
+ echo "⏳ Retrying in $SLEEP_TIME seconds... ($COUNTER/$MAX_RETRIES)"
+ sleep $SLEEP_TIME
+ done
+
+ echo "⏰ Deployment timed out."
+ exit 1
diff --git a/.gitignore b/.gitignore
index 4babc6f47d..b9e16bf8e7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@
frontend/.env
backend/.env
todo.md
+
+.vercel
\ No newline at end of file
diff --git a/Dockerfile.all-in-one b/Dockerfile.all-in-one
index 60bf629a44..5b72b734e5 100644
--- a/Dockerfile.all-in-one
+++ b/Dockerfile.all-in-one
@@ -21,17 +21,19 @@ RUN apk add --no-cache nodejs yarn nginx supervisor
COPY --from=node-frontend /app/frontend /app/frontend
COPY ./backend /app/backend
-RUN chown -R www-data:www-data /app/backend \
+RUN mkdir -p /app/backend/bootstrap/cache \
+ && mkdir -p /app/backend/storage \
+ && chown -R www-data:www-data /app/backend \
&& find /app/backend -type d -exec chmod 755 {} \; \
&& find /app/backend -type f -exec chmod 644 {} \; \
- && chmod -R 777 /app/backend/storage /app/backend/bootstrap/cache \
+ && chmod -R 755 /app/backend/storage /app/backend/bootstrap/cache \
&& composer install --working-dir=/app/backend \
--ignore-platform-reqs \
--no-interaction \
--no-dev \
--optimize-autoloader \
--prefer-dist \
- && chmod -R 777 /app/backend/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer
+ && chmod -R 755 /app/backend/vendor/ezyang/htmlpurifier/library/HTMLPurifier/DefinitionCache/Serializer
COPY ./docker/all-in-one/nginx/nginx.conf /etc/nginx/nginx.conf
COPY ./docker/all-in-one/supervisor/supervisord.conf /etc/supervisord.conf
diff --git a/README.md b/README.md
index 4b511adef0..c6e5a54090 100644
--- a/README.md
+++ b/README.md
@@ -6,11 +6,10 @@
-Demo Event 🌟 • Website 🌎 • Documentation 📄 • Installation ⚙️ +Demo Event 🌟 • Website 🌎 • Documentation 📄 • Installation ⚙️
+
++{{ __('ℹ️ Your order is pending payment. Tickets have been issued but will not be valid until payment is received.') }} +
++{{ __('ℹ️ This order is pending payment. Please mark the payment as received on the order management page once payment is received.') }} +
+| {{ __('Ticket') }} | -{{ __('Price') }} | -{{ __('Total') }} | -|
| - {{ $ticket->getItemName() }} x {{ $ticket->getQuantity()}} - | -{{ Currency::format($ticket->getPrice() * $ticket->getQuantity(), $event->getCurrency()) }} | -||
| - {{ __('Total') }} - | -- {{ Currency::format($order->getTotalGross(), $event->getCurrency()) }} - | -||
{{ __('Congratulations! Your order for :eventTitle on :eventDate at :eventTime was successful. Please find your order details below.', ['eventTitle' => $event->getTitle(), 'eventDate' => (new Carbon(DateHelper::convertFromUTC($event->getStartDate(), $event->getTimezone())))->format('F j, Y'), 'eventTime' => (new Carbon(DateHelper::convertFromUTC($event->getStartDate(), $event->getTimezone())))->format('g:i A')]) }}
+@else ++{{ __('Your order is pending payment. Tickets have been issued but will not be valid until payment is received.') }} +
+ +diff --git a/backend/resources/views/invoice.blade.php b/backend/resources/views/invoice.blade.php new file mode 100644 index 0000000000..86aa461ad7 --- /dev/null +++ b/backend/resources/views/invoice.blade.php @@ -0,0 +1,390 @@ +@php use Carbon\Carbon; @endphp +@php use HiEvents\Helper\Currency; @endphp +@php /** @var \HiEvents\DomainObjects\EventDomainObject $event */ @endphp +@php /** @var \HiEvents\DomainObjects\EventSettingDomainObject $eventSettings */ @endphp +@php /** @var \HiEvents\DomainObjects\OrderDomainObject $order */ @endphp +@php /** @var \HiEvents\DomainObjects\InvoiceDomainObject $invoice */ @endphp + + + +
+ + +| + {{ __('Invoice Number') }} + #{{ $invoice->getInvoiceNumber() }} + | ++ {{ __('Date Issued') }} + {{ Carbon::parse($order->getCreatedAt())->format('d/m/Y') }} + | + @if($invoice->getDueDate()) ++ {{ __('Due Date') }} + {{ Carbon::parse($invoice->getDueDate())->format('d/m/Y') }} + | + @endif ++ {{ __('Amount Due') }} + {{ Currency::format($order->getTotalGross(), $order->getCurrency()) }} + | +
| {{ __('DESCRIPTION') }} | +{{ __('RATE') }} | +{{ __('QTY') }} | +{{ __('AMOUNT') }} | +
|---|---|---|---|
|
+ {{ $orderItem['item_name'] }}
+ @if(!empty($orderItem['description']))
+ {{ $orderItem['description'] }}
+ @endif
+ |
+
+ @if($orderItem['price_before_discount'])
+ {{ Currency::format($orderItem['price_before_discount'], $order->getCurrency()) }}
+ {{ Currency::format($orderItem['price'], $order->getCurrency()) }}
+ @else
+ {{ Currency::format($orderItem['price'], $order->getCurrency()) }}
+ @endif
+ |
+ {{ $orderItem['quantity'] }} | +
+ @if($orderItem['price_before_discount'])
+ {{ Currency::format($orderItem['price_before_discount'] * $orderItem['quantity'], $order->getCurrency()) }}
+ {{ Currency::format($orderItem['total_before_additions'], $order->getCurrency()) }}
+ @else
+ {{ Currency::format($orderItem['total_before_additions'], $order->getCurrency()) }}
+ @endif
+ |
+
| {{ __('Subtotal') }} | +{{ Currency::format($order->getTotalBeforeAdditions(), $order->getCurrency()) }} | +
| {{ __('Total Discount') }} | +-{{ Currency::format($totalDiscount, $order->getCurrency()) }} | +
| {{ $tax['name'] }} ({{ $tax['rate'] }}@if($tax['type'] === 'PERCENTAGE') + % + @else + {{ $order->getCurrency() }} + @endif) | +{{ Currency::format($tax['value'], $order->getCurrency()) }} | +
| {{ __('Total Tax') }} | +{{ Currency::format($order->getTotalTax(), $order->getCurrency()) }} | +
| {{ $fee['name'] }} ({{ $fee['rate'] }}@if($fee['type'] === 'PERCENTAGE') + % + @else + {{ $order->getCurrency() }} + @endif) | +{{ Currency::format($fee['value'], $order->getCurrency()) }} | +
| {{ __('Total Service Fee') }} | +{{ Currency::format($order->getTotalFee(), $order->getCurrency()) }} | +
| {{ __('Total Amount') }} | +{{ Currency::format($order->getTotalGross(), $order->getCurrency()) }} | +
{{__('Congratulations 🎉')}}
-