diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000000..d8d2c8b384 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,184 @@ +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: | + # Increase MAX_RETRIES or SLEEP_TIME if your deployments take longer + MAX_RETRIES=30 + 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 af088f60a9..66a71cd446 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@
-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 🎉')}}
-