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