]> Untitled Git - lemmy-ui.git/blob - src/shared/components/home/admin-settings.tsx
Merge branch 'main' into breakout-role-utils
[lemmy-ui.git] / src / shared / components / home / admin-settings.tsx
1 import { Component, linkEvent } from "inferno";
2 import {
3   BannedPersonsResponse,
4   CreateCustomEmoji,
5   DeleteCustomEmoji,
6   EditCustomEmoji,
7   EditSite,
8   GetFederatedInstancesResponse,
9   GetSiteResponse,
10   PersonView,
11 } from "lemmy-js-client";
12 import { i18n } from "../../i18next";
13 import { InitialFetchRequest } from "../../interfaces";
14 import { FirstLoadService } from "../../services/FirstLoadService";
15 import { HttpService, RequestState } from "../../services/HttpService";
16 import {
17   capitalizeFirstLetter,
18   fetchThemeList,
19   myAuthRequired,
20   removeFromEmojiDataModel,
21   setIsoData,
22   showLocal,
23   toast,
24   updateEmojiDataModel,
25 } from "../../utils";
26 import { HtmlTags } from "../common/html-tags";
27 import { Spinner } from "../common/icon";
28 import Tabs from "../common/tabs";
29 import { PersonListing } from "../person/person-listing";
30 import { EmojiForm } from "./emojis-form";
31 import RateLimitForm from "./rate-limit-form";
32 import { SiteForm } from "./site-form";
33 import { TaglineForm } from "./tagline-form";
34
35 interface AdminSettingsState {
36   siteRes: GetSiteResponse;
37   banned: PersonView[];
38   currentTab: string;
39   instancesRes: RequestState<GetFederatedInstancesResponse>;
40   bannedRes: RequestState<BannedPersonsResponse>;
41   leaveAdminTeamRes: RequestState<GetSiteResponse>;
42   emojiLoading: boolean;
43   loading: boolean;
44   themeList: string[];
45   isIsomorphic: boolean;
46 }
47
48 export class AdminSettings extends Component<any, AdminSettingsState> {
49   private isoData = setIsoData(this.context);
50   state: AdminSettingsState = {
51     siteRes: this.isoData.site_res,
52     banned: [],
53     currentTab: "site",
54     bannedRes: { state: "empty" },
55     instancesRes: { state: "empty" },
56     leaveAdminTeamRes: { state: "empty" },
57     emojiLoading: false,
58     loading: false,
59     themeList: [],
60     isIsomorphic: false,
61   };
62
63   constructor(props: any, context: any) {
64     super(props, context);
65
66     this.handleEditSite = this.handleEditSite.bind(this);
67     this.handleEditEmoji = this.handleEditEmoji.bind(this);
68     this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
69     this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
70
71     // Only fetch the data if coming from another route
72     if (FirstLoadService.isFirstLoad) {
73       const [bannedRes, instancesRes] = this.isoData.routeData;
74       this.state = {
75         ...this.state,
76         bannedRes,
77         instancesRes,
78         isIsomorphic: true,
79       };
80     }
81   }
82
83   async fetchData() {
84     this.setState({
85       bannedRes: { state: "loading" },
86       instancesRes: { state: "loading" },
87       themeList: [],
88       loading: true,
89     });
90
91     const auth = myAuthRequired();
92
93     const [bannedRes, instancesRes, themeList] = await Promise.all([
94       HttpService.client.getBannedPersons({ auth }),
95       HttpService.client.getFederatedInstances({ auth }),
96       fetchThemeList(),
97     ]);
98
99     this.setState({
100       bannedRes,
101       instancesRes,
102       themeList,
103       loading: false,
104     });
105   }
106
107   static fetchInitialData({
108     auth,
109     client,
110   }: InitialFetchRequest): Promise<any>[] {
111     const promises: Promise<RequestState<any>>[] = [];
112
113     if (auth) {
114       promises.push(client.getBannedPersons({ auth }));
115       promises.push(client.getFederatedInstances({ auth }));
116     } else {
117       promises.push(
118         Promise.resolve({ state: "empty" }),
119         Promise.resolve({ state: "empty" })
120       );
121     }
122
123     return promises;
124   }
125
126   async componentDidMount() {
127     if (!this.state.isIsomorphic) {
128       await this.fetchData();
129     }
130   }
131
132   get documentTitle(): string {
133     return `${i18n.t("admin_settings")} - ${
134       this.state.siteRes.site_view.site.name
135     }`;
136   }
137
138   render() {
139     const federationData =
140       this.state.instancesRes.state === "success"
141         ? this.state.instancesRes.data.federated_instances
142         : undefined;
143
144     return (
145       <div className="container-lg">
146         <HtmlTags
147           title={this.documentTitle}
148           path={this.context.router.route.match.url}
149         />
150         <Tabs
151           tabs={[
152             {
153               key: "site",
154               label: i18n.t("site"),
155               getNode: () => (
156                 <div className="row">
157                   <div className="col-12 col-md-6">
158                     <SiteForm
159                       showLocal={showLocal(this.isoData)}
160                       allowedInstances={federationData?.allowed}
161                       blockedInstances={federationData?.blocked}
162                       onSaveSite={this.handleEditSite}
163                       siteRes={this.state.siteRes}
164                       themeList={this.state.themeList}
165                       loading={this.state.loading}
166                     />
167                   </div>
168                   <div className="col-12 col-md-6">
169                     {this.admins()}
170                     {this.bannedUsers()}
171                   </div>
172                 </div>
173               ),
174             },
175             {
176               key: "rate_limiting",
177               label: "Rate Limiting",
178               getNode: () => (
179                 <RateLimitForm
180                   rateLimits={
181                     this.state.siteRes.site_view.local_site_rate_limit
182                   }
183                   onSaveSite={this.handleEditSite}
184                   loading={this.state.loading}
185                 />
186               ),
187             },
188             {
189               key: "taglines",
190               label: i18n.t("taglines"),
191               getNode: () => (
192                 <div className="row">
193                   <TaglineForm
194                     taglines={this.state.siteRes.taglines}
195                     onSaveSite={this.handleEditSite}
196                     loading={this.state.loading}
197                   />
198                 </div>
199               ),
200             },
201             {
202               key: "emojis",
203               label: i18n.t("emojis"),
204               getNode: () => (
205                 <div className="row">
206                   <EmojiForm
207                     onCreate={this.handleCreateEmoji}
208                     onDelete={this.handleDeleteEmoji}
209                     onEdit={this.handleEditEmoji}
210                     loading={this.state.emojiLoading}
211                   />
212                 </div>
213               ),
214             },
215           ]}
216         />
217       </div>
218     );
219   }
220
221   admins() {
222     return (
223       <>
224         <h5>{capitalizeFirstLetter(i18n.t("admins"))}</h5>
225         <ul className="list-unstyled">
226           {this.state.siteRes.admins.map(admin => (
227             <li key={admin.person.id} className="list-inline-item">
228               <PersonListing person={admin.person} />
229             </li>
230           ))}
231         </ul>
232         {this.leaveAdmin()}
233       </>
234     );
235   }
236
237   leaveAdmin() {
238     return (
239       <button
240         onClick={linkEvent(this, this.handleLeaveAdminTeam)}
241         className="btn btn-danger mb-2"
242       >
243         {this.state.leaveAdminTeamRes.state == "loading" ? (
244           <Spinner />
245         ) : (
246           i18n.t("leave_admin_team")
247         )}
248       </button>
249     );
250   }
251
252   bannedUsers() {
253     switch (this.state.bannedRes.state) {
254       case "loading":
255         return (
256           <h5>
257             <Spinner large />
258           </h5>
259         );
260       case "success": {
261         const bans = this.state.bannedRes.data.banned;
262         return (
263           <>
264             <h5>{i18n.t("banned_users")}</h5>
265             <ul className="list-unstyled">
266               {bans.map(banned => (
267                 <li key={banned.person.id} className="list-inline-item">
268                   <PersonListing person={banned.person} />
269                 </li>
270               ))}
271             </ul>
272           </>
273         );
274       }
275     }
276   }
277
278   async handleEditSite(form: EditSite) {
279     this.setState({ loading: true });
280
281     const editRes = await HttpService.client.editSite(form);
282
283     if (editRes.state === "success") {
284       this.setState(s => {
285         s.siteRes.site_view = editRes.data.site_view;
286         // TODO: Where to get taglines from?
287         s.siteRes.taglines = editRes.data.taglines;
288         return s;
289       });
290       toast(i18n.t("site_saved"));
291     }
292
293     this.setState({ loading: false });
294
295     return editRes;
296   }
297
298   handleSwitchTab(i: { ctx: AdminSettings; tab: string }) {
299     i.ctx.setState({ currentTab: i.tab });
300   }
301
302   async handleLeaveAdminTeam(i: AdminSettings) {
303     i.setState({ leaveAdminTeamRes: { state: "loading" } });
304     this.setState({
305       leaveAdminTeamRes: await HttpService.client.leaveAdmin({
306         auth: myAuthRequired(),
307       }),
308     });
309
310     if (this.state.leaveAdminTeamRes.state === "success") {
311       toast(i18n.t("left_admin_team"));
312       this.context.router.history.replace("/");
313     }
314   }
315
316   async handleEditEmoji(form: EditCustomEmoji) {
317     this.setState({ emojiLoading: true });
318
319     const res = await HttpService.client.editCustomEmoji(form);
320     if (res.state === "success") {
321       updateEmojiDataModel(res.data.custom_emoji);
322     }
323
324     this.setState({ emojiLoading: false });
325   }
326
327   async handleDeleteEmoji(form: DeleteCustomEmoji) {
328     this.setState({ emojiLoading: true });
329
330     const res = await HttpService.client.deleteCustomEmoji(form);
331     if (res.state === "success") {
332       removeFromEmojiDataModel(res.data.id);
333     }
334
335     this.setState({ emojiLoading: false });
336   }
337
338   async handleCreateEmoji(form: CreateCustomEmoji) {
339     this.setState({ emojiLoading: true });
340
341     const res = await HttpService.client.createCustomEmoji(form);
342     if (res.state === "success") {
343       updateEmojiDataModel(res.data.custom_emoji);
344     }
345
346     this.setState({ emojiLoading: false });
347   }
348 }