tangled
alpha
login
or
join now
yoginth.com
/
hey
1
fork
atom
Hey is a decentralized and permissionless social media app built with Lens Protocol 🌿
1
fork
atom
overview
issues
pulls
pipelines
Remove API
yoginth.com
6 months ago
115bfb59
466cd726
+13
-249
21 changed files
expand all
collapse all
unified
split
apps
mobile
components
Shared
SinglePost
PostBody.tsx
index.tsx
web
src
components
Comment
CommentFeed.tsx
NoneRelevantFeed.tsx
Common
Layout.tsx
Providers
PreferencesProvider.tsx
index.tsx
Composer
Actions
CollectSettings
SplitConfig.tsx
Notification
List.tsx
Post
QuotedPost.tsx
Quotes.tsx
Settings
Preferences
AppIcon.tsx
IncludeLowScore.tsx
index.tsx
index.tsx
Shared
Navbar
NavItems
Logout.tsx
index.tsx
helpers
fetcher.ts
hooks
usePostMetadata.tsx
routes.tsx
store
persisted
usePreferencesStore.ts
-2
apps/mobile/components/Shared/SinglePost/PostBody.tsx
···
9
9
interface PostBodyProps {
10
10
contentClassName?: string;
11
11
post: AnyPostFragment;
12
12
-
quoted?: boolean;
13
12
showMore?: boolean;
14
13
}
15
14
16
15
const PostBody = ({
17
16
contentClassName = "",
18
17
post,
19
19
-
quoted = false,
20
18
showMore = false
21
19
}: PostBodyProps) => {
22
20
const targetPost = isRepost(post) ? post.repostOf : post;
+1
-3
apps/mobile/components/Shared/SinglePost/index.tsx
···
8
8
timelineItem?: TimelineItemFragment;
9
9
post: AnyPostFragment;
10
10
showMore?: boolean;
11
11
-
showType?: boolean;
12
11
}
13
12
14
13
const SinglePost = ({
15
14
timelineItem,
16
15
post,
17
17
-
showMore = true,
18
18
-
showType = true
16
16
+
showMore = true
19
17
}: SinglePostProps) => {
20
18
const rootPost = timelineItem ? timelineItem?.primary : post;
21
19
+1
-3
apps/web/src/components/Comment/CommentFeed.tsx
···
66
66
hasMore={hasMore}
67
67
items={filteredComments}
68
68
loading={loading}
69
69
-
renderItem={(comment) => (
70
70
-
<SinglePost key={comment.id} post={comment} showType={false} />
71
71
-
)}
69
69
+
renderItem={(comment) => <SinglePost key={comment.id} post={comment} />}
72
70
/>
73
71
);
74
72
};
+1
-1
apps/web/src/components/Comment/NoneRelevantFeed.tsx
···
93
93
hasMore={hasMore}
94
94
items={filteredComments}
95
95
renderItem={(comment) => (
96
96
-
<SinglePost key={comment.id} post={comment} showType={false} />
96
96
+
<SinglePost key={comment.id} post={comment} />
97
97
)}
98
98
/>
99
99
) : null}
-3
apps/web/src/components/Common/Layout.tsx
···
16
16
import { useTheme } from "@/hooks/useTheme";
17
17
import { useAccountStore } from "@/store/persisted/useAccountStore";
18
18
import { hydrateAuthTokens, signOut } from "@/store/persisted/useAuthStore";
19
19
-
import { usePreferencesStore } from "@/store/persisted/usePreferencesStore";
20
19
import { useProStore } from "@/store/persisted/useProStore";
21
20
import ReloadTabsWatcher from "./ReloadTabsWatcher";
22
21
···
25
24
const { theme } = useTheme();
26
25
const { currentAccount, setCurrentAccount } = useAccountStore();
27
26
const { setProBannerDismissed } = useProStore();
28
28
-
const { resetPreferences } = usePreferencesStore();
29
27
const isMounted = useIsClient();
30
28
const { accessToken } = hydrateAuthTokens();
31
29
···
35
33
}, [pathname]);
36
34
37
35
const onError = useCallback(() => {
38
38
-
resetPreferences();
39
36
signOut();
40
37
reloadAllTabs();
41
38
}, []);
-32
apps/web/src/components/Common/Providers/PreferencesProvider.tsx
···
1
1
-
import { useQuery } from "@tanstack/react-query";
2
2
-
import type { ReactNode } from "react";
3
3
-
import { useEffect } from "react";
4
4
-
import { hono } from "@/helpers/fetcher";
5
5
-
import { useAccountStore } from "@/store/persisted/useAccountStore";
6
6
-
import { usePreferencesStore } from "@/store/persisted/usePreferencesStore";
7
7
-
8
8
-
interface PreferencesProviderProps {
9
9
-
children: ReactNode;
10
10
-
}
11
11
-
12
12
-
const PreferencesProvider = ({ children }: PreferencesProviderProps) => {
13
13
-
const { currentAccount } = useAccountStore();
14
14
-
const { setAppIcon, setIncludeLowScore } = usePreferencesStore();
15
15
-
16
16
-
const { data: preferences } = useQuery({
17
17
-
enabled: Boolean(currentAccount?.address),
18
18
-
queryFn: () => hono.preferences.get(),
19
19
-
queryKey: ["preferences", currentAccount?.address]
20
20
-
});
21
21
-
22
22
-
useEffect(() => {
23
23
-
if (preferences) {
24
24
-
setIncludeLowScore(preferences.includeLowScore);
25
25
-
setAppIcon(preferences.appIcon);
26
26
-
}
27
27
-
}, [preferences]);
28
28
-
29
29
-
return <>{children}</>;
30
30
-
};
31
31
-
32
32
-
export default PreferencesProvider;
+3
-6
apps/web/src/components/Common/Providers/index.tsx
···
6
6
import ErrorBoundary from "@/components/Common/ErrorBoundary";
7
7
import authLink from "@/helpers/authLink";
8
8
import { ThemeProvider } from "@/hooks/useTheme";
9
9
-
import PreferencesProvider from "./PreferencesProvider";
10
9
import Web3Provider from "./Web3Provider";
11
10
12
11
export const queryClient = new QueryClient({
···
25
24
<QueryClientProvider client={queryClient}>
26
25
<Web3Provider>
27
26
<ApolloProvider client={lensApolloClient}>
28
28
-
<PreferencesProvider>
29
29
-
<HelmetProvider>
30
30
-
<ThemeProvider>{children}</ThemeProvider>
31
31
-
</HelmetProvider>
32
32
-
</PreferencesProvider>
27
27
+
<HelmetProvider>
28
28
+
<ThemeProvider>{children}</ThemeProvider>
29
29
+
</HelmetProvider>
33
30
</ApolloProvider>
34
31
</Web3Provider>
35
32
</QueryClientProvider>
+1
-1
apps/web/src/components/Composer/Actions/CollectSettings/SplitConfig.tsx
···
64
64
if (i === index) {
65
65
return {
66
66
...recipient,
67
67
-
[type]: type === "address" ? value : Number.parseInt(value)
67
67
+
[type]: type === "address" ? value : Number.parseInt(value, 10)
68
68
};
69
69
}
70
70
return recipient;
+1
-3
apps/web/src/components/Notification/List.tsx
···
19
19
import cn from "@/helpers/cn";
20
20
import useLoadMoreOnIntersect from "@/hooks/useLoadMoreOnIntersect";
21
21
import { useNotificationStore } from "@/store/persisted/useNotificationStore";
22
22
-
import { usePreferencesStore } from "@/store/persisted/usePreferencesStore";
23
22
import NotificationShimmer from "./Shimmer";
24
23
import TokenDistributedNotification from "./Type/TokenDistributedNotification";
25
24
···
40
39
}
41
40
42
41
const List = ({ feedType }: ListProps) => {
43
43
-
const { includeLowScore } = usePreferencesStore();
44
42
const { setLastSeenNotificationId } = useNotificationStore();
45
43
46
44
const getNotificationType = useCallback(() => {
···
64
62
65
63
const request: NotificationRequest = {
66
64
filter: {
67
67
-
includeLowScore,
65
65
+
includeLowScore: true,
68
66
notificationTypes: getNotificationType()
69
67
}
70
68
};
+1
-1
apps/web/src/components/Post/QuotedPost.tsx
···
40
40
{post.isDeleted ? (
41
41
<HiddenPost type={post.__typename} />
42
42
) : (
43
43
-
<PostBody post={post} quoted showMore />
43
43
+
<PostBody post={post} showMore />
44
44
)}
45
45
</PostWrapper>
46
46
);
+1
-1
apps/web/src/components/Post/Quotes.tsx
···
64
64
<div className="virtual-divider-list-window">
65
65
<WindowVirtualizer>
66
66
{quotes.map((quote) => (
67
67
-
<SinglePost key={quote.id} post={quote} showType={false} />
67
67
+
<SinglePost key={quote.id} post={quote} />
68
68
))}
69
69
{hasMore && <span ref={loadMoreRef} />}
70
70
</WindowVirtualizer>
-75
apps/web/src/components/Settings/Preferences/AppIcon.tsx
···
1
1
-
import { CheckCircleIcon as CheckCircleIconOutline } from "@heroicons/react/24/outline";
2
2
-
import { CheckCircleIcon as CheckCircleIconSolid } from "@heroicons/react/24/solid";
3
3
-
import { STATIC_IMAGES_URL } from "@hey/data/constants";
4
4
-
import { useMutation } from "@tanstack/react-query";
5
5
-
import { toast } from "sonner";
6
6
-
import ProFeatureNotice from "@/components/Shared/ProFeatureNotice";
7
7
-
import { Image, Tooltip } from "@/components/Shared/UI";
8
8
-
import errorToast from "@/helpers/errorToast";
9
9
-
import { hono } from "@/helpers/fetcher";
10
10
-
import { useAccountStore } from "@/store/persisted/useAccountStore";
11
11
-
import { usePreferencesStore } from "@/store/persisted/usePreferencesStore";
12
12
-
13
13
-
const icons = [
14
14
-
{ id: 0, name: "Default" },
15
15
-
{ id: 1, name: "Pride" },
16
16
-
{ id: 2, name: "Emerald" },
17
17
-
{ id: 3, name: "Indigo" },
18
18
-
{ id: 4, name: "Violet" }
19
19
-
];
20
20
-
21
21
-
const AppIcon = () => {
22
22
-
const { currentAccount } = useAccountStore();
23
23
-
const { appIcon, setAppIcon } = usePreferencesStore();
24
24
-
25
25
-
const { mutate, isPending } = useMutation({
26
26
-
mutationFn: ({ appIcon }: { appIcon: number }) =>
27
27
-
hono.preferences.update({ appIcon }),
28
28
-
onError: errorToast,
29
29
-
onSuccess: (data) => {
30
30
-
setAppIcon(data.appIcon ?? 0);
31
31
-
toast.success("App icon updated");
32
32
-
}
33
33
-
});
34
34
-
35
35
-
const handleSelectIcon = (iconId: number) => {
36
36
-
mutate({ appIcon: iconId });
37
37
-
};
38
38
-
39
39
-
if (!currentAccount?.hasSubscribed) {
40
40
-
return <ProFeatureNotice className="m-5" feature="custom app icons" />;
41
41
-
}
42
42
-
43
43
-
return (
44
44
-
<div className="m-5 flex flex-col gap-y-5">
45
45
-
<b>Choose App Icon</b>
46
46
-
<div className="flex flex-wrap items-center gap-x-8">
47
47
-
{icons.map((icon) => (
48
48
-
<Tooltip content={icon.name} key={icon.id} placement="top">
49
49
-
<button
50
50
-
className="flex flex-col items-center space-y-2"
51
51
-
disabled={isPending}
52
52
-
onClick={() => handleSelectIcon(icon.id)}
53
53
-
type="button"
54
54
-
>
55
55
-
<Image
56
56
-
alt={icon.name}
57
57
-
className="size-10"
58
58
-
height={40}
59
59
-
src={`${STATIC_IMAGES_URL}/app-icon/${icon.id}.png`}
60
60
-
width={40}
61
61
-
/>
62
62
-
{icon.id === appIcon ? (
63
63
-
<CheckCircleIconSolid className="size-5 text-green-600" />
64
64
-
) : (
65
65
-
<CheckCircleIconOutline className="size-5 text-gray-500 dark:text-gray-200" />
66
66
-
)}
67
67
-
</button>
68
68
-
</Tooltip>
69
69
-
))}
70
70
-
</div>
71
71
-
</div>
72
72
-
);
73
73
-
};
74
74
-
75
75
-
export default AppIcon;
-36
apps/web/src/components/Settings/Preferences/IncludeLowScore.tsx
···
1
1
-
import { SwatchIcon } from "@heroicons/react/24/outline";
2
2
-
import { useMutation } from "@tanstack/react-query";
3
3
-
import { toast } from "sonner";
4
4
-
import ToggleWithHelper from "@/components/Shared/ToggleWithHelper";
5
5
-
import errorToast from "@/helpers/errorToast";
6
6
-
import { hono } from "@/helpers/fetcher";
7
7
-
import { usePreferencesStore } from "@/store/persisted/usePreferencesStore";
8
8
-
9
9
-
const IncludeLowScore = () => {
10
10
-
const { includeLowScore, setIncludeLowScore } = usePreferencesStore();
11
11
-
12
12
-
const { mutate, isPending } = useMutation({
13
13
-
mutationFn: ({ includeLowScore }: { includeLowScore: boolean }) =>
14
14
-
hono.preferences.update({ includeLowScore }),
15
15
-
onError: errorToast,
16
16
-
onSuccess: (data) => {
17
17
-
setIncludeLowScore(data.includeLowScore);
18
18
-
toast.success("Notification preference updated");
19
19
-
}
20
20
-
});
21
21
-
22
22
-
return (
23
23
-
<div className="m-5">
24
24
-
<ToggleWithHelper
25
25
-
description="Turn on low-signal notification filter"
26
26
-
disabled={isPending}
27
27
-
heading="Notification Signal filter"
28
28
-
icon={<SwatchIcon className="size-5" />}
29
29
-
on={includeLowScore}
30
30
-
setOn={() => mutate({ includeLowScore: !includeLowScore })}
31
31
-
/>
32
32
-
</div>
33
33
-
);
34
34
-
};
35
35
-
36
36
-
export default IncludeLowScore;
-31
apps/web/src/components/Settings/Preferences/index.tsx
···
1
1
-
import BackButton from "@/components/Shared/BackButton";
2
2
-
import NotLoggedIn from "@/components/Shared/NotLoggedIn";
3
3
-
import PageLayout from "@/components/Shared/PageLayout";
4
4
-
import { Card, CardHeader } from "@/components/Shared/UI";
5
5
-
import { useAccountStore } from "@/store/persisted/useAccountStore";
6
6
-
import AppIcon from "./AppIcon";
7
7
-
import IncludeLowScore from "./IncludeLowScore";
8
8
-
9
9
-
const PreferencesSettings = () => {
10
10
-
const { currentAccount } = useAccountStore();
11
11
-
12
12
-
if (!currentAccount) {
13
13
-
return <NotLoggedIn />;
14
14
-
}
15
15
-
16
16
-
return (
17
17
-
<PageLayout title="Preferences settings">
18
18
-
<Card>
19
19
-
<CardHeader
20
20
-
icon={<BackButton path="/settings" />}
21
21
-
title="Preferences"
22
22
-
/>
23
23
-
<IncludeLowScore />
24
24
-
<div className="divider" />
25
25
-
<AppIcon />
26
26
-
</Card>
27
27
-
</PageLayout>
28
28
-
);
29
29
-
};
30
30
-
31
31
-
export default PreferencesSettings;
-6
apps/web/src/components/Settings/index.tsx
···
1
1
import {
2
2
-
AdjustmentsVerticalIcon,
3
2
ArrowRightIcon,
4
3
AtSymbolIcon,
5
4
CodeBracketIcon,
···
52
51
icon: <AtSymbolIcon className="size-5" />,
53
52
title: "Username",
54
53
url: "/settings/username"
55
55
-
},
56
56
-
{
57
57
-
icon: <AdjustmentsVerticalIcon className="size-5" />,
58
58
-
title: "Preferences",
59
59
-
url: "/settings/preferences"
60
54
},
61
55
{
62
56
icon: <FingerPrintIcon className="size-5" />,
-4
apps/web/src/components/Shared/Navbar/NavItems/Logout.tsx
···
3
3
import errorToast from "@/helpers/errorToast";
4
4
import reloadAllTabs from "@/helpers/reloadAllTabs";
5
5
import { signOut } from "@/store/persisted/useAuthStore";
6
6
-
import { usePreferencesStore } from "@/store/persisted/usePreferencesStore";
7
6
8
7
interface LogoutProps {
9
8
className?: string;
···
11
10
}
12
11
13
12
const Logout = ({ className = "", onClick }: LogoutProps) => {
14
14
-
const { resetPreferences } = usePreferencesStore();
15
15
-
16
13
const handleLogout = async () => {
17
14
try {
18
18
-
resetPreferences();
19
15
signOut();
20
16
reloadAllTabs();
21
17
} catch (error) {
+1
-3
apps/web/src/components/Shared/Navbar/index.tsx
···
21
21
import useHasNewNotifications from "@/hooks/useHasNewNotifications";
22
22
import { useAuthModalStore } from "@/store/non-persisted/modal/useAuthModalStore";
23
23
import { useAccountStore } from "@/store/persisted/useAccountStore";
24
24
-
import { usePreferencesStore } from "@/store/persisted/usePreferencesStore";
25
24
import SignedAccount from "./SignedAccount";
26
25
27
26
const navigationItems = {
···
96
95
const Navbar = () => {
97
96
const { pathname } = useLocation();
98
97
const { currentAccount } = useAccountStore();
99
99
-
const { appIcon } = usePreferencesStore();
100
98
const { setShowAuthModal } = useAuthModalStore();
101
99
102
100
const handleLogoClick = useCallback(
···
120
118
alt="Logo"
121
119
className="size-8"
122
120
height={32}
123
123
-
src={`${STATIC_IMAGES_URL}/app-icon/${appIcon}.png`}
121
121
+
src={`${STATIC_IMAGES_URL}/app-icon/0.png`}
124
122
width={32}
125
123
/>
126
124
</Link>
+1
-12
apps/web/src/helpers/fetcher.ts
···
1
1
import { HEY_API_URL } from "@hey/data/constants";
2
2
import { Status } from "@hey/data/enums";
3
3
-
import type { AppStatus, Oembed, Preferences, STS } from "@hey/types/api";
3
3
+
import type { AppStatus, Oembed, STS } from "@hey/types/api";
4
4
import { hydrateAuthTokens } from "@/store/persisted/useAuthStore";
5
5
import { isTokenExpiringSoon, refreshTokens } from "./tokenManager";
6
6
···
81
81
oembed: {
82
82
get: (url: string): Promise<Oembed> => {
83
83
return fetchApi<Oembed>(`/oembed/get?url=${url}`, { method: "GET" });
84
84
-
}
85
85
-
},
86
86
-
preferences: {
87
87
-
get: (): Promise<Preferences> => {
88
88
-
return fetchApi<Preferences>("/preferences/get", { method: "GET" });
89
89
-
},
90
90
-
update: (preferences: Partial<Preferences>): Promise<Preferences> => {
91
91
-
return fetchApi<Preferences>("/preferences/update", {
92
92
-
body: JSON.stringify(preferences),
93
93
-
method: "POST"
94
94
-
});
95
84
}
96
85
}
97
86
};
+1
-1
apps/web/src/hooks/usePostMetadata.tsx
···
83
83
}),
84
84
video: {
85
85
cover: videoThumbnail.url,
86
86
-
duration: Number.parseInt(videoDurationInSeconds),
86
86
+
duration: Number.parseInt(videoDurationInSeconds, 10),
87
87
item: primaryAttachment.uri,
88
88
type: primaryAttachment.mimeType,
89
89
...(license && { license })
-2
apps/web/src/routes.tsx
···
25
25
import ManagerSettings from "@/components/Settings/Manager";
26
26
import { default as AccountMonetizeSettings } from "@/components/Settings/Monetize";
27
27
import { default as AccountPersonalizeSettings } from "@/components/Settings/Personalize";
28
28
-
import PreferencesSettings from "@/components/Settings/Preferences";
29
28
import SessionsSettings from "@/components/Settings/Sessions";
30
29
import UsernameSettings from "@/components/Settings/Username";
31
30
import Custom404 from "@/components/Shared/404";
···
74
73
<Route element={<DeveloperSettings />} path="developer" />
75
74
<Route element={<FundsSettings />} path="funds" />
76
75
<Route element={<ManagerSettings />} path="manager" />
77
77
-
<Route element={<PreferencesSettings />} path="preferences" />
78
76
<Route element={<SessionsSettings />} path="sessions" />
79
77
<Route element={<UsernameSettings />} path="username" />
80
78
</Route>
-23
apps/web/src/store/persisted/usePreferencesStore.ts
···
1
1
-
import { Localstorage } from "@hey/data/storage";
2
2
-
import { createPersistedTrackedStore } from "@/store/createTrackedStore";
3
3
-
4
4
-
interface State {
5
5
-
appIcon: number;
6
6
-
includeLowScore: boolean;
7
7
-
resetPreferences: () => void;
8
8
-
setAppIcon: (appIcon: number) => void;
9
9
-
setIncludeLowScore: (includeLowScore: boolean) => void;
10
10
-
}
11
11
-
12
12
-
const { useStore: usePreferencesStore } = createPersistedTrackedStore<State>(
13
13
-
(set) => ({
14
14
-
appIcon: 0,
15
15
-
includeLowScore: false,
16
16
-
resetPreferences: () => set(() => ({ includeLowScore: false })),
17
17
-
setAppIcon: (appIcon) => set(() => ({ appIcon })),
18
18
-
setIncludeLowScore: (includeLowScore) => set(() => ({ includeLowScore }))
19
19
-
}),
20
20
-
{ name: Localstorage.PreferencesStore }
21
21
-
);
22
22
-
23
23
-
export { usePreferencesStore };