mirror of
https://github.com/LemmyNet/lemmy-ui.git
synced 2024-11-29 07:41:13 +00:00
Adding registration applications.
This commit is contained in:
parent
9948783325
commit
1683a6b857
16 changed files with 696 additions and 59 deletions
|
@ -72,7 +72,7 @@
|
|||
"husky": "^7.0.4",
|
||||
"import-sort-style-module": "^6.0.0",
|
||||
"iso-639-1": "^2.1.10",
|
||||
"lemmy-js-client": "0.14.0-rc.1",
|
||||
"lemmy-js-client": "0.15.0-rc.1",
|
||||
"lint-staged": "^12.1.2",
|
||||
"mini-css-extract-plugin": "^2.4.5",
|
||||
"node-fetch": "^2.6.1",
|
||||
|
|
|
@ -7,6 +7,8 @@ import {
|
|||
GetSiteResponse,
|
||||
GetUnreadCount,
|
||||
GetUnreadCountResponse,
|
||||
GetUnreadRegistrationApplicationCount,
|
||||
GetUnreadRegistrationApplicationCountResponse,
|
||||
PrivateMessageResponse,
|
||||
UserOperation,
|
||||
} from "lemmy-js-client";
|
||||
|
@ -41,6 +43,7 @@ interface NavbarState {
|
|||
expanded: boolean;
|
||||
unreadInboxCount: number;
|
||||
unreadReportCount: number;
|
||||
unreadApplicationCount: number;
|
||||
searchParam: string;
|
||||
toggleSearch: boolean;
|
||||
showDropdown: boolean;
|
||||
|
@ -52,11 +55,13 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
private userSub: Subscription;
|
||||
private unreadInboxCountSub: Subscription;
|
||||
private unreadReportCountSub: Subscription;
|
||||
private unreadApplicationCountSub: Subscription;
|
||||
private searchTextField: RefObject<HTMLInputElement>;
|
||||
emptyState: NavbarState = {
|
||||
isLoggedIn: !!this.props.site_res.my_user,
|
||||
unreadInboxCount: 0,
|
||||
unreadReportCount: 0,
|
||||
unreadApplicationCount: 0,
|
||||
expanded: false,
|
||||
searchParam: "",
|
||||
toggleSearch: false,
|
||||
|
@ -115,6 +120,11 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
UserService.Instance.unreadReportCountSub.subscribe(res => {
|
||||
this.setState({ unreadReportCount: res });
|
||||
});
|
||||
// Subscribe to unread application count
|
||||
this.unreadApplicationCountSub =
|
||||
UserService.Instance.unreadApplicationCountSub.subscribe(res => {
|
||||
this.setState({ unreadApplicationCount: res });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,6 +133,7 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
this.userSub.unsubscribe();
|
||||
this.unreadInboxCountSub.unsubscribe();
|
||||
this.unreadReportCountSub.unsubscribe();
|
||||
this.unreadApplicationCountSub.unsubscribe();
|
||||
}
|
||||
|
||||
updateUrl() {
|
||||
|
@ -215,6 +226,31 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
</li>
|
||||
</ul>
|
||||
)}
|
||||
{UserService.Instance.myUserInfo?.local_user_view.person
|
||||
.admin && (
|
||||
<ul class="navbar-nav ml-1">
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
to="/registration_applications"
|
||||
className="p-1 navbar-toggler nav-link border-0"
|
||||
onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
|
||||
title={i18n.t("unread_registration_applications", {
|
||||
count: this.state.unreadApplicationCount,
|
||||
formattedCount: numToSI(
|
||||
this.state.unreadApplicationCount
|
||||
),
|
||||
})}
|
||||
>
|
||||
<Icon icon="edit" />
|
||||
{this.state.unreadApplicationCount > 0 && (
|
||||
<span class="mx-1 badge badge-light">
|
||||
{numToSI(this.state.unreadApplicationCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
|
@ -366,6 +402,31 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
</li>
|
||||
</ul>
|
||||
)}
|
||||
{UserService.Instance.myUserInfo?.local_user_view.person
|
||||
.admin && (
|
||||
<ul class="navbar-nav my-2">
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
to="/registration_applications"
|
||||
className="nav-link"
|
||||
onMouseUp={linkEvent(this, this.handleHideExpandNavbar)}
|
||||
title={i18n.t("unread_registration_applications", {
|
||||
count: this.state.unreadApplicationCount,
|
||||
formattedCount: numToSI(
|
||||
this.state.unreadApplicationCount
|
||||
),
|
||||
})}
|
||||
>
|
||||
<Icon icon="edit" />
|
||||
{this.state.unreadApplicationCount > 0 && (
|
||||
<span class="mx-1 badge badge-light">
|
||||
{numToSI(this.state.unreadApplicationCount)}
|
||||
</span>
|
||||
)}
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
)}
|
||||
<ul class="navbar-nav">
|
||||
<li class="nav-item dropdown">
|
||||
<button
|
||||
|
@ -537,6 +598,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
this.state.unreadReportCount = data.post_reports + data.comment_reports;
|
||||
this.setState(this.state);
|
||||
this.sendReportUnread();
|
||||
} else if (op == UserOperation.GetUnreadRegistrationApplicationCount) {
|
||||
let data =
|
||||
wsJsonToRes<GetUnreadRegistrationApplicationCountResponse>(msg).data;
|
||||
this.state.unreadApplicationCount = data.registration_applications;
|
||||
this.setState(this.state);
|
||||
this.sendApplicationUnread();
|
||||
} else if (op == UserOperation.GetSite) {
|
||||
// This is only called on a successful login
|
||||
let data = wsJsonToRes<GetSiteResponse>(msg).data;
|
||||
|
@ -586,7 +653,6 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
let unreadForm: GetUnreadCount = {
|
||||
auth: authField(),
|
||||
};
|
||||
|
||||
WebSocketService.Instance.send(wsClient.getUnreadCount(unreadForm));
|
||||
|
||||
console.log("Fetching reports...");
|
||||
|
@ -594,8 +660,18 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
let reportCountForm: GetReportCount = {
|
||||
auth: authField(),
|
||||
};
|
||||
|
||||
WebSocketService.Instance.send(wsClient.getReportCount(reportCountForm));
|
||||
|
||||
if (UserService.Instance.myUserInfo?.local_user_view.person.admin) {
|
||||
console.log("Fetching applications...");
|
||||
|
||||
let applicationCountForm: GetUnreadRegistrationApplicationCount = {
|
||||
auth: authField(),
|
||||
};
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.getUnreadRegistrationApplicationCount(applicationCountForm)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
get currentLocation() {
|
||||
|
@ -612,6 +688,12 @@ export class Navbar extends Component<NavbarProps, NavbarState> {
|
|||
);
|
||||
}
|
||||
|
||||
sendApplicationUnread() {
|
||||
UserService.Instance.unreadApplicationCountSub.next(
|
||||
this.state.unreadApplicationCount
|
||||
);
|
||||
}
|
||||
|
||||
get canAdmin(): boolean {
|
||||
return (
|
||||
UserService.Instance.myUserInfo &&
|
||||
|
|
|
@ -25,6 +25,9 @@ export class CommentReport extends Component<CommentReportProps, any> {
|
|||
render() {
|
||||
let r = this.props.report;
|
||||
let comment = r.comment;
|
||||
let tippyContent = i18n.t(
|
||||
r.comment_report.resolved ? "unresolve_report" : "resolve_report"
|
||||
);
|
||||
|
||||
// Set the original post data ( a troll could change it )
|
||||
comment.content = r.comment_report.original_comment_text;
|
||||
|
@ -78,12 +81,8 @@ export class CommentReport extends Component<CommentReportProps, any> {
|
|||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
onClick={linkEvent(this, this.handleResolveReport)}
|
||||
data-tippy-content={
|
||||
r.comment_report.resolved ? "unresolve_report" : "resolve_report"
|
||||
}
|
||||
aria-label={
|
||||
r.comment_report.resolved ? "unresolve_report" : "resolve_report"
|
||||
}
|
||||
data-tippy-content={tippyContent}
|
||||
aria-label={tippyContent}
|
||||
>
|
||||
<Icon
|
||||
icon="check"
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { Icon, Spinner } from "./icon";
|
||||
|
||||
interface MarkdownTextAreaProps {
|
||||
initialContent: string;
|
||||
initialContent?: string;
|
||||
finished?: boolean;
|
||||
buttonTitle?: string;
|
||||
replyType?: boolean;
|
||||
|
|
129
src/shared/components/common/registration-application.tsx
Normal file
129
src/shared/components/common/registration-application.tsx
Normal file
|
@ -0,0 +1,129 @@
|
|||
import { Component, linkEvent } from "inferno";
|
||||
import { T } from "inferno-i18next-dess";
|
||||
import {
|
||||
ApproveRegistrationApplication,
|
||||
RegistrationApplicationView,
|
||||
} from "lemmy-js-client";
|
||||
import { i18n } from "../../i18next";
|
||||
import { WebSocketService } from "../../services";
|
||||
import { authField, mdToHtml, wsClient } from "../../utils";
|
||||
import { PersonListing } from "../person/person-listing";
|
||||
import { MarkdownTextArea } from "./markdown-textarea";
|
||||
import { MomentTime } from "./moment-time";
|
||||
|
||||
interface RegistrationApplicationProps {
|
||||
application: RegistrationApplicationView;
|
||||
}
|
||||
|
||||
interface RegistrationApplicationState {
|
||||
denyReason?: string;
|
||||
}
|
||||
|
||||
export class RegistrationApplication extends Component<
|
||||
RegistrationApplicationProps,
|
||||
RegistrationApplicationState
|
||||
> {
|
||||
private emptyState: RegistrationApplicationState = {
|
||||
denyReason: this.props.application.registration_application.deny_reason,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.state = this.emptyState;
|
||||
this.handleDenyReasonChange = this.handleDenyReasonChange.bind(this);
|
||||
}
|
||||
|
||||
render() {
|
||||
let a = this.props.application;
|
||||
let ra = this.props.application.registration_application;
|
||||
let accepted = a.creator_local_user.accepted_application;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div>
|
||||
{i18n.t("applicant")}: <PersonListing person={a.creator} />
|
||||
</div>
|
||||
<div>
|
||||
{i18n.t("created")}: <MomentTime showAgo data={ra} />
|
||||
</div>
|
||||
<div>{i18n.t("answer")}:</div>
|
||||
<div className="md-div" dangerouslySetInnerHTML={mdToHtml(ra.answer)} />
|
||||
|
||||
{a.admin && (
|
||||
<div>
|
||||
{accepted ? (
|
||||
<T i18nKey="approved_by">
|
||||
#
|
||||
<PersonListing person={a.admin} />
|
||||
</T>
|
||||
) : (
|
||||
<div>
|
||||
<T i18nKey="denied_by">
|
||||
#
|
||||
<PersonListing person={a.admin} />
|
||||
</T>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label">{i18n.t("deny_reason")}</label>
|
||||
<div class="col-sm-10">
|
||||
<MarkdownTextArea
|
||||
initialContent={this.state.denyReason}
|
||||
onContentChange={this.handleDenyReasonChange}
|
||||
hideNavigationWarnings
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="btn btn-secondary mr-2"
|
||||
onClick={linkEvent(this, this.handleApprove)}
|
||||
aria-label={i18n.t("approve")}
|
||||
>
|
||||
{i18n.t("approve")}
|
||||
</button>
|
||||
{!this.props.application.registration_application.deny_reason && (
|
||||
<button
|
||||
className="btn btn-secondary mr-2"
|
||||
onClick={linkEvent(this, this.handleDeny)}
|
||||
aria-label={i18n.t("deny")}
|
||||
>
|
||||
{i18n.t("deny")}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleApprove(i: RegistrationApplication) {
|
||||
let form: ApproveRegistrationApplication = {
|
||||
id: i.props.application.registration_application.id,
|
||||
deny_reason: "",
|
||||
approve: true,
|
||||
auth: authField(),
|
||||
};
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.approveRegistrationApplication(form)
|
||||
);
|
||||
}
|
||||
|
||||
handleDeny(i: RegistrationApplication) {
|
||||
let form: ApproveRegistrationApplication = {
|
||||
id: i.props.application.registration_application.id,
|
||||
approve: false,
|
||||
deny_reason: i.state.denyReason,
|
||||
auth: authField(),
|
||||
};
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.approveRegistrationApplication(form)
|
||||
);
|
||||
}
|
||||
|
||||
handleDenyReasonChange(val: string) {
|
||||
this.state.denyReason = val;
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
|
@ -185,8 +185,6 @@ export class Login extends Component<any, State> {
|
|||
if (msg.error) {
|
||||
toast(i18n.t(msg.error), "danger");
|
||||
this.state = this.emptyState;
|
||||
// Refetch another captcha
|
||||
WebSocketService.Instance.send(wsClient.getCaptcha());
|
||||
this.setState(this.state);
|
||||
return;
|
||||
} else {
|
||||
|
|
|
@ -17,6 +17,7 @@ import {
|
|||
authField,
|
||||
isBrowser,
|
||||
joinLemmyUrl,
|
||||
mdToHtml,
|
||||
setIsoData,
|
||||
toast,
|
||||
validEmail,
|
||||
|
@ -27,6 +28,7 @@ import {
|
|||
} from "../../utils";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Icon, Spinner } from "../common/icon";
|
||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||
|
||||
const passwordStrengthOptions: Options<string> = [
|
||||
{
|
||||
|
@ -77,6 +79,7 @@ export class Signup extends Component<any, State> {
|
|||
captcha_uuid: undefined,
|
||||
captcha_answer: undefined,
|
||||
honeypot: undefined,
|
||||
answer: undefined,
|
||||
},
|
||||
registerLoading: false,
|
||||
captcha: undefined,
|
||||
|
@ -88,6 +91,7 @@ export class Signup extends Component<any, State> {
|
|||
super(props, context);
|
||||
|
||||
this.state = this.emptyState;
|
||||
this.handleAnswerChange = this.handleAnswerChange.bind(this);
|
||||
|
||||
this.parseMessage = this.parseMessage.bind(this);
|
||||
this.subscription = wsSubscribe(this.parseMessage);
|
||||
|
@ -159,18 +163,24 @@ export class Signup extends Component<any, State> {
|
|||
type="email"
|
||||
id="register-email"
|
||||
class="form-control"
|
||||
placeholder={i18n.t("optional")}
|
||||
placeholder={
|
||||
this.state.site_view.site.require_email_verification
|
||||
? i18n.t("required")
|
||||
: i18n.t("optional")
|
||||
}
|
||||
value={this.state.registerForm.email}
|
||||
autoComplete="email"
|
||||
onInput={linkEvent(this, this.handleRegisterEmailChange)}
|
||||
required={this.state.site_view.site.require_email_verification}
|
||||
minLength={3}
|
||||
/>
|
||||
{!validEmail(this.state.registerForm.email) && (
|
||||
<div class="mt-2 mb-0 alert alert-light" role="alert">
|
||||
<Icon icon="alert-triangle" classes="icon-inline mr-2" />
|
||||
{i18n.t("no_password_reset")}
|
||||
</div>
|
||||
)}
|
||||
{!this.state.site_view.site.require_email_verification &&
|
||||
!validEmail(this.state.registerForm.email) && (
|
||||
<div class="mt-2 mb-0 alert alert-light" role="alert">
|
||||
<Icon icon="alert-triangle" classes="icon-inline mr-2" />
|
||||
{i18n.t("no_password_reset")}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -219,6 +229,40 @@ export class Signup extends Component<any, State> {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{this.state.site_view.site.require_application && (
|
||||
<>
|
||||
<div class="form-group row">
|
||||
<div class="offset-sm-2 col-sm-10">
|
||||
<div class="mt-2 alert alert-light" role="alert">
|
||||
<Icon icon="alert-triangle" classes="icon-inline mr-2" />
|
||||
{i18n.t("fill_out_application")}
|
||||
</div>
|
||||
<div
|
||||
className="md-div"
|
||||
dangerouslySetInnerHTML={mdToHtml(
|
||||
this.state.site_view.site.application_question
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group row">
|
||||
<label
|
||||
class="col-sm-2 col-form-label"
|
||||
htmlFor="application_answer"
|
||||
>
|
||||
{i18n.t("answer")}
|
||||
</label>
|
||||
<div class="col-sm-10">
|
||||
<MarkdownTextArea
|
||||
onContentChange={this.handleAnswerChange}
|
||||
hideNavigationWarnings
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
{this.state.captcha && (
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2" htmlFor="register-captcha">
|
||||
|
@ -382,6 +426,11 @@ export class Signup extends Component<any, State> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleAnswerChange(val: string) {
|
||||
this.state.registerForm.answer = val;
|
||||
this.setState(this.state);
|
||||
}
|
||||
|
||||
handleHoneyPotChange(i: Signup, event: any) {
|
||||
i.state.registerForm.honeypot = event.target.value;
|
||||
i.setState(i.state);
|
||||
|
@ -434,13 +483,24 @@ export class Signup extends Component<any, State> {
|
|||
let data = wsJsonToRes<LoginResponse>(msg).data;
|
||||
this.state = this.emptyState;
|
||||
this.setState(this.state);
|
||||
UserService.Instance.login(data);
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.userJoin({
|
||||
auth: authField(),
|
||||
})
|
||||
);
|
||||
this.props.history.push("/communities");
|
||||
// Only log them in if a jwt was set
|
||||
if (data.jwt) {
|
||||
UserService.Instance.login(data);
|
||||
WebSocketService.Instance.send(
|
||||
wsClient.userJoin({
|
||||
auth: authField(),
|
||||
})
|
||||
);
|
||||
this.props.history.push("/communities");
|
||||
} else {
|
||||
if (data.verify_email_sent) {
|
||||
toast(i18n.t("verify_email_sent"));
|
||||
}
|
||||
if (data.registration_created) {
|
||||
toast(i18n.t("registration_application_sent"));
|
||||
}
|
||||
this.props.history.push("/");
|
||||
}
|
||||
} else if (op == UserOperation.GetCaptcha) {
|
||||
let data = wsJsonToRes<GetCaptchaResponse>(msg).data;
|
||||
if (data.ok) {
|
||||
|
|
|
@ -3,12 +3,7 @@ import { Prompt } from "inferno-router";
|
|||
import { CreateSite, EditSite, Site } from "lemmy-js-client";
|
||||
import { i18n } from "../../i18next";
|
||||
import { WebSocketService } from "../../services";
|
||||
import {
|
||||
authField,
|
||||
capitalizeFirstLetter,
|
||||
randomStr,
|
||||
wsClient,
|
||||
} from "../../utils";
|
||||
import { authField, capitalizeFirstLetter, wsClient } from "../../utils";
|
||||
import { Spinner } from "../common/icon";
|
||||
import { ImageUploadForm } from "../common/image-upload-form";
|
||||
import { MarkdownTextArea } from "../common/markdown-textarea";
|
||||
|
@ -24,7 +19,6 @@ interface SiteFormState {
|
|||
}
|
||||
|
||||
export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
||||
private id = `site-form-${randomStr()}`;
|
||||
private emptyState: SiteFormState = {
|
||||
siteForm: {
|
||||
enable_downvotes: true,
|
||||
|
@ -33,6 +27,10 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
name: null,
|
||||
icon: null,
|
||||
banner: null,
|
||||
require_email_verification: null,
|
||||
require_application: null,
|
||||
application_question: null,
|
||||
private_instance: null,
|
||||
auth: authField(),
|
||||
},
|
||||
loading: false,
|
||||
|
@ -43,6 +41,8 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
|
||||
this.state = this.emptyState;
|
||||
this.handleSiteSidebarChange = this.handleSiteSidebarChange.bind(this);
|
||||
this.handleSiteApplicationQuestionChange =
|
||||
this.handleSiteApplicationQuestionChange.bind(this);
|
||||
|
||||
this.handleIconUpload = this.handleIconUpload.bind(this);
|
||||
this.handleIconRemove = this.handleIconRemove.bind(this);
|
||||
|
@ -51,17 +51,21 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
this.handleBannerRemove = this.handleBannerRemove.bind(this);
|
||||
|
||||
if (this.props.site) {
|
||||
let site = this.props.site;
|
||||
this.state.siteForm = {
|
||||
name: this.props.site.name,
|
||||
sidebar: this.props.site.sidebar,
|
||||
description: this.props.site.description,
|
||||
enable_downvotes: this.props.site.enable_downvotes,
|
||||
open_registration: this.props.site.open_registration,
|
||||
enable_nsfw: this.props.site.enable_nsfw,
|
||||
community_creation_admin_only:
|
||||
this.props.site.community_creation_admin_only,
|
||||
icon: this.props.site.icon,
|
||||
banner: this.props.site.banner,
|
||||
name: site.name,
|
||||
sidebar: site.sidebar,
|
||||
description: site.description,
|
||||
enable_downvotes: site.enable_downvotes,
|
||||
open_registration: site.open_registration,
|
||||
enable_nsfw: site.enable_nsfw,
|
||||
community_creation_admin_only: site.community_creation_admin_only,
|
||||
icon: site.icon,
|
||||
banner: site.banner,
|
||||
require_email_verification: site.require_email_verification,
|
||||
require_application: site.require_application,
|
||||
application_question: site.application_question,
|
||||
private_instance: site.private_instance,
|
||||
auth: authField(),
|
||||
};
|
||||
}
|
||||
|
@ -79,6 +83,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
!this.props.site &&
|
||||
(this.state.siteForm.name ||
|
||||
this.state.siteForm.sidebar ||
|
||||
this.state.siteForm.application_question ||
|
||||
this.state.siteForm.description)
|
||||
) {
|
||||
window.onbeforeunload = () => true;
|
||||
|
@ -100,6 +105,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
!this.props.site &&
|
||||
(this.state.siteForm.name ||
|
||||
this.state.siteForm.sidebar ||
|
||||
this.state.siteForm.application_question ||
|
||||
this.state.siteForm.description)
|
||||
}
|
||||
message={i18n.t("block_leaving")}
|
||||
|
@ -162,9 +168,7 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-12 col-form-label" htmlFor={this.id}>
|
||||
{i18n.t("sidebar")}
|
||||
</label>
|
||||
<label class="col-12 col-form-label">{i18n.t("sidebar")}</label>
|
||||
<div class="col-12">
|
||||
<MarkdownTextArea
|
||||
initialContent={this.state.siteForm.sidebar}
|
||||
|
@ -173,6 +177,20 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.siteForm.require_application && (
|
||||
<div class="form-group row">
|
||||
<label class="col-12 col-form-label">
|
||||
{i18n.t("application_questionnaire")}
|
||||
</label>
|
||||
<div class="col-12">
|
||||
<MarkdownTextArea
|
||||
initialContent={this.state.siteForm.application_question}
|
||||
onContentChange={this.handleSiteApplicationQuestionChange}
|
||||
hideNavigationWarnings
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
|
@ -255,6 +273,66 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
id="create-site-require-email-verification"
|
||||
type="checkbox"
|
||||
checked={this.state.siteForm.require_email_verification}
|
||||
onChange={linkEvent(
|
||||
this,
|
||||
this.handleSiteRequireEmailVerification
|
||||
)}
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
htmlFor="create-site-require-email-verification"
|
||||
>
|
||||
{i18n.t("require_email_verification")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
id="create-site-require-application"
|
||||
type="checkbox"
|
||||
checked={this.state.siteForm.require_application}
|
||||
onChange={linkEvent(this, this.handleSiteRequireApplication)}
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
htmlFor="create-site-require-application"
|
||||
>
|
||||
{i18n.t("require_registration_application")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<div class="form-check">
|
||||
<input
|
||||
class="form-check-input"
|
||||
id="create-site-private-instance"
|
||||
type="checkbox"
|
||||
checked={this.state.siteForm.private_instance}
|
||||
onChange={linkEvent(this, this.handleSitePrivateInstance)}
|
||||
/>
|
||||
<label
|
||||
class="form-check-label"
|
||||
htmlFor="create-site-private-instance"
|
||||
>
|
||||
{i18n.t("private_instance")}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
<button
|
||||
|
@ -311,6 +389,11 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
this.setState(this.state);
|
||||
}
|
||||
|
||||
handleSiteApplicationQuestionChange(val: string) {
|
||||
this.state.siteForm.application_question = val;
|
||||
this.setState(this.state);
|
||||
}
|
||||
|
||||
handleSiteDescChange(i: SiteForm, event: any) {
|
||||
i.state.siteForm.description = event.target.value;
|
||||
i.setState(i.state);
|
||||
|
@ -336,6 +419,21 @@ export class SiteForm extends Component<SiteFormProps, SiteFormState> {
|
|||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleSiteRequireApplication(i: SiteForm, event: any) {
|
||||
i.state.siteForm.require_application = event.target.checked;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleSiteRequireEmailVerification(i: SiteForm, event: any) {
|
||||
i.state.siteForm.require_email_verification = event.target.checked;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleSitePrivateInstance(i: SiteForm, event: any) {
|
||||
i.state.siteForm.private_instance = event.target.checked;
|
||||
i.setState(i.state);
|
||||
}
|
||||
|
||||
handleCancel(i: SiteForm) {
|
||||
i.props.onCancel();
|
||||
}
|
||||
|
|
249
src/shared/components/person/registration-applications.tsx
Normal file
249
src/shared/components/person/registration-applications.tsx
Normal file
|
@ -0,0 +1,249 @@
|
|||
import { Component, linkEvent } from "inferno";
|
||||
import {
|
||||
ListRegistrationApplications,
|
||||
ListRegistrationApplicationsResponse,
|
||||
RegistrationApplicationResponse,
|
||||
RegistrationApplicationView,
|
||||
SiteView,
|
||||
UserOperation,
|
||||
} from "lemmy-js-client";
|
||||
import { Subscription } from "rxjs";
|
||||
import { i18n } from "../../i18next";
|
||||
import { InitialFetchRequest } from "../../interfaces";
|
||||
import { UserService, WebSocketService } from "../../services";
|
||||
import {
|
||||
authField,
|
||||
fetchLimit,
|
||||
isBrowser,
|
||||
setIsoData,
|
||||
setupTippy,
|
||||
toast,
|
||||
updateRegistrationApplicationRes,
|
||||
wsClient,
|
||||
wsJsonToRes,
|
||||
wsSubscribe,
|
||||
wsUserOp,
|
||||
} from "../../utils";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Spinner } from "../common/icon";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { RegistrationApplication } from "../common/registration-application";
|
||||
|
||||
enum UnreadOrAll {
|
||||
Unread,
|
||||
All,
|
||||
}
|
||||
|
||||
interface RegistrationApplicationsState {
|
||||
applications: RegistrationApplicationView[];
|
||||
page: number;
|
||||
site_view: SiteView;
|
||||
unreadOrAll: UnreadOrAll;
|
||||
loading: boolean;
|
||||
}
|
||||
|
||||
export class RegistrationApplications extends Component<
|
||||
any,
|
||||
RegistrationApplicationsState
|
||||
> {
|
||||
private isoData = setIsoData(this.context);
|
||||
private subscription: Subscription;
|
||||
private emptyState: RegistrationApplicationsState = {
|
||||
unreadOrAll: UnreadOrAll.Unread,
|
||||
applications: [],
|
||||
page: 1,
|
||||
site_view: this.isoData.site_res.site_view,
|
||||
loading: true,
|
||||
};
|
||||
|
||||
constructor(props: any, context: any) {
|
||||
super(props, context);
|
||||
|
||||
this.state = this.emptyState;
|
||||
this.handlePageChange = this.handlePageChange.bind(this);
|
||||
|
||||
if (!UserService.Instance.myUserInfo && isBrowser()) {
|
||||
toast(i18n.t("not_logged_in"), "danger");
|
||||
this.context.router.history.push(`/login`);
|
||||
}
|
||||
|
||||
this.parseMessage = this.parseMessage.bind(this);
|
||||
this.subscription = wsSubscribe(this.parseMessage);
|
||||
|
||||
// Only fetch the data if coming from another route
|
||||
if (this.isoData.path == this.context.router.route.match.url) {
|
||||
this.state.applications =
|
||||
this.isoData.routeData[0].registration_applications || []; // TODO test
|
||||
this.state.loading = false;
|
||||
} else {
|
||||
this.refetch();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
setupTippy();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
if (isBrowser()) {
|
||||
this.subscription.unsubscribe();
|
||||
}
|
||||
}
|
||||
|
||||
get documentTitle(): string {
|
||||
return `@${
|
||||
UserService.Instance.myUserInfo.local_user_view.person.name
|
||||
} ${i18n.t("registration_applications")} - ${
|
||||
this.state.site_view.site.name
|
||||
}`;
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div class="container">
|
||||
{this.state.loading ? (
|
||||
<h5>
|
||||
<Spinner large />
|
||||
</h5>
|
||||
) : (
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<HtmlTags
|
||||
title={this.documentTitle}
|
||||
path={this.context.router.route.match.url}
|
||||
/>
|
||||
<h5 class="mb-2">{i18n.t("registration_applications")}</h5>
|
||||
{this.selects()}
|
||||
{this.applicationList()}
|
||||
<Paginator
|
||||
page={this.state.page}
|
||||
onChange={this.handlePageChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
unreadOrAllRadios() {
|
||||
return (
|
||||
<div class="btn-group btn-group-toggle flex-wrap mb-2">
|
||||
<label
|
||||
className={`btn btn-outline-secondary pointer
|
||||
${this.state.unreadOrAll == UnreadOrAll.Unread && "active"}
|
||||
`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={UnreadOrAll.Unread}
|
||||
checked={this.state.unreadOrAll == UnreadOrAll.Unread}
|
||||
onChange={linkEvent(this, this.handleUnreadOrAllChange)}
|
||||
/>
|
||||
{i18n.t("unread")}
|
||||
</label>
|
||||
<label
|
||||
className={`btn btn-outline-secondary pointer
|
||||
${this.state.unreadOrAll == UnreadOrAll.All && "active"}
|
||||
`}
|
||||
>
|
||||
<input
|
||||
type="radio"
|
||||
value={UnreadOrAll.All}
|
||||
checked={this.state.unreadOrAll == UnreadOrAll.All}
|
||||
onChange={linkEvent(this, this.handleUnreadOrAllChange)}
|
||||
/>
|
||||
{i18n.t("all")}
|
||||
</label>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
selects() {
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<span class="mr-3">{this.unreadOrAllRadios()}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
applicationList() {
|
||||
return (
|
||||
<div>
|
||||
{this.state.applications.map(ra => (
|
||||
<>
|
||||
<hr />
|
||||
<RegistrationApplication
|
||||
key={ra.registration_application.id}
|
||||
application={ra}
|
||||
/>
|
||||
</>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
handleUnreadOrAllChange(i: RegistrationApplications, event: any) {
|
||||
i.state.unreadOrAll = Number(event.target.value);
|
||||
i.state.page = 1;
|
||||
i.setState(i.state);
|
||||
i.refetch();
|
||||
}
|
||||
|
||||
handlePageChange(page: number) {
|
||||
this.setState({ page });
|
||||
this.refetch();
|
||||
}
|
||||
|
||||
static fetchInitialData(req: InitialFetchRequest): Promise<any>[] {
|
||||
let promises: Promise<any>[] = [];
|
||||
|
||||
let form: ListRegistrationApplications = {
|
||||
unread_only: true,
|
||||
page: 1,
|
||||
limit: fetchLimit,
|
||||
auth: req.auth,
|
||||
};
|
||||
promises.push(req.client.listRegistrationApplications(form));
|
||||
|
||||
return promises;
|
||||
}
|
||||
|
||||
refetch() {
|
||||
let unread_only = this.state.unreadOrAll == UnreadOrAll.Unread;
|
||||
let form: ListRegistrationApplications = {
|
||||
unread_only: unread_only,
|
||||
page: this.state.page,
|
||||
limit: fetchLimit,
|
||||
auth: authField(),
|
||||
};
|
||||
WebSocketService.Instance.send(wsClient.listRegistrationApplications(form));
|
||||
}
|
||||
|
||||
parseMessage(msg: any) {
|
||||
let op = wsUserOp(msg);
|
||||
console.log(msg);
|
||||
if (msg.error) {
|
||||
toast(i18n.t(msg.error), "danger");
|
||||
return;
|
||||
} else if (msg.reconnect) {
|
||||
this.refetch();
|
||||
} else if (op == UserOperation.ListRegistrationApplications) {
|
||||
let data = wsJsonToRes<ListRegistrationApplicationsResponse>(msg).data;
|
||||
this.state.applications = data.registration_applications;
|
||||
this.state.loading = false;
|
||||
window.scrollTo(0, 0);
|
||||
this.setState(this.state);
|
||||
} else if (op == UserOperation.ApproveRegistrationApplication) {
|
||||
let data = wsJsonToRes<RegistrationApplicationResponse>(msg).data;
|
||||
updateRegistrationApplicationRes(
|
||||
data.registration_application,
|
||||
this.state.applications
|
||||
);
|
||||
let uacs = UserService.Instance.unreadApplicationCountSub;
|
||||
// Minor bug, where if the application switches from deny to approve, the count will still go down
|
||||
uacs.next(uacs.getValue() - 1);
|
||||
this.setState(this.state);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,11 +29,11 @@ import {
|
|||
wsSubscribe,
|
||||
wsUserOp,
|
||||
} from "../../utils";
|
||||
import { CommentReport } from "../comment/comment_report";
|
||||
import { CommentReport } from "../comment/comment-report";
|
||||
import { HtmlTags } from "../common/html-tags";
|
||||
import { Spinner } from "../common/icon";
|
||||
import { Paginator } from "../common/paginator";
|
||||
import { PostReport } from "../post/post_report";
|
||||
import { PostReport } from "../post/post-report";
|
||||
|
||||
enum UnreadOrAll {
|
||||
Unread,
|
||||
|
|
|
@ -20,6 +20,9 @@ export class PostReport extends Component<PostReportProps, any> {
|
|||
render() {
|
||||
let r = this.props.report;
|
||||
let post = r.post;
|
||||
let tippyContent = i18n.t(
|
||||
r.post_report.resolved ? "unresolve_report" : "resolve_report"
|
||||
);
|
||||
|
||||
// Set the original post data ( a troll could change it )
|
||||
post.name = r.post_report.original_post_name;
|
||||
|
@ -70,12 +73,8 @@ export class PostReport extends Component<PostReportProps, any> {
|
|||
<button
|
||||
className="btn btn-link btn-animate text-muted py-0"
|
||||
onClick={linkEvent(this, this.handleResolveReport)}
|
||||
data-tippy-content={
|
||||
r.post_report.resolved ? "unresolve_report" : "resolve_report"
|
||||
}
|
||||
aria-label={
|
||||
r.post_report.resolved ? "unresolve_report" : "resolve_report"
|
||||
}
|
||||
data-tippy-content={tippyContent}
|
||||
aria-label={tippyContent}
|
||||
>
|
||||
<Icon
|
||||
icon="check"
|
|
@ -6,12 +6,13 @@ import { AdminSettings } from "./components/home/admin-settings";
|
|||
import { Home } from "./components/home/home";
|
||||
import { Instances } from "./components/home/instances";
|
||||
import { Login } from "./components/home/login";
|
||||
import { PasswordChange } from "./components/home/password_change";
|
||||
import { PasswordChange } from "./components/home/password-change";
|
||||
import { Setup } from "./components/home/setup";
|
||||
import { Signup } from "./components/home/signup";
|
||||
import { Modlog } from "./components/modlog";
|
||||
import { Inbox } from "./components/person/inbox";
|
||||
import { Profile } from "./components/person/profile";
|
||||
import { RegistrationApplications } from "./components/person/registration-applications";
|
||||
import { Reports } from "./components/person/reports";
|
||||
import { Settings } from "./components/person/settings";
|
||||
import { CreatePost } from "./components/post/create-post";
|
||||
|
@ -128,6 +129,11 @@ export const routes: IRoutePropsWithFetch[] = [
|
|||
component: Reports,
|
||||
fetchInitialData: req => Reports.fetchInitialData(req),
|
||||
},
|
||||
{
|
||||
path: `/registration_applications`,
|
||||
component: RegistrationApplications,
|
||||
fetchInitialData: req => RegistrationApplications.fetchInitialData(req),
|
||||
},
|
||||
{
|
||||
path: `/search/q/:q/type/:type/sort/:sort/listing_type/:listing_type/community_id/:community_id/creator_id/:creator_id/page/:page`,
|
||||
component: Search,
|
||||
|
|
|
@ -20,6 +20,8 @@ export class UserService {
|
|||
new BehaviorSubject<number>(0);
|
||||
public unreadReportCountSub: BehaviorSubject<number> =
|
||||
new BehaviorSubject<number>(0);
|
||||
public unreadApplicationCountSub: BehaviorSubject<number> =
|
||||
new BehaviorSubject<number>(0);
|
||||
|
||||
private constructor() {
|
||||
if (this.auth) {
|
||||
|
|
|
@ -18,6 +18,7 @@ import {
|
|||
PostReportView,
|
||||
PostView,
|
||||
PrivateMessageView,
|
||||
RegistrationApplicationView,
|
||||
Search,
|
||||
SearchResponse,
|
||||
SearchType,
|
||||
|
@ -1104,6 +1105,20 @@ export function updateCommentReportRes(
|
|||
}
|
||||
}
|
||||
|
||||
export function updateRegistrationApplicationRes(
|
||||
data: RegistrationApplicationView,
|
||||
applications: RegistrationApplicationView[]
|
||||
) {
|
||||
let found = applications.find(
|
||||
ra => ra.registration_application.id == data.registration_application.id
|
||||
);
|
||||
if (found) {
|
||||
found.registration_application = data.registration_application;
|
||||
found.admin = data.admin;
|
||||
found.creator_local_user = data.creator_local_user;
|
||||
}
|
||||
}
|
||||
|
||||
export function commentsToFlatNodes(comments: CommentView[]): CommentNodeI[] {
|
||||
let nodes: CommentNodeI[] = [];
|
||||
for (let comment of comments) {
|
||||
|
|
|
@ -4997,10 +4997,10 @@ lcid@^1.0.0:
|
|||
dependencies:
|
||||
invert-kv "^1.0.0"
|
||||
|
||||
lemmy-js-client@0.14.0-rc.1:
|
||||
version "0.14.0-rc.1"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.14.0-rc.1.tgz#c714d5f308fa20d5244db3844630f7b197eafa1c"
|
||||
integrity sha512-UF3I+80WTYWwQg2+96HTl0O2Yv0wy6rYFjlLNyzfqMXUZBnsr1O/SdJD1/9yAFPFbGkKgWusdncLoGgzFyn8eg==
|
||||
lemmy-js-client@0.15.0-rc.1:
|
||||
version "0.15.0-rc.1"
|
||||
resolved "https://registry.yarnpkg.com/lemmy-js-client/-/lemmy-js-client-0.15.0-rc.1.tgz#7f5f0f069c76c5377a2a8654ce3a10563f405e14"
|
||||
integrity sha512-Dgq7G2jGLURoswV40DfUNNvxQ/Dg8Jkz9jarwJuOMmBF9MfM/wD1nlaPtvMHlxePXtgDc8Y/Ig84k1OEF8Myqw==
|
||||
|
||||
levn@^0.4.1:
|
||||
version "0.4.1"
|
||||
|
|
Loading…
Reference in a new issue