Skip to content

Commit

Permalink
feat: Implement role management and command menu
Browse files Browse the repository at this point in the history
- feat: Add admin role overwrite functionality
- feat: Introduce command menu configuration and filtering
- refactor: Standardize atom debug labels for better consistency
  • Loading branch information
Sampiiiii committed Nov 18, 2024
1 parent 344b063 commit 3466480
Show file tree
Hide file tree
Showing 18 changed files with 532 additions and 247 deletions.
12 changes: 12 additions & 0 deletions apps/forge/src/atoms/adminAtoms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ATOM_KEYS } from "@/config/constants.ts";
import { atomWithStorage } from "jotai/utils";

export const adminOverwrittenRoles = atomWithStorage<string[]>(ATOM_KEYS.ADMIN_OVERWRITTEN_ROLES, [], undefined, {
getOnInit: true,
});
adminOverwrittenRoles.debugLabel = "admin:overwrittenUserRoles";

export const adminOverwriteRoles = atomWithStorage<boolean>(ATOM_KEYS.ADMIN_OVERWRITE_ROLES, false, undefined, {
getOnInit: true,
});
adminOverwriteRoles.debugLabel = "admin:overwriteRoles";
50 changes: 40 additions & 10 deletions apps/forge/src/atoms/authSessionAtoms.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,28 @@
import {ATOM_KEYS} from "@/config/constants.ts";
// src/atoms/authSessionAtoms.ts

import { adminOverwriteRoles, adminOverwrittenRoles } from "@/atoms/adminAtoms.ts";
import { ATOM_KEYS } from "@/config/constants.ts";
import { User } from "@ignis/types/users";
// src/atoms/session/authSessionAtoms.ts
import { atom } from "jotai";
import { atomWithStorage } from 'jotai/utils'

import { atomWithStorage } from "jotai/utils";

// Atom for managing authentication state
export const isAuthenticatedAtom = atom(false);
isAuthenticatedAtom.debugLabel = "isAuthenticated";
isAuthenticatedAtom.debugLabel = "auth:isAuthenticated";

// Atom for storing previous pathname for redirection
export const previousPathnameAtom = atomWithStorage<string | null>(ATOM_KEYS.AUTH_REDIRECT_PATH, null, undefined, { getOnInit: true});
previousPathnameAtom.debugLabel = "previousPathname";
export const previousPathnameAtom = atomWithStorage<string | null>(ATOM_KEYS.AUTH_REDIRECT_PATH, null, undefined, {
getOnInit: true,
});
previousPathnameAtom.debugLabel = "auth:previousPathname";

// Atom for storing user data
export const userAtom = atom<User | null>(null);
userAtom.debugLabel = "user";
userAtom.debugLabel = "auth:user";

// Atom to manage the loading state during authentication checks
export const loadingAtom = atom(true);
loadingAtom.debugLabel = "authIsLoading";
loadingAtom.debugLabel = "auth:isLoading";

// Effect atom to clear `userAtom` if `isAuthenticatedAtom` is set to `false`
export const authEffectAtom = atom(
Expand All @@ -30,8 +33,35 @@ export const authEffectAtom = atom(
// If `isAuthenticatedAtom` is set to `false`, clear `userAtom`
if (!newAuthState) {
set(userAtom, null);
set(userRolesAtom, []);
set(originalUserRolesAtom, []);
}
},
);

authEffectAtom.debugLabel = "authEffect";
authEffectAtom.debugLabel = "auth:authEffect";

export const originalUserRolesAtom = atom<string[]>([]);
originalUserRolesAtom.debugLabel = "auth:originalRoles";

export const userRolesAtom = atom(
// Read function
(get) => {
const isOverwritten = get(adminOverwriteRoles);
const overwrittenRoles = get(adminOverwrittenRoles);
const originalRoles = get(originalUserRolesAtom);

return isOverwritten ? overwrittenRoles : originalRoles;
},
// Write function
(get, set, newRoles: string[]) => {
const isOverwritten = get(adminOverwriteRoles);

if (!isOverwritten) {
// Only update originalUserRolesAtom if admin overwrite is not active
set(originalUserRolesAtom, newRoles);
}
// If admin overwrite is active, writes to userRolesAtom will be ignored
},
);
userRolesAtom.debugLabel = "auth:userRoles";
5 changes: 3 additions & 2 deletions apps/forge/src/atoms/commandMenuAtoms.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// @/store/command-menu.ts
import { atom } from 'jotai'
import { atom } from "jotai";

export const commandMenuIsOpenAtom = atom(false)
export const commandMenuIsOpenAtom = atom(false);
commandMenuIsOpenAtom.debugLabel = "commandMenu:isOpen";
112 changes: 54 additions & 58 deletions apps/forge/src/atoms/signInAppAtoms.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,86 @@
// src/atoms/session/signInAppAtoms.ts

import { locationStatus } from "@/services/sign_in/locationService.ts";
import { SignInSession } from "@/types/sign_in";
import { LocationName } from "@ignis/types/sign_in";
import { atom } from "jotai";
import {locationStatus} from "@/services/sign_in/locationService.ts";
import {atomWithQuery} from "jotai-tanstack-query";
import { SignInSession } from "@/types/sign_in";

import { atomWithQuery } from "jotai-tanstack-query";

// ------ Sign in App Data Management (location data, handling selected location, etc.)

export const activeLocationAtom = atom<LocationName>("MAINSPACE");
activeLocationAtom.debugLabel = "activeLocation";
activeLocationAtom.debugLabel = "signIn:activeLocation";

export const locationStatusesAtom = atomWithQuery(() => ({
queryKey: ["locationStatus"],
queryFn: locationStatus,
staleTime: 4000,
refetchInterval: 5000,
queryKey: ["locationStatus"],
queryFn: locationStatus,
staleTime: 4000,
refetchInterval: 5000,
}));
locationStatusesAtom.debugLabel = "locationStatuses";
locationStatusesAtom.debugLabel = "signIn:locationStatuses";

// ------ Session Management (for sign in actions && global sign in for users)

// Individual atoms with proper typing from the interface
export const sessionUcardNumberAtom = atom<SignInSession['ucard_number']>('');
export const sessionUcardNumberAtom = atom<SignInSession["ucard_number"]>("");
sessionUcardNumberAtom.debugLabel = "signInSession:ucardNumber";
export const sessionUserAtom = atom<SignInSession['user']>(null);
export const sessionUserAtom = atom<SignInSession["user"]>(null);
sessionUserAtom.debugLabel = "signInSession:user";
export const sessionSignInReasonAtom = atom<SignInSession['sign_in_reason']>(null);
export const sessionSignInReasonAtom = atom<SignInSession["sign_in_reason"]>(null);
sessionSignInReasonAtom.debugLabel = "signInSession:signInReason";
export const sessionTrainingAtom = atom<SignInSession['training']>(null);
export const sessionTrainingAtom = atom<SignInSession["training"]>(null);
sessionTrainingAtom.debugLabel = "signInSession:training";
export const sessionNavigationBacktrackingAtom = atom<SignInSession['navigation_is_backtracking']>(false);
export const sessionNavigationBacktrackingAtom = atom<SignInSession["navigation_is_backtracking"]>(false);
sessionNavigationBacktrackingAtom.debugLabel = "signInSession:navigationBacktracking";
export const sessionErroredAtom = atom<SignInSession['session_errored']>(false);
export const sessionErroredAtom = atom<SignInSession["session_errored"]>(false);
sessionErroredAtom.debugLabel = "signInSession:sessionErrored";

// Combined session atom
export const sessionAtom = atom(
(get) => ({
ucard_number: get(sessionUcardNumberAtom),
user: get(sessionUserAtom),
sign_in_reason: get(sessionSignInReasonAtom),
training: get(sessionTrainingAtom),
navigation_is_backtracking: get(sessionNavigationBacktrackingAtom),
session_errored: get(sessionErroredAtom),
} satisfies SignInSession),
(_get, set, newSession: SignInSession | null) => {
if (newSession === null) {
// Reset all atoms to their default values
set(sessionUcardNumberAtom, '');
set(sessionUserAtom, null);
set(sessionSignInReasonAtom, null);
set(sessionTrainingAtom, null);
set(sessionNavigationBacktrackingAtom, false);
set(sessionErroredAtom, false);
} else {
// Update all atoms with new values
set(sessionUcardNumberAtom, newSession.ucard_number);
set(sessionUserAtom, newSession.user);
set(sessionSignInReasonAtom, newSession.sign_in_reason);
set(sessionTrainingAtom, newSession.training);
set(sessionNavigationBacktrackingAtom, newSession.navigation_is_backtracking);
set(sessionErroredAtom, newSession.session_errored);
}
(get) =>
({
ucard_number: get(sessionUcardNumberAtom),
user: get(sessionUserAtom),
sign_in_reason: get(sessionSignInReasonAtom),
training: get(sessionTrainingAtom),
navigation_is_backtracking: get(sessionNavigationBacktrackingAtom),
session_errored: get(sessionErroredAtom),
}) satisfies SignInSession,
(_get, set, newSession: SignInSession | null) => {
if (newSession === null) {
// Reset all atoms to their default values
set(sessionUcardNumberAtom, "");
set(sessionUserAtom, null);
set(sessionSignInReasonAtom, null);
set(sessionTrainingAtom, null);
set(sessionNavigationBacktrackingAtom, false);
set(sessionErroredAtom, false);
} else {
// Update all atoms with new values
set(sessionUcardNumberAtom, newSession.ucard_number);
set(sessionUserAtom, newSession.user);
set(sessionSignInReasonAtom, newSession.sign_in_reason);
set(sessionTrainingAtom, newSession.training);
set(sessionNavigationBacktrackingAtom, newSession.navigation_is_backtracking);
set(sessionErroredAtom, newSession.session_errored);
}
},
);

sessionAtom.debugLabel = "SignInSession";
sessionAtom.debugLabel = "signInSession:session";

// Reset helper
export const resetSessionAtom = atom(null, (_get, set) => {
set(sessionAtom, null);
set(sessionAtom, null);
});
resetSessionAtom.debugLabel = "resetSession";

resetSessionAtom.debugLabel = "signInSession:resetSession";

export const initializeSessionAtom = atom(
null,
(_get, set, ucardNumber: string) => {
set(sessionUcardNumberAtom, ucardNumber);
set(sessionUserAtom, null);
set(sessionTrainingAtom, null);
set(sessionSignInReasonAtom, null);
set(sessionErroredAtom, false);
set(sessionNavigationBacktrackingAtom, false);
}
);
initializeSessionAtom.debugLabel = "initializeSession";
export const initializeSessionAtom = atom(null, (_get, set, ucardNumber: string) => {
set(sessionUcardNumberAtom, ucardNumber);
set(sessionUserAtom, null);
set(sessionTrainingAtom, null);
set(sessionSignInReasonAtom, null);
set(sessionErroredAtom, false);
set(sessionNavigationBacktrackingAtom, false);
});
initializeSessionAtom.debugLabel = "signInSession:initializeSession";
4 changes: 2 additions & 2 deletions apps/forge/src/components/app-navigation/sidebar-header.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import NavAdvertisementBanner from "@/components/app-navigation/nav-advertisementbanner";
import { SidebarTrigger } from "@ignis/ui/components/ui/sidebar";
import NavAdvertisementBanner from "@/components/app-navigation/nav-advertisementbanner.tsx";

export const SidebarHeader = () => {
return (
<header className="flex h-16 shrink-0 p-3 bg-sidebar items-center gap-2 transition-[width,height] ease-linear group-has-[[data-collapsible=icon]]/sidebar-wrapper:h-12">
<div className="flex items-center gap-2 px-4">
<SidebarTrigger className="-ml-1 h-5 w-5" />
</div>
<NavAdvertisementBanner />
<NavAdvertisementBanner />
</header>
);
};
74 changes: 74 additions & 0 deletions apps/forge/src/components/command-menu/actions/role-selector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { adminOverwriteRoles, adminOverwrittenRoles } from "@/atoms/adminAtoms";
import { AVAILABLE_ROLES } from "@/config/constants";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@ignis/ui/components/ui/card";
import { Label } from "@ignis/ui/components/ui/label";
import MultiSelectFormField from "@ignis/ui/components/ui/multi-select";
import { Switch } from "@ignis/ui/components/ui/switch";
import { useAtom } from "jotai";
import { UserIcon } from "lucide-react";

export function RoleSelector() {
const [selectedRoles, setSelectedRoles] = useAtom(adminOverwrittenRoles);
const [overwriteRoles, setOverwriteRoles] = useAtom(adminOverwriteRoles);

const roleOptions = AVAILABLE_ROLES.map((role) => ({
label: role.charAt(0).toUpperCase() + role.slice(1),
value: role,
icon: UserIcon,
}));

const handleRoleChange = (newRoles: string[]) => {
setSelectedRoles(newRoles);
};

const handleOverwriteToggle = (checked: boolean) => {
setOverwriteRoles(checked);
if (!checked) {
setSelectedRoles([]);
}
};

return (
<Card className="w-full max-w-md">
<CardHeader>
<CardTitle>Admin Role Selector</CardTitle>
<CardDescription>Use this to change which role you view the site with.</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="flex items-center space-x-2">
<Switch id="overwrite-roles" checked={overwriteRoles} onCheckedChange={handleOverwriteToggle} />
<Label
htmlFor="overwrite-roles"
className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
>
Enable role overwriting
</Label>
</div>
<div className="space-y-2">
<Label htmlFor="role-select" className="text-sm font-medium">
Select Roles
</Label>
<MultiSelectFormField
id="role-select"
options={roleOptions}
defaultValue={selectedRoles}
onValueChange={handleRoleChange}
placeholder="Select roles..."
className="w-full"
disabled={!overwriteRoles}
/>
</div>
{overwriteRoles && (
<div className="text-sm text-muted-foreground">
Selected role(s): {selectedRoles.length > 0 ? selectedRoles.join(", ") : "User"}
</div>
)}
{!overwriteRoles && (
<div className="p-2 bg-muted rounded-md text-sm text-muted-foreground">
Role overwriting is disabled. Enable it to select custom roles.
</div>
)}
</CardContent>
</Card>
);
}
Loading

0 comments on commit 3466480

Please sign in to comment.