fix(deps): update nextjs monorepo to v15 (major) (#1844)

This commit is contained in:
Meier Lukas
2025-01-04 19:47:23 +01:00
committed by GitHub
parent 92f4f9421e
commit d98552540a
51 changed files with 601 additions and 314 deletions

View File

@@ -18,7 +18,8 @@ import superjson from "superjson";
import type { SuperJSONResult } from "superjson";
import type { AppRouter } from "@homarr/api";
import { clientApi, createHeadersCallbackForSource, getTrpcUrl } from "@homarr/api/client";
import { clientApi, getTrpcUrl } from "@homarr/api/client";
import { createHeadersCallbackForSource } from "@homarr/api/shared";
import { env } from "~/env.mjs";

View File

@@ -11,15 +11,17 @@ import { HomarrLogoWithTitle } from "~/components/layout/logo/homarr-logo";
import { RegistrationForm } from "./_registration-form";
interface InviteUsagePageProps {
params: {
params: Promise<{
id: string;
};
searchParams: {
}>;
searchParams: Promise<{
token: string;
};
}>;
}
export default async function InviteUsagePage({ params, searchParams }: InviteUsagePageProps) {
export default async function InviteUsagePage(props: InviteUsagePageProps) {
const searchParams = await props.searchParams;
const params = await props.params;
if (!isProviderEnabled("credentials")) notFound();
const session = await auth();

View File

@@ -9,12 +9,13 @@ import { HomarrLogoWithTitle } from "~/components/layout/logo/homarr-logo";
import { LoginForm } from "./_login-form";
interface LoginProps {
searchParams: {
searchParams: Promise<{
callbackUrl?: string;
};
}>;
}
export default async function Login({ searchParams }: LoginProps) {
export default async function Login(props: LoginProps) {
const searchParams = await props.searchParams;
const session = await auth();
if (session) {

View File

@@ -31,15 +31,15 @@ import { GeneralSettingsContent } from "./_general";
import { LayoutSettingsContent } from "./_layout";
interface Props {
params: {
params: Promise<{
name: string;
};
searchParams: {
}>;
searchParams: Promise<{
tab?: keyof TranslationObject["board"]["setting"]["section"];
};
}>;
}
const getBoardAndPermissionsAsync = async (params: Props["params"]) => {
const getBoardAndPermissionsAsync = async (params: Awaited<Props["params"]>) => {
try {
const board = await api.board.getBoardByName({ name: params.name });
const { hasFullAccess } = await getBoardPermissionsAsync(board);
@@ -63,7 +63,9 @@ const getBoardAndPermissionsAsync = async (params: Props["params"]) => {
}
};
export default async function BoardSettingsPage({ params, searchParams }: Props) {
export default async function BoardSettingsPage(props: Props) {
const searchParams = await props.searchParams;
const params = await props.params;
const { board, permissions } = await getBoardAndPermissionsAsync(params);
const boardSettings = await getServerSettingByKeyAsync(db, "board");
const { hasFullAccess, hasChangeAccess } = await getBoardPermissionsAsync(board);

View File

@@ -28,9 +28,9 @@ export const createBoardLayout = <TParams extends Params>({
params,
children,
}: PropsWithChildren<{
params: TParams;
params: Promise<TParams>;
}>) => {
const initialBoard = await getInitialBoard(params).catch((error) => {
const initialBoard = await getInitialBoard(await params).catch((error) => {
if (error instanceof TRPCError && error.code === "NOT_FOUND") {
logger.warn(error);
notFound();

View File

@@ -14,6 +14,7 @@ import { auth } from "@homarr/auth/next";
import { ModalProvider } from "@homarr/modals";
import { Notifications } from "@homarr/notifications";
import { SpotlightProvider } from "@homarr/spotlight";
import type { SupportedLanguage } from "@homarr/translation";
import { isLocaleRTL, isLocaleSupported } from "@homarr/translation";
import { getI18nMessages } from "@homarr/translation/server";
@@ -63,14 +64,17 @@ export const viewport: Viewport = {
],
};
export default async function Layout(props: { children: React.ReactNode; params: { locale: string } }) {
if (!isLocaleSupported(props.params.locale)) {
export default async function Layout(props: {
children: React.ReactNode;
params: Promise<{ locale: SupportedLanguage }>;
}) {
if (!isLocaleSupported((await props.params).locale)) {
notFound();
}
const session = await auth();
const colorScheme = await getCurrentColorSchemeAsync();
const direction = isLocaleRTL(props.params.locale) ? "rtl" : "ltr";
const direction = isLocaleRTL((await props.params).locale) ? "rtl" : "ltr";
const i18nMessages = await getI18nMessages();
const StackedProvider = composeWrappers([
@@ -89,7 +93,7 @@ export default async function Layout(props: { children: React.ReactNode; params:
return (
// Instead of ColorSchemScript we use data-mantine-color-scheme to prevent flickering
<html
lang={props.params.locale}
lang={(await props.params).locale}
dir={direction}
data-mantine-color-scheme={colorScheme}
style={{

View File

@@ -37,16 +37,16 @@ export async function generateMetadata() {
};
}
const getHost = () => {
const getHostAsync = async () => {
if (process.env.HOSTNAME) {
return `${process.env.HOSTNAME}:3000`;
}
return headers().get("host");
return (await headers()).get("host");
};
export default async function AboutPage() {
const baseServerUrl = `http://${getHost()}`;
const baseServerUrl = `http://${await getHostAsync()}`;
const t = await getScopedI18n("management.page.about");
const attributes = await getPackageAttributesAsync();
const githubContributors = (await fetch(`${baseServerUrl}/api/about/contributors/github`).then((res) =>

View File

@@ -9,10 +9,11 @@ import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { AppEditForm } from "./_app-edit-form";
interface AppEditPageProps {
params: { id: string };
params: Promise<{ id: string }>;
}
export default async function AppEditPage({ params }: AppEditPageProps) {
export default async function AppEditPage(props: AppEditPageProps) {
const params = await props.params;
const session = await auth();
if (!session?.user.permissions.includes("app-modify-all")) {

View File

@@ -11,10 +11,11 @@ import { IntegrationAccessSettings } from "../../_components/integration-access-
import { EditIntegrationForm } from "./_integration-edit-form";
interface EditIntegrationPageProps {
params: { id: string };
params: Promise<{ id: string }>;
}
export default async function EditIntegrationPage({ params }: EditIntegrationPageProps) {
export default async function EditIntegrationPage(props: EditIntegrationPageProps) {
const params = await props.params;
const editT = await getScopedI18n("integration.page.edit");
const t = await getI18n();
const integration = await api.integration.byId({ id: params.id }).catch(catchTrpcNotFound);

View File

@@ -13,12 +13,15 @@ import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { NewIntegrationForm } from "./_integration-new-form";
interface NewIntegrationPageProps {
searchParams: Partial<z.infer<typeof validation.integration.create>> & {
kind: IntegrationKind;
};
searchParams: Promise<
Partial<z.infer<typeof validation.integration.create>> & {
kind: IntegrationKind;
}
>;
}
export default async function IntegrationsNewPage({ searchParams }: NewIntegrationPageProps) {
export default async function IntegrationsNewPage(props: NewIntegrationPageProps) {
const searchParams = await props.searchParams;
const session = await auth();
if (!session?.user.permissions.includes("integration-create")) {
notFound();

View File

@@ -46,12 +46,13 @@ import { DeleteIntegrationActionButton } from "./_integration-buttons";
import { IntegrationCreateDropdownContent } from "./new/_integration-new-dropdown";
interface IntegrationsPageProps {
searchParams: {
searchParams: Promise<{
tab?: IntegrationKind;
};
}>;
}
export default async function IntegrationsPage({ searchParams }: IntegrationsPageProps) {
export default async function IntegrationsPage(props: IntegrationsPageProps) {
const searchParams = await props.searchParams;
const session = await auth();
if (!session) {

View File

@@ -34,7 +34,7 @@ type SearchParamsSchemaInputFromSchema<TSchema extends Record<string, unknown>>
}>;
interface MediaListPageProps {
searchParams: SearchParamsSchemaInputFromSchema<z.infer<typeof searchParamsSchema>>;
searchParams: Promise<SearchParamsSchemaInputFromSchema<z.infer<typeof searchParamsSchema>>>;
}
export default async function GroupsListPage(props: MediaListPageProps) {
@@ -45,7 +45,7 @@ export default async function GroupsListPage(props: MediaListPageProps) {
}
const t = await getI18n();
const searchParams = searchParamsSchema.parse(props.searchParams);
const searchParams = searchParamsSchema.parse(await props.searchParams);
const { items: medias, totalCount } = await api.media.getPaginated(searchParams);
return (

View File

@@ -10,10 +10,11 @@ import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { SearchEngineEditForm } from "./_search-engine-edit-form";
interface SearchEngineEditPageProps {
params: { id: string };
params: Promise<{ id: string }>;
}
export default async function SearchEngineEditPage({ params }: SearchEngineEditPageProps) {
export default async function SearchEngineEditPage(props: SearchEngineEditPageProps) {
const params = await props.params;
const session = await auth();
if (!session?.user.permissions.includes("search-engine-modify-all")) {

View File

@@ -27,7 +27,7 @@ type SearchParamsSchemaInputFromSchema<TSchema extends Record<string, unknown>>
}>;
interface SearchEnginesPageProps {
searchParams: SearchParamsSchemaInputFromSchema<z.infer<typeof searchParamsSchema>>;
searchParams: Promise<SearchParamsSchemaInputFromSchema<z.infer<typeof searchParamsSchema>>>;
}
export default async function SearchEnginesPage(props: SearchEnginesPageProps) {
@@ -37,7 +37,7 @@ export default async function SearchEnginesPage(props: SearchEnginesPageProps) {
redirect("/auth/login");
}
const searchParams = searchParamsSchema.parse(props.searchParams);
const searchParams = searchParamsSchema.parse(await props.searchParams);
const { items: searchEngines, totalCount } = await api.searchEngine.getPaginated(searchParams);
const tEngine = await getScopedI18n("search.engine");

View File

@@ -30,7 +30,7 @@ export default async function ApiPage() {
if (!session?.user || !session.user.permissions.includes("admin")) {
notFound();
}
const document = openApiDocument(extractBaseUrlFromHeaders(headers()));
const document = openApiDocument(extractBaseUrlFromHeaders(await headers()));
const apiKeys = await api.apiKeys.getAll();
const t = await getScopedI18n("management.page.tool.api.tab");

View File

@@ -19,12 +19,13 @@ import { UserProfileAvatarForm } from "./_components/_profile-avatar-form";
import { UserProfileForm } from "./_components/_profile-form";
interface Props {
params: {
params: Promise<{
userId: string;
};
}>;
}
export async function generateMetadata({ params }: Props) {
export async function generateMetadata(props: Props) {
const params = await props.params;
const session = await auth();
const user = await api.user
.getById({
@@ -43,7 +44,8 @@ export async function generateMetadata({ params }: Props) {
};
}
export default async function EditUserPage({ params }: Props) {
export default async function EditUserPage(props: Props) {
const params = await props.params;
const t = await getI18n();
const tGeneral = await getScopedI18n("management.page.user.setting.general");
const session = await auth();

View File

@@ -15,10 +15,14 @@ import { NavigationLink } from "../groups/[id]/_navigation";
import { canAccessUserEditPage } from "./access";
interface LayoutProps {
params: { userId: string };
params: Promise<{ userId: string }>;
}
export default async function Layout({ children, params }: PropsWithChildren<LayoutProps>) {
export default async function Layout(props: PropsWithChildren<LayoutProps>) {
const params = await props.params;
const { children } = props;
const session = await auth();
const t = await getI18n();
const tUser = await getScopedI18n("management.page.user");

View File

@@ -10,12 +10,13 @@ import { canAccessUserEditPage } from "../access";
import { ChangePasswordForm } from "./_components/_change-password-form";
interface Props {
params: {
params: Promise<{
userId: string;
};
}>;
}
export default async function UserSecurityPage({ params }: Props) {
export default async function UserSecurityPage(props: Props) {
const params = await props.params;
const session = await auth();
const tSecurity = await getScopedI18n("management.page.user.setting.security");
const user = await api.user

View File

@@ -10,10 +10,14 @@ import { ManageContainer } from "~/components/manage/manage-container";
import { NavigationLink } from "./_navigation";
interface LayoutProps {
params: { id: string };
params: Promise<{ id: string }>;
}
export default async function Layout({ children, params }: PropsWithChildren<LayoutProps>) {
export default async function Layout(props: PropsWithChildren<LayoutProps>) {
const params = await props.params;
const { children } = props;
const t = await getI18n();
const tGroup = await getScopedI18n("management.page.group");
const group = await api.group.getById({ id: params.id });

View File

@@ -17,15 +17,17 @@ import { AddGroupMember } from "./_add-group-member";
import { RemoveGroupMember } from "./_remove-group-member";
interface GroupsDetailPageProps {
params: {
params: Promise<{
id: string;
};
searchParams: {
}>;
searchParams: Promise<{
search: string | undefined;
};
}>;
}
export default async function GroupsDetailPage({ params, searchParams }: GroupsDetailPageProps) {
export default async function GroupsDetailPage(props: GroupsDetailPageProps) {
const searchParams = await props.searchParams;
const params = await props.params;
const session = await auth();
if (!session?.user.permissions.includes("admin")) {

View File

@@ -14,12 +14,13 @@ import { ReservedGroupAlert } from "./_reserved-group-alert";
import { TransferGroupOwnership } from "./_transfer-group-ownership";
interface GroupsDetailPageProps {
params: {
params: Promise<{
id: string;
};
}>;
}
export default async function GroupsDetailPage({ params }: GroupsDetailPageProps) {
export default async function GroupsDetailPage(props: GroupsDetailPageProps) {
const params = await props.params;
const session = await auth();
if (!session?.user.permissions.includes("admin")) {

View File

@@ -12,12 +12,13 @@ import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { PermissionForm, PermissionSwitch, SaveAffix } from "./_group-permission-form";
interface GroupPermissionsPageProps {
params: {
params: Promise<{
id: string;
};
}>;
}
export default async function GroupPermissionsPage({ params }: GroupPermissionsPageProps) {
export default async function GroupPermissionsPage(props: GroupPermissionsPageProps) {
const params = await props.params;
const session = await auth();
if (!session?.user.permissions.includes("admin")) {

View File

@@ -24,7 +24,7 @@ type SearchParamsSchemaInputFromSchema<TSchema extends Record<string, unknown>>
}>;
interface GroupsListPageProps {
searchParams: SearchParamsSchemaInputFromSchema<z.infer<typeof searchParamsSchema>>;
searchParams: Promise<SearchParamsSchemaInputFromSchema<z.infer<typeof searchParamsSchema>>>;
}
export default async function GroupsListPage(props: GroupsListPageProps) {
@@ -35,7 +35,7 @@ export default async function GroupsListPage(props: GroupsListPageProps) {
}
const t = await getI18n();
const searchParams = searchParamsSchema.parse(props.searchParams);
const searchParams = searchParamsSchema.parse(await props.searchParams);
const { items: groups, totalCount } = await api.group.getPaginated(searchParams);
return (

View File

@@ -9,11 +9,11 @@ import { env } from "~/env.mjs";
import { WidgetPreviewPageContent } from "./_content";
interface Props {
params: { kind: string };
params: Promise<{ kind: string }>;
}
export default async function WidgetPreview(props: Props) {
if (!(props.params.kind in widgetImports || env.NODE_ENV !== "development")) {
if (!((await props.params).kind in widgetImports || env.NODE_ENV !== "development")) {
notFound();
}
@@ -26,7 +26,7 @@ export default async function WidgetPreview(props: Props) {
},
});
const sort = props.params.kind as WidgetKind;
const sort = (await props.params).kind as WidgetKind;
return (
<Center h="100vh">

View File

@@ -1,11 +1,10 @@
import { headers } from "next/headers";
import { userAgent } from "next/server";
import type { NextRequest } from "next/server";
import { userAgent } from "next/server";
import { createOpenApiFetchHandler } from "trpc-to-openapi";
import { appRouter, createTRPCContext } from "@homarr/api";
import { hashPasswordAsync } from "@homarr/auth";
import type { Session } from "@homarr/auth";
import { hashPasswordAsync } from "@homarr/auth";
import { createSessionAsync } from "@homarr/auth/server";
import { db, eq } from "@homarr/db";
import { apiKeys } from "@homarr/db/schema";
@@ -13,7 +12,7 @@ import { logger } from "@homarr/log";
const handlerAsync = async (req: NextRequest) => {
const apiKeyHeaderValue = req.headers.get("ApiKey");
const ipAddress = req.ip ?? headers().get("x-forwarded-for");
const ipAddress = req.headers.get("x-forwarded-for");
const { ua } = userAgent(req);
const session: Session | null = await getSessionOrDefaultFromHeadersAsync(apiKeyHeaderValue, ipAddress, ua);
@@ -88,9 +87,9 @@ const getSessionOrDefaultFromHeadersAsync = async (
};
export {
handlerAsync as DELETE,
handlerAsync as GET,
handlerAsync as PATCH,
handlerAsync as POST,
handlerAsync as PUT,
handlerAsync as DELETE,
handlerAsync as PATCH,
};

View File

@@ -1,16 +1,17 @@
import { NextRequest } from "next/server";
import { createHandlers } from "@homarr/auth";
import { createHandlersAsync } from "@homarr/auth";
import type { SupportedAuthProvider } from "@homarr/definitions";
import { logger } from "@homarr/log";
export const GET = async (req: NextRequest) => {
return await createHandlers(extractProvider(req), isSecureCookieEnabled(req)).handlers.GET(reqWithTrustedOrigin(req));
const { handlers } = await createHandlersAsync(extractProvider(req), isSecureCookieEnabled(req));
return await handlers.GET(reqWithTrustedOrigin(req));
};
export const POST = async (req: NextRequest) => {
return await createHandlers(extractProvider(req), isSecureCookieEnabled(req)).handlers.POST(
reqWithTrustedOrigin(req),
);
const { handlers } = await createHandlersAsync(extractProvider(req), isSecureCookieEnabled(req));
return await handlers.POST(reqWithTrustedOrigin(req));
};
/**

View File

@@ -5,7 +5,8 @@ import type { NextRequest } from "next/server";
import { db, eq } from "@homarr/db";
import { medias } from "@homarr/db/schema";
export async function GET(_req: NextRequest, { params }: { params: { id: string } }) {
export async function GET(_req: NextRequest, props: { params: Promise<{ id: string }> }) {
const params = await props.params;
const image = await db.query.medias.findFirst({
where: eq(medias.id, params.id),
columns: {

View File

@@ -88,7 +88,7 @@ export const UserAvatarMenu = ({ children, availableUpdates }: UserAvatarMenuPro
</Menu.Item>
<Menu.Divider />
<Menu.Item p={0} closeMenuOnClick={false}>
<Menu.Item p={0} closeMenuOnClick={false} component="div">
<CurrentLanguageCombobox />
</Menu.Item>
<Menu.Divider />
@@ -113,7 +113,7 @@ export const UserAvatarMenu = ({ children, availableUpdates }: UserAvatarMenuPro
{t("logout")}
</Menu.Item>
) : (
<Menu.Item onClick={() => router.push("/auth/login")} leftSection={<IconLogin size="1rem" />}>
<Menu.Item component={Link} href="/auth/login" leftSection={<IconLogin size="1rem" />}>
{t("login")}
</Menu.Item>
)}

View File

@@ -4,7 +4,7 @@ import { createTRPCClient, httpLink } from "@trpc/client";
import SuperJSON from "superjson";
import type { AppRouter } from "@homarr/api";
import { createHeadersCallbackForSource } from "@homarr/api/client";
import { createHeadersCallbackForSource } from "@homarr/api/shared";
import { createI18nMiddleware } from "@homarr/translation/middleware";
export async function middleware(request: NextRequest) {

View File

@@ -7,7 +7,7 @@ import type { ColorScheme } from "@homarr/definitions";
import { colorSchemeCookieKey } from "@homarr/definitions";
export const getCurrentColorSchemeAsync = cache(async () => {
const cookieValue = cookies().get(colorSchemeCookieKey)?.value;
const cookieValue = (await cookies()).get(colorSchemeCookieKey)?.value;
if (cookieValue) {
return cookieValue as ColorScheme;