feat(schemas): add new API schema definitions for invitation, deployment, and issue management
- Add AcceptInvitationParams and AcceptInvitationRequest schemas - Add AddDeployKeyParams, AddDomainParams, AddGpgKeyParams, AddMemberParams, AddReplyParams, AddRepoMemberParams, and AddSshKeyParams schemas - Add ApiEmptyResponse and ApiErrorResponse schemas - Add ApiResponse schemas for BranchMergeCheck, BranchProtectionRule, CaptchaResponse, ContextMe, CreateInvitationResponse, EmailResponse, Enable2FAResponse, Get2FAStatusResponse - Add ApiResponse schemas for Issue, IssueAssignee, IssueComment, IssueEvent, IssueLabel, IssueLabelRelation, IssueMilestone, IssuePrRelation, IssueReaction, IssueRepoRelation, IssueSubscriber, and IssueTemplate - Add ApiResponse schemas for Option_BranchProtectionRule, PrAssignee, PrCheckRun, PrCommit, PrEvent, PrFile, and PrLabel
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
import { createSignal, Show } from 'solid-js';
|
||||
import { useNavigate, useLocation } from '@solidjs/router';
|
||||
import AuthLayout from '@/app/auth/components/AuthLayout';
|
||||
import { SubmitButton, ErrorMessage } from '@/app/auth/components/FormElements';
|
||||
import { AuthService } from '@/client';
|
||||
import { ApiError } from '@/client/core/ApiError';
|
||||
|
||||
function ArrowLeft(props: { size: number }) {
|
||||
return (
|
||||
<svg width={props.size} height={props.size} viewBox="0 0 256 256" fill="currentColor">
|
||||
<path d="M224,128a8,8,0,0,1-8,8H59.31l58.35,58.34a8,8,0,0,1-11.32,11.32l-72-72a8,8,0,0,1,0-11.32l72-72a8,8,0,0,1,11.32,11.32L59.31,120H216A8,8,0,0,1,224,128Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function Envelope(props: { size: number; color: string }) {
|
||||
return (
|
||||
<svg width={props.size} height={props.size} viewBox="0 0 256 256" fill={props.color}>
|
||||
<path d="M224,48H32a8,8,0,0,0-8,8V192a16,16,0,0,0,16,16H216a16,16,0,0,0,16-16V56A8,8,0,0,0,224,48Zm-8,27.31V192H40V75.19l84.42,73.87a8,8,0,0,0,10.52.05ZM40,64l88,77,88-77v0H40Z" />
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ForgotPassword() {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
|
||||
const [email, setEmail] = createSignal('');
|
||||
const [error, setError] = createSignal('');
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
const isSent = () => location.hash === '#sent';
|
||||
|
||||
function setStepSent() { navigate('/forgot-password#sent', { replace: true }); }
|
||||
function setStepForm() { navigate('/forgot-password', { replace: true }); }
|
||||
|
||||
async function handleSubmit(e: SubmitEvent) {
|
||||
e.preventDefault();
|
||||
if (loading()) return;
|
||||
setError('');
|
||||
if (!email().trim()) { setError('Please enter your email'); return; }
|
||||
const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRe.test(email().trim())) { setError('Invalid email format'); return; }
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
await AuthService.authRequestPasswordReset({ requestBody: { email: email().trim() } });
|
||||
setStepSent();
|
||||
} catch (err) {
|
||||
if (err instanceof ApiError && err.status === 429) {
|
||||
setError('Too many requests, please try again later');
|
||||
} else {
|
||||
setError('Unable to request password reset, please try again');
|
||||
}
|
||||
} finally { setLoading(false); }
|
||||
}
|
||||
|
||||
return (
|
||||
<Show
|
||||
when={!isSent()}
|
||||
fallback={
|
||||
<AuthLayout eyebrow="Reset Password">
|
||||
<div style={{ display: 'flex', 'flex-direction': 'column', 'align-items': 'center', gap: '16px', 'text-align': 'center' }}>
|
||||
<div class="status-icon status-icon--accent">
|
||||
<Envelope size={32} color="var(--accent)" />
|
||||
</div>
|
||||
<h1 class="form-header">Email Sent</h1>
|
||||
<p style={{ 'font-family': 'var(--font-body)', 'font-size': '15px', color: 'var(--fg)', 'line-height': '1.5', margin: '0' }}>
|
||||
If {email() || 'the email address'} is registered, you will receive a password reset email.
|
||||
</p>
|
||||
<p style={{ 'font-family': 'var(--font-body)', 'font-size': '13px', color: 'var(--meta)', 'line-height': '1.45', margin: '0' }}>
|
||||
Please check your inbox and spam folder. The link is valid for 1 hour.
|
||||
</p>
|
||||
<div style={{ display: 'flex', 'flex-direction': 'column', gap: '8px', width: '100%', 'margin-top': '4px' }}>
|
||||
<a href="/login" style={{ 'text-decoration': 'none' }}>
|
||||
<button type="button" class="btn-primary btn-block">Back to Sign In</button>
|
||||
</a>
|
||||
<button type="button" class="btn-secondary btn-block"
|
||||
onClick={() => { setStepForm(); setError(''); }}>Change email</button>
|
||||
</div>
|
||||
</div>
|
||||
</AuthLayout>
|
||||
}
|
||||
>
|
||||
<AuthLayout eyebrow="Reset Password">
|
||||
<form onSubmit={handleSubmit} class="form-stack" noValidate>
|
||||
<h1 class="form-header">Forgot Password</h1>
|
||||
<p class="form-desc">Enter your registered email to receive a password reset link.</p>
|
||||
|
||||
{error() && <ErrorMessage>{error()}</ErrorMessage>}
|
||||
|
||||
<div class="field">
|
||||
<label for="email-input">Email</label>
|
||||
<input id="email-input" type="email" autocomplete="email" placeholder="your@email.com"
|
||||
value={email()} onInput={(e) => setEmail(e.currentTarget.value)} autofocus />
|
||||
</div>
|
||||
|
||||
<SubmitButton loading={loading()} loadingText="Sending…">Send Reset Link</SubmitButton>
|
||||
|
||||
<div style={{ display: 'flex', 'justify-content': 'center' }}>
|
||||
<a href="/login" class="back-link"><ArrowLeft size={14} /> Back to Sign In</a>
|
||||
</div>
|
||||
</form>
|
||||
</AuthLayout>
|
||||
</Show>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user