first
This commit is contained in:
commit
8538ab969a
434 changed files with 44051 additions and 0 deletions
9
apps/web/modules/shared/lib/api-client.ts
Normal file
9
apps/web/modules/shared/lib/api-client.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import type { AppRouter } from "@repo/api";
|
||||
import { getBaseUrl } from "@repo/utils";
|
||||
import { hc } from "hono/client";
|
||||
|
||||
export const apiClient = hc<AppRouter>(getBaseUrl(), {
|
||||
init: {
|
||||
credentials: "include",
|
||||
},
|
||||
}).api;
|
||||
15
apps/web/modules/shared/lib/cache.ts
Normal file
15
apps/web/modules/shared/lib/cache.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
"use server";
|
||||
|
||||
import { revalidatePath } from "next/cache";
|
||||
|
||||
export const clearCache = async (path?: string) => {
|
||||
try {
|
||||
if (path) {
|
||||
revalidatePath(path);
|
||||
} else {
|
||||
revalidatePath("/", "layout");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Could not revalidate path", path, error);
|
||||
}
|
||||
};
|
||||
148
apps/web/modules/shared/lib/content.ts
Normal file
148
apps/web/modules/shared/lib/content.ts
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
import slugify from "slugify";
|
||||
|
||||
export type ContentStructureItem = {
|
||||
label: string;
|
||||
path: string;
|
||||
children: ContentStructureItem[];
|
||||
isPage: boolean;
|
||||
};
|
||||
|
||||
export function getContentStructure({
|
||||
documents,
|
||||
meta,
|
||||
locale,
|
||||
}: {
|
||||
documents: {
|
||||
path: string;
|
||||
locale: string;
|
||||
title: string;
|
||||
}[];
|
||||
meta: {
|
||||
path: string;
|
||||
locale: string;
|
||||
data: Record<string, string | { title: string }>;
|
||||
}[];
|
||||
locale?: string;
|
||||
}) {
|
||||
const contentStructure: ContentStructureItem[] = [];
|
||||
|
||||
function addToContentItemArray(
|
||||
contentItemsArray: ContentStructureItem[],
|
||||
subPath: string,
|
||||
item: (typeof documents)[number],
|
||||
) {
|
||||
const pathParts = item.path
|
||||
.replace(new RegExp(`^${subPath}[/]*`), "")
|
||||
.split("/");
|
||||
|
||||
const rootItemPath = subPath
|
||||
? [subPath, pathParts[0]].join("/")
|
||||
: pathParts[0];
|
||||
|
||||
let rootItem = contentItemsArray.find(
|
||||
(contentItem) => contentItem.path === rootItemPath,
|
||||
);
|
||||
|
||||
const isPage = pathParts.length === 1;
|
||||
if (!rootItem) {
|
||||
const path = isPage ? item.path : rootItemPath;
|
||||
const metaData = meta
|
||||
.filter((meta) => meta.path === subPath)
|
||||
.sort((page) => (page.locale === locale ? -1 : 1))
|
||||
.at(0)?.data[pathParts[0]];
|
||||
const label = metaData
|
||||
? typeof metaData === "string"
|
||||
? metaData
|
||||
: metaData.title
|
||||
: (documents
|
||||
.filter((page) => page.path === rootItemPath)
|
||||
.sort((page) => (page.locale === locale ? -1 : 1))
|
||||
.at(0)?.title ?? pathParts[0]);
|
||||
|
||||
rootItem = {
|
||||
label,
|
||||
path,
|
||||
children: [],
|
||||
isPage,
|
||||
};
|
||||
|
||||
contentItemsArray.push(rootItem);
|
||||
}
|
||||
|
||||
if (isPage && !rootItem.isPage) {
|
||||
rootItem.isPage = true;
|
||||
}
|
||||
|
||||
if (pathParts.length > 1) {
|
||||
addToContentItemArray(rootItem.children, rootItemPath, item);
|
||||
}
|
||||
}
|
||||
|
||||
documents.forEach((page) => {
|
||||
addToContentItemArray(contentStructure, "", page);
|
||||
});
|
||||
|
||||
// recusrively sort items and their children
|
||||
function sortContentItems(items: ContentStructureItem[], basePath = "") {
|
||||
items.sort((a, b) => {
|
||||
if (a.path === "") {
|
||||
return -1;
|
||||
}
|
||||
if (b.path === "") {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const aIndex = Object.entries(
|
||||
meta
|
||||
.filter((meta) => meta.path === basePath)
|
||||
.sort((page) => (page.locale === locale ? -1 : 1))
|
||||
.at(0)?.data ?? {},
|
||||
).findIndex(([key]) => key === a.path.replace(`${basePath}/`, ""));
|
||||
|
||||
const bIndex = Object.entries(
|
||||
meta
|
||||
.filter((meta) => meta.path === basePath)
|
||||
.sort((page) => (page.locale === locale ? -1 : 1))
|
||||
.at(0)?.data ?? {},
|
||||
).findIndex(([key]) => key === b.path.replace(`${basePath}/`, ""));
|
||||
|
||||
// use position index from meta file or put the item at the end of the list
|
||||
return (
|
||||
(aIndex > -1 ? aIndex : items.length) -
|
||||
(bIndex > -1 ? bIndex : items.length)
|
||||
);
|
||||
});
|
||||
|
||||
items.forEach((item) => {
|
||||
if (item.children.length) {
|
||||
sortContentItems(item.children, item.path);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sortContentItems(contentStructure);
|
||||
|
||||
return contentStructure;
|
||||
}
|
||||
|
||||
export function getActivePathFromUrlParam(path: string | string[]) {
|
||||
return Array.isArray(path) ? path.join("/") : path || "";
|
||||
}
|
||||
|
||||
export function getLocalizedDocumentWithFallback<
|
||||
T extends { path: string; locale: string },
|
||||
>(documents: T[], path: string, locale: string) {
|
||||
return documents
|
||||
.filter((doc) => doc.path === path)
|
||||
.sort((doc) => (doc.locale === locale ? -1 : 1))[0];
|
||||
}
|
||||
|
||||
export function slugifyHeadline(headline: string) {
|
||||
return slugify(headline, {
|
||||
lower: true,
|
||||
replacement: "-",
|
||||
trim: true,
|
||||
strict: true,
|
||||
remove: /[*+~.()'"!:@]/g,
|
||||
});
|
||||
}
|
||||
74
apps/web/modules/shared/lib/middleware-helpers.ts
Normal file
74
apps/web/modules/shared/lib/middleware-helpers.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
import type { Organization, Session } from "@repo/auth";
|
||||
import { apiClient } from "@shared/lib/api-client";
|
||||
import type { NextRequest } from "next/server";
|
||||
|
||||
export const getSession = async (req: NextRequest): Promise<Session | null> => {
|
||||
const response = await fetch(
|
||||
new URL(
|
||||
"/api/auth/get-session?disableCookieCache=true",
|
||||
req.nextUrl.origin,
|
||||
),
|
||||
{
|
||||
headers: {
|
||||
cookie: req.headers.get("cookie") || "",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const session = await response.json();
|
||||
return session;
|
||||
} catch (error) {
|
||||
console.error("Error parsing session JSON:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getOrganizationsForSession = async (
|
||||
req: NextRequest,
|
||||
): Promise<Organization[]> => {
|
||||
const response = await fetch(
|
||||
new URL("/api/auth/organization/list", req.nextUrl.origin),
|
||||
{
|
||||
headers: {
|
||||
cookie: req.headers.get("cookie") || "",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return (await response.json()) ?? [];
|
||||
};
|
||||
|
||||
export const getPurchasesForSession = async (
|
||||
req: NextRequest,
|
||||
organizationId?: string,
|
||||
) => {
|
||||
const response = await apiClient.payments.purchases.$get(
|
||||
{
|
||||
query: {
|
||||
organizationId,
|
||||
},
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
cookie: req.headers.get("cookie") || "",
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const purchases = await response.json();
|
||||
|
||||
return purchases;
|
||||
};
|
||||
35
apps/web/modules/shared/lib/query-client.ts
Normal file
35
apps/web/modules/shared/lib/query-client.ts
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import {
|
||||
QueryClient,
|
||||
defaultShouldDehydrateQuery,
|
||||
} from "@tanstack/react-query";
|
||||
|
||||
export function createQueryClient() {
|
||||
return new QueryClient({
|
||||
defaultOptions: {
|
||||
queries: {
|
||||
staleTime: 30 * 1000,
|
||||
retry: false,
|
||||
},
|
||||
dehydrate: {
|
||||
shouldDehydrateQuery: (query) =>
|
||||
defaultShouldDehydrateQuery(query) ||
|
||||
query.state.status === "pending",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function createQueryKeyWithParams(
|
||||
key: string | string[],
|
||||
params: Record<string, string | number>,
|
||||
) {
|
||||
return [
|
||||
...(Array.isArray(key) ? key : [key]),
|
||||
Object.entries(params)
|
||||
.reduce((acc, [key, value]) => {
|
||||
acc.push(`${key}:${value}`);
|
||||
return acc;
|
||||
}, [] as string[])
|
||||
.join("_"),
|
||||
] as const;
|
||||
}
|
||||
21
apps/web/modules/shared/lib/server.ts
Normal file
21
apps/web/modules/shared/lib/server.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
import "server-only";
|
||||
import { createQueryClient } from "@shared/lib/query-client";
|
||||
|
||||
import type { AppRouter } from "@repo/api";
|
||||
import { getBaseUrl } from "@repo/utils";
|
||||
import { hc } from "hono/client";
|
||||
import { headers } from "next/headers";
|
||||
import { cache } from "react";
|
||||
|
||||
export const getServerQueryClient = cache(createQueryClient);
|
||||
|
||||
export const getServerApiClient = async () => {
|
||||
const headerObject = Object.fromEntries((await headers()).entries());
|
||||
|
||||
return hc<AppRouter>(getBaseUrl(), {
|
||||
init: {
|
||||
credentials: "include",
|
||||
headers: headerObject,
|
||||
},
|
||||
}).api;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue