diff --git a/application/src/main/resources/static/images/logo.png b/application/src/main/resources/static/images/logo.png new file mode 100644 index 000000000..135bb98e5 Binary files /dev/null and b/application/src/main/resources/static/images/logo.png differ diff --git a/application/src/main/resources/static/images/wordmark.svg b/application/src/main/resources/static/images/wordmark.svg new file mode 100644 index 000000000..be7572154 --- /dev/null +++ b/application/src/main/resources/static/images/wordmark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/application/src/main/resources/static/js/main.js b/application/src/main/resources/static/js/main.js new file mode 100644 index 000000000..2db2e606b --- /dev/null +++ b/application/src/main/resources/static/js/main.js @@ -0,0 +1,154 @@ +const Toast = (function () { + let container; + + function getContainer() { + if (container) return container; + + container = document.createElement("div"); + container.style.cssText = ` + position: fixed; + top: 20px; + left: 50%; + transform: translateX(-50%); + z-index: 9999; + `; + + if (document.body) { + document.body.appendChild(container); + } else { + document.addEventListener("DOMContentLoaded", () => { + document.body.appendChild(container); + }); + } + + return container; + } + + class ToastMessage { + constructor(message, type) { + this.message = message; + this.type = type; + this.element = null; + this.create(); + } + + create() { + this.element = document.createElement("div"); + this.element.textContent = this.message; + this.element.style.cssText = ` + background-color: ${this.type === "success" ? "#4CAF50" : "#F44336"}; + color: white; + padding: 12px 24px; + border-radius: 4px; + margin-bottom: 10px; + box-shadow: 0 2px 5px rgba(0,0,0,0.2); + opacity: 0; + transition: opacity 0.3s ease-in-out; + `; + getContainer().appendChild(this.element); + + setTimeout(() => { + this.element.style.opacity = "1"; + }, 10); + + setTimeout(() => { + this.remove(); + }, 3000); + } + + remove() { + this.element.style.opacity = "0"; + setTimeout(() => { + const parent = this.element.parentNode; + if (parent) { + parent.removeChild(this.element); + } + }, 300); + } + } + + function showToast(message, type) { + if (document.readyState === "loading") { + document.addEventListener("DOMContentLoaded", () => { + new ToastMessage(message, type); + }); + } else { + new ToastMessage(message, type); + } + } + + return { + success: function (message) { + showToast(message, "success"); + }, + error: function (message) { + showToast(message, "error"); + }, + }; +})(); + +function sendVerificationCode(button, sendRequest) { + let timer; + const countdown = 60; + + button.addEventListener("click", () => { + button.disabled = true; + sendRequest() + .then(() => { + startCountdown(); + Toast.success("发送成功"); + }) + .catch((e) => { + button.disabled = false; + if (e instanceof Error) { + Toast.error(e.message); + } else { + Toast.error("发送失败,请稍后再试"); + } + }); + }); + + function startCountdown() { + let remainingTime = countdown; + button.disabled = true; + button.classList.add("disabled"); + + timer = setInterval(() => { + if (remainingTime > 0) { + button.textContent = `${remainingTime}s`; + remainingTime--; + } else { + clearInterval(timer); + button.textContent = "Send"; + button.disabled = false; + button.classList.remove("disabled"); + } + }, 1000); + } +} + +document.addEventListener("DOMContentLoaded", () => { + const passwordContainers = document.querySelectorAll(".toggle-password-display-flag"); + + passwordContainers.forEach((container) => { + const passwordInput = container.querySelector('input[type="password"]'); + const toggleButton = container.querySelector(".toggle-password-button"); + const displayIcon = container.querySelector(".password-display-icon"); + const hiddenIcon = container.querySelector(".password-hidden-icon"); + + if (passwordInput && toggleButton && displayIcon && hiddenIcon) { + toggleButton.addEventListener("click", () => { + if (passwordInput.type === "password") { + passwordInput.type = "text"; + displayIcon.style.display = "none"; + hiddenIcon.style.display = "block"; + } else { + passwordInput.type = "password"; + displayIcon.style.display = "block"; + hiddenIcon.style.display = "none"; + } + }); + } + }); +}); + diff --git a/application/src/main/resources/static/styles/main.css b/application/src/main/resources/static/styles/main.css new file mode 100644 index 000000000..9d062b000 --- /dev/null +++ b/application/src/main/resources/static/styles/main.css @@ -0,0 +1,410 @@ +/* Base */ +.gateway-page { + width: 100vw; + height: 100vh; + background-color: #f5f5f5; + overflow: auto; +} + +.gateway-wrapper, +.gateway-wrapper:before, +.gateway-wrapper:after { + box-sizing: border-box; + border-width: 0; + border-style: solid; +} + +.gateway-wrapper *, +.gateway-wrapper *:before, +.gateway-wrapper *:after { + box-sizing: border-box; + border-width: 0; + border-style: solid; +} + +.gateway-wrapper { + --color-primary: #4ccba0; + --color-secondary: #0e1731; + --color-link: #1f75cb; + --color-text: #374151; + --color-border: #d1d5db; + --rounded-sm: 0.125em; + --rounded-base: 0.25em; + --rounded-lg: 0.5em; + --spacing-xl: 1.25em; + --spacing-lg: 1em; + --spacing-md: 0.875em; + --spacing-sm: 0.5em; + --text-md: 0.875em; +} + +.gateway-wrapper { + margin: 0 auto; + max-width: 28em; + padding: 5% 1em; + font-family: + ui-sans-serif, + system-ui, + -apple-system, + BlinkMacSystemFont, + Segoe UI, + Roboto, + Helvetica Neue, + Arial, + Noto Sans, + sans-serif, + "Apple Color Emoji", + "Segoe UI Emoji", + Segoe UI Symbol, + "Noto Color Emoji"; +} + +/* Form */ +.halo-form-wrapper { + border-radius: var(--rounded-lg); + background: #fff; + padding: 1.5em; +} + +.form-title { + all: unset; + margin-bottom: 1em; + display: block; + font-weight: 500; + font-size: 1.75em; +} + +.halo-form .form-item { + display: flex; + flex-direction: column; + margin-bottom: 1.3em; + width: 100%; +} + +.halo-form .form-item:last-child { + margin-bottom: 0; +} + +.halo-form .form-item-group { + gap: var(--spacing-lg); + display: flex; + align-items: center; + margin-bottom: 1.3em; +} + +.halo-form .form-item-group .form-item { + margin-bottom: 0; +} + +.halo-form .form-input { + border-radius: var(--rounded-base); + border: 1px solid var(--color-border); + height: 2.5em; + background: #fff; + padding: 0 0.75rem; +} + +.halo-form .form-input:focus-within { + border-color: var(--color-primary); + outline: 2px solid transparent; + outline-offset: "2px"; +} + +.halo-form .form-item input { + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + display: block; + font-size: 1em; + box-shadow: none; + width: 100%; + height: 100%; + background: transparent; +} + +.halo-form .form-item input:focus { + outline: none; +} + +.halo-form .form-input-stack { + display: flex; + align-items: center; + gap: 0.5em; +} + +.halo-form .form-input-stack-icon { + display: inline-flex; + align-items: center; + color: var(--color-text); + cursor: pointer; +} + +.halo-form .form-input-stack-select { + all: unset; + color: var(--color-text); + font-size: var(--text-md); + padding-right: 1.85em; + display: inline-flex; + align-items: center; + background: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='1em' height='1em' viewBox='0 0 24 24'%3E%3Cpath fill='%23000' d='m12 13.171l4.95-4.95l1.414 1.415L12 16L5.636 9.636L7.05 8.222z'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: right 0.3em center; +} + +.halo-form .form-input-stack-text { + color: var(--color-text); + font-size: var(--text-md); +} + +.halo-form .form-item label { + color: var(--color-text); + margin-bottom: 0.5em; +} + +.halo-form .form-item .form-label-group { + margin-bottom: 0.5em; + display: flex; + justify-content: space-between; + align-items: center; +} + +.halo-form .form-item .form-label-group label { + margin-bottom: 0; +} + +.halo-form .form-item-extra-link { + color: var(--color-link); + font-size: var(--text-md); + text-decoration: none; +} + +.halo-form .form-item-compact { + gap: var(--spacing-sm); + margin-bottom: 1.5em; + display: flex; + align-items: center; +} + +.halo-form .form-item-compact label { + color: var(--color-text); + font-size: var(--text-md); +} + +.halo-form button[type="submit"] { + background: var(--color-secondary); + border-radius: var(--rounded-base); + height: 2.5em; + color: #fff; + border: none; + cursor: pointer; +} + +.halo-form button[type="submit"]:hover { + opacity: 0.8; +} + +.halo-form button[type="submit"]:active { + opacity: 0.9; +} + +.halo-form button[disabled] { + cursor: not-allowed !important; +} + +.halo-form input[type="checkbox"] { + border: 1px solid var(--color-border); + border-radius: var(--rounded-sm); + appearance: none; + -webkit-appearance: none; + -moz-appearance: none; + padding: 0; + print-color-adjust: exact; + display: inline-block; + vertical-align: middle; + background-origin: border-box; + user-select: none; + flex-shrink: 0; + height: 1em; + width: 1em; + color: #2563eb; + background-color: #fff; +} + +.halo-form input[type="checkbox"]:focus { + outline: 2px solid transparent; + outline-offset: 2px; + box-shadow: + rgb(255, 255, 255) 0px 0px 0px 2px, + rgb(37, 99, 235) 0px 0px 0px 4px, + rgba(0, 0, 0, 0) 0px 0px 0px 0px; +} + +.halo-form input[type="checkbox"]:checked { + border-color: transparent; + background-color: currentColor; + background-size: 100% 100%; + background-position: center; + background-repeat: no-repeat; + background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); +} + +.halo-form .form-input-group { + gap: var(--spacing-sm); + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + align-items: center; +} + +.halo-form .form-input { + grid-column: span 2 / span 2; +} + +.halo-form .form-input-group button { + border-radius: var(--rounded-base); + border: 1px solid var(--color-border); + color: var(--color-text); + font-size: var(--text-md); + grid-column: span 1 / span 1; + height: 100%; + cursor: pointer; + background: #fff; +} + +.halo-form .form-input-group button:hover { + color: #333; + background: #f3f4f6; +} + +.halo-form .form-input-group button:active { + background: #f9fafb; +} + +.auth-provider-items { + all: unset; + gap: var(--spacing-md); + margin: 0; + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.auth-provider-items li { + all: unset; + border-radius: var(--rounded-lg); + overflow: hidden; + border: 1px solid #e5e7eb; + transition-property: all; + transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + transition-duration: 0.15s; +} + +.auth-provider-items li a { + gap: var(--spacing-sm); + padding: 0.7em 1em; + display: flex; + align-items: center; + color: #1f2937; + text-decoration: none; + font-size: 0.8em; +} + +.auth-provider-items li img { + width: 1.5em; + height: 1.5em; +} + +.auth-provider-items li:hover { + border-color: var(--color-primary); + background: #f3f4f6; +} + +.auth-provider-items li:hover a { + color: #111827; +} + +.auth-provider-items li:focus-within { + border-color: var(--color-primary); +} + +.divider-wrapper { + color: var(--color-text); + font-size: var(--text-md); + gap: var(--spacing-lg); + display: flex; + align-items: center; + margin: 1.5em 0; +} + +.divider-wrapper hr { + flex-grow: 1; + overflow: hidden; + border: 0; + border-top: 1px solid #f3f4f6; +} + +.alert { + border: 1px solid #e5e7eb; + border-radius: var(--rounded-base); + margin-bottom: var(--spacing-xl); + padding: var(--spacing-md) var(--spacing-xl); + font-size: var(--text-md); + overflow: hidden; + position: relative; + color: var(--color-text); +} + +.alert::before { + content: ""; + position: absolute; + height: 100%; + left: 0; + background: #d1d5db; + width: 0.25em; + top: 0; +} + +.alert-warning { + border-color: #fde047; +} + +.alert-warning::before { + background: #ea580c; +} + +.alert-error { + border-color: #fca5a5; +} + +.alert-error::before { + background: #dc2626; +} + +.alert-success { + border-color: #86efac; +} + +.alert-success::before { + background: #16a34a; +} + +.alert-info { + border-color: #7dd3fc; +} + +.alert-info::before { + background: #0284c7; +} + +@media (forced-colors: active) { + .halo-form input[type="checkbox"]:checked { + -webkit-appearance: auto; + -moz-appearance: auto; + appearance: auto; + } +} + +@media only screen and (max-width: 768px) { + .halo-form .form-item-group { + flex-direction: column; + } +} diff --git a/application/src/main/resources/templates/challenges/two-factor/totp.html b/application/src/main/resources/templates/challenges/two-factor/totp.html new file mode 100644 index 000000000..96baa8d8c --- /dev/null +++ b/application/src/main/resources/templates/challenges/two-factor/totp.html @@ -0,0 +1,43 @@ + + + +
+
+
+

+
+ +
+ +
+ +
+
+
+ +
+
+
+
+
+ diff --git a/application/src/main/resources/templates/challenges/two-factor/totp.properties b/application/src/main/resources/templates/challenges/two-factor/totp.properties new file mode 100644 index 000000000..46f96ed97 --- /dev/null +++ b/application/src/main/resources/templates/challenges/two-factor/totp.properties @@ -0,0 +1,4 @@ +title=两步验证 +messages.invalidError=错误的验证码 +form.code.label=验证码 +form.submit=验证 diff --git a/application/src/main/resources/templates/challenges/two-factor/totp_en.properties b/application/src/main/resources/templates/challenges/two-factor/totp_en.properties new file mode 100644 index 000000000..bffc96863 --- /dev/null +++ b/application/src/main/resources/templates/challenges/two-factor/totp_en.properties @@ -0,0 +1,4 @@ +title=Two-Factor Authentication +messages.invalidError=Invalid TOTP code +form.code.label=TOTP Code +form.submit=Verify diff --git a/application/src/main/resources/templates/gateway_modules/common_fragments.html b/application/src/main/resources/templates/gateway_modules/common_fragments.html new file mode 100644 index 000000000..75d718ca3 --- /dev/null +++ b/application/src/main/resources/templates/gateway_modules/common_fragments.html @@ -0,0 +1,110 @@ + + + + + + + + + +
+ +
+ + + +
+
+ +
+ + +
+ +
+ +
+
+ +
+
+ +
+
diff --git a/application/src/main/resources/templates/gateway_modules/common_fragments.properties b/application/src/main/resources/templates/gateway_modules/common_fragments.properties new file mode 100644 index 000000000..6617dd0d7 --- /dev/null +++ b/application/src/main/resources/templates/gateway_modules/common_fragments.properties @@ -0,0 +1 @@ +socialLogin.label=社交登录 \ No newline at end of file diff --git a/application/src/main/resources/templates/gateway_modules/common_fragments_en.properties b/application/src/main/resources/templates/gateway_modules/common_fragments_en.properties new file mode 100644 index 000000000..a10abbb87 --- /dev/null +++ b/application/src/main/resources/templates/gateway_modules/common_fragments_en.properties @@ -0,0 +1 @@ +socialLogin.label=Social Login \ No newline at end of file diff --git a/application/src/main/resources/templates/gateway_modules/input_fragments.html b/application/src/main/resources/templates/gateway_modules/input_fragments.html new file mode 100644 index 000000000..ffffb5cd6 --- /dev/null +++ b/application/src/main/resources/templates/gateway_modules/input_fragments.html @@ -0,0 +1,31 @@ +
+
+ + +
+ + + + + +
+
+
diff --git a/application/src/main/resources/templates/gateway_modules/layout.html b/application/src/main/resources/templates/gateway_modules/layout.html new file mode 100644 index 000000000..126838d12 --- /dev/null +++ b/application/src/main/resources/templates/gateway_modules/layout.html @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/application/src/main/resources/templates/gateway_modules/login_fragments.html b/application/src/main/resources/templates/gateway_modules/login_fragments.html new file mode 100644 index 000000000..87e8fe64c --- /dev/null +++ b/application/src/main/resources/templates/gateway_modules/login_fragments.html @@ -0,0 +1,95 @@ + +
+ + + + +
+ +
+ + +
+ +
+ +
+
+ +
+ +
+
+ +
+
+ +
+
+ +
+ + + +
diff --git a/application/src/main/resources/templates/gateway_modules/login_fragments.properties b/application/src/main/resources/templates/gateway_modules/login_fragments.properties new file mode 100644 index 000000000..5752dae82 --- /dev/null +++ b/application/src/main/resources/templates/gateway_modules/login_fragments.properties @@ -0,0 +1,13 @@ +messages.loginError=无效的凭证。 +messages.logoutSuccess=登出成功。 +messages.signupSuccess=恭喜!注册成功,请立即登录。 + +error.invalid-credential=无效的凭证。 +error.rate-limit-exceeded=请求过于频繁,请稍后再试。 + +form.rememberMe.label=保持登录会话 +form.submit=登录 +otherLogin.label=其他登录方式 +signup.description=没有账号? +signup.link=立即注册 +returnToSite=返回网站 \ No newline at end of file diff --git a/application/src/main/resources/templates/gateway_modules/login_fragments_en.properties b/application/src/main/resources/templates/gateway_modules/login_fragments_en.properties new file mode 100644 index 000000000..d3b5b33f4 --- /dev/null +++ b/application/src/main/resources/templates/gateway_modules/login_fragments_en.properties @@ -0,0 +1,13 @@ +messages.loginError=Invalid credentials. +messages.logoutSuccess=Logout successfully. +messages.signupSuccess=Congratulations! Sign up successfully, please sign in now. + +error.invalid-credential=Invalid credentials. +error.rate-limit-exceeded=Too many requests, please try again later. + +form.rememberMe.label=Remember me +form.submit=Login +otherLogin.label=Other Login +signup.description=Don't have an account? +signup.link=Sign up +returnToSite=Return to site \ No newline at end of file diff --git a/application/src/main/resources/templates/login.html b/application/src/main/resources/templates/login.html new file mode 100644 index 000000000..47d808459 --- /dev/null +++ b/application/src/main/resources/templates/login.html @@ -0,0 +1,20 @@ + + + +
+
+ +
+
+
+
+
+ +
+
+
+
+ \ No newline at end of file diff --git a/application/src/main/resources/templates/login.properties b/application/src/main/resources/templates/login.properties new file mode 100644 index 000000000..26367c07c --- /dev/null +++ b/application/src/main/resources/templates/login.properties @@ -0,0 +1 @@ +title=登录 \ No newline at end of file diff --git a/application/src/main/resources/templates/login_email.html b/application/src/main/resources/templates/login_email.html new file mode 100644 index 000000000..cbdda6d64 --- /dev/null +++ b/application/src/main/resources/templates/login_email.html @@ -0,0 +1,37 @@ +
+
+ + +
+ +
+
+
+ +
+
+ +
+ +
+
+
\ No newline at end of file diff --git a/application/src/main/resources/templates/login_en.properties b/application/src/main/resources/templates/login_en.properties new file mode 100644 index 000000000..eb0443eed --- /dev/null +++ b/application/src/main/resources/templates/login_en.properties @@ -0,0 +1 @@ +title=Login diff --git a/application/src/main/resources/templates/login_local.html b/application/src/main/resources/templates/login_local.html new file mode 100644 index 000000000..3ae24d400 --- /dev/null +++ b/application/src/main/resources/templates/login_local.html @@ -0,0 +1,56 @@ +
+ + + +
+ + +
+ +
+
+
+
+ + + +
+ + +
+
diff --git a/application/src/main/resources/templates/login_local.properties b/application/src/main/resources/templates/login_local.properties new file mode 100644 index 000000000..8163bfd74 --- /dev/null +++ b/application/src/main/resources/templates/login_local.properties @@ -0,0 +1,3 @@ +form.username.label=用户名 +form.password.label=密码 +form.password.forgot=忘记密码? diff --git a/application/src/main/resources/templates/login_local_en.properties b/application/src/main/resources/templates/login_local_en.properties new file mode 100644 index 000000000..d0fbb0901 --- /dev/null +++ b/application/src/main/resources/templates/login_local_en.properties @@ -0,0 +1,3 @@ +form.username.label=Username +form.password.label=Password +form.password.forgot=Forgot your password? diff --git a/application/src/main/resources/templates/logout.html b/application/src/main/resources/templates/logout.html new file mode 100644 index 000000000..cafcd1b7f --- /dev/null +++ b/application/src/main/resources/templates/logout.html @@ -0,0 +1,18 @@ + + + +
+
+

+
+
+ +
+
+
+
+
+ \ No newline at end of file diff --git a/application/src/main/resources/templates/logout.properties b/application/src/main/resources/templates/logout.properties new file mode 100644 index 000000000..4ff553242 --- /dev/null +++ b/application/src/main/resources/templates/logout.properties @@ -0,0 +1,3 @@ +title=退出登录 +form.title=确定要退出登录吗? +form.submit=退出登录 diff --git a/application/src/main/resources/templates/logout_en.properties b/application/src/main/resources/templates/logout_en.properties new file mode 100644 index 000000000..3ad1587f0 --- /dev/null +++ b/application/src/main/resources/templates/logout_en.properties @@ -0,0 +1,3 @@ +title=Logout +form.title=Are you sure want to log out? +form.submit=Logout \ No newline at end of file diff --git a/application/src/main/resources/templates/password-reset-link.html b/application/src/main/resources/templates/password-reset-link.html new file mode 100644 index 000000000..2a40d4d81 --- /dev/null +++ b/application/src/main/resources/templates/password-reset-link.html @@ -0,0 +1,39 @@ + + + +
+
+
+

+

+
+
+ + +
+
+ + +
+
+

+
+
+ +
+
+
+
+
+ \ No newline at end of file diff --git a/application/src/main/resources/templates/password-reset-link.properties b/application/src/main/resources/templates/password-reset-link.properties new file mode 100644 index 000000000..a401763b3 --- /dev/null +++ b/application/src/main/resources/templates/password-reset-link.properties @@ -0,0 +1,5 @@ +title=为 {0} 修改密码 +form.password.label=密码 +form.confirmPassword.label=确认密码 +form.password.tips=密码必须至少包含 8 个字符,并且至少包含一个大写字母、一个小写字母、一个数字和一个特殊字符。 +form.submit=修改密码 diff --git a/application/src/main/resources/templates/password-reset-link_en.properties b/application/src/main/resources/templates/password-reset-link_en.properties new file mode 100644 index 000000000..3075f3b83 --- /dev/null +++ b/application/src/main/resources/templates/password-reset-link_en.properties @@ -0,0 +1,5 @@ +title=Change password for @{0} +form.password.label=Password +form.confirmPassword.label=Confirm Password +form.password.tips=Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number, and one special character. +form.submit=Change password diff --git a/application/src/main/resources/templates/password-reset.html b/application/src/main/resources/templates/password-reset.html new file mode 100644 index 000000000..3e198e142 --- /dev/null +++ b/application/src/main/resources/templates/password-reset.html @@ -0,0 +1,36 @@ + + + +
+
+
+

+
+ +
+
+
+
+
+
+ +
+
+
+
+ +
+ +
+
+
+ +
+
+
+
+
+ diff --git a/application/src/main/resources/templates/password-reset.properties b/application/src/main/resources/templates/password-reset.properties new file mode 100644 index 000000000..f008656fc --- /dev/null +++ b/application/src/main/resources/templates/password-reset.properties @@ -0,0 +1,6 @@ +title=重置密码 +form.email.label=电子邮箱 +form.submit=提交 +sent.form.submit=返回到登录页面 +sent.form.message=检查您的电子邮件中是否有重置密码的链接。如果几分钟内没有出现,请检查您的垃圾邮件文件夹。 +sent.title=已发送重置密码的邮件 \ No newline at end of file diff --git a/application/src/main/resources/templates/password-reset_en.properties b/application/src/main/resources/templates/password-reset_en.properties new file mode 100644 index 000000000..36555ac69 --- /dev/null +++ b/application/src/main/resources/templates/password-reset_en.properties @@ -0,0 +1,6 @@ +title=Reset password +form.email.label=Email +form.submit=Submit +sent.form.submit=Return to login +sent.form.message=Check your email for a link to reset your password. If it doesn’t appear within a few minutes, check your spam folder. +sent.title=Password reset email has been sent \ No newline at end of file diff --git a/application/src/main/resources/templates/signup.html b/application/src/main/resources/templates/signup.html new file mode 100644 index 000000000..01186b5ae --- /dev/null +++ b/application/src/main/resources/templates/signup.html @@ -0,0 +1,202 @@ + + + + + + + + + + \ No newline at end of file diff --git a/application/src/main/resources/templates/signup.properties b/application/src/main/resources/templates/signup.properties new file mode 100644 index 000000000..ce6a56c6d --- /dev/null +++ b/application/src/main/resources/templates/signup.properties @@ -0,0 +1,13 @@ +title=注册 +form.username.label=用户名 +form.displayName.label=名称 +form.email.label=电子邮箱 +form.emailCode.label=邮箱验证码 +form.emailCode.sendButton=发送 +form.password.label=密码 +form.confirmPassword.label=确认密码 +form.submit=注册 + +error.invalid-email-code=无效的邮箱验证码 +error.duplicate-name=用户名已经被注册 +error.rate-limit-exceeded=请求过于频繁,请稍后再试 diff --git a/application/src/main/resources/templates/signup_en.properties b/application/src/main/resources/templates/signup_en.properties new file mode 100644 index 000000000..bdbb22206 --- /dev/null +++ b/application/src/main/resources/templates/signup_en.properties @@ -0,0 +1,9 @@ +title=Sign up +form.username.label=Username +form.displayName.label=Display name +form.email.label=Email +form.emailCode.label=Email Code +form.emailCode.sendButton=Send +form.password.label=Password +form.confirmPassword.label=Confirm password +form.submit=Sign up \ No newline at end of file diff --git a/ui/console-src/layouts/BasicLayout.vue b/ui/console-src/layouts/BasicLayout.vue index b1fc71be7..1e6ffcbf6 100644 --- a/ui/console-src/layouts/BasicLayout.vue +++ b/ui/console-src/layouts/BasicLayout.vue @@ -57,7 +57,7 @@ const handleLogout = () => { document.cookie = "XSRF-TOKEN=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"; - router.replace({ name: "Login" }); + window.location.href = "/login"; } catch (error) { console.error("Failed to logout", error); } diff --git a/ui/console-src/main.ts b/ui/console-src/main.ts index e05d17c4d..45ee18818 100644 --- a/ui/console-src/main.ts +++ b/ui/console-src/main.ts @@ -14,6 +14,7 @@ import { setupVueQuery } from "@/setup/setupVueQuery"; import { useGlobalInfoStore } from "@/stores/global-info"; import { useRoleStore } from "@/stores/role"; import { useUserStore } from "@/stores/user"; +import { getCookie } from "@/utils/cookie"; import { hasPermission } from "@/utils/permission"; import { setupCoreModules, @@ -78,8 +79,7 @@ async function initApp() { await userStore.fetchCurrentUser(); // set locale - i18n.global.locale.value = - localStorage.getItem("locale") || getBrowserLanguage(); + i18n.global.locale.value = getCookie("language") || getBrowserLanguage(); const globalInfoStore = useGlobalInfoStore(); await globalInfoStore.fetchGlobalInfo(); diff --git a/ui/src/utils/cookie.ts b/ui/src/utils/cookie.ts new file mode 100644 index 000000000..5b376aa44 --- /dev/null +++ b/ui/src/utils/cookie.ts @@ -0,0 +1,4 @@ +export function getCookie(name: string) { + const match = document.cookie.match(new RegExp("(^| )" + name + "=([^;]+)")); + return match ? match[2] : null; +} diff --git a/ui/uc-src/layouts/BasicLayout.vue b/ui/uc-src/layouts/BasicLayout.vue index 5db0136d3..be079413a 100644 --- a/ui/uc-src/layouts/BasicLayout.vue +++ b/ui/uc-src/layouts/BasicLayout.vue @@ -53,7 +53,7 @@ const handleLogout = () => { document.cookie = "XSRF-TOKEN=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"; - window.location.href = "/console/login"; + window.location.href = "/login"; } catch (error) { console.error("Failed to logout", error); } diff --git a/ui/uc-src/main.ts b/ui/uc-src/main.ts index 071543843..d79ac8536 100644 --- a/ui/uc-src/main.ts +++ b/ui/uc-src/main.ts @@ -6,6 +6,7 @@ import { setupVueQuery } from "@/setup/setupVueQuery"; import { useGlobalInfoStore } from "@/stores/global-info"; import { useRoleStore } from "@/stores/role"; import { useUserStore } from "@/stores/user"; +import { getCookie } from "@/utils/cookie"; import { hasPermission } from "@/utils/permission"; import { consoleApiClient } from "@halo-dev/api-client"; import router from "@uc/router"; @@ -66,8 +67,7 @@ async function initApp() { await userStore.fetchCurrentUser(); // set locale - i18n.global.locale.value = - localStorage.getItem("locale") || getBrowserLanguage(); + i18n.global.locale.value = getCookie("language") || getBrowserLanguage(); const globalInfoStore = useGlobalInfoStore(); await globalInfoStore.fetchGlobalInfo(); diff --git a/ui/uc-src/router/guards/auth-check.ts b/ui/uc-src/router/guards/auth-check.ts index 322a7be26..9ff1c02d5 100644 --- a/ui/uc-src/router/guards/auth-check.ts +++ b/ui/uc-src/router/guards/auth-check.ts @@ -13,7 +13,7 @@ export function setupAuthCheckGuard(router: Router) { const userStore = useUserStore(); if (userStore.isAnonymous) { - window.location.href = `/console/login?redirect_uri=${encodeURIComponent( + window.location.href = `/login?redirect_uri=${encodeURIComponent( window.location.href )}`; return;