Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ed94490
refactor: use new cache store in auth service
steveiliop56 May 29, 2026
3e5757c
fix: fix race conditions
steveiliop56 May 30, 2026
ac9689d
tests: add cache store tests
steveiliop56 May 30, 2026
fe84638
fix: fix bugs in cache order
steveiliop56 May 31, 2026
82d21c3
Merge branch 'refactor/service-cache' into refactor/oidc-codes
steveiliop56 May 31, 2026
695feca
refactor: rework oidc session storage
steveiliop56 May 31, 2026
faa3156
Merge branch 'main' into refactor/oidc-store
steveiliop56 May 31, 2026
83ed9ec
feat: add db cleanup routine back
steveiliop56 Jun 1, 2026
4fe5de2
chore: fix memory store
steveiliop56 Jun 1, 2026
a723004
tests: fix oidc service tests
steveiliop56 Jun 1, 2026
1c4ca8f
chore: differentiate oauth userinfo from oidc userinfo
steveiliop56 Jun 1, 2026
b5770ef
fix: add memory back in the db bootstrap
steveiliop56 Jun 1, 2026
5caee88
fix: ensure no oidc code reuse
steveiliop56 Jun 1, 2026
b3c152f
chore: rabbit comments
steveiliop56 Jun 1, 2026
97e0e0d
wip: backend
steveiliop56 Jun 1, 2026
2454ba5
refactor: use ticket approach for oidc flow
steveiliop56 Jun 1, 2026
da90792
Merge branch 'main' into refactor/oidc-authorize
steveiliop56 Jun 6, 2026
f078e35
fix: fix oauth oidc flow
steveiliop56 Jun 6, 2026
47b7f1e
feat: add back support for request oidc param
steveiliop56 Jun 6, 2026
5e954da
chore: go mod tidy
steveiliop56 Jun 6, 2026
ace64fa
tests: rework oidc tests and aim for better coverage
steveiliop56 Jun 7, 2026
a69d22b
feat: add new quick actions menu instead of individual dropdowns in f…
steveiliop56 Jun 8, 2026
4e671ed
tests: fix proxy controller tests
steveiliop56 Jun 8, 2026
ede6e80
fix: support for oidc post (forgot that)
steveiliop56 Jun 8, 2026
3c9817c
tests: fix oidc controller tests
steveiliop56 Jun 8, 2026
6a4d85d
chore: rabbit comments
steveiliop56 Jun 9, 2026
5c5d7a4
chore: own review comments
steveiliop56 Jun 9, 2026
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
36 changes: 0 additions & 36 deletions frontend/src/components/language/language.tsx

This file was deleted.

8 changes: 3 additions & 5 deletions frontend/src/components/layout/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useAppContext } from "@/context/app-context";
import { LanguageSelector } from "../language/language";
import { Outlet } from "react-router";
import { useCallback, useEffect, useState } from "react";
import { DomainWarning } from "../domain-warning/domain-warning";
import { ThemeToggle } from "../theme-toggle/theme-toggle";
import { QuickActions } from "../quick-actions/quick-actions";

const BaseLayout = ({ children }: { children: React.ReactNode }) => {
const { ui } = useAppContext();
Expand All @@ -21,9 +20,8 @@ const BaseLayout = ({ children }: { children: React.ReactNode }) => {
backgroundPosition: "center",
}}
>
<div className="absolute top-4 right-4 flex flex-row gap-2">
<ThemeToggle />
<LanguageSelector />
<div className="absolute top-4 right-4">
<QuickActions />
</div>
<div className="max-w-sm md:min-w-sm min-w-xs">{children}</div>
</div>
Expand Down
208 changes: 208 additions & 0 deletions frontend/src/components/quick-actions/quick-actions.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import { languages, SupportedLanguage } from "@/lib/i18n/locales";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuSeparator,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuTrigger,
} from "../ui/dropdown-menu";
import { useState } from "react";
import i18n from "@/lib/i18n/i18n";
import { useUserContext } from "@/context/user-context";
import { ScrollArea } from "../ui/scroll-area";
import { useTheme } from "../providers/theme-provider";
import {
Check,
DoorOpenIcon,
Languages,
Monitor,
Moon,
Palette,
Settings,
Sun,
} from "lucide-react";
import { useTranslation } from "react-i18next";
import { useLocation } from "react-router";
import { useRef } from "react";
import {
useScreenParams,
recompileScreenParams,
} from "@/lib/hooks/screen-params";
import { useMutation } from "@tanstack/react-query";
import axios from "axios";
import { toast } from "sonner";
import { useEffect } from "react";

function Avatar({ initial }: { initial: string }) {
return (
<span className="group relative grid size-10 place-items-center rounded-full">
<span className="absolute inset-0 overflow-hidden rounded-full bg-linear-to-b from-neutral-50 to-neutral-100 dark:from-neutral-700 dark:to-neutral-950 shadow-lg"></span>
<span className="relative text-sm font-semibold text-primary">
{initial}
</span>
</span>
);
}

export const QuickActions = () => {
const { auth } = useUserContext();
const { theme, setTheme } = useTheme();
const { t } = useTranslation();
const { search } = useLocation();

const [language, setLanguage] = useState<SupportedLanguage>(
i18n.language as SupportedLanguage,
);

const redirectTimer = useRef<number | null>(null);
const searchParams = new URLSearchParams(search);
const screenParams = useScreenParams(searchParams);
const compiledParams = recompileScreenParams(screenParams);

const logoutMutation = useMutation({
mutationFn: () => axios.post("/api/user/logout"),
mutationKey: ["logout"],
onSuccess: () => {
toast.success(t("logoutSuccessTitle"), {
description: t("logoutSuccessSubtitle"),
});

redirectTimer.current = window.setTimeout(() => {
window.location.replace(`/login${compiledParams}`);
}, 500);
},
onError: () => {
toast.error(t("logoutFailTitle"), {
description: t("logoutFailSubtitle"),
});
},
});

useEffect(() => {
return () => {
if (redirectTimer.current) {
clearTimeout(redirectTimer.current);
}
};
}, [redirectTimer]);

const initial = auth.authenticated
? (auth.name[0] || "U").toUpperCase()
: null;

const handleSelect = (option: string) => {
setLanguage(option as SupportedLanguage);
i18n.changeLanguage(option as SupportedLanguage);
};

const themes = [
{ key: "light", label: t("quickActionsThemeLight"), icon: Sun },
{ key: "dark", label: t("quickActionsThemeDark"), icon: Moon },
{ key: "system", label: t("quickActionsThemeSystem"), icon: Monitor },
] as const;

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button
aria-label={t("quickActionsTitle")}
className="rounded-full transition-transform duration-200 will-change-transform hover:scale-105 hover:cursor-pointer focus:ring-0 focus:outline-3 focus:outline-ring/50"
>
{auth.authenticated ? (
<Avatar initial={initial!} />
) : (
<span className="bg-card text-primary border-border size-10 flex items-center justify-center rounded-full border shadow-lg">
<Settings className="size-4" />
</span>
)}
</button>
</DropdownMenuTrigger>

<DropdownMenuContent
align="end"
sideOffset={8}
className="rounded-xl p-1"
>
{auth.authenticated && (
<>
<DropdownMenuLabel className="flex items-center gap-3 p-2">
<div className="bg-foreground text-background flex size-9 shrink-0 items-center justify-center rounded-full text-sm font-medium">
{initial}
</div>
<div className="flex min-w-0 flex-col">
<span className="truncate text-sm font-medium">
{auth.name}
</span>
<span className="text-muted-foreground truncate text-xs font-normal">
{auth.email}
</span>
</div>
</DropdownMenuLabel>

<DropdownMenuSeparator />
</>
)}

<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Languages className="size-4" />
{t("quickActionsLanguage")}
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent sideOffset={8} className="rounded-xl p-1">
<ScrollArea className="h-80">
{Object.entries(languages).map(([key, value]) => (
<DropdownMenuItem
key={key}
onSelect={() => handleSelect(key)}
>
{value}
{language === key && <Check className="size-4" />}
</DropdownMenuItem>
))}
</ScrollArea>
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>

<DropdownMenuSub>
<DropdownMenuSubTrigger>
<Palette className="size-4" />
{t("quickActionsTheme")}
</DropdownMenuSubTrigger>
<DropdownMenuPortal>
<DropdownMenuSubContent className="rounded-xl p-1" sideOffset={8}>
{themes.map(({ key, label, icon: Icon }) => (
<DropdownMenuItem key={key} onClick={() => setTheme(key)}>
<span className="flex items-center gap-2">
<Icon className="size-4" />
{label}
</span>
{theme === key && <Check className="size-4" />}
</DropdownMenuItem>
))}
</DropdownMenuSubContent>
</DropdownMenuPortal>
</DropdownMenuSub>

{auth.authenticated && (
<>
<DropdownMenuSeparator />
<DropdownMenuItem
onSelect={() => logoutMutation.mutate()}
className="text-destructive"
>
<DoorOpenIcon className="size-4" />
{t("quickActionsLogout")}
</DropdownMenuItem>
</>
)}
</DropdownMenuContent>
</DropdownMenu>
);
};
40 changes: 0 additions & 40 deletions frontend/src/components/theme-toggle/theme-toggle.tsx

This file was deleted.

56 changes: 56 additions & 0 deletions frontend/src/components/ui/scroll-area.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import * as React from "react"
import { ScrollArea as ScrollAreaPrimitive } from "radix-ui"

import { cn } from "@/lib/utils"

function ScrollArea({
className,
children,
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return (
<ScrollAreaPrimitive.Root
data-slot="scroll-area"
className={cn("relative", className)}
{...props}
>
<ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport"
className="size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1"
>
{children}
</ScrollAreaPrimitive.Viewport>
<ScrollBar />
<ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root>
)
}

function ScrollBar({
className,
orientation = "vertical",
...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return (
<ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar"
orientation={orientation}
className={cn(
"flex touch-none p-px transition-colors select-none",
orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" &&
"h-2.5 flex-col border-t border-t-transparent",
className
)}
{...props}
>
<ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb"
className="relative flex-1 rounded-full bg-border"
/>
</ScrollAreaPrimitive.ScrollAreaScrollbar>
)
}

export { ScrollArea, ScrollBar }
17 changes: 17 additions & 0 deletions frontend/src/lib/hooks/login-for.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
type UseLoginForProps = {
login_for?: "oidc" | "app";
compiledParams: string;
};

export const useLoginFor = (props: UseLoginForProps): string => {
const { login_for, compiledParams } = props;

switch (login_for) {
case "oidc":
return "/oidc/authorize" + compiledParams;
case "app":
return "/continue" + compiledParams;
default:
return "/logout";
}
};
Loading