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