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 { i18n } from "../../i18next";
22 import { InitialFetchRequest } from "../../interfaces";
23 import { removeFromEmojiDataModel, updateEmojiDataModel } from "../../markdown";
24 import { FirstLoadService } from "../../services/FirstLoadService";
25 import { HttpService, RequestState } from "../../services/HttpService";
26 import { toast } from "../../toast";
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";
36 type AdminSettingsData = RouteDataResponse<{
37 bannedRes: BannedPersonsResponse;
38 instancesRes: GetFederatedInstancesResponse;
41 interface AdminSettingsState {
42 siteRes: GetSiteResponse;
45 instancesRes: RequestState<GetFederatedInstancesResponse>;
46 bannedRes: RequestState<BannedPersonsResponse>;
47 leaveAdminTeamRes: RequestState<GetSiteResponse>;
48 emojiLoading: boolean;
51 isIsomorphic: boolean;
54 export class AdminSettings extends Component<any, AdminSettingsState> {
55 private isoData = setIsoData<AdminSettingsData>(this.context);
56 state: AdminSettingsState = {
57 siteRes: this.isoData.site_res,
60 bannedRes: { state: "empty" },
61 instancesRes: { state: "empty" },
62 leaveAdminTeamRes: { state: "empty" },
69 constructor(props: any, context: any) {
70 super(props, context);
72 this.handleEditSite = this.handleEditSite.bind(this);
73 this.handleEditEmoji = this.handleEditEmoji.bind(this);
74 this.handleDeleteEmoji = this.handleDeleteEmoji.bind(this);
75 this.handleCreateEmoji = this.handleCreateEmoji.bind(this);
77 // Only fetch the data if coming from another route
78 if (FirstLoadService.isFirstLoad) {
79 const { bannedRes, instancesRes } = this.isoData.routeData;
90 static async fetchInitialData({
93 }: InitialFetchRequest): Promise<AdminSettingsData> {
95 bannedRes: await client.getBannedPersons({
98 instancesRes: await client.getFederatedInstances({
104 async componentDidMount() {
105 if (!this.state.isIsomorphic) {
106 await this.fetchData();
110 get documentTitle(): string {
111 return `${i18n.t("admin_settings")} - ${
112 this.state.siteRes.site_view.site.name
117 const federationData =
118 this.state.instancesRes.state === "success"
119 ? this.state.instancesRes.data.federated_instances
123 <div className="admin-settings container-lg">
125 title={this.documentTitle}
126 path={this.context.router.route.match.url}
132 label: i18n.t("site"),
133 getNode: isSelected => (
135 className={classNames("tab-pane show", {
141 <div className="row">
142 <div className="col-12 col-md-6">
144 showLocal={showLocal(this.isoData)}
145 allowedInstances={federationData?.allowed}
146 blockedInstances={federationData?.blocked}
147 onSaveSite={this.handleEditSite}
148 siteRes={this.state.siteRes}
149 themeList={this.state.themeList}
150 loading={this.state.loading}
153 <div className="col-12 col-md-6">
162 key: "rate_limiting",
163 label: "Rate Limiting",
164 getNode: isSelected => (
166 className={classNames("tab-pane", {
170 id="rate_limiting-tab-pane"
174 this.state.siteRes.site_view.local_site_rate_limit
176 onSaveSite={this.handleEditSite}
177 loading={this.state.loading}
184 label: i18n.t("taglines"),
185 getNode: isSelected => (
187 className={classNames("tab-pane", {
191 id="taglines-tab-pane"
193 <div className="row">
195 taglines={this.state.siteRes.taglines}
196 onSaveSite={this.handleEditSite}
197 loading={this.state.loading}
205 label: i18n.t("emojis"),
206 getNode: isSelected => (
208 className={classNames("tab-pane", {
214 <div className="row">
216 onCreate={this.handleCreateEmoji}
217 onDelete={this.handleDeleteEmoji}
218 onEdit={this.handleEditEmoji}
219 loading={this.state.emojiLoading}
233 bannedRes: { state: "loading" },
234 instancesRes: { state: "loading" },
238 const auth = myAuthRequired();
240 const [bannedRes, instancesRes, themeList] = await Promise.all([
241 HttpService.client.getBannedPersons({ auth }),
242 HttpService.client.getFederatedInstances({ auth }),
256 <h5>{capitalizeFirstLetter(i18n.t("admins"))}</h5>
257 <ul className="list-unstyled">
258 {this.state.siteRes.admins.map(admin => (
259 <li key={admin.person.id} className="list-inline-item">
260 <PersonListing person={admin.person} />
272 onClick={linkEvent(this, this.handleLeaveAdminTeam)}
273 className="btn btn-danger mb-2"
275 {this.state.leaveAdminTeamRes.state == "loading" ? (
278 i18n.t("leave_admin_team")
285 switch (this.state.bannedRes.state) {
293 const bans = this.state.bannedRes.data.banned;
296 <h5>{i18n.t("banned_users")}</h5>
297 <ul className="list-unstyled">
298 {bans.map(banned => (
299 <li key={banned.person.id} className="list-inline-item">
300 <PersonListing person={banned.person} />
310 async handleEditSite(form: EditSite) {
311 this.setState({ loading: true });
313 const editRes = await HttpService.client.editSite(form);
315 if (editRes.state === "success") {
317 s.siteRes.site_view = editRes.data.site_view;
318 // TODO: Where to get taglines from?
319 s.siteRes.taglines = editRes.data.taglines;
322 toast(i18n.t("site_saved"));
325 this.setState({ loading: false });
330 handleSwitchTab(i: { ctx: AdminSettings; tab: string }) {
331 i.ctx.setState({ currentTab: i.tab });
334 async handleLeaveAdminTeam(i: AdminSettings) {
335 i.setState({ leaveAdminTeamRes: { state: "loading" } });
337 leaveAdminTeamRes: await HttpService.client.leaveAdmin({
338 auth: myAuthRequired(),
342 if (this.state.leaveAdminTeamRes.state === "success") {
343 toast(i18n.t("left_admin_team"));
344 this.context.router.history.replace("/");
348 async handleEditEmoji(form: EditCustomEmoji) {
349 this.setState({ emojiLoading: true });
351 const res = await HttpService.client.editCustomEmoji(form);
352 if (res.state === "success") {
353 updateEmojiDataModel(res.data.custom_emoji);
356 this.setState({ emojiLoading: false });
359 async handleDeleteEmoji(form: DeleteCustomEmoji) {
360 this.setState({ emojiLoading: true });
362 const res = await HttpService.client.deleteCustomEmoji(form);
363 if (res.state === "success") {
364 removeFromEmojiDataModel(res.data.id);
367 this.setState({ emojiLoading: false });
370 async handleCreateEmoji(form: CreateCustomEmoji) {
371 this.setState({ emojiLoading: true });
373 const res = await HttpService.client.createCustomEmoji(form);
374 if (res.state === "success") {
375 updateEmojiDataModel(res.data.custom_emoji);
378 this.setState({ emojiLoading: false });