- initial commit

This commit is contained in:
oonyeje 2025-11-16 09:49:07 -05:00
commit fbebfef8aa
37 changed files with 19556 additions and 0 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
.react-router
build
node_modules
README.md

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

4
.npmrc Normal file
View File

@ -0,0 +1,4 @@
force=true
loglevel=error
audit=false
fund=false

View File

@ -0,0 +1,9 @@
// Generated by React Router
import "react-router";
declare module "react-router" {
interface Future {
v8_middleware: false
}
}

View File

@ -0,0 +1,69 @@
// Generated by React Router
import "react-router"
declare module "react-router" {
interface Register {
pages: Pages
routeFiles: RouteFiles
routeModules: RouteModules
}
}
type Pages = {
"/": {
params: {};
};
"/sitemap.xml": {
params: {};
};
"/robots.txt": {
params: {};
};
"/_image/*": {
params: {
"*": string;
};
};
"/*": {
params: {
"*": string;
};
};
};
type RouteFiles = {
"root.tsx": {
id: "root";
page: "/" | "/sitemap.xml" | "/robots.txt" | "/_image/*" | "/*";
};
"routes/[sitemap.xml]._index.tsx": {
id: "routes/[sitemap.xml]._index";
page: "/sitemap.xml";
};
"routes/[robots.txt].tsx": {
id: "routes/[robots.txt]";
page: "/robots.txt";
};
"routes/[_image].$.ts": {
id: "routes/[_image].$";
page: "/_image/*";
};
"routes/_index.tsx": {
id: "routes/_index";
page: "/";
};
"routes/$.tsx": {
id: "routes/$";
page: "/*";
};
};
type RouteModules = {
"root": typeof import("./app/root.tsx");
"routes/[sitemap.xml]._index": typeof import("./app/routes/[sitemap.xml]._index.tsx");
"routes/[robots.txt]": typeof import("./app/routes/[robots.txt].tsx");
"routes/[_image].$": typeof import("./app/routes/[_image].$.ts");
"routes/_index": typeof import("./app/routes/_index.tsx");
"routes/$": typeof import("./app/routes/$.tsx");
};

17
.react-router/types/+server-build.d.ts vendored Normal file
View File

@ -0,0 +1,17 @@
// Generated by React Router
declare module "virtual:react-router/server-build" {
import { ServerBuild } from "react-router";
export const assets: ServerBuild["assets"];
export const assetsBuildDirectory: ServerBuild["assetsBuildDirectory"];
export const basename: ServerBuild["basename"];
export const entry: ServerBuild["entry"];
export const future: ServerBuild["future"];
export const isSpaMode: ServerBuild["isSpaMode"];
export const prerender: ServerBuild["prerender"];
export const publicPath: ServerBuild["publicPath"];
export const routeDiscovery: ServerBuild["routeDiscovery"];
export const routes: ServerBuild["routes"];
export const ssr: ServerBuild["ssr"];
export const unstable_getCriticalCss: ServerBuild["unstable_getCriticalCss"];
}

View File

@ -0,0 +1,59 @@
// Generated by React Router
import type { GetInfo, GetAnnotations } from "react-router/internal";
type Module = typeof import("../root.js")
type Info = GetInfo<{
file: "root.tsx",
module: Module
}>
type Matches = [{
id: "root";
module: typeof import("../root.js");
}];
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, false>;
export namespace Route {
// links
export type LinkDescriptors = Annotations["LinkDescriptors"];
export type LinksFunction = Annotations["LinksFunction"];
// meta
export type MetaArgs = Annotations["MetaArgs"];
export type MetaDescriptors = Annotations["MetaDescriptors"];
export type MetaFunction = Annotations["MetaFunction"];
// headers
export type HeadersArgs = Annotations["HeadersArgs"];
export type HeadersFunction = Annotations["HeadersFunction"];
// middleware
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
// clientMiddleware
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
// loader
export type LoaderArgs = Annotations["LoaderArgs"];
// clientLoader
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
// action
export type ActionArgs = Annotations["ActionArgs"];
// clientAction
export type ClientActionArgs = Annotations["ClientActionArgs"];
// HydrateFallback
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
// Component
export type ComponentProps = Annotations["ComponentProps"];
// ErrorBoundary
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
}

View File

@ -0,0 +1,62 @@
// Generated by React Router
import type { GetInfo, GetAnnotations } from "react-router/internal";
type Module = typeof import("../$.js")
type Info = GetInfo<{
file: "routes/$.tsx",
module: Module
}>
type Matches = [{
id: "root";
module: typeof import("../../root.js");
}, {
id: "routes/$";
module: typeof import("../$.js");
}];
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, false>;
export namespace Route {
// links
export type LinkDescriptors = Annotations["LinkDescriptors"];
export type LinksFunction = Annotations["LinksFunction"];
// meta
export type MetaArgs = Annotations["MetaArgs"];
export type MetaDescriptors = Annotations["MetaDescriptors"];
export type MetaFunction = Annotations["MetaFunction"];
// headers
export type HeadersArgs = Annotations["HeadersArgs"];
export type HeadersFunction = Annotations["HeadersFunction"];
// middleware
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
// clientMiddleware
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
// loader
export type LoaderArgs = Annotations["LoaderArgs"];
// clientLoader
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
// action
export type ActionArgs = Annotations["ActionArgs"];
// clientAction
export type ClientActionArgs = Annotations["ClientActionArgs"];
// HydrateFallback
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
// Component
export type ComponentProps = Annotations["ComponentProps"];
// ErrorBoundary
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
}

View File

@ -0,0 +1,62 @@
// Generated by React Router
import type { GetInfo, GetAnnotations } from "react-router/internal";
type Module = typeof import("../[_image].$.js")
type Info = GetInfo<{
file: "routes/[_image].$.ts",
module: Module
}>
type Matches = [{
id: "root";
module: typeof import("../../root.js");
}, {
id: "routes/[_image].$";
module: typeof import("../[_image].$.js");
}];
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, false>;
export namespace Route {
// links
export type LinkDescriptors = Annotations["LinkDescriptors"];
export type LinksFunction = Annotations["LinksFunction"];
// meta
export type MetaArgs = Annotations["MetaArgs"];
export type MetaDescriptors = Annotations["MetaDescriptors"];
export type MetaFunction = Annotations["MetaFunction"];
// headers
export type HeadersArgs = Annotations["HeadersArgs"];
export type HeadersFunction = Annotations["HeadersFunction"];
// middleware
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
// clientMiddleware
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
// loader
export type LoaderArgs = Annotations["LoaderArgs"];
// clientLoader
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
// action
export type ActionArgs = Annotations["ActionArgs"];
// clientAction
export type ClientActionArgs = Annotations["ClientActionArgs"];
// HydrateFallback
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
// Component
export type ComponentProps = Annotations["ComponentProps"];
// ErrorBoundary
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
}

View File

@ -0,0 +1,62 @@
// Generated by React Router
import type { GetInfo, GetAnnotations } from "react-router/internal";
type Module = typeof import("../[robots.txt].js")
type Info = GetInfo<{
file: "routes/[robots.txt].tsx",
module: Module
}>
type Matches = [{
id: "root";
module: typeof import("../../root.js");
}, {
id: "routes/[robots.txt]";
module: typeof import("../[robots.txt].js");
}];
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, false>;
export namespace Route {
// links
export type LinkDescriptors = Annotations["LinkDescriptors"];
export type LinksFunction = Annotations["LinksFunction"];
// meta
export type MetaArgs = Annotations["MetaArgs"];
export type MetaDescriptors = Annotations["MetaDescriptors"];
export type MetaFunction = Annotations["MetaFunction"];
// headers
export type HeadersArgs = Annotations["HeadersArgs"];
export type HeadersFunction = Annotations["HeadersFunction"];
// middleware
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
// clientMiddleware
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
// loader
export type LoaderArgs = Annotations["LoaderArgs"];
// clientLoader
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
// action
export type ActionArgs = Annotations["ActionArgs"];
// clientAction
export type ClientActionArgs = Annotations["ClientActionArgs"];
// HydrateFallback
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
// Component
export type ComponentProps = Annotations["ComponentProps"];
// ErrorBoundary
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
}

View File

@ -0,0 +1,62 @@
// Generated by React Router
import type { GetInfo, GetAnnotations } from "react-router/internal";
type Module = typeof import("../[sitemap.xml]._index.js")
type Info = GetInfo<{
file: "routes/[sitemap.xml]._index.tsx",
module: Module
}>
type Matches = [{
id: "root";
module: typeof import("../../root.js");
}, {
id: "routes/[sitemap.xml]._index";
module: typeof import("../[sitemap.xml]._index.js");
}];
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, false>;
export namespace Route {
// links
export type LinkDescriptors = Annotations["LinkDescriptors"];
export type LinksFunction = Annotations["LinksFunction"];
// meta
export type MetaArgs = Annotations["MetaArgs"];
export type MetaDescriptors = Annotations["MetaDescriptors"];
export type MetaFunction = Annotations["MetaFunction"];
// headers
export type HeadersArgs = Annotations["HeadersArgs"];
export type HeadersFunction = Annotations["HeadersFunction"];
// middleware
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
// clientMiddleware
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
// loader
export type LoaderArgs = Annotations["LoaderArgs"];
// clientLoader
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
// action
export type ActionArgs = Annotations["ActionArgs"];
// clientAction
export type ClientActionArgs = Annotations["ClientActionArgs"];
// HydrateFallback
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
// Component
export type ComponentProps = Annotations["ComponentProps"];
// ErrorBoundary
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
}

View File

@ -0,0 +1,62 @@
// Generated by React Router
import type { GetInfo, GetAnnotations } from "react-router/internal";
type Module = typeof import("../_index.js")
type Info = GetInfo<{
file: "routes/_index.tsx",
module: Module
}>
type Matches = [{
id: "root";
module: typeof import("../../root.js");
}, {
id: "routes/_index";
module: typeof import("../_index.js");
}];
type Annotations = GetAnnotations<Info & { module: Module, matches: Matches }, false>;
export namespace Route {
// links
export type LinkDescriptors = Annotations["LinkDescriptors"];
export type LinksFunction = Annotations["LinksFunction"];
// meta
export type MetaArgs = Annotations["MetaArgs"];
export type MetaDescriptors = Annotations["MetaDescriptors"];
export type MetaFunction = Annotations["MetaFunction"];
// headers
export type HeadersArgs = Annotations["HeadersArgs"];
export type HeadersFunction = Annotations["HeadersFunction"];
// middleware
export type MiddlewareFunction = Annotations["MiddlewareFunction"];
// clientMiddleware
export type ClientMiddlewareFunction = Annotations["ClientMiddlewareFunction"];
// loader
export type LoaderArgs = Annotations["LoaderArgs"];
// clientLoader
export type ClientLoaderArgs = Annotations["ClientLoaderArgs"];
// action
export type ActionArgs = Annotations["ActionArgs"];
// clientAction
export type ClientActionArgs = Annotations["ClientActionArgs"];
// HydrateFallback
export type HydrateFallbackProps = Annotations["HydrateFallbackProps"];
// Component
export type ComponentProps = Annotations["ComponentProps"];
// ErrorBoundary
export type ErrorBoundaryProps = Annotations["ErrorBoundaryProps"];
}

3
.webstudio/config.json Normal file
View File

@ -0,0 +1,3 @@
{
"projectId": "363652e3-9846-4534-acdf-9b2b3c66eec0"
}

6122
.webstudio/data.json Normal file

File diff suppressed because it is too large Load Diff

19
Dockerfile Normal file
View File

@ -0,0 +1,19 @@
FROM node:22-alpine AS dependencies-env
COPY .npmrc package.json /app/
WORKDIR /app
RUN npm install --omit=dev
FROM dependencies-env AS build-env
COPY . /app/
WORKDIR /app
RUN npm install
RUN npm run build
FROM node:22-alpine
COPY .npmrc package.json /app/
COPY --from=dependencies-env /app/node_modules /app/node_modules
COPY --from=build-env /app/build /app/build
COPY --from=build-env /app/public /app/public
WORKDIR /app
# there is a DOMAINS env with comma separated allowed domains for image processing
CMD ["npm", "run", "start"]

47
app/__generated__/$.server.tsx generated Normal file
View File

@ -0,0 +1,47 @@
/* eslint-disable */
/* This is a auto generated file for building the project */
import type { PageMeta } from "@webstudio-is/sdk";
import type { System, ResourceRequest } from "@webstudio-is/sdk";
export const getResources = (_props: { system: System }) => {
const _data = new Map<string, ResourceRequest>([
])
const _action = new Map<string, ResourceRequest>([
])
return { data: _data, action: _action }
}
export const getPageMeta = ({
system,
resources,
}: {
system: System;
resources: Record<string, any>;
}): PageMeta => {
return {
title: "Page not found",
description: undefined,
excludePageFromSearch: false,
language: undefined,
socialImageAssetName: undefined,
socialImageUrl: undefined,
status: 404,
redirect: undefined,
custom: [
],
};
};
type Params = Record<string, string | undefined>;
export const getRemixParams = ({ ...params }: Params): Params => {
params[0] = params["*"]
delete params["*"]
return params
}
export const contactEmail = "inasahealthcare@gmail.com";

144
app/__generated__/$.tsx generated Normal file
View File

@ -0,0 +1,144 @@
/* eslint-disable */
/* This is a auto generated file for building the project */
import { Fragment, useState } from "react";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body, Link as Link } from "@webstudio-is/sdk-components-react-router";
import { HtmlEmbed as HtmlEmbed, Fragment as Fragment_1, Image as Image, Slot as Slot } from "@webstudio-is/sdk-components-react";
export const projectId = "363652e3-9846-4534-acdf-9b2b3c66eec0";
export const lastPublished = "2025-11-16T14:37:49.266Z";
export const siteName = "Inasa Healthcare";
export const breakpoints = [{"id":"5zaWVFAeAfWgFjJNQ0GET"},{"id":"qOHlWr9cjM-6l364uX_FK","maxWidth":991},{"id":"8_hh5VMsAyWW46cnpT5HQ","maxWidth":767},{"id":"swNXPQRoKH4ij-T-zGDE7","maxWidth":479}];
export const favIconAsset: string | undefined =
"file_000000008414722f840750ada910d677_ohsCCBuh32fBBanvmDmte.png";
// Font assets on current page (can be preloaded)
export const pageFontAssets: string[] =
[]
export const pageBackgroundImageAssets: string[] =
[]
const Page = (_props: { system: any; }) => {
return <Body
className={`w-element w-element-1`}>
<Slot>
<Fragment_1>
<div
className={`w-element w-element-7`}>
<Link
href={"/"}
className={`w-element w-element-8`}>
<Image
src={"/assets/file_000000008414722f840750ada910d677_ohsCCBuh32fBBanvmDmte.png"}
width={1024}
height={1024}
loading={"lazy"}
alt={"logo"}
className={`w-image w-image-1`} />
<h3
className={`w-element w-element-9`}>
{"Inasa"}
{""}
<br />
{""}
{"Healthcare, LLC"}
</h3>
</Link>
<div
className={`w-element w-element-10`}>
<div
className={`w-element w-element-11`}>
<Link
href={"/about"}
className={`w-element w-element-12`}>
<b
className={`w-element w-element-28`}>
{"About"}
</b>
</Link>
<Link
href={"/insurance"}
className={`w-element w-element-13`}>
<b
className={`w-element`}>
{"Insurance"}
</b>
</Link>
<Link
href={"/services"}
className={`w-element w-element-14`}>
<b
className={`w-element`}>
{"Services"}
</b>
</Link>
<Link
href={"/contact"}
className={`w-element w-element-15`}>
<b
className={`w-element`}>
{"Contact"}
</b>
</Link>
</div>
<div
className={`w-element w-element-16`}>
<Link
href={"https://portal.kareo.com/app/new/login"}
className={`w-element w-element-17`}>
{"Patient Portal"}
</Link>
</div>
</div>
</div>
</Fragment_1>
</Slot>
<div
className={`w-element w-element-36`}>
<div
className={`w-element w-element-2`}>
<div
className={`w-element`}>
{"404"}
</div>
<div
className={`w-element w-element-3`}>
{"404"}
</div>
<div
className={`w-element w-element-4`}>
{"404"}
</div>
<div
className={`w-element w-element-5`} />
</div>
<p
className={`w-element w-element-6`}>
{"PAGE NOT FOUND"}
</p>
</div>
<Link
href={"https://bsidesol.com/"}
target={"_blank"}
className={`w-element w-built-by-b-side`}>
<div
className={`w-element`}>
{"Powered By BSide Solutions"}
</div>
</Link>
</Body>
}
export { Page }

View File

@ -0,0 +1,8 @@
export const sitemap = [
{
"path": "/",
"lastModified": "2025-11-16"
}
];

45
app/__generated__/_index.server.tsx generated Normal file
View File

@ -0,0 +1,45 @@
/* eslint-disable */
/* This is a auto generated file for building the project */
import type { PageMeta } from "@webstudio-is/sdk";
import type { System, ResourceRequest } from "@webstudio-is/sdk";
export const getResources = (_props: { system: System }) => {
const _data = new Map<string, ResourceRequest>([
])
const _action = new Map<string, ResourceRequest>([
])
return { data: _data, action: _action }
}
export const getPageMeta = ({
system,
resources,
}: {
system: System;
resources: Record<string, any>;
}): PageMeta => {
return {
title: "Home",
description: undefined,
excludePageFromSearch: undefined,
language: undefined,
socialImageAssetName: undefined,
socialImageUrl: undefined,
status: undefined,
redirect: undefined,
custom: [
],
};
};
type Params = Record<string, string | undefined>;
export const getRemixParams = ({ ...params }: Params): Params => {
return params
}
export const contactEmail = "inasahealthcare@gmail.com";

262
app/__generated__/_index.tsx generated Normal file
View File

@ -0,0 +1,262 @@
/* eslint-disable */
/* This is a auto generated file for building the project */
import { Fragment, useState } from "react";
import { useResource, useVariableState } from "@webstudio-is/react-sdk/runtime";
import { Body as Body, Link as Link } from "@webstudio-is/sdk-components-react-router";
import { Fragment as Fragment_1, Image as Image, Slot as Slot } from "@webstudio-is/sdk-components-react";
export const projectId = "363652e3-9846-4534-acdf-9b2b3c66eec0";
export const lastPublished = "2025-11-16T14:37:49.266Z";
export const siteName = "Inasa Healthcare";
export const breakpoints = [{"id":"5zaWVFAeAfWgFjJNQ0GET"},{"id":"qOHlWr9cjM-6l364uX_FK","maxWidth":991},{"id":"8_hh5VMsAyWW46cnpT5HQ","maxWidth":767},{"id":"swNXPQRoKH4ij-T-zGDE7","maxWidth":479}];
export const favIconAsset: string | undefined =
"file_000000008414722f840750ada910d677_ohsCCBuh32fBBanvmDmte.png";
// Font assets on current page (can be preloaded)
export const pageFontAssets: string[] =
[]
export const pageBackgroundImageAssets: string[] =
["download_PEKg__YwrTvWxyvw7zOpK.jpeg"]
export const CustomCode = () => {
return (<></>);
}
const Page = (_props: { system: any; }) => {
return <Body
className={`w-element`}>
<Slot>
<Fragment_1>
<div
className={`w-element w-element-7`}>
<Link
href={"/"}
className={`w-element w-element-8`}>
<Image
src={"/assets/file_000000008414722f840750ada910d677_ohsCCBuh32fBBanvmDmte.png"}
width={1024}
height={1024}
loading={"lazy"}
alt={"logo"}
className={`w-image w-image-1`} />
<h3
className={`w-element w-element-9`}>
{"Inasa"}
{""}
<br />
{""}
{"Healthcare, LLC"}
</h3>
</Link>
<div
className={`w-element w-element-10`}>
<div
className={`w-element w-element-11`}>
<Link
href={"/about"}
className={`w-element w-element-12`}>
<b
className={`w-element w-element-28`}>
{"About"}
</b>
</Link>
<Link
href={"/insurance"}
className={`w-element w-element-13`}>
<b
className={`w-element`}>
{"Insurance"}
</b>
</Link>
<Link
href={"/services"}
className={`w-element w-element-14`}>
<b
className={`w-element`}>
{"Services"}
</b>
</Link>
<Link
href={"/contact"}
className={`w-element w-element-15`}>
<b
className={`w-element`}>
{"Contact"}
</b>
</Link>
</div>
<div
className={`w-element w-element-16`}>
<Link
href={"https://portal.kareo.com/app/new/login"}
className={`w-element w-element-17`}>
{"Patient Portal"}
</Link>
</div>
</div>
</div>
</Fragment_1>
</Slot>
<div
className={`w-element w-page-content`}>
<div
className={`w-element w-hero`}>
<div
className={`w-element w-element-19`}>
<h1
className={`w-element w-element-18`}>
<b
className={`w-element`}>
{"Welcome to Inasa Healthcare, LLC"}
</b>
</h1>
</div>
</div>
<div
className={`w-element w-container`} />
</div>
<Slot>
<Fragment_1>
<div
className={`w-element w-element-20`}>
<div
href={"/"}
className={`w-element w-element-25`}>
<Link
href={"/"}
className={`w-element w-element-34`}>
<div
className={`w-element w-element-32`}>
<Image
src={"/assets/file_000000008414722f840750ada910d677_ohsCCBuh32fBBanvmDmte.png"}
width={1024}
height={1024}
loading={"lazy"}
alt={"logo"}
className={`w-image w-image-2`} />
</div>
<div
className={`w-element`}>
<h3
className={`w-element w-element-33`}>
{"Inasa"}
{""}
<br />
{""}
{"Healthcare, LLC"}
</h3>
</div>
</Link>
<div
className={`w-element w-element-29`}>
<h3
className={`w-element w-element-30`}>
<span
className={`w-element`}>
{"Quick Links"}
</span>
</h3>
<ul
className={`w-element w-element-31`}>
<li
className={`w-element`}>
{"Home"}
</li>
<li
className={`w-element`}>
{"About"}
</li>
<li
className={`w-element`}>
{"Insurance"}
</li>
<li
className={`w-element`}>
{"Services"}
</li>
<li
className={`w-element`}>
{"Contact"}
</li>
</ul>
</div>
</div>
<div
className={`w-element w-element-21`}>
<h3
className={`w-element w-element-26`}>
<span
className={`w-element`}>
{"Our Services"}
</span>
</h3>
<ul
className={`w-element w-element-22`}>
<li
className={`w-element`}>
{"View All Services"}
</li>
<li
className={`w-element`}>
<Link
href={"https://d2oe0ra32qx05a.cloudfront.net/?practiceKey=k_1_112536"}
className={`w-element w-element-35`}>
{"Book Appointment"}
</Link>
</li>
</ul>
</div>
<div
className={`w-element w-element-23`}>
<h3
className={`w-element w-element-27`}>
<span
className={`w-element`}>
{"Contact Info"}
</span>
</h3>
<ul
className={`w-element w-element-24`}>
<li
className={`w-element`}>
{"Home"}
</li>
<li
className={`w-element`}>
{"About"}
</li>
<li
className={`w-element`}>
{"Insurance"}
</li>
<li
className={`w-element`}>
{"Services"}
</li>
<li
className={`w-element`}>
{"Contact"}
</li>
</ul>
</div>
</div>
</Fragment_1>
</Slot>
</Body>
}
export { Page }

464
app/__generated__/index.css generated Normal file
View File

@ -0,0 +1,464 @@
@layer presets {
:root {
display: grid;
min-height: 100%;
font-family: Arial, Roboto, sans-serif;
font-size: 16px;
line-height: 1.2;
white-space: pre-wrap;
white-space-collapse: preserve
}
a.w-element {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
outline-width: 1px
}
b.w-element {
font-weight: 700;
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px
}
body.w-element {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
margin: 0
}
div.w-element {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
outline-width: 1px
}
h1.w-element {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
outline-width: 1px
}
h3.w-element {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
outline-width: 1px
}
li.w-element {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
outline-width: 1px
}
p.w-element {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
outline-width: 1px
}
span.w-element {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
outline-width: 1px
}
ul.w-element {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
outline-width: 1px
}
img.w-image {
box-sizing: border-box;
border-top-width: 1px;
border-right-width: 1px;
border-bottom-width: 1px;
border-left-width: 1px;
outline-width: 1px;
max-width: 100%;
display: block;
height: auto
}
div.w-html-embed {
display: contents;
white-space: normal;
white-space-collapse: collapse
}
}
@media all {
.w-element-1 {
display: flex;
justify-content: center;
align-items: start;
background-color: rgba(255, 255, 255, 1)
}
.w-element-2 {
position: relative;
text-align: center;
font-weight: 900;
font-size: 8rem;
line-height: 1;
letter-spacing: -0.05em
}
.w-element-3 {
position: absolute;
top: 0;
right: -0.125rem;
bottom: 0;
left: 0.125rem;
opacity: 0.3
}
.w-element-4 {
position: absolute;
top: 0;
right: 0.125rem;
bottom: 0;
left: -0.125rem;
opacity: 0.3
}
.w-element-5 {
position: absolute;
top: 50%;
left: 0;
width: 100%;
background-color: rgba(255, 255, 255, 1);
height: 0.375rem
}
.w-element-6 {
margin-top: 1.5rem;
font-weight: 700;
font-size: 1.5rem;
line-height: 2rem;
letter-spacing: 0.05em
}
.w-built-by-b-side {
display: inline-flex;
row-gap: 6px;
column-gap: 6px;
align-items: center;
justify-content: center;
position: fixed;
z-index: 1000;
padding-top: 6px;
padding-right: 10px;
padding-bottom: 6px;
padding-left: 10px;
right: 16px;
bottom: 16px;
color: rgba(251, 252, 253, 1);
font-family: system-ui, sans-serif;
font-size: 12px;
font-weight: 500;
line-height: 1;
border-top-left-radius: 9px;
border-top-right-radius: 9px;
border-bottom-right-radius: 9px;
border-bottom-left-radius: 9px;
text-decoration-line: none;
-webkit-background-clip: padding-box, border-box;
background-clip: padding-box, border-box;
background-origin: padding-box, border-box;
background-image: linear-gradient(135deg,#4a4efa 0%,#bd2fdb 66%,#ec59ce 100%), linear-gradient(135deg,#92fddc 0%,#7d7ffb 31.94%,#ed72fe 64.24%,#fdd791 100%);
border: 1px solid transparent;
white-space: nowrap
}
.w-logo {
display: block;
width: 16px;
height: 16px;
flex-shrink: 0
}
.w-element-7 {
display: flex;
flex-direction: row;
justify-content: space-between;
height: 15svh;
position: fixed;
padding-left: 10%;
padding-right: 10%;
width: 100%;
background-image: none;
background-size: auto auto;
background-repeat: repeat;
background-attachment: scroll;
background-origin: padding-box;
-webkit-background-clip: border-box;
background-clip: border-box;
background-color: rgba(255, 255, 255, 1);
box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.2);
background-position: 0% 0%
}
.w-element-8 {
align-self: center;
display: flex;
flex-direction: row;
column-gap: 8px;
align-items: center;
justify-content: start;
flex-wrap: nowrap;
width: 40%;
text-decoration-line: none
}
.w-image-1 {
width: 25%;
height: 25%;
align-self: center
}
.w-element-9 {
font-family: ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa, Manjari, "Arial Rounded MT", "Arial Rounded MT Bold", Calibri, source-sans-pro, sans-serif;
color: rgba(22, 50, 113, 1)
}
.w-element-10 {
display: flex;
flex-direction: row;
justify-content: flex-end;
width: 70%;
column-gap: 1rem;
row-gap: 1rem
}
.w-element-11 {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
column-gap: 2rem;
row-gap: 2rem;
justify-content: end
}
.w-element-12 {
display: inline-block;
text-decoration-line: none;
color: rgba(0, 0, 0, 1)
}
.w-element-13 {
display: inline-block;
color: rgba(0, 0, 0, 1);
text-decoration-line: none
}
.w-element-14 {
display: inline-block;
color: rgba(0, 0, 0, 1);
text-decoration-line: none
}
.w-page-content {
background-color: rgba(253, 243, 243, 1);
margin-top: 15svh;
height: max-content
}
.w-container {
height: 80svh;
padding-left: 10%;
padding-right: 10%
}
.w-element-15 {
display: inline-block;
color: rgba(0, 0, 0, 1);
text-decoration-line: none
}
.w-hero {
height: 60svh;
display: flex;
flex-direction: row;
width: 100%;
background-image: url("/assets/download_PEKg__YwrTvWxyvw7zOpK.jpeg");
background-size: cover;
background-repeat: no-repeat;
background-attachment: scroll;
background-origin: padding-box;
-webkit-background-clip: border-box;
background-clip: border-box;
align-items: center;
justify-content: center;
background-position: 0% 0%
}
.w-element-16 {
width: auto;
display: flex;
column-gap: 20px;
row-gap: 20px;
align-self: center
}
.w-element-17 {
border-top-left-radius: 9%;
border-top-right-radius: 9%;
border-bottom-left-radius: 9%;
border-bottom-right-radius: 9%;
background-color: rgba(131, 208, 255, 1);
box-shadow: 0px 4px 10px 4px rgba(4, 30, 33, 0.05);
height: min-content;
width: auto;
min-width: 8rem;
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
background-image: linear-gradient(135deg,#4a4efa 0%,#bd2fdb 66%,#ec59ce 100%);
background-size: auto auto;
background-repeat: repeat;
background-attachment: scroll;
background-origin: padding-box;
-webkit-background-clip: border-box;
background-clip: border-box;
background-blend-mode: soft-light;
color: rgba(255, 255, 255, 1);
min-height: max-content;
text-decoration-line: none;
border: 1px none rgba(0, 0, 0, 0.89);
padding: 4px;
background-position: 0% 0%
}
.w-element-18 {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
flex-wrap: nowrap;
color: rgba(7, 7, 7, 1)
}
.w-element-19 {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center
}
.w-element-20 {
background-color: rgba(131, 208, 255, 1);
height: 45svh;
padding-left: 10%;
padding-right: 10%;
display: flex;
align-items: start;
flex-direction: row;
width: 100%;
color: rgba(255, 255, 255, 1);
padding-top: 5%;
justify-content: space-between
}
.w-element-21 {
width: max-content;
display: flex;
flex-direction: column;
align-items: start
}
.w-element-22 {
list-style-type: none;
padding-left: 0px;
margin-top: 0em;
display: flex;
flex-direction: column;
column-gap: 1em;
row-gap: 1em
}
.w-element-23 {
width: max-content;
display: flex;
flex-direction: column;
align-items: start
}
.w-element-24 {
list-style-type: none;
padding-left: 0px;
margin-top: 0em;
display: flex;
flex-direction: column;
column-gap: 1em;
row-gap: 1em
}
.w-element-25 {
align-self: flex-start;
display: flex;
flex-direction: row;
column-gap: 32px;
align-items: start;
justify-content: start;
flex-wrap: nowrap;
width: 40%;
text-decoration-line: none;
row-gap: 32px
}
.w-element-26 {
margin-top: 0em
}
.w-element-27 {
margin-top: 0em
}
.w-element-29 {
width: max-content;
display: flex;
flex-direction: column;
align-items: start;
align-self: auto
}
.w-element-30 {
margin-top: 0em
}
.w-element-31 {
list-style-type: none;
padding-left: 0px;
margin-top: 0em;
display: flex;
flex-direction: column;
column-gap: 1em;
row-gap: 1em
}
.w-element-32 {
display: flex;
flex-direction: row
}
.w-image-2 {
align-self: center;
width: 5em
}
.w-element-33 {
font-family: ui-rounded, "Hiragino Maru Gothic ProN", Quicksand, Comfortaa, Manjari, "Arial Rounded MT", "Arial Rounded MT Bold", Calibri, source-sans-pro, sans-serif;
color: rgba(22, 50, 113, 1)
}
.w-element-34 {
text-decoration-line: none
}
.w-element-35 {
display: inline-block;
color: rgba(255, 255, 255, 1);
text-decoration-line: none
}
.w-element-36 {
align-self: center
}
}
@media all and (max-width: 991px) {
.w-element-11 {
column-gap: 0.8rem;
row-gap: 0.8rem
}
}
@media all and (max-width: 479px) {
.w-element-20 {
flex-direction: column;
height: 50%
}
.w-element-25 {
flex-direction: column
}
}

35
app/constants.mjs Normal file
View File

@ -0,0 +1,35 @@
/**
* We use mjs extension as constants in this file is shared with the build script
* and we use `node --eval` to extract the constants.
*/
export const assetBaseUrl = "/assets/";
/**
* URL.canParse(props.src)
* @type {(url: string) => boolean}
*/
const UrlCanParse = (url) => {
try {
new URL(url);
return true;
} catch {
return false;
}
};
/**
* @type {import("@webstudio-is/image").ImageLoader}
*/
export const imageLoader = (props) => {
if (props.format === "raw") {
return props.src;
}
// IPX (sharp) does not support ico
if (props.src.endsWith('.ico')) {
return props.src;
}
// handle absolute urls
const path = UrlCanParse(props.src) ? `/${props.src}` : props.src;
// https://github.com/unjs/ipx?tab=readme-ov-file#modifiers
return `/_image/w_${props.width},q_${props.quality}${path}`;
};

13
app/extension.ts Normal file
View File

@ -0,0 +1,13 @@
import { ResourceRequest } from "@webstudio-is/sdk";
declare module "react-router" {
interface AppLoadContext {
EXCLUDE_FROM_SEARCH: boolean;
getDefaultActionResource?: (options: {
url: URL;
projectId: string;
contactEmail: string;
formData: FormData;
}) => ResourceRequest;
}
}

39
app/root.tsx Normal file
View File

@ -0,0 +1,39 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { Links, Meta, Outlet, useMatches } from "react-router";
// @todo think about how to make __generated__ typeable
// @ts-ignore
import { CustomCode, projectId, lastPublished } from "./__generated__/_index";
const Root = () => {
// Get language from matches
const matches = useMatches();
const lastMatchWithLanguage = matches.findLast((match) => {
// @ts-ignore
const language = match?.data?.pageMeta?.language;
return language != null;
});
// @ts-ignore
const lang = lastMatchWithLanguage?.data?.pageMeta?.language ?? "en";
return (
<html
lang={lang}
data-ws-project={projectId}
data-ws-last-published={lastPublished}
>
<head>
<meta charSet="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<Meta />
<Links />
<CustomCode />
</head>
<Outlet />
</html>
);
};
export default Root;

4
app/routes.ts Normal file
View File

@ -0,0 +1,4 @@
import { type RouteConfig } from "@react-router/dev/routes";
import { flatRoutes } from "@react-router/fs-routes";
export default flatRoutes() satisfies RouteConfig;

315
app/routes/$.tsx Normal file
View File

@ -0,0 +1,315 @@
import {
type MetaFunction,
type LinksFunction,
type LinkDescriptor,
type ActionFunctionArgs,
type LoaderFunctionArgs,
type HeadersFunction,
data,
redirect,
useLoaderData,
} from "react-router";
import {
isLocalResource,
loadResource,
loadResources,
formIdFieldName,
formBotFieldName,
cachedFetch,
} from "@webstudio-is/sdk/runtime";
import {
ReactSdkContext,
PageSettingsMeta,
PageSettingsTitle,
} from "@webstudio-is/react-sdk/runtime";
import {
projectId,
Page,
siteName,
favIconAsset,
pageFontAssets,
pageBackgroundImageAssets,
breakpoints,
} from "../__generated__/$";
import {
getResources,
getPageMeta,
getRemixParams,
contactEmail,
} from "../__generated__/$.server";
import * as constants from "../constants.mjs";
import css from "../__generated__/index.css?url";
import { sitemap } from "../__generated__/$resources.sitemap.xml";
const customFetch: typeof fetch = (input, init) => {
if (typeof input !== "string") {
return cachedFetch(projectId, input, init);
}
if (isLocalResource(input, "sitemap.xml")) {
// @todo: dynamic import sitemap ???
const response = new Response(JSON.stringify(sitemap));
response.headers.set("content-type", "application/json; charset=utf-8");
return Promise.resolve(response);
}
if (isLocalResource(input, "current-date")) {
const now = new Date();
// Normalize to midnight UTC to prevent hydration mismatches
const startOfDay = new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
);
const data = {
iso: startOfDay.toISOString(),
year: startOfDay.getUTCFullYear(),
month: startOfDay.getUTCMonth() + 1, // 1-12 instead of 0-11
day: startOfDay.getUTCDate(),
timestamp: startOfDay.getTime(),
};
const response = new Response(JSON.stringify(data));
response.headers.set("content-type", "application/json; charset=utf-8");
return Promise.resolve(response);
}
return cachedFetch(projectId, input, init);
};
export const loader = async (arg: LoaderFunctionArgs) => {
const url = new URL(arg.request.url);
const host =
arg.request.headers.get("x-forwarded-host") ||
arg.request.headers.get("host") ||
"";
url.host = host;
url.protocol = "https";
const params = getRemixParams(arg.params);
const system = {
params,
search: Object.fromEntries(url.searchParams),
origin: url.origin,
pathname: url.pathname,
};
const resources = await loadResources(
customFetch,
getResources({ system }).data
);
const pageMeta = getPageMeta({ system, resources });
if (pageMeta.redirect) {
const status =
pageMeta.status === 301 || pageMeta.status === 302
? pageMeta.status
: 302;
throw redirect(pageMeta.redirect, status);
}
// typecheck
arg.context.EXCLUDE_FROM_SEARCH satisfies boolean;
if (arg.context.EXCLUDE_FROM_SEARCH) {
pageMeta.excludePageFromSearch = arg.context.EXCLUDE_FROM_SEARCH;
}
return data(
{
host,
url: url.href,
system,
resources,
pageMeta,
},
// No way for current information to change, so add cache for 10 minutes
// In case of CRM Data, this should be set to 0
{
status: pageMeta.status,
headers: {
"Cache-Control": "public, max-age=600",
},
}
);
};
export const headers: HeadersFunction = () => {
return {
"Cache-Control": "public, max-age=0, must-revalidate",
};
};
export const meta: MetaFunction<typeof loader> = ({ data }) => {
const metas: ReturnType<MetaFunction> = [];
if (data === undefined) {
return metas;
}
const origin = `https://${data.host}`;
if (siteName) {
metas.push({
"script:ld+json": {
"@context": "https://schema.org",
"@type": "WebSite",
name: siteName,
url: origin,
},
});
}
return metas;
};
export const links: LinksFunction = () => {
const result: LinkDescriptor[] = [];
result.push({
rel: "stylesheet",
href: css,
});
if (favIconAsset) {
result.push({
rel: "icon",
href: constants.imageLoader({
src: `${constants.assetBaseUrl}${favIconAsset}`,
// width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search
width: 144,
height: 144,
fit: "pad",
quality: 100,
format: "auto",
}),
type: undefined,
});
}
for (const asset of pageFontAssets) {
result.push({
rel: "preload",
href: `${constants.assetBaseUrl}${asset}`,
as: "font",
crossOrigin: "anonymous",
});
}
for (const backgroundImageAsset of pageBackgroundImageAssets) {
result.push({
rel: "preload",
href: `${constants.assetBaseUrl}${backgroundImageAsset}`,
as: "image",
});
}
return result;
};
const getRequestHost = (request: Request): string =>
request.headers.get("x-forwarded-host") || request.headers.get("host") || "";
export const action = async ({
request,
context,
}: ActionFunctionArgs): Promise<
{ success: true } | { success: false; errors: string[] }
> => {
try {
const url = new URL(request.url);
url.host = getRequestHost(request);
const formData = await request.formData();
const system = {
params: {},
search: {},
origin: url.origin,
pathname: url.pathname,
};
const resourceName = formData.get(formIdFieldName);
let resource =
typeof resourceName === "string"
? getResources({ system }).action.get(resourceName)
: undefined;
const formBotValue = formData.get(formBotFieldName);
if (formBotValue == null || typeof formBotValue !== "string") {
throw new Error("Form bot field not found");
}
const submitTime = parseInt(formBotValue, 16);
// Assumes that the difference between the server time and the form submission time,
// including any client-server time drift, is within a 5-minute range.
// Note: submitTime might be NaN because formBotValue can be any string used for logging purposes.
// Example: `formBotValue: jsdom`, or `formBotValue: headless-env`
if (
Number.isNaN(submitTime) ||
Math.abs(Date.now() - submitTime) > 1000 * 60 * 5
) {
throw new Error(`Form bot value invalid ${formBotValue}`);
}
formData.delete(formIdFieldName);
formData.delete(formBotFieldName);
if (resource) {
resource.body = Object.fromEntries(formData);
} else {
if (contactEmail === undefined) {
throw new Error("Contact email not found");
}
resource = context.getDefaultActionResource?.({
url,
projectId,
contactEmail,
formData,
});
}
if (resource === undefined) {
throw Error("Resource not found");
}
const { ok, statusText } = await loadResource(fetch, resource);
if (ok) {
return { success: true };
}
return { success: false, errors: [statusText] };
} catch (error) {
console.error(error);
return {
success: false,
errors: [error instanceof Error ? error.message : "Unknown error"],
};
}
};
const Outlet = () => {
const { system, resources, url, pageMeta, host } =
useLoaderData<typeof loader>();
return (
<ReactSdkContext.Provider
value={{
...constants,
resources,
breakpoints,
onError: console.error,
}}
>
{/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
<Page key={url} system={system} />
<PageSettingsMeta
url={url}
pageMeta={pageMeta}
host={host}
siteName={siteName}
imageLoader={constants.imageLoader}
assetBaseUrl={constants.assetBaseUrl}
/>
<PageSettingsTitle>{pageMeta.title}</PageSettingsTitle>
</ReactSdkContext.Provider>
);
};
export default Outlet;

24
app/routes/[_image].$.ts Normal file
View File

@ -0,0 +1,24 @@
import { env } from "node:process";
import type { LoaderFunctionArgs } from "react-router";
import {
createIPX,
createIPXH3Handler,
ipxFSStorage,
ipxHttpStorage,
} from "ipx";
import { createApp, toWebHandler } from "h3";
const domains = env.DOMAINS?.split(/\s*,\s*/) ?? [];
const ipx = createIPX({
storage: ipxFSStorage({ dir: "./public" }),
httpStorage: ipxHttpStorage({ domains }),
});
const handleRequest = toWebHandler(
createApp().use("/_image", createIPXH3Handler(ipx))
);
export const loader = async (args: LoaderFunctionArgs) => {
return handleRequest(args.request);
};

View File

@ -0,0 +1,24 @@
import type { LoaderFunctionArgs } from "react-router";
export const loader = (arg: LoaderFunctionArgs) => {
const host =
arg.request.headers.get("x-forwarded-host") ||
arg.request.headers.get("host") ||
"";
return new Response(
`
User-agent: *
Disallow: /api/
Sitemap: https://${host}/sitemap.xml
`,
{
headers: {
"Content-Type": "text/plain",
},
status: 200,
}
);
};

View File

@ -0,0 +1,34 @@
import type { LoaderFunctionArgs } from "react-router";
import { sitemap } from "../__generated__/$resources.sitemap.xml";
export const loader = (arg: LoaderFunctionArgs) => {
const host =
arg.request.headers.get("x-forwarded-host") ||
arg.request.headers.get("host") ||
"";
const urls = sitemap.map((page) => {
const url = new URL(`https://${host}${page.path}`);
return `
<url>
<loc>${url.href}</loc>
<lastmod>${page.lastModified.split("T")[0]}</lastmod>
</url>
`;
});
return new Response(
`<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
${urls.join("")}
</urlset>
`,
{
headers: {
"Content-Type": "application/xml",
},
status: 200,
}
);
};

315
app/routes/_index.tsx Normal file
View File

@ -0,0 +1,315 @@
import {
type MetaFunction,
type LinksFunction,
type LinkDescriptor,
type ActionFunctionArgs,
type LoaderFunctionArgs,
type HeadersFunction,
data,
redirect,
useLoaderData,
} from "react-router";
import {
isLocalResource,
loadResource,
loadResources,
formIdFieldName,
formBotFieldName,
cachedFetch,
} from "@webstudio-is/sdk/runtime";
import {
ReactSdkContext,
PageSettingsMeta,
PageSettingsTitle,
} from "@webstudio-is/react-sdk/runtime";
import {
projectId,
Page,
siteName,
favIconAsset,
pageFontAssets,
pageBackgroundImageAssets,
breakpoints,
} from "../__generated__/_index";
import {
getResources,
getPageMeta,
getRemixParams,
contactEmail,
} from "../__generated__/_index.server";
import * as constants from "../constants.mjs";
import css from "../__generated__/index.css?url";
import { sitemap } from "../__generated__/$resources.sitemap.xml";
const customFetch: typeof fetch = (input, init) => {
if (typeof input !== "string") {
return cachedFetch(projectId, input, init);
}
if (isLocalResource(input, "sitemap.xml")) {
// @todo: dynamic import sitemap ???
const response = new Response(JSON.stringify(sitemap));
response.headers.set("content-type", "application/json; charset=utf-8");
return Promise.resolve(response);
}
if (isLocalResource(input, "current-date")) {
const now = new Date();
// Normalize to midnight UTC to prevent hydration mismatches
const startOfDay = new Date(
Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate())
);
const data = {
iso: startOfDay.toISOString(),
year: startOfDay.getUTCFullYear(),
month: startOfDay.getUTCMonth() + 1, // 1-12 instead of 0-11
day: startOfDay.getUTCDate(),
timestamp: startOfDay.getTime(),
};
const response = new Response(JSON.stringify(data));
response.headers.set("content-type", "application/json; charset=utf-8");
return Promise.resolve(response);
}
return cachedFetch(projectId, input, init);
};
export const loader = async (arg: LoaderFunctionArgs) => {
const url = new URL(arg.request.url);
const host =
arg.request.headers.get("x-forwarded-host") ||
arg.request.headers.get("host") ||
"";
url.host = host;
url.protocol = "https";
const params = getRemixParams(arg.params);
const system = {
params,
search: Object.fromEntries(url.searchParams),
origin: url.origin,
pathname: url.pathname,
};
const resources = await loadResources(
customFetch,
getResources({ system }).data
);
const pageMeta = getPageMeta({ system, resources });
if (pageMeta.redirect) {
const status =
pageMeta.status === 301 || pageMeta.status === 302
? pageMeta.status
: 302;
throw redirect(pageMeta.redirect, status);
}
// typecheck
arg.context.EXCLUDE_FROM_SEARCH satisfies boolean;
if (arg.context.EXCLUDE_FROM_SEARCH) {
pageMeta.excludePageFromSearch = arg.context.EXCLUDE_FROM_SEARCH;
}
return data(
{
host,
url: url.href,
system,
resources,
pageMeta,
},
// No way for current information to change, so add cache for 10 minutes
// In case of CRM Data, this should be set to 0
{
status: pageMeta.status,
headers: {
"Cache-Control": "public, max-age=600",
},
}
);
};
export const headers: HeadersFunction = () => {
return {
"Cache-Control": "public, max-age=0, must-revalidate",
};
};
export const meta: MetaFunction<typeof loader> = ({ data }) => {
const metas: ReturnType<MetaFunction> = [];
if (data === undefined) {
return metas;
}
const origin = `https://${data.host}`;
if (siteName) {
metas.push({
"script:ld+json": {
"@context": "https://schema.org",
"@type": "WebSite",
name: siteName,
url: origin,
},
});
}
return metas;
};
export const links: LinksFunction = () => {
const result: LinkDescriptor[] = [];
result.push({
rel: "stylesheet",
href: css,
});
if (favIconAsset) {
result.push({
rel: "icon",
href: constants.imageLoader({
src: `${constants.assetBaseUrl}${favIconAsset}`,
// width,height must be multiple of 48 https://developers.google.com/search/docs/appearance/favicon-in-search
width: 144,
height: 144,
fit: "pad",
quality: 100,
format: "auto",
}),
type: undefined,
});
}
for (const asset of pageFontAssets) {
result.push({
rel: "preload",
href: `${constants.assetBaseUrl}${asset}`,
as: "font",
crossOrigin: "anonymous",
});
}
for (const backgroundImageAsset of pageBackgroundImageAssets) {
result.push({
rel: "preload",
href: `${constants.assetBaseUrl}${backgroundImageAsset}`,
as: "image",
});
}
return result;
};
const getRequestHost = (request: Request): string =>
request.headers.get("x-forwarded-host") || request.headers.get("host") || "";
export const action = async ({
request,
context,
}: ActionFunctionArgs): Promise<
{ success: true } | { success: false; errors: string[] }
> => {
try {
const url = new URL(request.url);
url.host = getRequestHost(request);
const formData = await request.formData();
const system = {
params: {},
search: {},
origin: url.origin,
pathname: url.pathname,
};
const resourceName = formData.get(formIdFieldName);
let resource =
typeof resourceName === "string"
? getResources({ system }).action.get(resourceName)
: undefined;
const formBotValue = formData.get(formBotFieldName);
if (formBotValue == null || typeof formBotValue !== "string") {
throw new Error("Form bot field not found");
}
const submitTime = parseInt(formBotValue, 16);
// Assumes that the difference between the server time and the form submission time,
// including any client-server time drift, is within a 5-minute range.
// Note: submitTime might be NaN because formBotValue can be any string used for logging purposes.
// Example: `formBotValue: jsdom`, or `formBotValue: headless-env`
if (
Number.isNaN(submitTime) ||
Math.abs(Date.now() - submitTime) > 1000 * 60 * 5
) {
throw new Error(`Form bot value invalid ${formBotValue}`);
}
formData.delete(formIdFieldName);
formData.delete(formBotFieldName);
if (resource) {
resource.body = Object.fromEntries(formData);
} else {
if (contactEmail === undefined) {
throw new Error("Contact email not found");
}
resource = context.getDefaultActionResource?.({
url,
projectId,
contactEmail,
formData,
});
}
if (resource === undefined) {
throw Error("Resource not found");
}
const { ok, statusText } = await loadResource(fetch, resource);
if (ok) {
return { success: true };
}
return { success: false, errors: [statusText] };
} catch (error) {
console.error(error);
return {
success: false,
errors: [error instanceof Error ? error.message : "Unknown error"],
};
}
};
const Outlet = () => {
const { system, resources, url, pageMeta, host } =
useLoaderData<typeof loader>();
return (
<ReactSdkContext.Provider
value={{
...constants,
resources,
breakpoints,
onError: console.error,
}}
>
{/* Use the URL as the key to force scripts in HTML Embed to reload on dynamic pages */}
<Page key={url} system={system} />
<PageSettingsMeta
url={url}
pageMeta={pageMeta}
host={host}
siteName={siteName}
imageLoader={constants.imageLoader}
assetBaseUrl={constants.assetBaseUrl}
/>
<PageSettingsTitle>{pageMeta.title}</PageSettingsTitle>
</ReactSdkContext.Provider>
);
};
export default Outlet;

11084
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

39
package.json Normal file
View File

@ -0,0 +1,39 @@
{
"type": "module",
"private": true,
"sideEffects": false,
"scripts": {
"build": "react-router build",
"dev": "react-router dev",
"typecheck": "tsc",
"start": "react-router-serve ./build/server/index.js"
},
"dependencies": {
"@react-router/dev": "^7.5.3",
"@react-router/fs-routes": "^7.5.3",
"@webstudio-is/image": "0.234.0",
"@webstudio-is/react-sdk": "0.234.0",
"@webstudio-is/sdk": "0.234.0",
"@webstudio-is/sdk-components-animation": "0.234.0",
"@webstudio-is/sdk-components-react-radix": "0.234.0",
"@webstudio-is/sdk-components-react-router": "0.234.0",
"@webstudio-is/sdk-components-react": "0.234.0",
"isbot": "^5.1.25",
"react": "18.3.0-canary-14898b6a9-20240318",
"react-dom": "18.3.0-canary-14898b6a9-20240318",
"react-router": "^7.5.3",
"vite": "^6.3.4",
"@react-router/node": "^7.5.3",
"@react-router/serve": "^7.5.3",
"h3": "^1.15.1",
"ipx": "^3.0.3"
},
"devDependencies": {
"@types/react": "^18.2.70",
"@types/react-dom": "^18.2.25",
"typescript": "5.8.2"
},
"engines": {
"node": ">=20.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

29
tsconfig.json Normal file
View File

@ -0,0 +1,29 @@
{
"include": [
"**/*.ts",
"**/*.tsx",
"**/*.mjs"
],
"compilerOptions": {
"lib": [
"DOM",
"DOM.Iterable",
"ESNext"
],
"types": [
"vite/client",
"@webstudio-is/react-sdk/placeholder"
],
"isolatedModules": true,
"esModuleInterop": true,
"jsx": "react-jsx",
"module": "ESNext",
"moduleResolution": "bundler",
"target": "ES2022",
"strict": true,
"allowJs": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"skipLibCheck": true
}
}

14
vite.config.ts Normal file
View File

@ -0,0 +1,14 @@
import { defineConfig } from "vite";
import { reactRouter } from "@react-router/dev/vite";
export default defineConfig({
plugins: [reactRouter()],
resolve: {
conditions: ["browser", "development|production"],
},
ssr: {
resolve: {
conditions: ["node", "development|production"],
},
},
});