7 import { capitalizeFirstLetter } from "@utils/helpers";
8 import { RouteDataResponse } from "@utils/types";
9 import classNames from "classnames";
10 import { Component, linkEvent } from "inferno";
12 BannedPersonsResponse,
17 GetFederatedInstancesResponse,
20 } from "lemmy-js-client";
21 import { InitialFetchRequest } from "../../interfaces";
22 import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
23 import { FirstLoadService, I18NextService } from "../../services";
24 import { HttpService, RequestState } from "../../services/HttpService";
25 import { toast } from "../../toast";
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";
35 type AdminSettingsData = RouteDataResponse<{
36 bannedRes: BannedPersonsResponse;
37 instancesRes: GetFederatedInstancesResponse;
40 interface AdminSettingsState {
41 siteRes: GetSiteResponse;
44 instancesRes: RequestState<GetFederatedInstancesResponse>;
45 bannedRes: RequestState<BannedPersonsResponse>;
46 leaveAdminTeamRes: RequestState<GetSiteResponse>;
47 emojiLoading: boolean;
50 isIsomorphic: boolean;
53 export class AdminSettings extends Component<any, AdminSettingsState> {
54 private isoData = setIsoData<AdminSettingsData>(this.context);
55 state: AdminSettingsState = {
56 siteRes: this.isoData.site_res,
59 bannedRes: { state: "empty" },
60 instancesRes: { state: "empty" },
61 leaveAdminTeamRes: { state: "empty" },
68 constructor(props: any, context: any) {
69 super(props, context);
71 this.handleEditSite = this.handleEditSite.bind(this);
72 this.handleEditEmoji = this.handleEditEmoji.bind(this);
73 this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
74 this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
76 // Only fetch the data if coming from another route
77 if (FirstLoadService.isFirstLoad) {
78 const { bannedRes, instancesRes } = this.isoData.routeData;
89 static async fetchInitialData({
92 }: InitialFetchRequest): Promise<AdminSettingsData> {
94 bannedRes: await client.getBannedPersons({
97 instancesRes: await client.getFederatedInstances({
103 async componentDidMount() {
104 if (!this.state.isIsomorphic) {
105 await this.fetchData();
109 get documentTitle(): string {
110 return `${I18NextService.i18n.t("admin_settings")} - ${
111 this.state.siteRes.site_view.site.name
116 const federationData =
117 this.state.instancesRes.state === "success"
118 ? this.state.instancesRes.data.federated_instances
122 <div className="admin-settings container-lg">
124 title={this.documentTitle}
125 path={this.context.router.route.match.url}
131 label: I18NextService.i18n.t("site"),
132 getNode: isSelected => (
134 className={classNames("tab-pane show", {
140 <div className="row">
141 <div className="col-12 col-md-6">
143 showLocal={showLocal(this.isoData)}
144 allowedInstances={federationData?.allowed}
145 blockedInstances={federationData?.blocked}
146 onSaveSite={this.handleEditSite}
147 siteRes={this.state.siteRes}
148 themeList={this.state.themeList}
149 loading={this.state.loading}
152 <div className="col-12 col-md-6">
161 key: "rate_limiting",
162 label: "Rate Limiting",
163 getNode: isSelected => (
165 className={classNames("tab-pane", {
169 id="rate_limiting-tab-pane"
173 this.state.siteRes.site_view.local_site_rate_limit
175 onSaveSite={this.handleEditSite}
176 loading={this.state.loading}
183 label: I18NextService.i18n.t("taglines"),
184 getNode: isSelected => (
186 className={classNames("tab-pane", {
190 id="taglines-tab-pane"
192 <div className="row">
194 taglines={this.state.siteRes.taglines}
195 onSaveSite={this.handleEditSite}
196 loading={this.state.loading}
204 label: I18NextService.i18n.t("emojis"),
205 getNode: isSelected => (
207 className={classNames("tab-pane", {
213 <div className="row">
215 onCreate={this.handleCreateEmoji}
216 onDelete={this.handleDeleteEmoji}
217 onEdit={this.handleEditEmoji}
218 loading={this.state.emojiLoading}
232 bannedRes: { state: "loading" },
233 instancesRes: { state: "loading" },
237 const auth = myAuthRequired();
239 const [bannedRes, instancesRes, themeList] = await Promise.all([
240 HttpService.client.getBannedPersons({ auth }),
241 HttpService.client.getFederatedInstances({ auth }),
255 <h5>{capitalizeFirstLetter(I18NextService.i18n.t("admins"))}</h5>
256 <ul className="list-unstyled">
257 {this.state.siteRes.admins.map(admin => (
258 <li key={admin.person.id} className="list-inline-item">
259 <PersonListing person={admin.person} />
271 onClick={linkEvent(this, this.handleLeaveAdminTeam)}
272 className="btn btn-danger mb-2"
274 {this.state.leaveAdminTeamRes.state == "loading" ? (
277 I18NextService.i18n.t("leave_admin_team")
284 switch (this.state.bannedRes.state) {
292 const bans = this.state.bannedRes.data.banned;
295 <h5>{I18NextService.i18n.t("banned_users")}</h5>
296 <ul className="list-unstyled">
297 {bans.map(banned => (
298 <li key={banned.person.id} className="list-inline-item">
299 <PersonListing person={banned.person} />
309 async handleEditSite(form: EditSite) {
310 this.setState({ loading: true });
312 const editRes = await HttpService.client.editSite(form);
314 if (editRes.state === "success") {
316 s.siteRes.site_view = editRes.data.site_view;
317 // TODO: Where to get taglines from?
318 s.siteRes.taglines = editRes.data.taglines;
321 toast(I18NextService.i18n.t("site_saved"));
324 this.setState({ loading: false });
329 handleSwitchTab(i: { ctx: AdminSettings; tab: string }) {
330 i.ctx.setState({ currentTab: i.tab });
333 async handleLeaveAdminTeam(i: AdminSettings) {
334 i.setState({ leaveAdminTeamRes: { state: "loading" } });
336 leaveAdminTeamRes: await HttpService.client.leaveAdmin({
337 auth: myAuthRequired(),
341 if (this.state.leaveAdminTeamRes.state === "success") {
342 toast(I18NextService.i18n.t("left_admin_team"));
343 this.context.router.history.replace("/");
347 async handleEditEmoji(form: EditCustomEmoji) {
348 this.setState({ emojiLoading: true });
350 const res = await HttpService.client.editCustomEmoji(form);
351 if (res.state === "success") {
352 updateEmojiDataModel(res.data.custom_emoji);
355 this.setState({ emojiLoading: false });
358 async handleDeleteEmoji(form: DeleteCustomEmoji) {
359 this.setState({ emojiLoading: true });
361 const res = await HttpService.client.deleteCustomEmoji(form);
362 if (res.state === "success") {
363 removeFromEmojiDataModel(res.data.id);
366 this.setState({ emojiLoading: false });
369 async handleCreateEmoji(form: CreateCustomEmoji) {
370 this.setState({ emojiLoading: true });
372 const res = await HttpService.client.createCustomEmoji(form);
373 if (res.state === "success") {
374 updateEmojiDataModel(res.data.custom_emoji);
377 this.setState({ emojiLoading: false });