Skip to content

Commit 6b4fcd9

Browse files
authored
feat: XP Boosts Management
* feat: add new page | XP Boosts Management * feat: add new UI components * refactor: add className property * feat: add new utilities * feat: remove pointer interactions (images) * feat: add new UI and initial interactions * feat: consume personal XP Boosts * refactor: reorder * feat: add functionality to change availability state * feat: update input search availability * refactor: remove unused function * refactor: extract locale compare function for sorting * feat: add more information * feat: add basic result UI * feat: find user by display name * feat: add support to search by psn or xbl * feat: consume teammate XP Boosts * feat: use personal account to get query public profile * refactor: extract in a component * feat: enable search on current accounts * feat: add general search * docs: add final dot
1 parent 9532552 commit 6b4fcd9

File tree

49 files changed

+4618
-320
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+4618
-320
lines changed

package-lock.json

Lines changed: 1087 additions & 171 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,18 @@
3434
"@mantine/core": "^7.9.1",
3535
"@mantine/hooks": "^7.9.1",
3636
"@radix-ui/react-accordion": "^1.1.2",
37+
"@radix-ui/react-context-menu": "^2.2.1",
3738
"@radix-ui/react-dialog": "^1.0.5",
3839
"@radix-ui/react-dropdown-menu": "^2.0.6",
3940
"@radix-ui/react-icons": "^1.3.0",
4041
"@radix-ui/react-label": "^2.0.2",
4142
"@radix-ui/react-popover": "^1.0.7",
43+
"@radix-ui/react-radio-group": "^1.2.0",
4244
"@radix-ui/react-scroll-area": "^1.0.5",
4345
"@radix-ui/react-separator": "^1.0.3",
4446
"@radix-ui/react-slot": "^1.0.2",
4547
"@radix-ui/react-switch": "^1.0.3",
48+
"@radix-ui/react-toggle": "^1.1.0",
4649
"@radix-ui/react-tooltip": "^1.0.7",
4750
"@tanstack/react-router": "^1.29.2",
4851
"axios": "^1.6.8",

src/components/account-list/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover'
1414
import { useAccountList } from './hooks'
1515

1616
import { getStatusProvider } from '../../lib/statuses'
17-
import { cn, parseDisplayName } from '../../lib/utils'
17+
import { cn, parseCustomDisplayName } from '../../lib/utils'
1818

1919
export function AccountList() {
2020
const {
@@ -51,7 +51,7 @@ export function AccountList() {
5151
{selected ? (
5252
<span className="block w-full">
5353
<span className="block truncate max-w-[10rem] w-full">
54-
{parseDisplayName(selected)}
54+
{parseCustomDisplayName(selected)}
5555
</span>
5656
<span className="block text-muted-foreground text-xs truncate">
5757
{getStatusProvider(selected.provider)}
@@ -83,7 +83,7 @@ export function AccountList() {
8383
<CommandEmpty>No account found</CommandEmpty>
8484
<CommandGroup>
8585
{accounts.map((account) => {
86-
const displayName = parseDisplayName(account)
86+
const displayName = parseCustomDisplayName(account)
8787

8888
return (
8989
<CommandItem

src/components/menu/history.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { useClaimedRewards } from '../../hooks/stw-operations/claimed-rewards'
99
import { useGetAccounts } from '../../hooks/accounts'
1010

1111
import { parseResource } from '../../lib/parsers/resources'
12-
import { parseDisplayName } from '../../lib/utils'
12+
import { parseCustomDisplayName } from '../../lib/utils'
1313

1414
export function HistoryMenu() {
1515
const { data } = useClaimedRewards()
@@ -48,7 +48,7 @@ function RewardSection({ data }: { data: Array<RewardsNotification> }) {
4848
return data.map((item, index) => (
4949
<div key={index}>
5050
<div className="font-bold mb-2 break-all">
51-
{parseDisplayName(accountList[item.accountId])}:
51+
{parseCustomDisplayName(accountList[item.accountId])}:
5252
</div>
5353
<ul
5454
className="space-y-1"

src/components/menu/sidebar.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,18 @@ export function SidebarMenu({
110110
Homebase Name
111111
</Link>
112112
</li>
113+
<li className="item">
114+
<Link
115+
to="/stw-operations/xpboosts"
116+
className={currentClassNameHover}
117+
activeProps={{
118+
className: cn(activeClassName),
119+
}}
120+
onClick={goToPage}
121+
>
122+
XP Boosts
123+
</Link>
124+
</li>
113125
</ul>
114126
</div>
115127
<Title className="pb-0">Account Management</Title>

src/components/selectors/accounts/hooks.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ import { useGetAccounts } from '../../../hooks/accounts'
66
import { useGetGroups } from '../../../hooks/groups'
77
import { useGetTags } from '../../../hooks/tags'
88

9-
import { parseDisplayName } from '../../../lib/utils'
9+
import {
10+
localeCompareForSorting,
11+
parseCustomDisplayName,
12+
} from '../../../lib/utils'
1013

1114
export function useAccountSelectorData({
1215
selectedAccounts,
@@ -21,7 +24,7 @@ export function useAccountSelectorData({
2124

2225
const areThereAccounts = accountsArray.length > 0
2326
const accounts: Array<SelectOption> = accountsArray.map((account) => {
24-
const label = parseDisplayName(account)
27+
const label = parseCustomDisplayName(account)
2528

2629
return {
2730
label,
@@ -73,7 +76,16 @@ export function useAccountSelectorData({
7376
.map(([key]) => key),
7477
]),
7578
]
76-
return accountIds.map((accountId) => accountList[accountId])
79+
80+
return accountIds
81+
.map((accountId) => accountList[accountId])
82+
.filter((account) => account !== undefined)
83+
.toSorted((itemA, itemB) =>
84+
localeCompareForSorting(
85+
parseCustomDisplayName(itemA),
86+
parseCustomDisplayName(itemB)
87+
)
88+
)
7789
}
7890

7991
return {

src/components/selectors/accounts/index.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { InputTags } from '../../ui/third-party/extended/input-tags'
55

66
export function AccountSelectors({
77
accounts,
8+
isDisabled,
89
tags,
910
onUpdateAccounts,
1011
onUpdateTags,
@@ -13,6 +14,7 @@ export function AccountSelectors({
1314
options: Array<SelectOption>
1415
value: Array<SelectOption>
1516
}
17+
isDisabled?: boolean
1618
tags: {
1719
options: Array<SelectOption>
1820
value: Array<SelectOption>
@@ -27,13 +29,15 @@ export function AccountSelectors({
2729
options={accounts.options}
2830
value={accounts.value}
2931
onChange={onUpdateAccounts ?? (() => {})}
32+
isDisabled={isDisabled}
3033
/>
3134
<SeparatorWithTitle>Or</SeparatorWithTitle>
3235
<InputTags
3336
placeholder="Select some tags..."
3437
options={tags.options}
3538
value={tags.value}
3639
onChange={onUpdateTags ?? (() => {})}
40+
isDisabled={isDisabled}
3741
/>
3842
</div>
3943
)

src/components/ui/context-menu.tsx

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
import type {
2+
ComponentPropsWithoutRef,
3+
ElementRef,
4+
HTMLAttributes,
5+
} from 'react'
6+
7+
import * as ContextMenuPrimitive from '@radix-ui/react-context-menu'
8+
import { Check, ChevronRight, Circle } from 'lucide-react'
9+
import { forwardRef } from 'react'
10+
11+
import { cn } from '../../lib/utils'
12+
13+
const ContextMenu = ContextMenuPrimitive.Root
14+
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
15+
const ContextMenuGroup = ContextMenuPrimitive.Group
16+
const ContextMenuPortal = ContextMenuPrimitive.Portal
17+
const ContextMenuSub = ContextMenuPrimitive.Sub
18+
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
19+
20+
const ContextMenuSubTrigger = forwardRef<
21+
ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
22+
ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
23+
inset?: boolean
24+
}
25+
>(({ className, inset, children, ...props }, ref) => (
26+
<ContextMenuPrimitive.SubTrigger
27+
ref={ref}
28+
className={cn(
29+
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground',
30+
inset && 'pl-8',
31+
className
32+
)}
33+
{...props}
34+
>
35+
{children}
36+
<ChevronRight className="ml-auto h-4 w-4" />
37+
</ContextMenuPrimitive.SubTrigger>
38+
))
39+
ContextMenuSubTrigger.displayName =
40+
ContextMenuPrimitive.SubTrigger.displayName
41+
42+
const ContextMenuSubContent = forwardRef<
43+
ElementRef<typeof ContextMenuPrimitive.SubContent>,
44+
ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
45+
>(({ className, ...props }, ref) => (
46+
<ContextMenuPrimitive.SubContent
47+
ref={ref}
48+
className={cn(
49+
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
50+
className
51+
)}
52+
{...props}
53+
/>
54+
))
55+
ContextMenuSubContent.displayName =
56+
ContextMenuPrimitive.SubContent.displayName
57+
58+
const ContextMenuContent = forwardRef<
59+
ElementRef<typeof ContextMenuPrimitive.Content>,
60+
ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
61+
>(({ className, ...props }, ref) => (
62+
<ContextMenuPrimitive.Portal>
63+
<ContextMenuPrimitive.Content
64+
ref={ref}
65+
className={cn(
66+
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md animate-in fade-in-80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
67+
className
68+
)}
69+
{...props}
70+
/>
71+
</ContextMenuPrimitive.Portal>
72+
))
73+
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
74+
75+
const ContextMenuItem = forwardRef<
76+
ElementRef<typeof ContextMenuPrimitive.Item>,
77+
ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
78+
inset?: boolean
79+
}
80+
>(({ className, inset, ...props }, ref) => (
81+
<ContextMenuPrimitive.Item
82+
ref={ref}
83+
className={cn(
84+
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
85+
inset && 'pl-8',
86+
className
87+
)}
88+
{...props}
89+
/>
90+
))
91+
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
92+
93+
const ContextMenuCheckboxItem = forwardRef<
94+
ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
95+
ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
96+
>(({ className, children, checked, ...props }, ref) => (
97+
<ContextMenuPrimitive.CheckboxItem
98+
ref={ref}
99+
className={cn(
100+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
101+
className
102+
)}
103+
checked={checked}
104+
{...props}
105+
>
106+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
107+
<ContextMenuPrimitive.ItemIndicator>
108+
<Check className="h-4 w-4" />
109+
</ContextMenuPrimitive.ItemIndicator>
110+
</span>
111+
{children}
112+
</ContextMenuPrimitive.CheckboxItem>
113+
))
114+
ContextMenuCheckboxItem.displayName =
115+
ContextMenuPrimitive.CheckboxItem.displayName
116+
117+
const ContextMenuRadioItem = forwardRef<
118+
ElementRef<typeof ContextMenuPrimitive.RadioItem>,
119+
ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
120+
>(({ className, children, ...props }, ref) => (
121+
<ContextMenuPrimitive.RadioItem
122+
ref={ref}
123+
className={cn(
124+
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
125+
className
126+
)}
127+
{...props}
128+
>
129+
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
130+
<ContextMenuPrimitive.ItemIndicator>
131+
<Circle className="h-2 w-2 fill-current" />
132+
</ContextMenuPrimitive.ItemIndicator>
133+
</span>
134+
{children}
135+
</ContextMenuPrimitive.RadioItem>
136+
))
137+
ContextMenuRadioItem.displayName =
138+
ContextMenuPrimitive.RadioItem.displayName
139+
140+
const ContextMenuLabel = forwardRef<
141+
ElementRef<typeof ContextMenuPrimitive.Label>,
142+
ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
143+
inset?: boolean
144+
}
145+
>(({ className, inset, ...props }, ref) => (
146+
<ContextMenuPrimitive.Label
147+
ref={ref}
148+
className={cn(
149+
'px-2 py-1.5 text-sm font-semibold text-foreground',
150+
inset && 'pl-8',
151+
className
152+
)}
153+
{...props}
154+
/>
155+
))
156+
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
157+
158+
const ContextMenuSeparator = forwardRef<
159+
ElementRef<typeof ContextMenuPrimitive.Separator>,
160+
ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
161+
>(({ className, ...props }, ref) => (
162+
<ContextMenuPrimitive.Separator
163+
ref={ref}
164+
className={cn('-mx-1 my-1 h-px bg-border', className)}
165+
{...props}
166+
/>
167+
))
168+
ContextMenuSeparator.displayName =
169+
ContextMenuPrimitive.Separator.displayName
170+
171+
const ContextMenuShortcut = ({
172+
className,
173+
...props
174+
}: HTMLAttributes<HTMLSpanElement>) => {
175+
return (
176+
<span
177+
className={cn(
178+
'ml-auto text-xs tracking-widest text-muted-foreground',
179+
className
180+
)}
181+
{...props}
182+
/>
183+
)
184+
}
185+
ContextMenuShortcut.displayName = 'ContextMenuShortcut'
186+
187+
export {
188+
ContextMenu,
189+
ContextMenuTrigger,
190+
ContextMenuContent,
191+
ContextMenuItem,
192+
ContextMenuCheckboxItem,
193+
ContextMenuRadioItem,
194+
ContextMenuLabel,
195+
ContextMenuSeparator,
196+
ContextMenuShortcut,
197+
ContextMenuGroup,
198+
ContextMenuPortal,
199+
ContextMenuSub,
200+
ContextMenuSubContent,
201+
ContextMenuSubTrigger,
202+
ContextMenuRadioGroup,
203+
}

0 commit comments

Comments
 (0)