diff --git a/.forgejo/workflows/docker-build.yml b/.forgejo/workflows/docker-build.yml new file mode 100644 index 0000000..46f554d --- /dev/null +++ b/.forgejo/workflows/docker-build.yml @@ -0,0 +1,44 @@ +name: Build and Push Docker Image + +on: + push: + tags: [v*] + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Codeberg Container Registry + uses: docker/login-action@v3 + with: + registry: codeberg.org + username: ${{ secrets.CODEBERG_USERNAME }} + password: ${{ secrets.CODEBERG_TOKEN }} + + - name: Extract metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: codeberg.org/lvl0/incr + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest + + - name: Build and push Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max \ No newline at end of file diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..9d46aca --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,84 @@ +# Multi-stage build for Laravel + React application +FROM node:20-alpine AS frontend-builder + +WORKDIR /app + +# Copy package files +COPY package*.json ./ + +# Install Node dependencies +RUN npm ci --only=production + +# Copy frontend source +COPY resources/ resources/ +COPY public/ public/ +COPY vite.config.ts ./ +COPY tsconfig.json ./ +COPY components.json ./ +COPY eslint.config.js ./ + +# Build frontend assets +RUN npm run build + +# PHP runtime stage +FROM php:8.2-fpm-alpine + +# Install system dependencies +RUN apk add --no-cache \ + git \ + curl \ + libpng-dev \ + libxml2-dev \ + zip \ + unzip \ + oniguruma-dev \ + mysql-client + +# Install PHP extensions +RUN docker-php-ext-install \ + pdo_mysql \ + mbstring \ + exif \ + pcntl \ + bcmath \ + gd + +# Install Composer +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer + +# Set working directory +WORKDIR /var/www/html + +# Copy application code first +COPY . . + +# Install PHP dependencies after copying all files +RUN composer install --no-dev --optimize-autoloader --no-interaction + +# Copy built frontend assets from builder stage +COPY --from=frontend-builder /app/public/build/ ./public/build/ + +# Set proper permissions +RUN chown -R www-data:www-data /var/www/html \ + && chmod -R 755 /var/www/html/storage \ + && chmod -R 755 /var/www/html/bootstrap/cache + +# Create a startup script +RUN echo '#!/bin/sh' > /usr/local/bin/start-app \ + && echo 'cp -r /var/www/html/public/. /var/www/html/public_shared/ 2>/dev/null || true' >> /usr/local/bin/start-app \ + && echo 'php artisan config:cache' >> /usr/local/bin/start-app \ + && echo 'php artisan route:cache' >> /usr/local/bin/start-app \ + && echo 'php artisan view:cache' >> /usr/local/bin/start-app \ + && echo 'php artisan migrate --force' >> /usr/local/bin/start-app \ + && echo 'php-fpm' >> /usr/local/bin/start-app \ + && chmod +x /usr/local/bin/start-app + +# Expose port 9000 for PHP-FPM +EXPOSE 9000 + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD php artisan --version || exit 1 + +# Start the application +CMD ["/usr/local/bin/start-app"] \ No newline at end of file diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..a133062 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,65 @@ +version: '3.8' + +services: + app: + image: codeberg.org/lvl0/incr:v0.1.0-alpha-1 + # build: + # context: ../ + # dockerfile: docker/Dockerfile + container_name: incr-app + restart: unless-stopped + working_dir: /var/www/html + environment: + - APP_ENV=production + - APP_DEBUG=false + - DB_CONNECTION=mysql + - DB_HOST=db + - DB_PORT=3306 + - DB_DATABASE=incr + - DB_USERNAME=incr_user + - DB_PASSWORD=incr_password + volumes: + - ../storage:/var/www/html/storage + - ../public:/var/www/html/public + depends_on: + - db + networks: + - incr-network + + db: + image: mysql:8.0 + container_name: incr-db + restart: unless-stopped + environment: + - MYSQL_DATABASE=incr + - MYSQL_USER=incr_user + - MYSQL_PASSWORD=incr_password + - MYSQL_ROOT_PASSWORD=root_password + volumes: + - db_data:/var/lib/mysql + ports: + - "3306:3306" + networks: + - incr-network + + nginx: + image: nginx:alpine + container_name: incr-nginx + restart: unless-stopped + ports: + - "80:80" + volumes: + - ../public:/var/www/html/public:ro + - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro + depends_on: + - app + networks: + - incr-network + +networks: + incr-network: + driver: bridge + +volumes: + db_data: + driver: local \ No newline at end of file diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 0000000..3877706 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,26 @@ +server { + listen 80; + server_name localhost; + root /var/www/html/public; + index index.php index.html; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass app:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.ht { + deny all; + } + + location ~* \.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index 912a683..1760079 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -16,6 +16,15 @@ .font-digital { font-family: '7Segment', monospace; } +.glow-red { + box-shadow: 0 0 20px rgba(239, 68, 68, 0.4); + transition: box-shadow 300ms ease; +} + +.glow-red:hover { + box-shadow: 0 0 25px rgba(239, 68, 68, 0.6); +} + @custom-variant dark (&:is(.dark *)); @theme { diff --git a/resources/js/components/Display/InlineForm.tsx b/resources/js/components/Display/InlineForm.tsx index 9d6b693..7dd4ca8 100644 --- a/resources/js/components/Display/InlineForm.tsx +++ b/resources/js/components/Display/InlineForm.tsx @@ -2,7 +2,7 @@ import AddMilestoneForm from '@/components/Milestones/AddMilestoneForm'; import AddPurchaseForm from '@/components/Transactions/AddPurchaseForm'; import UpdatePriceForm from '@/components/Pricing/UpdatePriceForm'; import { cn } from '@/lib/utils'; -import { X } from 'lucide-react'; +import ComponentTitle from '@/components/ui/ComponentTitle'; interface InlineFormProps { type: 'purchase' | 'milestone' | 'price' | null; @@ -13,66 +13,58 @@ interface InlineFormProps { className?: string; } -export default function InlineForm({ - type, - onClose, +export default function InlineForm({ + type, + onClose, onPurchaseSuccess, onMilestoneSuccess, onPriceSuccess, - className + className }: InlineFormProps) { if (!type) return null; const title = type === 'purchase' ? 'ADD PURCHASE' : type === 'milestone' ? 'ADD MILESTONE' : 'UPDATE PRICE'; return ( -
- Current price: €{currentPrice.toFixed(4)} +
+ [CURRENT] €{currentPrice.toFixed(4)}
)} - -