mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-27 06:41:12 +00:00
Add card to federation success page
This commit is contained in:
parent
b88de28f2e
commit
235843f96f
6 changed files with 200 additions and 85 deletions
|
@ -249,7 +249,6 @@ hr {
|
||||||
|
|
||||||
.fl-1 {
|
.fl-1 {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.img-blur {
|
.img-blur {
|
||||||
|
|
|
@ -72,7 +72,7 @@ export class App extends Component<AppProps, any> {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ErrorGuard>
|
<ErrorGuard>
|
||||||
<div tabIndex={-1} className="fl-1">
|
<div tabIndex={-1}>
|
||||||
{RouteComponent &&
|
{RouteComponent &&
|
||||||
(isAuthPath(path ?? "") ? (
|
(isAuthPath(path ?? "") ? (
|
||||||
<AuthGuard {...routeProps}>
|
<AuthGuard {...routeProps}>
|
||||||
|
|
|
@ -13,6 +13,7 @@ interface PictrsImageProps {
|
||||||
nsfw?: boolean;
|
nsfw?: boolean;
|
||||||
iconOverlay?: boolean;
|
iconOverlay?: boolean;
|
||||||
pushup?: boolean;
|
pushup?: boolean;
|
||||||
|
cardTop?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class PictrsImage extends Component<PictrsImageProps, any> {
|
export class PictrsImage extends Component<PictrsImageProps, any> {
|
||||||
|
@ -21,28 +22,30 @@ export class PictrsImage extends Component<PictrsImageProps, any> {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { src, icon, iconOverlay, banner, thumbnail, nsfw, pushup, cardTop } =
|
||||||
|
this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<picture>
|
<picture>
|
||||||
<source srcSet={this.src("webp")} type="image/webp" />
|
<source srcSet={this.src("webp")} type="image/webp" />
|
||||||
<source srcSet={this.props.src} />
|
<source srcSet={src} />
|
||||||
<source srcSet={this.src("jpg")} type="image/jpeg" />
|
<source srcSet={this.src("jpg")} type="image/jpeg" />
|
||||||
<img
|
<img
|
||||||
src={this.props.src}
|
src={src}
|
||||||
alt={this.alt()}
|
alt={this.alt()}
|
||||||
title={this.alt()}
|
title={this.alt()}
|
||||||
loading="lazy"
|
loading="lazy"
|
||||||
className={classNames("overflow-hidden pictrs-image", {
|
className={classNames("overflow-hidden pictrs-image", {
|
||||||
"img-fluid": !this.props.icon && !this.props.iconOverlay,
|
"img-fluid": !icon && !iconOverlay,
|
||||||
banner: this.props.banner,
|
banner,
|
||||||
"thumbnail rounded object-fit-cover":
|
"thumbnail rounded object-fit-cover": thumbnail && !icon && !banner,
|
||||||
this.props.thumbnail && !this.props.icon && !this.props.banner,
|
"img-expanded slight-radius": !thumbnail && !icon,
|
||||||
"img-expanded slight-radius":
|
"img-blur": thumbnail && nsfw,
|
||||||
!this.props.thumbnail && !this.props.icon,
|
"object-fit-cover img-icon me-1": icon,
|
||||||
"img-blur": this.props.thumbnail && this.props.nsfw,
|
|
||||||
"object-fit-cover img-icon me-1": this.props.icon,
|
|
||||||
"ms-2 mb-0 rounded-circle object-fit-cover avatar-overlay":
|
"ms-2 mb-0 rounded-circle object-fit-cover avatar-overlay":
|
||||||
this.props.iconOverlay,
|
iconOverlay,
|
||||||
"avatar-pushup": this.props.pushup,
|
"avatar-pushup": pushup,
|
||||||
|
"card-img-top": cardTop,
|
||||||
})}
|
})}
|
||||||
/>
|
/>
|
||||||
</picture>
|
</picture>
|
||||||
|
|
68
src/shared/components/common/subscribe-button.tsx
Normal file
68
src/shared/components/common/subscribe-button.tsx
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
import { NoOptionI18nKeys } from "i18next";
|
||||||
|
import { MouseEventHandler } from "inferno";
|
||||||
|
import { SubscribedType } from "lemmy-js-client";
|
||||||
|
import { I18NextService, UserService } from "../../services";
|
||||||
|
import { Icon, Spinner } from "./icon";
|
||||||
|
|
||||||
|
interface SubscribeButtonProps {
|
||||||
|
subscribed: SubscribedType;
|
||||||
|
onFollow: MouseEventHandler;
|
||||||
|
onUnFollow: MouseEventHandler;
|
||||||
|
loading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SubscribeButton({
|
||||||
|
subscribed,
|
||||||
|
onFollow,
|
||||||
|
onUnFollow,
|
||||||
|
loading,
|
||||||
|
}: SubscribeButtonProps) {
|
||||||
|
let i18key: NoOptionI18nKeys;
|
||||||
|
|
||||||
|
switch (subscribed) {
|
||||||
|
case "NotSubscribed": {
|
||||||
|
i18key = "subscribe";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "Subscribed": {
|
||||||
|
i18key = "joined";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
i18key = "subscribe_pending";
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!UserService.Instance.myUserInfo) {
|
||||||
|
return (
|
||||||
|
<button type="button" className="btn btn-secondary d-block mb-2 w-100">
|
||||||
|
{I18NextService.i18n.t("subscribe")}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
className={`btn btn-${
|
||||||
|
subscribed === "Pending" ? "warning" : "secondary"
|
||||||
|
} d-block mb-2 w-100`}
|
||||||
|
onClick={subscribed === "NotSubscribed" ? onFollow : onUnFollow}
|
||||||
|
>
|
||||||
|
{loading ? (
|
||||||
|
<Spinner />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{subscribed === "Subscribed" && (
|
||||||
|
<Icon icon="check" classes="icon-inline me-1" />
|
||||||
|
)}
|
||||||
|
{I18NextService.i18n.t(i18key)}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,7 +1,6 @@
|
||||||
import { myAuthRequired } from "@utils/app";
|
import { myAuthRequired } from "@utils/app";
|
||||||
import { getUnixTime, hostname } from "@utils/helpers";
|
import { getUnixTime, hostname } from "@utils/helpers";
|
||||||
import { amAdmin, amMod, amTopMod } from "@utils/roles";
|
import { amAdmin, amMod, amTopMod } from "@utils/roles";
|
||||||
import { NoOptionI18nKeys } from "i18next";
|
|
||||||
import { Component, InfernoNode, linkEvent } from "inferno";
|
import { Component, InfernoNode, linkEvent } from "inferno";
|
||||||
import { T } from "inferno-i18next-dess";
|
import { T } from "inferno-i18next-dess";
|
||||||
import { Link } from "inferno-router";
|
import { Link } from "inferno-router";
|
||||||
|
@ -23,6 +22,7 @@ import { I18NextService, UserService } from "../../services";
|
||||||
import { Badges } from "../common/badges";
|
import { Badges } from "../common/badges";
|
||||||
import { BannerIconHeader } from "../common/banner-icon-header";
|
import { BannerIconHeader } from "../common/banner-icon-header";
|
||||||
import { Icon, PurgeWarning, Spinner } from "../common/icon";
|
import { Icon, PurgeWarning, Spinner } from "../common/icon";
|
||||||
|
import { SubscribeButton } from "../common/subscribe-button";
|
||||||
import { CommunityForm } from "../community/community-form";
|
import { CommunityForm } from "../community/community-form";
|
||||||
import { CommunityLink } from "../community/community-link";
|
import { CommunityLink } from "../community/community-link";
|
||||||
import { PersonListing } from "../person/person-listing";
|
import { PersonListing } from "../person/person-listing";
|
||||||
|
@ -124,7 +124,10 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
|
|
||||||
sidebar() {
|
sidebar() {
|
||||||
const myUSerInfo = UserService.Instance.myUserInfo;
|
const myUSerInfo = UserService.Instance.myUserInfo;
|
||||||
const { name, actor_id } = this.props.community_view.community;
|
const {
|
||||||
|
community: { name, actor_id },
|
||||||
|
subscribed,
|
||||||
|
} = this.props.community_view;
|
||||||
return (
|
return (
|
||||||
<aside className="mb-3">
|
<aside className="mb-3">
|
||||||
<div id="sidebarContainer">
|
<div id="sidebarContainer">
|
||||||
|
@ -132,7 +135,12 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
<div className="card-body">
|
<div className="card-body">
|
||||||
{this.communityTitle()}
|
{this.communityTitle()}
|
||||||
{this.props.editable && this.adminButtons()}
|
{this.props.editable && this.adminButtons()}
|
||||||
{this.subscribeButton}
|
<SubscribeButton
|
||||||
|
subscribed={subscribed}
|
||||||
|
onFollow={linkEvent(this, this.handleFollowCommunity)}
|
||||||
|
onUnFollow={linkEvent(this, this.handleUnfollowCommunity)}
|
||||||
|
loading={this.state.followCommunityLoading}
|
||||||
|
/>
|
||||||
{this.canPost && this.createPost()}
|
{this.canPost && this.createPost()}
|
||||||
{myUSerInfo && this.blockCommunity()}
|
{myUSerInfo && this.blockCommunity()}
|
||||||
{!myUSerInfo && (
|
{!myUSerInfo && (
|
||||||
|
@ -231,64 +239,6 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get subscribeButton() {
|
|
||||||
const community_view = this.props.community_view;
|
|
||||||
|
|
||||||
let i18key: NoOptionI18nKeys;
|
|
||||||
|
|
||||||
switch (community_view.subscribed) {
|
|
||||||
case "NotSubscribed": {
|
|
||||||
i18key = "subscribe";
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "Subscribed": {
|
|
||||||
i18key = "joined";
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "Pending": {
|
|
||||||
i18key = "subscribe_pending";
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!UserService.Instance.myUserInfo) {
|
|
||||||
return (
|
|
||||||
<button type="button" className="btn btn-secondary d-block mb-2 w-100">
|
|
||||||
{I18NextService.i18n.t("subscribe")}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className={`btn btn-${
|
|
||||||
community_view.subscribed === "Pending" ? "warning" : "secondary"
|
|
||||||
} d-block mb-2 w-100`}
|
|
||||||
onClick={linkEvent(
|
|
||||||
this,
|
|
||||||
community_view.subscribed === "NotSubscribed"
|
|
||||||
? this.handleFollowCommunity
|
|
||||||
: this.handleUnfollowCommunity
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{this.state.followCommunityLoading ? (
|
|
||||||
<Spinner />
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
{community_view.subscribed === "Subscribed" && (
|
|
||||||
<Icon icon="check" classes="icon-inline me-1" />
|
|
||||||
)}
|
|
||||||
{I18NextService.i18n.t(i18key)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
blockCommunity() {
|
blockCommunity() {
|
||||||
const { subscribed, blocked } = this.props.community_view;
|
const { subscribed, blocked } = this.props.community_view;
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,17 @@
|
||||||
import { myAuthRequired, setIsoData } from "@utils/app";
|
import { myAuthRequired, setIsoData } from "@utils/app";
|
||||||
import { getQueryParams } from "@utils/helpers";
|
import { getQueryParams } from "@utils/helpers";
|
||||||
import { QueryParams, RouteDataResponse } from "@utils/types";
|
import { QueryParams, RouteDataResponse } from "@utils/types";
|
||||||
import { Component } from "inferno";
|
import { Component, linkEvent } from "inferno";
|
||||||
import { Link } from "inferno-router";
|
import { CommunityView, ResolveObjectResponse } from "lemmy-js-client";
|
||||||
import { ResolveObjectResponse } from "lemmy-js-client";
|
|
||||||
import { InitialFetchRequest } from "../interfaces";
|
import { InitialFetchRequest } from "../interfaces";
|
||||||
import { FirstLoadService, HttpService, I18NextService } from "../services";
|
import { FirstLoadService, HttpService, I18NextService } from "../services";
|
||||||
import { RequestState } from "../services/HttpService";
|
import { RequestState } from "../services/HttpService";
|
||||||
import { HtmlTags } from "./common/html-tags";
|
import { HtmlTags } from "./common/html-tags";
|
||||||
import { Spinner } from "./common/icon";
|
import { Spinner } from "./common/icon";
|
||||||
import { LoadingEllipses } from "./common/loading-ellipses";
|
import { LoadingEllipses } from "./common/loading-ellipses";
|
||||||
|
import { PictrsImage } from "./common/pictrs-image";
|
||||||
|
import { SubscribeButton } from "./common/subscribe-button";
|
||||||
|
import { CommunityLink } from "./community/community-link";
|
||||||
|
|
||||||
interface RemoteFetchProps {
|
interface RemoteFetchProps {
|
||||||
uri?: string;
|
uri?: string;
|
||||||
|
@ -22,6 +24,7 @@ type RemoteFetchData = RouteDataResponse<{
|
||||||
interface RemoteFetchState {
|
interface RemoteFetchState {
|
||||||
resolveObjectRes: RequestState<ResolveObjectResponse>;
|
resolveObjectRes: RequestState<ResolveObjectResponse>;
|
||||||
isIsomorphic: boolean;
|
isIsomorphic: boolean;
|
||||||
|
followCommunityLoading: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUriFromQuery = (uri?: string): string | undefined =>
|
const getUriFromQuery = (uri?: string): string | undefined =>
|
||||||
|
@ -38,18 +41,92 @@ function uriToQuery(uri: string) {
|
||||||
return match ? `!${match[2]}@${match[1]}` : "";
|
return match ? `!${match[2]}@${match[1]}` : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const _test_com_view: CommunityView = {
|
||||||
|
community: {
|
||||||
|
id: 2,
|
||||||
|
name: "test",
|
||||||
|
title: "Testy",
|
||||||
|
description:
|
||||||
|
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
|
||||||
|
removed: false,
|
||||||
|
published: "2023-07-08T03:12:46.323277",
|
||||||
|
updated: "2023-07-22T00:29:46.425846",
|
||||||
|
deleted: false,
|
||||||
|
nsfw: false,
|
||||||
|
actor_id: "https://localhost/c/test",
|
||||||
|
local: true,
|
||||||
|
icon: "http://localhost:1236/pictrs/image/dc71f5e8-fdb2-48ae-8a49-f504e4adaf26.png",
|
||||||
|
banner:
|
||||||
|
"http://localhost:1236/pictrs/image/fdf28def-5c4d-49a6-84bc-fd6b5d901cc7.jpeg",
|
||||||
|
hidden: false,
|
||||||
|
posting_restricted_to_mods: false,
|
||||||
|
instance_id: 1,
|
||||||
|
followers_url: "dwdwdwd",
|
||||||
|
inbox_url: "dwdwdwdw",
|
||||||
|
},
|
||||||
|
subscribed: "Subscribed",
|
||||||
|
blocked: false,
|
||||||
|
counts: {
|
||||||
|
id: 1,
|
||||||
|
community_id: 2,
|
||||||
|
subscribers: 1,
|
||||||
|
posts: 1,
|
||||||
|
comments: 30,
|
||||||
|
published: "2023-07-08T03:12:46.323277",
|
||||||
|
users_active_day: 1,
|
||||||
|
users_active_week: 1,
|
||||||
|
users_active_month: 1,
|
||||||
|
users_active_half_year: 1,
|
||||||
|
hot_rank: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleToggleFollow(i: RemoteFetch, follow: boolean) {
|
||||||
|
const { resolveObjectRes } = i.state;
|
||||||
|
if (resolveObjectRes.state === "success" && resolveObjectRes.data.community) {
|
||||||
|
i.setState({
|
||||||
|
followCommunityLoading: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const communityRes = await HttpService.client.followCommunity({
|
||||||
|
auth: myAuthRequired(),
|
||||||
|
community_id: resolveObjectRes.data.community.community.id,
|
||||||
|
follow,
|
||||||
|
});
|
||||||
|
|
||||||
|
i.setState(prev => {
|
||||||
|
if (
|
||||||
|
communityRes.state === "success" &&
|
||||||
|
prev.resolveObjectRes.state === "success" &&
|
||||||
|
prev.resolveObjectRes.data.community
|
||||||
|
) {
|
||||||
|
prev.resolveObjectRes.data.community = communityRes.data.community_view;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
followCommunityLoading: false,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleFollow = (i: RemoteFetch) => handleToggleFollow(i, true);
|
||||||
|
const handleUnfollow = (i: RemoteFetch) => handleToggleFollow(i, false);
|
||||||
|
|
||||||
export class RemoteFetch extends Component<any, RemoteFetchState> {
|
export class RemoteFetch extends Component<any, RemoteFetchState> {
|
||||||
private isoData = setIsoData<RemoteFetchData>(this.context);
|
private isoData = setIsoData<RemoteFetchData>(this.context);
|
||||||
state: RemoteFetchState = {
|
state: RemoteFetchState = {
|
||||||
resolveObjectRes: { state: "empty" },
|
resolveObjectRes: { state: "empty" },
|
||||||
isIsomorphic: false,
|
isIsomorphic: false,
|
||||||
|
followCommunityLoading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
constructor(props: any, context: any) {
|
constructor(props: any, context: any) {
|
||||||
super(props, context);
|
super(props, context);
|
||||||
|
|
||||||
if (FirstLoadService.isFirstLoad) {
|
if (FirstLoadService.isFirstLoad) {
|
||||||
// const { resolveObjectRes } = this.isoData.routeData;
|
// const { resolveObjectRes } = this.isoData.routeData;
|
||||||
|
|
||||||
this.state = {
|
this.state = {
|
||||||
...this.state,
|
...this.state,
|
||||||
|
@ -77,12 +154,12 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="remote-fetch container-lg d-flex">
|
<div className="remote-fetch container-lg">
|
||||||
<HtmlTags
|
<HtmlTags
|
||||||
title={this.documentTitle}
|
title={this.documentTitle}
|
||||||
path={this.context.router.route.match.url}
|
path={this.context.router.route.match.url}
|
||||||
/>
|
/>
|
||||||
<div className="row flex-grow-1 align-items-center">
|
<div className="row">
|
||||||
<div className="col-12 col-lg-6 offset-lg-3 text-center">
|
<div className="col-12 col-lg-6 offset-lg-3 text-center">
|
||||||
{this.content}
|
{this.content}
|
||||||
</div>
|
</div>
|
||||||
|
@ -92,7 +169,7 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
||||||
}
|
}
|
||||||
|
|
||||||
get content() {
|
get content() {
|
||||||
const status = "loading" as "success" | "loading" | "empty";
|
const status = "success" as "success" | "loading" | "empty";
|
||||||
|
|
||||||
const { uri } = getRemoteFetchQueryParams();
|
const { uri } = getRemoteFetchQueryParams();
|
||||||
const remoteCommunityName = uri ? uriToQuery(uri) : "remote community";
|
const remoteCommunityName = uri ? uriToQuery(uri) : "remote community";
|
||||||
|
@ -102,9 +179,27 @@ export class RemoteFetch extends Component<any, RemoteFetchState> {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1>Community Federated!</h1>
|
<h1>Community Federated!</h1>
|
||||||
<Link href="/" className="btn btn-lg bt-link btn-primary mt-5">
|
<div className="card mt-5">
|
||||||
Click to visit com
|
{_test_com_view.community.banner && (
|
||||||
</Link>
|
<PictrsImage src={_test_com_view.community.banner} cardTop />
|
||||||
|
)}
|
||||||
|
<div className="card-body">
|
||||||
|
<h2 className="card-title">
|
||||||
|
<CommunityLink community={_test_com_view.community} />
|
||||||
|
</h2>
|
||||||
|
{_test_com_view.community.description && (
|
||||||
|
<div className="card-text mb-3 preview-lines">
|
||||||
|
{_test_com_view.community.description}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<SubscribeButton
|
||||||
|
subscribed={_test_com_view.subscribed}
|
||||||
|
onFollow={linkEvent(this, handleFollow)}
|
||||||
|
onUnFollow={linkEvent(this, handleUnfollow)}
|
||||||
|
loading={this.state.followCommunityLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue