- initial commit
This commit is contained in:
commit
fbebfef8aa
4
.dockerignore
Normal file
4
.dockerignore
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
.react-router
|
||||||
|
build
|
||||||
|
node_modules
|
||||||
|
README.md
|
||||||
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
node_modules
|
||||||
9
.react-router/types/+future.ts
Normal file
9
.react-router/types/+future.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// Generated by React Router
|
||||||
|
|
||||||
|
import "react-router";
|
||||||
|
|
||||||
|
declare module "react-router" {
|
||||||
|
interface Future {
|
||||||
|
v8_middleware: false
|
||||||
|
}
|
||||||
|
}
|
||||||
69
.react-router/types/+routes.ts
Normal file
69
.react-router/types/+routes.ts
Normal 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
17
.react-router/types/+server-build.d.ts
vendored
Normal 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"];
|
||||||
|
}
|
||||||
59
.react-router/types/app/+types/root.ts
Normal file
59
.react-router/types/app/+types/root.ts
Normal 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"];
|
||||||
|
}
|
||||||
62
.react-router/types/app/routes/+types/$.ts
Normal file
62
.react-router/types/app/routes/+types/$.ts
Normal 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"];
|
||||||
|
}
|
||||||
62
.react-router/types/app/routes/+types/[_image].$.ts
Normal file
62
.react-router/types/app/routes/+types/[_image].$.ts
Normal 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"];
|
||||||
|
}
|
||||||
62
.react-router/types/app/routes/+types/[robots.txt].ts
Normal file
62
.react-router/types/app/routes/+types/[robots.txt].ts
Normal 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"];
|
||||||
|
}
|
||||||
@ -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"];
|
||||||
|
}
|
||||||
62
.react-router/types/app/routes/+types/_index.ts
Normal file
62
.react-router/types/app/routes/+types/_index.ts
Normal 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
3
.webstudio/config.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"projectId": "363652e3-9846-4534-acdf-9b2b3c66eec0"
|
||||||
|
}
|
||||||
6122
.webstudio/data.json
Normal file
6122
.webstudio/data.json
Normal file
File diff suppressed because it is too large
Load Diff
19
Dockerfile
Normal file
19
Dockerfile
Normal 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
47
app/__generated__/$.server.tsx
generated
Normal 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
144
app/__generated__/$.tsx
generated
Normal 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 }
|
||||||
|
|
||||||
8
app/__generated__/$resources.sitemap.xml.ts
generated
Normal file
8
app/__generated__/$resources.sitemap.xml.ts
generated
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
export const sitemap = [
|
||||||
|
{
|
||||||
|
"path": "/",
|
||||||
|
"lastModified": "2025-11-16"
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
45
app/__generated__/_index.server.tsx
generated
Normal file
45
app/__generated__/_index.server.tsx
generated
Normal 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
262
app/__generated__/_index.tsx
generated
Normal 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
464
app/__generated__/index.css
generated
Normal 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
35
app/constants.mjs
Normal 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
13
app/extension.ts
Normal 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
39
app/root.tsx
Normal 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
4
app/routes.ts
Normal 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
315
app/routes/$.tsx
Normal 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
24
app/routes/[_image].$.ts
Normal 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);
|
||||||
|
};
|
||||||
24
app/routes/[robots.txt].tsx
Normal file
24
app/routes/[robots.txt].tsx
Normal 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,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
34
app/routes/[sitemap.xml]._index.tsx
Normal file
34
app/routes/[sitemap.xml]._index.tsx
Normal 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
315
app/routes/_index.tsx
Normal 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
11084
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
package.json
Normal file
39
package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
public/assets/download_PEKg__YwrTvWxyvw7zOpK.jpeg
Normal file
BIN
public/assets/download_PEKg__YwrTvWxyvw7zOpK.jpeg
Normal file
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
BIN
public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
29
tsconfig.json
Normal file
29
tsconfig.json
Normal 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
14
vite.config.ts
Normal 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"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
Loading…
x
Reference in New Issue
Block a user