diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..13510cf --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,33 @@ +name: Build and push image + +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to registry + uses: docker/login-action@v3 + with: + registry: forge.quinefoundation.com + username: ${{ secrets.REGISTRY_USERNAME }} + password: ${{ secrets.REGISTRY_PASSWORD }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true + tags: forge.quinefoundation.com/ironmagma/riverside:latest diff --git a/docker/php/entrypoint.sh b/docker/php/entrypoint.sh index 842af74..548cd77 100644 --- a/docker/php/entrypoint.sh +++ b/docker/php/entrypoint.sh @@ -1,5 +1,4 @@ #!/bin/sh -set -e DB_HOST="${DB_HOST:-postgres}" DB_USER="${DB_USER:-drupal}" @@ -19,39 +18,50 @@ HAS_TABLES=$($DRUSH sql:query \ "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema='public' AND table_name='config';" \ 2>/dev/null || echo "0") -if [ "$HAS_TABLES" = "1" ]; then - echo "[entrypoint] Database populated, importing configuration..." - $DRUSH config:import -y 2>/dev/null && \ - echo "[entrypoint] Config imported." || \ - echo "[entrypoint] No config to import, continuing." - $DRUSH theme:enable claro_compact -y -else +IS_SETUP=$($DRUSH sql:query \ + "SELECT COUNT(*) FROM config WHERE name='core.extension' AND data LIKE '%riverside_pt%';" \ + 2>/dev/null || echo "0") + +if [ "$HAS_TABLES" != "1" ]; then echo "[entrypoint] Fresh database, installing Drupal..." $DRUSH site:install standard \ --site-name="${SITE_NAME:-Portfolio}" \ --account-name=admin \ --account-pass="${ADMIN_PASS:-admin}" \ - -y + -y || { echo "[entrypoint] FATAL: site:install failed."; exit 1; } echo "[entrypoint] Drupal installed." +fi - echo "[entrypoint] Enabling modules..." - $DRUSH en -y views views_ui field_ui text options link datetime - $DRUSH en -y webform webform_ui - $DRUSH en -y symfony_mailer +if [ "$IS_SETUP" != "1" ]; then + echo "[entrypoint] Running setup (first boot or recovery from failed setup)..." - $DRUSH en -y riverside_pt - echo "[entrypoint] Modules enabled." + $DRUSH en -y views views_ui field_ui text options link datetime && \ + echo "[entrypoint] Core modules enabled." || echo "[entrypoint] WARNING: core modules failed." + $DRUSH en -y webform webform_ui && \ + echo "[entrypoint] Webform enabled." || echo "[entrypoint] WARNING: webform failed." + $DRUSH en -y symfony_mailer && \ + echo "[entrypoint] Mailer enabled." || echo "[entrypoint] WARNING: symfony_mailer failed." + $DRUSH en -y riverside_pt && \ + echo "[entrypoint] riverside_pt enabled." || echo "[entrypoint] WARNING: riverside_pt failed." - echo "[entrypoint] Setting themes..." - $DRUSH theme:enable olivero claro_compact - echo "[entrypoint] Themes set." + $DRUSH config:set system.site page.front /home -y && \ + echo "[entrypoint] Front page set." || echo "[entrypoint] WARNING: front page config failed." + + $DRUSH theme:enable olivero claro_compact -y && \ + echo "[entrypoint] Themes set." || echo "[entrypoint] WARNING: theme enable failed." if ls /var/www/html/config/sync/*.yml >/dev/null 2>&1; then echo "[entrypoint] Importing configuration from sync dir..." - $DRUSH config:import -y + $DRUSH config:import -y || echo "[entrypoint] WARNING: config import failed." fi -fi + echo "[entrypoint] Setup complete." +else + echo "[entrypoint] Setup already complete, importing configuration..." + $DRUSH config:import -y 2>/dev/null && \ + echo "[entrypoint] Config imported." || \ + echo "[entrypoint] No config to import, continuing." +fi echo "[entrypoint] Starting services..." exec supervisord -c /etc/supervisor/conf.d/supervisord.conf diff --git a/web/modules/custom/riverside_pt/css/front.css b/web/modules/custom/riverside_pt/css/front.css new file mode 100644 index 0000000..3cb8f3f --- /dev/null +++ b/web/modules/custom/riverside_pt/css/front.css @@ -0,0 +1,143 @@ +/* Hide Drupal's default page-title block on the front page — our H1 is inside the hero */ +.path-frontpage .block-page-title-block { + display: none; +} + +/* ── Hero ───────────────────────────────────────────────────── */ +.rpt-hero { + background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); + padding: 5rem 1.5rem; + text-align: center; +} + +.rpt-hero__inner { + max-width: 680px; + margin: 0 auto; +} + +.rpt-hero__heading { + font-size: clamp(2rem, 5vw, 3.25rem); + font-weight: 700; + color: #1e3a8a; + line-height: 1.15; + margin-bottom: 1rem; +} + +.rpt-hero__body { + font-size: 1.125rem; + color: #4b5563; + line-height: 1.7; + margin-bottom: 2rem; +} + +.rpt-hero__actions { + display: flex; + gap: 1rem; + justify-content: center; + flex-wrap: wrap; +} + +/* ── Shared buttons ─────────────────────────────────────────── */ +.rpt-btn { + display: inline-block; + padding: 0.75rem 1.5rem; + border-radius: 999px; + font-weight: 600; + font-size: 0.9375rem; + text-decoration: none; + transition: background 0.15s, color 0.15s, border-color 0.15s; + cursor: pointer; +} + +.rpt-btn--primary { + background: #3b82f6; + color: #fff; + border: 2px solid #3b82f6; +} + +.rpt-btn--primary:hover, +.rpt-btn--primary:focus { + background: #1d4ed8; + border-color: #1d4ed8; + color: #fff; +} + +.rpt-btn--secondary { + background: transparent; + color: #3b82f6; + border: 2px solid #3b82f6; +} + +.rpt-btn--secondary:hover, +.rpt-btn--secondary:focus { + background: #3b82f6; + color: #fff; +} + +.rpt-btn--outline { + background: transparent; + color: #3b82f6; + border: 1px solid #3b82f6; + border-radius: 4px; + font-size: 0.875rem; + padding: 0.5rem 1rem; +} + +.rpt-btn--outline:hover, +.rpt-btn--outline:focus { + background: #3b82f6; + color: #fff; +} + +/* ── Services section ───────────────────────────────────────── */ +.rpt-services { + padding: 4rem 1.5rem; + background: #fff; +} + +.rpt-services__header { + text-align: center; + margin-bottom: 3rem; +} + +.rpt-services__heading { + font-size: 2rem; + font-weight: 700; + color: #1e3a8a; + margin-bottom: 0.375rem; +} + +.rpt-services__subtitle { + font-size: 1.0625rem; + color: #6b7280; +} + +.rpt-services__grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); + gap: 1.5rem; + max-width: 1040px; + margin: 0 auto; +} + +.rpt-service-card { + display: flex; + flex-direction: column; + gap: 0.75rem; + border: 1px solid #e5e7eb; + border-radius: 8px; + padding: 1.5rem; +} + +.rpt-service-card__title { + font-size: 1.0625rem; + font-weight: 600; + color: #111827; +} + +.rpt-service-card__body { + font-size: 0.9375rem; + color: #6b7280; + line-height: 1.6; + flex: 1; +} diff --git a/web/modules/custom/riverside_pt/css/nav.css b/web/modules/custom/riverside_pt/css/nav.css new file mode 100644 index 0000000..851cfde --- /dev/null +++ b/web/modules/custom/riverside_pt/css/nav.css @@ -0,0 +1,26 @@ +.primary-nav__menu-link.nav-cta { + border: 2px solid #3b82f6; + border-radius: 999px; + color: #3b82f6; + padding: 0.25rem 1rem; + transition: background 0.15s, color 0.15s; +} + +.primary-nav__menu-link.nav-cta:hover, +.primary-nav__menu-link.nav-cta:focus { + background: #3b82f6; + border-color: #3b82f6; + color: #fff; +} + +.primary-nav__menu-link.nav-cta--primary { + background: #3b82f6; + border-color: #3b82f6; + color: #fff; +} + +.primary-nav__menu-link.nav-cta--primary:hover, +.primary-nav__menu-link.nav-cta--primary:focus { + background: #1d4ed8; + border-color: #1d4ed8; +} diff --git a/web/modules/custom/riverside_pt/riverside_pt.install b/web/modules/custom/riverside_pt/riverside_pt.install index 1bdbc14..5cc47c6 100644 --- a/web/modules/custom/riverside_pt/riverside_pt.install +++ b/web/modules/custom/riverside_pt/riverside_pt.install @@ -1,6 +1,9 @@ TRUE, ]), ], fn($entity) => $entity->save()); + + try { + _riverside_pt_build_navigation(); + } + catch (\Exception $e) { + \Drupal::logger('riverside_pt')->error('Navigation setup failed: @msg', ['@msg' => $e->getMessage()]); + } + + \Drupal::configFactory()->getEditable('system.site') + ->set('page.front', '/home') + ->save(); +} + +function _riverside_pt_build_navigation(): void { + $em = \Drupal::entityTypeManager(); + + // Remove whatever links Standard profile put in the main menu. + foreach ($em->getStorage('menu_link_content')->loadByProperties(['menu_name' => 'main']) as $link) { + $link->delete(); + } + + // Create placeholder basic pages. + foreach (['Services', 'About', 'FAQ', 'Contact'] as $title) { + $existing = $em->getStorage('node')->loadByProperties(['title' => $title, 'type' => 'page']); + if ($existing) { + continue; + } + $node = Node::create(['type' => 'page', 'title' => $title, 'status' => 1]); + $node->save(); + PathAlias::create([ + 'path' => '/node/' . $node->id(), + 'alias' => '/' . strtolower($title), + 'langcode' => 'en', + ])->save(); + } + + // Build the primary navigation. + $defs = [ + ['title' => 'Home', 'uri' => 'route:', 'weight' => 0, 'class' => NULL], + ['title' => 'Services', 'uri' => 'internal:/services', 'weight' => 1, 'class' => NULL], + ['title' => 'About', 'uri' => 'internal:/about', 'weight' => 2, 'class' => NULL], + ['title' => 'FAQ', 'uri' => 'internal:/faq', 'weight' => 3, 'class' => NULL], + ['title' => 'Contact', 'uri' => 'internal:/contact', 'weight' => 4, 'class' => 'nav-cta'], + ['title' => 'Book An Appointment', 'uri' => 'internal:/schedule', 'weight' => 5, 'class' => 'nav-cta nav-cta--primary'], + ]; + + foreach ($defs as $def) { + $options = $def['class'] ? ['attributes' => ['class' => explode(' ', $def['class'])]] : []; + MenuLinkContent::create([ + 'title' => $def['title'], + 'link' => ['uri' => $def['uri'], 'options' => $options], + 'menu_name' => 'main', + 'weight' => $def['weight'], + 'enabled' => TRUE, + ])->save(); + } } diff --git a/web/modules/custom/riverside_pt/riverside_pt.libraries.yml b/web/modules/custom/riverside_pt/riverside_pt.libraries.yml index a055d1b..4501c46 100644 --- a/web/modules/custom/riverside_pt/riverside_pt.libraries.yml +++ b/web/modules/custom/riverside_pt/riverside_pt.libraries.yml @@ -1,3 +1,13 @@ +front: + css: + theme: + css/front.css: {} + +navigation: + css: + theme: + css/nav.css: {} + schedule: css: theme: diff --git a/web/modules/custom/riverside_pt/riverside_pt.module b/web/modules/custom/riverside_pt/riverside_pt.module index b587820..d71d108 100644 --- a/web/modules/custom/riverside_pt/riverside_pt.module +++ b/web/modules/custom/riverside_pt/riverside_pt.module @@ -4,6 +4,10 @@ use Drupal\Core\Breadcrumb\Breadcrumb; use Drupal\Core\Link; use Drupal\Core\Routing\RouteMatchInterface; +function riverside_pt_page_attachments(array &$attachments): void { + $attachments['#attached']['library'][] = 'riverside_pt/navigation'; +} + function riverside_pt_system_breadcrumb_alter(Breadcrumb &$breadcrumb, RouteMatchInterface $route_match, array $context): void { if ($route_match->getRouteName() === 'riverside_pt.booking') { $breadcrumb = new Breadcrumb(); diff --git a/web/modules/custom/riverside_pt/riverside_pt.routing.yml b/web/modules/custom/riverside_pt/riverside_pt.routing.yml index a9642f0..2284c49 100644 --- a/web/modules/custom/riverside_pt/riverside_pt.routing.yml +++ b/web/modules/custom/riverside_pt/riverside_pt.routing.yml @@ -1,3 +1,11 @@ +riverside_pt.home: + path: '/home' + defaults: + _controller: '\Drupal\riverside_pt\Controller\HomeController::page' + _title: 'Riverside Physical Therapy' + requirements: + _permission: 'access content' + riverside_pt.schedule: path: '/schedule' defaults: diff --git a/web/modules/custom/riverside_pt/src/Controller/HomeController.php b/web/modules/custom/riverside_pt/src/Controller/HomeController.php new file mode 100644 index 0000000..425d08f --- /dev/null +++ b/web/modules/custom/riverside_pt/src/Controller/HomeController.php @@ -0,0 +1,61 @@ + 'inline_template', + '#template' => self::template(), + '#attached' => ['library' => ['riverside_pt/front']], + ]; + } + + private static function template(): string { + return <<<'TWIG' +
+
+

Heal your body

+

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

+ +
+
+ +
+
+

Bringing Relief

+

Our wide range of services

+
+
+
+

Diagnostic Evaluation

+

Comprehensive assessment to identify the root cause of your pain and build a personalized recovery plan.

+ More Info +
+
+

Sports Rehabilitation

+

Targeted programs to help athletes recover from injury and return to peak performance safely.

+ More Info +
+
+

Pre/Post-Surgical Rehab

+

Expert care before and after surgery to maximize recovery outcomes and restore full function.

+ More Info +
+
+

Neurological Physical Therapy

+

Specialized therapy for nervous system conditions, helping you regain strength and independence.

+ More Info +
+
+
+TWIG; + } + +}