Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,14 @@ export const AccountMenu = () => {
<HeaderMenuItems position="right" classNameSize="w-[272px]">
<div className="divide-y divide-foreground/10">
<div>
{isAdmin && !autoLogin && (
<div className="flex items-center justify-between rounded-md border border-border/40 bg-muted px-4 py-2 text-xs font-medium text-foreground">
<span>You are an Admin</span>
<span className="rounded-full bg-primary/10 px-2 py-0.5 text-[10px] uppercase tracking-wide text-primary">
Admin
</span>
</div>
)}
<div className="h-[44px] items-center px-4 pt-3">
<div className="flex items-center justify-between">
<span
Expand Down
4 changes: 4 additions & 0 deletions src/frontend/src/constants/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,10 @@ export const CONTROL_NEW_USER = {
password: "",
is_active: false,
is_superuser: false,
optins: {
trial_access_until: "",
skip_trial_access: false,
},
};

export const tabsCode = [];
Expand Down
25 changes: 24 additions & 1 deletion src/frontend/src/customization/components/ClerkAccountMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IS_CLERK_AUTH, useLogout } from "@/clerk/auth";
import { LogOut, Users, Settings, User } from "lucide-react";
import { AccountMenu } from "@/components/core/appHeaderComponent/components/AccountMenu";
import { cn } from "@/utils/utils";
import useAuthStore from "@/stores/authStore";
import {
DropdownMenu,
DropdownMenuTrigger,
Expand All @@ -17,6 +18,7 @@ export function ClerkAccountMenu() {
const { openUserProfile, openOrganizationProfile } = useClerk();
const { user } = useUser();
const [menuOpen, setMenuOpen] = useState(false);
const isAdmin = useAuthStore((state) => state.isAdmin);

const handleLogout = () => {
mutationLogout();
Expand Down Expand Up @@ -72,6 +74,14 @@ export function ClerkAccountMenu() {

{/* Menu Items */}
<div className="pt-2 text-sm">
{isAdmin ? (
<div className="mx-4 mb-2 flex items-center justify-between rounded-md border border-border/40 bg-muted px-3 py-2 text-xs font-medium text-foreground">
<span>You are an Admin</span>
<span className="rounded-full bg-primary/10 px-2 py-0.5 text-[10px] uppercase tracking-wide text-primary">
Admin
</span>
</div>
) : null}
{/* Manage Account */}
<DropdownMenuItem
onClick={handleOpenUserProfile}
Expand All @@ -90,6 +100,19 @@ export function ClerkAccountMenu() {
Members
</DropdownMenuItem>

{/* Admin */}
<DropdownMenuItem
asChild
className="flex items-center gap-3 px-5 py-3 rounded-lg hover:bg-muted/30 transition"
>
<a href="/admin" className="flex items-center gap-3">
<span className="inline-flex h-4 w-4 items-center justify-center rounded-full bg-primary/10 text-[10px] font-semibold uppercase text-primary">
A
</span>
Admin
</a>
</DropdownMenuItem>

{/* Settings */}
<DropdownMenuItem asChild>
<a
Expand Down Expand Up @@ -122,4 +145,4 @@ export function ClerkAccountMenu() {
);
}

export default ClerkAccountMenu;
export default ClerkAccountMenu;
74 changes: 72 additions & 2 deletions src/frontend/src/modals/userManagementModal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,14 @@ export default function UserManagementModal({
const [confirmPassword, setConfirmPassword] = useState(data?.password ?? "");
const [isActive, setIsActive] = useState(data?.is_active ?? false);
const [isSuperUser, setIsSuperUser] = useState(data?.is_superuser ?? false);
const [trialDate, setTrialDate] = useState<string>(
data?.optins?.trial_access_until
? data.optins.trial_access_until.split("T")[0]
: "",
);
const [skipTrial, setSkipTrial] = useState<boolean>(
Boolean(data?.optins?.skip_trial_access),
);
const [inputState, setInputState] = useState<UserInputType>(CONTROL_NEW_USER);
const { userData } = useContext(AuthContext);

Expand All @@ -50,6 +58,12 @@ export default function UserManagementModal({
setUserName(data.username);
setIsActive(data.is_active);
setIsSuperUser(data.is_superuser);
setTrialDate(
data.optins?.trial_access_until
? data.optins.trial_access_until.split("T")[0]
: "",
);
setSkipTrial(Boolean(data.optins?.skip_trial_access));

handleInput({ target: { name: "username", value: username } });
handleInput({ target: { name: "is_active", value: isActive } });
Expand All @@ -64,6 +78,8 @@ export default function UserManagementModal({
setConfirmPassword("");
setIsActive(false);
setIsSuperUser(false);
setTrialDate("");
setSkipTrial(false);
}

return (
Expand All @@ -85,7 +101,21 @@ export default function UserManagementModal({
return;
}
resetForm();
onConfirm(1, inputState);
const trialAccess = trialDate
? new Date(trialDate).toISOString()
: undefined;
onConfirm(1, {
...inputState,
username,
password,
is_active: isActive,
is_superuser: isSuperUser,
optins: {
...(data?.optins ?? {}),
...(trialAccess ? { trial_access_until: trialAccess } : {}),
skip_trial_access: skipTrial,
},
});
setOpen(false);
event.preventDefault();
}}
Expand Down Expand Up @@ -278,6 +308,44 @@ export default function UserManagementModal({
</Form.Field>
)}
</div>

<div className="grid gap-2">
<Form.Field name="trial_end_date">
<Form.Label className="data-[invalid]:label-invalid">
Trial end date
</Form.Label>
<Form.Control asChild>
<input
type="date"
value={trialDate}
className="primary-input"
onChange={(event) => setTrialDate(event.target.value)}
/>
</Form.Control>
</Form.Field>
<Form.Field name="skip_trial_access">
<div className="flex items-center gap-2">
<Form.Control asChild>
<Checkbox
checked={skipTrial}
id="skip_trial_access"
className="relative top-0.5"
onCheckedChange={(value) => {
setSkipTrial(Boolean(value));
}}
/>
</Form.Control>
<Form.Label className="data-[invalid]:label-invalid">
Skip trial for this user
</Form.Label>
</div>
</Form.Field>
{data?.optins?.trial_access_until ? (
<p className="text-xs text-muted-foreground">
Stored value: {data.optins.trial_access_until}
</p>
) : null}
</div>
</div>

<div className="float-right">
Expand All @@ -292,7 +360,9 @@ export default function UserManagementModal({
</Button>

<Form.Submit asChild>
<Button className="mt-8">{confirmationText}</Button>
<Button className="mt-8" type="submit">
{confirmationText}
</Button>
</Form.Submit>
</div>
</Form.Root>
Expand Down
38 changes: 25 additions & 13 deletions src/frontend/src/pages/AppInitPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import useAuthStore from "@/stores/authStore";
export function AppInitPage() {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated);
const { pathname } = useLocation();
const isClerkAuth = IS_CLERK_AUTH;

const refreshStars = useDarkStore((state) => state.refreshStars);
const refreshDiscordCount = useDarkStore((state) => state.refreshDiscordCount);
Expand All @@ -35,23 +36,28 @@ export function AppInitPage() {

// Skip the initialization queries when unauthenticated visitors load the
// unauthenticated marketing landing page rendered at the root route.
const skipAppInit = !isAuthenticated && pathname === "/";
const skipAppInit = !isAuthenticated && pathname === "/";
const waitForClerkAuth = isClerkAuth && !isAuthenticated;

const { isFetched, refetch } = useGetAutoLogin({
enabled: isLoaded && !shouldSkip && !skipAppInit,
const shouldSkipAutoLogin = isClerkAuth;

const { isFetched: isAutoLoginFetched, refetch: refetchAutoLogin } = useGetAutoLogin({
enabled: !shouldSkipAutoLogin && isLoaded && !shouldSkip && !skipAppInit && !waitForClerkAuth,
});

const isFetched = shouldSkipAutoLogin ? true : isAutoLoginFetched;

const { isFetched: isConfigFetched } = useGetConfig({
enabled: isFetched && !shouldSkip && !skipAppInit,
enabled: isFetched && !shouldSkip && !skipAppInit && !waitForClerkAuth,
});

useGetVersionQuery({ enabled: isFetched && !shouldSkip && !skipAppInit });
useGetGlobalVariables({ enabled: isFetched && !shouldSkip && !skipAppInit });
useGetTagsQuery({ enabled: isFetched && !shouldSkip && !skipAppInit });
useGetFoldersQuery({ enabled: isFetched && !shouldSkip && !skipAppInit });
useGetVersionQuery({ enabled: isFetched && !shouldSkip && !skipAppInit && !waitForClerkAuth });
useGetGlobalVariables({ enabled: isFetched && !shouldSkip && !skipAppInit && !waitForClerkAuth });
useGetTagsQuery({ enabled: isFetched && !shouldSkip && !skipAppInit && !waitForClerkAuth });
useGetFoldersQuery({ enabled: isFetched && !shouldSkip && !skipAppInit && !waitForClerkAuth });

const { isFetched: isExamplesFetched, refetch: refetchExamples } = useGetBasicExamplesQuery({
enabled: !shouldSkip && !skipAppInit,
enabled: !shouldSkip && !skipAppInit && !waitForClerkAuth,
});

useEffect(() => {
Expand All @@ -63,16 +69,22 @@ export function AppInitPage() {
refreshDiscordCount();
}

if (isConfigFetched && !shouldSkip) {
refetch();
if (isConfigFetched && !shouldSkip && !waitForClerkAuth) {
if (!shouldSkipAutoLogin) {
refetchAutoLogin();
}
refetchExamples();
}
}, [isFetched, isConfigFetched, shouldSkip, skipAppInit]);
}, [isFetched, isConfigFetched, shouldSkip, skipAppInit, waitForClerkAuth]);

if (skipAppInit) {
return <Outlet />;
}

if (waitForClerkAuth) {
return <CustomLoadingPage />;
}

return (
//need parent component with width and height
<>
Expand All @@ -85,4 +97,4 @@ export function AppInitPage() {
)}
</>
);
}
}
35 changes: 35 additions & 0 deletions src/frontend/src/pages/SettingsPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ const SettingsPageBase = ({
const SettingsPageWithClerk = () => {
const { openUserProfile, openOrganizationProfile } = useClerk();
const { organization, isLoaded: isOrganizationLoaded } = useOrganization();
const isAdmin = useAuthStore((state) => state.isAdmin);

const canManageMembers = Boolean(
isOrganizationLoaded && organization?.id && openOrganizationProfile,
Expand Down Expand Up @@ -144,6 +145,20 @@ const SettingsPageWithClerk = () => {
onClick: handleManageMembers,
disabled: !canManageMembers,
},
...(isAdmin
? [
{
title: "Admin",
href: "/admin",
icon: (
<ForwardedIconComponent
name="Shield"
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
/>
),
} satisfies SidebarNavItem,
]
: []),
{
title: "Debugging",
href: "/settings/debugging",
Expand All @@ -161,6 +176,26 @@ const SettingsPageWithClerk = () => {
};

const SettingsPageWithoutClerk = () => <SettingsPageBase />;
const SettingsPageWithoutClerk = () => {
const isAdmin = useAuthStore((state) => state.isAdmin);

const additionalNavItems: SidebarNavItem[] = isAdmin
? [
{
title: "Admin",
href: "/admin",
icon: (
<ForwardedIconComponent
name="Shield"
className="w-4 flex-shrink-0 justify-start stroke-[1.5]"
/>
),
},
]
: [];

return <SettingsPageBase additionalNavItems={additionalNavItems} />;
};

export default function SettingsPage(): JSX.Element {
if (IS_CLERK_AUTH) {
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -420,4 +420,4 @@ const router = createBrowserRouter(
{ basename: BASENAME || undefined },
);

export default router;
export default router;
6 changes: 6 additions & 0 deletions src/frontend/src/types/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ export type changeUser = {
discord_clicked?: boolean;
dialog_dismissed?: boolean;
mcp_dialog_dismissed?: boolean;
trial_access_until?: string;
skip_trial_access?: boolean;
trial_access_days?: number;
};
};

Expand All @@ -187,6 +190,9 @@ export type Users = {
discord_clicked?: boolean;
dialog_dismissed?: boolean;
mcp_dialog_dismissed?: boolean;
trial_access_until?: string;
skip_trial_access?: boolean;
trial_access_days?: number;
};
};

Expand Down
5 changes: 5 additions & 0 deletions src/frontend/src/types/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,11 @@ export type UserInputType = {
id?: string;
create_at?: string;
updated_at?: string;
optins?: {
trial_access_until?: string;
skip_trial_access?: boolean;
trial_access_days?: number;
};
};

export type ApiKeyType = {
Expand Down
Loading