Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 23 additions & 18 deletions src/components/ui/carousel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,14 +56,30 @@ function Carousel({
},
plugins
)
const [canScrollPrev, setCanScrollPrev] = React.useState(false)
const [canScrollNext, setCanScrollNext] = React.useState(false)

const onSelect = React.useCallback((api: CarouselApi) => {
if (!api) return
setCanScrollPrev(api.canScrollPrev())
setCanScrollNext(api.canScrollNext())
}, [])
const subscribe = React.useCallback(
(callback: () => void) => {
if (!api) return () => {}
api.on("reInit", callback)
api.on("select", callback)
return () => {
api.off("reInit", callback)
api.off("select", callback)
}
},
[api]
)

const canScrollPrev = React.useSyncExternalStore(
subscribe,
() => api?.canScrollPrev() ?? false,
() => false
)
const canScrollNext = React.useSyncExternalStore(
subscribe,
() => api?.canScrollNext() ?? false,
() => false
)

const scrollPrev = React.useCallback(() => {
api?.scrollPrev()
Expand Down Expand Up @@ -91,17 +107,6 @@ function Carousel({
setApi(api)
}, [api, setApi])

React.useEffect(() => {
if (!api) return
onSelect(api)
api.on("reInit", onSelect)
api.on("select", onSelect)

return () => {
api?.off("select", onSelect)
}
}, [api, onSelect])

return (
<CarouselContext.Provider
value={{
Expand Down
32 changes: 15 additions & 17 deletions src/components/ui/multi-select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -276,24 +276,23 @@ const MultipleSelector = ({
}
}, [open])

useEffect(() => {
const [lastValue, setLastValue] = React.useState(value)
if (value !== lastValue) {
setLastValue(value)
if (value) {
setSelected(value)
}
}, [value])

useEffect(() => {
/** If `onSearch` is provided, do not trigger options updated. */
if (!arrayOptions || onSearch) {
return
}

const newOption = transToGroupOption(arrayOptions || [], groupBy)
}

if (JSON.stringify(newOption) !== JSON.stringify(options)) {
setOptions(newOption)
}
}, [arrayDefaultOptions, arrayOptions, groupBy, onSearch, options])
const incomingOptions = !arrayOptions || onSearch
? null
: transToGroupOption(arrayOptions, groupBy)
const incomingOptionsKey = incomingOptions ? JSON.stringify(incomingOptions) : null
const [lastOptionsKey, setLastOptionsKey] = React.useState<string | null>(null)
if (incomingOptions && incomingOptionsKey !== lastOptionsKey) {
setLastOptionsKey(incomingOptionsKey)
setOptions(incomingOptions)
}

useEffect(() => {
/** sync search */
Expand Down Expand Up @@ -414,7 +413,7 @@ const MultipleSelector = ({
const selectables = React.useMemo<GroupOption>(() => removePickedOption(options, selected), [options, selected])

/** Avoid Creatable Selector freezing or lagging when paste a long string. */
const commandFilter = React.useCallback(() => {
const commandFilter = () => {
if (commandProps?.filter) {
return commandProps.filter
}
Expand All @@ -425,9 +424,8 @@ const MultipleSelector = ({
}
}

// Using default filter in `cmdk`. We don&lsquo;t have to provide it.
return undefined
}, [creatable, commandProps?.filter])
}

return (
<Command
Expand Down
26 changes: 14 additions & 12 deletions src/hooks/use-mobile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ import * as React from "react"

const MOBILE_BREAKPOINT = 768

export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
function subscribe(callback: () => void) {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
mql.addEventListener("change", callback)
return () => mql.removeEventListener("change", callback)
}

function getSnapshot() {
return window.innerWidth < MOBILE_BREAKPOINT
}

React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
function getServerSnapshot() {
return false
}

return !!isMobile
export function useIsMobile() {
return React.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot)
}
21 changes: 10 additions & 11 deletions src/layouts/panel/sidebar/cluster/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {CLUSTER_ICON_LIST} from "./icon-list";
import ClusterAddDialog from "./add-dialog.tsx";
import SidebarClusterStatus from "./status.tsx";
import SidebarClusterLoader from "./loader.tsx";
import clusterStore, {useClusterActions} from "@/store/cluster-store.ts";
import {useClusterActions, useClusterId} from "@/store/cluster-store.ts";
import {useRouter} from "@/routes/hooks";
import SidebarClusterSettings
from "@/layouts/panel/sidebar/cluster/settings.tsx";
Expand All @@ -45,23 +45,23 @@ export function SidebarClusterSwitcher() {
const {t} = useTranslation();
const {isMobile} = useSidebar()
const {clusters, setClusters, loading} = useClusters();
const [activeCluster, setActiveCluster] = React.useState<Cluster | null>(null);
const [clickedCluster, setClickedCluster] = React.useState<Cluster | null>(null);
const [open, setOpen] = React.useState(false);
const [dialogMode, setDialogMode] = React.useState("add");
const {setClusterId} = useClusterActions();
const clusterId = useClusterId();
const {replace} = useRouter();

const activeCluster: Cluster | null = clusters && clusters.length > 0
? (clusters.find(c => c.id === clusterId) ?? clusters[0])
: null;

React.useEffect(() => {
if (clusters && clusters.length > 0) {
const selectedClusterId = clusterStore.getState().clusterId;
const match = selectedClusterId ? clusters.find(c => c.id === selectedClusterId) : null;
if (match) {
setActiveCluster(match);
return;
const stored = clusterId ? clusters.find(c => c.id === clusterId) : null;
if (!stored) {
setClusterId(clusters[0].id);
}
setActiveCluster(clusters[0]);
setClusterId(clusters[0].id);
return;
}
if (!loading && (!clusters || clusters.length === 0)) {
Expand All @@ -74,9 +74,8 @@ export function SidebarClusterSwitcher() {
window.location.reload();
})();
}
}, [clusters, loading, replace, setClusters, setClusterId]);
}, [clusters, clusterId, loading, replace, setClusters, setClusterId]);
const updateCluster = (cluster: Cluster) => {
setActiveCluster(cluster);
setClusterId(cluster.id);
replace("/overview/");
};
Expand Down
37 changes: 19 additions & 18 deletions src/pages/authentication/login/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,12 @@ export default function LoginForm() {
const [otp, setOtp] = useState("")
const [recoveryOpen, setRecoveryOpen] = useState(false)
const [recoveryCode, setRecoveryCode] = useState("")
const [rememberMe, setRememberMe] = useState(false)
const [rememberMe, setRememberMe] = useState(() => !!localStorage.getItem("remembered_email"))

useEffect(() => {
const rememberedEmail = localStorage.getItem("remembered_email")
if (rememberedEmail) {
form.setValue("email", rememberedEmail)
setRememberMe(true)
setTimeout(() => setFocus("password"), 0)
} else {
setTimeout(() => setFocus("email"), 0)
Expand Down Expand Up @@ -89,7 +88,7 @@ export default function LoginForm() {
}
}

const handleOtpSubmit = useCallback(async () => {
const handleOtpSubmit = useCallback(async (code: string) => {
const { email, password } = getValues()
if (!email || !password) return

Expand All @@ -98,7 +97,7 @@ export default function LoginForm() {
const response = await login({
email,
password,
multi_factor_code: otp,
multi_factor_code: code,
})

setOtp("")
Expand All @@ -110,9 +109,9 @@ export default function LoginForm() {
} finally {
setLoading(false)
}
}, [getValues, login, navigate, otp, t])
}, [getValues, login, navigate, t])

const handleRecoverySubmit = useCallback(async () => {
const handleRecoverySubmit = useCallback(async (code: string) => {
const { email, password } = getValues()
if (!email || !password) return

Expand All @@ -121,7 +120,7 @@ export default function LoginForm() {
const response = await login({
email,
password,
multi_factor_code: recoveryCode,
multi_factor_code: code,
})

if (response.success) {
Expand All @@ -132,15 +131,17 @@ export default function LoginForm() {
} finally {
setLoading(false)
}
}, [getValues, login, navigate, recoveryCode, t])
}, [getValues, login, navigate, t])

useEffect(() => {
if (otp.length === 6 && !loading) handleOtpSubmit()
}, [otp, loading, handleOtpSubmit])
const handleOtpChange = (value: string) => {
setOtp(value)
if (value.length === 6 && !loading) handleOtpSubmit(value)
}

useEffect(() => {
if (recoveryCode.length === 16 && !loading) handleRecoverySubmit()
}, [recoveryCode, loading, handleRecoverySubmit])
const handleRecoveryChange = (value: string) => {
setRecoveryCode(value)
if (value.length === 16 && !loading) handleRecoverySubmit(value)
}

if (token.authentication_token) {
return <Navigate to="/cluster/" replace />
Expand Down Expand Up @@ -248,7 +249,7 @@ export default function LoginForm() {
</DialogHeader>

<div className="flex justify-center my-4">
<InputOTP maxLength={6} value={otp} onChange={setOtp}>
<InputOTP maxLength={6} value={otp} onChange={handleOtpChange}>
<InputOTPGroup>
{[0, 1, 2].map((i) => (
<InputOTPSlot key={i} index={i} />
Expand All @@ -263,7 +264,7 @@ export default function LoginForm() {
</div>

<DialogFooter>
<Button onClick={handleOtpSubmit} disabled={loading}>
<Button onClick={() => handleOtpSubmit(otp)} disabled={loading}>
{t("authentication.login.otp.submit")}
</Button>
</DialogFooter>
Expand All @@ -284,7 +285,7 @@ export default function LoginForm() {
<InputOTP
maxLength={16}
value={recoveryCode}
onChange={setRecoveryCode}
onChange={handleRecoveryChange}
>
{[0, 4, 8, 12].map((start) => (
<InputOTPGroup key={start}>
Expand All @@ -297,7 +298,7 @@ export default function LoginForm() {

<DialogFooter>
<Button
onClick={handleRecoverySubmit}
onClick={() => handleRecoverySubmit(recoveryCode)}
disabled={loading || recoveryCode.length !== 16}
>
{t("authentication.login.recovery.confirm")}
Expand Down
Loading