From dc2e636f61b40b6dfb4a2eefb79f932ecb5687ce Mon Sep 17 00:00:00 2001 From: Dessalines Date: Wed, 6 Dec 2023 18:11:12 -0500 Subject: [PATCH] Halfway done with redux. --- src/client/index.tsx | 2 +- src/server/handlers/catch-all-handler.tsx | 10 +---- src/shared/components/app/app.tsx | 15 ++++++-- src/shared/components/app/navbar.tsx | 19 +++++++--- .../components/common/anonymous-guard.tsx | 11 ++++-- src/shared/components/common/auth-guard.tsx | 20 +++++----- .../components/common/banner-icon-header.tsx | 12 +++++- .../components/common/image-upload-form.tsx | 5 ++- .../components/common/listing-type-select.tsx | 14 ++++--- src/shared/components/common/pictrs-image.tsx | 8 ++-- .../components/common/subscribe-button.tsx | 6 ++- .../components/community/communities.tsx | 1 + .../components/community/community-form.tsx | 4 ++ .../components/community/community-link.tsx | 9 ++++- src/shared/components/community/community.tsx | 7 +++- .../components/community/create-community.tsx | 1 + src/shared/components/community/sidebar.tsx | 8 +++- src/shared/components/home/login.tsx | 27 ++++++++----- src/shared/components/home/setup.tsx | 13 ++++--- src/shared/components/home/signup.tsx | 6 ++- src/shared/components/home/site-form.tsx | 2 + src/shared/components/home/site-sidebar.tsx | 8 +++- src/shared/components/person/inbox.tsx | 2 + .../components/person/password-change.tsx | 12 ++++-- .../components/person/person-listing.tsx | 1 + src/shared/components/person/profile.tsx | 1 + src/shared/components/person/settings.tsx | 26 ++++++------- src/shared/components/post/post-listing.tsx | 18 ++++++--- .../private_message/private-message.tsx | 6 ++- src/shared/components/remote-fetch.tsx | 7 +++- src/shared/components/search.tsx | 3 +- src/shared/services/HttpService.ts | 15 +++++--- src/shared/services/I18NextService.ts | 14 ++++--- src/shared/services/UnreadCounterService.ts | 20 ++++++---- src/shared/utils/app/initialize-site.ts | 10 +++-- src/shared/utils/app/my-auth.ts | 4 ++ src/shared/utils/app/setup-redux.ts | 38 ++++++++++++++----- 37 files changed, 260 insertions(+), 125 deletions(-) diff --git a/src/client/index.tsx b/src/client/index.tsx index 28181048..88b13f7c 100644 --- a/src/client/index.tsx +++ b/src/client/index.tsx @@ -16,7 +16,7 @@ async function startClient() { await setupDateFns(); - const store = setupRedux(windowData); + const store = setupRedux(windowData!); const wrapper = ( diff --git a/src/server/handlers/catch-all-handler.tsx b/src/server/handlers/catch-all-handler.tsx index 22fca585..0dd97430 100644 --- a/src/server/handlers/catch-all-handler.tsx +++ b/src/server/handlers/catch-all-handler.tsx @@ -1,4 +1,4 @@ -import { initializeSite, isAuthPath } from "@utils/app"; +import { initializeSite, isAuthPath, setupRedux } from "@utils/app"; import { getHttpBaseInternal } from "@utils/env"; import { ErrorPageData } from "@utils/types"; import type { Request, Response } from "express"; @@ -21,7 +21,6 @@ import { getErrorPageData } from "../utils/get-error-page-data"; import { setForwardedHeaders } from "../utils/set-forwarded-headers"; import { getJwtCookie } from "../utils/has-jwt-cookie"; import { Provider } from "inferno-redux"; -import { configureStore, createSlice } from "@reduxjs/toolkit"; export default async (req: Request, res: Response) => { try { @@ -110,12 +109,7 @@ export default async (req: Request, res: Response) => { errorPageData, }; - const slice = createSlice({ - name: "isoData", - initialState: { value: isoData }, - reducers: {}, - }); - const store = configureStore({ reducer: slice.reducer }); + const store = setupRedux(isoData); const wrapper = ( diff --git a/src/shared/components/app/app.tsx b/src/shared/components/app/app.tsx index 0d443368..69e85960 100644 --- a/src/shared/components/app/app.tsx +++ b/src/shared/components/app/app.tsx @@ -17,6 +17,9 @@ import AnonymousGuard from "../common/anonymous-guard"; import { CodeTheme } from "./code-theme"; export class App extends Component { + get isoData(): IsoDataOptionalSite { + return this.context.store.getState().value; + } private readonly mainContentRef: RefObject; constructor(props: any, context: any) { super(props, context); @@ -29,8 +32,7 @@ export class App extends Component { } render() { - const reduxState: IsoDataOptionalSite = this.context.store.getState().value; - const siteRes = reduxState.site_res; + const siteRes = this.isoData.site_res; const siteView = siteRes?.site_view; return ( @@ -73,11 +75,16 @@ export class App extends Component {
{RouteComponent && (isAuthPath(path ?? "") ? ( - + ) : isAnonymousPath(path ?? "") ? ( - + ) : ( diff --git a/src/shared/components/app/navbar.tsx b/src/shared/components/app/navbar.tsx index e899061a..96431ddf 100644 --- a/src/shared/components/app/navbar.tsx +++ b/src/shared/components/app/navbar.tsx @@ -38,7 +38,7 @@ function handleCollapseClick(i: Navbar) { } function handleLogOut(i: Navbar) { - HttpService._Instance.logout(); + HttpService.logout(); handleCollapseClick(i); } @@ -110,7 +110,11 @@ export class Navbar extends Component { > {siteView?.site.icon && showAvatars(this.props.siteRes?.my_user) && ( - + )} {siteView?.site.name} @@ -379,9 +383,14 @@ export class Navbar extends Component { aria-expanded="false" data-bs-toggle="dropdown" > - {showAvatars() && person.avatar && ( - - )} + {showAvatars(this.props.siteRes?.my_user) && + person.avatar && ( + + )} {person.display_name ?? person.name}
    { +class AnonymousGuard extends Component< + AnonymousGuardProps, + AnonymousGuardState +> { state = { hasRedirected: false, } as AnonymousGuardState; @@ -16,7 +21,7 @@ class AnonymousGuard extends Component { } componentDidMount() { - if (UserService.Instance.myUserInfo) { + if (this.props.isLoggedIn) { this.context.router.history.replace(`/`); } else { this.setState({ hasRedirected: true }); diff --git a/src/shared/components/common/auth-guard.tsx b/src/shared/components/common/auth-guard.tsx index 03352901..fd9af96a 100644 --- a/src/shared/components/common/auth-guard.tsx +++ b/src/shared/components/common/auth-guard.tsx @@ -1,30 +1,28 @@ import { Component } from "inferno"; import { RouteComponentProps } from "inferno-router/dist/Route"; -import { UserService } from "../../services"; import { Spinner } from "./icon"; +interface AuthGuardProps { + componentProps: RouteComponentProps>; + isLoggedIn: boolean; +} + interface AuthGuardState { hasRedirected: boolean; } -class AuthGuard extends Component< - RouteComponentProps>, - AuthGuardState -> { +class AuthGuard extends Component { state = { hasRedirected: false, } as AuthGuardState; - constructor( - props: RouteComponentProps>, - context: any, - ) { + constructor(props: AuthGuardProps, context: any) { super(props, context); } componentDidMount() { - if (!UserService.Instance.myUserInfo) { - const { pathname, search } = this.props.location; + if (!this.props.isLoggedIn) { + const { pathname, search } = this.props.componentProps.location; this.context.router.history.replace( `/login?prev=${encodeURIComponent(pathname + search)}`, ); diff --git a/src/shared/components/common/banner-icon-header.tsx b/src/shared/components/common/banner-icon-header.tsx index d2cba13e..c87160c9 100644 --- a/src/shared/components/common/banner-icon-header.tsx +++ b/src/shared/components/common/banner-icon-header.tsx @@ -1,9 +1,11 @@ import { Component } from "inferno"; import { PictrsImage } from "./pictrs-image"; +import { MyUserInfo } from "lemmy-js-client"; interface BannerIconHeaderProps { banner?: string; icon?: string; + myUserInfo?: MyUserInfo; } export class BannerIconHeader extends Component { @@ -17,13 +19,21 @@ export class BannerIconHeader extends Component { return ( (banner || icon) && (
    - {banner && } + {banner && ( + + )} {icon && ( )}
    diff --git a/src/shared/components/common/image-upload-form.tsx b/src/shared/components/common/image-upload-form.tsx index 71337418..4b2c6592 100644 --- a/src/shared/components/common/image-upload-form.tsx +++ b/src/shared/components/common/image-upload-form.tsx @@ -1,7 +1,7 @@ import { randomStr } from "@utils/helpers"; import classNames from "classnames"; import { Component, linkEvent } from "inferno"; -import { HttpService, I18NextService, UserService } from "../../services"; +import { HttpService, I18NextService } from "../../services"; import { toast } from "../../toast"; import { Icon } from "./icon"; @@ -11,6 +11,7 @@ interface ImageUploadFormProps { onUpload(url: string): any; onRemove(): any; rounded?: boolean; + isLoggedIn: boolean; } interface ImageUploadFormState { @@ -63,7 +64,7 @@ export class ImageUploadForm extends Component< accept="image/*,video/*" className="small form-control" name={this.id} - disabled={!UserService.Instance.myUserInfo} + disabled={!this.props.isLoggedIn} onChange={linkEvent(this, this.handleImageUpload)} /> diff --git a/src/shared/components/common/listing-type-select.tsx b/src/shared/components/common/listing-type-select.tsx index 3439fac4..4c537e20 100644 --- a/src/shared/components/common/listing-type-select.tsx +++ b/src/shared/components/common/listing-type-select.tsx @@ -1,14 +1,16 @@ import { randomStr } from "@utils/helpers"; import classNames from "classnames"; import { Component, linkEvent } from "inferno"; -import { ListingType } from "lemmy-js-client"; -import { I18NextService, UserService } from "../../services"; +import { ListingType, MyUserInfo } from "lemmy-js-client"; +import { I18NextService } from "../../services"; +import { moderatesSomething } from "@utils/roles"; interface ListingTypeSelectProps { type_: ListingType; showLocal: boolean; showSubscribed: boolean; onChange(val: ListingType): void; + myUserInfo?: MyUserInfo; } interface ListingTypeSelectState { @@ -52,15 +54,15 @@ export class ListingTypeSelect extends Component< value={"Subscribed"} checked={this.state.type_ === "Subscribed"} onChange={linkEvent(this, this.handleTypeChange)} - disabled={!UserService.Instance.myUserInfo} + disabled={!this.props.myUserInfo} /> - {(UserService.Instance.myUserInfo?.moderates.length ?? 0) > 0 && ( + {moderatesSomething(this.props.myUserInfo) && ( <> { @@ -27,9 +27,9 @@ export class PictrsImage extends Component { const { src, icon, iconOverlay, banner, thumbnail, nsfw, pushup, cardTop } = this.props; let user_blur_nsfw = true; - if (UserService.Instance.myUserInfo) { + if (this.props.myUserInfo) { user_blur_nsfw = - UserService.Instance.myUserInfo?.local_user_view.local_user.blur_nsfw; + this.props.myUserInfo?.local_user_view.local_user.blur_nsfw; } const blur_image = nsfw && user_blur_nsfw; diff --git a/src/shared/components/common/subscribe-button.tsx b/src/shared/components/common/subscribe-button.tsx index 58195391..9a92d9a4 100644 --- a/src/shared/components/common/subscribe-button.tsx +++ b/src/shared/components/common/subscribe-button.tsx @@ -3,12 +3,13 @@ import classNames from "classnames"; import { NoOptionI18nKeys } from "i18next"; import { Component, MouseEventHandler, linkEvent } from "inferno"; import { CommunityView } from "lemmy-js-client"; -import { I18NextService, UserService } from "../../services"; +import { I18NextService } from "../../services"; import { VERSION } from "../../version"; import { Icon, Spinner } from "./icon"; import { toast } from "../../toast"; interface SubscribeButtonProps { + loggedIn: boolean; communityView: CommunityView; onFollow: MouseEventHandler; onUnFollow: MouseEventHandler; @@ -25,6 +26,7 @@ export function SubscribeButton({ onUnFollow, loading = false, isLink = false, + loggedIn, }: SubscribeButtonProps) { let i18key: NoOptionI18nKeys; @@ -51,7 +53,7 @@ export function SubscribeButton({ isLink ? "btn-link d-inline-block" : "d-block mb-2 w-100", ); - if (!UserService.Instance.myUserInfo) { + if (!loggedIn) { return ( <>
@@ -180,6 +183,7 @@ export class CommunityForm extends Component< imageSrc={this.state.form.banner} onUpload={this.handleBannerUpload} onRemove={this.handleBannerRemove} + isLoggedIn={!!this.props.myUserInfo} /> diff --git a/src/shared/components/community/community-link.tsx b/src/shared/components/community/community-link.tsx index 7e096825..d69de053 100644 --- a/src/shared/components/community/community-link.tsx +++ b/src/shared/components/community/community-link.tsx @@ -65,7 +65,14 @@ export class CommunityLink extends Component { {!this.props.hideAvatar && !this.props.community.removed && showAvatars(this.props.myUserInfo) && - icon && } + icon && ( + + )} {displayName} ); diff --git a/src/shared/components/community/community.tsx b/src/shared/components/community/community.tsx index 7a91e950..f7d29ece 100644 --- a/src/shared/components/community/community.tsx +++ b/src/shared/components/community/community.tsx @@ -507,10 +507,15 @@ export class Community extends Component< return ( community && (
- +

{community.title}

{ {I18NextService.i18n.t("create_community")} { this.sidebar() ) : ( { {this.communityTitle()} {this.props.editable && this.adminButtons()} {

{this.props.showIcon && !community.removed && ( - + )} diff --git a/src/shared/components/home/login.tsx b/src/shared/components/home/login.tsx index 2a6a928a..dbd8f111 100644 --- a/src/shared/components/home/login.tsx +++ b/src/shared/components/home/login.tsx @@ -1,10 +1,9 @@ -import { setIsoData } from "@utils/app"; import { isBrowser, updateDataBsTheme } from "@utils/browser"; import { getQueryParams } from "@utils/helpers"; import { Component, linkEvent } from "inferno"; import { RouteComponentProps } from "inferno-router/dist/Route"; import { GetSiteResponse, LoginResponse } from "lemmy-js-client"; -import { I18NextService, UserService } from "../../services"; +import { I18NextService } from "../../services"; import { EMPTY_REQUEST, HttpService, @@ -17,6 +16,9 @@ import { Spinner } from "../common/icon"; import PasswordInput from "../common/password-input"; import TotpModal from "../common/totp-modal"; import { UnreadCounterService } from "../../services"; +import { IsoData, IsoDataOptionalSite } from "../../interfaces"; +import { updateSite } from "@utils/app/setup-redux"; +import { EnhancedStore } from "@reduxjs/toolkit"; interface LoginProps { prev?: string; @@ -39,15 +41,20 @@ interface State { show2faModal: boolean; } -async function handleLoginSuccess(i: Login, loginRes: LoginResponse) { - UserService.Instance.login({ +async function handleLoginSuccess( + i: Login, + loginRes: LoginResponse, + store: EnhancedStore, +) { + HttpService.login({ res: loginRes, }); const site = await HttpService.client.getSite(); if (site.state === "success") { - // TODO this is the key, you need to update the redux store here - UserService.Instance.myUserInfo = site.data.my_user; + store.dispatch(updateSite(site.data)); + + // TODO why is this just the theme?? updateDataBsTheme(site.data); } @@ -86,7 +93,7 @@ async function handleLoginSubmit(i: Login, event: any) { } case "success": { - handleLoginSuccess(i, loginRes.data); + handleLoginSuccess(i, loginRes.data, this.context.store); break; } } @@ -111,7 +118,9 @@ export class Login extends Component< RouteComponentProps>, State > { - private isoData = setIsoData(this.context); + get isoData(): IsoData { + return this.context.store.getState().value; + } state: State = { loginRes: EMPTY_REQUEST, @@ -169,7 +178,7 @@ export class Login extends Component< const successful = loginRes.state === "success"; if (successful) { this.setState({ show2faModal: false }); - handleLoginSuccess(this, loginRes.data); + handleLoginSuccess(this, loginRes.data, this.context.store); } else { toast(I18NextService.i18n.t("incorrect_totp_code"), "danger"); } diff --git a/src/shared/components/home/setup.tsx b/src/shared/components/home/setup.tsx index 5dbad944..80694b1c 100644 --- a/src/shared/components/home/setup.tsx +++ b/src/shared/components/home/setup.tsx @@ -1,4 +1,4 @@ -import { fetchThemeList, setIsoData } from "@utils/app"; +import { fetchThemeList } from "@utils/app"; import { Component, linkEvent } from "inferno"; import { Helmet } from "inferno-helmet"; import { @@ -7,7 +7,7 @@ import { LoginResponse, Register, } from "lemmy-js-client"; -import { I18NextService, UserService } from "../../services"; +import { I18NextService } from "../../services"; import { EMPTY_REQUEST, HttpService, @@ -17,6 +17,7 @@ import { import { Spinner } from "../common/icon"; import PasswordInput from "../common/password-input"; import { SiteForm } from "./site-form"; +import { IsoData } from "../../interfaces"; interface State { form: { @@ -37,7 +38,9 @@ interface State { } export class Setup extends Component { - private isoData = setIsoData(this.context); + get isoData(): IsoData { + return this.context.store.getState().value; + } state: State = { registerRes: EMPTY_REQUEST, @@ -45,7 +48,7 @@ export class Setup extends Component { form: { show_nsfw: true, }, - doneRegisteringUser: !!UserService.Instance.myUserInfo, + doneRegisteringUser: !!this.isoData.site_res.my_user, siteRes: this.isoData.site_res, }; @@ -194,7 +197,7 @@ export class Setup extends Component { if (i.state.registerRes.state === "success") { const data = i.state.registerRes.data; - UserService.Instance.login({ res: data }); + HttpService.login({ res: data }); i.setState({ doneRegisteringUser: true }); } } diff --git a/src/shared/components/home/signup.tsx b/src/shared/components/home/signup.tsx index df992c21..42ea415d 100644 --- a/src/shared/components/home/signup.tsx +++ b/src/shared/components/home/signup.tsx @@ -24,6 +24,7 @@ import { Icon, Spinner } from "../common/icon"; import { MarkdownTextArea } from "../common/markdown-textarea"; import PasswordInput from "../common/password-input"; import { IsoData } from "../../interfaces"; +import { updateSite } from "@utils/app/setup-redux"; interface State { registerRes: RequestState; @@ -400,14 +401,15 @@ export class Signup extends Component { // Only log them in if a jwt was set if (data.jwt) { - UserService.Instance.login({ + HttpService.login({ res: data, }); const site = await HttpService.client.getSite(); + // TODO test this if (site.state === "success") { - UserService.Instance.myUserInfo = site.data.my_user; + i.context.store.dispatch(updateSite(site.data)); } i.props.history.replace("/communities"); diff --git a/src/shared/components/home/site-form.tsx b/src/shared/components/home/site-form.tsx index f9d2e46b..3deb7a45 100644 --- a/src/shared/components/home/site-form.tsx +++ b/src/shared/components/home/site-form.tsx @@ -167,6 +167,7 @@ export class SiteForm extends Component { imageSrc={this.state.siteForm.icon} onUpload={this.handleIconUpload} onRemove={this.handleIconRemove} + isLoggedIn={!!this.props.siteRes.my_user} rounded />

@@ -181,6 +182,7 @@ export class SiteForm extends Component { imageSrc={this.state.siteForm.banner} onUpload={this.handleBannerUpload} onRemove={this.handleBannerRemove} + isLoggedIn={!!this.props.siteRes.my_user} />
diff --git a/src/shared/components/home/site-sidebar.tsx b/src/shared/components/home/site-sidebar.tsx index 52ecbbc7..85f34b4d 100644 --- a/src/shared/components/home/site-sidebar.tsx +++ b/src/shared/components/home/site-sidebar.tsx @@ -1,6 +1,6 @@ import classNames from "classnames"; import { Component, linkEvent } from "inferno"; -import { PersonView, Site, SiteAggregates } from "lemmy-js-client"; +import { MyUserInfo, PersonView, Site, SiteAggregates } from "lemmy-js-client"; import { mdToHtml } from "../../markdown"; import { I18NextService } from "../../services"; import { Badges } from "../common/badges"; @@ -14,6 +14,7 @@ interface SiteSidebarProps { counts?: SiteAggregates; admins?: PersonView[]; isMobile?: boolean; + myUserInfo?: MyUserInfo; } interface SiteSidebarState { @@ -36,7 +37,10 @@ export class SiteSidebar extends Component {
{this.siteName()} {!this.state.collapsed && ( - + )}
diff --git a/src/shared/components/person/inbox.tsx b/src/shared/components/person/inbox.tsx index d64aba41..428317db 100644 --- a/src/shared/components/person/inbox.tsx +++ b/src/shared/components/person/inbox.tsx @@ -560,6 +560,7 @@ export class Inbox extends Component { return ( { ; @@ -24,7 +25,9 @@ interface State { } export class PasswordChange extends Component { - private isoData = setIsoData(this.context); + get isoData(): IsoData { + return this.context.store.getState().value; + } state: State = { passwordChangeRes: EMPTY_REQUEST, @@ -128,9 +131,10 @@ export class PasswordChange extends Component { if (i.state.passwordChangeRes.state === "success") { toast(I18NextService.i18n.t("password_changed")); + // TODO test this const site = await HttpService.client.getSite(); if (site.state === "success") { - UserService.Instance.myUserInfo = site.data.my_user; + i.context.store.dispatch(updateSite(site.data)); } i.props.history.replace("/"); diff --git a/src/shared/components/person/person-listing.tsx b/src/shared/components/person/person-listing.tsx index 92ab71f7..9fcc4328 100644 --- a/src/shared/components/person/person-listing.tsx +++ b/src/shared/components/person/person-listing.tsx @@ -92,6 +92,7 @@ export class PersonListing extends Component { )} {displayName} diff --git a/src/shared/components/person/profile.tsx b/src/shared/components/person/profile.tsx index c32c964d..b4823f54 100644 --- a/src/shared/components/person/profile.tsx +++ b/src/shared/components/person/profile.tsx @@ -490,6 +490,7 @@ export class Profile extends Component< )}
diff --git a/src/shared/components/person/settings.tsx b/src/shared/components/person/settings.tsx index 82531c9d..d94c4b3f 100644 --- a/src/shared/components/person/settings.tsx +++ b/src/shared/components/person/settings.tsx @@ -4,7 +4,6 @@ import { fetchThemeList, fetchUsers, instanceToChoice, - myAuth, personToChoice, setTheme, showLocal, @@ -64,6 +63,7 @@ import TotpModal from "../common/totp-modal"; import { LoadingEllipses } from "../common/loading-ellipses"; import { updateDataBsTheme } from "../../utils/browser"; import { getHttpBaseInternal } from "../../utils/env"; +import { updateSite } from "@utils/app/setup-redux"; type SettingsData = RouteDataResponse<{ instancesRes: GetFederatedInstancesResponse; @@ -763,6 +763,7 @@ export class Settings extends Component { imageSrc={this.state.saveUserSettingsForm.avatar} onUpload={this.handleAvatarUpload} onRemove={this.handleAvatarRemove} + isLoggedIn={!!this.isoData.site_res.my_user} rounded />
@@ -777,6 +778,7 @@ export class Settings extends Component { imageSrc={this.state.saveUserSettingsForm.banner} onUpload={this.handleBannerUpload} onRemove={this.handleBannerRemove} + isLoggedIn={!!this.isoData.site_res.my_user} /> @@ -1298,13 +1300,11 @@ export class Settings extends Component { } async handleUnblockCommunity(i: { ctx: Settings; communityId: number }) { - if (myAuth()) { - const res = await HttpService.client.blockCommunity({ - community_id: i.communityId, - block: false, - }); - i.ctx.communityBlock(res); - } + const res = await HttpService.client.blockCommunity({ + community_id: i.communityId, + block: false, + }); + i.ctx.communityBlock(res); } async handleBlockInstance({ value }: Choice) { @@ -1530,8 +1530,8 @@ export class Settings extends Component { siteRes: siteRes.data, }); - // TODO need to update this - UserService.Instance.myUserInfo = siteRes.data.my_user; + // TODO need to test this + i.context.store.dispatch(updateSite(siteRes.data)); } toast(I18NextService.i18n.t("saved")); @@ -1631,8 +1631,8 @@ export class Settings extends Component { }, } = siteRes.data.my_user!.local_user_view; - // TODO need to update redux - UserService.Instance.myUserInfo = siteRes.data.my_user; + // TODO need to test this + i.context.store.dispatch(updateSite(siteRes.data)); updateDataBsTheme(siteRes.data); i.setState(prev => ({ @@ -1695,7 +1695,7 @@ export class Settings extends Component { delete_content: false, }); if (deleteAccountRes.state === "success") { - UserService.Instance.logout(); + HttpService.logout(); this.context.router.history.replace("/"); } diff --git a/src/shared/components/post/post-listing.tsx b/src/shared/components/post/post-listing.tsx index 6459a9de..4219d393 100644 --- a/src/shared/components/post/post-listing.tsx +++ b/src/shared/components/post/post-listing.tsx @@ -1,4 +1,3 @@ -import { myAuth } from "@utils/app"; import { canShare, share } from "@utils/browser"; import { getExternalHost, getHttpBase } from "@utils/env"; import { @@ -255,7 +254,10 @@ export class PostListing extends Component { <>
@@ -264,7 +266,10 @@ export class PostListing extends Component { className="p-0 border-0 bg-transparent d-inline-block" onClick={linkEvent(this, this.handleImageExpandClick)} > - +
@@ -310,6 +315,7 @@ export class PostListing extends Component { thumbnail alt="" nsfw={pv.post.nsfw || pv.community.nsfw} + myUserInfo={this.props.myUserInfo} /> ); } @@ -630,7 +636,7 @@ export class PostListing extends Component { {mobile && !this.props.viewOnly && ( {
{ i.setState({ imageExpanded: !i.state.imageExpanded }); setupTippy(); - if (myAuth() && !i.postView.read) { + if (!!this.props.myUserInfo && !i.postView.read) { i.props.onMarkPostAsRead({ post_ids: [i.postView.post.id], read: true, diff --git a/src/shared/components/private_message/private-message.tsx b/src/shared/components/private_message/private-message.tsx index 035983f8..ca27d2e2 100644 --- a/src/shared/components/private_message/private-message.tsx +++ b/src/shared/components/private_message/private-message.tsx @@ -5,11 +5,12 @@ import { DeletePrivateMessage, EditPrivateMessage, MarkPrivateMessageAsRead, + MyUserInfo, Person, PrivateMessageView, } from "lemmy-js-client"; import { mdToHtml } from "../../markdown"; -import { I18NextService, UserService } from "../../services"; +import { I18NextService } from "../../services"; import { Icon, Spinner } from "../common/icon"; import { MomentTime } from "../common/moment-time"; import { PersonListing } from "../person/person-listing"; @@ -28,6 +29,7 @@ interface PrivateMessageState { interface PrivateMessageProps { private_message_view: PrivateMessageView; + myUserInfo?: MyUserInfo; onDelete(form: DeletePrivateMessage): void; onMarkRead(form: MarkPrivateMessageAsRead): void; onReport(form: CreatePrivateMessageReport): void; @@ -57,7 +59,7 @@ export class PrivateMessage extends Component< get mine(): boolean { return ( - UserService.Instance.myUserInfo?.local_user_view.person.id === + this.props.myUserInfo?.local_user_view.person.id === this.props.private_message_view.creator.id ); } diff --git a/src/shared/components/remote-fetch.tsx b/src/shared/components/remote-fetch.tsx index 0be4ab25..f18e0102 100644 --- a/src/shared/components/remote-fetch.tsx +++ b/src/shared/components/remote-fetch.tsx @@ -151,7 +151,11 @@ export class RemoteFetch extends Component {

{I18NextService.i18n.t("community_federated")}

{communityView.community.banner && ( - + )}

@@ -163,6 +167,7 @@ export class RemoteFetch extends Component {

)} { window.scrollTo(0, 0); restoreScrollPosition(this.context); - if (myAuth()) { + if (this.isoData.site_res.my_user) { this.setState({ resolveObjectRes: LOADING_REQUEST }); this.setState({ resolveObjectRes: await HttpService.silent_client.resolveObject({ diff --git a/src/shared/services/HttpService.ts b/src/shared/services/HttpService.ts index 3bc37555..1bfd42bb 100644 --- a/src/shared/services/HttpService.ts +++ b/src/shared/services/HttpService.ts @@ -104,6 +104,9 @@ export function wrapClient(client: LemmyHttp, silent = false) { // auth: string; // } +/** + * An HTTP service, only to be used in the browser client + */ export class HttpService { static #_instance: HttpService; #silent_client: WrappedLemmyHttp; @@ -114,13 +117,13 @@ export class HttpService { const auth = cookie.parse(document.cookie)[authCookieName]; if (auth) { - HttpService.client.setHeaders({ Authorization: `Bearer ${auth}` }); + lemmyHttp.setHeaders({ Authorization: `Bearer ${auth}` }); } this.#client = wrapClient(lemmyHttp); this.#silent_client = wrapClient(lemmyHttp, true); } - public login({ + public static login({ res, showToast = true, }: { @@ -130,15 +133,17 @@ export class HttpService { if (isBrowser() && res.jwt) { showToast && toast(I18NextService.i18n.t("logged_in")); setAuthCookie(res.jwt); + const headers = { Authorization: `Bearer ${res.jwt}` }; + this.#_instance.#client.setHeaders(headers); + this.#_instance.#silent_client.setHeaders(headers); } } - public logout() { + public static logout() { if (isBrowser()) { clearAuthCookie(); } - - this.#client.logout(); + this.#_instance.#client.logout(); if (isAuthPath(location.pathname)) { location.replace("/"); diff --git a/src/shared/services/I18NextService.ts b/src/shared/services/I18NextService.ts index a7e8c979..e55d63ea 100644 --- a/src/shared/services/I18NextService.ts +++ b/src/shared/services/I18NextService.ts @@ -1,6 +1,5 @@ import { isBrowser } from "@utils/browser"; import i18next, { Resource } from "i18next"; -import { UserService } from "../services"; import { ar } from "../translations/ar"; import { bg } from "../translations/bg"; import { ca } from "../translations/ca"; @@ -32,6 +31,7 @@ import { sv } from "../translations/sv"; import { vi } from "../translations/vi"; import { zh } from "../translations/zh"; import { zh_Hant } from "../translations/zh_Hant"; +import { MyUserInfo } from "lemmy-js-client"; export const languages = [ { resource: ar, code: "ar", name: "العربية" }, @@ -74,20 +74,24 @@ function format(value: any, format: any): any { return format === "uppercase" ? value.toUpperCase() : value; } -class LanguageDetector { +export class LanguageDetector { static readonly type = "languageDetector"; + private myLanguages: string[]; + // TODO What's going on here? test this. detect() { + return this.myLanguages; + } + + setupMyLanguages(myUserInfo?: MyUserInfo): string[] { const langs: string[] = []; const myLang = - UserService.Instance.myUserInfo?.local_user_view.local_user - .interface_language ?? "browser"; + myUserInfo?.local_user_view.local_user.interface_language ?? "browser"; if (myLang !== "browser") langs.push(myLang); if (isBrowser()) langs.push(...navigator.languages); - return langs; } } diff --git a/src/shared/services/UnreadCounterService.ts b/src/shared/services/UnreadCounterService.ts index 9275a6be..0637e1ac 100644 --- a/src/shared/services/UnreadCounterService.ts +++ b/src/shared/services/UnreadCounterService.ts @@ -1,10 +1,10 @@ import { HttpService } from "../services"; import { updateUnreadCountsInterval } from "../config"; import { poll } from "@utils/helpers"; -import { myAuth } from "@utils/app"; -import { amAdmin } from "@utils/roles"; +import { amAdmin, moderatesSomething } from "@utils/roles"; import { isBrowser } from "@utils/browser"; import { BehaviorSubject } from "rxjs"; +import { MyUserInfo } from "lemmy-js-client"; /** * Service to poll and keep track of unread messages / notifications. @@ -14,6 +14,8 @@ export class UnreadCounterService { unreadPrivateMessages = 0; unreadReplies = 0; unreadMentions = 0; + myUserInfo?: MyUserInfo = undefined; + public unreadInboxCountSubject: BehaviorSubject = new BehaviorSubject(0); @@ -36,14 +38,18 @@ export class UnreadCounterService { } } + public setMyUserInfo(myUserInfo: MyUserInfo) { + this.myUserInfo = myUserInfo; + } + private get shouldUpdate() { if (window.document.visibilityState === "hidden") { return false; - } - if (!myAuth()) { + } else if (!this.myUserInfo) { return false; + } else { + return true; } - return true; } public async updateInboxCounts() { @@ -61,7 +67,7 @@ export class UnreadCounterService { } public async updateReports() { - if (this.shouldUpdate && UserService.Instance.moderatesSomething) { + if (this.shouldUpdate && moderatesSomething(this.myUserInfo)) { const reportCountRes = await HttpService.client.getReportCount({}); if (reportCountRes.state === "success") { this.commentReportCount = reportCountRes.data.comment_reports ?? 0; @@ -78,7 +84,7 @@ export class UnreadCounterService { } public async updateApplications() { - if (this.shouldUpdate && amAdmin()) { + if (this.shouldUpdate && amAdmin(this.myUserInfo)) { const unreadApplicationsRes = await HttpService.client.getUnreadRegistrationApplicationCount(); if (unreadApplicationsRes.state === "success") { diff --git a/src/shared/utils/app/initialize-site.ts b/src/shared/utils/app/initialize-site.ts index 5e148615..27e0263c 100644 --- a/src/shared/utils/app/initialize-site.ts +++ b/src/shared/utils/app/initialize-site.ts @@ -1,12 +1,16 @@ import { GetSiteResponse } from "lemmy-js-client"; import { setupEmojiDataModel, setupMarkdown } from "../../markdown"; -import { I18NextService, UserService } from "../../services"; +import { I18NextService } from "../../services"; import { updateDataBsTheme } from "@utils/browser"; +import { LanguageDetector } from "shared/services/I18NextService"; export default function initializeSite(site?: GetSiteResponse) { - // TODO Should already be in siteRes - UserService.Instance.myUserInfo = site?.my_user; updateDataBsTheme(site); + + // TODO test this + const ld = new LanguageDetector(); + ld.setupMyLanguages(site?.my_user); + I18NextService.i18n.changeLanguage(); if (site) { setupEmojiDataModel(site.custom_emojis ?? []); diff --git a/src/shared/utils/app/my-auth.ts b/src/shared/utils/app/my-auth.ts index 046407d7..17ccd795 100644 --- a/src/shared/utils/app/my-auth.ts +++ b/src/shared/utils/app/my-auth.ts @@ -1,8 +1,12 @@ import { isBrowser } from "@utils/browser"; import { toast } from "../../../shared/toast"; import { I18NextService } from "../../services"; +import cookie from "cookie"; +import { authCookieName } from "../../config"; +// TODO get rid of this export default function myAuth(throwErr = false): string | undefined { + const auth = cookie.parse(document.cookie)[authCookieName]; if (auth) { return auth; } else { diff --git a/src/shared/utils/app/setup-redux.ts b/src/shared/utils/app/setup-redux.ts index b3177cb1..c23572e4 100644 --- a/src/shared/utils/app/setup-redux.ts +++ b/src/shared/utils/app/setup-redux.ts @@ -1,13 +1,33 @@ -import { configureStore, createSlice } from "@reduxjs/toolkit"; +import { PayloadAction, configureStore, createSlice } from "@reduxjs/toolkit"; import { IsoDataOptionalSite } from "../../../shared/interfaces"; +import { GetSiteResponse } from "lemmy-js-client"; + +interface isoDataState { + value?: IsoDataOptionalSite; +} + +const initialState: isoDataState = { + value: undefined, +}; + +export const isoDataSlice = createSlice({ + name: "isoData", + initialState, + reducers: { + updateIsoData: (state, action: PayloadAction) => { + state.value = action.payload; + }, + updateSite: (state, action: PayloadAction) => { + state.value!.site_res = action.payload; + }, + }, +}); + +export const { updateIsoData, updateSite } = isoDataSlice.actions; + +export default function setupRedux(isoData: IsoDataOptionalSite) { + const store = configureStore({ reducer: isoDataSlice.reducer }); + store.dispatch(updateIsoData(isoData)); -// TODO add reducer function here -export default function setupRedux(isoData?: IsoDataOptionalSite) { - const slice = createSlice({ - name: "isoData", - initialState: { value: isoData }, - reducers: {}, - }); - const store = configureStore({ reducer: slice.reducer }); return store; }