diff --git a/package-lock.json b/package-lock.json
index 8e3eb38..d74ad0d 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"@mantine/core": "^7.14.1",
"@mantine/hooks": "^7.14.1",
"@radix-ui/react-accordion": "^1.1.2",
+ "@radix-ui/react-checkbox": "^1.3.1",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
@@ -34,6 +35,7 @@
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-router": "^1.46.4",
+ "@tanstack/react-table": "^8.21.3",
"@uidotdev/usehooks": "^2.4.1",
"axios": "^1.7.7",
"axios-retry": "^4.5.0",
@@ -2774,6 +2776,204 @@
}
}
},
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.1.tgz",
+ "integrity": "sha512-xTaLKAO+XXMPK/BpVTSaAAhlefmvMSACjIhK9mGsImvX2ljcTDm8VGR1CuS1uYcNdR5J+oiOhoJZc5un6bh3VQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.2",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/primitive": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.2.tgz",
+ "integrity": "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-presence": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.4.tgz",
+ "integrity": "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.2.tgz",
+ "integrity": "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.2.tgz",
+ "integrity": "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-collapsible": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz",
@@ -5403,6 +5603,39 @@
}
}
},
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event/node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-use-escape-keydown": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz",
@@ -6005,6 +6238,26 @@
"react-dom": "^17.0.0 || ^18.0.0"
}
},
+ "node_modules/@tanstack/react-table": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.21.3.tgz",
+ "integrity": "sha512-5nNMTSETP4ykGegmVkhjcS8tTLW6Vl4axfEGQN3v0zdHYbK4UfoqfPChclTrJ4EoK9QynqAu9oUf8VEmrpZ5Ww==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/table-core": "8.21.3"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": ">=16.8",
+ "react-dom": ">=16.8"
+ }
+ },
"node_modules/@tanstack/router-generator": {
"version": "1.30.0",
"resolved": "https://registry.npmjs.org/@tanstack/router-generator/-/router-generator-1.30.0.tgz",
@@ -6062,6 +6315,19 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
+ "node_modules/@tanstack/table-core": {
+ "version": "8.21.3",
+ "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.3.tgz",
+ "integrity": "sha512-ldZXEhOBb8Is7xLs01fR3YEc3DERiz5silj8tnGkFZytt1abEvl/GhUmCE0PMLaMPTa3Jk4HbKmRlHmu+gCftg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
"node_modules/@tootallnate/once": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
diff --git a/package.json b/package.json
index a988e0b..34a4821 100644
--- a/package.json
+++ b/package.json
@@ -37,6 +37,7 @@
"@mantine/core": "^7.14.1",
"@mantine/hooks": "^7.14.1",
"@radix-ui/react-accordion": "^1.1.2",
+ "@radix-ui/react-checkbox": "^1.3.1",
"@radix-ui/react-context-menu": "^2.2.1",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
@@ -54,6 +55,7 @@
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.0.7",
"@tanstack/react-router": "^1.46.4",
+ "@tanstack/react-table": "^8.21.3",
"@uidotdev/usehooks": "^2.4.1",
"axios": "^1.7.7",
"axios-retry": "^4.5.0",
diff --git a/src/components/menu/sidebar.tsx b/src/components/menu/sidebar.tsx
index 5e45e6f..c9c1f21 100644
--- a/src/components/menu/sidebar.tsx
+++ b/src/components/menu/sidebar.tsx
@@ -270,6 +270,23 @@ export function SidebarMenu({
)}
+ {getMenuOptionVisibility('friendsManagement') && (
+
+
+ {t(
+ 'account-management.options.friends-management'
+ )}
+
+
+ )}
{getMenuOptionVisibility('redeemCodes') && (
,
+ ComponentPropsWithoutRef
+>(({ className, ...props }, ref) => (
+
+
+
+
+
+))
+Checkbox.displayName = CheckboxPrimitive.Root.displayName
+
+export { Checkbox }
diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx
new file mode 100644
index 0000000..0f88d6a
--- /dev/null
+++ b/src/components/ui/table.tsx
@@ -0,0 +1,130 @@
+import type {
+ HTMLAttributes,
+ TdHTMLAttributes,
+ ThHTMLAttributes,
+} from 'react'
+
+import { forwardRef } from 'react'
+
+import { cn } from '../../lib/utils'
+
+const Table = forwardRef<
+ HTMLTableElement,
+ HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+Table.displayName = 'Table'
+
+const TableHeader = forwardRef<
+ HTMLTableSectionElement,
+ HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableHeader.displayName = 'TableHeader'
+
+const TableBody = forwardRef<
+ HTMLTableSectionElement,
+ HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableBody.displayName = 'TableBody'
+
+const TableFooter = forwardRef<
+ HTMLTableSectionElement,
+ HTMLAttributes
+>(({ className, ...props }, ref) => (
+ tr]:last:border-b-0',
+ className
+ )}
+ {...props}
+ />
+))
+TableFooter.displayName = 'TableFooter'
+
+const TableRow = forwardRef<
+ HTMLTableRowElement,
+ HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableRow.displayName = 'TableRow'
+
+const TableHead = forwardRef<
+ HTMLTableCellElement,
+ ThHTMLAttributes
+>(({ className, ...props }, ref) => (
+ |
+))
+TableHead.displayName = 'TableHead'
+
+const TableCell = forwardRef<
+ HTMLTableCellElement,
+ TdHTMLAttributes
+>(({ className, ...props }, ref) => (
+ |
+))
+TableCell.displayName = 'TableCell'
+
+const TableCaption = forwardRef<
+ HTMLTableCaptionElement,
+ HTMLAttributes
+>(({ className, ...props }, ref) => (
+
+))
+TableCaption.displayName = 'TableCaption'
+
+export {
+ Table,
+ TableHeader,
+ TableBody,
+ TableFooter,
+ TableHead,
+ TableRow,
+ TableCell,
+ TableCaption,
+}
diff --git a/src/kernel/startup/auto-llamas.ts b/src/kernel/startup/auto-llamas.ts
index 4145c5a..53873bd 100644
--- a/src/kernel/startup/auto-llamas.ts
+++ b/src/kernel/startup/auto-llamas.ts
@@ -3,7 +3,11 @@ import type {
AutoLlamasData,
AutoLlamasRecord,
} from '../../types/auto-llamas'
-import type { MCPQueryProfileProfileChangesPrerollData } from '../../types/services/mcp'
+import type {
+ MCPQueryProfileChanges,
+ MCPQueryProfileProfileChangesPrerollData,
+} from '../../types/services/mcp'
+import type { StorefrontCatalogResponse } from '../../types/services/storefront'
import type { RewardsNotification } from '../../types/notifications'
import { Collection } from '@discordjs/collection'
@@ -292,53 +296,33 @@ export class ProcessAutoLlamas {
}
}
- // eslint-disable-next-line no-constant-condition
- while (true) {
- let success = true
+ const accountId = account.accountId
- try {
- const accessToken =
- await Authentication.verifyAccessToken(account)
+ if (type === ProcessLlamaType.Survivor) {
+ // eslint-disable-next-line no-constant-condition
+ while (true) {
+ try {
+ const accessToken =
+ await Authentication.verifyAccessToken(account)
- if (!accessToken) {
- break
- }
+ if (!accessToken) {
+ break
+ }
- try {
- await populatePrerolledOffers({
+ await ProcessAutoLlamas.populateOffers({
accessToken,
- accountId: account.accountId,
+ accountId,
})
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- } catch (error) {
- //
- }
-
- const queryProfile = await getQueryProfile({
- accessToken,
- accountId: account.accountId,
- })
- const profileChanges =
- queryProfile.data.profileChanges[0] ?? null
-
- const xRayTickets =
- Object.values(profileChanges?.profile.items).find(
- (item) =>
- (item.templateId as string) ===
- 'AccountResource:currency_xrayllama'
- )?.quantity ?? 0
- const llamaTokens =
- Object.values(profileChanges?.profile.items).find(
- (item) =>
- (item.templateId as string) ===
- 'AccountResource:voucher_cardpack_bronze'
- )?.quantity ?? 0
+ const { llamaTokens, profile, xRayTickets } =
+ await ProcessAutoLlamas.getProfileData({
+ accessToken,
+ accountId,
+ })
- let currencyTotal: number | null = null
- let currencySubType = 'AccountResource:currency_xrayllama'
+ let currencyTotal: number | null = null
+ let currencySubType = 'AccountResource:currency_xrayllama'
- if (type === ProcessLlamaType.Survivor) {
if (current.actions.survivors) {
if (current.actions['use-token']) {
if (llamaTokens > 0) {
@@ -354,215 +338,404 @@ export class ProcessAutoLlamas {
}
}
}
- } else if (type === ProcessLlamaType.FreeUpgrade) {
- if (current.actions['free-llamas']) {
- currencyTotal = 0
+
+ const currencyIsToken =
+ currencySubType ===
+ 'AccountResource:voucher_cardpack_bronze'
+
+ if (currencyTotal === null) {
+ break
}
- }
- const currencyIsToken =
- currencySubType ===
- 'AccountResource:voucher_cardpack_bronze'
+ const cardPacks =
+ await ProcessAutoLlamas.getCurrentCatalog({
+ accessToken,
+ })
- if (currencyTotal === null) {
- break
- }
+ if (!cardPacks) {
+ break
+ }
- const catalog = await getCatalog({
- accessToken,
- })
- const cardPacks = catalog.data.storefronts.find(
- (item) => item.name === 'CardPackStorePreroll'
- )
+ const llama = cardPacks.catalogEntries.find((item) => {
+ return currencyIsToken
+ ? item.devName === 'Always.UpgradePack.02'
+ : item.devName === 'Always.UpgradePack.01'
+ })
- if (!cardPacks) {
- break
- }
+ if (!llama) {
+ break
+ }
- const llama = cardPacks.catalogEntries.find((item) => {
- if (type === ProcessLlamaType.Survivor) {
- if (currencyIsToken) {
- return item.devName === 'Always.UpgradePack.02'
- }
+ const { maxPurchases } =
+ await ProcessAutoLlamas.checkDailyLimit({
+ accessToken,
+ accountId,
+ currencyIsToken,
+ llama,
+ })
- return (
- item.devName === 'Always.UpgradePack.01' &&
- item.dailyLimit === 50 &&
- item.prices[0]?.regularPrice === 50 &&
- item.prices[0]?.finalPrice === 50
- )
+ if (maxPurchases) {
+ break
}
- return (
- (item.devName?.toLowerCase().includes('free') ||
- item.title?.toLowerCase().includes('free')) &&
- item.prices[0]?.regularPrice === 50 &&
- item.prices[0]?.finalPrice === 0
- )
- })
+ const prerollData = Object.values(
+ profile.items ?? {}
+ ).find(
+ (item) =>
+ isMCPQueryProfileChangesPrerollData(item) &&
+ item.attributes.offerId === llama.offerId
+ ) as MCPQueryProfileProfileChangesPrerollData | undefined
- if (!llama) {
- break
- }
+ if (!prerollData) {
+ break
+ }
- const mainProfile = await getQueryProfileMainProfile({
- accessToken,
- accountId: account.accountId,
- })
- const totalPurchases =
- mainProfile.data.profileChanges[0]?.profile.stats
- .attributes.daily_purchases.purchaseList[
- llama.offerId
- ] ?? 0
- const dailyLimit =
- currencyIsToken && llama.dailyLimit <= -1
- ? 10
- : llama.dailyLimit ?? 2
-
- if (totalPurchases >= dailyLimit) {
- break
- }
+ const canPurchase = prerollData.attributes.items.some(
+ ({ itemType }) => {
+ const newKey = itemType
+ .replace(
+ /_((very)?low|medium|(very)?high|extreme)$/gi,
+ ''
+ )
+ .replace('AccountResource:', '')
+ .replace('CardPack:zcp_', '')
+ const survivor = getKey(newKey, survivorsJson)
+ const mythicSurvivor = getKey(
+ newKey,
+ survivorsMythicLeadsJson
+ )
+ const isWorker = newKey.startsWith('Worker:')
+ const rarity = parseRarity(newKey)
+
+ return (
+ survivor ||
+ mythicSurvivor ||
+ (isWorker &&
+ [RarityType.Legendary, RarityType.Mythic].includes(
+ rarity.rarity
+ ))
+ )
+ }
+ )
- const prerollData = Object.values(
- profileChanges.profile.items ?? {}
- ).find(
- (item) =>
- isMCPQueryProfileChangesPrerollData(item) &&
- item.attributes.offerId === llama.offerId
- ) as MCPQueryProfileProfileChangesPrerollData | undefined
+ if (!canPurchase) {
+ break
+ }
- if (!prerollData) {
+ await ProcessAutoLlamas.buyAndNotify({
+ accessToken,
+ currencySubType,
+ profile,
+ accountId: account.accountId,
+ expectedTotalPrice: currencyTotal,
+ offerId: llama.offerId,
+ })
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (error) {
break
}
+ }
- const canPurchase =
- type === ProcessLlamaType.FreeUpgrade
- ? true
- : prerollData.attributes.items.some(({ itemType }) => {
- const newKey = itemType
- .replace(
- /_((very)?low|medium|(very)?high|extreme)$/gi,
- ''
- )
- .replace('AccountResource:', '')
- .replace('CardPack:zcp_', '')
- const survivor = getKey(newKey, survivorsJson)
- const mythicSurvivor = getKey(
- newKey,
- survivorsMythicLeadsJson
- )
- const isWorker = newKey.startsWith('Worker:')
- const rarity = parseRarity(newKey)
-
- return (
- survivor ||
- mythicSurvivor ||
- (isWorker &&
- [
- RarityType.Legendary,
- RarityType.Mythic,
- ].includes(rarity.rarity))
- )
- })
+ return
+ }
- if (!canPurchase) {
- break
- }
+ try {
+ const accessToken =
+ await Authentication.verifyAccessToken(account)
- const response = await purchaseCatalogEntry({
- accessToken,
- currencySubType,
- accountId: account.accountId,
- offerId: llama.offerId,
- expectedTotalPrice: currencyTotal,
- })
- const responseNotifications =
- response.data.notifications ?? []
-
- const notifications: Array<{
- itemType: string
- quantity: number
- }> = []
-
- responseNotifications?.forEach((notification) => {
- if (notification.loot) {
- if (notification.loot.items) {
- notification.loot.items.forEach((loot) => {
- notifications.push({
- itemType: loot.itemType,
- quantity: loot.quantity,
- })
- })
- } else if (notification.loot.lootGranted) {
- notification.loot.lootGranted.items.forEach((loot) => {
- notifications.push({
- itemType: loot.itemType,
- quantity: loot.quantity,
- })
- })
- }
- } else if (notification.lootGranted) {
- notification.lootGranted?.items.forEach((loot) => {
- notifications.push({
- itemType: loot.itemType,
- quantity: loot.quantity,
- })
- })
- } else if (notification.lootResult) {
- notification.lootResult?.items.forEach((loot) => {
- notifications.push({
- itemType: loot.itemType,
- quantity: loot.quantity,
- })
- })
- }
- })
+ if (!accessToken) {
+ return
+ }
- const rewards: RewardsNotification['rewards'] = {}
+ await ProcessAutoLlamas.populateOffers({
+ accessToken,
+ accountId,
+ })
- notifications.forEach(({ itemType, quantity }) => {
- if (!itemType.toLowerCase().startsWith('accolades:')) {
- const newItemType =
- itemType === 'AccountResource:campaign_event_currency'
- ? profileChanges.profile.stats.attributes
- .event_currency?.templateId ?? itemType
- : itemType
+ const { profile } = await ProcessAutoLlamas.getProfileData({
+ accessToken,
+ accountId,
+ })
- if (!rewards[newItemType]) {
- rewards[newItemType] = 0
- }
+ const cardPacks = await ProcessAutoLlamas.getCurrentCatalog({
+ accessToken,
+ })
- rewards[newItemType] += quantity
- }
- })
+ if (!cardPacks) {
+ return
+ }
- const result: RewardsNotification = {
- accolades: {
- totalMissionXPRedeemed: 0,
- totalQuestXPRedeemed: 0,
- },
- rewards,
- createdAt: getDateWithDefaultFormat(),
- id: crypto.randomUUID(),
- accountId: account.accountId,
- }
+ const llamas = cardPacks.catalogEntries.filter((item) => {
+ return item.prices[0]?.finalPrice === 0
+ })
- MainWindow.instance.webContents.send(
- ElectronAPIEventKeys.ClaimRewardsClientGlobalSyncNotification,
- [result]
- )
+ if (llamas.length <= 0) {
+ return
+ }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- } catch (error) {
- success = false
+ const availableLlamas: StorefrontCatalogResponse['storefronts'][number]['catalogEntries'] =
+ []
+
+ for (const llama of llamas) {
+ // const { dailyLimit, maxPurchases, totalPurchases } =
+ // await ProcessAutoLlamas.checkDailyLimit({
+ // accessToken,
+ // accountId,
+ // llama,
+ // currencyIsToken: false,
+ // })
+
+ // if (maxPurchases) {
+ // continue
+ // }
+
+ // Array.from({ length: dailyLimit - totalPurchases }).forEach(
+ Array.from({
+ length: llama.dailyLimit <= -1 ? 30 : llama.dailyLimit,
+ }).forEach(() => {
+ availableLlamas.push(llama)
+ })
}
- if (!success) {
- break
+ if (availableLlamas.length <= 0) {
+ return
}
+
+ availableLlamas.forEach((llama) => {
+ ProcessAutoLlamas.buyAndNotify({
+ accessToken,
+ profile,
+ accountId: account.accountId,
+ currencySubType: 'AccountResource:currency_xrayllama',
+ expectedTotalPrice: 0,
+ offerId: llama.offerId,
+ }).catch(() => {})
+ })
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (error) {
+ //
}
+
+ return
}
)
})
}
+
+ static async populateOffers({
+ accessToken,
+ accountId,
+ }: {
+ accessToken: string
+ accountId: string
+ }) {
+ try {
+ await populatePrerolledOffers({
+ accessToken,
+ accountId,
+ })
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (error) {
+ //
+ }
+ }
+
+ static async getProfileData({
+ accessToken,
+ accountId,
+ }: {
+ accessToken: string
+ accountId: string
+ }) {
+ const queryProfile = await getQueryProfile({
+ accessToken,
+ accountId,
+ })
+ const profileChanges = queryProfile.data.profileChanges[0] ?? null
+
+ const xRayTickets =
+ Object.values(profileChanges?.profile.items).find(
+ (item) =>
+ (item.templateId as string) ===
+ 'AccountResource:currency_xrayllama'
+ )?.quantity ?? 0
+ const llamaTokens =
+ Object.values(profileChanges?.profile.items).find(
+ (item) =>
+ (item.templateId as string) ===
+ 'AccountResource:voucher_cardpack_bronze'
+ )?.quantity ?? 0
+
+ return {
+ llamaTokens,
+ xRayTickets,
+ profile: queryProfile.data.profileChanges[0]?.profile,
+ }
+ }
+
+ static async getCurrentCatalog({
+ accessToken,
+ }: {
+ accessToken: string
+ }) {
+ const catalog = await getCatalog({
+ accessToken,
+ })
+ const cardPacks = catalog.data.storefronts.find(
+ (item) => item.name === 'CardPackStorePreroll'
+ )
+
+ return cardPacks
+ }
+
+ static async checkDailyLimit({
+ accessToken,
+ accountId,
+ currencyIsToken,
+ llama,
+ }: {
+ accessToken: string
+ accountId: string
+ currencyIsToken: boolean
+ llama: StorefrontCatalogResponse['storefronts'][number]['catalogEntries'][number]
+ }) {
+ try {
+ const mainProfile = await getQueryProfileMainProfile({
+ accessToken,
+ accountId,
+ })
+ const getDenyRequirement = llama.requirements?.find(
+ (item) => item.requirementType === 'DenyOnFulfillment'
+ )
+ const totalPurchases =
+ mainProfile.data.profileChanges[0]?.profile.stats.attributes
+ .daily_purchases?.purchaseList?.[llama.offerId] ??
+ mainProfile.data.profileChanges[0]?.profile.stats.attributes
+ .in_app_purchases?.fulfillmentCounts?.[
+ getDenyRequirement?.requiredId ?? -1
+ ] ??
+ 0
+ const dailyLimit =
+ currencyIsToken && llama.dailyLimit <= -1
+ ? 30
+ : llama.dailyLimit ?? 30
+
+ return {
+ maxPurchases: totalPurchases >= dailyLimit,
+ totalPurchases,
+ dailyLimit,
+ }
+
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (error) {
+ //
+ }
+
+ return {
+ maxPurchases: false,
+ totalPurchases: 0,
+ dailyLimit: 0,
+ }
+ }
+
+ static async buyAndNotify({
+ accessToken,
+ accountId,
+ currencySubType,
+ expectedTotalPrice,
+ offerId,
+ profile,
+ }: {
+ accessToken: string
+ accountId: string
+ currencySubType?: string
+ offerId: string
+ expectedTotalPrice: number
+ profile: MCPQueryProfileChanges['profile']
+ }) {
+ const response = await purchaseCatalogEntry({
+ accessToken,
+ accountId,
+ currencySubType,
+ offerId,
+ expectedTotalPrice,
+ })
+ const responseNotifications = response.data.notifications ?? []
+
+ const notifications: Array<{
+ itemType: string
+ quantity: number
+ }> = []
+
+ responseNotifications?.forEach((notification) => {
+ if (notification.loot) {
+ if (notification.loot.items) {
+ notification.loot.items.forEach((loot) => {
+ notifications.push({
+ itemType: loot.itemType,
+ quantity: loot.quantity,
+ })
+ })
+ } else if (notification.loot.lootGranted) {
+ notification.loot.lootGranted.items.forEach((loot) => {
+ notifications.push({
+ itemType: loot.itemType,
+ quantity: loot.quantity,
+ })
+ })
+ }
+ } else if (notification.lootGranted) {
+ notification.lootGranted?.items.forEach((loot) => {
+ notifications.push({
+ itemType: loot.itemType,
+ quantity: loot.quantity,
+ })
+ })
+ } else if (notification.lootResult) {
+ notification.lootResult?.items.forEach((loot) => {
+ notifications.push({
+ itemType: loot.itemType,
+ quantity: loot.quantity,
+ })
+ })
+ }
+ })
+
+ const rewards: RewardsNotification['rewards'] = {}
+
+ notifications.forEach(({ itemType, quantity }) => {
+ if (!itemType.toLowerCase().startsWith('accolades:')) {
+ const newItemType =
+ itemType === 'AccountResource:campaign_event_currency'
+ ? profile.stats.attributes.event_currency?.templateId ??
+ itemType
+ : itemType
+
+ if (!rewards[newItemType]) {
+ rewards[newItemType] = 0
+ }
+
+ rewards[newItemType] += quantity
+ }
+ })
+
+ const result: RewardsNotification = {
+ accountId,
+ rewards,
+ accolades: {
+ totalMissionXPRedeemed: 0,
+ totalQuestXPRedeemed: 0,
+ },
+ createdAt: getDateWithDefaultFormat(),
+ id: crypto.randomUUID(),
+ }
+
+ MainWindow.instance.webContents.send(
+ ElectronAPIEventKeys.ClaimRewardsClientGlobalSyncNotification,
+ [result]
+ )
+ }
}
diff --git a/src/lib/validations/schemas/settings.ts b/src/lib/validations/schemas/settings.ts
index 6132e32..8ac4b26 100644
--- a/src/lib/validations/schemas/settings.ts
+++ b/src/lib/validations/schemas/settings.ts
@@ -60,6 +60,7 @@ export const customizableMenuSettingsSchema = z
accountManagement: z.boolean().default(true),
vbucksInformation: z.boolean().default(true),
+ friendsManagement: z.boolean().default(true),
redeemCodes: z.boolean().default(true),
devicesAuth: z.boolean().default(true),
epicGamesSettings: z.boolean().default(true),
diff --git a/src/lib/validations/schemas/storefront.ts b/src/lib/validations/schemas/storefront.ts
index da6d400..d321a36 100644
--- a/src/lib/validations/schemas/storefront.ts
+++ b/src/lib/validations/schemas/storefront.ts
@@ -48,7 +48,18 @@ export const storefrontCatalogSchema = z.object({
monthlyLimit: z.number(),
refundable: z.boolean(),
// appStoreId: z.array(z.string()),
- // requirements: z.array(z.unknown()),
+ requirements: z
+ .array(
+ z.object({
+ requirementType: z.enum([
+ 'DenyOnFulfillment',
+ 'RequireFulfillment',
+ ]),
+ requiredId: z.string(),
+ minQuantity: z.number(),
+ })
+ )
+ .optional(),
meta: z
.object({
PurchaseLimitingEventId: z.string(),
diff --git a/src/locales/en-US/account-management/friends-management.json b/src/locales/en-US/account-management/friends-management.json
new file mode 100644
index 0000000..addc766
--- /dev/null
+++ b/src/locales/en-US/account-management/friends-management.json
@@ -0,0 +1,3 @@
+{
+ "coming-soon": "Coming Soon!"
+}
diff --git a/src/locales/en-US/sidebar.json b/src/locales/en-US/sidebar.json
index 8e2e425..91754bd 100644
--- a/src/locales/en-US/sidebar.json
+++ b/src/locales/en-US/sidebar.json
@@ -20,6 +20,7 @@
"title": "Account Management",
"options": {
"vbucks-information": "V-Bucks Information",
+ "friends-management": "Friends Management",
"redeem-codes": "Redeem Codes",
"devices-auth": "Devices Auth",
"epic-settings": "Epic Games Settings"
diff --git a/src/locales/resources.ts b/src/locales/resources.ts
index 250e2fa..92a8743 100644
--- a/src/locales/resources.ts
+++ b/src/locales/resources.ts
@@ -18,6 +18,7 @@ import enUS_stwOperations_XPBoosts from './en-US/stw-operations/xpboosts.json'
import enUS_stwOperations_Llamas from './en-US/stw-operations/llamas.json'
import enUS_stwOperations_Unlock from './en-US/stw-operations/unlock.json'
import enUS_accountManagement_VBucksInformation from './en-US/account-management/vbucks-information.json'
+import enUS_accountManagement_FriendsManagement from './en-US/account-management/friends-management.json'
import enUS_accountManagement_EULA from './en-US/account-management/eula.json'
import enUS_accountManagement_RedeemCodes from './en-US/account-management/redeem-codes.json'
import enUS_accountManagement_DevicesAuth from './en-US/account-management/devices-auth.json'
@@ -49,6 +50,7 @@ const enUS = {
},
'account-management': {
'vbucks-information': enUS_accountManagement_VBucksInformation,
+ 'friends-management': enUS_accountManagement_FriendsManagement,
'redeem-codes': enUS_accountManagement_RedeemCodes,
'devices-auth': enUS_accountManagement_DevicesAuth,
'epic-settings': enUS_accountManagement_EpicSettings,
diff --git a/src/routeTree.gen.ts b/src/routeTree.gen.ts
index aad52b3..9bd49fd 100644
--- a/src/routeTree.gen.ts
+++ b/src/routeTree.gen.ts
@@ -27,6 +27,7 @@ import { Route as AdvancedModeMatchmakingTrackRouteImport } from './routes/advan
import { Route as AccountsRemoveRouteImport } from './routes/accounts/remove/route'
import { Route as AccountManagementVbucksInformationRouteImport } from './routes/account-management/vbucks-information/route'
import { Route as AccountManagementRedeemCodesRouteImport } from './routes/account-management/redeem-codes/route'
+import { Route as AccountManagementFriendsManagementRouteImport } from './routes/account-management/friends-management/route'
import { Route as AccountManagementEulaRouteImport } from './routes/account-management/eula/route'
import { Route as AccountManagementEpicGamesSettingsRouteImport } from './routes/account-management/epic-games-settings/route'
import { Route as AccountManagementDevicesAuthRouteImport } from './routes/account-management/devices-auth/route'
@@ -125,6 +126,12 @@ const AccountManagementRedeemCodesRouteRoute =
getParentRoute: () => rootRoute,
} as any)
+const AccountManagementFriendsManagementRouteRoute =
+ AccountManagementFriendsManagementRouteImport.update({
+ path: '/account-management/friends-management',
+ getParentRoute: () => rootRoute,
+ } as any)
+
const AccountManagementEulaRouteRoute = AccountManagementEulaRouteImport.update(
{
path: '/account-management/eula',
@@ -173,6 +180,10 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof AccountManagementEulaRouteImport
parentRoute: typeof rootRoute
}
+ '/account-management/friends-management': {
+ preLoaderRoute: typeof AccountManagementFriendsManagementRouteImport
+ parentRoute: typeof rootRoute
+ }
'/account-management/redeem-codes': {
preLoaderRoute: typeof AccountManagementRedeemCodesRouteImport
parentRoute: typeof rootRoute
@@ -244,6 +255,7 @@ export const routeTree = rootRoute.addChildren([
AccountManagementDevicesAuthRouteRoute,
AccountManagementEpicGamesSettingsRouteRoute,
AccountManagementEulaRouteRoute,
+ AccountManagementFriendsManagementRouteRoute,
AccountManagementRedeemCodesRouteRoute,
AccountManagementVbucksInformationRouteRoute,
AccountsRemoveRouteRoute,
diff --git a/src/routes/account-management/friends-management/route.tsx b/src/routes/account-management/friends-management/route.tsx
new file mode 100644
index 0000000..4aa4ff3
--- /dev/null
+++ b/src/routes/account-management/friends-management/route.tsx
@@ -0,0 +1,565 @@
+import { createRoute } from '@tanstack/react-router'
+import {
+ ColumnDef,
+ // ColumnFiltersState,
+ // Row,
+ // SortingState,
+ // VisibilityState,
+ flexRender,
+ getCoreRowModel,
+ getFacetedRowModel,
+ getFacetedUniqueValues,
+ getFilteredRowModel,
+ getPaginationRowModel,
+ getSortedRowModel,
+ useReactTable,
+} from '@tanstack/react-table'
+import {
+ BanIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ ChevronsLeftIcon,
+ ChevronsRightIcon,
+ Trash2Icon,
+} from 'lucide-react'
+import { useState } from 'react'
+import { useTranslation } from 'react-i18next'
+import { z } from 'zod'
+
+import { Route as RootRoute } from '../../__root'
+
+import { HomeBreadcrumb } from '../../../components/navigations/breadcrumb/home'
+import {
+ Breadcrumb,
+ BreadcrumbItem,
+ BreadcrumbList,
+ BreadcrumbPage,
+ BreadcrumbSeparator,
+} from '../../../components/ui/breadcrumb'
+import { Button } from '../../../components/ui/button'
+import { Checkbox } from '../../../components/ui/checkbox'
+import { Combobox } from '../../../components/ui/extended/combobox'
+import { Input } from '../../../components/ui/input'
+// import { Label } from '../../../components/ui/label'
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from '../../../components/ui/select'
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from '../../../components/ui/table'
+import {
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+} from '../../../components/ui/tabs'
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from '../../../components/ui/tooltip'
+
+import { useCustomizableMenuSettingsVisibility } from '../../../hooks/settings'
+
+import { numberWithCommaSeparator } from '../../../lib/parsers/numbers'
+import { cn } from '../../../lib/utils'
+
+export const Route = createRoute({
+ getParentRoute: () => RootRoute,
+ path: '/account-management/friends-management',
+ component: () => {
+ const { t } = useTranslation(['sidebar'], {
+ keyPrefix: 'account-management',
+ })
+
+ return (
+ <>
+
+
+
+
+
+ {t('title')}
+
+
+
+
+ {t('options.friends-management')}
+
+
+
+
+
+ >
+ )
+ },
+})
+
+function Content() {
+ const { t } = useTranslation(['account-management', 'general'])
+
+ const [data] = useState>>(() => [
+ {
+ accountId: 'Andhord',
+ alias: 'Anjor',
+ created: '2020-01-01T02:00:00.000Z',
+ favorite: false,
+ groups: [],
+ note: '',
+ },
+ {
+ accountId: 'Kuva_.',
+ alias: 'Copia de Kuda',
+ created: '2020-01-01T02:00:00.000Z',
+ favorite: false,
+ groups: [],
+ note: '',
+ },
+ {
+ accountId: 'Yo Fist',
+ alias: '',
+ created: '2020-01-01T02:00:00.000Z',
+ favorite: false,
+ groups: [],
+ note: '',
+ },
+ ])
+ const [rowSelection, setRowSelection] = useState({})
+ const [pagination, setPagination] = useState({
+ pageIndex: 0,
+ pageSize: 10,
+ })
+
+ const table = useReactTable({
+ data,
+ columns,
+ state: {
+ // sorting,
+ // columnVisibility,
+ rowSelection,
+ // columnFilters,
+ pagination,
+ },
+ getRowId: (row) => row.accountId,
+ enableRowSelection: true,
+ onRowSelectionChange: setRowSelection,
+ // onSortingChange: setSorting,
+ // onColumnFiltersChange: setColumnFilters,
+ // onColumnVisibilityChange: setColumnVisibility,
+ onPaginationChange: setPagination,
+ getCoreRowModel: getCoreRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFacetedRowModel: getFacetedRowModel(),
+ getFacetedUniqueValues: getFacetedUniqueValues(),
+ })
+
+ const { getMenuOptionVisibility } =
+ useCustomizableMenuSettingsVisibility()
+
+ return (
+
+
+
+
+ {}}
+ // onSelectItem={onSelectItem}
+ emptyContentClassname="py-6 text-center text-sm"
+ // disabled={accountSelectorIsDisabled}
+ // disabledItem={accountSelectorIsDisabled}
+ // inputSearchIsDisabled={accountSelectorIsDisabled}
+ hideInputSearchWhenOnlyOneOptionIsAvailable
+ hideSelectorOnSelectItem
+ />
+
+
+
+
+
+
+
+
+ Friends
+ Incoming
+ Outgoing
+ Blocklist
+
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+ Content
+
+
+
+
+
+
+ Total friends
+
+ {numberWithCommaSeparator(1234)}
+
+
+
+
+
+
+
+
+
+
+
+ {table.getHeaderGroups().map((headerGroup) => (
+ th:first-child]:w-10',
+ '[&>th:nth-child(2)]:w-44'
+ )}
+ key={headerGroup.id}
+ >
+ {headerGroup.headers.map((header) => {
+ return (
+
+ {header.isPlaceholder
+ ? null
+ : flexRender(
+ header.column.columnDef.header,
+ header.getContext()
+ )}
+
+ )
+ })}
+
+ ))}
+
+
+ {table.getRowModel().rows.map((row, index) => (
+
+ {row.getVisibleCells().map((cell) => (
+
+ {flexRender(
+ cell.column.columnDef.cell,
+ cell.getContext()
+ )}
+
+ ))}
+
+ ))}
+
+
+
+
+
+ {/*
+ {table.getFilteredSelectedRowModel().rows.length} of{' '}
+ {table.getFilteredRowModel().rows.length} row(s) selected.
+
*/}
+
+
+ {/* */}
+
+
+
+ Page {table.getState().pagination.pageIndex + 1} of{' '}
+ {table.getPageCount()}
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+export const schema = z.object({
+ accountId: z.string(),
+ groups: z.array(z.any()),
+ alias: z.string(),
+ note: z.string(),
+ favorite: z.boolean(),
+ created: z.string(),
+})
+
+const columns: Array>> = [
+ {
+ id: 'select',
+ header: ({ table }) => (
+
+
+ table.toggleAllPageRowsSelected(!!value)
+ }
+ aria-label="select all"
+ />
+
+ ),
+ cell: ({ row }) => (
+
+ row.toggleSelected(!!value)}
+ aria-label="select row"
+ />
+
+ ),
+ },
+ {
+ accessorKey: 'header',
+ header: () => {
+ return Name
+ },
+ cell: ({ row }) => {
+ return (
+
+
+
+ )
+ },
+ },
+ {
+ id: 'actions',
+ header: 'Actions',
+ cell: () => (
+
+ {/*
*/}
+
+
+
+
+
+
+
+
+
+ Block
+
+
+
+
+
+
+
+
+
+
+ Remove
+
+
+
+
+ ),
+ },
+]
diff --git a/src/state/settings/customizable-menu.ts b/src/state/settings/customizable-menu.ts
index 36162b5..4823a57 100644
--- a/src/state/settings/customizable-menu.ts
+++ b/src/state/settings/customizable-menu.ts
@@ -36,6 +36,7 @@ export const customizableMenuSettingsRelations: Record<
],
accountManagement: [
'vbucksInformation',
+ 'friendsManagement',
'redeemCodes',
'devicesAuth',
'epicGamesSettings',
diff --git a/src/types/services/mcp/query-profile-main.d.ts b/src/types/services/mcp/query-profile-main.d.ts
index a946d6c..30e5f52 100644
--- a/src/types/services/mcp/query-profile-main.d.ts
+++ b/src/types/services/mcp/query-profile-main.d.ts
@@ -23,14 +23,14 @@ export type MCPQueryProfileMainProfile = {
lastInterval: string
purchaseList: Record
}
- daily_purchases: {
+ daily_purchases?: {
lastInterval: string
- purchaseList: Record
+ purchaseList?: Record
}
- in_app_purchases: {
+ in_app_purchases?: {
receipts: Array
ignoredReceipts: Array
- fulfillmentCounts: Record
+ fulfillmentCounts?: Record
refreshTimers: {
EpicPurchasingService: {
nextEntitlementRefresh: string