diff --git a/apps/web/.env.example b/apps/web/.env.example index 93baa7328b..2b348dbc32 100644 --- a/apps/web/.env.example +++ b/apps/web/.env.example @@ -21,3 +21,9 @@ NODE_OPTIONS=--inspect NEXT_PUBLIC_TRON_PRO_API_KEY='' BITQUERY_API_KEY='' BITQUERY_BEARER_TOKEN='' + +AUTH_SESSION_SECRET="" +ZITADEL_ISSUER="" +ZITADEL_CLIENT_ID="" +ZITADEL_CLIENT_SECRET="" +ZITADEL_SA_TOKEN="" \ No newline at end of file diff --git a/apps/web/next.config.mjs b/apps/web/next.config.mjs index 5933fb4b18..4ce5cf958c 100644 --- a/apps/web/next.config.mjs +++ b/apps/web/next.config.mjs @@ -8,6 +8,11 @@ const bundleAnalyzer = withBundleAnalyzer({ /** @type {import('next').NextConfig} */ const nextConfig = bundleAnalyzer({ ...defaultNextConfig, + logging: { + fetches: { + fullUrl: true, + }, + }, experimental: { ...defaultNextConfig.experimental, testProxy: process.env.NEXT_PUBLIC_APP_ENV === 'test', diff --git a/apps/web/package.json b/apps/web/package.json index e3f0789895..86ad9784a4 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -60,6 +60,8 @@ "@vercel/speed-insights": "1.0.12", "@wagmi/connectors": "5.7.3", "@wagmi/core": "catalog:web3", + "@zitadel/client": "1.0.2", + "@zitadel/proto": "1.0.2", "cors": "2.8.5", "d3": "7.8.4", "date-fns": "2.30.0", @@ -67,6 +69,7 @@ "echarts-for-react": "3.0.2", "fewcha-plugin-wallet-adapter": "0.1.3", "framer-motion": "11.15.0", + "iron-session": "8.0.4", "lodash.frompairs": "4.0.1", "lodash.maxby": "4.6.0", "lodash.once": "4.1.1", diff --git a/apps/web/src/app/(networks)/(non-evm)/aptos/pool/ui/tables/pools/columns.tsx b/apps/web/src/app/(networks)/(non-evm)/aptos/pool/ui/tables/pools/columns.tsx index 602d6518b1..c66e041041 100644 --- a/apps/web/src/app/(networks)/(non-evm)/aptos/pool/ui/tables/pools/columns.tsx +++ b/apps/web/src/app/(networks)/(non-evm)/aptos/pool/ui/tables/pools/columns.tsx @@ -9,17 +9,19 @@ export const NAME_COLUMN: ColumnDef = { header: 'Name', cell: (props) => , meta: { - skeleton: ( -
-
- - + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- -
-
- ), + ), + }, }, } @@ -34,7 +36,9 @@ export const TVL_COLUMN: ColumnDef = { ? '$0.00' : formatUSD(props.row.original.liquidityUSD), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -49,7 +53,9 @@ export const VOLUME_1D_COLUMN: ColumnDef = { ? '$0.00' : formatUSD(props.row.original.volumeUSD1d), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -64,7 +70,9 @@ export const FEES_1D_COLUMN: ColumnDef = { ? '$0.00' : formatUSD(props.row.original.feeUSD1d), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -76,7 +84,9 @@ export const TRANSACTIONS_1D_COLUMN: ColumnDef = { rowA.txCount1d - rowB.txCount1d, cell: (props) => props.row.original.txCount1d, meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -86,6 +96,8 @@ export const APR_COLUMN: ColumnDef = { accessorFn: (row) => row.totalApr1d, cell: (props) => formatPercent(props.row.original.totalApr1d), meta: { - skeleton: , + body: { + skeleton: , + }, }, } diff --git a/apps/web/src/app/(networks)/(non-evm)/aptos/pool/ui/tables/positions/columns.tsx b/apps/web/src/app/(networks)/(non-evm)/aptos/pool/ui/tables/positions/columns.tsx index d968ef1af2..93ea250fb5 100644 --- a/apps/web/src/app/(networks)/(non-evm)/aptos/pool/ui/tables/positions/columns.tsx +++ b/apps/web/src/app/(networks)/(non-evm)/aptos/pool/ui/tables/positions/columns.tsx @@ -10,17 +10,19 @@ export const NAME_COLUMN: ColumnDef = { header: 'Name', cell: (props) => , meta: { - skeleton: ( -
-
- - + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- -
-
- ), + ), + }, }, } @@ -32,13 +34,15 @@ export const TVL_COLUMN: ColumnDef = { ), meta: { - skeleton: ( -
-
- + body: { + skeleton: ( +
+
+ +
-
- ), + ), + }, }, } @@ -52,13 +56,15 @@ export const MYPOSITION_TVL_COLUMN: ColumnDef< ), meta: { - skeleton: ( -
-
- + body: { + skeleton: ( +
+
+ +
-
- ), + ), + }, }, } @@ -70,12 +76,14 @@ export const MYPOSITION_APR_COLUMN: ColumnDef< header: 'APR', cell: (props) => , meta: { - skeleton: ( -
-
- + body: { + skeleton: ( +
+
+ +
-
- ), + ), + }, }, } diff --git a/apps/web/src/app/(networks)/(non-evm)/tron/_common/ui/Pools/PoolsTable/PoolColumns.tsx b/apps/web/src/app/(networks)/(non-evm)/tron/_common/ui/Pools/PoolsTable/PoolColumns.tsx index 1914468757..c25699eceb 100644 --- a/apps/web/src/app/(networks)/(non-evm)/tron/_common/ui/Pools/PoolsTable/PoolColumns.tsx +++ b/apps/web/src/app/(networks)/(non-evm)/tron/_common/ui/Pools/PoolsTable/PoolColumns.tsx @@ -9,17 +9,19 @@ export const NAME_COLUMN: ColumnDef = { header: 'Name', cell: (props) => , meta: { - skeleton: ( -
-
- - + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- -
-
- ), + ), + }, }, } @@ -34,7 +36,9 @@ export const TVL_COLUMN: ColumnDef = { ? '$0.00' : formatUSD(props.row.original.liquidityUSD), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -49,7 +53,9 @@ export const VOLUME_1D_COLUMN: ColumnDef = { ? '$0.00' : formatUSD(props.row.original.volumeUSD1d), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -64,7 +70,9 @@ export const FEES_1D_COLUMN: ColumnDef = { ? '$0.00' : formatUSD(props.row.original.feeUSD1d), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -76,7 +84,9 @@ export const TRANSACTIONS_1D_COLUMN: ColumnDef = { rowA.txCount1d - rowB.txCount1d, cell: (props) => props.row.original.txCount1d, meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -86,6 +96,8 @@ export const APR_COLUMN: ColumnDef = { accessorFn: (row) => row.totalApr1d, cell: (props) => formatPercent(props.row.original.totalApr1d), meta: { - skeleton: , + body: { + skeleton: , + }, }, } diff --git a/apps/web/src/app/(networks)/(non-evm)/tron/_common/ui/Pools/PositionsTable/PositionColumns.tsx b/apps/web/src/app/(networks)/(non-evm)/tron/_common/ui/Pools/PositionsTable/PositionColumns.tsx index 4a2238bbf5..350595fde8 100644 --- a/apps/web/src/app/(networks)/(non-evm)/tron/_common/ui/Pools/PositionsTable/PositionColumns.tsx +++ b/apps/web/src/app/(networks)/(non-evm)/tron/_common/ui/Pools/PositionsTable/PositionColumns.tsx @@ -12,17 +12,19 @@ export const POSITION_NAME_COLUMN: ColumnDef = { header: 'Name', cell: (props) => , meta: { - skeleton: ( -
-
- - + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- -
-
- ), + ), + }, }, } @@ -31,13 +33,15 @@ export const VALUE_COLUMN: ColumnDef = { header: 'Value', cell: (props) => , meta: { - skeleton: ( -
-
- + body: { + skeleton: ( +
+
+ +
-
- ), + ), + }, }, } @@ -46,13 +50,15 @@ export const SIZE_COLUMN: ColumnDef = { header: 'Size', cell: (props) => , meta: { - skeleton: ( -
-
- + body: { + skeleton: ( +
+
+ +
-
- ), + ), + }, }, } export const APR_COLUMN: ColumnDef = { @@ -60,12 +66,14 @@ export const APR_COLUMN: ColumnDef = { header: 'APR', cell: (props) => , meta: { - skeleton: ( -
-
- + body: { + skeleton: ( +
+
+ +
-
- ), + ), + }, }, } diff --git a/apps/web/src/app/portal/(authenticated)/api/auth/callback-connect/route.ts b/apps/web/src/app/portal/(authenticated)/api/auth/callback-connect/route.ts new file mode 100644 index 0000000000..f29ace0bc5 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/api/auth/callback-connect/route.ts @@ -0,0 +1,77 @@ +import { redirect } from 'next/navigation' +import type { NextRequest } from 'next/server' +import { getSessionData } from 'src/app/portal/_common/lib/client-config' +import { getIdpIntent } from 'src/app/portal/_common/lib/get-idp-intent' +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' +import { z } from 'zod' + +const schema = z.object({ + id: z.string(), + token: z.string(), + user: z.string().nullable(), + redirect: z.string(), +}) + +async function GET(req: NextRequest) { + const url = new URL(req.url) + + const result = schema.safeParse({ + id: url.searchParams.get('id'), + token: url.searchParams.get('token'), + user: url.searchParams.get('user'), + redirect: url.searchParams.get('redirect'), + }) + + if (!result.success) { + return new Response(JSON.stringify(result.error, null, 2), { status: 400 }) + } + + let intent: Awaited> + try { + intent = await getIdpIntent(result.data.id, result.data.token) + } catch (e) { + console.error(e) + return new Response('Failed to get IDP intent', { status: 500 }) + } + + const session = await getSessionData() + + if (!session.isLoggedIn) { + return new Response('Not logged in', { status: 412 }) + } + + if (result.data.user !== null) { + return new Response('User already exists', { status: 412 }) + } + + const userServiceClient = getUserServiceClient() + + const user = await userServiceClient.getUserByID({ + $typeName: 'zitadel.user.v2.GetUserByIDRequest', + userId: session.user.id, + }) + + if (user.user?.type.case !== 'human') { + return new Response('Not a human user', { status: 412 }) + } + + try { + userServiceClient.addIDPLink({ + $typeName: 'zitadel.user.v2.AddIDPLinkRequest', + userId: session.user.id, + idpLink: { + $typeName: 'zitadel.user.v2.IDPLink', + idpId: intent.idpInformation.idpId, + userId: intent.idpInformation.userId, + userName: user.user.username, + }, + }) + } catch (e) { + console.error(e) + return new Response('Failed to add IDP link', { status: 500 }) + } + + return redirect(result.data.redirect) +} + +export { GET } diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/(dashboard)/page.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/(dashboard)/page.tsx new file mode 100644 index 0000000000..1a4078939e --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/(dashboard)/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
profile
+} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/_common/lib/team.ts b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/_common/lib/team.ts new file mode 100644 index 0000000000..b2e9fd6f01 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/_common/lib/team.ts @@ -0,0 +1,10 @@ +import { cache } from 'react' + +export type Team = Awaited> + +export const getTeam = cache(async (id: string) => { + return { + id, + name: `Team${id}`, + } +}) diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/_common/ui/dashboard-sidebar.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/_common/ui/dashboard-sidebar.tsx new file mode 100644 index 0000000000..6124952617 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/_common/ui/dashboard-sidebar.tsx @@ -0,0 +1,65 @@ +'use client' + +import { ChartBarIcon, CogIcon, HomeIcon } from '@heroicons/react/24/solid' +import { classNames } from '@sushiswap/ui' +import Link from 'next/link' +import { usePathname } from 'next/navigation' +import { TeamSwitcher } from './team-switcher' + +interface DashboardEntry { + children: React.ReactNode + selected?: boolean + href: string +} + +function DashboardEntry({ children, selected = false, href }: DashboardEntry) { + return ( + +
+ {children} +
+ + ) +} + +export function DashboardSidebar({ teamId }: { teamId: string }) { + const pathname = usePathname() + + console.log(pathname) + + return ( +
+ +
+ + + Dashboard + + + + Statistics + + + + Settings + +
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/_common/ui/team-switcher.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/_common/ui/team-switcher.tsx new file mode 100644 index 0000000000..c15303d813 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/_common/ui/team-switcher.tsx @@ -0,0 +1,99 @@ +'use client' + +import { CheckIcon } from '@heroicons/react-v1/solid' +import { ChevronDownIcon, PlusIcon } from '@heroicons/react/24/solid' +import { useOnClickOutside } from '@sushiswap/hooks' +import { Separator, classNames } from '@sushiswap/ui' +import { useRouter } from 'next/navigation' +import { useRef, useState } from 'react' + +interface TeamSwitcher { + currentTeamId: string +} + +const teams = ['1', '2', '3', '4', '5'].map((id) => ({ + id, + name: `Team${id}`, +})) + +export function TeamSwitcher({ currentTeamId }: TeamSwitcher) { + const [open, setOpen] = useState(false) + const ref = useRef(null) + + const router = useRouter() + + useOnClickOutside(ref, () => { + setOpen(false) + }) + + const onTeamClick = (teamId: string) => { + if (currentTeamId === teamId) return + router.push(`/portal/dashboard/${teamId}`) + } + + return ( +
+
+
+
setOpen(!open)} + onKeyUp={(e) => e.key === 'Enter' && setOpen(!open)} + > + {currentTeamId} + +
+
+ {teams.map((team) => ( +
onTeamClick(team.id)} + onKeyUp={() => onTeamClick(team.id)} + > + {team.name} + {team.id === currentTeamId && ( + + )} +
+ ))} + +
+ Create team + +
+
+
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/layout.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/layout.tsx new file mode 100644 index 0000000000..e474218df0 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/layout.tsx @@ -0,0 +1,18 @@ +import { Container } from '@sushiswap/ui' +import { DashboardSidebar } from './_common/ui/dashboard-sidebar' + +export default async function Layout({ + children, + params, +}: { children: React.ReactNode; params: Promise<{ teamId: string }> }) { + const teamId = (await params).teamId + + return ( +
+ + + {children} + +
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-action.ts b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-action.ts new file mode 100644 index 0000000000..af821ae4e3 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-action.ts @@ -0,0 +1,44 @@ +'use server' + +import { getSessionData } from 'src/app/portal/_common/lib/client-config' +import { logoutAction } from 'src/app/portal/_common/lib/logout-action' +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' +import { getDeleteAccountChecklist } from './delete-account-checks' + +export type FormState = + | { + error: string + } + | { + success: true + } + +export async function deleteAccountAction(): Promise { + const session = await getSessionData() + + if (!session.isLoggedIn) { + return { error: 'User is not logged in' } + } + + const checklist = await getDeleteAccountChecklist() + + if (!checklist.canDelete) { + return { error: 'User cannot delete account' } + } + + try { + const userServiceClient = getUserServiceClient() + await userServiceClient.deleteUser({ + $typeName: 'zitadel.user.v2.DeleteUserRequest', + userId: session.user.id, + }) + } catch (e) { + console.error(e) + return { error: 'Failed to delete user' } + } + + // Handles the /portal redirect as well + await logoutAction() + + return { success: true } +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-card.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-card.tsx new file mode 100644 index 0000000000..74465c9cf5 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-card.tsx @@ -0,0 +1,54 @@ +import { + Button, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Checkbox, + List, +} from '@sushiswap/ui' +import { getDeleteAccountChecklist } from './delete-account-checks' +import { DeleteAccountModal } from './delete-account-modal' + +export async function DeleteAccountCard() { + const checklist = await getDeleteAccountChecklist() + + return ( + + + Delete Account + + Permanently delete your account and all associated data + + + +
+ + + + + + + + + + +
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-checks.ts b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-checks.ts new file mode 100644 index 0000000000..949d1c77da --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-checks.ts @@ -0,0 +1,26 @@ +import { getSessionData } from 'src/app/portal/_common/lib/client-config' + +export type DeleteAccountChecklist = { + checks?: { + teamMembership: boolean + } + canDelete: boolean +} + +export async function getDeleteAccountChecklist(): Promise { + const session = await getSessionData() + + if (!session.isLoggedIn) { + return { canDelete: false } + } + + // TODO: Checks + const checks = { + teamMembership: true, + } + + return { + checks, + canDelete: !Object.values(checks).some((check) => !check), + } +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-modal.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-modal.tsx new file mode 100644 index 0000000000..0589d4e116 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/delete-account/delete-account-modal.tsx @@ -0,0 +1,110 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { + Button, + Checkbox, + Collapsible, + Dialog, + DialogClose, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, + FormControl, + FormField, + FormItem, + useForm, +} from '@sushiswap/ui' +import { type ReactNode, useCallback, useState } from 'react' +import { FormProvider } from 'react-hook-form' +import { z } from 'zod' +import { deleteAccountAction } from './delete-account-action' + +interface DeleteAccountModal { + children: ReactNode +} + +const deleteAccountFormSchema = z.object({ + confirm: z.boolean().refine((value) => value === true, {}), +}) + +type DeleteAccountFormValues = z.infer + +export function DeleteAccountModal({ children }: DeleteAccountModal) { + const [globalErrorMsg, setGlobalErrorMsg] = useState(null) + + const form = useForm({ + defaultValues: { + confirm: false, + }, + mode: 'all', + resolver: zodResolver(deleteAccountFormSchema), + }) + + const onSubmit = useCallback(async (_values: DeleteAccountFormValues) => { + const result = await deleteAccountAction() + + if ('error' in result) { + setGlobalErrorMsg(result.error) + } + }, []) + + const isDisabled = form.formState.isSubmitting || !form.formState.isValid + + return ( + setGlobalErrorMsg(null)}> + + {children} + + + Delete Account + + Deleting your account is permanent. All your data will be lost and + cannot be recovered. + + + ( + + +
+ + +
+
+
+ )} + /> + +
+ + +
+ {globalErrorMsg || ''} +
+
+
+
+
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/identity-providers/identity-providers-card.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/identity-providers/identity-providers-card.tsx new file mode 100644 index 0000000000..3a564321af --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/identity-providers/identity-providers-card.tsx @@ -0,0 +1,73 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@sushiswap/ui' +import { getSessionData } from 'src/app/portal/_common/lib/client-config' +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' +import { + OAuthButton, + OAuthId, + OAuthProvider, +} from 'src/app/portal/_common/ui/oauth/oauth-button' + +interface IdentityProvidersCard { + teamId: string +} + +export async function IdentityProvidersCard({ teamId }: IdentityProvidersCard) { + const session = await getSessionData() + + if (!session.isLoggedIn) { + return null + } + + const idpLinks = await getUserServiceClient() + .listIDPLinks({ + $typeName: 'zitadel.user.v2.ListIDPLinksRequest', + userId: session.user.id, + }) + .then((res) => res.result) + + const redirectPath = `/portal/dashboard/${teamId}/settings` + + const googleConnected = idpLinks.some( + (link) => link.idpId === OAuthId[OAuthProvider.Google], + ) + const githubConnected = idpLinks.some( + (link) => link.idpId === OAuthId[OAuthProvider.Github], + ) + + return ( + + + Identity Providers + Connect supported identity providers + + +
+ + +
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/password/password-action.ts b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/password/password-action.ts new file mode 100644 index 0000000000..e06f700316 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/password/password-action.ts @@ -0,0 +1,55 @@ +'use server' + +import { getSessionData } from 'src/app/portal/_common/lib/client-config' +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' +import { changeOrCreatePasswordSchema } from './password-form-schema' + +export type FormState = + | { + error: string + } + | { + error: string + field: keyof (typeof changeOrCreatePasswordSchema)['_output'] + } + | { + success: true + } + +export async function changeOrCreatePasswordAction( + data: FormData, +): Promise { + const formData = Object.fromEntries(data.entries()) + const result = changeOrCreatePasswordSchema.safeParse(formData) + + if (!result.success) { + return { error: 'Invalid form data' } + } + + const session = await getSessionData() + + if (!session.isLoggedIn) { + return { error: 'Not logged in' } + } + + try { + const userServiceClient = getUserServiceClient() + userServiceClient.setPassword({ + $typeName: 'zitadel.user.v2.SetPasswordRequest', + userId: session.user.id, + verification: { + case: undefined, + }, + newPassword: { + $typeName: 'zitadel.user.v2.Password', + changeRequired: false, + password: result.data.password, + }, + }) + } catch (e) { + console.error(e) + return { error: 'Failed to change password' } + } + + return { success: true } +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/password/password-card.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/password/password-card.tsx new file mode 100644 index 0000000000..365559c97f --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/password/password-card.tsx @@ -0,0 +1,183 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { + Button, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + Collapsible, + FormControl, + FormField, + FormItem, + FormLabel, + TextField, + classNames, + formClassnames, + useForm, +} from '@sushiswap/ui' +import { useCallback, useEffect, useState } from 'react' +import { FormProvider } from 'react-hook-form' +import type { z } from 'zod' +import { changeOrCreatePasswordAction } from './password-action' +import { changeOrCreatePasswordSchema } from './password-form-schema' + +type ChangeOrCreatePasswordValues = z.infer + +export function PasswordCard() { + const [globalMsg, setGlobalMsg] = useState<{ + type: 'error' | 'success' + message: string + } | null>(null) + + const form = useForm({ + defaultValues: { + password: '', + passwordConfirmation: '', + }, + mode: 'onBlur', + resolver: zodResolver(changeOrCreatePasswordSchema), + }) + + useEffect(() => { + if (form.getValues('password')) { + form.trigger(['password']) + } + if (form.getValues('passwordConfirmation')) { + form.trigger(['passwordConfirmation']) + } + }, [ + form.getValues, + form.trigger, + ...form.watch(['password', 'passwordConfirmation']), + ]) + + useEffect(() => { + let timeout: NodeJS.Timeout + + if (globalMsg?.type === 'success') { + timeout = setTimeout(() => setGlobalMsg(null), 2000) + } + + return () => clearTimeout(timeout) + }, [globalMsg]) + + const onSubmit = useCallback( + async (values: ChangeOrCreatePasswordValues) => { + const formData = new FormData() + formData.append('password', values.password) + formData.append('passwordConfirmation', values.passwordConfirmation) + + const result = await changeOrCreatePasswordAction(formData) + + if ('error' in result) { + if ('field' in result) { + form.setError(result.field, { message: result.error }) + } else { + setGlobalMsg({ + type: 'error', + message: result.error, + }) + } + } else { + setGlobalMsg({ + type: 'success', + message: 'Password changed successfully', + }) + form.reset() + } + }, + [form.setError, form.reset], + ) + + const isPending = form.formState.isSubmitting + + return ( + + + Password + Change or create your password + + +
+ +
+
+ ( + + + <> + Password + onChange(e.target.value)} + onBlur={onBlur} + className={formClassnames({ isError })} + disabled={isPending} + /> + + + + )} + /> + ( + + + <> + Repeat Password + onChange(e.target.value)} + onBlur={onBlur} + className={formClassnames({ isError })} + disabled={isPending} + /> + + + + )} + /> +
+
+ + +
+ {globalMsg?.message || ''} +
+
+
+
+
+
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/password/password-form-schema.ts b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/password/password-form-schema.ts new file mode 100644 index 0000000000..eb9e3ad709 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/password/password-form-schema.ts @@ -0,0 +1,23 @@ +import { zPassword } from 'src/app/portal/_common/lib/zod' +import { z } from 'zod' + +export const changeOrCreatePasswordSchema = z + .object({ + password: zPassword, + passwordConfirmation: zPassword, + }) + .superRefine((data, ctx) => { + if ( + data.password && + data.passwordConfirmation && + data.password !== data.passwordConfirmation + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Passwords do not match', + path: ['passwordConfirmation'], + }) + } + + return data + }) diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/user-details/user-details-card.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/user-details/user-details-card.tsx new file mode 100644 index 0000000000..8bf0aa04ee --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/_common/ui/user-details/user-details-card.tsx @@ -0,0 +1,49 @@ +'use client' + +import { ClipboardDocumentIcon } from '@heroicons/react/24/solid' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + ClipboardController, + List, +} from '@sushiswap/ui' +import { useSession } from 'src/app/portal/_common/ui/auth-provider/auth-provider' + +export function UserDetailsCard() { + const session = useSession() + + if (!session.isLoggedIn) { + return null + } + + return ( + + + User Details + Useful when requesting support + + + + + + {session.user.id} + + {({ setCopied }) => ( + setCopied(session.user.id)} + /> + )} + + + + + + + ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/page.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/page.tsx new file mode 100644 index 0000000000..2ed73561bd --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/(account)/page.tsx @@ -0,0 +1,23 @@ +import { DeleteAccountCard } from './_common/ui/delete-account/delete-account-card' +import { IdentityProvidersCard } from './_common/ui/identity-providers/identity-providers-card' +import { PasswordCard } from './_common/ui/password/password-card' +import { UserDetailsCard } from './_common/ui/user-details/user-details-card' + +export default async function Page({ + params, +}: { params: Promise<{ teamId: string }> }) { + const teamId = (await params).teamId + + return ( +
+
+ + +
+
+ + +
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/billing/page.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/billing/page.tsx new file mode 100644 index 0000000000..d9e3419fbc --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/billing/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
BILLING
+} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/layout.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/layout.tsx new file mode 100644 index 0000000000..484a2fb839 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/layout.tsx @@ -0,0 +1,49 @@ +'use client' + +import { Tabs, TabsList, TabsTrigger } from '@sushiswap/ui' +import { usePathname, useRouter } from 'next/navigation' + +function getTab(pathname: string) { + if (pathname.endsWith('/settings')) { + return 'account' + } + + if (pathname.endsWith('/settings/team')) { + return 'team' + } + + if (pathname.endsWith('/settings/billing')) { + return 'billing' + } + + throw new Error('Invalid pathname') +} + +export default function Layout({ + children, + params, +}: { children: React.ReactNode; params: Promise<{ teamId: string }> }) { + const pathname = usePathname() + const router = useRouter() + + return ( +
+ { + const value = _value === 'account' ? '' : `/${_value}` + + const { teamId } = await params + router.push(`/portal/dashboard/${teamId}/settings${value}`) + }} + > + + Account + Team + Billing + + + {children} +
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/delete-team/delete-team-card.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/delete-team/delete-team-card.tsx new file mode 100644 index 0000000000..311e0879f3 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/delete-team/delete-team-card.tsx @@ -0,0 +1,35 @@ +import { + Button, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@sushiswap/ui' +import { DeleteTeamModal } from './delete-team-modal' +// import { DeleteAccountModal } from './delete-account-modal' + +interface DeleteTeamCard { + teamId: string +} + +export async function DeleteTeamCard({ teamId }: DeleteTeamCard) { + // Permission-based + const canDelete = true + + return ( + + + Delete Team + Permanently delete the team + + + + + + + + ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/delete-team/delete-team-modal.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/delete-team/delete-team-modal.tsx new file mode 100644 index 0000000000..0fce0e4848 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/delete-team/delete-team-modal.tsx @@ -0,0 +1,121 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { + Button, + Checkbox, + Collapsible, + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, + FormControl, + FormField, + FormItem, + useForm, +} from '@sushiswap/ui' +import { useMutation } from '@tanstack/react-query' +import { useRouter } from 'next/navigation' +import { type ReactNode, useCallback, useState } from 'react' +import { FormProvider } from 'react-hook-form' +import { z } from 'zod' + +interface DeleteTeamModal { + children: ReactNode + teamId: string +} + +const deleteTeamFormSchema = z.object({ + confirm: z.boolean().refine((value) => value === true, {}), +}) + +type DeleteTeamFormValues = z.infer + +export function DeleteTeamModal({ children }: DeleteTeamModal) { + const [globalErrorMsg, setGlobalErrorMsg] = useState(null) + const router = useRouter() + + const form = useForm({ + defaultValues: { + confirm: false, + }, + mode: 'all', + resolver: zodResolver(deleteTeamFormSchema), + }) + + const { mutateAsync } = useMutation({ + mutationKey: ['delete-team'], + mutationFn: async () => { + return undefined + }, + onSuccess: () => { + router.push('/portal/dashboard') + }, + }) + + const onSubmit = useCallback( + async (_values: DeleteTeamFormValues) => { + await mutateAsync() + }, + [mutateAsync], + ) + + const isDisabled = form.formState.isSubmitting || !form.formState.isValid + + return ( + setGlobalErrorMsg(null)}> + + {children} + + + Delete Team + + Deleting the team is permanent. All keys, members and billing + information will be lost. + + + ( + + +
+ + +
+
+
+ )} + /> + +
+ + +
+ {globalErrorMsg || ''} +
+
+
+
+
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/manage-team/manage-team-card.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/manage-team/manage-team-card.tsx new file mode 100644 index 0000000000..05d5bd5511 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/manage-team/manage-team-card.tsx @@ -0,0 +1,28 @@ +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from '@sushiswap/ui' +import { ManageTeamForm } from './manage-team-form' + +interface ManageTeamCard { + teamId: string +} + +export async function ManageTeamCard({ teamId }: ManageTeamCard) { + return ( + + + Manage Team + Manage your team + + + + + + ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/manage-team/manage-team-form.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/manage-team/manage-team-form.tsx new file mode 100644 index 0000000000..a66f4767d3 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/manage-team/manage-team-form.tsx @@ -0,0 +1,175 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { + Button, + Collapsible, + FormControl, + FormField, + FormItem, + FormLabel, + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, + TextField, + classNames, + formClassnames, +} from '@sushiswap/ui' +import { useMutation } from '@tanstack/react-query' +import { revalidateTag } from 'next/cache' +import { useCallback, useEffect, useState } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import { z } from 'zod' + +interface ManageTeamForm { + team: { + id: string + name: string + memberPermissions: z.infer + } +} + +const zMemberPermission = z.enum(['none', 'add-remove']) + +const manageTeamFormSchema = z.object({ + id: z.string(), + name: z + .string() + .min(4, { message: 'Minimum length is 4 characters' }) + .max(16, { message: 'Maximum length is 16 characters' }), + memberPermissions: zMemberPermission, +}) + +type ManageTeamFormValues = z.infer + +export function ManageTeamForm({ team: initialTeam }: ManageTeamForm) { + const [globalMsg, setGlobalMsg] = useState<{ + type: 'error' | 'success' + message: string + } | null>(null) + + useEffect(() => { + let timeout: NodeJS.Timeout + + if (globalMsg?.type === 'success') { + timeout = setTimeout(() => setGlobalMsg(null), 2000) + } + + return () => clearTimeout(timeout) + }, [globalMsg]) + + const form = useForm({ + defaultValues: initialTeam, + mode: 'all', + resolver: zodResolver(manageTeamFormSchema), + }) + + const { mutateAsync } = useMutation({ + mutationKey: ['manage-team'], + onMutate: async (_values: ManageTeamFormValues) => {}, + onSuccess: () => { + revalidateTag(`portal-team-${initialTeam.id}`) + }, + }) + + const onSubmit = useCallback( + async (values: ManageTeamFormValues) => { + await mutateAsync(values) + }, + [mutateAsync], + ) + + const isDirty = form.formState.isDirty + const isPending = form.formState.isSubmitting + + const canSubmit = isDirty && !isPending + + return ( +
+ +
+
+ ( + + + <> + Team Name + onChange(e.target.value)} + onBlur={onBlur} + className={formClassnames({ isError, isDirty })} + disabled={isPending} + /> + + + + )} + /> + ( + + + Member Permissions + + + + )} + /> +
+
+ + +
+ {globalMsg?.message || ''} +
+
+
+
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/team-details/team-details-card.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/team-details/team-details-card.tsx new file mode 100644 index 0000000000..0163675da4 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/team-details/team-details-card.tsx @@ -0,0 +1,46 @@ +'use client' + +import { ClipboardDocumentIcon } from '@heroicons/react/24/solid' +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + ClipboardController, + List, +} from '@sushiswap/ui' + +interface TeamDetailsCard { + teamId: string +} + +export function TeamDetailsCard({ teamId }: TeamDetailsCard) { + return ( + + + Team Details + Useful when requesting support + + + + + + {teamId} + + {({ setCopied }) => ( + setCopied(teamId)} + /> + )} + + + + + + + ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/team-members/team-members.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/team-members/team-members.tsx new file mode 100644 index 0000000000..f23bb603ee --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/_common/ui/team-members/team-members.tsx @@ -0,0 +1,106 @@ +'use client' + +import { + Button, + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, + DataTable, + Popover, + PopoverContent, + PopoverTrigger, + SelectIcon, +} from '@sushiswap/ui' +import { useQuery } from '@tanstack/react-query' +import type { ColumnDef } from '@tanstack/react-table' + +interface TeamMembers { + team: { + id: string + members: { n: number }[] + } +} + +const columns = [ + { + header: 'Email', + accessorFn: () => 'lufy@sushi.com', + enableSorting: false, + }, + { + header: 'Role', + accessorFn: () => 'Member', + enableSorting: false, + }, + { + header: 'Actions', + cell: () => { + return ( + + + + + { + event.preventDefault() + }} + > +
+ + +
+
+
+ ) + }, + meta: { + header: { + className: 'text-right', + }, + body: { + className: 'text-right', + }, + }, + }, +] satisfies ColumnDef[] + +export function TeamMembers({ team: initialTeam }: TeamMembers) { + const { data } = useQuery({ + queryKey: ['portal-team', initialTeam.id], + queryFn: async () => { + return initialTeam.members + }, + initialData: initialTeam.members, + }) + + return ( + + + Team Members + Explore and manage members + + + + + + ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/page.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/page.tsx new file mode 100644 index 0000000000..cf967f9a43 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/settings/team/page.tsx @@ -0,0 +1,25 @@ +import { DeleteTeamCard } from './_common/ui/delete-team/delete-team-card' +import { ManageTeamCard } from './_common/ui/manage-team/manage-team-card' +import { TeamDetailsCard } from './_common/ui/team-details/team-details-card' +import { TeamMembers } from './_common/ui/team-members/team-members' + +export default async function Page({ + params, +}: { params: Promise<{ teamId: string }> }) { + const teamId = (await params).teamId + + return ( +
+
+
+ + +
+ +
+ +
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/statistics/page.tsx b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/statistics/page.tsx new file mode 100644 index 0000000000..ad99d8cb9d --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/[teamId]/statistics/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
statistics
+} diff --git a/apps/web/src/app/portal/(authenticated)/dashboard/route.ts b/apps/web/src/app/portal/(authenticated)/dashboard/route.ts new file mode 100644 index 0000000000..4f860fc20c --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/dashboard/route.ts @@ -0,0 +1,13 @@ +import { type NextRequest, NextResponse } from 'next/server' +import { getSessionData } from '../../_common/lib/client-config' + +export async function GET(request: NextRequest) { + const session = await getSessionData() + + // TODO: Implement this logic + const defaultTeamId = '1' + + return NextResponse.redirect( + `${request.nextUrl.protocol}/${request.nextUrl.host}/portal/dashboard/${defaultTeamId}`, + ) +} diff --git a/apps/web/src/app/portal/(authenticated)/layout.tsx b/apps/web/src/app/portal/(authenticated)/layout.tsx new file mode 100644 index 0000000000..d7418b8aff --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/layout.tsx @@ -0,0 +1,14 @@ +import { redirect } from 'next/navigation' +import { getSessionData } from '../_common/lib/client-config' + +export default async function Layout({ + children, +}: { children: React.ReactNode }) { + const authSession = await getSessionData() + + if (!authSession.isLoggedIn) { + redirect('/portal/login') + } + + return children +} diff --git a/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/resend-code/resend-code-action.ts b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/resend-code/resend-code-action.ts new file mode 100644 index 0000000000..f089dd0d80 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/resend-code/resend-code-action.ts @@ -0,0 +1,31 @@ +'use server' + +import { getSessionData } from 'src/app/portal/_common/lib/client-config' +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' + +export async function resendCodeAction() { + const session = await getSessionData() + if (!session.isLoggedIn) { + return { error: 'Not logged in' } + } + + try { + const userServiceClient = getUserServiceClient() + await userServiceClient.resendEmailCode({ + $typeName: 'zitadel.user.v2.ResendEmailCodeRequest', + userId: session.user.id, + verification: { + case: 'sendCode', + value: { + $typeName: 'zitadel.user.v2.SendEmailVerificationCode', + urlTemplate: undefined, // TODO: Email template + }, + }, + }) + } catch (e) { + console.error(e) + return { error: 'Failed to resend code' } + } + + return { success: true } +} diff --git a/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/resend-code/resend-code-button.tsx b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/resend-code/resend-code-button.tsx new file mode 100644 index 0000000000..11939e8532 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/resend-code/resend-code-button.tsx @@ -0,0 +1,32 @@ +'use client' + +import { Button, Loader } from '@sushiswap/ui' +import { CheckMarkIcon } from '@sushiswap/ui/icons/CheckMarkIcon' +import { FailedMarkIcon } from '@sushiswap/ui/icons/FailedMarkIcon' +import { useMutation } from '@tanstack/react-query' +import { resendCodeAction } from './resend-code-action' + +export function ResendCodeButton() { + const { mutate, isSuccess, error, isPending } = useMutation({ + mutationKey: ['resend-code'], + mutationFn: async () => { + await resendCodeAction() + }, + }) + + return ( + + ) +} diff --git a/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/verify-email-action.ts b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/verify-email-action.ts new file mode 100644 index 0000000000..f28c092a30 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/verify-email-action.ts @@ -0,0 +1,57 @@ +'use server' + +import { redirect } from 'next/navigation' +import { getSession } from 'src/app/portal/_common/lib/client-config' +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' +import { verifyEmailFormSchema } from './verify-form-schema' + +export type FormState = + | { + error: string + } + | { + error: string + field: keyof (typeof verifyEmailFormSchema)['_output'] + } + | { + success: true + } + +export async function verifyEmailAction(data: FormData): Promise { + const session = await getSession() + if (!session.isLoggedIn) { + return { error: 'Not logged in' } + } + + const formData = Object.fromEntries(data.entries()) + const result = verifyEmailFormSchema.safeParse(formData) + + if (!result.success) { + return { error: 'Invalid form data' } + } + + try { + const userServiceClient = getUserServiceClient() + await userServiceClient.verifyEmail({ + $typeName: 'zitadel.user.v2.VerifyEmailRequest', + userId: session.user.id, + verificationCode: result.data.code, + }) + + session.user.email.isVerified = true + await session.save() + } catch (e) { + console.error(e) + if (e instanceof Error && 'code' in e && typeof e.code === 'number') { + switch (e.code) { + case 3: { + return { error: 'Invalid code', field: 'code' } + } + } + } + } + + redirect('/portal') + + return { success: true } +} diff --git a/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/verify-form-schema.ts b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/verify-form-schema.ts new file mode 100644 index 0000000000..b4aa10145b --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/verify-form-schema.ts @@ -0,0 +1,9 @@ +import { z } from 'zod' + +export const verifyEmailFormSchema = z.object({ + code: z + .string() + .trim() + .toUpperCase() + .length(6, { message: 'Code must be 6 characters long' }), +}) diff --git a/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/verify-form.tsx b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/verify-form.tsx new file mode 100644 index 0000000000..0eafe4f7da --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/verify/_common/ui/verify-form/verify-form.tsx @@ -0,0 +1,101 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { + Button, + FormControl, + FormField, + FormItem, + FormLabel, + TextField, + formClassnames, +} from '@sushiswap/ui' +import { useCallback, useState } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import type { z } from 'zod' +import { ResendCodeButton } from './resend-code/resend-code-button' +import { verifyEmailAction } from './verify-email-action' +import { verifyEmailFormSchema } from './verify-form-schema' + +type VerifyFormValues = z.infer + +export function VerifyForm() { + const [globalErrorMsg, setGlobalErrorMsg] = useState(null) + + const form = useForm({ + defaultValues: { + code: '', + }, + mode: 'onBlur', + resolver: zodResolver(verifyEmailFormSchema), + }) + + const onSubmit = useCallback( + async (values: VerifyFormValues) => { + const formData = new FormData() + formData.append('code', values.code) + + const result = await verifyEmailAction(formData) + + if ('error' in result) { + if ('field' in result) { + form.setError(result.field, { message: result.error }) + } else { + setGlobalErrorMsg(result.error) + } + } + }, + [form.setError], + ) + + return ( +
+ +
+
+ ( + + + <> + Code + onChange(e.target.value)} + onBlur={onBlur} + className={formClassnames({ isError })} + disabled={form.formState.isSubmitting} + /> + + + + )} + /> +
+
+ + +
+ {globalErrorMsg && ( +
+ {globalErrorMsg} +
+ )} +
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(authenticated)/verify/api/set-email-verified/route.ts b/apps/web/src/app/portal/(authenticated)/verify/api/set-email-verified/route.ts new file mode 100644 index 0000000000..a07688a3af --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/verify/api/set-email-verified/route.ts @@ -0,0 +1,30 @@ +import { redirect } from 'next/navigation' +import { getSession } from 'src/app/portal/_common/lib/client-config' +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' + +export async function GET() { + const authSession = await getSession() + if (!authSession.isLoggedIn) { + return + } + + const userServiceClient = getUserServiceClient() + const result = await userServiceClient.getUserByID({ + $typeName: 'zitadel.user.v2.GetUserByIDRequest', + userId: authSession.user.id, + }) + if ( + !result.user || + result.user.type.case !== 'human' || + !result.user.type.value.email + ) { + // Should only happen if the server is down + throw new Error('Something went wrong') + } + if (result.user.type.value.email.isVerified) { + authSession.user.email.isVerified = true + await authSession.save() + } + + redirect('/portal') +} diff --git a/apps/web/src/app/portal/(authenticated)/verify/layout.tsx b/apps/web/src/app/portal/(authenticated)/verify/layout.tsx new file mode 100644 index 0000000000..aae89488c5 --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/verify/layout.tsx @@ -0,0 +1,38 @@ +import { Container } from '@sushiswap/ui' +import { redirect } from 'next/navigation' +import { getSession } from 'src/app/portal/_common/lib/client-config' +import { getUserServiceClient } from '../../_common/lib/zitadel-client' + +export default async function Layout({ + children, +}: { children: React.ReactNode }) { + const authSession = await getSession() + + if (!authSession.isLoggedIn || authSession.user.email.isVerified) { + redirect('/portal') + } + + // Check if the user verified their email in the meantime + const userServiceClient = getUserServiceClient() + const result = await userServiceClient.getUserByID({ + $typeName: 'zitadel.user.v2.GetUserByIDRequest', + userId: authSession.user.id, + }) + if ( + !result.user || + result.user.type.case !== 'human' || + !result.user.type.value.email + ) { + // Should only happen if the server is down + throw new Error('Something went wrong') + } + if (result.user.type.value.email.isVerified) { + redirect('/portal/verify/api/set-email-verified') + } + + return ( + + {children} + + ) +} diff --git a/apps/web/src/app/portal/(authenticated)/verify/page.tsx b/apps/web/src/app/portal/(authenticated)/verify/page.tsx new file mode 100644 index 0000000000..6bf53bae2b --- /dev/null +++ b/apps/web/src/app/portal/(authenticated)/verify/page.tsx @@ -0,0 +1,10 @@ +import { VerifyForm } from './_common/ui/verify-form/verify-form' + +export default function Page() { + return ( +
+

Verify E-mail

+ +
+ ) +} diff --git a/apps/web/src/app/portal/(unauthenticated)/_common/lib/create-zitadel-session.ts b/apps/web/src/app/portal/(unauthenticated)/_common/lib/create-zitadel-session.ts new file mode 100644 index 0000000000..4e008607e7 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/_common/lib/create-zitadel-session.ts @@ -0,0 +1,70 @@ +import ms from 'ms' +import { getSessionServiceClient } from 'src/app/portal/_common/lib/zitadel-client' + +type CreateZitadelSession = + | { + type: 'password' + email: string + password: string + } + | { + type: 'idp' + userId: string + idpIntentId: string + idpIntentToken: string + } + +export async function createZitadelSession(body: CreateZitadelSession) { + const sessionServiceClient = getSessionServiceClient() + + if (body.type === 'idp') { + return sessionServiceClient.createSession({ + $typeName: 'zitadel.session.v2.CreateSessionRequest', + lifetime: { + $typeName: 'google.protobuf.Duration', + seconds: BigInt(ms('7d')) / 1000n, + nanos: 0, + }, + metadata: {}, + checks: { + $typeName: 'zitadel.session.v2.Checks', + user: { + $typeName: 'zitadel.session.v2.CheckUser', + search: { + case: 'userId', + value: body.userId, + }, + }, + idpIntent: { + $typeName: 'zitadel.session.v2.CheckIDPIntent', + idpIntentId: body.idpIntentId, + idpIntentToken: body.idpIntentToken, + }, + }, + }) + } + + return sessionServiceClient.createSession({ + $typeName: 'zitadel.session.v2.CreateSessionRequest', + lifetime: { + $typeName: 'google.protobuf.Duration', + seconds: BigInt(ms('7d')) / 1000n, + nanos: 0, + }, + metadata: {}, + checks: { + $typeName: 'zitadel.session.v2.Checks', + user: { + $typeName: 'zitadel.session.v2.CheckUser', + search: { + case: 'loginName', // e-mail, + value: body.email, + }, + }, + password: { + $typeName: 'zitadel.session.v2.CheckPassword', + password: body.password, + }, + }, + }) +} diff --git a/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/lib/get-user-by-id.ts b/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/lib/get-user-by-id.ts new file mode 100644 index 0000000000..e7b0bddf39 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/lib/get-user-by-id.ts @@ -0,0 +1,48 @@ +import { authEnv } from 'src/app/portal/_common/lib/auth-env' +import { z } from 'zod' + +// Not exhaustive +// https://zitadel.com/docs/apis/resources/user_service_v2/user-service-get-user-by-id +const schema = z.object({ + user: z.object({ + userId: z.string(), + state: z.enum([ + 'USER_STATE_UNSPECIFIED', + 'USER_STATE_ACTIVE', + 'USER_STATE_INACTIVE', + 'USER_STATE_DELETED', + 'USER_STATE_LOCKED', + 'USER_STATE_INITIAL', + ]), + username: z.string(), + loginNames: z.array(z.string()), + preferredLoginName: z.string(), + human: z.object({ + email: z.object({ + email: z.string(), + isVerified: z.boolean(), + }), + }), + }), +}) + +export async function getUserById(id: string) { + const response = await fetch(`${authEnv.ZITADEL_ISSUER}/v2/users/${id}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: `Bearer ${authEnv.ZITADEL_SA_TOKEN}`, + }, + }) + + const data = await response.json() + + const result = schema.safeParse(data) + + if (!result.success) { + throw new Error(`Couldn't fetch user data`) + } + + return result.data.user +} diff --git a/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/lib/login.ts b/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/lib/login.ts new file mode 100644 index 0000000000..c744671570 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/lib/login.ts @@ -0,0 +1,18 @@ +import { createZitadelSession } from 'src/app/portal/(unauthenticated)/_common/lib/create-zitadel-session' + +interface Login { + userId: string + idpIntentId: string + idpIntentToken: string +} + +export async function login(params: Login) { + const result = await createZitadelSession({ + type: 'idp', + userId: params.userId, + idpIntentId: params.idpIntentId, + idpIntentToken: params.idpIntentToken, + }) + + return result +} diff --git a/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/lib/register.ts b/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/lib/register.ts new file mode 100644 index 0000000000..3fed85788c --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/lib/register.ts @@ -0,0 +1,34 @@ +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' +import type { IdpIntent } from '../../../../../_common/lib/get-idp-intent' + +export async function register(idpIntent: IdpIntent) { + const userServiceClient = getUserServiceClient() + const result = await userServiceClient.addHumanUser({ + profile: { + $typeName: 'zitadel.user.v2.SetHumanProfile', + givenName: idpIntent.idpInformation.rawInformation.givenName, + familyName: idpIntent.idpInformation.rawInformation.familyName, + }, + email: { + $typeName: 'zitadel.user.v2.SetHumanEmail', + email: idpIntent.idpInformation.rawInformation.email, + verification: { + case: 'isVerified', + value: true, + }, + }, + idpLinks: [ + { + $typeName: 'zitadel.user.v2.IDPLink', + idpId: idpIntent.idpInformation.idpId, + userId: idpIntent.idpInformation.userId, + userName: idpIntent.idpInformation.rawInformation.email, + }, + ], + passwordType: { + case: undefined, + }, + }) + + return result +} diff --git a/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/route.ts b/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/route.ts new file mode 100644 index 0000000000..dbd488bb0d --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/api/auth/callback/route.ts @@ -0,0 +1,102 @@ +import { redirect } from 'next/navigation' +import type { NextRequest } from 'next/server' +import { + createSession, + getSession, +} from 'src/app/portal/_common/lib/client-config' +import { z } from 'zod' +import { getIdpIntent } from '../../../../_common/lib/get-idp-intent' +import { getSessionServiceClient } from '../../../../_common/lib/zitadel-client' +import { getUserById } from './lib/get-user-by-id' +import { login } from './lib/login' +import { register } from './lib/register' + +const schema = z.object({ + id: z.string(), + token: z.string(), + user: z.string().nullable(), +}) + +async function GET(req: NextRequest) { + const url = new URL(req.url) + + const result = schema.safeParse({ + id: url.searchParams.get('id'), + token: url.searchParams.get('token'), + user: url.searchParams.get('user'), + }) + + if (!result.success) { + return new Response(JSON.stringify(result.error, null, 2), { status: 400 }) + } + + const data = result.data + let email: string | undefined + + console.log(data) + + if (!data.user) { + try { + // Register + const idpIntent = await getIdpIntent(data.id, data.token) + const { userId } = await register(idpIntent) + data.user = userId + email = idpIntent.idpInformation.rawInformation.email + } catch (e) { + console.error(e) + if (e instanceof Error && 'code' in e) { + // User already exists + if (e.code === 6) { + return redirect('/portal/login?error=oauthAlreadyExists') + } + } + return new Response('An unknown error occured', { status: 500 }) + } + } + + const session = await login({ + userId: data.user, + idpIntentId: data.id, + idpIntentToken: data.token, + }) + + if (!email) { + const user = await getUserById(data.user) + if (user.state !== 'USER_STATE_ACTIVE') { + return new Response('User is not active', { status: 400 }) + } + + email = user.human.email.email + } + + const previousSession = await getSession() + let logoutP: Promise | null = null + if (previousSession.isLoggedIn) { + const sessionServiceClient = getSessionServiceClient() + logoutP = sessionServiceClient.deleteSession({ + $typeName: 'zitadel.session.v2.DeleteSessionRequest', + sessionId: previousSession.session.id, + sessionToken: previousSession.session.token, + }) + } + + await createSession({ + session: { + id: session.sessionId, + token: session.sessionToken, + }, + user: { + id: data.user, + email: { + email, + isVerified: true, + }, + }, + }) + + await logoutP + + return redirect('/portal') +} + +export { GET } diff --git a/apps/web/src/app/portal/(unauthenticated)/layout.tsx b/apps/web/src/app/portal/(unauthenticated)/layout.tsx new file mode 100644 index 0000000000..5f7bef2fc5 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/layout.tsx @@ -0,0 +1,14 @@ +import { redirect } from 'next/navigation' +import { getSessionData } from '../_common/lib/client-config' + +export default async function Layout({ + children, +}: { children: React.ReactNode }) { + const authSession = await getSessionData() + + if (authSession.isLoggedIn) { + redirect('/portal') + } + + return children +} diff --git a/apps/web/src/app/portal/(unauthenticated)/login/_common/ui/login-action.ts b/apps/web/src/app/portal/(unauthenticated)/login/_common/ui/login-action.ts new file mode 100644 index 0000000000..2a0bc95730 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/login/_common/ui/login-action.ts @@ -0,0 +1,131 @@ +'use server' + +import { isRedirectError } from 'next/dist/client/components/redirect-error' +import { redirect } from 'next/navigation' +import { createSession } from 'src/app/portal/_common/lib/client-config' +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' +import { isPromiseRejected } from 'sushi' +import { createZitadelSession } from '../../../_common/lib/create-zitadel-session' +import { loginFormSchema } from './login-form-schema' + +export type FormState = + | { + error: string + } + | { + error: string + field: keyof (typeof loginFormSchema)['_output'] + } + | { + success: true + } + +export async function loginAction(data: FormData): Promise { + const formData = Object.fromEntries(data.entries()) + const result = loginFormSchema.safeParse(formData) + + if (!result.success) { + return { error: 'Invalid form data' } + } + + try { + const [session, user] = await Promise.allSettled([ + createZitadelSession({ type: 'password', ...result.data }), + fetchZitadelUser(result.data.email), + ]) + + if (isPromiseRejected(session)) { + const e = session.reason + if (e instanceof Error && 'code' in e && typeof e.code === 'number') { + switch (e.code) { + case 3: + return { error: 'Invalid password', field: 'password' } + case 5: + return { error: 'Email not found', field: 'email' } + } + } + return { error: 'An unknown error occured' } + } + + if (isPromiseRejected(user)) { + return { error: 'Failed to fetch user' } + } + + await createSession({ + session: { + id: session.value.sessionId, + token: session.value.sessionToken, + }, + user: { + id: user.value.userId, + email: { + isVerified: user.value.type.value.email.isVerified, + email: user.value.type.value.email.email, + }, + }, + }) + + if (!user.value.type.value.email.isVerified) { + redirect('/portal/verify') + } else { + redirect('/portal') + } + + return { success: true } + } catch (e) { + if (isRedirectError(e)) { + throw e + } + + console.error(e) + return { error: 'An unknown error occured' } + } +} + +async function fetchZitadelUser(email: string) { + const userServiceClient = getUserServiceClient() + + return userServiceClient + .listUsers( + { + queries: [ + { + $typeName: 'zitadel.user.v2.SearchQuery', + query: { + case: 'emailQuery', + value: { + $typeName: 'zitadel.user.v2.EmailQuery', + method: 0, // Equals + emailAddress: email, + }, + }, + }, + ], + }, + {}, + ) + .then((res) => { + if (res.result.length > 0) { + const user = res.result[0] + if (user.type.case !== 'human') { + throw new Error('Machine users are not allowed') + } + + if (!user.type.value.email) { + throw new Error('User has no email') + } + + return { + ...user, + type: { + ...user.type, + value: { + ...user.type.value, + email: user.type.value.email, + }, + }, + } + } + throw new Error('User not found') + }) +} diff --git a/apps/web/src/app/portal/(unauthenticated)/login/_common/ui/login-form-schema.ts b/apps/web/src/app/portal/(unauthenticated)/login/_common/ui/login-form-schema.ts new file mode 100644 index 0000000000..46f574798c --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/login/_common/ui/login-form-schema.ts @@ -0,0 +1,7 @@ +import { z } from 'zod' +import { zPassword } from '../../../../_common/lib/zod' + +export const loginFormSchema = z.object({ + email: z.string().email(), + password: zPassword, +}) diff --git a/apps/web/src/app/portal/(unauthenticated)/login/_common/ui/login-form.tsx b/apps/web/src/app/portal/(unauthenticated)/login/_common/ui/login-form.tsx new file mode 100644 index 0000000000..1527221571 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/login/_common/ui/login-form.tsx @@ -0,0 +1,135 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { + Button, + FormControl, + FormField, + FormItem, + FormLabel, + TextField, + formClassnames, +} from '@sushiswap/ui' +import { useSearchParams } from 'next/navigation' +import { useCallback, useState } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import type { z } from 'zod' +import { loginAction } from './login-action' +import { loginFormSchema } from './login-form-schema' + +type LoginFormValues = z.infer + +const paramErrors = { + oauthAlreadyExists: + 'User with this email already exists, please try logging in with your password instead', +} as Record + +export function LoginForm() { + const searchParamsError = useSearchParams().get('error') + + const [globalErrorMsg, setGlobalErrorMsg] = useState( + searchParamsError ? paramErrors[searchParamsError] : null, + ) + + const form = useForm>({ + defaultValues: { + email: '', + password: '', + }, + mode: 'onBlur', + resolver: zodResolver(loginFormSchema), + }) + + const onSubmit = useCallback( + async (values: LoginFormValues) => { + const formData = new FormData() + formData.append('email', values.email) + formData.append('password', values.password) + + const result = await loginAction(formData) + + if ('error' in result) { + if ('field' in result) { + form.setError(result.field, { message: result.error }) + } else { + setGlobalErrorMsg(result.error) + } + } + }, + [form.setError], + ) + + return ( +
+ +
+
+ ( + + + <> + E-mail + onChange(e.target.value)} + onBlur={onBlur} + className={formClassnames({ isError })} + disabled={form.formState.isSubmitting} + /> + + + + )} + /> + ( + + + <> + Password + onChange(e.target.value)} + onBlur={onBlur} + className={formClassnames({ isError })} + disabled={form.formState.isSubmitting} + /> + + + + )} + /> +
+ + {globalErrorMsg && ( +
+ {globalErrorMsg} +
+ )} +
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(unauthenticated)/login/layout.tsx b/apps/web/src/app/portal/(unauthenticated)/login/layout.tsx new file mode 100644 index 0000000000..e90ecca339 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/login/layout.tsx @@ -0,0 +1,9 @@ +import { Container } from '@sushiswap/ui' + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/apps/web/src/app/portal/(unauthenticated)/login/page.tsx b/apps/web/src/app/portal/(unauthenticated)/login/page.tsx new file mode 100644 index 0000000000..ef6b607b13 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/login/page.tsx @@ -0,0 +1,49 @@ +import { Separator } from '@sushiswap/ui' +import Link from 'next/link' +import { OAuthButton, OAuthProvider } from '../../_common/ui/oauth/oauth-button' +import { LoginForm } from './_common/ui/login-form' + +export default function Page() { + return ( +
+

Sign In

+ +
+ +
+ + {`Forgot password?`} + +
+ {`Don't have an account yet?`} + + {`Sign Up`} + +
+
+
+ +
+ + +
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(unauthenticated)/register/_common/ui/register-action.ts b/apps/web/src/app/portal/(unauthenticated)/register/_common/ui/register-action.ts new file mode 100644 index 0000000000..bdab2f6350 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/register/_common/ui/register-action.ts @@ -0,0 +1,136 @@ +'use server' + +import type { CreateSessionResponse } from '@zitadel/proto/zitadel/session/v2/session_service_pb' +import type { AddHumanUserResponse } from '@zitadel/proto/zitadel/user/v2/user_service_pb' +import { redirect } from 'next/navigation' +import { createZitadelSession } from 'src/app/portal/(unauthenticated)/_common/lib/create-zitadel-session' +import { createSession } from 'src/app/portal/_common/lib/client-config' +import { getUserServiceClient } from 'src/app/portal/_common/lib/zitadel-client' +import type { z } from 'zod' +import { registerFormSchema } from './register-form-schema' + +export type FormState = + | { + error: string + } + | { + error: string + field: keyof (typeof registerFormSchema)['_output'] + } + | { + success: true + } + +export async function registerAction(data: FormData): Promise { + const formData = Object.fromEntries(data.entries()) + const result = registerFormSchema.safeParse(formData) + + if (!result.success) { + return { error: 'Invalid form data' } + } + + // Check for existing users (with the same email) + try { + const existingUsers = await fetchZitadelUsers(result.data.email) + if (existingUsers.result.length > 0) { + return { + error: 'An user with this e-mail already exists', + field: 'email', + } + } + } catch (e) { + console.error(e) + return { error: 'Failed to check for existing users' } + } + + // Create a new user + let user: AddHumanUserResponse + try { + user = await createZitadelUser(result.data) + } catch (e) { + console.error(e) + return { error: 'Failed to create user' } + } + + // Create a session for the user + let session: CreateSessionResponse + try { + session = await createZitadelSession({ + type: 'password', + email: result.data.email, + password: result.data.password, + }) + } catch (e) { + console.error(e) + return { error: 'Failed to create session' } + } + + // Create a session in the app (sets cookies) + await createSession({ + session: { + id: session.sessionId, + token: session.sessionToken, + }, + user: { + id: user.userId, + email: { + email: result.data.email, + isVerified: false, + }, + }, + }) + + redirect('/portal/verify') + + return { success: true } +} + +async function fetchZitadelUsers(email: string) { + const userServiceClient = getUserServiceClient() + const existingUsers = await userServiceClient.listUsers( + { + queries: [ + { + $typeName: 'zitadel.user.v2.SearchQuery', + query: { + case: 'emailQuery', + value: { + $typeName: 'zitadel.user.v2.EmailQuery', + method: 0, // Equals + emailAddress: email, + }, + }, + }, + ], + }, + {}, + ) + + return existingUsers +} + +async function createZitadelUser(data: z.infer) { + const userServiceClient = getUserServiceClient() + const user = await userServiceClient.addHumanUser({ + profile: { + givenName: data.email, + familyName: 'Password', + }, + email: { + email: data.email, + verification: { + case: 'sendCode', + value: {}, + // TODO: Template + }, + }, + passwordType: { + case: 'password', + value: { + password: data.password, + }, + }, + }) + + return user +} diff --git a/apps/web/src/app/portal/(unauthenticated)/register/_common/ui/register-form-schema.ts b/apps/web/src/app/portal/(unauthenticated)/register/_common/ui/register-form-schema.ts new file mode 100644 index 0000000000..27ebddfca0 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/register/_common/ui/register-form-schema.ts @@ -0,0 +1,24 @@ +import { zPassword } from 'src/app/portal/_common/lib/zod' +import { z } from 'zod' + +export const registerFormSchema = z + .object({ + email: z.string().email(), + password: zPassword, + passwordConfirmation: zPassword, + }) + .superRefine((data, ctx) => { + if ( + data.password && + data.passwordConfirmation && + data.password !== data.passwordConfirmation + ) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: 'Passwords do not match', + path: ['passwordConfirmation'], + }) + } + + return data + }) diff --git a/apps/web/src/app/portal/(unauthenticated)/register/_common/ui/register-form.tsx b/apps/web/src/app/portal/(unauthenticated)/register/_common/ui/register-form.tsx new file mode 100644 index 0000000000..5874902661 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/register/_common/ui/register-form.tsx @@ -0,0 +1,164 @@ +'use client' + +import { zodResolver } from '@hookform/resolvers/zod' +import { + Button, + FormControl, + FormField, + FormItem, + FormLabel, + TextField, + formClassnames, +} from '@sushiswap/ui' +import { useCallback, useEffect, useState } from 'react' +import { FormProvider, useForm } from 'react-hook-form' +import type { z } from 'zod' +import { registerAction } from './register-action' +import { registerFormSchema } from './register-form-schema' + +type RegisterFormValues = z.infer + +export function RegisterForm() { + const [globalErrorMsg, setGlobalErrorMsg] = useState(null) + + const form = useForm({ + defaultValues: { + email: '', + password: '', + passwordConfirmation: '', + }, + mode: 'onBlur', + resolver: zodResolver(registerFormSchema), + }) + + useEffect(() => { + if (form.getValues('password')) { + form.trigger(['password']) + } + if (form.getValues('passwordConfirmation')) { + form.trigger(['passwordConfirmation']) + } + }, [ + form.getValues, + form.trigger, + ...form.watch(['password', 'passwordConfirmation']), + ]) + + const onSubmit = useCallback( + async (values: RegisterFormValues) => { + const formData = new FormData() + formData.append('email', values.email) + formData.append('password', values.password) + formData.append('passwordConfirmation', values.passwordConfirmation) + + const result = await registerAction(formData) + + if ('error' in result) { + if ('field' in result) { + form.setError(result.field, { message: result.error }) + } else { + setGlobalErrorMsg(result.error) + } + } + }, + [form.setError], + ) + + const isPending = form.formState.isSubmitting + + return ( +
+ +
+
+ ( + + + <> + E-mail + onChange(e.target.value)} + onBlur={onBlur} + className={formClassnames({ isError })} + disabled={isPending} + /> + + + + )} + /> + ( + + + <> + Password + onChange(e.target.value)} + onBlur={onBlur} + className={formClassnames({ isError })} + disabled={isPending} + /> + + + + )} + /> + ( + + + <> + Repeat Password + onChange(e.target.value)} + onBlur={onBlur} + className={formClassnames({ isError })} + disabled={isPending} + /> + + + + )} + /> +
+ + {globalErrorMsg && ( +
+ {globalErrorMsg} +
+ )} +
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(unauthenticated)/register/layout.tsx b/apps/web/src/app/portal/(unauthenticated)/register/layout.tsx new file mode 100644 index 0000000000..e90ecca339 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/register/layout.tsx @@ -0,0 +1,9 @@ +import { Container } from '@sushiswap/ui' + +export default function Layout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ) +} diff --git a/apps/web/src/app/portal/(unauthenticated)/register/page.tsx b/apps/web/src/app/portal/(unauthenticated)/register/page.tsx new file mode 100644 index 0000000000..59a4db5d05 --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/register/page.tsx @@ -0,0 +1,27 @@ +import { Separator } from '@sushiswap/ui' +import { OAuthButton, OAuthProvider } from '../../_common/ui/oauth/oauth-button' +import { RegisterForm } from './_common/ui/register-form' + +export default function Page() { + return ( +
+

Register

+ + + +
+ + +
+
+
+ ) +} diff --git a/apps/web/src/app/portal/(unauthenticated)/reset-password/page.tsx b/apps/web/src/app/portal/(unauthenticated)/reset-password/page.tsx new file mode 100644 index 0000000000..c3a3e69ade --- /dev/null +++ b/apps/web/src/app/portal/(unauthenticated)/reset-password/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
reset password
+} diff --git a/apps/web/src/app/portal/_common/lib/auth-env.ts b/apps/web/src/app/portal/_common/lib/auth-env.ts new file mode 100644 index 0000000000..3c1fb28896 --- /dev/null +++ b/apps/web/src/app/portal/_common/lib/auth-env.ts @@ -0,0 +1,11 @@ +import { z } from 'zod' + +export const authEnv = z + .object({ + AUTH_SESSION_SECRET: z.string(), + ZITADEL_ISSUER: z.string(), + ZITADEL_CLIENT_ID: z.string(), + ZITADEL_CLIENT_SECRET: z.string(), + ZITADEL_SA_TOKEN: z.string(), + }) + .parse(process.env) diff --git a/apps/web/src/app/portal/_common/lib/client-config.ts b/apps/web/src/app/portal/_common/lib/client-config.ts new file mode 100644 index 0000000000..40f655f5d4 --- /dev/null +++ b/apps/web/src/app/portal/_common/lib/client-config.ts @@ -0,0 +1,71 @@ +import { + type IronSession, + type SessionOptions, + getIronSession, +} from 'iron-session' +import { cookies } from 'next/headers' +import { authEnv } from './auth-env' + +export type ActiveSession = { + isLoggedIn: true + session: { + id: string + token: string + } + user: { + id: string + email: { + email: string + isVerified: boolean + } + } +} + +export type Session = ActiveSession | { isLoggedIn: false } + +export const defaultSession: Session = { isLoggedIn: false } + +export const sessionOptions: SessionOptions = { + password: authEnv.AUTH_SESSION_SECRET, + cookieName: 'portal-session', + cookieOptions: { + secure: process.env.NODE_ENV === 'production', + sameSite: 'lax', + maxAge: 60 * 60 * 24 * 7, + }, + ttl: 60 * 60 * 24 * 7, +} + +export async function createSession(data: Omit) { + const session = await getIronSession( + await cookies(), + sessionOptions, + ) + + session.isLoggedIn = true + session.session = data.session + session.user = data.user + + await session.save() +} + +export async function getSession< + T extends Partial = Session, +>(): Promise> { + const cookiez = await cookies() + const session = await getIronSession(cookiez, sessionOptions) + + if (!session.isLoggedIn) { + session.isLoggedIn = false + } + + return session +} + +export async function getSessionData< + T extends Partial = Session, +>(): Promise { + const session = await getSession() + + return JSON.parse(JSON.stringify(session)) +} diff --git a/apps/web/src/app/portal/_common/lib/get-idp-intent.ts b/apps/web/src/app/portal/_common/lib/get-idp-intent.ts new file mode 100644 index 0000000000..a47cb0242d --- /dev/null +++ b/apps/web/src/app/portal/_common/lib/get-idp-intent.ts @@ -0,0 +1,122 @@ +import { authEnv } from 'src/app/portal/_common/lib/auth-env' +import { z } from 'zod' + +const googleSchema = z.object({ + User: z.object({ + email: z.string(), + given_name: z.string(), + name: z.string(), + }), +}) + +const githubSchema = z.object({ + email: z.string(), + name: z.string(), +}) + +const schema = z + .object({ + details: z.object({ + sequence: z.string(), + changeDate: z.string(), + resourceOwner: z.string(), + }), + idpInformation: z.object({ + oauth: z + .object({ + accessToken: z.string(), + idToken: z.string().optional(), + }) + .nullable(), + ldap: z.object({}).optional(), + saml: z.object({}).optional(), + idpId: z.string(), + userId: z.string(), + userName: z.string(), + rawInformation: googleSchema.or(githubSchema.or(z.object({}))), + }), + userId: z.string().optional(), + }) + .transform((data) => { + const rawInfo = data.idpInformation.rawInformation + + let userData: + | { + email: string + familyName: string + givenName: string + name: string + } + | undefined = undefined + + // Google + if ('User' in rawInfo) { + userData = { + email: rawInfo.User.email, + familyName: 'Google', + givenName: rawInfo.User.given_name, + name: rawInfo.User.name, + } + } + + // Github + if ('email' in rawInfo) { + userData = { + email: rawInfo.email, + familyName: 'GitHub', + givenName: rawInfo.name, + name: rawInfo.name, + } + } + + if (!userData) { + throw new Error('Invalid raw information') + } + + return { + details: { + sequence: data.details.sequence, + changeDate: data.details.changeDate, + resourceOwner: data.details.resourceOwner, + }, + idpInformation: { + oauth: data.idpInformation.oauth, + ldap: data.idpInformation.ldap, + saml: data.idpInformation.saml, + idpId: data.idpInformation.idpId, + userId: data.idpInformation.userId, + userName: data.idpInformation.userName, + rawInformation: userData, + }, + userId: data.userId, + } + }) + +export type IdpIntent = z.infer + +export async function getIdpIntent(id: string, token: string) { + const response = await fetch( + `${authEnv.ZITADEL_ISSUER}/v2/idp_intents/${id}`, + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Accept: 'application/json', + Authorization: `Bearer ${authEnv.ZITADEL_SA_TOKEN}`, + }, + body: JSON.stringify({ + idpIntentToken: token, + }), + }, + ) + + const data = await response.json() + + const result = schema.safeParse(data) + + if (!result.success) { + throw new Error(`Couldn't get intent`) + } + + return result.data +} diff --git a/apps/web/src/app/portal/_common/lib/get-new-idp-intent.ts b/apps/web/src/app/portal/_common/lib/get-new-idp-intent.ts new file mode 100644 index 0000000000..03b2b29dfc --- /dev/null +++ b/apps/web/src/app/portal/_common/lib/get-new-idp-intent.ts @@ -0,0 +1,78 @@ +import { headers } from 'next/headers' +import { authEnv } from 'src/app/portal/_common/lib/auth-env' +import { z } from 'zod' + +export type GetIdpIntentConfig = + | { + type: 'login' + } + | { + type: 'connect' + redirect: string + } + +const newIdpIntentSchema = z.object({ + details: z.object({ + sequence: z.string(), + changeDate: z.string(), + resourceOwner: z.string(), + }), + authUrl: z.string(), +}) + +function getSuccessUrl({ + host, + proto, + config, +}: { + host: string + proto: string + config: GetIdpIntentConfig +}) { + let url = `${proto}://${host}/portal/api/auth` + + switch (config.type) { + case 'login': { + url += '/callback' + break + } + case 'connect': { + url += `/callback-connect?redirect=${config.redirect}` + break + } + } + + return url +} + +export async function getNewIdpIntent({ + idpId, + config, +}: { + idpId: string + config: GetIdpIntentConfig +}) { + const headers_ = await headers() + const host = headers_.get('Host')! + const proto = headers_.get('X-Forwarded-Proto') || 'https' + + const response = await fetch(`${authEnv.ZITADEL_ISSUER}/v2/idp_intents`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: `Bearer ${authEnv.ZITADEL_SA_TOKEN}`, + }, + body: JSON.stringify({ + idpId, + urls: { + successUrl: getSuccessUrl({ host, proto, config }), + failureUrl: `${proto}://${host}/portal`, + }, + }), + }) + + const data = await response.json() + + return newIdpIntentSchema.parse(data) +} diff --git a/apps/web/src/app/portal/_common/lib/logout-action.ts b/apps/web/src/app/portal/_common/lib/logout-action.ts new file mode 100644 index 0000000000..422d1524fb --- /dev/null +++ b/apps/web/src/app/portal/_common/lib/logout-action.ts @@ -0,0 +1,33 @@ +'use server' + +import { redirect } from 'next/navigation' +import { getSession } from 'src/app/portal/_common/lib/client-config' +import { getSessionServiceClient } from './zitadel-client' + +export async function logoutAction() { + const session = await getSession() + + if (!session.isLoggedIn) { + redirect('/portal') + return + } + + try { + const sessionServiceClient = getSessionServiceClient() + await sessionServiceClient.deleteSession( + { + $typeName: 'zitadel.session.v2.DeleteSessionRequest', + sessionId: session.session.id, + sessionToken: session.session.token, + }, + { headers: { Authorization: '' } }, + ) + } catch (e) { + console.error(e) + } + + session.destroy() + + redirect('/portal') + return +} diff --git a/apps/web/src/app/portal/_common/lib/zitadel-client.ts b/apps/web/src/app/portal/_common/lib/zitadel-client.ts new file mode 100644 index 0000000000..6735c49b05 --- /dev/null +++ b/apps/web/src/app/portal/_common/lib/zitadel-client.ts @@ -0,0 +1,40 @@ +import { createServerTransport } from '@zitadel/client/node' +import { + createSessionServiceClient, + createUserServiceClient, +} from '@zitadel/client/v2' +import { authEnv } from 'src/app/portal/_common/lib/auth-env' + +let serverTransport: ReturnType | undefined + +export function getServerTransport() { + if (!serverTransport) { + serverTransport = createServerTransport(authEnv.ZITADEL_SA_TOKEN, { + baseUrl: authEnv.ZITADEL_ISSUER, + }) + } + + return serverTransport +} + +let userServiceClient: ReturnType | undefined + +export function getUserServiceClient() { + if (!userServiceClient) { + userServiceClient = createUserServiceClient(getServerTransport()) + } + + return userServiceClient +} + +let sessionServiceClient: + | ReturnType + | undefined + +export function getSessionServiceClient() { + if (!sessionServiceClient) { + sessionServiceClient = createSessionServiceClient(getServerTransport()) + } + + return sessionServiceClient +} diff --git a/apps/web/src/app/portal/_common/lib/zod.ts b/apps/web/src/app/portal/_common/lib/zod.ts new file mode 100644 index 0000000000..b2e88677eb --- /dev/null +++ b/apps/web/src/app/portal/_common/lib/zod.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +export const zPassword = z + .string() + .min(8, { message: 'Minimum password length is 8 characters.' }) + .max(64, { message: 'Maximum password length is 64 characters.' }) + .regex(/[a-z]/, { + message: 'Use at least one lowercase letter.', + }) + .regex(/[A-Z]/, { + message: 'Use at least one uppercase letter.', + }) + .regex(/[0-9]/, { message: 'Use at least one digit.' }) diff --git a/apps/web/src/app/portal/_common/ui/auth-provider/auth-provider.tsx b/apps/web/src/app/portal/_common/ui/auth-provider/auth-provider.tsx new file mode 100644 index 0000000000..f78dde6975 --- /dev/null +++ b/apps/web/src/app/portal/_common/ui/auth-provider/auth-provider.tsx @@ -0,0 +1,33 @@ +'use client' + +import { createContext, useContext, useEffect, useState } from 'react' +import type { Session } from '../../lib/client-config' + +const AuthContext = createContext({ isLoggedIn: false }) + +interface AuthProvider { + children: React.ReactNode + initialSession: Session +} + +export function AuthProvider({ initialSession, children }: AuthProvider) { + const [session, setSession] = useState(initialSession) + + useEffect(() => { + if (session !== initialSession) { + setSession(initialSession) + } + }, [session, initialSession]) + + return {children} +} + +export function useSession() { + const session = useContext(AuthContext) + + if (!session) { + throw new Error('useSession must be used within an AuthProvider') + } + + return session +} diff --git a/apps/web/src/app/portal/_common/ui/header/header-profile.tsx b/apps/web/src/app/portal/_common/ui/header/header-profile.tsx new file mode 100644 index 0000000000..a74525b4c1 --- /dev/null +++ b/apps/web/src/app/portal/_common/ui/header/header-profile.tsx @@ -0,0 +1,48 @@ +'use client' + +import { + Button, + Popover, + PopoverContent, + PopoverTrigger, + Separator, +} from '@sushiswap/ui' +import Link from 'next/link' +import { logoutAction } from 'src/app/portal/_common/lib/logout-action' +import { useSession } from '../auth-provider/auth-provider' + +export function HeaderProfile() { + const session = useSession() + + if (!session.isLoggedIn) { + return ( +
+ + + + + + +
+ ) + } + + return ( + + + + + +
Dunno something
+ +
+ +
+
+
+ ) +} diff --git a/apps/web/src/app/portal/_common/ui/header/header.tsx b/apps/web/src/app/portal/_common/ui/header/header.tsx new file mode 100644 index 0000000000..9956895784 --- /dev/null +++ b/apps/web/src/app/portal/_common/ui/header/header.tsx @@ -0,0 +1,54 @@ +import { Navigation, NavigationElementType } from '@sushiswap/ui' +import { SushiIcon } from '@sushiswap/ui/icons/SushiIcon' +import { getSessionData } from '../../lib/client-config' +import { HeaderProfile } from './header-profile' + +export async function Header() { + const session = await getSessionData() + + return ( +
+ , + href: '/portal', + show: 'everywhere', + type: NavigationElementType.Custom, + }, + ...(session.isLoggedIn && session.user.email.isVerified + ? [ + { + title: 'Dashboard', + href: '/portal/dashboard', + show: 'everywhere', + type: NavigationElementType.Single, + } as const, + ] + : []), + { + title: 'Pricing', + href: '/portal/pricing', + show: 'everywhere', + type: NavigationElementType.Single, + }, + { + title: 'Docs', + href: 'https://docs.sushi.com', + show: 'everywhere', + type: NavigationElementType.Single, + }, + { + title: 'Support', + href: 'https://sushi.com', + show: 'everywhere', + type: NavigationElementType.Single, + }, + ]} + rightElement={} + /> +
+ ) +} diff --git a/apps/web/src/app/portal/_common/ui/oauth/oauth-button.tsx b/apps/web/src/app/portal/_common/ui/oauth/oauth-button.tsx new file mode 100644 index 0000000000..fac7da8c74 --- /dev/null +++ b/apps/web/src/app/portal/_common/ui/oauth/oauth-button.tsx @@ -0,0 +1,64 @@ +import { Button, type IconComponent } from '@sushiswap/ui' +import { GithubIcon } from '@sushiswap/ui/icons/GithubIcon' +import { GoogleIcon } from '@sushiswap/ui/icons/GoogleIcon' +import { + type GetIdpIntentConfig, + getNewIdpIntent, +} from '../../lib/get-new-idp-intent' + +export enum OAuthProvider { + Github = 0, + Google = 1, +} + +export const OAuthId: Record = { + [OAuthProvider.Github]: '299270310144770125', + [OAuthProvider.Google]: '299271776439894093', +} + +const OAuthIcon: Record = { + [OAuthProvider.Github]: GithubIcon, + [OAuthProvider.Google]: GoogleIcon, +} + +interface OAuthButton { + provider: OAuthProvider + text: string + disabled?: boolean + config: GetIdpIntentConfig +} + +export const dynamic = false + +export async function OAuthButton({ + provider, + text, + disabled = false, + config, +}: OAuthButton) { + const idpIntent = await getNewIdpIntent({ + idpId: OAuthId[provider], + config, + }) + + const Icon = OAuthIcon[provider] + const Comp = ( + + ) + + if (disabled) return Comp + + return ( + + {Comp} + + ) +} diff --git a/apps/web/src/app/portal/layout.tsx b/apps/web/src/app/portal/layout.tsx new file mode 100644 index 0000000000..fe33591171 --- /dev/null +++ b/apps/web/src/app/portal/layout.tsx @@ -0,0 +1,18 @@ +import { getSessionData } from './_common/lib/client-config' +import { Header } from './_common/ui/header/header' +import { Providers } from './providers' + +export default async function Layout({ + children, +}: { children: React.ReactNode }) { + const authSession = await getSessionData() + + console.log(authSession) + + return ( + +
+ {children} + + ) +} diff --git a/apps/web/src/app/portal/middleware.ts b/apps/web/src/app/portal/middleware.ts new file mode 100644 index 0000000000..c965ca39f2 --- /dev/null +++ b/apps/web/src/app/portal/middleware.ts @@ -0,0 +1,22 @@ +import { type NextRequest, NextResponse } from 'next/server' +import { getSessionData } from './_common/lib/client-config' + +export async function portalMiddleware(request: NextRequest) { + const session = await getSessionData() + + if (!session.isLoggedIn) { + return + } + + if (!session.user.email.isVerified) { + console.log(request.nextUrl) + if (request.nextUrl.pathname !== '/portal/verify') { + console.log('redirect') + return NextResponse.redirect( + `${request.nextUrl.protocol}/${request.nextUrl.host}/portal/verify`, + ) + } + } + + return +} diff --git a/apps/web/src/app/portal/page.tsx b/apps/web/src/app/portal/page.tsx new file mode 100644 index 0000000000..1506db0a74 --- /dev/null +++ b/apps/web/src/app/portal/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return <>A +} diff --git a/apps/web/src/app/portal/pricing/layout.tsx b/apps/web/src/app/portal/pricing/layout.tsx new file mode 100644 index 0000000000..119abf12e9 --- /dev/null +++ b/apps/web/src/app/portal/pricing/layout.tsx @@ -0,0 +1,3 @@ +export default function Layout({ children }: { children: React.ReactNode }) { + return <>{children} +} diff --git a/apps/web/src/app/portal/pricing/page.tsx b/apps/web/src/app/portal/pricing/page.tsx new file mode 100644 index 0000000000..f2b49ddee9 --- /dev/null +++ b/apps/web/src/app/portal/pricing/page.tsx @@ -0,0 +1,3 @@ +export default function Page() { + return
Pricing
+} diff --git a/apps/web/src/app/portal/providers.tsx b/apps/web/src/app/portal/providers.tsx new file mode 100644 index 0000000000..a5fb2066a9 --- /dev/null +++ b/apps/web/src/app/portal/providers.tsx @@ -0,0 +1,18 @@ +'use client' + +import { QueryClientProvider } from 'src/providers/query-client-provider' +import type { Session } from './_common/lib/client-config' +import { AuthProvider } from './_common/ui/auth-provider/auth-provider' + +interface Providers { + children: React.ReactNode + authSession: Session +} + +export function Providers({ children, authSession }: Providers) { + return ( + + {children} + + ) +} diff --git a/apps/web/src/app/tokenlist-request/approved/page.tsx b/apps/web/src/app/tokenlist-request/approved/page.tsx index e577056b2b..c39acd993a 100644 --- a/apps/web/src/app/tokenlist-request/approved/page.tsx +++ b/apps/web/src/app/tokenlist-request/approved/page.tsx @@ -27,7 +27,7 @@ const COLUMNS: ColumnDef[] = [ header: 'Network', accessorFn: (row) => row.chainId, cell: (props) => EvmChain.from(props.row.original.chainId)?.name, - meta: { skeleton: }, + meta: { body: { skeleton: } }, }, { id: 'logo', @@ -57,7 +57,9 @@ const COLUMNS: ColumnDef[] = [
), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -66,7 +68,9 @@ const COLUMNS: ColumnDef[] = [ accessorFn: (row) => row.name, cell: (props) => props.row.original.name, meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -75,7 +79,9 @@ const COLUMNS: ColumnDef[] = [ accessorFn: (row) => row.symbol, cell: (props) => props.row.original.symbol, meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -98,7 +104,9 @@ const COLUMNS: ColumnDef[] = [
), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, ] diff --git a/apps/web/src/app/tokenlist-request/pending/page.tsx b/apps/web/src/app/tokenlist-request/pending/page.tsx index 7767dd118b..7614008f5a 100644 --- a/apps/web/src/app/tokenlist-request/pending/page.tsx +++ b/apps/web/src/app/tokenlist-request/pending/page.tsx @@ -79,7 +79,9 @@ const COLUMNS: ColumnDef[] = [
), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -99,7 +101,9 @@ const COLUMNS: ColumnDef[] = [ /> ), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -108,7 +112,9 @@ const COLUMNS: ColumnDef[] = [ accessorFn: (row) => row.metrics.marketcapUSD, cell: (props) => formatUSD(props.row.original.metrics.marketcapUSD), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -117,7 +123,9 @@ const COLUMNS: ColumnDef[] = [ accessorFn: (row) => row.metrics.volumeUSD24h, cell: (props) => formatUSD(props.row.original.metrics.volumeUSD24h), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -130,7 +138,9 @@ const COLUMNS: ColumnDef[] = [ new Date(props.row.original.createdAt * 1000), )} days`, meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -139,7 +149,9 @@ const COLUMNS: ColumnDef[] = [ accessorFn: (row) => row.metrics.holders, cell: (props) => formatNumber(props.row.original.metrics.holders), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -232,7 +244,9 @@ const COLUMNS: ColumnDef[] = [ ) }, meta: { - skeleton: , + body: { + skeleton: , + }, }, }, ] diff --git a/apps/web/src/middleware.ts b/apps/web/src/middleware.ts index a8fbb05eed..73443c64da 100644 --- a/apps/web/src/middleware.ts +++ b/apps/web/src/middleware.ts @@ -1,6 +1,7 @@ import { type NextRequest, NextResponse } from 'next/server' import { ChainKey, getEvmChainInfo } from 'sushi/chain' import { isSushiSwapChainId } from 'sushi/config' +import { portalMiddleware } from './app/portal/middleware' export const config = { matcher: [ @@ -20,12 +21,17 @@ export const config = { '/:chainId/positions/:path*', '/:chainId/migrate', '/:chainId/rewards', + '/portal/:path*', ], } export async function middleware(req: NextRequest) { const { pathname, searchParams, search } = req.nextUrl + if (pathname === 'portal' || pathname.startsWith('/portal/')) { + return portalMiddleware(req) + } + if ( pathname === '/explore' || pathname === '/pools' || diff --git a/apps/web/src/ui/pathname-button.tsx b/apps/web/src/ui/pathname-button.tsx index a3e70469d6..2b205694e4 100644 --- a/apps/web/src/ui/pathname-button.tsx +++ b/apps/web/src/ui/pathname-button.tsx @@ -27,4 +27,4 @@ const PathnameButton = forwardRef( PathnameButton.displayName = 'PathnameButton' -export { PathnameButton } +export type { PathnameButton } diff --git a/apps/web/src/ui/pool/ConcentratedPositionsTable/Tables/Smart/columns/index.tsx b/apps/web/src/ui/pool/ConcentratedPositionsTable/Tables/Smart/columns/index.tsx index 72080c0b74..618253c970 100644 --- a/apps/web/src/ui/pool/ConcentratedPositionsTable/Tables/Smart/columns/index.tsx +++ b/apps/web/src/ui/pool/ConcentratedPositionsTable/Tables/Smart/columns/index.tsx @@ -118,17 +118,19 @@ export const STEER_NAME_COLUMN: ColumnDef< ) }, meta: { - skeleton: ( -
-
- - -
-
- + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- ), + ), + }, }, size: 300, } @@ -141,11 +143,13 @@ export const STEER_STRATEGY_COLUMN: ColumnDef< header: 'Strategy', cell: (props) => , meta: { - skeleton: ( -
- -
- ), + body: { + skeleton: ( +
+ +
+ ), + }, }, size: 300, } @@ -159,6 +163,8 @@ export const STEER_POSITION_SIZE_COLUMN: ColumnDef< accessorFn: (row) => row.totalAmountUSD ?? 0, cell: (props) => `$${formatNumber(props.row.original.totalAmountUSD)}`, meta: { - skeleton: , + body: { + skeleton: , + }, }, } diff --git a/apps/web/src/ui/pool/DistributionDataTable.tsx b/apps/web/src/ui/pool/DistributionDataTable.tsx index df4cb64c56..68824e8954 100644 --- a/apps/web/src/ui/pool/DistributionDataTable.tsx +++ b/apps/web/src/ui/pool/DistributionDataTable.tsx @@ -43,9 +43,13 @@ const COLUMNS = [ ) }, meta: { - skeleton: , - headerDescription: - 'The amount of tokens that gets distributed per day to everyone that provided liquidity to this pool.', + body: { + skeleton: , + }, + header: { + description: + 'The amount of tokens that gets distributed per day to everyone that provided liquidity to this pool.', + }, }, }, { @@ -70,7 +74,9 @@ const COLUMNS = [ ) }, meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -95,7 +101,9 @@ const COLUMNS = [ ) }, meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -107,8 +115,12 @@ const COLUMNS = [ return }, meta: { - skeleton: , - headerDescription: 'Only rewards in-range positions', + body: { + skeleton: , + }, + header: { + description: 'Only rewards in-range positions', + }, }, }, ] satisfies ColumnDef[] diff --git a/apps/web/src/ui/pool/PoolsTable.tsx b/apps/web/src/ui/pool/PoolsTable.tsx index f96a5cd4dc..2f1fb75e04 100644 --- a/apps/web/src/ui/pool/PoolsTable.tsx +++ b/apps/web/src/ui/pool/PoolsTable.tsx @@ -254,7 +254,9 @@ const COLUMNS = [ size: 80, meta: { disableLink: true, - skeleton: , + body: { + skeleton: , + }, }, } satisfies ColumnDef, ] as ColumnDef[] diff --git a/apps/web/src/ui/pool/SmartPoolsTable.tsx b/apps/web/src/ui/pool/SmartPoolsTable.tsx index 2e1ff84e40..6312039049 100644 --- a/apps/web/src/ui/pool/SmartPoolsTable.tsx +++ b/apps/web/src/ui/pool/SmartPoolsTable.tsx @@ -164,17 +164,19 @@ const COLUMNS = [ ) }, meta: { - skeleton: ( -
-
- - -
-
- + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- ), + ), + }, }, size: 300, }, @@ -196,7 +198,9 @@ const COLUMNS = [ ), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -227,7 +231,9 @@ const COLUMNS = [ ), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -239,7 +245,9 @@ const COLUMNS = [ ? '$0.00' : formatUSD(props.row.original.feeUSD1d), meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -282,7 +290,9 @@ const COLUMNS = [ ) }, meta: { - skeleton: , + body: { + skeleton: , + }, }, }, { @@ -461,7 +471,9 @@ const COLUMNS = [ ), meta: { disableLink: true, - skeleton: , + body: { + skeleton: , + }, }, }, ] satisfies ColumnDef[] diff --git a/apps/web/src/ui/pool/SmartPositionsTable.tsx b/apps/web/src/ui/pool/SmartPositionsTable.tsx index 8af8b8e7f3..9ef23563bf 100644 --- a/apps/web/src/ui/pool/SmartPositionsTable.tsx +++ b/apps/web/src/ui/pool/SmartPositionsTable.tsx @@ -69,7 +69,9 @@ const COLUMNS = [ ) }, meta: { - skeleton: , + body: { + skeleton: , + }, }, }, ] satisfies ColumnDef[] diff --git a/apps/web/src/ui/pool/Steer/SteerLiquidityManagement/Add/SteerPositionZap.tsx b/apps/web/src/ui/pool/Steer/SteerLiquidityManagement/Add/SteerPositionZap.tsx index 233b0bf3b3..8641f7ddc2 100644 --- a/apps/web/src/ui/pool/Steer/SteerLiquidityManagement/Add/SteerPositionZap.tsx +++ b/apps/web/src/ui/pool/Steer/SteerLiquidityManagement/Add/SteerPositionZap.tsx @@ -23,9 +23,9 @@ import { useApproved, } from 'src/lib/wagmi/systems/Checker/Provider' import { ZapInfoCard } from 'src/ui/pool/ZapInfoCard' +import { Percent } from 'sushi' import { defaultCurrency, isWNativeSupported } from 'sushi/config' import { Amount, type Type, tryParseAmount } from 'sushi/currency' -import { Percent } from 'sushi/math' import type { SendTransactionReturnType } from 'viem' import { useAccount, diff --git a/apps/web/src/ui/pool/V3FeesTable.tsx b/apps/web/src/ui/pool/V3FeesTable.tsx index fd0c0a9fb1..de26884a18 100644 --- a/apps/web/src/ui/pool/V3FeesTable.tsx +++ b/apps/web/src/ui/pool/V3FeesTable.tsx @@ -104,17 +104,19 @@ const NAME_COLUMN_POOL: ColumnDef = {
), meta: { - skeleton: ( -
-
- - + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- -
-
- ), + ), + }, }, size: 300, } @@ -130,7 +132,9 @@ const TVL_COLUMN: ColumnDef = { ? '$0.00' : formatUSD(props.row.original.liquidityUSD), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -145,7 +149,9 @@ const VOLUME_COLUMN: ColumnDef = { ? '$0.00' : formatUSD(props.row.original.volumeUSD), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -208,7 +214,9 @@ const PROTOCOL_FEE_COLUMN: ColumnDef = {
), meta: { - skeleton: , + body: { + skeleton: , + }, }, } diff --git a/apps/web/src/ui/pool/columns.tsx b/apps/web/src/ui/pool/columns.tsx index 30c1f9a435..b233088673 100644 --- a/apps/web/src/ui/pool/columns.tsx +++ b/apps/web/src/ui/pool/columns.tsx @@ -60,17 +60,19 @@ export const REWARDS_V3_NAME_COLUMN: ColumnDef = { header: 'Pool', cell: (props) => , meta: { - skeleton: ( -
-
- - -
-
- + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- ), + ), + }, }, } @@ -83,7 +85,9 @@ export const REWARDS_V3_POSITION_SIZE_COLUMN: ColumnDef< accessorFn: (row) => row.userTVL ?? 0, cell: (props) => `$${formatNumber(props.row.original.userTVL || 0)}`, meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -114,7 +118,9 @@ export const REWARDS_V3_APR_COLUMN: ColumnDef = { ), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -125,7 +131,9 @@ export const REWARDS_V3_CLAIMABLE_COLUMN: ColumnDef = accessorFn: (row) => row.userTVL ?? 0, cell: (props) => , meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -138,17 +146,19 @@ export const NAME_COLUMN_POOL: ColumnDef< cell: (props) => , meta: { - skeleton: ( -
-
- - -
-
- + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- ), + ), + }, }, size: 300, } @@ -261,17 +271,19 @@ export const EXPLORE_NAME_COLUMN_POOL: ColumnDef = { }, size: 300, meta: { - skeleton: ( -
-
- - -
-
- + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- ), + ), + }, }, } @@ -304,7 +316,9 @@ export const TVL_COLUMN: ColumnDef = {
), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -337,7 +351,9 @@ export const VOLUME_1D_COLUMN: ColumnDef = {
), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -370,7 +386,9 @@ export const VOLUME_1W_COLUMN: ColumnDef = {
), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -382,7 +400,9 @@ export const TRANSACTIONS_1D_COLUMN: ColumnDef = { rowA.txCount1d - rowB.txCount1d, cell: (props) => props.row.original.txCount1d, meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -412,7 +432,9 @@ export const APR_WITH_REWARDS_COLUMN: ColumnDef = { ), meta: { - skeleton: , + body: { + skeleton: , + }, disableLink: true, }, } @@ -431,7 +453,9 @@ export const APR_COLUMN = { ), meta: { - skeleton: , + body: { + skeleton: , + }, }, } as const satisfies ColumnDef< MaybeNestedPool>, @@ -455,7 +479,9 @@ export const VALUE_COLUMN = { ), meta: { - skeleton: , + body: { + skeleton: , + }, }, } as const satisfies ColumnDef< SushiPositionWithPool, @@ -470,17 +496,19 @@ export const NAME_COLUMN_V3: ColumnDef< header: 'Name', cell: (props) => , meta: { - skeleton: ( -
-
- - -
-
- + body: { + skeleton: ( +
+
+ + +
+
+ +
-
- ), + ), + }, }, } @@ -492,7 +520,9 @@ export const PRICE_RANGE_COLUMN: ColumnDef< header: 'Price Range', cell: (props) => , meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -504,7 +534,9 @@ export const CLIQ_APR_COLUMN: ColumnDef< header: 'Price Range', cell: (props) => , meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -517,7 +549,9 @@ export const POSITION_SIZE_CELL: ColumnDef< header: 'Position Size', cell: (props) => formatUSD(props.row.original.position.positionUSD), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -530,7 +564,9 @@ export const POSITION_UNCLAIMED_CELL: ColumnDef< header: 'Unclaimed fees', cell: (props) => formatUSD(props.row.original.position.unclaimedUSD), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -539,7 +575,9 @@ export const TX_SENDER_V2_COLUMN: ColumnDef = { header: 'Maker', cell: (props) => shortenAddress(props.row.original.sender), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -576,7 +614,9 @@ export const TX_AMOUNT_IN_V2_COLUMN = ( } }, meta: { - skeleton: , + body: { + skeleton: , + }, }, }) @@ -613,7 +653,9 @@ export const TX_AMOUNT_OUT_V2_COLUMN = ( } }, meta: { - skeleton: , + body: { + skeleton: , + }, }, }) @@ -622,7 +664,9 @@ export const TX_AMOUNT_USD_V2_COLUMN: ColumnDef = { header: 'Amount (USD)', cell: (props) => formatUSD(props.row.original.amountUSD), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -634,7 +678,9 @@ export const TX_CREATED_TIME_V2_COLUMN: ColumnDef = { addSuffix: true, }), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -643,7 +689,9 @@ export const TX_TYPE_COLUMN: ColumnDef = { header: 'Type', cell: (props) => TransactionType[props.row.original.type], meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -652,7 +700,9 @@ export const TX_ORIGIN_V3_COLUMN: ColumnDef = { header: 'Maker', cell: (props) => shortenAddress(props.row.original.origin), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -693,7 +743,9 @@ export const TX_AMOUNT_IN_V3_COLUMN = ( } }, meta: { - skeleton: , + body: { + skeleton: , + }, }, }) @@ -734,7 +786,9 @@ export const TX_AMOUNT_OUT_V3_COLUMN = ( } }, meta: { - skeleton: , + body: { + skeleton: , + }, }, }) @@ -743,7 +797,9 @@ export const TX_AMOUNT_USD_V3_COLUMN: ColumnDef = { header: 'Amount (USD)', cell: (props) => formatUSD(props.row.original.amountUSD), meta: { - skeleton: , + body: { + skeleton: , + }, }, } @@ -755,6 +811,8 @@ export const TX_TIME_V3_COLUMN: ColumnDef = { addSuffix: true, }), meta: { - skeleton: , + body: { + skeleton: , + }, }, } diff --git a/biome.json b/biome.json index 744b49b2c8..4c99e7bb8e 100644 --- a/biome.json +++ b/biome.json @@ -16,7 +16,6 @@ "coverage", "tsconfig.json", "tsconfig.*.json", - "packages/graph-client", "apps/web/src/lib/wagmi/hooks/exploits/data/rp2-approvals.json", "**/*-env.d.ts", "**/*-cache.d.ts" diff --git a/packages/graph-client/package.json b/packages/graph-client/package.json index c5bd3a4e2d..bae7816a9c 100644 --- a/packages/graph-client/package.json +++ b/packages/graph-client/package.json @@ -3,11 +3,7 @@ "version": "0.0.0", "private": true, "description": "Graph Client", - "keywords": [ - "sushi", - "graph", - "subgraph" - ], + "keywords": ["sushi", "graph", "subgraph"], "repository": { "type": "git", "url": "https://github.com/sushiswap/sushiswap.git", @@ -36,22 +32,13 @@ }, "typesVersions": { "*": { - ".": [ - "src/subgraphs/**/*", - "src/lib/**/*" - ], - "./multichain": [ - "src/multichain/*" - ], - "./composite": [ - "src/composite/*" - ] + ".": ["src/subgraphs/**/*", "src/lib/**/*"], + "./multichain": ["src/multichain/*"], + "./composite": ["src/composite/*"] } }, "typings": "dist/index.d.ts", - "files": [ - "dist/**" - ], + "files": ["dist/**"], "scripts": { "build": "pnpm build:types && pnpm build:compile", "build:compile": "tsc && tsc-alias -p tsconfig.json", diff --git a/packages/graph-client/src/subgraphs/bentobox/queries/rebases.ts b/packages/graph-client/src/subgraphs/bentobox/queries/rebases.ts index abe75b6612..72d645dd33 100644 --- a/packages/graph-client/src/subgraphs/bentobox/queries/rebases.ts +++ b/packages/graph-client/src/subgraphs/bentobox/queries/rebases.ts @@ -2,15 +2,15 @@ import type { VariablesOf } from 'gql.tada' import type { BentoBoxChainId } from 'sushi/config' import { getBentoBoxSubgraphUrl } from 'sushi/config/subgraph' +import { getSubgraphUrl } from 'src/lib/get-subgraph-url.js' import { addChainId } from 'src/lib/modifiers/add-chain-id.js' import { convertIdToMultichainId } from 'src/lib/modifiers/convert-id-to-multichain-id.js' import { copyIdToAddress } from 'src/lib/modifiers/copy-id-to-address.js' -import { type RequestOptions } from 'src/lib/request.js' import { requestPaged } from 'src/lib/request-paged.js' +import type { RequestOptions } from 'src/lib/request.js' import type { ChainIdVariable } from 'src/lib/types/chainId.js' import type { Hex } from 'src/lib/types/hex.js' import { graphql } from '../graphql.js' -import { getSubgraphUrl } from 'src/lib/get-subgraph-url.js' export const BentoBoxRebasesQuery = graphql(` query Rebases($first: Int = 1000, $skip: Int = 0, $block: Block_height, $orderBy: Rebase_orderBy, $orderDirection: OrderDirection, $where: Rebase_filter) { diff --git a/packages/graph-client/src/subgraphs/bentobox/schema.graphql b/packages/graph-client/src/subgraphs/bentobox/schema.graphql index 6cd26e044c..b37be5db3a 100644 --- a/packages/graph-client/src/subgraphs/bentobox/schema.graphql +++ b/packages/graph-client/src/subgraphs/bentobox/schema.graphql @@ -3,7 +3,9 @@ Marks the GraphQL type as indexable entity. Each type that should be an entity """ directive @entity on OBJECT -"""Defined a Subgraph ID for an object type""" +""" +Defined a Subgraph ID for an object type +""" directive @subgraphId(id: String!) on OBJECT """ @@ -83,7 +85,9 @@ input Balance_filter { share_in: [BigInt!] share_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Balance_filter] or: [Balance_filter] @@ -107,118 +111,239 @@ enum Balance_orderBy { } type BentoBox { - """ BentoBox address """ + """ + BentoBox address + """ id: ID! - """ Protocols that belong to this bento box """ - protocols(skip: Int = 0, first: Int = 100, orderBy: Protocol_orderBy, orderDirection: OrderDirection, where: Protocol_filter): [Protocol!] + """ + Protocols that belong to this bento box + """ + protocols( + skip: Int = 0 + first: Int = 100 + orderBy: Protocol_orderBy + orderDirection: OrderDirection + where: Protocol_filter + ): [Protocol!] - """ Users that belong to this bento box """ - users(skip: Int = 0, first: Int = 100, orderBy: User_orderBy, orderDirection: OrderDirection, where: User_filter): [User!] + """ + Users that belong to this bento box + """ + users( + skip: Int = 0 + first: Int = 100 + orderBy: User_orderBy + orderDirection: OrderDirection + where: User_filter + ): [User!] - """ Tokens that belong to this bento box """ - tokens(skip: Int = 0, first: Int = 100, orderBy: Token_orderBy, orderDirection: OrderDirection, where: Token_filter): [Token!] + """ + Tokens that belong to this bento box + """ + tokens( + skip: Int = 0 + first: Int = 100 + orderBy: Token_orderBy + orderDirection: OrderDirection + where: Token_filter + ): [Token!] - """ Master contracts that belong to this bento box """ - masterContracts(skip: Int = 0, first: Int = 100, orderBy: MasterContract_orderBy, orderDirection: OrderDirection, where: MasterContract_filter): [MasterContract!]! + """ + Master contracts that belong to this bento box + """ + masterContracts( + skip: Int = 0 + first: Int = 100 + orderBy: MasterContract_orderBy + orderDirection: OrderDirection + where: MasterContract_filter + ): [MasterContract!]! - """ Clones that belong to this bento box """ - clones(skip: Int = 0, first: Int = 100, orderBy: Clone_orderBy, orderDirection: OrderDirection, where: Clone_filter): [Clone!] + """ + Clones that belong to this bento box + """ + clones( + skip: Int = 0 + first: Int = 100 + orderBy: Clone_orderBy + orderDirection: OrderDirection + where: Clone_filter + ): [Clone!] - """ Flash loans that belong to this bento box """ - flashloans(skip: Int = 0, first: Int = 100, orderBy: FlashLoan_orderBy, orderDirection: OrderDirection, where: FlashLoan_filter): [FlashLoan!] + """ + Flash loans that belong to this bento box + """ + flashloans( + skip: Int = 0 + first: Int = 100 + orderBy: FlashLoan_orderBy + orderDirection: OrderDirection + where: FlashLoan_filter + ): [FlashLoan!] - """ Transactions that belong to this bento box """ - transactions(skip: Int = 0, first: Int = 100, orderBy: Transaction_orderBy, orderDirection: OrderDirection, where: Transaction_filter): [Transaction!] + """ + Transactions that belong to this bento box + """ + transactions( + skip: Int = 0 + first: Int = 100 + orderBy: Transaction_orderBy + orderDirection: OrderDirection + where: Transaction_filter + ): [Transaction!] - """ Totals that belong to this bento box """ - totals(skip: Int = 0, first: Int = 100, orderBy: Rebase_orderBy, orderDirection: OrderDirection, where: Rebase_filter): [Rebase!] + """ + Totals that belong to this bento box + """ + totals( + skip: Int = 0 + first: Int = 100 + orderBy: Rebase_orderBy + orderDirection: OrderDirection + where: Rebase_filter + ): [Rebase!] } type BentoBoxDailyKpi { - """ created by id, infix '-day-' and unix timestamp, e.g. '0x00..00-day-1657270000' + """ + created by id, infix '-day-' and unix timestamp, e.g. '0x00..00-day-1657270000' """ id: ID! - """ Start date """ + """ + Start date + """ date: Int! - """ Deposit count """ + """ + Deposit count + """ depositCount: BigInt! - """ Deposit count for the given timeframe """ + """ + Deposit count for the given timeframe + """ newDepositCount: BigInt! - """ Withdraw count """ + """ + Withdraw count + """ withdrawCount: BigInt! - """ Withdraw count for the given timeframe """ + """ + Withdraw count for the given timeframe + """ newWithdrawCount: BigInt! - """ Transfer count """ + """ + Transfer count + """ transferCount: BigInt! - """ Transfer count for the given timeframe """ + """ + Transfer count for the given timeframe + """ newTransferCount: BigInt! - """ Protocol count """ + """ + Protocol count + """ protocolCount: BigInt! - """ Protocol count for the given timeframe """ + """ + Protocol count for the given timeframe + """ newProtocolCount: BigInt! - """ User count """ + """ + User count + """ userCount: BigInt! - """ User count for the given timeframe """ + """ + User count for the given timeframe + """ newUserCount: BigInt! - """ Token count """ + """ + Token count + """ tokenCount: BigInt! - """ Token count for the given timeframe """ + """ + Token count for the given timeframe + """ newTokenCount: BigInt! - """ Master contract count """ + """ + Master contract count + """ masterContractCount: BigInt! - """ Master contract count for the given timeframe """ + """ + Master contract count for the given timeframe + """ newMasterContractCount: BigInt! - """ Clone count """ + """ + Clone count + """ cloneCount: BigInt! - """ Clone count for the given timeframe """ + """ + Clone count for the given timeframe + """ newCloneCount: BigInt! - """ Flash loan count """ + """ + Flash loan count + """ flashloanCount: BigInt! - """ Flash loan count for the given timeframe """ + """ + Flash loan count for the given timeframe + """ newFlashloanCount: BigInt! - """ Transaction count """ + """ + Transaction count + """ transactionCount: BigInt! - """ Transaction count for the given timeframe """ + """ + Transaction count for the given timeframe + """ newTransactionCount: BigInt! - """ Strategy count """ + """ + Strategy count + """ strategyCount: BigInt! - """ Strategy for the given timeframe """ + """ + Strategy for the given timeframe + """ newStrategyCount: BigInt! - """ Active strategy count """ + """ + Active strategy count + """ activeStrategyCount: BigInt! - """ Active strategy count for the given timeframe """ + """ + Active strategy count for the given timeframe + """ newActiveStrategyCount: BigInt! - """ Pending strategy count """ + """ + Pending strategy count + """ pendingStrategyCount: BigInt! - """ Pending strategy count for the given timeframe """ + """ + Pending strategy count for the given timeframe + """ newPendingStrategyCount: BigInt! } @@ -448,7 +573,9 @@ input BentoBoxDailyKpi_filter { newPendingStrategyCount_in: [BigInt!] newPendingStrategyCount_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [BentoBoxDailyKpi_filter] or: [BentoBoxDailyKpi_filter] @@ -486,89 +613,144 @@ enum BentoBoxDailyKpi_orderBy { } type BentoBoxHourlyKpi { - """ created by a prefix 'bentobox-hour-' and unix timestamp, e.g. 'bentobox-hour-1657270000' + """ + created by a prefix 'bentobox-hour-' and unix timestamp, e.g. 'bentobox-hour-1657270000' """ id: ID! - """ Start date """ + """ + Start date + """ date: Int! - """ Deposit count """ + """ + Deposit count + """ depositCount: BigInt! - """ Deposit count for the given timeframe """ + """ + Deposit count for the given timeframe + """ newDepositCount: BigInt! - """ Withdraw count """ + """ + Withdraw count + """ withdrawCount: BigInt! - """ Withdraw count for the given timeframe """ + """ + Withdraw count for the given timeframe + """ newWithdrawCount: BigInt! - """ Transfer count """ + """ + Transfer count + """ transferCount: BigInt! - """ Transfer count for the given timeframe """ + """ + Transfer count for the given timeframe + """ newTransferCount: BigInt! - """ Protocol count """ + """ + Protocol count + """ protocolCount: BigInt! - """ Protocol count for the given timeframe """ + """ + Protocol count for the given timeframe + """ newProtocolCount: BigInt! - """ User count """ + """ + User count + """ userCount: BigInt! - """ User count for the given timeframe """ + """ + User count for the given timeframe + """ newUserCount: BigInt! - """ Token count """ + """ + Token count + """ tokenCount: BigInt! - """ Token count for the given timeframe """ + """ + Token count for the given timeframe + """ newTokenCount: BigInt! - """ Master contract count """ + """ + Master contract count + """ masterContractCount: BigInt! - """ Master contract count for the given timeframe """ + """ + Master contract count for the given timeframe + """ newMasterContractCount: BigInt! - """ Clone count """ + """ + Clone count + """ cloneCount: BigInt! - """ Clone count for the given timeframe """ + """ + Clone count for the given timeframe + """ newCloneCount: BigInt! - """ Flash loan count """ + """ + Flash loan count + """ flashloanCount: BigInt! - """ Flash loan count for the given timeframe """ + """ + Flash loan count for the given timeframe + """ newFlashloanCount: BigInt! - """ Transaction count """ + """ + Transaction count + """ transactionCount: BigInt! - """ Transaction count for the given timeframe """ + """ + Transaction count for the given timeframe + """ newTransactionCount: BigInt! - """ Strategy count """ + """ + Strategy count + """ strategyCount: BigInt! - """ Strategy for the given timeframe """ + """ + Strategy for the given timeframe + """ newStrategyCount: BigInt! - """ Active strategy count """ + """ + Active strategy count + """ activeStrategyCount: BigInt! - """ Active strategy count given timeframe """ + """ + Active strategy count given timeframe + """ newActiveStrategyCount: BigInt! - """ Pending strategy count """ + """ + Pending strategy count + """ pendingStrategyCount: BigInt! - """ Pending strategy count given timeframe """ + """ + Pending strategy count given timeframe + """ newPendingStrategyCount: BigInt! } @@ -798,7 +980,9 @@ input BentoBoxHourlyKpi_filter { newPendingStrategyCount_in: [BigInt!] newPendingStrategyCount_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [BentoBoxHourlyKpi_filter] or: [BentoBoxHourlyKpi_filter] @@ -836,46 +1020,74 @@ enum BentoBoxHourlyKpi_orderBy { } type BentoBoxKpi { - """ BentoBox address """ + """ + BentoBox address + """ id: ID! - """ Deposit count """ + """ + Deposit count + """ depositCount: BigInt! - """ Withdraw count """ + """ + Withdraw count + """ withdrawCount: BigInt! - """ Transfer count """ + """ + Transfer count + """ transferCount: BigInt! - """ Protocol count """ + """ + Protocol count + """ protocolCount: BigInt! - """ User count """ + """ + User count + """ userCount: BigInt! - """ Token count """ + """ + Token count + """ tokenCount: BigInt! - """ Master contract count """ + """ + Master contract count + """ masterContractCount: BigInt! - """ Clone count """ + """ + Clone count + """ cloneCount: BigInt! - """ Flash loan count """ + """ + Flash loan count + """ flashloanCount: BigInt! - """ Transaction count """ + """ + Transaction count + """ transactionCount: BigInt! - """ Strategy count """ + """ + Strategy count + """ strategyCount: BigInt! - """ Active strategy count """ + """ + Active strategy count + """ activeStrategyCount: BigInt! - """ Pending strategy count """ + """ + Pending strategy count + """ pendingStrategyCount: BigInt! } @@ -993,7 +1205,9 @@ input BentoBoxKpi_filter { pendingStrategyCount_in: [BigInt!] pendingStrategyCount_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [BentoBoxKpi_filter] or: [BentoBoxKpi_filter] @@ -1034,7 +1248,9 @@ input BentoBox_filter { transactions_: Transaction_filter totals_: Rebase_filter - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [BentoBox_filter] or: [BentoBox_filter] @@ -1069,22 +1285,34 @@ input Block_height { scalar Bytes type Clone { - """ Clone address """ + """ + Clone address + """ id: ID! - """ BentoBox this clone belongs to """ + """ + BentoBox this clone belongs to + """ bentoBox: BentoBox! - """ Master contract this clone belongs to """ + """ + Master contract this clone belongs to + """ masterContract: MasterContract! - """ Clone data """ + """ + Clone data + """ data: Bytes! - """ Block number of this clone """ + """ + Block number of this clone + """ block: BigInt! - """ Timestamp of this clone """ + """ + Timestamp of this clone + """ timestamp: BigInt! } @@ -1166,7 +1394,9 @@ input Clone_filter { timestamp_in: [BigInt!] timestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Clone_filter] or: [Clone_filter] @@ -1185,31 +1415,49 @@ enum Clone_orderBy { } type FlashLoan { - """ Concatenation of transaction and log index """ + """ + Concatenation of transaction and log index + """ id: ID! - """ BentoBox this flash loan belongs to """ + """ + BentoBox this flash loan belongs to + """ bentoBox: BentoBox! - """ Borrower address """ + """ + Borrower address + """ borrower: Bytes! - """ Receiver address """ + """ + Receiver address + """ receiver: Bytes! - """ Token this flash loan belongs to """ + """ + Token this flash loan belongs to + """ token: Token! - """ Amount of this flash loan """ + """ + Amount of this flash loan + """ amount: BigInt! - """ Fee amount of this flash loan """ + """ + Fee amount of this flash loan + """ feeAmount: BigInt! - """ Block number of this flash loan """ + """ + Block number of this flash loan + """ block: BigInt! - """ Timestamp of this flash loan """ + """ + Timestamp of this flash loan + """ timestamp: BigInt! } @@ -1317,7 +1565,9 @@ input FlashLoan_filter { timestamp_in: [BigInt!] timestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [FlashLoan_filter] or: [FlashLoan_filter] @@ -1344,25 +1594,39 @@ enum FlashLoan_orderBy { } type Harvest { - """ Concatenation of strategy and block number """ + """ + Concatenation of strategy and block number + """ id: ID! - """ The Token this harvest belongs to """ + """ + The Token this harvest belongs to + """ token: Token! - """ The Strategy this harvest belongs to """ + """ + The Strategy this harvest belongs to + """ strategy: Strategy! - """ The ProfitOrLoss of this harvest """ + """ + The ProfitOrLoss of this harvest + """ profitOrLoss: ProfitOrLoss - """ The InvestOrDivest of this harvest """ + """ + The InvestOrDivest of this harvest + """ investOrDivest: InvestOrDivest - """ Block number of this harvest """ + """ + Block number of this harvest + """ block: BigInt! - """ Timestamp of this harvest """ + """ + Timestamp of this harvest + """ timestamp: BigInt! } @@ -1476,7 +1740,9 @@ input Harvest_filter { timestamp_in: [BigInt!] timestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Harvest_filter] or: [Harvest_filter] @@ -1518,25 +1784,39 @@ enum Harvest_orderBy { scalar Int8 type InvestOrDivest { - """ Concatenation of strategy and invest or divest count """ + """ + Concatenation of strategy and invest or divest count + """ id: ID! - """ The Harvest this invest or divest belongs to """ + """ + The Harvest this invest or divest belongs to + """ harvest: Harvest! - """ The cached token elastic at time of invest or divest""" + """ + The cached token elastic at time of invest or divest + """ elastic: BigInt! - """ The cached token base at time of invest or divest""" + """ + The cached token base at time of invest or divest + """ base: BigInt! - """ Amount of invest or divest """ + """ + Amount of invest or divest + """ amount: BigInt! - """ Block number of this invest or divest """ + """ + Block number of this invest or divest + """ block: BigInt! - """ Timestamp of this invest or divest """ + """ + Timestamp of this invest or divest + """ timestamp: BigInt! } @@ -1611,7 +1891,9 @@ input InvestOrDivest_filter { timestamp_in: [BigInt!] timestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [InvestOrDivest_filter] or: [InvestOrDivest_filter] @@ -1631,33 +1913,63 @@ enum InvestOrDivest_orderBy { } type MasterContract { - """ MasterContract address """ + """ + MasterContract address + """ id: ID! - """ BentoBox this master contract belongs to """ + """ + BentoBox this master contract belongs to + """ bentoBox: BentoBox! - """ Clones that belong to this master contract """ - clones(skip: Int = 0, first: Int = 100, orderBy: Clone_orderBy, orderDirection: OrderDirection, where: Clone_filter): [Clone!] + """ + Clones that belong to this master contract + """ + clones( + skip: Int = 0 + first: Int = 100 + orderBy: Clone_orderBy + orderDirection: OrderDirection + where: Clone_filter + ): [Clone!] - """ MasterContractApprovals that belong to this master contract """ - masterContractApprovals(skip: Int = 0, first: Int = 100, orderBy: MasterContractApproval_orderBy, orderDirection: OrderDirection, where: MasterContractApproval_filter): [MasterContractApproval!] + """ + MasterContractApprovals that belong to this master contract + """ + masterContractApprovals( + skip: Int = 0 + first: Int = 100 + orderBy: MasterContractApproval_orderBy + orderDirection: OrderDirection + where: MasterContractApproval_filter + ): [MasterContractApproval!] - """ MasterContract is whitelisted by Sushi Operations""" + """ + MasterContract is whitelisted by Sushi Operations + """ approved: Boolean! } type MasterContractApproval { - """Concatenation of user adddress and master contract address""" + """ + Concatenation of user adddress and master contract address + """ id: ID! - """ MasterContract this master contract approval belongs to """ + """ + MasterContract this master contract approval belongs to + """ masterContract: MasterContract! - """ User this master contract approval belongs to """ + """ + User this master contract approval belongs to + """ user: User! - """ If user has approved this master contract """ + """ + If user has approved this master contract + """ approved: Boolean! } @@ -1717,7 +2029,9 @@ input MasterContractApproval_filter { approved_in: [Boolean!] approved_not_in: [Boolean!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [MasterContractApproval_filter] or: [MasterContractApproval_filter] @@ -1772,7 +2086,9 @@ input MasterContract_filter { approved_in: [Boolean!] approved_not_in: [Boolean!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [MasterContract_filter] or: [MasterContract_filter] @@ -1787,32 +2103,48 @@ enum MasterContract_orderBy { approved } -"""Defines the order direction, either ascending or descending""" +""" +Defines the order direction, either ascending or descending +""" enum OrderDirection { asc desc } type ProfitOrLoss { - """ Concatenation of strategy and profit or loss count """ + """ + Concatenation of strategy and profit or loss count + """ id: ID! - """ The Harvest this profit or loss belongs to """ + """ + The Harvest this profit or loss belongs to + """ harvest: Harvest! - """ The cached token elastic at time of profit or loss""" + """ + The cached token elastic at time of profit or loss + """ elastic: BigInt! - """ The cached token base at time of profit or loss""" + """ + The cached token base at time of profit or loss + """ base: BigInt! - """ Amount of profit or loss """ + """ + Amount of profit or loss + """ amount: BigInt! - """ Block number of this profit or loss """ + """ + Block number of this profit or loss + """ block: BigInt! - """ Timestamp of this profit or loss """ + """ + Timestamp of this profit or loss + """ timestamp: BigInt! } @@ -1887,7 +2219,9 @@ input ProfitOrLoss_filter { timestamp_in: [BigInt!] timestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [ProfitOrLoss_filter] or: [ProfitOrLoss_filter] @@ -1907,10 +2241,14 @@ enum ProfitOrLoss_orderBy { } type Protocol { - """ Protocol address """ + """ + Protocol address + """ id: ID! - """ BentoBox this protocol belongs to """ + """ + BentoBox this protocol belongs to + """ bentoBox: BentoBox! } @@ -1945,7 +2283,9 @@ input Protocol_filter { bentoBox_not_ends_with_nocase: String bentoBox_: BentoBox_filter - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Protocol_filter] or: [Protocol_filter] @@ -2739,24 +3079,36 @@ type Query { subgraphError: _SubgraphErrorPolicy_! = deny ): [TokenDailyKpi!]! - """Access to subgraph metadata""" + """ + Access to subgraph metadata + """ _meta(block: Block_height): _Meta_ } type Rebase { - """ Token address """ + """ + Token address + """ id: ID! - """ BentoBox this rebase belongs to """ + """ + BentoBox this rebase belongs to + """ bentoBox: BentoBox! - """ Token this rebase belongs to """ + """ + Token this rebase belongs to + """ token: Token! - """ Base (Share) """ + """ + Base (Share) + """ base: BigInt! - """ Elastic (Amount) """ + """ + Elastic (Amount) + """ elastic: BigInt! } @@ -2828,7 +3180,9 @@ input Rebase_filter { elastic_in: [BigInt!] elastic_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Rebase_filter] or: [Rebase_filter] @@ -2851,106 +3205,177 @@ enum Rebase_orderBy { } type Strategy { - """ Strategy address """ + """ + Strategy address + """ id: ID! - """ StrategyKpi of this strategy """ + """ + StrategyKpi of this strategy + """ kpi: StrategyKpi! - """ Harvests which belong to this strategy """ - harvests(skip: Int = 0, first: Int = 100, orderBy: Harvest_orderBy, orderDirection: OrderDirection, where: Harvest_filter): [Harvest!] + """ + Harvests which belong to this strategy + """ + harvests( + skip: Int = 0 + first: Int = 100 + orderBy: Harvest_orderBy + orderDirection: OrderDirection + where: Harvest_filter + ): [Harvest!] - """ The Token this strategy belongs to """ + """ + The Token this strategy belongs to + """ token: Token! - """ Block number of this strategy """ + """ + Block number of this strategy + """ block: BigInt! - """ Timestamp of this strategy """ + """ + Timestamp of this strategy + """ timestamp: BigInt! } type StrategyDailyKpi { - """ created by id, infix '-day-' and unix timestamp, e.g. '0x00..00-day-1657270000' + """ + created by id, infix '-day-' and unix timestamp, e.g. '0x00..00-day-1657270000' """ id: ID! - """ The Strategy this KPI belongs to """ + """ + The Strategy this KPI belongs to + """ strategy: Strategy! - """ Start date """ + """ + Start date + """ date: Int! - """ Harvest count """ + """ + Harvest count + """ harvestCount: BigInt! - """ Invest or divest count """ + """ + Invest or divest count + """ investOrDivestCount: BigInt! - """ Invest count """ + """ + Invest count + """ investCount: BigInt! - """ Invested """ + """ + Invested + """ invested: BigInt! - """ Divest count """ + """ + Divest count + """ divestCount: BigInt! - """ Divested""" + """ + Divested + """ divested: BigInt! - """ Profit or loss count """ + """ + Profit or loss count + """ profitOrLossCount: BigInt! - """ Profit count """ + """ + Profit count + """ profitCount: BigInt! - """ Loss count """ + """ + Loss count + """ lossCount: BigInt! - """ Profit & Loss""" + """ + Profit & Loss + """ profitAndLoss: BigInt! - """ APR """ + """ + APR + """ apr: BigDecimal - """ Utilization """ + """ + Utilization + """ utilization: BigDecimal - """ Harvest count for the given timeframe """ + """ + Harvest count for the given timeframe + """ newHarvestCount: BigInt! - """ Invest or divest count for the given timeframe """ + """ + Invest or divest count for the given timeframe + """ newInvestOrDivestCount: BigInt! - """ Invest count for the given timeframe """ + """ + Invest count for the given timeframe + """ newInvestCount: BigInt! - """ Invested for the given timeframe """ + """ + Invested for the given timeframe + """ newInvested: BigInt! - """ Divest count for the given timeframe """ + """ + Divest count for the given timeframe + """ newDivestCount: BigInt! - """ Divested for the given timeframe """ + """ + Divested for the given timeframe + """ newDivested: BigInt! - """ Profit or loss count for the given timeframe """ + """ + Profit or loss count for the given timeframe + """ newProfitOrLossCount: BigInt! - """ Profit count for the given timeframe """ + """ + Profit count for the given timeframe + """ newProfitCount: BigInt! - """ Loss count for the given timeframe """ + """ + Loss count for the given timeframe + """ newLossCount: BigInt! - """ Profit & Los for the given timeframe """ + """ + Profit & Los for the given timeframe + """ newProfitAndLoss: BigInt! - """ APR for the given timeframe """ + """ + APR for the given timeframe + """ newApr: BigDecimal - """ Utilization for the given timeframe """ + """ + Utilization for the given timeframe + """ newUtilization: BigDecimal } @@ -3185,7 +3610,9 @@ input StrategyDailyKpi_filter { newUtilization_in: [BigDecimal!] newUtilization_not_in: [BigDecimal!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [StrategyDailyKpi_filter] or: [StrategyDailyKpi_filter] @@ -3225,16 +3652,24 @@ enum StrategyDailyKpi_orderBy { } type StrategyData { - """ Token address """ + """ + Token address + """ id: ID! - """ Strategy start date """ + """ + Strategy start date + """ strategyStartDate: BigInt! - """ Target percentage """ + """ + Target percentage + """ targetPercentage: BigInt! - """ BentoBox's understanding of the balance """ + """ + BentoBox's understanding of the balance + """ balance: BigInt! } @@ -3272,7 +3707,9 @@ input StrategyData_filter { balance_in: [BigInt!] balance_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [StrategyData_filter] or: [StrategyData_filter] @@ -3286,86 +3723,139 @@ enum StrategyData_orderBy { } type StrategyHourlyKpi { - """ created by id, infix '-hour-' and unix timestamp, e.g. '0x00..00-hour-1657270000' + """ + created by id, infix '-hour-' and unix timestamp, e.g. '0x00..00-hour-1657270000' """ id: ID! - """ The Strategy this KPI belongs to """ + """ + The Strategy this KPI belongs to + """ strategy: Strategy! - """ Start date """ + """ + Start date + """ date: Int! - """ Harvest count """ + """ + Harvest count + """ harvestCount: BigInt! - """ Invest or divest count """ + """ + Invest or divest count + """ investOrDivestCount: BigInt! - """ Invest count """ + """ + Invest count + """ investCount: BigInt! - """ Invested """ + """ + Invested + """ invested: BigInt! - """ Divest count """ + """ + Divest count + """ divestCount: BigInt! - """ Divested""" + """ + Divested + """ divested: BigInt! - """ Profit or loss count """ + """ + Profit or loss count + """ profitOrLossCount: BigInt! - """ Profit count """ + """ + Profit count + """ profitCount: BigInt! - """ Loss count """ + """ + Loss count + """ lossCount: BigInt! - """ Profit & Loss""" + """ + Profit & Loss + """ profitAndLoss: BigInt! - """ APR """ + """ + APR + """ apr: BigDecimal - """ Utilization """ + """ + Utilization + """ utilization: BigDecimal - """ Harvest count for the given timeframe """ + """ + Harvest count for the given timeframe + """ newHarvestCount: BigInt! - """ Invest or divest count for the given timeframe """ + """ + Invest or divest count for the given timeframe + """ newInvestOrDivestCount: BigInt! - """ Invest count for the given timeframe """ + """ + Invest count for the given timeframe + """ newInvestCount: BigInt! - """ Invested for the given timeframe """ + """ + Invested for the given timeframe + """ newInvested: BigInt! - """ Divest count for the given timeframe """ + """ + Divest count for the given timeframe + """ newDivestCount: BigInt! - """ Divested for the given timeframe """ + """ + Divested for the given timeframe + """ newDivested: BigInt! - """ Profit or loss count for the given timeframe """ + """ + Profit or loss count for the given timeframe + """ newProfitOrLossCount: BigInt! - """ Profit count for the given timeframe """ + """ + Profit count for the given timeframe + """ newProfitCount: BigInt! - """ Loss count for the given timeframe """ + """ + Loss count for the given timeframe + """ newLossCount: BigInt! - """ Profit & Los for the given timeframe """ + """ + Profit & Los for the given timeframe + """ newProfitAndLoss: BigInt! - """ APR for the given timeframe """ + """ + APR for the given timeframe + """ newApr: BigDecimal - """ Utilization for the given timeframe """ + """ + Utilization for the given timeframe + """ newUtilization: BigDecimal } @@ -3600,7 +4090,9 @@ input StrategyHourlyKpi_filter { newUtilization_in: [BigDecimal!] newUtilization_not_in: [BigDecimal!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [StrategyHourlyKpi_filter] or: [StrategyHourlyKpi_filter] @@ -3640,43 +4132,69 @@ enum StrategyHourlyKpi_orderBy { } type StrategyKpi { - """ Strategy address """ + """ + Strategy address + """ id: ID! - """ Harvest count """ + """ + Harvest count + """ harvestCount: BigInt! - """ Invest or divest count """ + """ + Invest or divest count + """ investOrDivestCount: BigInt! - """ Invest count """ + """ + Invest count + """ investCount: BigInt! - """ Invested """ + """ + Invested + """ invested: BigInt! - """ Divest count """ + """ + Divest count + """ divestCount: BigInt! - """ Divested""" + """ + Divested + """ divested: BigInt! - """ Profit or loss count """ + """ + Profit or loss count + """ profitOrLossCount: BigInt! - """ Profit count """ + """ + Profit count + """ profitCount: BigInt! - """ Loss count """ + """ + Loss count + """ lossCount: BigInt! - """ Profit & Loss""" + """ + Profit & Loss + """ profitAndLoss: BigInt! - """ Isolated Strategy APR """ + """ + Isolated Strategy APR + """ apr: BigDecimal - """ Utilization """ + """ + Utilization + """ utilization: BigDecimal } @@ -3786,7 +4304,9 @@ input StrategyKpi_filter { utilization_in: [BigDecimal!] utilization_not_in: [BigDecimal!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [StrategyKpi_filter] or: [StrategyKpi_filter] @@ -3877,7 +4397,9 @@ input Strategy_filter { timestamp_in: [BigInt!] timestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Strategy_filter] or: [Strategy_filter] @@ -4694,7 +5216,9 @@ type Subscription { subgraphError: _SubgraphErrorPolicy_! = deny ): [TokenDailyKpi!]! - """Access to subgraph metadata""" + """ + Access to subgraph metadata + """ _meta(block: Block_height): _Meta_ } @@ -4702,58 +5226,97 @@ type Subscription { scalar Timestamp type Token { - """ Token address """ + """ + Token address + """ id: ID! - """ The BentoBox this token belongs to """ + """ + The BentoBox this token belongs to + """ bentoBox: BentoBox! - """ The Rebase that belongs to this token """ + """ + The Rebase that belongs to this token + """ rebase: Rebase! - """ Strategies, past and present, which belong to this token """ - strategies(skip: Int = 0, first: Int = 100, orderBy: Strategy_orderBy, orderDirection: OrderDirection, where: Strategy_filter): [Strategy!] + """ + Strategies, past and present, which belong to this token + """ + strategies( + skip: Int = 0 + first: Int = 100 + orderBy: Strategy_orderBy + orderDirection: OrderDirection + where: Strategy_filter + ): [Strategy!] - """ The Kpi which belong to this token """ + """ + The Kpi which belong to this token + """ kpi: TokenKpi! - """ Token symbol, if fetched successfully, else default to ??? """ + """ + Token symbol, if fetched successfully, else default to ??? + """ symbol: String! - """ If the symbol was succesfully fetched """ + """ + If the symbol was succesfully fetched + """ symbolSuccess: Boolean! - """ Token name, if fetched successfully, else default to ??? """ + """ + Token name, if fetched successfully, else default to ??? + """ name: String! - """ If the name was succesfully fetched """ + """ + If the name was succesfully fetched + """ nameSuccess: Boolean! - """ Token decimals, if fetched successfully, else default to 18 """ + """ + Token decimals, if fetched successfully, else default to 18 + """ decimals: BigInt! - """ If the decimals was succesfully fetched """ + """ + If the decimals was succesfully fetched + """ decimalsSuccess: Boolean! } type TokenDailyKpi { - """ created by id, infix '-day-' and unix timestamp, e.g. '0x00..00-day-1657270000' + """ + created by id, infix '-day-' and unix timestamp, e.g. '0x00..00-day-1657270000' """ id: ID! - """ Token this KPI belongs to """ + """ + Token this KPI belongs to + """ token: Token! - """ Liquidity """ + """ + Liquidity + """ liquidity: BigInt! - """ Liquidity for the given timeframe """ + """ + Liquidity for the given timeframe + """ newLiquidity: BigInt! - """ Strategy count """ + """ + Strategy count + """ strategyCount: BigInt! - """ Strategy count for the given timeframe """ + """ + Strategy count for the given timeframe + """ newStrategyCount: BigInt! } @@ -4820,7 +5383,9 @@ input TokenDailyKpi_filter { newStrategyCount_in: [BigInt!] newStrategyCount_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [TokenDailyKpi_filter] or: [TokenDailyKpi_filter] @@ -4843,23 +5408,34 @@ enum TokenDailyKpi_orderBy { } type TokenHourlyKpi { - """ created by id, infix '-hour-' and unix timestamp, e.g. '0x00..00-hour-1657270000' + """ + created by id, infix '-hour-' and unix timestamp, e.g. '0x00..00-hour-1657270000' """ id: ID! - """ Token this KPI belongs to """ + """ + Token this KPI belongs to + """ token: Token! - """ Liquidity """ + """ + Liquidity + """ liquidity: BigInt! - """ Liquidity for the given timeframe """ + """ + Liquidity for the given timeframe + """ newLiquidity: BigInt! - """ Strategy count """ + """ + Strategy count + """ strategyCount: BigInt! - """ Strategy count for the given timeframe """ + """ + Strategy count for the given timeframe + """ newStrategyCount: BigInt! } @@ -4926,7 +5502,9 @@ input TokenHourlyKpi_filter { newStrategyCount_in: [BigInt!] newStrategyCount_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [TokenHourlyKpi_filter] or: [TokenHourlyKpi_filter] @@ -4949,19 +5527,29 @@ enum TokenHourlyKpi_orderBy { } type TokenKpi { - """ Token address """ + """ + Token address + """ id: ID! - """ Token that belongs to this Kpi """ + """ + Token that belongs to this Kpi + """ token: Token! - """ Liquidity """ + """ + Liquidity + """ liquidity: BigInt! - """ Strategy count """ + """ + Strategy count + """ strategyCount: BigInt! - """ Token Strategy APR """ + """ + Token Strategy APR + """ apr: BigDecimal } @@ -5020,7 +5608,9 @@ input TokenKpi_filter { apr_in: [BigDecimal!] apr_not_in: [BigDecimal!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [TokenKpi_filter] or: [TokenKpi_filter] @@ -5042,25 +5632,39 @@ enum TokenKpi_orderBy { } type TokenStrategy { - """ Token address """ + """ + Token address + """ id: ID! - """ The Token this strategy belongs to """ + """ + The Token this strategy belongs to + """ token: Token! - """ Strategy address """ + """ + Strategy address + """ strategy: Strategy - """ Pending strategy address """ + """ + Pending strategy address + """ pendingStrategy: Strategy - """ The Strategy data """ + """ + The Strategy data + """ data: StrategyData! - """ The block number of this strategy """ + """ + The block number of this strategy + """ block: BigInt! - """ The timestamp of this strategy """ + """ + The timestamp of this strategy + """ timestamp: BigInt! } @@ -5174,7 +5778,9 @@ input TokenStrategy_filter { timestamp_in: [BigInt!] timestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [TokenStrategy_filter] or: [TokenStrategy_filter] @@ -5341,7 +5947,9 @@ input Token_filter { decimalsSuccess_in: [Boolean!] decimalsSuccess_not_in: [Boolean!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Token_filter] or: [Token_filter] @@ -5370,34 +5978,54 @@ enum Token_orderBy { } type Transaction { - """ Concatenation of transaction hash and log index """ + """ + Concatenation of transaction hash and log index + """ id: ID! - """ BentoBox this transaction belongs to """ + """ + BentoBox this transaction belongs to + """ bentoBox: BentoBox! - """ Transaction type """ + """ + Transaction type + """ type: TransactionType! - """ User from whom this transaction is made """ + """ + User from whom this transaction is made + """ from: User! - """ User to whom the transaction is sent """ + """ + User to whom the transaction is sent + """ to: User! - """ Token this transaction belongs to """ + """ + Token this transaction belongs to + """ token: Token! - """ Amount of this transaction """ + """ + Amount of this transaction + """ amount: BigInt - """ Share of this transaction """ + """ + Share of this transaction + """ share: BigInt! - """ Block number of this transaction """ + """ + Block number of this transaction + """ block: BigInt! - """ Timestamp of this transaction """ + """ + Timestamp of this transaction + """ timestamp: BigInt! } @@ -5537,7 +6165,9 @@ input Transaction_filter { timestamp_in: [BigInt!] timestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Transaction_filter] or: [Transaction_filter] @@ -5571,22 +6201,46 @@ enum Transaction_orderBy { } type User { - """ User address """ + """ + User address + """ id: ID! - """ BentoBox this user belongs to """ + """ + BentoBox this user belongs to + """ bentoBox: BentoBox! - """ MasterContractApprovals that belong to this user """ - masterContractApprovals(skip: Int = 0, first: Int = 100, orderBy: MasterContractApproval_orderBy, orderDirection: OrderDirection, where: MasterContractApproval_filter): [MasterContractApproval!] + """ + MasterContractApprovals that belong to this user + """ + masterContractApprovals( + skip: Int = 0 + first: Int = 100 + orderBy: MasterContractApproval_orderBy + orderDirection: OrderDirection + where: MasterContractApproval_filter + ): [MasterContractApproval!] - """ Balances that belong to this user """ - balances(skip: Int = 0, first: Int = 100, orderBy: Balance_orderBy, orderDirection: OrderDirection, where: Balance_filter): [Balance!] + """ + Balances that belong to this user + """ + balances( + skip: Int = 0 + first: Int = 100 + orderBy: Balance_orderBy + orderDirection: OrderDirection + where: Balance_filter + ): [Balance!] - """ Block number of this user """ + """ + Block number of this user + """ block: BigInt! - """ Timestamp of this user """ + """ + Timestamp of this user + """ timestamp: BigInt! } @@ -5639,7 +6293,9 @@ input User_filter { timestamp_in: [BigInt!] timestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [User_filter] or: [User_filter] @@ -5656,37 +6312,53 @@ enum User_orderBy { } type _Block_ { - """The hash of the block""" + """ + The hash of the block + """ hash: Bytes - """The block number""" + """ + The block number + """ number: Int! - """Integer representation of the timestamp stored in blocks for the chain""" + """ + Integer representation of the timestamp stored in blocks for the chain + """ timestamp: Int - """The hash of the parent block""" + """ + The hash of the parent block + """ parentHash: Bytes } -"""The type for the top-level _meta field""" +""" +The type for the top-level _meta field +""" type _Meta_ { "Information about a specific subgraph block. The hash of the block\nwill be null if the _meta field has a block constraint that asks for\na block number. It will be filled if the _meta field has no block constraint\nand therefore asks for the latest block\n" block: _Block_! - """The deployment ID""" + """ + The deployment ID + """ deployment: String! - """If `true`, the subgraph encountered indexing errors at some past block""" + """ + If `true`, the subgraph encountered indexing errors at some past block + """ hasIndexingErrors: Boolean! } enum _SubgraphErrorPolicy_ { - """Data will be returned even if the subgraph has indexing errors""" + """ + Data will be returned even if the subgraph has indexing errors + """ allow """ If the subgraph has indexing errors, data will be omitted. The default. """ deny -} \ No newline at end of file +} diff --git a/packages/graph-client/src/subgraphs/data-api/queries/analytics/day-buckets.ts b/packages/graph-client/src/subgraphs/data-api/queries/analytics/day-buckets.ts index 60931bad05..5f317d4f88 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/analytics/day-buckets.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/analytics/day-buckets.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' +import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' export const AnalyticsDayBucketsQuery = graphql( diff --git a/packages/graph-client/src/subgraphs/data-api/queries/features/chain-ids-by-feature.ts b/packages/graph-client/src/subgraphs/data-api/queries/features/chain-ids-by-feature.ts index 4c29b361b1..9618f6616d 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/features/chain-ids-by-feature.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/features/chain-ids-by-feature.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' +import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' export const ChainIdsByFeatureQuery = graphql( diff --git a/packages/graph-client/src/subgraphs/data-api/queries/pool/pools.ts b/packages/graph-client/src/subgraphs/data-api/queries/pool/pools.ts index 7873b2d0d8..52821765bb 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/pool/pools.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/pool/pools.ts @@ -1,10 +1,10 @@ import type { VariablesOf } from 'gql.tada' import { type RequestOptions, request } from 'src/lib/request.js' +import { isEvmChainId } from 'sushi' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' +import { Token } from 'sushi/currency' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' -import { Token } from 'sushi/currency' -import { isEvmChainId } from 'sushi' export const PoolsQuery = graphql( ` diff --git a/packages/graph-client/src/subgraphs/data-api/queries/pool/top-evm-pools.ts b/packages/graph-client/src/subgraphs/data-api/queries/pool/top-evm-pools.ts index ca9f3795a6..deaf8eb0ce 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/pool/top-evm-pools.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/pool/top-evm-pools.ts @@ -1,9 +1,9 @@ import type { VariablesOf } from 'gql.tada' import { type RequestOptions, request } from 'src/lib/request.js' +import { type EvmChainId, isEvmChainId } from 'sushi' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' -import { EvmChainId, isEvmChainId } from 'sushi' export const TopPoolsQuery = graphql( ` @@ -49,12 +49,12 @@ export async function getTopPools( ) { const url = `${SUSHI_DATA_API_HOST}/graphql` try { - if (!isEvmChainId(parseInt(variables.chainId))) { + if (!isEvmChainId(Number.parseInt(variables.chainId))) { throw new Error( `Invalid chainId: ${variables.chainId}, this only supports evm networks and must be a number.`, ) } - const chainId = parseInt(variables.chainId) as EvmChainId + const chainId = Number.parseInt(variables.chainId) as EvmChainId const result = await request( { url, diff --git a/packages/graph-client/src/subgraphs/data-api/queries/pool/v2-pool.ts b/packages/graph-client/src/subgraphs/data-api/queries/pool/v2-pool.ts index 7b0bfbe1d6..ac7691d48a 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/pool/v2-pool.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/pool/v2-pool.ts @@ -1,20 +1,20 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { - ChefType, - EvmChainId, - RewarderType, - SushiSwapProtocol, + type ChefType, + type EvmChainId, type PoolBase, type PoolHistory1D, type PoolV2, type PoolWithAprs, type PoolWithIncentives, + type RewarderType, + SushiSwapProtocol, } from 'sushi' import { isSushiSwapV2ChainId } from 'sushi/config' -import { SUSHI_DATA_API_HOST } from '../../data-api-host.js' import type { Address } from 'viem' +import { SUSHI_DATA_API_HOST } from '../../data-api-host.js' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/pool/v3-pool.ts b/packages/graph-client/src/subgraphs/data-api/queries/pool/v3-pool.ts index 1757ec551e..8498b24b87 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/pool/v3-pool.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/pool/v3-pool.ts @@ -1,21 +1,21 @@ import type { VariablesOf } from 'gql.tada' import type { PoolHasSteerVaults } from '@sushiswap/steer-sdk' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { - EvmChainId, - ChefType, - RewarderType, - SushiSwapProtocol, + type ChefType, + type EvmChainId, type PoolBase, type PoolHistory1D, type PoolV3, type PoolWithAprs, type PoolWithIncentives, + type RewarderType, + SushiSwapProtocol, } from 'sushi' import { isSushiSwapV3ChainId } from 'sushi/config' -import { SUSHI_DATA_API_HOST } from '../../data-api-host.js' import type { Address } from 'viem' +import { SUSHI_DATA_API_HOST } from '../../data-api-host.js' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-claimables.ts b/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-claimables.ts index cc4b20927a..6f4ef5c39f 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-claimables.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-claimables.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-history.ts b/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-history.ts index d0a1517379..c44220db9f 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-history.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-history.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-positions.ts b/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-positions.ts index 00bbd14334..9420bf4215 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-positions.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-positions.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-wallet.ts b/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-wallet.ts index b6c0da44da..ac2a503242 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-wallet.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/portfolio/portfolio-wallet.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/smart-pools.ts b/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/smart-pools.ts index d5027a6527..373cff205b 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/smart-pools.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/smart-pools.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import type { EvmChainId } from 'sushi' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import type { Address } from 'viem' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/vault.ts b/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/vault.ts index 52ddc49c25..79c94af32f 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/vault.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/vault.ts @@ -1,6 +1,6 @@ import type { SteerChainId } from '@sushiswap/steer-sdk' import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import type { Address } from 'viem' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/vaults.ts b/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/vaults.ts index 4b40a62db1..390b55cb19 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/vaults.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/smart-pool/vaults.ts @@ -1,11 +1,11 @@ import type { SteerChainId } from '@sushiswap/steer-sdk' import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import type { Address } from 'viem' import { graphql } from '../../graphql.js' -import type { VaultV1 } from './vault.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' +import type { VaultV1 } from './vault.js' export const VaultsQuery = graphql( ` diff --git a/packages/graph-client/src/subgraphs/data-api/queries/sushi-bar/history.ts b/packages/graph-client/src/subgraphs/data-api/queries/sushi-bar/history.ts index 106a74c86f..3a719c7b31 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/sushi-bar/history.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/sushi-bar/history.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/sushi-bar/stats.ts b/packages/graph-client/src/subgraphs/data-api/queries/sushi-bar/stats.ts index 59d6336a7b..34879b7642 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/sushi-bar/stats.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/sushi-bar/stats.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/approved-community-tokens.ts b/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/approved-community-tokens.ts index cb41770416..231bbe8454 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/approved-community-tokens.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/approved-community-tokens.ts @@ -1,7 +1,7 @@ +import { request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' -import { request } from 'src/lib/request.js' export const ApprovedCommunityTokensQuery = graphql( ` diff --git a/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/pending-tokens.ts b/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/pending-tokens.ts index ddd6fa3d25..1934830c4d 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/pending-tokens.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/pending-tokens.ts @@ -1,7 +1,7 @@ +import { request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' -import { request } from 'src/lib/request.js' export const PendingTokensQuery = graphql( ` diff --git a/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/token-analysis.ts b/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/token-analysis.ts index 8ec9beadc3..0cedd77ca0 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/token-analysis.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/token-list-submission/token-analysis.ts @@ -1,11 +1,11 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { type ChainId } from 'sushi' +import { type RequestOptions, request } from 'src/lib/request.js' +import type { ChainIdVariable } from 'src/lib/types/chainId.js' +import type { ChainId } from 'sushi' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' -import type { ChainIdVariable } from 'src/lib/types/chainId.js' export const TokenAnalysisQuery = graphql( ` diff --git a/packages/graph-client/src/subgraphs/data-api/queries/token-list/token-list-balances.ts b/packages/graph-client/src/subgraphs/data-api/queries/token-list/token-list-balances.ts index ab771dc234..fc0e4a59cd 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/token-list/token-list-balances.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/token-list/token-list-balances.ts @@ -1,11 +1,11 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { getIdFromChainIdAddress, type ChainId } from 'sushi' +import { type RequestOptions, request } from 'src/lib/request.js' +import type { ChainIdVariable } from 'src/lib/types/chainId.js' +import { type ChainId, getIdFromChainIdAddress } from 'sushi' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' -import type { ChainIdVariable } from 'src/lib/types/chainId.js' export const TokenListBalancesQuery = graphql( ` diff --git a/packages/graph-client/src/subgraphs/data-api/queries/token-list/token-list.ts b/packages/graph-client/src/subgraphs/data-api/queries/token-list/token-list.ts index 64ca2a7f97..95aa7d23f0 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/token-list/token-list.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/token-list/token-list.ts @@ -1,11 +1,11 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { getIdFromChainIdAddress, type ChainId } from 'sushi' +import { type RequestOptions, request } from 'src/lib/request.js' +import type { ChainIdVariable } from 'src/lib/types/chainId.js' +import { type ChainId, getIdFromChainIdAddress } from 'sushi' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' -import type { ChainIdVariable } from 'src/lib/types/chainId.js' export const TokenListQuery = graphql( ` diff --git a/packages/graph-client/src/subgraphs/data-api/queries/trending-tokens/trending-tokens.ts b/packages/graph-client/src/subgraphs/data-api/queries/trending-tokens/trending-tokens.ts index 37f6a40b2e..09e94bf841 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/trending-tokens/trending-tokens.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/trending-tokens/trending-tokens.ts @@ -1,11 +1,11 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { getIdFromChainIdAddress, type ChainId } from 'sushi' +import { type RequestOptions, request } from 'src/lib/request.js' +import type { ChainIdVariable } from 'src/lib/types/chainId.js' +import { type ChainId, getIdFromChainIdAddress } from 'sushi' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' -import type { ChainIdVariable } from 'src/lib/types/chainId.js' export const TrendingTokensQuery = graphql( ` diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v2/buckets.ts b/packages/graph-client/src/subgraphs/data-api/queries/v2/buckets.ts index 78b9606780..259ae76ad0 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v2/buckets.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v2/buckets.ts @@ -1,10 +1,10 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' +import type { EvmChainId } from 'sushi' import { isSushiSwapV2ChainId } from 'sushi/config' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' -import type { EvmChainId } from 'sushi' export const V2PoolBucketsQuery = graphql( ` diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v2/burns.ts b/packages/graph-client/src/subgraphs/data-api/queries/v2/burns.ts index 08285abe69..bc188bb289 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v2/burns.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v2/burns.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v2/mints.ts b/packages/graph-client/src/subgraphs/data-api/queries/v2/mints.ts index 75a7cd11f4..e95650f36b 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v2/mints.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v2/mints.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v2/positions.ts b/packages/graph-client/src/subgraphs/data-api/queries/v2/positions.ts index 4a1cf3f86e..9b773dcfb3 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v2/positions.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v2/positions.ts @@ -1,9 +1,14 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { EvmChainId, ChefType, RewarderType, SushiSwapProtocol } from 'sushi' +import { type RequestOptions, request } from 'src/lib/request.js' +import { + type ChefType, + type EvmChainId, + type RewarderType, + SushiSwapProtocol, +} from 'sushi' import { isSushiSwapV2ChainId } from 'sushi/config' -import { getAddress, type Address } from 'viem' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' +import { type Address, getAddress } from 'viem' import { graphql } from '../../graphql.js' export const V2PositionsQuery = graphql( diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v2/swaps.ts b/packages/graph-client/src/subgraphs/data-api/queries/v2/swaps.ts index 85d886ed3c..6edd55104e 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v2/swaps.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v2/swaps.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v3/buckets.ts b/packages/graph-client/src/subgraphs/data-api/queries/v3/buckets.ts index b3fe7ecdbf..d6589e0030 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v3/buckets.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v3/buckets.ts @@ -1,7 +1,7 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { EvmChainId } from 'sushi' +import { type RequestOptions, request } from 'src/lib/request.js' +import type { EvmChainId } from 'sushi' import { isSushiSwapV3ChainId } from 'sushi/config' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v3/burns.ts b/packages/graph-client/src/subgraphs/data-api/queries/v3/burns.ts index e6f95bf011..756ab7056b 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v3/burns.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v3/burns.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v3/collects.ts b/packages/graph-client/src/subgraphs/data-api/queries/v3/collects.ts index 2245a17f93..fffa1b223c 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v3/collects.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v3/collects.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v3/mints.ts b/packages/graph-client/src/subgraphs/data-api/queries/v3/mints.ts index c5bd2f78fe..a648f937c7 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v3/mints.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v3/mints.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v3/pools-by-tokens.ts b/packages/graph-client/src/subgraphs/data-api/queries/v3/pools-by-tokens.ts index a9083be876..0ffc888a3c 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v3/pools-by-tokens.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v3/pools-by-tokens.ts @@ -1,10 +1,10 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { SushiSwapProtocol, type PoolBase, type PoolV3 } from 'sushi' +import { type RequestOptions, request } from 'src/lib/request.js' +import { type PoolBase, type PoolV3, SushiSwapProtocol } from 'sushi' import { isSushiSwapV3ChainId } from 'sushi/config' -import type { Address } from 'viem' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' +import type { Address } from 'viem' import { graphql } from '../../graphql.js' export const V3PoolsByTokensQuery = graphql( diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v3/pools.ts b/packages/graph-client/src/subgraphs/data-api/queries/v3/pools.ts index 972742cb76..43b70281ee 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v3/pools.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v3/pools.ts @@ -1,15 +1,15 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { - EvmChainId, - SushiSwapProtocol, + type EvmChainId, type PoolBase, type PoolV3, + SushiSwapProtocol, } from 'sushi' import { isSushiSwapV3ChainId } from 'sushi/config' -import type { Address } from 'viem' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' +import type { Address } from 'viem' import { graphql } from '../../graphql.js' import { SUSHI_REQUEST_HEADERS } from '../../request-headers.js' diff --git a/packages/graph-client/src/subgraphs/data-api/queries/v3/swaps.ts b/packages/graph-client/src/subgraphs/data-api/queries/v3/swaps.ts index 9d252e8ac4..5bf947177a 100644 --- a/packages/graph-client/src/subgraphs/data-api/queries/v3/swaps.ts +++ b/packages/graph-client/src/subgraphs/data-api/queries/v3/swaps.ts @@ -1,6 +1,6 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { SUSHI_DATA_API_HOST } from 'sushi/config/subgraph' import { graphql } from '../../graphql.js' diff --git a/packages/graph-client/src/subgraphs/data-api/request-headers.ts b/packages/graph-client/src/subgraphs/data-api/request-headers.ts index 659b480c6a..bfb6a4e1c9 100644 --- a/packages/graph-client/src/subgraphs/data-api/request-headers.ts +++ b/packages/graph-client/src/subgraphs/data-api/request-headers.ts @@ -1,3 +1,3 @@ export const SUSHI_REQUEST_HEADERS = { - 'Origin': 'https://sushi.com' -} \ No newline at end of file + Origin: 'https://sushi.com', +} diff --git a/packages/graph-client/src/subgraphs/data-api/schema.graphql b/packages/graph-client/src/subgraphs/data-api/schema.graphql index 5cc57ce12c..10b9f44646 100644 --- a/packages/graph-client/src/subgraphs/data-api/schema.graphql +++ b/packages/graph-client/src/subgraphs/data-api/schema.graphql @@ -20,14 +20,27 @@ type SushiDayBuckets { type Query { sushiDayBuckets(chainId: SushiSwapChainId!): SushiDayBuckets! - pools(chainId: PoolChainId!, page: Int = 1, search: [String], protocols: [Protocol], onlyIncentivized: Boolean = false, onlySmartPools: Boolean = false, orderBy: PoolsOrderBy = liquidityUSD, orderDirection: OrderDirection = desc): Pools! + pools( + chainId: PoolChainId! + page: Int = 1 + search: [String] + protocols: [Protocol] + onlyIncentivized: Boolean = false + onlySmartPools: Boolean = false + orderBy: PoolsOrderBy = liquidityUSD + orderDirection: OrderDirection = desc + ): Pools! poolAddresses(chainId: PoolChainId!, protocols: [Protocol]): [Bytes!]! topPools(chainId: String!): [TopPool!]! v2Pool(address: Bytes!, chainId: SushiSwapV2ChainId!): V2Pool! v3Pool(address: Bytes!, chainId: SushiSwapV3ChainId!): V3Pool! v2PoolBuckets(address: Bytes!, chainId: SushiSwapV2ChainId!): PoolBuckets! v3PoolBuckets(address: Bytes!, chainId: SushiSwapV3ChainId!): PoolBuckets! - v3PoolsByTokens(token0: Bytes!, token1: Bytes!, chainId: SushiSwapV3ChainId!): [V3BasePool!]! + v3PoolsByTokens( + token0: Bytes! + token1: Bytes! + chainId: SushiSwapV3ChainId! + ): [V3BasePool!]! v3Pools(chainId: SushiSwapV3ChainId!): [V3BasePool!]! portfolioWallet(id: ID!): PortfolioWallet! portfolioLiquidityPositions(id: ID!): PortfolioPositions! @@ -40,22 +53,42 @@ type Query { vaults(chainId: SmartPoolChainId!, poolAddress: Bytes!): [Vault!]! sushiBarStats: SushiBarStats! sushiBarHistory: SushiBarHistory! - tokenList(chainId: TokenListChainId!, first: Int = 50, skip: Int, search: String, customTokens: [Bytes!]): [TokenListEntry!]! - tokenListBalances(chainId: TokenListChainId!, account: Bytes!, includeNative: Boolean = true, customTokens: [Bytes!]): [TokenListEntryWithBalance!]! + tokenList( + chainId: TokenListChainId! + first: Int = 50 + skip: Int + search: String + customTokens: [Bytes!] + ): [TokenListEntry!]! + tokenListBalances( + chainId: TokenListChainId! + account: Bytes! + includeNative: Boolean = true + customTokens: [Bytes!] + ): [TokenListEntryWithBalance!]! tokenAnalysis(chainId: Int!, address: Bytes!): TokenAnalysis! pendingTokens: [PendingToken!]! approvedCommunityTokens: [ApprovedToken!]! trendingTokens(chainId: TrendingTokensChainId!): [TrendingToken!]! - v2LiquidityPositions(user: Bytes!, chainId: SushiSwapV2ChainId!): [V2LiquidityPosition!]! + v2LiquidityPositions( + user: Bytes! + chainId: SushiSwapV2ChainId! + ): [V2LiquidityPosition!]! v2Swaps(address: Bytes!, chainId: SushiSwapV2ChainId!): [V2Swap!]! v2Burns(address: Bytes!, chainId: SushiSwapV2ChainId!): [V2Burn!]! v2Mints(address: Bytes!, chainId: SushiSwapV2ChainId!): [V2Mint!]! - v2Transactions(address: Bytes!, chainId: SushiSwapV2ChainId!): [V2Transaction!]! + v2Transactions( + address: Bytes! + chainId: SushiSwapV2ChainId! + ): [V2Transaction!]! v3Swaps(address: Bytes!, chainId: SushiSwapV3ChainId!): [V3Swap!]! v3Burns(address: Bytes!, chainId: SushiSwapV3ChainId!): [V3Burn!]! v3Mints(address: Bytes!, chainId: SushiSwapV3ChainId!): [V3Mint!]! v3Collects(address: Bytes!, chainId: SushiSwapV3ChainId!): [V3Collect!]! - v3Transactions(address: Bytes!, chainId: SushiSwapV3ChainId!): [V3Transaction!]! + v3Transactions( + address: Bytes! + chainId: SushiSwapV3ChainId! + ): [V3Transaction!]! } enum PoolsOrderBy { @@ -476,31 +509,49 @@ A field whose value is a byte string: https://en.wikipedia.org/wiki/Byte_string """ scalar Bytes -"""A field whose value is a bigint""" +""" +A field whose value is a bigint +""" scalar BigInt -"""A field whose value is a chain ID""" +""" +A field whose value is a chain ID +""" scalar ChainId -"""A field whose value is a sushi swap chain ID""" +""" +A field whose value is a sushi swap chain ID +""" scalar SushiSwapChainId -"""A field whose value is a sushi swap v2 chain ID""" +""" +A field whose value is a sushi swap v2 chain ID +""" scalar SushiSwapV2ChainId -"""A field whose value is a sushi swap v3 chain ID""" +""" +A field whose value is a sushi swap v3 chain ID +""" scalar SushiSwapV3ChainId -"""A field whose value is a pool chain ID""" +""" +A field whose value is a pool chain ID +""" scalar PoolChainId -"""A field whose value is a smart pool chain ID""" +""" +A field whose value is a smart pool chain ID +""" scalar SmartPoolChainId -"""A field whose value is a token list chain ID""" +""" +A field whose value is a token list chain ID +""" scalar TokenListChainId -"""A field whose value is a trending token list chain ID""" +""" +A field whose value is a trending token list chain ID +""" scalar TrendingTokensChainId enum ChainIdFeature { @@ -826,4 +877,4 @@ type V3Transaction { swaps: [V3Swap] burns: [V3Burn] mints: [V3Mint] -} \ No newline at end of file +} diff --git a/packages/graph-client/src/subgraphs/data-api/types/PoolChainId.ts b/packages/graph-client/src/subgraphs/data-api/types/PoolChainId.ts index 3542c292ce..d6da6f555b 100644 --- a/packages/graph-client/src/subgraphs/data-api/types/PoolChainId.ts +++ b/packages/graph-client/src/subgraphs/data-api/types/PoolChainId.ts @@ -1,6 +1,12 @@ // This file is auto-generated by scripts/update-data-api-types.ts import type { ChainId } from 'sushi/chain' -export const PoolChainIds = [42161,42170,43114,8453,288,56288,56,42220,1,250,122,100,11235,1666600000,1284,1285,137,534352,2222,1088,199,314,7000,1116,108,10,59144,1101,81457,2046399126,30,146,43111,11155111] as const -export type PoolChainId = typeof PoolChainIds[number] -export function isPoolChainId(value: ChainId): value is PoolChainId {return PoolChainIds.includes(value as PoolChainId)} \ No newline at end of file +export const PoolChainIds = [ + 42161, 42170, 43114, 8453, 288, 56288, 56, 42220, 1, 250, 122, 100, 11235, + 1666600000, 1284, 1285, 137, 534352, 2222, 1088, 199, 314, 7000, 1116, 108, + 10, 59144, 1101, 81457, 2046399126, 30, 146, 43111, 11155111, +] as const +export type PoolChainId = (typeof PoolChainIds)[number] +export function isPoolChainId(value: ChainId): value is PoolChainId { + return PoolChainIds.includes(value as PoolChainId) +} diff --git a/packages/graph-client/src/subgraphs/data-api/types/SmartPoolChainId.ts b/packages/graph-client/src/subgraphs/data-api/types/SmartPoolChainId.ts index 7daba64ec1..c839d6394a 100644 --- a/packages/graph-client/src/subgraphs/data-api/types/SmartPoolChainId.ts +++ b/packages/graph-client/src/subgraphs/data-api/types/SmartPoolChainId.ts @@ -1,6 +1,11 @@ // This file is auto-generated by scripts/update-data-api-types.ts import type { ChainId } from 'sushi/chain' -export const SmartPoolChainIds = [137,56,10,42161,1088,8453,43114,1101,2222,59144,534352,250,81457,30,314] as const -export type SmartPoolChainId = typeof SmartPoolChainIds[number] -export function isSmartPoolChainId(value: ChainId): value is SmartPoolChainId {return SmartPoolChainIds.includes(value as SmartPoolChainId)} \ No newline at end of file +export const SmartPoolChainIds = [ + 137, 56, 10, 42161, 1088, 8453, 43114, 1101, 2222, 59144, 534352, 250, 81457, + 30, 314, +] as const +export type SmartPoolChainId = (typeof SmartPoolChainIds)[number] +export function isSmartPoolChainId(value: ChainId): value is SmartPoolChainId { + return SmartPoolChainIds.includes(value as SmartPoolChainId) +} diff --git a/packages/graph-client/src/subgraphs/data-api/types/TokenListChainId.ts b/packages/graph-client/src/subgraphs/data-api/types/TokenListChainId.ts index 5bb5bfc081..d2b0ce4d49 100644 --- a/packages/graph-client/src/subgraphs/data-api/types/TokenListChainId.ts +++ b/packages/graph-client/src/subgraphs/data-api/types/TokenListChainId.ts @@ -1,6 +1,13 @@ // This file is auto-generated by scripts/update-data-api-types.ts import type { ChainId } from 'sushi/chain' -export const TokenListChainIds = [42161,42170,43114,8453,288,56288,56,42220,1,250,122,100,11235,1666600000,1284,1285,137,534352,2222,1088,199,314,7000,1116,108,10,59144,1101,81457,2046399126,30,146,43111,11155111,25,5000,324,169,34443,167000,810180,33139] as const -export type TokenListChainId = typeof TokenListChainIds[number] -export function isTokenListChainId(value: ChainId): value is TokenListChainId {return TokenListChainIds.includes(value as TokenListChainId)} \ No newline at end of file +export const TokenListChainIds = [ + 42161, 42170, 43114, 8453, 288, 56288, 56, 42220, 1, 250, 122, 100, 11235, + 1666600000, 1284, 1285, 137, 534352, 2222, 1088, 199, 314, 7000, 1116, 108, + 10, 59144, 1101, 81457, 2046399126, 30, 146, 43111, 11155111, 25, 5000, 324, + 169, 34443, 167000, 810180, 33139, +] as const +export type TokenListChainId = (typeof TokenListChainIds)[number] +export function isTokenListChainId(value: ChainId): value is TokenListChainId { + return TokenListChainIds.includes(value as TokenListChainId) +} diff --git a/packages/graph-client/src/subgraphs/data-api/types/TrendingTokensChainId.ts b/packages/graph-client/src/subgraphs/data-api/types/TrendingTokensChainId.ts index 3b1cd701fc..520c26e42b 100644 --- a/packages/graph-client/src/subgraphs/data-api/types/TrendingTokensChainId.ts +++ b/packages/graph-client/src/subgraphs/data-api/types/TrendingTokensChainId.ts @@ -1,6 +1,15 @@ // This file is auto-generated by scripts/update-data-api-types.ts import type { ChainId } from 'sushi/chain' -export const TrendingTokensChainIds = [42161,42170,43114,8453,288,56288,56,42220,1,250,122,100,1666600000,1284,137,534352,2222,1088,199,7000,1116,108,10,59144,1101,81457,2046399126,30,146,25,5000,324,169,34443,167000,810180,33139] as const -export type TrendingTokensChainId = typeof TrendingTokensChainIds[number] -export function isTrendingTokensChainId(value: ChainId): value is TrendingTokensChainId {return TrendingTokensChainIds.includes(value as TrendingTokensChainId)} \ No newline at end of file +export const TrendingTokensChainIds = [ + 42161, 42170, 43114, 8453, 288, 56288, 56, 42220, 1, 250, 122, 100, + 1666600000, 1284, 137, 534352, 2222, 1088, 199, 7000, 1116, 108, 10, 59144, + 1101, 81457, 2046399126, 30, 146, 25, 5000, 324, 169, 34443, 167000, 810180, + 33139, +] as const +export type TrendingTokensChainId = (typeof TrendingTokensChainIds)[number] +export function isTrendingTokensChainId( + value: ChainId, +): value is TrendingTokensChainId { + return TrendingTokensChainIds.includes(value as TrendingTokensChainId) +} diff --git a/packages/graph-client/src/subgraphs/furo/queries/tokens.ts b/packages/graph-client/src/subgraphs/furo/queries/tokens.ts index fe43c6599e..ee615c319a 100644 --- a/packages/graph-client/src/subgraphs/furo/queries/tokens.ts +++ b/packages/graph-client/src/subgraphs/furo/queries/tokens.ts @@ -2,15 +2,15 @@ import type { VariablesOf } from 'gql.tada' import type { FuroChainId } from 'sushi/config' import { getFuroSubgraphUrl } from 'sushi/config/subgraph' +import { getSubgraphUrl } from 'src/lib/get-subgraph-url.js' import { addChainId } from 'src/lib/modifiers/add-chain-id.js' import { convertIdToMultichainId } from 'src/lib/modifiers/convert-id-to-multichain-id.js' import { copyIdToAddress } from 'src/lib/modifiers/copy-id-to-address.js' -import type { RequestOptions } from 'src/lib/request.js' import { requestPaged } from 'src/lib/request-paged.js' +import type { RequestOptions } from 'src/lib/request.js' import type { ChainIdVariable } from 'src/lib/types/chainId.js' import type { Hex } from 'src/lib/types/hex.js' import { graphql } from '../graphql.js' -import { getSubgraphUrl } from 'src/lib/get-subgraph-url.js' export const FuroTokensQuery = graphql(` query Tokens($first: Int = 1000, $skip: Int = 0, $block: Block_height, $orderBy: Token_orderBy, $orderDirection: OrderDirection, $where: Token_filter) { diff --git a/packages/graph-client/src/subgraphs/furo/schema.graphql b/packages/graph-client/src/subgraphs/furo/schema.graphql index 60a1e3c975..13d18abd52 100644 --- a/packages/graph-client/src/subgraphs/furo/schema.graphql +++ b/packages/graph-client/src/subgraphs/furo/schema.graphql @@ -3,7 +3,9 @@ Marks the GraphQL type as indexable entity. Each type that should be an entity """ directive @entity on OBJECT -"""Defined a Subgraph ID for an object type""" +""" +Defined a Subgraph ID for an object type +""" directive @subgraphId(id: String!) on OBJECT """ @@ -87,7 +89,9 @@ input Global_filter { transactionCount_in: [BigInt!] transactionCount_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Global_filter] or: [Global_filter] @@ -104,7 +108,9 @@ enum Global_orderBy { "8 bytes signed integer\n" scalar Int8 -"""Defines the order direction, either ascending or descending""" +""" +Defines the order direction, either ascending or descending +""" enum OrderDirection { asc desc @@ -382,24 +388,35 @@ type Query { subgraphError: _SubgraphErrorPolicy_! = deny ): [TokenDaySnapshot!]! - """Access to subgraph metadata""" + """ + Access to subgraph metadata + """ _meta(block: Block_height): _Meta_ } type Rebase { - """ Token address """ + """ + Token address + """ id: ID! - """ Token this rebase belongs to """ + """ + Token this rebase belongs to + """ token: Token! - """ Base (Share) """ + """ + Base (Share) + """ base: BigInt! - """ Elastic (Amount) """ + """ + Elastic (Amount) + """ elastic: BigInt! - """ created at block, used internally to know when to ignore updates for the rebase + """ + created at block, used internally to know when to ignore updates for the rebase """ createdAtBlock: BigInt! } @@ -459,7 +476,9 @@ input Rebase_filter { createdAtBlock_in: [BigInt!] createdAtBlock_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Rebase_filter] or: [Rebase_filter] @@ -487,27 +506,39 @@ type Stream { id: ID! recipient: User! - """ Initial shares """ + """ + Initial shares + """ initialShares: BigInt! - """ Initial amount """ + """ + Initial amount + """ initialAmount: BigInt! - """ Extended shares, increases for each time the stream is updated with a top up amount + """ + Extended shares, increases for each time the stream is updated with a top up amount """ extendedShares: BigInt! - """ Set when a stream is extended, useful to keep track of how much many shares a stream contained after extending + """ + Set when a stream is extended, useful to keep track of how much many shares a stream contained after extending """ initialSharesExtended: BigInt! - """ Amount that has been withdrawn after extending the stream """ + """ + Amount that has been withdrawn after extending the stream + """ withdrawnAmountAfterExtension: BigInt! - """ Remaining shares """ + """ + Remaining shares + """ remainingShares: BigInt! - """ Withdrawn amount """ + """ + Withdrawn amount + """ withdrawnAmount: BigInt! token: Token! status: FuroStatus! @@ -754,7 +785,9 @@ input Stream_filter { modifiedAtTimestamp_in: [BigInt!] modifiedAtTimestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Stream_filter] or: [Stream_filter] @@ -1074,7 +1107,9 @@ type Subscription { subgraphError: _SubgraphErrorPolicy_! = deny ): [TokenDaySnapshot!]! - """Access to subgraph metadata""" + """ + Access to subgraph metadata + """ _meta(block: Block_height): _Meta_ } @@ -1096,7 +1131,9 @@ type Token { } type TokenDaySnapshot { - """ {tokenId}-day-{timestamp} """ + """ + {tokenId}-day-{timestamp} + """ id: ID! date: Int! token: Token! @@ -1168,7 +1205,9 @@ input TokenDaySnapshot_filter { transactionCount_in: [BigInt!] transactionCount_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [TokenDaySnapshot_filter] or: [TokenDaySnapshot_filter] @@ -1194,7 +1233,9 @@ enum TokenDaySnapshot_orderBy { } type TokenHourSnapshot { - """ {tokenId}-hour-{timestamp} """ + """ + {tokenId}-hour-{timestamp} + """ id: ID! date: Int! token: Token! @@ -1266,7 +1307,9 @@ input TokenHourSnapshot_filter { transactionCount_in: [BigInt!] transactionCount_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [TokenHourSnapshot_filter] or: [TokenHourSnapshot_filter] @@ -1406,7 +1449,9 @@ input Token_filter { createdAtTimestamp_in: [BigInt!] createdAtTimestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Token_filter] or: [Token_filter] @@ -1597,7 +1642,9 @@ input Transaction_filter { createdAtTimestamp_in: [BigInt!] createdAtTimestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Transaction_filter] or: [Transaction_filter] @@ -1674,10 +1721,34 @@ enum Transaction_orderBy { type User { id: ID! - incomingStreams(skip: Int = 0, first: Int = 100, orderBy: Stream_orderBy, orderDirection: OrderDirection, where: Stream_filter): [Stream!]! - outgoingStreams(skip: Int = 0, first: Int = 100, orderBy: Stream_orderBy, orderDirection: OrderDirection, where: Stream_filter): [Stream!]! - incomingVestings(skip: Int = 0, first: Int = 100, orderBy: Vesting_orderBy, orderDirection: OrderDirection, where: Vesting_filter): [Vesting!]! - outgoingVestings(skip: Int = 0, first: Int = 100, orderBy: Vesting_orderBy, orderDirection: OrderDirection, where: Vesting_filter): [Vesting!]! + incomingStreams( + skip: Int = 0 + first: Int = 100 + orderBy: Stream_orderBy + orderDirection: OrderDirection + where: Stream_filter + ): [Stream!]! + outgoingStreams( + skip: Int = 0 + first: Int = 100 + orderBy: Stream_orderBy + orderDirection: OrderDirection + where: Stream_filter + ): [Stream!]! + incomingVestings( + skip: Int = 0 + first: Int = 100 + orderBy: Vesting_orderBy + orderDirection: OrderDirection + where: Vesting_filter + ): [Vesting!]! + outgoingVestings( + skip: Int = 0 + first: Int = 100 + orderBy: Vesting_orderBy + orderDirection: OrderDirection + where: Vesting_filter + ): [Vesting!]! createdAtBlock: BigInt! createdAtTimestamp: BigInt! } @@ -1712,7 +1783,9 @@ input User_filter { createdAtTimestamp_in: [BigInt!] createdAtTimestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [User_filter] or: [User_filter] @@ -1737,16 +1810,24 @@ type Vesting { cliffShares: BigInt! stepShares: BigInt! - """ shares remaining, decreases on withdrawal """ + """ + shares remaining, decreases on withdrawal + """ remainingShares: BigInt! - """ Initial shares """ + """ + Initial shares + """ initialShares: BigInt! - """ Initial amount """ + """ + Initial amount + """ initialAmount: BigInt! - """ Withdrawn amount """ + """ + Withdrawn amount + """ withdrawnAmount: BigInt! token: Token! status: FuroStatus! @@ -2009,7 +2090,9 @@ input Vesting_filter { cancelledAtTimestamp_in: [BigInt!] cancelledAtTimestamp_not_in: [BigInt!] - """Filter for the block changed event.""" + """ + Filter for the block changed event. + """ _change_block: BlockChangedFilter and: [Vesting_filter] or: [Vesting_filter] @@ -2060,37 +2143,53 @@ enum Vesting_orderBy { } type _Block_ { - """The hash of the block""" + """ + The hash of the block + """ hash: Bytes - """The block number""" + """ + The block number + """ number: Int! - """Integer representation of the timestamp stored in blocks for the chain""" + """ + Integer representation of the timestamp stored in blocks for the chain + """ timestamp: Int - """The hash of the parent block""" + """ + The hash of the parent block + """ parentHash: Bytes } -"""The type for the top-level _meta field""" +""" +The type for the top-level _meta field +""" type _Meta_ { "Information about a specific subgraph block. The hash of the block\nwill be null if the _meta field has a block constraint that asks for\na block number. It will be filled if the _meta field has no block constraint\nand therefore asks for the latest block\n" block: _Block_! - """The deployment ID""" + """ + The deployment ID + """ deployment: String! - """If `true`, the subgraph encountered indexing errors at some past block""" + """ + If `true`, the subgraph encountered indexing errors at some past block + """ hasIndexingErrors: Boolean! } enum _SubgraphErrorPolicy_ { - """Data will be returned even if the subgraph has indexing errors""" + """ + Data will be returned even if the subgraph has indexing errors + """ allow """ If the subgraph has indexing errors, data will be omitted. The default. """ deny -} \ No newline at end of file +} diff --git a/packages/graph-client/src/subgraphs/strapi/queries/academy-articles.ts b/packages/graph-client/src/subgraphs/strapi/queries/academy-articles.ts index 5f6fd90d32..458c463e9c 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/academy-articles.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/academy-articles.ts @@ -1,11 +1,11 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' -import { ImageFieldsFragment } from 'src/subgraphs/strapi/fragments/image-fields.js' -import { AuthorFieldsFragment } from 'src/subgraphs/strapi/fragments/author-fields.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { AuthorFieldsFragment } from 'src/subgraphs/strapi/fragments/author-fields.js' +import { ImageFieldsFragment } from 'src/subgraphs/strapi/fragments/image-fields.js' import { transformImage } from 'src/subgraphs/strapi/transforms/transform-image.js' +import { graphql } from '../graphql.js' export const StrapiAcademyArticlesQuery = graphql( `query AcademyArticles($filters: ArticleFiltersInput, $pagination: PaginationArg, $publicationState: PublicationState = LIVE, $sort: [String] = ["publishedAt:desc"]) { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/banners.ts b/packages/graph-client/src/subgraphs/strapi/queries/banners.ts index 1907504946..2d599877c0 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/banners.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/banners.ts @@ -1,9 +1,9 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' import { ImageFieldsFragment } from 'src/subgraphs/strapi/fragments/image-fields.js' +import { graphql } from '../graphql.js' export const StrapiBannersQuery = graphql( `query Banners { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/blog-articles.ts b/packages/graph-client/src/subgraphs/strapi/queries/blog-articles.ts index 316b90ed22..5c4ddfb859 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/blog-articles.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/blog-articles.ts @@ -1,11 +1,11 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' -import { ImageFieldsFragment } from 'src/subgraphs/strapi/fragments/image-fields.js' -import { AuthorFieldsFragment } from 'src/subgraphs/strapi/fragments/author-fields.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { AuthorFieldsFragment } from 'src/subgraphs/strapi/fragments/author-fields.js' +import { ImageFieldsFragment } from 'src/subgraphs/strapi/fragments/image-fields.js' import { transformImage } from 'src/subgraphs/strapi/transforms/transform-image.js' +import { graphql } from '../graphql.js' export const StrapiBlogArticlesQuery = graphql( `query BlogArticles($filters: ArticleFiltersInput, $pagination: PaginationArg, $publicationState: PublicationState = LIVE, $sort: [String] = ["publishedAt:desc"]) { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/difficulties.ts b/packages/graph-client/src/subgraphs/strapi/queries/difficulties.ts index c20bc018ed..9790064675 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/difficulties.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/difficulties.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { graphql } from '../graphql.js' export const StrapiDifficultiesQuery = graphql( `query Difficulties { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/faq-answer-group.ts b/packages/graph-client/src/subgraphs/strapi/queries/faq-answer-group.ts index 17ac3ef796..a6f456923f 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/faq-answer-group.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/faq-answer-group.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { graphql } from '../graphql.js' export const StrapiFaqAnswerGroupQuery = graphql( `query FaqAnswerGroup($slug: String!) { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/faq-answer-search.ts b/packages/graph-client/src/subgraphs/strapi/queries/faq-answer-search.ts index 4928937253..78d348f30a 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/faq-answer-search.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/faq-answer-search.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { graphql } from '../graphql.js' export const StrapiFaqAnswerSearchQuery = graphql( `query FaqAnswerSearch($search: String!, $pagination: PaginationArg = {}) { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/faq-answers.ts b/packages/graph-client/src/subgraphs/strapi/queries/faq-answers.ts index 532b69e164..872a57aa80 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/faq-answers.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/faq-answers.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { graphql } from '../graphql.js' export const StrapiFaqAnswersQuery = graphql( `query FaqAnswers($filters: FaqAnswerFiltersInput, $pagination: PaginationArg, $publicationState: PublicationState = LIVE, $sort: [String] = ["publishedAt:desc"]) { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/faq-categories.ts b/packages/graph-client/src/subgraphs/strapi/queries/faq-categories.ts index 24209dc1c4..ba7bb1edf5 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/faq-categories.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/faq-categories.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { graphql } from '../graphql.js' export const StrapiFaqCategoriesQuery = graphql( `query FaqCategories($filters: FaqCategoryFiltersInput, $pagination: PaginationArg, $publicationState: PublicationState = LIVE, $sort: [String] = ["publishedAt:desc"]) { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/faq-category.ts b/packages/graph-client/src/subgraphs/strapi/queries/faq-category.ts index 0a15054366..f858cbd61a 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/faq-category.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/faq-category.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { graphql } from '../graphql.js' export const StrapiFaqCategoryQuery = graphql( `query FaqCategory($slug: String!) { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/faq-most-searched.ts b/packages/graph-client/src/subgraphs/strapi/queries/faq-most-searched.ts index 8e49e90891..9ddc3fe2e6 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/faq-most-searched.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/faq-most-searched.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { graphql } from '../graphql.js' export const StrapiFaqMostSearchedQuery = graphql( `query FaqMostSearched { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/faq-products.ts b/packages/graph-client/src/subgraphs/strapi/queries/faq-products.ts index 5ff98734c2..ee477778e0 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/faq-products.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/faq-products.ts @@ -1,9 +1,9 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' import { ImageFieldsFragment } from '../fragments/image-fields.js' +import { graphql } from '../graphql.js' import { transformImage } from '../transforms/transform-image.js' export const StrapiFaqProductsQuery = graphql( diff --git a/packages/graph-client/src/subgraphs/strapi/queries/products.ts b/packages/graph-client/src/subgraphs/strapi/queries/products.ts index 782d281b38..674642b4c2 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/products.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/products.ts @@ -1,10 +1,10 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' import { ImageFieldsFragment } from 'src/subgraphs/strapi/fragments/image-fields.js' import { transformImage } from 'src/subgraphs/strapi/transforms/transform-image.js' +import { graphql } from '../graphql.js' export const StrapiProductsQuery = graphql( `query Products { diff --git a/packages/graph-client/src/subgraphs/strapi/queries/topics.ts b/packages/graph-client/src/subgraphs/strapi/queries/topics.ts index dca1d20f88..996148ac74 100644 --- a/packages/graph-client/src/subgraphs/strapi/queries/topics.ts +++ b/packages/graph-client/src/subgraphs/strapi/queries/topics.ts @@ -1,8 +1,8 @@ import type { VariablesOf } from 'gql.tada' -import { request, type RequestOptions } from 'src/lib/request.js' -import { graphql } from '../graphql.js' +import { type RequestOptions, request } from 'src/lib/request.js' import { STRAPI_GRAPHQL_URL } from 'src/subgraphs/strapi/constants.js' +import { graphql } from '../graphql.js' export const StrapiTopicsQuery = graphql( `query Topics { diff --git a/packages/graph-client/src/subgraphs/strapi/schema.graphql b/packages/graph-client/src/subgraphs/strapi/schema.graphql index 0a47ea51bf..51661acee1 100644 --- a/packages/graph-client/src/subgraphs/strapi/schema.graphql +++ b/packages/graph-client/src/subgraphs/strapi/schema.graphql @@ -13,7 +13,9 @@ The `BigInt` scalar type represents non-fractional signed whole numeric values. """ scalar Long -"""The `Upload` scalar type represents a file upload.""" +""" +The `Upload` scalar type represents a file upload. +""" scalar Upload type Error { @@ -269,7 +271,11 @@ type ComponentSharedSeo { type ComponentSharedSlider { id: ID! - files(filters: UploadFileFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): UploadFileRelationResponseCollection + files( + filters: UploadFileFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): UploadFileRelationResponseCollection } input ComponentSharedTableOfContentsEntryFiltersInput { @@ -288,7 +294,11 @@ type ComponentSharedTableOfContentsEntry { type ComponentSharedTableOfContents { id: ID! - entries(filters: ComponentSharedTableOfContentsEntryFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): [ComponentSharedTableOfContentsEntry] + entries( + filters: ComponentSharedTableOfContentsEntryFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): [ComponentSharedTableOfContentsEntry] header: String! } @@ -402,8 +412,16 @@ type UploadFolder { name: String! pathId: Int! parent: UploadFolderEntityResponse - children(filters: UploadFolderFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): UploadFolderRelationResponseCollection - files(filters: UploadFileFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): UploadFileRelationResponseCollection + children( + filters: UploadFolderFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): UploadFolderRelationResponseCollection + files( + filters: UploadFileFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): UploadFileRelationResponseCollection path: String! createdAt: DateTime updatedAt: DateTime @@ -560,8 +578,16 @@ type UsersPermissionsRole { name: String! description: String type: String - permissions(filters: UsersPermissionsPermissionFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): UsersPermissionsPermissionRelationResponseCollection - users(filters: UsersPermissionsUserFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): UsersPermissionsUserRelationResponseCollection + permissions( + filters: UsersPermissionsPermissionFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): UsersPermissionsPermissionRelationResponseCollection + users( + filters: UsersPermissionsUserFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): UsersPermissionsUserRelationResponseCollection createdAt: DateTime updatedAt: DateTime } @@ -639,7 +665,11 @@ type UsersPermissionsUserRelationResponseCollection { data: [UsersPermissionsUserEntity!]! } -union AboutBlocksDynamicZone = ComponentSharedMedia | ComponentSharedRichText | ComponentSharedSlider | Error +union AboutBlocksDynamicZone = + | ComponentSharedMedia + | ComponentSharedRichText + | ComponentSharedSlider + | Error scalar AboutBlocksDynamicZoneInput @@ -710,16 +740,44 @@ type Article { customDate: DateTime slug: String! cover: UploadFileEntityResponse! - authors(filters: AuthorFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): AuthorRelationResponseCollection - categories(filters: CategoryFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): CategoryRelationResponseCollection + authors( + filters: AuthorFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): AuthorRelationResponseCollection + categories( + filters: CategoryFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): CategoryRelationResponseCollection difficulty: DifficultyEntityResponse - topics(filters: TopicFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): TopicRelationResponseCollection - products(filters: ProductFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ProductRelationResponseCollection - articleTypes(filters: ArticleTypeFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleTypeRelationResponseCollection + topics( + filters: TopicFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): TopicRelationResponseCollection + products( + filters: ProductFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ProductRelationResponseCollection + articleTypes( + filters: ArticleTypeFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleTypeRelationResponseCollection createdAt: DateTime updatedAt: DateTime publishedAt: DateTime - localizations(filters: ArticleFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleRelationResponseCollection + localizations( + filters: ArticleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleRelationResponseCollection locale: String } @@ -761,7 +819,12 @@ input ArticleTypeInput { type ArticleType { type: String - types(filters: ArticleFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleRelationResponseCollection + types( + filters: ArticleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleRelationResponseCollection createdAt: DateTime updatedAt: DateTime publishedAt: DateTime @@ -811,7 +874,12 @@ type Author { avatar: UploadFileEntityResponse! email: String! handle: String! - articles(filters: ArticleFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleRelationResponseCollection + articles( + filters: ArticleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleRelationResponseCollection createdAt: DateTime updatedAt: DateTime } @@ -905,10 +973,19 @@ type Category { name: String! slug: String! description: String! - articles(filters: ArticleFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleRelationResponseCollection + articles( + filters: ArticleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleRelationResponseCollection createdAt: DateTime updatedAt: DateTime - localizations(filters: CategoryFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): CategoryRelationResponseCollection + localizations( + filters: CategoryFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): CategoryRelationResponseCollection locale: String } @@ -961,11 +1038,20 @@ type Difficulty { shortDescription: String longDescription: String slug: String - articles(filters: ArticleFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleRelationResponseCollection + articles( + filters: ArticleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleRelationResponseCollection label: String createdAt: DateTime updatedAt: DateTime - localizations(filters: DifficultyFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): DifficultyRelationResponseCollection + localizations( + filters: DifficultyFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): DifficultyRelationResponseCollection locale: String } @@ -1065,7 +1151,12 @@ input FaqAnswerGroupInput { type FaqAnswerGroup { name: String! - faqAnswers(filters: FaqAnswerFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): FaqAnswerRelationResponseCollection! + faqAnswers( + filters: FaqAnswerFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): FaqAnswerRelationResponseCollection! slug: String! faqDefaultAnswer: FaqAnswerEntityResponse faqCategory: FaqCategoryEntityResponse! @@ -1116,7 +1207,12 @@ input FaqCategoryInput { type FaqCategory { name: String! slug: String! - faqAnswerGroups(filters: FaqAnswerGroupFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): FaqAnswerGroupRelationResponseCollection! + faqAnswerGroups( + filters: FaqAnswerGroupFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): FaqAnswerGroupRelationResponseCollection! createdAt: DateTime updatedAt: DateTime publishedAt: DateTime @@ -1306,7 +1402,12 @@ type Product { description: String show: Boolean longName: String - articles(filters: ArticleFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleRelationResponseCollection + articles( + filters: ArticleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleRelationResponseCollection slug: String url: String relevantArticleIds: JSON @@ -1314,7 +1415,12 @@ type Product { createdAt: DateTime updatedAt: DateTime publishedAt: DateTime - localizations(filters: ProductFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ProductRelationResponseCollection + localizations( + filters: ProductFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ProductRelationResponseCollection locale: String } @@ -1361,11 +1467,21 @@ input TopicInput { type Topic { name: String slug: String - articles(filters: ArticleFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleRelationResponseCollection + articles( + filters: ArticleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleRelationResponseCollection createdAt: DateTime updatedAt: DateTime publishedAt: DateTime - localizations(filters: TopicFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): TopicRelationResponseCollection + localizations( + filters: TopicFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): TopicRelationResponseCollection locale: String } @@ -1406,7 +1522,38 @@ type TrendingSearchEntityResponse { data: TrendingSearchEntity } -union GenericMorph = ComponentSharedDivider | ComponentSharedMedia | ComponentSharedRichText | ComponentSharedSeo | ComponentSharedSlider | ComponentSharedTableOfContentsEntry | ComponentSharedTableOfContents | UploadFile | UploadFolder | SchedulerScheduler | I18NLocale | UsersPermissionsPermission | UsersPermissionsRole | UsersPermissionsUser | About | Article | ArticleType | Author | Banner | Category | Difficulty | FaqAnswer | FaqAnswerGroup | FaqCategory | FaqMostSearched | FaqProduct | Global | GlobalAcademy | Product | Topic | TrendingSearch +union GenericMorph = + | ComponentSharedDivider + | ComponentSharedMedia + | ComponentSharedRichText + | ComponentSharedSeo + | ComponentSharedSlider + | ComponentSharedTableOfContentsEntry + | ComponentSharedTableOfContents + | UploadFile + | UploadFolder + | SchedulerScheduler + | I18NLocale + | UsersPermissionsPermission + | UsersPermissionsRole + | UsersPermissionsUser + | About + | Article + | ArticleType + | Author + | Banner + | Category + | Difficulty + | FaqAnswer + | FaqAnswerGroup + | FaqCategory + | FaqMostSearched + | FaqProduct + | Global + | GlobalAcademy + | Product + | Topic + | TrendingSearch input FileInfoInput { name: String @@ -1414,7 +1561,9 @@ input FileInfoInput { caption: String } -"""A string used to identify an i18n locale""" +""" +A string used to identify an i18n locale +""" scalar I18NLocaleCode type UsersPermissionsMe { @@ -1475,45 +1624,131 @@ input PaginationArg { type Query { uploadFile(id: ID): UploadFileEntityResponse - uploadFiles(filters: UploadFileFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): UploadFileEntityResponseCollection! + uploadFiles( + filters: UploadFileFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): UploadFileEntityResponseCollection! uploadFolder(id: ID): UploadFolderEntityResponse - uploadFolders(filters: UploadFolderFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): UploadFolderEntityResponseCollection! - schedulerScheduler(filters: SchedulerSchedulerFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): SchedulerSchedulerEntityResponseCollection! + uploadFolders( + filters: UploadFolderFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): UploadFolderEntityResponseCollection! + schedulerScheduler( + filters: SchedulerSchedulerFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): SchedulerSchedulerEntityResponseCollection! i18NLocale(id: ID): I18NLocaleEntityResponse - i18NLocales(filters: I18NLocaleFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): I18NLocaleEntityResponseCollection! + i18NLocales( + filters: I18NLocaleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): I18NLocaleEntityResponseCollection! usersPermissionsRole(id: ID): UsersPermissionsRoleEntityResponse - usersPermissionsRoles(filters: UsersPermissionsRoleFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): UsersPermissionsRoleEntityResponseCollection! + usersPermissionsRoles( + filters: UsersPermissionsRoleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): UsersPermissionsRoleEntityResponseCollection! usersPermissionsUser(id: ID): UsersPermissionsUserEntityResponse - usersPermissionsUsers(filters: UsersPermissionsUserFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): UsersPermissionsUserEntityResponseCollection! + usersPermissionsUsers( + filters: UsersPermissionsUserFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): UsersPermissionsUserEntityResponseCollection! about: AboutEntityResponse article(id: ID, locale: I18NLocaleCode): ArticleEntityResponse - articles(filters: ArticleFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleEntityResponseCollection! + articles( + filters: ArticleFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleEntityResponseCollection! articleType(id: ID): ArticleTypeEntityResponse - articleTypes(filters: ArticleTypeFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ArticleTypeEntityResponseCollection! + articleTypes( + filters: ArticleTypeFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ArticleTypeEntityResponseCollection! author(id: ID): AuthorEntityResponse - authors(filters: AuthorFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): AuthorEntityResponseCollection! + authors( + filters: AuthorFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): AuthorEntityResponseCollection! banner(id: ID): BannerEntityResponse - banners(filters: BannerFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): BannerEntityResponseCollection! + banners( + filters: BannerFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): BannerEntityResponseCollection! category(id: ID, locale: I18NLocaleCode): CategoryEntityResponse - categories(filters: CategoryFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): CategoryEntityResponseCollection! + categories( + filters: CategoryFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): CategoryEntityResponseCollection! difficulty(id: ID, locale: I18NLocaleCode): DifficultyEntityResponse - difficulties(filters: DifficultyFiltersInput, pagination: PaginationArg = {}, sort: [String] = []): DifficultyEntityResponseCollection! + difficulties( + filters: DifficultyFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + ): DifficultyEntityResponseCollection! faqAnswer(id: ID): FaqAnswerEntityResponse - faqAnswers(filters: FaqAnswerFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): FaqAnswerEntityResponseCollection! + faqAnswers( + filters: FaqAnswerFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): FaqAnswerEntityResponseCollection! faqAnswerGroup(id: ID): FaqAnswerGroupEntityResponse - faqAnswerGroups(filters: FaqAnswerGroupFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): FaqAnswerGroupEntityResponseCollection! + faqAnswerGroups( + filters: FaqAnswerGroupFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): FaqAnswerGroupEntityResponseCollection! faqCategory(id: ID): FaqCategoryEntityResponse - faqCategories(filters: FaqCategoryFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): FaqCategoryEntityResponseCollection! + faqCategories( + filters: FaqCategoryFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): FaqCategoryEntityResponseCollection! faqMostSearched(id: ID): FaqMostSearchedEntityResponse - faqMostSearcheds(filters: FaqMostSearchedFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): FaqMostSearchedEntityResponseCollection! + faqMostSearcheds( + filters: FaqMostSearchedFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): FaqMostSearchedEntityResponseCollection! faqProduct(id: ID): FaqProductEntityResponse - faqProducts(filters: FaqProductFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): FaqProductEntityResponseCollection! + faqProducts( + filters: FaqProductFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): FaqProductEntityResponseCollection! global: GlobalEntityResponse globalAcademy: GlobalAcademyEntityResponse product(id: ID, locale: I18NLocaleCode): ProductEntityResponse - products(filters: ProductFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): ProductEntityResponseCollection! + products( + filters: ProductFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): ProductEntityResponseCollection! topic(id: ID, locale: I18NLocaleCode): TopicEntityResponse - topics(filters: TopicFiltersInput, pagination: PaginationArg = {}, sort: [String] = [], publicationState: PublicationState = LIVE): TopicEntityResponseCollection! + topics( + filters: TopicFiltersInput + pagination: PaginationArg = {} + sort: [String] = [] + publicationState: PublicationState = LIVE + ): TopicEntityResponseCollection! trendingSearch: TrendingSearchEntityResponse me: UsersPermissionsMe } @@ -1523,15 +1758,30 @@ type Mutation { updateUploadFile(id: ID!, data: UploadFileInput!): UploadFileEntityResponse deleteUploadFile(id: ID!): UploadFileEntityResponse createUploadFolder(data: UploadFolderInput!): UploadFolderEntityResponse - updateUploadFolder(id: ID!, data: UploadFolderInput!): UploadFolderEntityResponse + updateUploadFolder( + id: ID! + data: UploadFolderInput! + ): UploadFolderEntityResponse deleteUploadFolder(id: ID!): UploadFolderEntityResponse - createSchedulerScheduler(data: SchedulerSchedulerInput!): SchedulerSchedulerEntityResponse - updateSchedulerScheduler(id: ID!, data: SchedulerSchedulerInput!): SchedulerSchedulerEntityResponse + createSchedulerScheduler( + data: SchedulerSchedulerInput! + ): SchedulerSchedulerEntityResponse + updateSchedulerScheduler( + id: ID! + data: SchedulerSchedulerInput! + ): SchedulerSchedulerEntityResponse deleteSchedulerScheduler(id: ID!): SchedulerSchedulerEntityResponse updateAbout(data: AboutInput!): AboutEntityResponse deleteAbout: AboutEntityResponse - createArticle(data: ArticleInput!, locale: I18NLocaleCode): ArticleEntityResponse - updateArticle(id: ID!, data: ArticleInput!, locale: I18NLocaleCode): ArticleEntityResponse + createArticle( + data: ArticleInput! + locale: I18NLocaleCode + ): ArticleEntityResponse + updateArticle( + id: ID! + data: ArticleInput! + locale: I18NLocaleCode + ): ArticleEntityResponse deleteArticle(id: ID!, locale: I18NLocaleCode): ArticleEntityResponse createArticleType(data: ArticleTypeInput!): ArticleTypeEntityResponse updateArticleType(id: ID!, data: ArticleTypeInput!): ArticleTypeEntityResponse @@ -1542,23 +1792,45 @@ type Mutation { createBanner(data: BannerInput!): BannerEntityResponse updateBanner(id: ID!, data: BannerInput!): BannerEntityResponse deleteBanner(id: ID!): BannerEntityResponse - createCategory(data: CategoryInput!, locale: I18NLocaleCode): CategoryEntityResponse - updateCategory(id: ID!, data: CategoryInput!, locale: I18NLocaleCode): CategoryEntityResponse + createCategory( + data: CategoryInput! + locale: I18NLocaleCode + ): CategoryEntityResponse + updateCategory( + id: ID! + data: CategoryInput! + locale: I18NLocaleCode + ): CategoryEntityResponse deleteCategory(id: ID!, locale: I18NLocaleCode): CategoryEntityResponse - createDifficulty(data: DifficultyInput!, locale: I18NLocaleCode): DifficultyEntityResponse - updateDifficulty(id: ID!, data: DifficultyInput!, locale: I18NLocaleCode): DifficultyEntityResponse + createDifficulty( + data: DifficultyInput! + locale: I18NLocaleCode + ): DifficultyEntityResponse + updateDifficulty( + id: ID! + data: DifficultyInput! + locale: I18NLocaleCode + ): DifficultyEntityResponse deleteDifficulty(id: ID!, locale: I18NLocaleCode): DifficultyEntityResponse createFaqAnswer(data: FaqAnswerInput!): FaqAnswerEntityResponse updateFaqAnswer(id: ID!, data: FaqAnswerInput!): FaqAnswerEntityResponse deleteFaqAnswer(id: ID!): FaqAnswerEntityResponse createFaqAnswerGroup(data: FaqAnswerGroupInput!): FaqAnswerGroupEntityResponse - updateFaqAnswerGroup(id: ID!, data: FaqAnswerGroupInput!): FaqAnswerGroupEntityResponse + updateFaqAnswerGroup( + id: ID! + data: FaqAnswerGroupInput! + ): FaqAnswerGroupEntityResponse deleteFaqAnswerGroup(id: ID!): FaqAnswerGroupEntityResponse createFaqCategory(data: FaqCategoryInput!): FaqCategoryEntityResponse updateFaqCategory(id: ID!, data: FaqCategoryInput!): FaqCategoryEntityResponse deleteFaqCategory(id: ID!): FaqCategoryEntityResponse - createFaqMostSearched(data: FaqMostSearchedInput!): FaqMostSearchedEntityResponse - updateFaqMostSearched(id: ID!, data: FaqMostSearchedInput!): FaqMostSearchedEntityResponse + createFaqMostSearched( + data: FaqMostSearchedInput! + ): FaqMostSearchedEntityResponse + updateFaqMostSearched( + id: ID! + data: FaqMostSearchedInput! + ): FaqMostSearchedEntityResponse deleteFaqMostSearched(id: ID!): FaqMostSearchedEntityResponse createFaqProduct(data: FaqProductInput!): FaqProductEntityResponse updateFaqProduct(id: ID!, data: FaqProductInput!): FaqProductEntityResponse @@ -1567,57 +1839,137 @@ type Mutation { deleteGlobal: GlobalEntityResponse updateGlobalAcademy(data: GlobalAcademyInput!): GlobalAcademyEntityResponse deleteGlobalAcademy: GlobalAcademyEntityResponse - createProduct(data: ProductInput!, locale: I18NLocaleCode): ProductEntityResponse - updateProduct(id: ID!, data: ProductInput!, locale: I18NLocaleCode): ProductEntityResponse + createProduct( + data: ProductInput! + locale: I18NLocaleCode + ): ProductEntityResponse + updateProduct( + id: ID! + data: ProductInput! + locale: I18NLocaleCode + ): ProductEntityResponse deleteProduct(id: ID!, locale: I18NLocaleCode): ProductEntityResponse createTopic(data: TopicInput!, locale: I18NLocaleCode): TopicEntityResponse - updateTopic(id: ID!, data: TopicInput!, locale: I18NLocaleCode): TopicEntityResponse + updateTopic( + id: ID! + data: TopicInput! + locale: I18NLocaleCode + ): TopicEntityResponse deleteTopic(id: ID!, locale: I18NLocaleCode): TopicEntityResponse updateTrendingSearch(data: TrendingSearchInput!): TrendingSearchEntityResponse deleteTrendingSearch: TrendingSearchEntityResponse - upload(refId: ID, ref: String, field: String, info: FileInfoInput, file: Upload!): UploadFileEntityResponse! - multipleUpload(refId: ID, ref: String, field: String, files: [Upload]!): [UploadFileEntityResponse]! + upload( + refId: ID + ref: String + field: String + info: FileInfoInput + file: Upload! + ): UploadFileEntityResponse! + multipleUpload( + refId: ID + ref: String + field: String + files: [Upload]! + ): [UploadFileEntityResponse]! updateFileInfo(id: ID!, info: FileInfoInput): UploadFileEntityResponse! removeFile(id: ID!): UploadFileEntityResponse - createArticleLocalization(id: ID, data: ArticleInput, locale: I18NLocaleCode): ArticleEntityResponse - createCategoryLocalization(id: ID, data: CategoryInput, locale: I18NLocaleCode): CategoryEntityResponse - createDifficultyLocalization(id: ID, data: DifficultyInput, locale: I18NLocaleCode): DifficultyEntityResponse - createProductLocalization(id: ID, data: ProductInput, locale: I18NLocaleCode): ProductEntityResponse - createTopicLocalization(id: ID, data: TopicInput, locale: I18NLocaleCode): TopicEntityResponse + createArticleLocalization( + id: ID + data: ArticleInput + locale: I18NLocaleCode + ): ArticleEntityResponse + createCategoryLocalization( + id: ID + data: CategoryInput + locale: I18NLocaleCode + ): CategoryEntityResponse + createDifficultyLocalization( + id: ID + data: DifficultyInput + locale: I18NLocaleCode + ): DifficultyEntityResponse + createProductLocalization( + id: ID + data: ProductInput + locale: I18NLocaleCode + ): ProductEntityResponse + createTopicLocalization( + id: ID + data: TopicInput + locale: I18NLocaleCode + ): TopicEntityResponse - """Create a new role""" - createUsersPermissionsRole(data: UsersPermissionsRoleInput!): UsersPermissionsCreateRolePayload + """ + Create a new role + """ + createUsersPermissionsRole( + data: UsersPermissionsRoleInput! + ): UsersPermissionsCreateRolePayload - """Update an existing role""" - updateUsersPermissionsRole(id: ID!, data: UsersPermissionsRoleInput!): UsersPermissionsUpdateRolePayload + """ + Update an existing role + """ + updateUsersPermissionsRole( + id: ID! + data: UsersPermissionsRoleInput! + ): UsersPermissionsUpdateRolePayload - """Delete an existing role""" + """ + Delete an existing role + """ deleteUsersPermissionsRole(id: ID!): UsersPermissionsDeleteRolePayload - """Create a new user""" - createUsersPermissionsUser(data: UsersPermissionsUserInput!): UsersPermissionsUserEntityResponse! + """ + Create a new user + """ + createUsersPermissionsUser( + data: UsersPermissionsUserInput! + ): UsersPermissionsUserEntityResponse! - """Update an existing user""" - updateUsersPermissionsUser(id: ID!, data: UsersPermissionsUserInput!): UsersPermissionsUserEntityResponse! + """ + Update an existing user + """ + updateUsersPermissionsUser( + id: ID! + data: UsersPermissionsUserInput! + ): UsersPermissionsUserEntityResponse! - """Delete an existing user""" + """ + Delete an existing user + """ deleteUsersPermissionsUser(id: ID!): UsersPermissionsUserEntityResponse! login(input: UsersPermissionsLoginInput!): UsersPermissionsLoginPayload! - """Register a user""" + """ + Register a user + """ register(input: UsersPermissionsRegisterInput!): UsersPermissionsLoginPayload! - """Request a reset password token""" + """ + Request a reset password token + """ forgotPassword(email: String!): UsersPermissionsPasswordPayload """ Reset user password. Confirm with a code (resetToken from forgotPassword) """ - resetPassword(password: String!, passwordConfirmation: String!, code: String!): UsersPermissionsLoginPayload + resetPassword( + password: String! + passwordConfirmation: String! + code: String! + ): UsersPermissionsLoginPayload - """Change user password. Confirm with the current password.""" - changePassword(currentPassword: String!, password: String!, passwordConfirmation: String!): UsersPermissionsLoginPayload + """ + Change user password. Confirm with the current password. + """ + changePassword( + currentPassword: String! + password: String! + passwordConfirmation: String! + ): UsersPermissionsLoginPayload - """Confirm an email users email address""" + """ + Confirm an email users email address + """ emailConfirmation(confirmation: String!): UsersPermissionsLoginPayload -} \ No newline at end of file +} diff --git a/packages/ui/src/components/checkbox.tsx b/packages/ui/src/components/checkbox.tsx index c787b9e8f8..65ee692a16 100644 --- a/packages/ui/src/components/checkbox.tsx +++ b/packages/ui/src/components/checkbox.tsx @@ -13,7 +13,7 @@ const Checkbox = React.forwardRef< - extends React.HTMLAttributes { - column: Column - title: string - description?: string +type DataTableColumnHeaderProps = Omit< + React.HTMLAttributes, + 'title' +> & { + header: Header } export function DataTableColumnHeader({ - column, - title, - className, - description, + header, }: DataTableColumnHeaderProps) { const onClick = useCallback(() => { - if (column.getIsSorted() === false) { + if (header.column.getIsSorted() === false) { // desc - column.toggleSorting(true) + header.column.toggleSorting(true) } - if (column.getIsSorted() === 'desc') { + if (header.column.getIsSorted() === 'desc') { // asc - column.toggleSorting(false) + header.column.toggleSorting(false) } - if (column.getIsSorted() === 'asc') { + if (header.column.getIsSorted() === 'asc') { // clear - column.clearSorting() + header.column.clearSorting() } - }, [column]) + }, [header.column]) - // When the header (=title) is undefined, it becomes a function, can't render functions... - if (typeof title !== 'string') { - title = '' - } + const className = header?.column.columnDef?.meta?.header?.className + const description = header.column.columnDef?.meta?.header?.description + + const Title = useMemo(() => { + const title = header.column.columnDef.header + + if (typeof title === 'string') { + if (description) { + return ( + + {title} + + ) + } else { + return {title} + } + } - if (!column.getCanSort()) { + return flexRender(title, header.getContext()) + }, [header, description]) + + if (!header.column.getCanSort()) { return ( -
+
{description ? ( - - - {title} - - + {Title} {description} ) : ( - {title} + <>{Title} )}
) @@ -81,12 +90,10 @@ export function DataTableColumnHeader({
- - {title} - - {column.getIsSorted() === 'desc' ? ( + {Title} + {header.column.getIsSorted() === 'desc' ? ( - ) : column.getIsSorted() === 'asc' ? ( + ) : header.column.getIsSorted() === 'asc' ? ( ) : ( @@ -99,10 +106,10 @@ export function DataTableColumnHeader({ ) : (