Skip to content

Commit

Permalink
reforat imports
Browse files Browse the repository at this point in the history
  • Loading branch information
walteh committed Jun 26, 2024
1 parent c18a503 commit a8cd9cd
Show file tree
Hide file tree
Showing 16 changed files with 238 additions and 84 deletions.
4 changes: 3 additions & 1 deletion .vscode/unthread.me.code-workspace
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSaveMode": "file",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "always"
"source.fixAll.eslint": "always",
"source.organizeImports": "always",
"source.fixAll": "always"
},
"files.associations": {
"tsconfig.*json": "jsonc",
Expand Down
Binary file modified bun.lockb
Binary file not shown.
6 changes: 6 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,15 @@
"chai": "^5.1.1",
"daisyui": "^4.12.7",
"eslint": "^8.57.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-import-resolver-vite": "^2.0.1",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-module-resolver": "^1.5.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.34.3",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-refresh": "^0.4.7",
"eslint-plugin-unused-imports": "^4.0.0",
"express": "^4.19.2",
"ky": "^1.3.0",
"path": "^0.12.7",
Expand Down
3 changes: 1 addition & 2 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import Home from "@src/pages/Home";
import { FC } from "react";
import { Navigate, Route, Routes } from "react-router-dom";

import Home from "@src/pages/Home";

import useAccessTokenUpdater from "./hooks/useAccessTokenUpdater";

const App: FC = () => {
Expand Down
2 changes: 1 addition & 1 deletion src/components/LayoutHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { FC } from "react";
import Logo from "@src/components/Logo.tsx";
import { FC } from "react";
import Menu from "./Menu";

const LayoutHeader: FC = () => {
Expand Down
27 changes: 27 additions & 0 deletions src/components/UserThreadsView.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import useUserThreads from "@src/hooks/useUserThreads";

const UserInsightsViews = () => {
const [threads, isLoading, error] = useUserThreads();

if (isLoading) return <div>Loading...</div>;
if (error) return <div>Error: {error}</div>;

return (
<div>
{threads ? (
<div>
<h2>Threads</h2>
<ul>
{threads.map((thread) => (
<li key={thread.id}>{thread.text}</li>
))}
</ul>
</div>
) : (
<div>No threads data available</div>
)}
</div>
);
};

export default UserInsightsViews;
4 changes: 2 additions & 2 deletions src/hooks/useAccessTokenExpiresIn.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import useStore from "@src/threadsapi/store";
import { usePersistantStore } from "@src/threadsapi/store";
import { useState } from "react";

import { useInterval } from "usehooks-ts";

const useAccessTokenExpiresIn = (): number => {
const expirationTime = useStore((state) => state.access_token_expires_at);
const expirationTime = usePersistantStore((state) => state.access_token_expires_at);

const [expiresIn, setExpiresIn] = useState(expirationTime ? expirationTime - Date.now() : 0);

Expand Down
15 changes: 6 additions & 9 deletions src/hooks/useAccessTokenUpdater.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { exchangeCodeForAccessToken } from "@src/threadsapi/api";
import { useInMemoryStore, usePersistantStore } from "@src/threadsapi/store";
import ky from "ky";
import React from "react";
import { useSearchParams } from "react-router-dom";

import ky from "ky";

import useStore from "@src/threadsapi/store";
import { exchangeCodeForAccessToken } from "@src/threadsapi/api";
// import useAccessTokenExpiresIn from "./useAccessTokenExpiresIn";

const useAccessTokenUpdater = () => {
const [searchParams, setSearchParams] = useSearchParams();
const updateAccessToken = useStore((state) => state.updateAccessToken);
const updateIsLoggingIn = useStore((state) => state.updateIsLoggingIn);
// const clearAccessToken = useStore((state) => state.clearAccessToken);
const updateAccessToken = usePersistantStore((state) => state.updateAccessToken);
const updateIsLoggingIn = useInMemoryStore((state) => state.updateIsLoggingIn);
// const clearAccessToken = usePersistantStore((state) => state.clearAccessToken);

// update the access token if a code is present in the URL
React.useEffect(() => {
Expand Down
10 changes: 5 additions & 5 deletions src/hooks/useUserInsights.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import React from "react";
import ky from "ky";
import {
AccessTokenResponse,
getUserInsights,
GetUserInsightsParams,
Metric,
MetricTypeMap,
UserInsightsResponse,
GetUserInsightsParams,
} from "@src/threadsapi/api";
import useStore from "@src/threadsapi/store";
import { usePersistantStore } from "@src/threadsapi/store";
import ky from "ky";
import React from "react";

const useUserInsights = <M extends Metric>(metric: M, params?: GetUserInsightsParams) => {
const [insights, setInsights] = React.useState<UserInsightsResponse<MetricTypeMap[M]> | null>(null);
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | null>(null);
const accessToken = useStore((state) => state.access_token);
const accessToken = usePersistantStore((state) => state.access_token);

React.useEffect(() => {
async function fetchUserInsights(token: AccessTokenResponse) {
Expand Down
9 changes: 5 additions & 4 deletions src/hooks/useUserProfile.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React from "react";
import ky from "ky";
import { AccessTokenResponse, getUserProfile, UserProfileResponse } from "@src/threadsapi/api";
import useStore from "@src/threadsapi/store";
import { usePersistantStore } from "@src/threadsapi/store";

import ky from "ky";
import React from "react";

const useUserProfile = () => {
const [profile, setProfile] = React.useState<UserProfileResponse | null>(null);
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | null>(null);
const accessToken = useStore((state) => state.access_token);
const accessToken = usePersistantStore((state) => state.access_token);

React.useEffect(() => {
async function fetchAccessTokenAndProfile(token: AccessTokenResponse) {
Expand Down
36 changes: 36 additions & 0 deletions src/hooks/useUserThreads.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { AccessTokenResponse, getUserThreads, GetUserThreadsParams, ThreadMedia, UserThreadsResponse } from "@src/threadsapi/api"; // Update with your actual API import
import { usePersistantStore } from "@src/threadsapi/store"; // Update with your actual store import
import ky from "ky";
import React from "react";

const useUserThreads = (params?: GetUserThreadsParams) => {
const [threads, setThreads] = React.useState<ThreadMedia[] | null>(null);
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [error, setError] = React.useState<string | null>(null);
const accessToken = usePersistantStore((state) => state.access_token);

React.useEffect(() => {
async function fetchUserThreads(token: AccessTokenResponse) {
setIsLoading(true);
try {
const kyd = ky.create({ prefixUrl: "https://graph.threads.net" });
const userThreads: UserThreadsResponse = await getUserThreads(kyd, token, params);
setThreads(userThreads.data);
setError(null);
} catch (error) {
console.error("Error fetching user threads:", error);
setError("Failed to fetch user threads");
} finally {
setIsLoading(false);
}
}

if (accessToken) {
void fetchUserThreads(accessToken);
}
}, [accessToken, params]);

return [threads, isLoading, error] as const;
};

export default useUserThreads;
15 changes: 7 additions & 8 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import UserInsightsViews from "@src/components/UserInsightsViews";
import useAccessTokenExpiresIn from "@src/hooks/useAccessTokenExpiresIn";
import useUserProfile from "@src/hooks/useUserProfile";
import { getAuthorizationStartURL } from "@src/threadsapi/api";
import { FC } from "react";

import useStore from "../threadsapi/store";

import { getAuthorizationStartURL } from "@src/threadsapi/api";
import useUserProfile from "@src/hooks/useUserProfile";
import useAccessTokenExpiresIn from "@src/hooks/useAccessTokenExpiresIn";
import UserInsightsViews from "@src/components/UserInsightsViews";
import { useInMemoryStore, usePersistantStore } from "../threadsapi/store";

const Home: FC = () => {
const [token] = useStore((state) => [state.access_token] as const);
const [is_logging_in] = useStore((state) => [state.is_logging_in] as const);
const [token] = usePersistantStore((state) => [state.access_token] as const);
const [is_logging_in] = useInMemoryStore((state) => [state.is_logging_in] as const);

const [userProfile] = useUserProfile();
const access_token_expires_in = useAccessTokenExpiresIn();
Expand Down
6 changes: 2 additions & 4 deletions src/threadsapi/api.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { exchangeCodeForAccessToken } from "./api";

import ky from "ky";

import { expect, test } from "bun:test";
import ky from "ky";
import { exchangeCodeForAccessToken } from "./api";

test("should return the access token", async () => {
// Arrange
Expand Down
124 changes: 104 additions & 20 deletions src/threadsapi/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,30 +163,114 @@ export const getUserInsights = async <M extends Metric>(
};

// Wrapper functions
export const getViewsInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
return await getUserInsights(inst, accessToken, "views", { all_time: true });
};
// export const getViewsInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
// return await getUserInsights(inst, accessToken, "views", { all_time: true });
// };

export const getLikesInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
return await getUserInsights(inst, accessToken, "likes", { all_time: true });
};
// export const getLikesInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
// return await getUserInsights(inst, accessToken, "likes", { all_time: true });
// };

export const getRepliesInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
return await getUserInsights(inst, accessToken, "replies", { all_time: true });
};
// export const getRepliesInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
// return await getUserInsights(inst, accessToken, "replies", { all_time: true });
// };

export const getRepostsInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
return await getUserInsights(inst, accessToken, "reposts", { all_time: true });
};
// export const getRepostsInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
// return await getUserInsights(inst, accessToken, "reposts", { all_time: true });
// };

export const getQuotesInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
return await getUserInsights(inst, accessToken, "quotes", { all_time: true });
};
// export const getQuotesInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
// return await getUserInsights(inst, accessToken, "quotes", { all_time: true });
// };

export const getFollowersCountInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
return await getUserInsights(inst, accessToken, "followers_count");
};
// export const getFollowersCountInsights = async (inst: KyInstance, accessToken: AccessTokenResponse) => {
// return await getUserInsights(inst, accessToken, "followers_count");
// };

// export const getFollowerDemographicsInsights = async (inst: KyInstance, accessToken: AccessTokenResponse, breakdown: Breakdown) => {
// return await getUserInsights(inst, accessToken, "follower_demographics", { breakdown });
// };

export interface ThreadMedia {
id: string;
media_product_type: string;
media_type: string;
media_url?: string;
permalink?: string;
owner: { id: string };
username: string;
text?: string;
timestamp: string;
shortcode: string;
thumbnail_url?: string;
children?: ThreadMedia[];
is_quote_post: boolean;
}

export interface UserThreadsResponse {
data: ThreadMedia[];
paging?: {
cursors: {
before: string;
after: string;
};
};
}

export interface GetUserThreadsParams {
since?: string;
until?: string;
limit?: number;
all_time?: boolean;
}

export const getUserThreads = async (
inst: KyInstance,
accessToken: AccessTokenResponse,
params?: GetUserThreadsParams,
): Promise<UserThreadsResponse> => {
const searchParams: Record<string, string | number | boolean> = {
fields: "id,media_product_type,media_type,media_url,permalink,owner,username,text,timestamp,shortcode,thumbnail_url,children,is_quote_post",
access_token: accessToken.access_token,
};

if (params?.all_time) {
searchParams.since = 1712991600; // from the docs
searchParams.until = Math.floor(Date.now() / 1000);
} else {
if (params?.since) searchParams.since = params.since;
if (params?.until) searchParams.until = params.until;
}

searchParams.limit = params?.limit ?? Number.MAX_SAFE_INTEGER;

const resp = await inst
.get(`v1.0/${accessToken.user_id}/threads`, {
searchParams,
headers: {
"Content-Type": "application/json",
},
timeout: 10000,
})
.then((response) => response.json<UserThreadsResponse>())
.catch((error: unknown) => {
console.error("Error fetching user threads:", error);
throw error;
});

// if (params?.all_time && resp.paging?.cursors.after) {
// const next = resp.paging.cursors.after;
// const more = await getUserThreads(
// inst,
// {
// access_token: next,
// user_id: accessToken.user_id,
// },
// { ...params, since: next },
// );
// resp.data.push(...more.data);
// resp.paging = more.paging;
// }

export const getFollowerDemographicsInsights = async (inst: KyInstance, accessToken: AccessTokenResponse, breakdown: Breakdown) => {
return await getUserInsights(inst, accessToken, "follower_demographics", { breakdown });
return resp;
};
Loading

0 comments on commit a8cd9cd

Please sign in to comment.