Skip to content
Merged
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
45 changes: 25 additions & 20 deletions app/(auth)/sign-in.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ import {
} from "react-native";
import { Link, useRouter } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
import { useTranslation } from "react-i18next";
import { Boxes, Eye, EyeOff } from "lucide-react-native";
import { Button } from "~/components/ui/Button";
import { Input } from "~/components/ui/Input";
import { authClient } from "~/lib/auth-client";
import { useServerStore } from "~/stores/server-store";

const SSO_PROVIDER_LABELS: Record<string, string> = {
google: "Continue with Google",
apple: "Continue with Apple",
github: "Continue with GitHub",
/** Brand names for SSO providers — not translated; only the surrounding copy is. */
const SSO_PROVIDER_NAMES: Record<string, string> = {
google: "Google",
apple: "Apple",
github: "GitHub",
};

const PLATFORM_RESTRICTED: Record<string, string[]> = {
Expand All @@ -27,6 +29,7 @@ const PLATFORM_RESTRICTED: Record<string, string[]> = {

export default function SignInScreen() {
const router = useRouter();
const { t } = useTranslation();
const ssoProviders = useServerStore((s) => s.ssoProviders);
const [email, setEmail] = React.useState("");
const [password, setPassword] = React.useState("");
Expand All @@ -36,7 +39,7 @@ export default function SignInScreen() {

const handleSignIn = async () => {
if (!email || !password) {
setErrorMsg("Please enter your email and password.");
setErrorMsg(t("auth.errEmailPassword"));
return;
}
setErrorMsg(null);
Expand All @@ -47,12 +50,12 @@ export default function SignInScreen() {
password,
});
if (error) {
setErrorMsg(error.message ?? "Sign in failed. Please try again.");
setErrorMsg(error.message ?? t("auth.errSignInFailed"));
} else {
router.replace("/(tabs)");
}
} catch {
setErrorMsg("Something went wrong. Please try again.");
setErrorMsg(t("auth.errGeneric"));
} finally {
setLoading(false);
}
Expand All @@ -67,7 +70,7 @@ export default function SignInScreen() {
callbackURL: "/(tabs)",
});
} catch {
setErrorMsg("Something went wrong. Please try again.");
setErrorMsg(t("auth.errGeneric"));
} finally {
setLoading(false);
}
Expand Down Expand Up @@ -95,20 +98,20 @@ export default function SignInScreen() {
<Boxes size={32} color="rgb(30 64 175)" />
</View>
<Text className="text-center text-3xl font-bold text-foreground">
Welcome back
{t("auth.welcomeBack")}
</Text>
<Text className="mt-2 text-center text-base text-muted-foreground">
Sign in to your account to continue.
{t("auth.signInSubtitle")}
</Text>
</View>

<View className="gap-4">
<View>
<Text className="mb-1.5 text-sm font-medium text-foreground">
Email
{t("auth.email")}
</Text>
<Input
placeholder="you@company.com"
placeholder={t("auth.emailPlaceholder")}
autoCapitalize="none"
autoComplete="email"
keyboardType="email-address"
Expand All @@ -125,10 +128,10 @@ export default function SignInScreen() {

<View>
<Text className="mb-1.5 text-sm font-medium text-foreground">
Password
{t("auth.password")}
</Text>
<Input
placeholder="Enter your password"
placeholder={t("auth.passwordPlaceholder")}
secureTextEntry={!showPassword}
textContentType="password"
returnKeyType="go"
Expand All @@ -145,7 +148,7 @@ export default function SignInScreen() {
hitSlop={10}
accessibilityRole="button"
accessibilityLabel={
showPassword ? "Hide password" : "Show password"
showPassword ? t("auth.hidePassword") : t("auth.showPassword")
}
>
{showPassword ? (
Expand All @@ -163,15 +166,15 @@ export default function SignInScreen() {
) : null}

<Button className="mt-2" onPress={handleSignIn} loading={loading}>
{loading ? "Signing in…" : "Sign In"}
{loading ? t("auth.signingIn") : t("auth.signIn")}
</Button>
</View>

{visibleProviders.length > 0 && (
<>
<View className="my-8 flex-row items-center">
<View className="h-px flex-1 bg-border" />
<Text className="mx-4 text-sm text-muted-foreground">or</Text>
<Text className="mx-4 text-sm text-muted-foreground">{t("auth.or")}</Text>
<View className="h-px flex-1 bg-border" />
</View>

Expand All @@ -183,7 +186,9 @@ export default function SignInScreen() {
onPress={() => handleSocialSignIn(id)}
disabled={loading}
>
{SSO_PROVIDER_LABELS[id] ?? `Continue with ${id}`}
{t("auth.continueWith", {
provider: SSO_PROVIDER_NAMES[id] ?? id,
})}
</Button>
))}
</View>
Expand All @@ -192,11 +197,11 @@ export default function SignInScreen() {

<View className="mt-8 flex-row justify-center">
<Text className="text-sm text-muted-foreground">
Don&apos;t have an account?{" "}
{t("auth.noAccount")}{" "}
</Text>
<Link href="/(auth)/sign-up">
<Text className="text-sm font-semibold text-primary">
Sign Up
{t("auth.signUp")}
</Text>
</Link>
</View>
Expand Down
51 changes: 28 additions & 23 deletions app/(auth)/sign-up.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ import {
} from "react-native";
import { Link, useRouter } from "expo-router";
import { SafeAreaView } from "react-native-safe-area-context";
import { useTranslation } from "react-i18next";
import { Boxes, Eye, EyeOff } from "lucide-react-native";
import { Button } from "~/components/ui/Button";
import { Input } from "~/components/ui/Input";
import { authClient } from "~/lib/auth-client";
import { useServerStore } from "~/stores/server-store";

const SSO_PROVIDER_LABELS: Record<string, string> = {
google: "Continue with Google",
apple: "Continue with Apple",
github: "Continue with GitHub",
/** Brand names for SSO providers — not translated; only the surrounding copy is. */
const SSO_PROVIDER_NAMES: Record<string, string> = {
google: "Google",
apple: "Apple",
github: "GitHub",
};

const PLATFORM_RESTRICTED: Record<string, string[]> = {
Expand All @@ -27,6 +29,7 @@ const PLATFORM_RESTRICTED: Record<string, string[]> = {

export default function SignUpScreen() {
const router = useRouter();
const { t } = useTranslation();
const ssoProviders = useServerStore((s) => s.ssoProviders);
const [name, setName] = React.useState("");
const [email, setEmail] = React.useState("");
Expand All @@ -37,11 +40,11 @@ export default function SignUpScreen() {

const handleSignUp = async () => {
if (!name || !email || !password) {
setErrorMsg("Please fill in all fields.");
setErrorMsg(t("auth.errAllFields"));
return;
}
if (password.length < 8) {
setErrorMsg("Password must be at least 8 characters.");
setErrorMsg(t("auth.errPasswordLength"));
return;
}
setErrorMsg(null);
Expand All @@ -53,12 +56,12 @@ export default function SignUpScreen() {
password,
});
if (error) {
setErrorMsg(error.message ?? "Sign up failed. Please try again.");
setErrorMsg(error.message ?? t("auth.errSignUpFailed"));
} else {
router.replace("/(tabs)");
}
} catch {
setErrorMsg("Something went wrong. Please try again.");
setErrorMsg(t("auth.errGeneric"));
} finally {
setLoading(false);
}
Expand All @@ -73,7 +76,7 @@ export default function SignUpScreen() {
callbackURL: "/(tabs)",
});
} catch {
setErrorMsg("Something went wrong. Please try again.");
setErrorMsg(t("auth.errGeneric"));
} finally {
setLoading(false);
}
Expand Down Expand Up @@ -101,20 +104,20 @@ export default function SignUpScreen() {
<Boxes size={32} color="rgb(30 64 175)" />
</View>
<Text className="text-center text-3xl font-bold text-foreground">
Create account
{t("auth.createAccountTitle")}
</Text>
<Text className="mt-2 text-center text-base text-muted-foreground">
Sign up to get started with ObjectStack.
{t("auth.signUpSubtitle")}
</Text>
</View>

<View className="gap-4">
<View>
<Text className="mb-1.5 text-sm font-medium text-foreground">
Full Name
{t("auth.fullName")}
</Text>
<Input
placeholder="John Doe"
placeholder={t("auth.namePlaceholder")}
textContentType="name"
autoComplete="name"
autoCapitalize="words"
Expand All @@ -129,10 +132,10 @@ export default function SignUpScreen() {

<View>
<Text className="mb-1.5 text-sm font-medium text-foreground">
Email
{t("auth.email")}
</Text>
<Input
placeholder="you@company.com"
placeholder={t("auth.emailPlaceholder")}
autoCapitalize="none"
autoComplete="email"
keyboardType="email-address"
Expand All @@ -148,10 +151,10 @@ export default function SignUpScreen() {

<View>
<Text className="mb-1.5 text-sm font-medium text-foreground">
Password
{t("auth.password")}
</Text>
<Input
placeholder="At least 8 characters"
placeholder={t("auth.passwordHint")}
secureTextEntry={!showPassword}
textContentType="newPassword"
returnKeyType="go"
Expand All @@ -167,7 +170,7 @@ export default function SignUpScreen() {
hitSlop={10}
accessibilityRole="button"
accessibilityLabel={
showPassword ? "Hide password" : "Show password"
showPassword ? t("auth.hidePassword") : t("auth.showPassword")
}
>
{showPassword ? (
Expand All @@ -185,15 +188,15 @@ export default function SignUpScreen() {
) : null}

<Button className="mt-2" onPress={handleSignUp} loading={loading}>
{loading ? "Creating account…" : "Create Account"}
{loading ? t("auth.creatingAccount") : t("auth.createAccountCta")}
</Button>
</View>

{visibleProviders.length > 0 && (
<>
<View className="my-8 flex-row items-center">
<View className="h-px flex-1 bg-border" />
<Text className="mx-4 text-sm text-muted-foreground">or</Text>
<Text className="mx-4 text-sm text-muted-foreground">{t("auth.or")}</Text>
<View className="h-px flex-1 bg-border" />
</View>

Expand All @@ -205,7 +208,9 @@ export default function SignUpScreen() {
onPress={() => handleSocialSignIn(id)}
disabled={loading}
>
{SSO_PROVIDER_LABELS[id] ?? `Continue with ${id}`}
{t("auth.continueWith", {
provider: SSO_PROVIDER_NAMES[id] ?? id,
})}
</Button>
))}
</View>
Expand All @@ -214,11 +219,11 @@ export default function SignUpScreen() {

<View className="mt-8 flex-row justify-center">
<Text className="text-sm text-muted-foreground">
Already have an account?{" "}
{t("auth.haveAccount")}{" "}
</Text>
<Link href="/(auth)/sign-in">
<Text className="text-sm font-semibold text-primary">
Sign In
{t("auth.signIn")}
</Text>
</Link>
</View>
Expand Down
20 changes: 11 additions & 9 deletions app/(tabs)/profile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { SafeAreaView } from "react-native-safe-area-context";
import { webContentMaxWidth } from "~/lib/responsive";
import { UserCircle } from "lucide-react-native";
import { useRouter } from "expo-router";
import { useTranslation } from "react-i18next";
import { Button } from "~/components/ui/Button";
import { authClient } from "~/lib/auth-client";
import { useToast } from "~/components/ui/Toast";
Expand All @@ -11,6 +12,7 @@ import { useConfirm } from "~/components/ui/ConfirmDialog";
export default function ProfileScreen() {
const { data: session } = authClient.useSession();
const router = useRouter();
const { t } = useTranslation();
const { toastError } = useToast();
const confirm = useConfirm();

Expand All @@ -19,15 +21,15 @@ export default function ProfileScreen() {
await authClient.signOut();
router.replace("/(auth)/sign-in");
} catch {
toastError("Failed to sign out. Please try again.");
toastError(t("more.signOutFailed"));
}
};

const handleSignOut = async () => {
const ok = await confirm({
title: "Sign Out",
message: "Are you sure you want to sign out?",
confirmLabel: "Sign Out",
title: t("more.signOutTitle"),
message: t("more.signOutConfirm"),
confirmLabel: t("more.signOutTitle"),
destructive: true,
});
if (ok) void performSignOut();
Expand All @@ -45,19 +47,19 @@ export default function ProfileScreen() {
<UserCircle size={56} color="#94a3b8" />
</View>
<Text className="mt-4 text-xl font-bold text-foreground">
{session?.user.name ?? "User"}
{session?.user.name ?? t("more.profileFallback")}
</Text>
<Text className="mt-1 text-sm text-muted-foreground">
{session?.user.email ?? ""}
</Text>
</View>

<View className="mt-8 gap-3">
<Button variant="outline">Edit Profile</Button>
<Button variant="outline">Settings</Button>
<Button variant="ghost">Help &amp; Support</Button>
<Button variant="outline">{t("more.editProfile")}</Button>
<Button variant="outline">{t("more.settings")}</Button>
<Button variant="ghost">{t("more.helpSupport")}</Button>
<Button variant="destructive" onPress={handleSignOut}>
Sign Out
{t("more.signOut")}
</Button>
</View>
</ScrollView>
Expand Down
Loading
Loading