Testimonails and Introscript updates

This commit is contained in:
2026-05-03 11:16:53 +12:00
parent 751c2d7e98
commit f27e0fed07
13 changed files with 191 additions and 23 deletions
+3 -4
View File
@@ -156,11 +156,10 @@ nginx/goodwalk.co.nz.svelte.conf.example
``` ```
Important: Important:
- The normal `deploy.ps1` flow does not deploy or reload the shared nginx stack. - `deploy.ps1` now copies the repo nginx config to `/docker/nginx/conf.d/goodwalk.co.nz.conf` and reloads the shared nginx container as part of deployment.
- Copy the updated nginx config to `/docker/nginx/conf.d/goodwalk.co.nz.conf` and reload nginx once. - The repo nginx config uses Docker's internal resolver so future app/mail container rebuilds will not leave nginx pinned to stale upstream IPs.
- The repo example now uses Docker's internal resolver so future app/mail container rebuilds will not leave nginx pinned to stale upstream IPs.
Then reload nginx: Manual nginx commands, if you ever need them:
```bash ```bash
docker compose -p nginx -f /docker/nginx/docker-compose.yml exec nginx nginx -t docker compose -p nginx -f /docker/nginx/docker-compose.yml exec nginx nginx -t
+20 -1
View File
@@ -20,6 +20,10 @@ $SshConfigPath = Join-Path $LocalProjectPath 'ssh-config'
$RemoteDeploymentPath = '/docker/goodwalk-svelte' $RemoteDeploymentPath = '/docker/goodwalk-svelte'
$ComposeFileName = 'docker-compose.prod.yml' $ComposeFileName = 'docker-compose.prod.yml'
$DockerProjectName = 'goodwalk-svelte' $DockerProjectName = 'goodwalk-svelte'
$NginxConfigSource = 'nginx/goodwalk.co.nz.svelte.conf.example'
$NginxConfigTarget = '/docker/nginx/conf.d/goodwalk.co.nz.conf'
$NginxComposeFile = '/docker/nginx/docker-compose.yml'
$NginxProjectName = 'nginx'
# Optional deployment settings. # Optional deployment settings.
$VerifyUrl = 'https://www.goodwalk.co.nz/api/health' $VerifyUrl = 'https://www.goodwalk.co.nz/api/health'
@@ -161,6 +165,10 @@ Assert-NotBlank -Name 'LocalProjectPath' -Value $LocalProjectPath
Assert-NotBlank -Name 'RemoteDeploymentPath' -Value $RemoteDeploymentPath Assert-NotBlank -Name 'RemoteDeploymentPath' -Value $RemoteDeploymentPath
Assert-NotBlank -Name 'ComposeFileName' -Value $ComposeFileName Assert-NotBlank -Name 'ComposeFileName' -Value $ComposeFileName
Assert-NotBlank -Name 'DockerProjectName' -Value $DockerProjectName Assert-NotBlank -Name 'DockerProjectName' -Value $DockerProjectName
Assert-NotBlank -Name 'NginxConfigSource' -Value $NginxConfigSource
Assert-NotBlank -Name 'NginxConfigTarget' -Value $NginxConfigTarget
Assert-NotBlank -Name 'NginxComposeFile' -Value $NginxComposeFile
Assert-NotBlank -Name 'NginxProjectName' -Value $NginxProjectName
if (-not [string]::IsNullOrWhiteSpace($Service)) { if (-not [string]::IsNullOrWhiteSpace($Service)) {
$Service = $Service.Trim() $Service = $Service.Trim()
@@ -196,6 +204,8 @@ Write-Host "[deploy] Local project path: $LocalProjectPath"
Write-Host "[deploy] Remote deployment path: $RemoteDeploymentPath" Write-Host "[deploy] Remote deployment path: $RemoteDeploymentPath"
Write-Host "[deploy] Remote compose file: $ComposeFileName" Write-Host "[deploy] Remote compose file: $ComposeFileName"
Write-Host "[deploy] Docker project name: $DockerProjectName" Write-Host "[deploy] Docker project name: $DockerProjectName"
Write-Host "[deploy] Shared nginx config: $NginxConfigTarget"
Write-Host "[deploy] Shared nginx compose file: $NginxComposeFile"
Write-Host "[deploy] SSH target: $sshTarget" Write-Host "[deploy] SSH target: $sshTarget"
Write-Host "[deploy] SSH config: $SshConfigPath" Write-Host "[deploy] SSH config: $SshConfigPath"
if (-not [string]::IsNullOrWhiteSpace($Service)) { if (-not [string]::IsNullOrWhiteSpace($Service)) {
@@ -212,6 +222,7 @@ Write-Host ' - Only the top-level Goodwalk compose project will be updated.'
Write-Host ' - Legacy WordPress/onboarding compose files are not used.' Write-Host ' - Legacy WordPress/onboarding compose files are not used.'
Write-Host ' - Remote .env files are preserved because they are not uploaded.' Write-Host ' - Remote .env files are preserved because they are not uploaded.'
Write-Host ' - No global Docker prune/stop/delete commands are used.' Write-Host ' - No global Docker prune/stop/delete commands are used.'
Write-Host ' - Shared nginx will be updated and reloaded with the Docker-DNS-based config.'
if (-not $Force) { if (-not $Force) {
$confirmation = Read-Host "Type DEPLOY to continue with the remote path '$RemoteDeploymentPath'" $confirmation = Read-Host "Type DEPLOY to continue with the remote path '$RemoteDeploymentPath'"
@@ -253,7 +264,15 @@ try {
'--compose-file', '--compose-file',
$ComposeFileName, $ComposeFileName,
'--project-name', '--project-name',
$DockerProjectName $DockerProjectName,
'--nginx-source',
$NginxConfigSource,
'--nginx-target',
$NginxConfigTarget,
'--nginx-compose-file',
$NginxComposeFile,
'--nginx-project-name',
$NginxProjectName
) + $(if (-not [string]::IsNullOrWhiteSpace($Service)) { @('--service', $Service) } else { @() })) ) + $(if (-not [string]::IsNullOrWhiteSpace($Service)) { @('--service', $Service) } else { @() }))
Write-Host '' Write-Host ''
+1 -1
View File
@@ -380,7 +380,7 @@ def client_email(data: BookingSubmission) -> str:
style="max-width:600px;width:100%;border-radius:16px;overflow:hidden; style="max-width:600px;width:100%;border-radius:16px;overflow:hidden;
box-shadow:0 4px 24px rgba(0,0,0,0.08);"> box-shadow:0 4px 24px rgba(0,0,0,0.08);">
{_logo_header(subtitle="Auckland’s favourite dog walking service")} {_logo_header(subtitle="Professional dog walking services")}
<!-- Body --> <!-- Body -->
<tr> <tr>
+4 -4
View File
@@ -13,7 +13,7 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^5.2.11", "@sveltejs/adapter-node": "^5.2.11",
"@sveltejs/kit": "^2.17.1", "@sveltejs/kit": "^2.59.0",
"@sveltejs/vite-plugin-svelte": "^5.0.3", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/svelte": "^5.3.1", "@testing-library/svelte": "^5.3.1",
@@ -1271,9 +1271,9 @@
} }
}, },
"node_modules/@sveltejs/kit": { "node_modules/@sveltejs/kit": {
"version": "2.58.0", "version": "2.59.0",
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.58.0.tgz", "resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.59.0.tgz",
"integrity": "sha512-kT9GCN8yJTkCK1W+Gi/bvGooWAM7y7WXP+yd+rf6QOIjyoK1ERPrMwSufXJUNu2pMWIqruhFvmz+LbOqsEmKmA==", "integrity": "sha512-WeJaGKvDf3uVQB4bnDHhM+BXCY34LC1v0HiPqnSpvNkjB54r8DAUP1rpk73s+5zprIirEKtUcVfgh6+fPODjzQ==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
+1 -1
View File
@@ -17,7 +17,7 @@
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^5.2.11", "@sveltejs/adapter-node": "^5.2.11",
"@sveltejs/kit": "^2.17.1", "@sveltejs/kit": "^2.59.0",
"@sveltejs/vite-plugin-svelte": "^5.0.3", "@sveltejs/vite-plugin-svelte": "^5.0.3",
"@testing-library/jest-dom": "^6.9.1", "@testing-library/jest-dom": "^6.9.1",
"@testing-library/svelte": "^5.3.1", "@testing-library/svelte": "^5.3.1",
+60
View File
@@ -6,12 +6,19 @@ DEPLOY_PATH=""
COMPOSE_FILE="" COMPOSE_FILE=""
PROJECT_NAME="" PROJECT_NAME=""
SERVICE_NAME="" SERVICE_NAME=""
NGINX_SOURCE=""
NGINX_TARGET=""
NGINX_COMPOSE_FILE=""
NGINX_PROJECT_NAME=""
usage() { usage() {
cat <<'EOF' cat <<'EOF'
Usage: Usage:
deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name> deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name>
deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name> [--service <name>] deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name> [--service <name>]
deploy-remote.sh --archive <path> --deploy-path <path> --compose-file <name> --project-name <name> \
[--service <name>] [--nginx-source <path>] [--nginx-target <path>] \
[--nginx-compose-file <path>] [--nginx-project-name <name>]
This script only updates the main Goodwalk compose project at the specified This script only updates the main Goodwalk compose project at the specified
deployment path. It does not touch unrelated Docker projects or global Docker deployment path. It does not touch unrelated Docker projects or global Docker
@@ -46,6 +53,22 @@ while [[ $# -gt 0 ]]; do
SERVICE_NAME="${2:-}" SERVICE_NAME="${2:-}"
shift 2 shift 2
;; ;;
--nginx-source)
NGINX_SOURCE="${2:-}"
shift 2
;;
--nginx-target)
NGINX_TARGET="${2:-}"
shift 2
;;
--nginx-compose-file)
NGINX_COMPOSE_FILE="${2:-}"
shift 2
;;
--nginx-project-name)
NGINX_PROJECT_NAME="${2:-}"
shift 2
;;
-h|--help) -h|--help)
usage usage
exit 0 exit 0
@@ -66,6 +89,22 @@ fi
[[ "$DEPLOY_PATH" != "/" ]] || fail "Refusing to deploy to /" [[ "$DEPLOY_PATH" != "/" ]] || fail "Refusing to deploy to /"
[[ -f "$ARCHIVE_PATH" ]] || fail "Archive not found: $ARCHIVE_PATH" [[ -f "$ARCHIVE_PATH" ]] || fail "Archive not found: $ARCHIVE_PATH"
nginx_args=("$NGINX_SOURCE" "$NGINX_TARGET" "$NGINX_COMPOSE_FILE" "$NGINX_PROJECT_NAME")
nginx_args_present=0
for value in "${nginx_args[@]}"; do
if [[ -n "$value" ]]; then
nginx_args_present=1
break
fi
done
if (( nginx_args_present )); then
[[ -n "$NGINX_SOURCE" ]] || fail "--nginx-source is required when nginx deployment is enabled"
[[ -n "$NGINX_TARGET" ]] || fail "--nginx-target is required when nginx deployment is enabled"
[[ -n "$NGINX_COMPOSE_FILE" ]] || fail "--nginx-compose-file is required when nginx deployment is enabled"
[[ -n "$NGINX_PROJECT_NAME" ]] || fail "--nginx-project-name is required when nginx deployment is enabled"
fi
if docker compose version >/dev/null 2>&1; then if docker compose version >/dev/null 2>&1; then
COMPOSE_CMD=(docker compose) COMPOSE_CMD=(docker compose)
elif command -v docker-compose >/dev/null 2>&1; then elif command -v docker-compose >/dev/null 2>&1; then
@@ -89,6 +128,12 @@ echo "[deploy-remote] Docker project: $PROJECT_NAME"
if [[ -n "$SERVICE_NAME" ]]; then if [[ -n "$SERVICE_NAME" ]]; then
echo "[deploy-remote] Target service: $SERVICE_NAME" echo "[deploy-remote] Target service: $SERVICE_NAME"
fi fi
if (( nginx_args_present )); then
echo "[deploy-remote] Nginx config source: $NGINX_SOURCE"
echo "[deploy-remote] Nginx config target: $NGINX_TARGET"
echo "[deploy-remote] Nginx compose file: $NGINX_COMPOSE_FILE"
echo "[deploy-remote] Nginx project: $NGINX_PROJECT_NAME"
fi
echo "[deploy-remote] Staging archive in: $STAGING_DIR" echo "[deploy-remote] Staging archive in: $STAGING_DIR"
mkdir -p "$DEPLOY_PATH" mkdir -p "$DEPLOY_PATH"
@@ -171,4 +216,19 @@ if [[ -z "$SERVICE_NAME" || "$SERVICE_NAME" == "app" || "$SERVICE_NAME" == "db"
"${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" exec -T app node scripts/sync-homepage-content.mjs "${COMPOSE_CMD[@]}" -p "$PROJECT_NAME" -f "$COMPOSE_FILE" exec -T app node scripts/sync-homepage-content.mjs
fi fi
if (( nginx_args_present )); then
[[ -f "$DEPLOY_PATH/$NGINX_SOURCE" ]] || fail "Nginx config missing from deployment payload: $DEPLOY_PATH/$NGINX_SOURCE"
[[ -f "$NGINX_COMPOSE_FILE" ]] || fail "Nginx compose file was not found on the server: $NGINX_COMPOSE_FILE"
echo "[deploy-remote] Updating shared nginx config to avoid stale container IPs"
mkdir -p "$(dirname "$NGINX_TARGET")"
cp "$DEPLOY_PATH/$NGINX_SOURCE" "$NGINX_TARGET"
echo "[deploy-remote] Validating nginx configuration"
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -t
echo "[deploy-remote] Reloading shared nginx"
"${COMPOSE_CMD[@]}" -p "$NGINX_PROJECT_NAME" -f "$NGINX_COMPOSE_FILE" exec -T nginx nginx -s reload
fi
echo "[deploy-remote] Remote deployment finished" echo "[deploy-remote] Remote deployment finished"
+16 -4
View File
@@ -10,9 +10,21 @@
<div id="intro"> <div id="intro">
<div class="intro-inner"> <div class="intro-inner">
<div class="intro-trust-badge"> <div class="intro-trust-badge">
<div class="intro-trust-mark" aria-hidden="true"> <a
<Icon name="fab fa-google" /> class="intro-trust-mark intro-trust-mark-link"
</div> href={intro.reviewCta.href}
target="_blank"
rel="noopener"
aria-label="Read our Google reviews"
>
<img
class="intro-google-logo"
src="/images/google-g-logo.svg"
alt=""
width="28"
height="29"
/>
</a>
<div class="intro-trust-copy"> <div class="intro-trust-copy">
<p>{intro.text}</p> <p>{intro.text}</p>
@@ -24,7 +36,7 @@
{/each} {/each}
</div> </div>
<a href={intro.reviewCta.href} target="_blank" rel="noopener"> <a class="intro-trust-cta" href={intro.reviewCta.href} target="_blank" rel="noopener">
{intro.reviewCta.label} {intro.reviewCta.label}
</a> </a>
</div> </div>
+1 -1
View File
@@ -67,7 +67,7 @@
<div class="modal-divider"></div> <div class="modal-divider"></div>
<p class="modal-sub"> <p class="modal-sub">
In the meantime, feel free to follow along on Instagram for daily walks and happy dogs. In the meantime, feel free to follow along on Instagram for daily walks and happy dogs!
</p> </p>
<button class="modal-btn" type="button" on:click={onClose}> <button class="modal-btn" type="button" on:click={onClose}>
@@ -8,9 +8,9 @@
export let testimonials: TestimonialContent[]; export let testimonials: TestimonialContent[];
export let heading = 'Why people choose us!'; export let heading = 'Why people choose us!';
export let blurb = export let blurb =
'Real dogs, real routines, trusted by happy owners. Follow along on Instagram to see the Tiny Gang out on their daily adventures.'; "Happy owners, even happier dogs. Our Auckland dog walking clients love what the Tiny Gang brings to their dog's routine — and you can see why. Follow along on Instagram for daily adventures, wagging tails and the odd zoomie";
export let instagramHref = 'https://www.instagram.com/goodwalk.nz/'; export let instagramHref = 'https://www.instagram.com/goodwalk.nz/';
export let instagramLabel = '@goodwalk.nz'; export let instagramLabel = 'goodwalk.nz';
type TestimonialSlide = TestimonialContent & { imageUrl: string }; type TestimonialSlide = TestimonialContent & { imageUrl: string };
+2 -2
View File
@@ -50,8 +50,8 @@ export const homepageContent: HomePageContent = {
title: 'Happy pets,', title: 'Happy pets,',
subtitle: 'happy humans', subtitle: 'happy humans',
body: body:
'Offering tailored pack walks for small and medium dogs, and one-on-one walks for large breeds. Our walkers give personalised attention to each dog, easing stress, anxiety and ensuring a quality experience. Our expertise in small-medium breeds ensures tailored care for their unique needs. Join our', 'Professional dog walking services in Auckland for small, medium and large breeds. Our experienced walkers provide tailored pack walks for smaller dogs and one-on-one walks for larger breeds - giving every dog the personalised attention they deserve. We specialise in understanding the unique needs of small-to-medium breeds, helping to ease stress and anxiety while keeping tails wagging. Ready to join our',
emphasis: 'TINY GANG!', emphasis: 'TINY GANG?',
cta: { label: 'Book now', href: '#newlead', variant: 'green' }, cta: { label: 'Book now', href: '#newlead', variant: 'green' },
imageUrl: '/images/auckland-dog-walking-happy-dogs-happy-humans.webp', imageUrl: '/images/auckland-dog-walking-happy-dogs-happy-humans.webp',
imageAlt: 'Woman cuddling a dog for Goodwalk Auckland dog walking services' imageAlt: 'Woman cuddling a dog for Goodwalk Auckland dog walking services'
+5 -1
View File
@@ -299,6 +299,10 @@
font-size: 21px; font-size: 21px;
} }
.intro-google-logo {
width: 24px;
}
#intro p { #intro p {
font-size: 17px; font-size: 17px;
line-height: 1.45; line-height: 1.45;
@@ -312,7 +316,7 @@
gap: 10px; gap: 10px;
} }
#intro a { .intro-trust-cta {
gap: 8px; gap: 8px;
padding: 8px 14px; padding: 8px 14px;
font-size: 15px; font-size: 15px;
+70 -2
View File
@@ -67,6 +67,7 @@ section {
.intro-trust-mark { .intro-trust-mark {
display: inline-flex; display: inline-flex;
position: relative;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 56px; width: 56px;
@@ -75,9 +76,50 @@ section {
background: #fff; background: #fff;
color: #e00706; color: #e00706;
font-size: 24px; font-size: 24px;
overflow: hidden;
box-shadow: inset 0 0 0 1px rgba(14, 27, 41, 0.08); box-shadow: inset 0 0 0 1px rgba(14, 27, 41, 0.08);
} }
.intro-trust-mark-link {
text-decoration: none;
isolation: isolate;
transition:
transform 0.22s cubic-bezier(0.22, 1, 0.36, 1),
box-shadow 0.22s ease;
}
.intro-trust-mark-link::after {
content: '';
position: absolute;
inset: -30% 35%;
background: linear-gradient(120deg, transparent 0%, rgba(255, 255, 255, 0.2) 30%, rgba(255, 255, 255, 0.92) 50%, rgba(255, 255, 255, 0.2) 70%, transparent 100%);
transform: translateX(-220%) rotate(18deg);
opacity: 0;
pointer-events: none;
animation: introGoogleShine 4.8s ease-in-out infinite;
}
.intro-trust-mark-link:hover,
.intro-trust-mark-link:focus-visible {
transform: translateY(-1px) scale(1.04);
box-shadow:
inset 0 0 0 1px rgba(14, 27, 41, 0.08),
0 10px 24px rgba(17, 20, 24, 0.12);
}
.intro-trust-mark-link:focus-visible {
outline: 2px solid rgba(10, 48, 78, 0.28);
outline-offset: 3px;
}
.intro-google-logo {
display: block;
position: relative;
z-index: 1;
width: 28px;
height: auto;
}
.intro-trust-copy { .intro-trust-copy {
min-width: 0; min-width: 0;
} }
@@ -107,7 +149,7 @@ section {
font-size: 14px; font-size: 14px;
} }
#intro a { .intro-trust-cta {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
@@ -121,11 +163,37 @@ section {
box-shadow: inset 0 0 0 1px rgba(14, 27, 41, 0.08); box-shadow: inset 0 0 0 1px rgba(14, 27, 41, 0.08);
} }
#intro a .icon { .intro-trust-cta .icon {
color: #e00706; color: #e00706;
font-size: 20px; font-size: 20px;
} }
@keyframes introGoogleShine {
0%,
64%,
100% {
transform: translateX(-220%) rotate(18deg);
opacity: 0;
}
72% {
opacity: 1;
}
84% {
transform: translateX(220%) rotate(18deg);
opacity: 0;
}
}
@media (prefers-reduced-motion: reduce) {
.intro-trust-mark-link,
.intro-trust-mark-link::after {
animation: none;
transition: none;
}
}
#promise, #promise,
#testimonials, #testimonials,
#info { #info {
+6
View File
@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 262">
<path fill="#4285F4" d="M255.68 133.53c0-8.89-.8-17.43-2.29-25.63H130.8v48.48h70.06c-3.02 16.31-12.2 30.12-25.99 39.35v32.65h41.95c24.54-22.6 38.86-55.92 38.86-94.85Z"/>
<path fill="#34A853" d="M130.8 261.1c35.1 0 64.53-11.63 86.04-31.55l-41.95-32.65c-11.63 7.79-26.53 12.38-44.09 12.38-33.88 0-62.58-22.88-72.83-53.62H14.6v33.67c21.39 42.42 65.29 71.77 116.2 71.77Z"/>
<path fill="#FBBC05" d="M57.97 155.65c-2.61-7.79-4.09-16.11-4.09-24.65s1.48-16.86 4.09-24.65V72.68H14.6C5.28 91.24 0 110.62 0 131s5.28 39.76 14.6 58.32l43.37-33.67Z"/>
<path fill="#EA4335" d="M130.8 52.72c19.08 0 36.23 6.57 49.72 19.48l37.29-37.29C195.28 13.35 165.87 0 130.8 0 79.89 0 35.99 29.35 14.6 72.68l43.37 33.67c10.25-30.74 38.95-53.63 72.83-53.63Z"/>
</svg>

After

Width:  |  Height:  |  Size: 809 B