Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions apps/space/app/error.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,36 @@
*/

// ui
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";

const handleRetry = () => {
window.location.reload();
};

function ErrorPage() {
const handleRetry = () => {
window.location.reload();
};
const { t } = useTranslation();

return (
<div className="grid h-screen place-items-center bg-surface-1 p-4">
<div className="space-y-8 text-center">
<div className="space-y-2">
<h3 className="text-16 font-semibold">Yikes! That doesn{"'"}t look good.</h3>
<h3 className="text-16 font-semibold">{t("localized_ui.space_public.error_title")}</h3>
<p className="mx-auto text-13 text-secondary md:w-1/2">
That crashed Plane, pun intended. No worries, though. Our engineers have been notified. If you have more
details, please write to{" "}
{t("localized_ui.space_public.error_description_prefix")}{" "}
<a href="mailto:support@plane.so" className="text-accent-primary">
support@plane.so
</a>{" "}
or on our{" "}
{t("localized_ui.space_public.error_description_middle")}{" "}
<a href="https://forum.plane.so" target="_blank" className="text-accent-primary" rel="noopener noreferrer">
Forum
{t("localized_ui.space_public.forum")}
</a>
.
</p>
</div>
<div className="flex items-center justify-center gap-2">
<Button variant="primary" size="lg" onClick={handleRetry}>
Refresh
{t("localized_ui.space_public.refresh")}
</Button>
{/* <Button variant="secondary" size="lg" onClick={() => {}}>
Sign out
Expand Down
17 changes: 12 additions & 5 deletions apps/space/app/not-found.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,29 @@
* See the LICENSE file for details.
*/

import { useTranslation } from "@plane/i18n";
// assets
import SomethingWentWrongImage from "@/app/assets/something-went-wrong.svg?url";

function NotFound() {
const { t } = useTranslation();
return (
<div className="grid h-screen w-screen place-items-center bg-surface-1">
<div className="text-center">
<div className="mx-auto grid size-32 place-items-center rounded-full md:size-52">
<div className="grid size-16 place-items-center md:size-32">
<img src={SomethingWentWrongImage} alt="Something went wrong" width={128} height={128} />
<img
src={SomethingWentWrongImage}
alt={t("localized_ui.space_public.something_went_wrong")}
width={128}
height={128}
/>
</div>
</div>
<h1 className="mt-8 text-18 font-semibold md:mt-12 md:text-24">That didn{"'"}t work</h1>
<p className="mt-2 text-13 md:mt-4 md:text-14">
Check the URL you are entering in the browser{"'"}s address bar and try again.
</p>
<h1 className="mt-8 text-18 font-semibold md:mt-12 md:text-24">
{t("localized_ui.space_public.not_found_title")}
</h1>
<p className="mt-2 text-13 md:mt-4 md:text-14">{t("localized_ui.space_public.not_found_hint")}</p>
Comment on lines +7 to +29
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify that all three translation keys exist in locale files

# Expected locales per PR summary
LOCALES="cs de en es fr id it ja ko pl pt-BR ro ru sk tr-TR ua vi-VN zh-CN zh-TW"

echo "Checking for translation keys in locale files..."
echo "Keys to verify: something_went_wrong, not_found_title, not_found_hint"
echo ""

# Search for the keys in locale files
rg -n --type=ts -g 'packages/i18n/src/locales/*/translations.ts' \
  -A 2 'space_public.*:\s*\{' | head -100

echo ""
echo "Verifying each key appears in locale files:"
for key in "something_went_wrong" "not_found_title" "not_found_hint"; do
  echo ""
  echo "=== Checking $key ==="
  count=$(rg -c --type=ts -g 'packages/i18n/src/locales/*/translations.ts' "\"$key\":" | wc -l)
  echo "Found in $count locale files"
  if [ "$count" -lt 19 ]; then
    echo "⚠️  Expected in 19 locale files but found in $count"
  fi
done

Repository: makeplane/plane

Length of output: 6187


Translation keys something_went_wrong, not_found_title, and not_found_hint are not defined in any locale files.

The code references these three translation keys under localized_ui.space_public.*, but they do not exist in any of the 19 locale files (packages/i18n/src/locales/{cs,de,en,es,fr,id,it,ja,ko,pl,pt-BR,ro,ru,sk,tr-TR,ua,vi-VN,zh-CN,zh-TW}/translations.ts). This will cause missing translations at runtime. Add these keys to all 19 locale files before merging.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/space/app/not-found.tsx` around lines 7 - 29, The NotFound component
uses translation keys localized_ui.space_public.something_went_wrong,
localized_ui.space_public.not_found_title, and
localized_ui.space_public.not_found_hint via useTranslation's t but those keys
are missing; add these three keys with appropriate translated strings to every
locale translations.ts (all 19 files) under the localized_ui.space_public object
so t(...) resolves at runtime, keeping key names identical to what's used in
NotFound and ensuring each locale has a sensible fallback/translation.

</div>
</div>
);
Expand Down
44 changes: 11 additions & 33 deletions apps/space/components/account/auth-forms/auth-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,48 +4,26 @@
* See the LICENSE file for details.
*/

import { useTranslation } from "@plane/i18n";
// helpers
import { EAuthModes } from "@/types/auth";

type TAuthHeader = {
authMode: EAuthModes;
};

type TAuthHeaderContent = {
header: string;
subHeader: string;
};

type TAuthHeaderDetails = {
[mode in EAuthModes]: TAuthHeaderContent;
};

const Titles: TAuthHeaderDetails = {
[EAuthModes.SIGN_IN]: {
header: "Sign in to upvote or comment",
subHeader: "Contribute in nudging the features you want to get built.",
},
[EAuthModes.SIGN_UP]: {
header: "View, comment, and do more",
subHeader: "Sign up or log in to work with Plane work items and Pages.",
},
};

export function AuthHeader(props: TAuthHeader) {
const { authMode } = props;

const getHeaderSubHeader = (mode: EAuthModes | null): TAuthHeaderContent => {
if (mode) {
return Titles[mode];
}

return {
header: "Comment or react to work items",
subHeader: "Use plane to add your valuable inputs to features.",
};
};

const { header, subHeader } = getHeaderSubHeader(authMode);
const { t } = useTranslation();

const header =
authMode === EAuthModes.SIGN_IN
? t("localized_ui.space_public.auth.sign_in_header")
: t("localized_ui.space_public.auth.sign_up_header");
const subHeader =
authMode === EAuthModes.SIGN_IN
? t("localized_ui.space_public.auth.sign_in_subheader")
: t("localized_ui.space_public.auth.sign_up_subheader");

return (
<>
Expand Down
11 changes: 7 additions & 4 deletions apps/space/components/account/auth-forms/auth-root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useEffect, useState } from "react";
import { observer } from "mobx-react";
import { useSearchParams } from "next/navigation";
// plane imports
import { useTranslation } from "@plane/i18n";
import { SitesAuthService } from "@plane/services";
import type { IEmailCheckData } from "@plane/types";
import { OAuthOptions } from "@plane/ui";
Expand Down Expand Up @@ -43,6 +44,7 @@ export const AuthRoot = observer(function AuthRoot() {
const [isPasswordAutoset, setIsPasswordAutoset] = useState(true);
// hooks
const { config } = useInstance();
const { t } = useTranslation();

useEffect(() => {
if (error_code) {
Expand Down Expand Up @@ -84,7 +86,8 @@ export const AuthRoot = observer(function AuthRoot() {
const isSMTPConfigured = config?.is_smtp_configured || false;
const isMagicLoginEnabled = config?.is_magic_login_enabled || false;
const isEmailPasswordEnabled = config?.is_email_password_enabled || false;
const oAuthActionText = authMode === EAuthModes.SIGN_UP ? "Sign up" : "Sign in";
const oAuthActionText =
authMode === EAuthModes.SIGN_UP ? t("localized_ui.space_public.sign_up") : t("localized_ui.space_public.sign_in");
const { isOAuthEnabled, oAuthOptions } = useOAuthConfig(oAuthActionText);

// submit handler- email verification
Expand Down Expand Up @@ -134,8 +137,8 @@ export const AuthRoot = observer(function AuthRoot() {
};

// generating the unique code
const generateEmailUniqueCode = async (email: string): Promise<{ code: string } | undefined> => {
const payload = { email: email };
const generateEmailUniqueCode = async (emailId: string): Promise<{ code: string } | undefined> => {
const payload = { email: emailId };
return await authService
.generateUniqueCode(payload)
.then(() => ({ code: "" }))
Expand Down Expand Up @@ -185,7 +188,7 @@ export const AuthRoot = observer(function AuthRoot() {
}}
/>
)}
<TermsAndConditions isSignUp={authMode === EAuthModes.SIGN_UP ? true : false} />
<TermsAndConditions isSignUp={authMode === EAuthModes.SIGN_UP} />
</div>
</div>
);
Expand Down
19 changes: 12 additions & 7 deletions apps/space/components/account/auth-forms/email.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,13 @@
*/

import type { FormEvent } from "react";
import { useMemo, useRef, useState } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { observer } from "mobx-react";
// icons
import { CircleAlert, XCircle } from "lucide-react";
// types
import { Button } from "@plane/propel/button";
import { useTranslation } from "@plane/i18n";
import type { IEmailCheckData } from "@plane/types";
// ui
import { Input, Spinner } from "@plane/ui";
Expand All @@ -25,13 +26,14 @@ type TAuthEmailForm = {

export const AuthEmailForm = observer(function AuthEmailForm(props: TAuthEmailForm) {
const { onSubmit, defaultEmail } = props;
const { t } = useTranslation();
// states
const [isSubmitting, setIsSubmitting] = useState(false);
const [email, setEmail] = useState(defaultEmail);

const emailError = useMemo(
() => (email && !checkEmailValidity(email) ? { email: "Email is invalid" } : undefined),
[email]
() => (email && !checkEmailValidity(email) ? { email: t("localized_ui.space_auth.email_invalid") } : undefined),
[email, t]
);

const handleFormSubmit = async (event: FormEvent<HTMLFormElement>) => {
Expand All @@ -49,11 +51,15 @@ export const AuthEmailForm = observer(function AuthEmailForm(props: TAuthEmailFo
const [isFocused, setIsFocused] = useState(true);
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
inputRef.current?.focus();
}, []);

return (
<form onSubmit={handleFormSubmit} className="mt-5 space-y-4">
<div className="space-y-1">
<label className="text-13 font-medium text-tertiary" htmlFor="email">
Email
{t("localized_ui.space_auth.email")}
</label>
<div
className={cn(
Expand All @@ -76,13 +82,12 @@ export const AuthEmailForm = observer(function AuthEmailForm(props: TAuthEmailFo
placeholder="name@company.com"
className={`h-10 w-full border-0 disable-autofill-style placeholder:text-placeholder autofill:bg-danger-subtle focus:bg-none active:bg-transparent`}
autoComplete="off"
autoFocus
ref={inputRef}
/>
Comment on lines 82 to 86
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore initial focus to the auth email input

The email form no longer autofocuses its primary input, so users who open Space auth and immediately type or press Enter must first click into the field. This is a workflow regression from the previous behavior and affects keyboard-first sign-in/sign-up flows across the first auth step; please restore autoFocus (or equivalent mount-time focus logic) on the initial email field.

Useful? React with 👍 / 👎.

{email.length > 0 && (
<button
type="button"
aria-label="Clear email"
aria-label={t("localized_ui.space_auth.clear_email")}
onClick={() => {
setEmail("");
inputRef.current?.focus();
Expand All @@ -101,7 +106,7 @@ export const AuthEmailForm = observer(function AuthEmailForm(props: TAuthEmailFo
)}
</div>
<Button type="submit" variant="primary" className="w-full" size="xl" disabled={isButtonDisabled}>
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Continue"}
{isSubmitting ? <Spinner height="20px" width="20px" /> : t("localized_ui.space_auth.continue")}
</Button>
</form>
);
Expand Down
40 changes: 21 additions & 19 deletions apps/space/components/account/auth-forms/password.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { observer } from "mobx-react";
import { Eye, EyeOff, XCircle } from "lucide-react";
// plane imports
import { API_BASE_URL, E_PASSWORD_STRENGTH } from "@plane/constants";
import { useTranslation } from "@plane/i18n";
import { Button } from "@plane/propel/button";
import { AuthService } from "@plane/services";
import { Input, Spinner, PasswordStrengthIndicator } from "@plane/ui";
Expand Down Expand Up @@ -41,6 +42,7 @@ const authService = new AuthService();

export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props) {
const { email, nextPath, isSMTPConfigured, handleAuthStep, handleEmailClear, mode } = props;
const { t } = useTranslation();
// ref
const formRef = useRef<HTMLFormElement>(null);
// states
Expand Down Expand Up @@ -79,14 +81,11 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)

const isButtonDisabled = useMemo(
() =>
!isSubmitting &&
!!passwordFormData.password &&
(mode === EAuthModes.SIGN_UP
? getPasswordStrength(passwordFormData.password) === E_PASSWORD_STRENGTH.STRENGTH_VALID &&
passwordFormData.password === passwordFormData.confirm_password
: true)
? false
: true,
isSubmitting ||
!passwordFormData.password ||
(mode === EAuthModes.SIGN_UP &&
(getPasswordStrength(passwordFormData.password) !== E_PASSWORD_STRENGTH.STRENGTH_VALID ||
passwordFormData.password !== passwordFormData.confirm_password)),
[isSubmitting, mode, passwordFormData.confirm_password, passwordFormData.password]
);

Expand Down Expand Up @@ -123,7 +122,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
<input type="hidden" value={nextPath} name="next_path" />
<div className="space-y-1">
<label className="text-13 font-medium text-tertiary" htmlFor="email">
Email
{t("localized_ui.space_auth.email")}
</label>
<div className={`relative flex items-center rounded-md border border-subtle bg-surface-1`}>
<Input
Expand All @@ -147,20 +146,21 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)

<div className="space-y-1">
<label className="text-13 font-medium text-tertiary" htmlFor="password">
{mode === EAuthModes.SIGN_IN ? "Password" : "Set a password"}
{mode === EAuthModes.SIGN_IN
? t("localized_ui.space_auth.password")
: t("localized_ui.space_auth.set_password")}
</label>
<div className="relative flex items-center rounded-md bg-surface-1">
<Input
type={showPassword?.password ? "text" : "password"}
name="password"
value={passwordFormData.password}
onChange={(e) => handleFormChange("password", e.target.value)}
placeholder="Enter password"
placeholder={t("localized_ui.space_auth.enter_password")}
className="h-10 w-full border border-subtle !bg-surface-1 pr-12 disable-autofill-style placeholder:text-placeholder"
onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)}
autoComplete="off"
autoFocus
/>
Comment on lines 156 to 164
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restore mount-time focus for password entry

The password step no longer sets initial focus on its primary input after autoFocus was removed, so users who submit the email step and immediately start typing are left with focus on the prior element instead of the password field. This regresses keyboard-first sign-in/sign-up flows and can cause Enter/typing to behave unexpectedly until the user clicks into the input.

Useful? React with 👍 / 👎.

{showPassword?.password ? (
<EyeOff
Expand All @@ -180,15 +180,15 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
{mode === EAuthModes.SIGN_UP && (
<div className="space-y-1">
<label className="text-13 font-medium text-tertiary" htmlFor="confirm_password">
Confirm password
{t("localized_ui.space_auth.confirm_password")}
</label>
<div className="relative flex items-center rounded-md bg-surface-1">
<Input
type={showPassword?.retypePassword ? "text" : "password"}
name="confirm_password"
value={passwordFormData.confirm_password}
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
placeholder="Confirm password"
placeholder={t("localized_ui.space_auth.confirm_password")}
className="h-10 w-full border border-subtle !bg-surface-1 pr-12 disable-autofill-style placeholder:text-placeholder"
onFocus={() => setIsRetryPasswordInputFocused(true)}
onBlur={() => setIsRetryPasswordInputFocused(false)}
Expand All @@ -208,7 +208,9 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
</div>
{!!passwordFormData.confirm_password &&
passwordFormData.password !== passwordFormData.confirm_password &&
renderPasswordMatchError && <span className="text-13 text-danger-primary">Passwords don{"'"}t match</span>}
renderPasswordMatchError && (
<span className="text-13 text-danger-primary">{t("localized_ui.space_auth.passwords_dont_match")}</span>
)}
</div>
)}

Expand All @@ -219,9 +221,9 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
{isSubmitting ? (
<Spinner height="20px" width="20px" />
) : isSMTPConfigured ? (
"Continue"
t("localized_ui.space_auth.continue")
) : (
"Go to workspace"
t("localized_ui.space_auth.go_to_workspace")
)}
</Button>
{isSMTPConfigured && (
Expand All @@ -232,13 +234,13 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
className="w-full"
size="xl"
>
Sign in with unique code
{t("localized_ui.space_auth.sign_in_with_unique_code")}
</Button>
)}
</>
) : (
<Button type="submit" variant="primary" className="w-full" size="xl" disabled={isButtonDisabled}>
{isSubmitting ? <Spinner height="20px" width="20px" /> : "Create account"}
{isSubmitting ? <Spinner height="20px" width="20px" /> : t("localized_ui.space_auth.create_account")}
</Button>
)}
</div>
Expand Down
Loading