Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
96b5a07
Add a grid overlay option to V2 block backgrounds
joshbermanssw Jun 18, 2026
d9d7d3d
Show hex codes in the background colour picker and add SSW Dark Gray
joshbermanssw Jun 18, 2026
1b8c0d0
Add v3 consulting blocks (hero, logo carousel, feature steps)
joshbermanssw Jun 18, 2026
e200cfc
Migrate the React consulting page to a v2 page
joshbermanssw Jun 18, 2026
4554a25
fix new bgcolouroptions
joshbermanssw Jun 18, 2026
ee6465a
upd y-height; add raidal bgs
joshbermanssw Jun 18, 2026
c151c4d
reduce radial transparency
joshbermanssw Jun 18, 2026
56e0361
move radials
joshbermanssw Jun 18, 2026
b01c1de
add y line
joshbermanssw Jun 18, 2026
303715e
add pluggable media slot to v3 hero with animated React atom
joshbermanssw Jun 18, 2026
779f2b3
make v3 feature step brow editable and fix rich-text step defaults
joshbermanssw Jun 18, 2026
18e4bba
regenerate tina lock for v3 schema changes
joshbermanssw Jun 18, 2026
9cd9f62
update React consulting page content
joshbermanssw Jun 18, 2026
89926be
add v3 Process block
joshbermanssw Jun 18, 2026
6e133ee
regenerate tina lock for v3 Process schema
joshbermanssw Jun 18, 2026
cdb5840
add Process block to React consulting page
joshbermanssw Jun 18, 2026
68a39da
fix feature steps grid like pattern
joshbermanssw Jun 18, 2026
ecc24ba
fix AST schema
joshbermanssw Jun 18, 2026
f310942
Add stats + testimonial
joshbermanssw Jun 18, 2026
68e911d
l!nt and some ui updates to testimonial sections
joshbermanssw Jun 18, 2026
436ff5f
inc py size
joshbermanssw Jun 18, 2026
f21992f
stackCards + CTA
joshbermanssw Jun 18, 2026
e44347e
add FAQ block
joshbermanssw Jun 18, 2026
0f307a8
add lead capture form v1
joshbermanssw Jun 18, 2026
7c945b4
ui pass over with alex
joshbermanssw Jun 18, 2026
cf400e0
l1nt
joshbermanssw Jun 18, 2026
98f4c7f
l1nt 2
joshbermanssw Jun 18, 2026
026add1
fix build
joshbermanssw Jun 18, 2026
c0e834c
Fix /consulting prerender crash from null React page reference
joshbermanssw Jun 18, 2026
8bd5227
Merge branch 'main' into jb/react-consulting-v2-page-redesign
isaaclombardssw Jun 18, 2026
1ef3711
add SSW adaptive form-field primitives + design tokens
joshbermanssw Jun 19, 2026
231cc27
rebuild lead capture as a multi-step contact form
joshbermanssw Jun 19, 2026
ab40222
add case study link to v3 testimonials
joshbermanssw Jun 19, 2026
93d31ec
update consulting content, logo carousel heading + tina lock
joshbermanssw Jun 19, 2026
29a710d
lint
joshbermanssw Jun 19, 2026
1573c77
upd text
joshbermanssw Jun 19, 2026
a71d1e6
upd text
joshbermanssw Jun 19, 2026
aaf313a
restore #lead-capture-heading anchor target
joshbermanssw Jun 19, 2026
2927c5a
fix(lint): reorder checkbox tailwind classes for prettier
joshbermanssw Jun 19, 2026
04d8b0d
fix(consulting): replace broken testimonial logo with hearing-austral…
joshbermanssw Jun 19, 2026
4badd9a
feat(videoModal): support CMS thumbnail override
joshbermanssw Jun 19, 2026
9f88332
feat(blocks): add v3 Video Highlights and Card Carousel blocks
joshbermanssw Jun 19, 2026
f514df4
feat(consulting): populate React page with new blocks + assets
joshbermanssw Jun 19, 2026
5d6088b
style(cardCarousel): polish v3 card visuals
joshbermanssw Jun 19, 2026
4e44a66
chore(consulting): update card carousel section content
joshbermanssw Jun 19, 2026
58a5ca7
update text
joshbermanssw Jun 19, 2026
8fa957c
fix(videoHighlights): suppress Tina custom-component type error
joshbermanssw Jun 19, 2026
8739470
fix(leadCapture): send location as country + state options for JotForm
joshbermanssw Jun 19, 2026
aac6f9c
fix(consulting): use valid Hearing Australia logo in React testimonial
joshbermanssw Jun 19, 2026
6033729
upd gitignroe
joshbermanssw Jun 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ NEXT_PUBLIC_GITHUB_SHA=0000000000000000000000000000000000000000
# Current site URL
SITE_URL=***

#JOTFORM API KEY
JOTFORM_API_KEY=***

# Recaptcha Keys
GOOGLE_RECAPTCHA_KEY=***

Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public/admin/.gitignore
# testing
/coverage

# playwright-mcp scratch output (snapshots, console logs, screenshots)
.playwright-mcp/

# next.js
/.next/
/out/
Expand Down
73 changes: 73 additions & 0 deletions app/api/lead-capture/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { NextRequest } from "next/server";

/**
* Receives lead-capture answers from the V3LeadCapture block and forwards them
* to JotForm's submissions API, keeping the API key server-side.
*
* Body: { jotFormId: string, fields: Record<qid, value> }
* Each `fields` key is a JotForm question id (qid); JotForm expects them encoded
* as `submission[{qid}]=value`. An array value is a multi-option field, encoded
* as `submission[{qid}][{index}]=value` (e.g. location → country + state).
*/
export async function POST(request: NextRequest) {
try {
const apiKey = process.env.JOTFORM_API_KEY;
if (!apiKey) {
return Response.json(
{ message: "JotForm is not configured." },
{ status: 500 }
);
}

const { jotFormId, fields } = await request.json();

if (!jotFormId || !fields || typeof fields !== "object") {
return Response.json(
{ message: "Missing jotFormId or fields." },
{ status: 400 }
);
}

const body = new URLSearchParams();
for (const [qid, value] of Object.entries(fields)) {
if (Array.isArray(value)) {
value.forEach((entry, index) => {
if (entry != null && entry !== "") {
body.append(`submission[${qid}][${index}]`, String(entry));
}
});
} else if (value != null && value !== "") {
body.append(`submission[${qid}]`, String(value));
}
}

const res = await fetch(
`https://api.jotform.com/form/${encodeURIComponent(
jotFormId
)}/submissions?apiKey=${encodeURIComponent(apiKey)}`,
{
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: body.toString(),
}
);

if (!res.ok) {
const detail = await res.text();
console.error("JotForm submission failed:", res.status, detail);
return Response.json(
{ message: "Failed to submit lead." },
{ status: 502 }
);
}

return Response.json({ ok: true }, { status: 200 });
} catch (error) {
console.error("lead-capture error:", error);
return Response.json({ message: "Unexpected error." }, { status: 500 });
}
}

export async function GET() {
return Response.json({ message: "Unsupported method" }, { status: 405 });
}
2 changes: 1 addition & 1 deletion app/consulting/[filename]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ export async function generateMetadata(

const seo =
newPage?.props?.data?.consultingv2?.seo ||
oldPage.props?.data?.consulting?.seo;
oldPage?.props?.data?.consulting?.seo;

const headerUrl = newPage?.props?.header?.url || oldPage?.props?.header?.url;
if (seo && !seo.canonical) {
Expand Down
5 changes: 5 additions & 0 deletions app/consulting/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ export default function ConsultingIndex({ tinaProps }) {
const categories = useMemo(() => {
return node.categories.reduce((acc, curr) => {
const mappedPages = curr.pages.reduce((pageAcc, p) => {
// Skip entries with neither an external URL nor a linked page —
// otherwise reading p.page.id below crashes the build prerender.
if (!p.externalUrl && !p.page?.id) {
return pageAcc;
}
const mappedPage = {
url:
p.externalUrl ||
Expand Down
68 changes: 68 additions & 0 deletions components/blocks-renderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,62 @@ const Spacer = dynamic(() =>
import("./blocks/spacer/spacer").then((mod) => mod.Spacer)
);

const V3Hero = dynamic(() =>
import("./blocks/v3/hero/hero").then((mod) => mod.V3Hero)
);

const V3LogoCarousel = dynamic(() =>
import("./blocks/v3/logoCarousel/logoCarousel").then(
(mod) => mod.V3LogoCarousel
)
);

const V3FeatureSteps = dynamic(() =>
import("./blocks/v3/featureSteps/featureSteps").then(
(mod) => mod.V3FeatureSteps
)
);

const V3Process = dynamic(() =>
import("./blocks/v3/process/process").then((mod) => mod.V3Process)
);

const V3Statistics = dynamic(() => import("./blocks/v3/statistics/statistics"));

const V3Cta = dynamic(() =>
import("./blocks/v3/cta/cta").then((mod) => mod.V3Cta)
);

const V3Testimonials = dynamic(() =>
import("./blocks/v3/testimonials/testimonials").then(
(mod) => mod.V3Testimonials
)
);

const V3StackCards = dynamic(() =>
import("./blocks/v3/stackCards/stackCards").then((mod) => mod.V3StackCards)
);

const V3Faq = dynamic(() =>
import("./blocks/v3/faq/faq").then((mod) => mod.V3Faq)
);

const V3LeadCapture = dynamic(() =>
import("./blocks/v3/leadCapture/leadCapture").then((mod) => mod.V3LeadCapture)
);

const V3VideoHighlights = dynamic(() =>
import("./blocks/v3/videoHighlights/videoHighlights").then(
(mod) => mod.V3VideoHighlights
)
);

const V3CardCarousel = dynamic(() =>
import("./blocks/v3/cardCarousel/cardCarousel").then(
(mod) => mod.V3CardCarousel
)
);

const componentMap = {
AboutUs,
Carousel,
Expand Down Expand Up @@ -198,6 +254,18 @@ const componentMap = {
TechnologyCardCarousel,
Spacer,
UtilityButton,
V3Hero,
V3LogoCarousel,
V3FeatureSteps,
V3Process,
V3Statistics,
V3Cta,
V3Testimonials,
V3StackCards,
V3Faq,
V3LeadCapture,
V3VideoHighlights,
V3CardCarousel,
};

export const Blocks = ({ prefix, blocks }) => {
Expand Down
24 changes: 24 additions & 0 deletions components/blocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,12 +54,36 @@ import { SpacerSchema } from "./spacer/spacer.schema";
import { tableBlockSchema } from "./tableLayout.schema";
import { testimonialsListSchema } from "./testimonialsList";
import { upcomingEventsBlockSchema } from "./upcomingEvents";
import { V3FeatureStepsSchema } from "./v3/featureSteps/featureSteps.schema";
import { V3HeroSchema } from "./v3/hero/hero.schema";
import { V3ProcessSchema } from "./v3/process/process.schema";
import { V3StatisticsTemplate } from "./v3/statistics/statistics.schema";
import { V3CtaSchema } from "./v3/cta/cta.schema";
import { V3LogoCarouselSchema } from "./v3/logoCarousel/logoCarousel.schema";
import { V3TestimonialsSchema } from "./v3/testimonials/testimonials.schema";
import { V3StackCardsSchema } from "./v3/stackCards/stackCards.schema";
import { V3FaqSchema } from "./v3/faq/faq.schema";
import { V3LeadCaptureSchema } from "./v3/leadCapture/leadCapture.template";
import { V3VideoHighlightsSchema } from "./v3/videoHighlights/videoHighlights.schema";
import { V3CardCarouselSchema } from "./v3/cardCarousel/cardCarousel.schema";
import { verticalImageLayoutBlockSchema } from "./verticalImageLayout";
import { verticalListItemSchema } from "./verticalListItem";
import { videoEmbedBlockSchema } from "./videoEmbed.schema";

//NOTE: this is the order that blocks will appear in the Tina Editor
export const pageBlocks: Template[] = [
V3HeroSchema,
V3LogoCarouselSchema,
V3FeatureStepsSchema,
V3ProcessSchema,
V3StatisticsTemplate,
V3CtaSchema,
V3TestimonialsSchema,
V3StackCardsSchema,
V3FaqSchema,
V3LeadCaptureSchema,
V3VideoHighlightsSchema,
V3CardCarouselSchema,
BreadcrumbSchema,
ImageTextBlockSchema,
LogoCarouselSchema,
Expand Down
Loading
Loading