-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Implement role management and command menu
- feat: Add admin role overwrite functionality - feat: Introduce command menu configuration and filtering - refactor: Standardize atom debug labels for better consistency
- Loading branch information
Showing
18 changed files
with
532 additions
and
247 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
74
apps/forge/src/components/command-menu/actions/role-selector.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> | ||
); | ||
} |
Oops, something went wrong.