diff --git a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx index 2458b2171..792e08903 100644 --- a/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx +++ b/apps/nextjs/src/app/[locale]/auth/login/_login-form.tsx @@ -4,7 +4,7 @@ import type { PropsWithChildren } from "react"; import { useCallback, useEffect, useRef, useState } from "react"; import { useRouter, useSearchParams } from "next/navigation"; import { Anchor, Button, Card, Code, Collapse, Divider, PasswordInput, Stack, Text, TextInput } from "@mantine/core"; -import { useDisclosure, useMounted } from "@mantine/hooks"; +import { useDisclosure } from "@mantine/hooks"; import { z } from "zod/v4"; import { signIn } from "@homarr/auth/client"; @@ -32,7 +32,6 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c const searchParams = useSearchParams(); const isError = searchParams.has("error"); const router = useRouter(); - const isMounted = useMounted(); const [isPending, setIsPending] = useState(false); const form = useZodForm(extendedValidation, { initialValues: { @@ -140,7 +139,7 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c {providers.includes("credentials") && ( - + {t("action.login.label")} @@ -148,7 +147,7 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c )} {providers.includes("ldap") && ( - + {t("action.login.labelWith", { provider: "LDAP" })} )} @@ -159,13 +158,7 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c )} {providers.includes("oidc") && ( - )} @@ -175,13 +168,12 @@ export const LoginForm = ({ providers, oidcClientName, isOidcAutoLoginEnabled, c }; interface SubmitButtonProps { - isMounted: boolean; isPending: boolean; form: UseFormReturnType; provider: "credentials" | "ldap"; } -const SubmitButton = ({ isMounted, isPending, form, provider, children }: PropsWithChildren) => { +const SubmitButton = ({ isPending, form, provider, children }: PropsWithChildren) => { const isCurrentProviderActive = form.getValues().provider === provider; return ( @@ -192,7 +184,7 @@ const SubmitButton = ({ isMounted, isPending, form, provider, children }: PropsW fullWidth onClick={() => form.setFieldValue("provider", provider)} loading={isPending && isCurrentProviderActive} - disabled={!isMounted || (isPending && !isCurrentProviderActive)} + disabled={isPending && !isCurrentProviderActive} > {children} diff --git a/apps/nextjs/src/components/backup/database-restore-flow.tsx b/apps/nextjs/src/components/backup/database-restore-flow.tsx index 0708cd525..4476bdab9 100644 --- a/apps/nextjs/src/components/backup/database-restore-flow.tsx +++ b/apps/nextjs/src/components/backup/database-restore-flow.tsx @@ -254,14 +254,14 @@ export const DatabaseRestoreFlow = ({ variant = "card", onRestoreComplete }: Dat -
+ {file?.name} {file ? `${(file.size / 1024 / 1024).toFixed(2)} MB` : ""} -
+
+
+ ); + + switch (claimState) { + case "checking": + title = t("checkingTitle"); + description = t("checking"); + action = null; + break; + case "signIn": + title = t("signInTitle"); + description = t("signInDescription"); + action = ( + + ); + break; + case "locked": + title = t("lockedTitle"); + description = t("lockedDescription"); + action = ; + break; + case "error": + break; + } + return (
- {claimState === "checking" - ? t("checkingTitle") - : signIn - ? t("signInTitle") - : claimState === "locked" - ? t("lockedTitle") - : t("errorTitle")} + {title} - {signIn - ? t("signInDescription") - : claimState === "locked" - ? t("lockedDescription") - : claimState === "checking" - ? t("checking") - : (claimError ?? t("errorDescription"))} + {description} - {signIn ? ( - - ) : claimState === "locked" ? ( - - ) : claimState === "error" ? ( - - - {claimError ?? t("errorDescription")} - - - - ) : null} + {action}
diff --git a/packages/ui/src/components/integration-marquee.module.css b/packages/ui/src/components/integration-marquee.module.css index a3cb0daa2..81095fac0 100644 --- a/packages/ui/src/components/integration-marquee.module.css +++ b/packages/ui/src/components/integration-marquee.module.css @@ -1,41 +1,9 @@ -.root { - --integration-marquee-accent: var(--mantine-primary-color-filled); - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: var(--mantine-spacing-xl); - height: 100%; - overflow: hidden; - transform: rotate(10deg); -} - -.track { - display: flex; - flex-direction: column; - align-items: center; - gap: var(--mantine-spacing-xl); - animation: integrationMarquee var(--marquee-duration) linear infinite; -} - -.iconFrame { - display: grid; - flex: 0 0 4.5rem; - width: 4.5rem; - height: 4.5rem; - place-items: center; - border: 1px solid color-mix(in srgb, var(--integration-marquee-accent) 24%, transparent); - border-radius: var(--mantine-radius-lg); - background: color-mix(in srgb, var(--integration-marquee-accent) 8%, var(--mantine-color-body)); - box-shadow: var(--mantine-shadow-sm); -} - -@keyframes integrationMarquee { - to { - transform: translateY(-50%); - } +.marquee { + mask-image: linear-gradient(to bottom, transparent, black 14%, black 86%, transparent); } @media (prefers-reduced-motion: reduce) { - .track { + .content { animation: none; } } diff --git a/packages/ui/src/components/integration-marquee.tsx b/packages/ui/src/components/integration-marquee.tsx index e29ce87ef..75dcc5f3d 100644 --- a/packages/ui/src/components/integration-marquee.tsx +++ b/packages/ui/src/components/integration-marquee.tsx @@ -1,5 +1,4 @@ -import type { CSSProperties } from "react"; -import { Image } from "@mantine/core"; +import { Image, Marquee, SimpleGrid, ThemeIcon } from "@mantine/core"; import { splitToNChunks } from "@homarr/common"; import { integrationDefs } from "@homarr/definitions"; @@ -15,19 +14,31 @@ const iconGroups = splitToNChunks( const animationDuration = iconGroups.flat().length * 2; export const IntegrationMarquee = ({ className }: { className?: string }) => ( -
+ {iconGroups.map((icons, columnIndex) => ( -
- {[...icons, ...icons].map((icon, iconIndex) => ( -
- -
+ {icons.map((icon) => ( + ))} -
+ ))} -
+ );