diff --git a/extensions/polar/CHANGELOG.md b/extensions/polar/CHANGELOG.md index 8cd3e6eb292..96b77a16f1d 100644 --- a/extensions/polar/CHANGELOG.md +++ b/extensions/polar/CHANGELOG.md @@ -1,7 +1,9 @@ # Polar Changelog -## 2024-12-20 +## [Update] - 2024-12-20 +- Implements a View Customers command +- Properly checks if scopes are sufficient on authorization - Make sure to open Order on the correct page on https://polar.sh ## [Initial Version] - 2024-12-10 \ No newline at end of file diff --git a/extensions/polar/package-lock.json b/extensions/polar/package-lock.json index 4250a37046a..08b5998c402 100644 --- a/extensions/polar/package-lock.json +++ b/extensions/polar/package-lock.json @@ -7,7 +7,7 @@ "name": "polar", "license": "MIT", "dependencies": { - "@polar-sh/sdk": "^0.18.1", + "@polar-sh/sdk": "^0.19.2", "@raycast/api": "^1.69.0", "@raycast/utils": "^1.18.1", "@tanstack/react-query": "^5.62.3", @@ -915,9 +915,9 @@ } }, "node_modules/@polar-sh/sdk": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/@polar-sh/sdk/-/sdk-0.18.1.tgz", - "integrity": "sha512-+G2ccMTwdrf42GSInG9BCgTUu2AFiOkj/ylbvKRkKGK0gEA78kK0pygRC9HF9Fqx+9SmxiJZtp/7UmDKVgaDHw==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@polar-sh/sdk/-/sdk-0.19.2.tgz", + "integrity": "sha512-n1emRNmhcAzRfVAWBiVVCJ2krBSZ4wANVTRO7hCMchYCkxyV+kiRdjyvBdVG3JpRsS6SR7yBr2+CRJkuHPPeDg==", "dependencies": { "standardwebhooks": "^1.0.0" }, diff --git a/extensions/polar/package.json b/extensions/polar/package.json index 126af1ed524..af3db67c1c5 100644 --- a/extensions/polar/package.json +++ b/extensions/polar/package.json @@ -25,10 +25,18 @@ "description": "View your active subscriptions", "mode": "view", "icon": "command-icon.png" + }, + { + "name": "customers", + "title": "View Customers", + "subtitle": "Polar", + "description": "View your customers", + "mode": "view", + "icon": "command-icon.png" } ], "dependencies": { - "@polar-sh/sdk": "^0.18.1", + "@polar-sh/sdk": "^0.19.2", "@raycast/api": "^1.69.0", "@raycast/utils": "^1.18.1", "@tanstack/react-query": "^5.62.3", diff --git a/extensions/polar/src/customers.tsx b/extensions/polar/src/customers.tsx new file mode 100644 index 00000000000..75317b067f4 --- /dev/null +++ b/extensions/polar/src/customers.tsx @@ -0,0 +1,145 @@ +import { Action, ActionPanel, Detail, List } from "@raycast/api"; +import React, { useCallback, useEffect, useState } from "react"; +import { authenticate } from "./oauth"; +import { PolarProvider, queryClient } from "./providers"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { useCustomers } from "./hooks/customers"; +import { Customer } from "@polar-sh/sdk/models/components"; +import { useOrganization } from "./hooks/organizations"; + +export default function Command() { + const [accessToken, setAccessToken] = useState(); + + useEffect(() => { + authenticate().then(setAccessToken); + }, []); + + if (!accessToken) { + return ; + } + + return ( + + + + + + ); +} + +interface CustomerProps { + customer: Customer; +} + +const CustomerItem = ({ customer }: CustomerProps) => { + const { data: organization } = useOrganization(customer.organizationId); + + return ( + + + + + + + {/* Add billing info */} + + + + + + + } + /> + } + actions={ + + + + + } + accessories={[ + { + text: organization?.name, + }, + ]} + /> + ); +}; + +const CustomersView = () => { + const { + data: customers, + isLoading, + fetchNextPage, + hasNextPage, + } = useCustomers({}, 20); + + const handleLoadMore = useCallback(() => { + fetchNextPage(); + }, [fetchNextPage]); + + return ( + + {customers?.pages + .flatMap((page) => page.result.items) + .map((customer) => ( + + ))} + + ); +}; diff --git a/extensions/polar/src/hooks/customers.ts b/extensions/polar/src/hooks/customers.ts new file mode 100644 index 00000000000..b66e28be1f9 --- /dev/null +++ b/extensions/polar/src/hooks/customers.ts @@ -0,0 +1,27 @@ +import { CustomersListRequest } from "@polar-sh/sdk/models/operations"; +import { useInfiniteQuery } from "@tanstack/react-query"; +import { useContext } from "react"; +import { PolarContext } from "../providers"; + +export const useCustomers = ( + parameters: CustomersListRequest, + limit: number, +) => { + const polar = useContext(PolarContext); + + return useInfiniteQuery({ + queryKey: ["customers", parameters], + queryFn: ({ pageParam = 1 }) => + polar.customers.list({ ...parameters, page: pageParam, limit: limit }), + initialPageParam: 1, + getNextPageParam: (lastPage, pages) => { + const currentPage = pages.length; + const totalPages = Math.ceil( + lastPage.result.pagination.totalCount / limit, + ); + const nextPage = totalPages > currentPage ? currentPage + 1 : undefined; + + return nextPage; + }, + }); +}; diff --git a/extensions/polar/src/oauth.ts b/extensions/polar/src/oauth.ts index 954798f6b51..3252b0c7a02 100644 --- a/extensions/polar/src/oauth.ts +++ b/extensions/polar/src/oauth.ts @@ -3,6 +3,9 @@ import fetch from "node-fetch"; const CLIENT_ID = "polar_ci_emNfLiLOhk0njeLomDs14g"; +const SCOPES = + "openid profile email user:read organizations:read organizations:write products:read products:write benefits:read benefits:write subscriptions:read subscriptions:write orders:read metrics:read customers:read customers:write"; + async function fetchTokens( authRequest: OAuth.AuthorizationRequest, authCode: string, @@ -58,13 +61,12 @@ export const authenticate = async (): Promise => { const authRequest = await client.authorizationRequest({ endpoint: "https://polar.sh/oauth2/authorize", clientId: CLIENT_ID, - scope: - "openid profile email user:read organizations:read organizations:write products:read products:write benefits:read benefits:write subscriptions:read subscriptions:write orders:read metrics:read", + scope: SCOPES, }); const tokenSet = await client.getTokens(); - if (tokenSet?.accessToken) { + if (tokenSet?.accessToken && tokenSet.scope === SCOPES) { if (tokenSet.refreshToken && tokenSet.isExpired()) { const tokenResponse = await refreshTokens(tokenSet.refreshToken); await client.setTokens(tokenResponse);