chore: initialize project
Deploy monie-landing (kaniko) / build-and-deploy (push) Failing after 1m46s

This commit is contained in:
2026-04-04 21:36:32 +03:00
commit 014058071a
78 changed files with 12498 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
export * from "./scroll-to-section";
+33
View File
@@ -0,0 +1,33 @@
export function scrollToSection(id: string) {
if (typeof document === "undefined") {
return;
}
const section = document.getElementById(id);
if (!section) {
return;
}
section.scrollIntoView({ behavior: "smooth", block: "start" });
}
export function scrollToSectionOrNavigateHome(id: string) {
if (typeof window === "undefined") {
return;
}
if (window.location.pathname !== "/") {
window.location.href = `/#${id}`;
return;
}
const section = document.getElementById(id);
if (!section) {
window.location.hash = `#${id}`;
return;
}
section.scrollIntoView({ behavior: "smooth", block: "start" });
}
+1
View File
@@ -0,0 +1 @@
export * from "./locale-context";
+125
View File
@@ -0,0 +1,125 @@
import { useRouterState } from "@tanstack/react-router";
import type { ReactNode } from "react";
import {
createContext,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import { m } from "#/paraglide/messages";
import type { locales } from "#/paraglide/runtime";
import {
getLocale,
setLocale as setParaglideLocale,
} from "#/paraglide/runtime";
import { resolveSeoPage } from "./seo";
type Locale = (typeof locales)[number];
type LocaleContextValue = {
locale: Locale;
setLocale: (locale: Locale) => Promise<void>;
};
const LocaleContext = createContext<LocaleContextValue | null>(null);
function getCurrentLocale(): Locale {
return getLocale() as Locale;
}
export function LocaleProvider({ children }: { children: ReactNode }) {
const [locale, setLocaleState] = useState<Locale>(getCurrentLocale);
const pathname = useRouterState({
select: (state) => state.location.pathname,
});
const setLocale = useCallback(async (nextLocale: Locale) => {
await setParaglideLocale(nextLocale, { reload: false });
setLocaleState(getCurrentLocale());
}, []);
useEffect(() => {
if (typeof document !== "undefined") {
document.documentElement.lang = locale;
const seoPage = resolveSeoPage(pathname);
document.title =
seoPage === "about" ? m.seo_about_title() : m.seo_home_title();
const descriptionMeta = document.querySelector(
'meta[name="description"]',
);
if (descriptionMeta) {
descriptionMeta.setAttribute(
"content",
seoPage === "about"
? m.seo_about_description()
: m.seo_home_description(),
);
}
}
}, [locale, pathname]);
useEffect(() => {
if (typeof document === "undefined" || typeof window === "undefined") {
return;
}
const link = document.querySelector<HTMLLinkElement>("link#app-favicon");
if (!link) {
return;
}
const applyFavicon = () => {
const explicitTheme = document.documentElement.getAttribute("data-theme");
const prefersDark = window.matchMedia(
"(prefers-color-scheme: dark)",
).matches;
const isDark =
explicitTheme === "dark" || (explicitTheme !== "light" && prefersDark);
link.href = isDark ? "/favicon-dark.svg" : "/favicon-light.svg";
};
const media = window.matchMedia("(prefers-color-scheme: dark)");
const observer = new MutationObserver(applyFavicon);
applyFavicon();
media.addEventListener("change", applyFavicon);
observer.observe(document.documentElement, {
attributes: true,
attributeFilter: ["data-theme"],
});
return () => {
media.removeEventListener("change", applyFavicon);
observer.disconnect();
};
}, []);
const value = useMemo(
() => ({
locale,
setLocale,
}),
[locale, setLocale],
);
return (
<LocaleContext.Provider value={value}>{children}</LocaleContext.Provider>
);
}
export function useLocale() {
const context = useContext(LocaleContext);
if (!context) {
throw new Error("useLocale must be used within LocaleProvider");
}
return context;
}
export function useMessages() {
useLocale();
return m;
}
@@ -0,0 +1,33 @@
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";
function readMessages(locale: "ru" | "en") {
const filepath = resolve(process.cwd(), "messages", `${locale}.json`);
return JSON.parse(readFileSync(filepath, "utf-8")) as Record<string, string>;
}
describe("message catalogs", () => {
it("have the same keyset for ru and en", () => {
const ru = readMessages("ru");
const en = readMessages("en");
const ruKeys = Object.keys(ru).sort();
const enKeys = Object.keys(en).sort();
expect(ruKeys).toEqual(enKeys);
});
it("have non-empty translations", () => {
const catalogs = [readMessages("ru"), readMessages("en")];
for (const catalog of catalogs) {
for (const [key, value] of Object.entries(catalog)) {
expect(
value.trim().length,
`Empty translation for key: ${key}`,
).toBeGreaterThan(0);
}
}
});
});
+15
View File
@@ -0,0 +1,15 @@
import { describe, expect, it } from "vitest";
import { resolveSeoPage } from "./seo";
describe("resolveSeoPage", () => {
it("returns about page for /about and nested about routes", () => {
expect(resolveSeoPage("/about")).toBe("about");
expect(resolveSeoPage("/about/team")).toBe("about");
});
it("returns home page for non-about routes", () => {
expect(resolveSeoPage("/")).toBe("home");
expect(resolveSeoPage("/pricing")).toBe("home");
expect(resolveSeoPage("/aboutness")).toBe("home");
});
});
+9
View File
@@ -0,0 +1,9 @@
export type SeoPage = "home" | "about";
export function resolveSeoPage(pathname: string): SeoPage {
if (pathname === "/about" || pathname.startsWith("/about/")) {
return "about";
}
return "home";
}
+26
View File
@@ -0,0 +1,26 @@
import type { SVGProps } from "react";
type LowerLeftLogoProps = SVGProps<SVGSVGElement>;
export function LowerLeftLogo({ className, ...props }: LowerLeftLogoProps) {
return (
<svg
viewBox="0 0 218 78"
width="218"
height="78"
className={className}
xmlns="http://www.w3.org/2000/svg"
{...props}
>
<title>Monie</title>
<g fill="currentColor" fillRule="evenodd">
<path d="M 167 39 L 166 40 L 166 47 L 165 48 L 165 53 L 164 54 L 164 59 L 163 60 L 163 61 L 164 62 L 164 63 L 166 65 L 171 65 L 172 64 L 174 64 L 175 63 L 174 62 L 172 62 L 171 61 L 171 56 L 172 55 L 172 49 L 173 48 L 173 43 L 174 42 L 174 41 L 173 40 L 173 39 Z" />
<path d="M 130 39 L 129 40 L 129 48 L 128 49 L 128 54 L 127 55 L 127 60 L 126 61 L 126 64 L 133 64 L 133 63 L 134 62 L 134 56 L 135 55 L 135 51 L 142 44 L 143 44 L 144 43 L 148 43 L 149 44 L 149 45 L 150 46 L 150 48 L 149 49 L 149 53 L 148 54 L 148 58 L 147 59 L 147 63 L 149 65 L 154 65 L 155 64 L 157 64 L 158 63 L 157 63 L 155 61 L 155 55 L 156 54 L 156 49 L 157 48 L 157 43 L 156 42 L 156 41 L 154 39 L 148 39 L 147 40 L 145 40 L 143 42 L 142 42 L 141 43 L 140 43 L 138 45 L 138 46 L 137 47 L 136 46 L 136 40 L 135 39 Z" />
<path d="M 196 38 L 195 39 L 191 39 L 190 40 L 189 40 L 188 41 L 187 41 L 181 47 L 181 48 L 180 49 L 180 51 L 179 52 L 179 58 L 180 59 L 180 60 L 183 63 L 184 63 L 185 64 L 187 64 L 188 65 L 195 65 L 196 64 L 199 64 L 200 63 L 201 63 L 202 62 L 203 62 L 205 60 L 206 60 L 206 59 L 205 59 L 204 60 L 203 60 L 202 61 L 201 61 L 200 62 L 198 62 L 197 63 L 192 63 L 191 62 L 190 62 L 187 59 L 187 55 L 186 54 L 188 52 L 196 52 L 197 51 L 200 51 L 201 50 L 203 50 L 207 46 L 207 43 L 206 42 L 206 41 L 205 40 L 204 40 L 203 39 L 200 39 L 199 38 Z M 194 40 L 199 40 L 201 42 L 201 46 L 197 50 L 196 50 L 195 51 L 188 51 L 187 50 L 187 49 L 188 48 L 188 47 L 189 46 L 189 45 Z" />
<path d="M 108 38 L 107 39 L 103 39 L 102 40 L 101 40 L 100 41 L 99 41 L 98 42 L 97 42 L 96 43 L 96 44 L 94 46 L 94 47 L 93 48 L 93 49 L 92 50 L 92 57 L 93 58 L 93 59 L 94 60 L 94 61 L 95 62 L 96 62 L 98 64 L 101 64 L 102 65 L 109 65 L 110 64 L 113 64 L 114 63 L 115 63 L 120 58 L 120 57 L 121 56 L 121 55 L 122 54 L 122 47 L 121 46 L 121 44 L 117 40 L 116 40 L 115 39 L 112 39 L 111 38 Z M 106 41 L 107 40 L 111 40 L 114 43 L 114 44 L 115 45 L 115 53 L 114 54 L 114 56 L 113 57 L 113 58 L 112 59 L 112 60 L 109 63 L 108 63 L 107 64 L 105 64 L 104 63 L 103 63 L 100 60 L 100 49 L 101 48 L 101 46 L 102 45 L 102 44 L 105 41 Z" />
<path d="M 173 23 L 172 24 L 170 24 L 169 25 L 169 26 L 168 27 L 168 30 L 170 32 L 173 32 L 174 31 L 175 31 L 177 29 L 177 25 L 176 24 L 175 24 L 174 23 Z" />
<path d="M 52 10 L 51 11 L 47 11 L 46 12 L 45 12 L 44 13 L 43 13 L 41 15 L 40 15 L 30 25 L 30 26 L 27 29 L 27 30 L 25 32 L 25 33 L 23 35 L 23 36 L 22 37 L 22 38 L 21 39 L 21 40 L 19 42 L 19 43 L 18 44 L 18 45 L 17 46 L 17 47 L 16 48 L 16 49 L 15 50 L 15 51 L 14 52 L 14 54 L 13 55 L 13 56 L 12 57 L 12 58 L 11 59 L 11 61 L 10 62 L 10 65 L 11 66 L 13 66 L 14 67 L 18 67 L 19 66 L 21 66 L 22 65 L 23 65 L 25 63 L 25 61 L 26 60 L 26 59 L 27 58 L 27 56 L 28 55 L 28 53 L 29 52 L 29 50 L 30 49 L 30 47 L 31 46 L 31 45 L 32 44 L 32 43 L 33 42 L 33 41 L 34 40 L 34 39 L 35 38 L 35 37 L 36 36 L 36 35 L 37 34 L 37 33 L 39 31 L 39 30 L 41 28 L 41 27 L 47 21 L 48 22 L 48 25 L 47 26 L 47 29 L 46 30 L 46 32 L 45 33 L 45 36 L 44 37 L 44 40 L 43 41 L 43 45 L 42 46 L 42 54 L 43 55 L 43 57 L 46 60 L 47 60 L 48 61 L 53 61 L 54 60 L 55 60 L 57 58 L 58 58 L 59 57 L 59 56 L 62 53 L 62 52 L 63 51 L 63 50 L 65 48 L 65 47 L 66 46 L 66 45 L 68 43 L 68 42 L 69 41 L 69 40 L 71 38 L 71 37 L 73 35 L 73 34 L 76 31 L 76 30 L 79 27 L 80 27 L 81 28 L 80 29 L 80 31 L 79 32 L 79 34 L 78 35 L 78 37 L 77 38 L 77 40 L 76 41 L 76 44 L 75 45 L 75 48 L 74 49 L 74 54 L 73 55 L 73 57 L 74 58 L 74 61 L 75 62 L 75 63 L 77 65 L 78 65 L 79 66 L 81 66 L 82 67 L 90 67 L 91 66 L 89 66 L 88 65 L 87 65 L 85 63 L 85 62 L 84 61 L 84 50 L 85 49 L 85 46 L 86 45 L 86 42 L 87 41 L 87 38 L 88 37 L 88 34 L 89 33 L 89 31 L 90 30 L 90 27 L 91 26 L 91 23 L 92 22 L 92 19 L 90 17 L 84 17 L 83 18 L 82 18 L 80 20 L 79 20 L 73 26 L 73 27 L 69 31 L 69 32 L 66 35 L 66 36 L 64 38 L 64 39 L 62 41 L 62 42 L 59 45 L 59 46 L 56 49 L 55 49 L 54 48 L 54 47 L 53 46 L 53 44 L 54 43 L 54 38 L 55 37 L 55 34 L 56 33 L 56 31 L 57 30 L 57 27 L 58 26 L 58 24 L 59 23 L 59 19 L 60 18 L 60 16 L 59 15 L 59 13 L 58 12 L 57 12 L 56 11 L 53 11 Z" />
</g>
</svg>
);
}