mobile v0.1 with deployment for ios (#191)
* stage 1: project init * stage 2: skeleton with navigation * step 2.5: streams list * step 4: stream playback experience * step 5-6: compose experience * fix: broken record * transcode media particles to mp4 * build: reproducible go generate * build: rename skaffold module for particle processor worker * infra: increase particle processor worker resources Was dealing with OOM errors * tweaks to mobile * log transcode work * view on desktop placeholder * tweak padding * cap video resolution to save on memory * infra: bump memory limits as insurance * ux improvements * update bundle id for mobile * config for mobile
This commit was merged in pull request #191.
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
import { useState } from "react";
|
||||
import {
|
||||
KeyboardAvoidingView,
|
||||
Platform,
|
||||
Pressable,
|
||||
Text,
|
||||
TextInput,
|
||||
View,
|
||||
} from "react-native";
|
||||
import { SafeAreaView } from "react-native-safe-area-context";
|
||||
import { useAuthStore } from "@/stores/auth-store";
|
||||
|
||||
type Step = "email" | "code";
|
||||
|
||||
export function SignInScreen() {
|
||||
const [step, setStep] = useState<Step>("email");
|
||||
const [email, setEmail] = useState("");
|
||||
|
||||
return (
|
||||
<SafeAreaView className="flex-1 bg-background">
|
||||
<KeyboardAvoidingView
|
||||
behavior={Platform.OS === "ios" ? "padding" : undefined}
|
||||
className="flex-1"
|
||||
>
|
||||
<View className="flex-1 justify-center px-6">
|
||||
{step === "email" ? (
|
||||
<EmailStep
|
||||
onCodeSent={(submittedEmail) => {
|
||||
setEmail(submittedEmail);
|
||||
setStep("code");
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<CodeStep email={email} onBack={() => setStep("email")} />
|
||||
)}
|
||||
</View>
|
||||
</KeyboardAvoidingView>
|
||||
</SafeAreaView>
|
||||
);
|
||||
}
|
||||
|
||||
function EmailStep({ onCodeSent }: { onCodeSent: (email: string) => void }) {
|
||||
const [email, setEmail] = useState("");
|
||||
const isRequestingCode = useAuthStore((s) => s.isRequestingCode);
|
||||
const error = useAuthStore((s) => s.error);
|
||||
const requestCode = useAuthStore((s) => s.requestCode);
|
||||
const clearError = useAuthStore((s) => s.clearError);
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
await requestCode(email);
|
||||
onCodeSent(email);
|
||||
} catch {
|
||||
// Error surfaced via the store
|
||||
}
|
||||
};
|
||||
|
||||
const disabled = isRequestingCode || email.trim().length === 0;
|
||||
|
||||
return (
|
||||
<View className="gap-5">
|
||||
<View className="gap-1">
|
||||
<Text className="text-foreground text-3xl font-semibold">Sign in</Text>
|
||||
<Text className="text-muted-foreground text-base">
|
||||
Enter your email to receive a sign-in code.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="gap-2">
|
||||
<Text className="text-foreground text-sm font-medium">Email</Text>
|
||||
<TextInput
|
||||
value={email}
|
||||
onChangeText={(text) => {
|
||||
setEmail(text);
|
||||
if (error) clearError();
|
||||
}}
|
||||
placeholder="[email protected]"
|
||||
placeholderTextColor="#878787"
|
||||
keyboardType="email-address"
|
||||
autoCapitalize="none"
|
||||
autoCorrect={false}
|
||||
autoComplete="email"
|
||||
textContentType="emailAddress"
|
||||
autoFocus
|
||||
returnKeyType="go"
|
||||
onSubmitEditing={submit}
|
||||
className="border-input text-foreground rounded-lg border bg-background px-4 py-3 text-base"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{error ? (
|
||||
<Text className="text-destructive text-sm">{error}</Text>
|
||||
) : null}
|
||||
|
||||
<Pressable
|
||||
onPress={submit}
|
||||
disabled={disabled}
|
||||
className={`rounded-lg px-4 py-3.5 items-center ${
|
||||
disabled ? "bg-muted" : "bg-primary"
|
||||
}`}
|
||||
>
|
||||
<Text
|
||||
className={`text-base font-semibold ${
|
||||
disabled ? "text-muted-foreground" : "text-primary-foreground"
|
||||
}`}
|
||||
>
|
||||
{isRequestingCode ? "Sending..." : "Continue"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
function CodeStep({ email, onBack }: { email: string; onBack: () => void }) {
|
||||
const [code, setCode] = useState("");
|
||||
const isSigningIn = useAuthStore((s) => s.isSigningIn);
|
||||
const error = useAuthStore((s) => s.error);
|
||||
const signIn = useAuthStore((s) => s.signIn);
|
||||
const clearError = useAuthStore((s) => s.clearError);
|
||||
|
||||
const submit = async () => {
|
||||
try {
|
||||
await signIn(email, code);
|
||||
} catch {
|
||||
// Error surfaced via the store; keep the screen visible
|
||||
}
|
||||
};
|
||||
|
||||
const disabled = isSigningIn || code.trim().length === 0;
|
||||
|
||||
return (
|
||||
<View className="gap-5">
|
||||
<View className="gap-1">
|
||||
<Text className="text-foreground text-3xl font-semibold">
|
||||
Check your email
|
||||
</Text>
|
||||
<Text className="text-muted-foreground text-base">
|
||||
We sent a code to{" "}
|
||||
<Text className="text-foreground font-medium">{email}</Text>.
|
||||
</Text>
|
||||
</View>
|
||||
|
||||
<View className="gap-2">
|
||||
<Text className="text-foreground text-sm font-medium">Code</Text>
|
||||
<TextInput
|
||||
value={code}
|
||||
onChangeText={(text) => {
|
||||
setCode(text);
|
||||
if (error) clearError();
|
||||
}}
|
||||
placeholder="Enter code"
|
||||
placeholderTextColor="#878787"
|
||||
keyboardType="number-pad"
|
||||
autoFocus
|
||||
returnKeyType="go"
|
||||
textContentType="oneTimeCode"
|
||||
onSubmitEditing={submit}
|
||||
className="border-input text-foreground rounded-lg border bg-background px-4 py-3 text-base tracking-widest"
|
||||
/>
|
||||
</View>
|
||||
|
||||
{error ? (
|
||||
<Text className="text-destructive text-sm">{error}</Text>
|
||||
) : null}
|
||||
|
||||
<View className="gap-2">
|
||||
<Pressable
|
||||
onPress={submit}
|
||||
disabled={disabled}
|
||||
className={`rounded-lg px-4 py-3.5 items-center ${
|
||||
disabled ? "bg-muted" : "bg-primary"
|
||||
}`}
|
||||
>
|
||||
<Text
|
||||
className={`text-base font-semibold ${
|
||||
disabled ? "text-muted-foreground" : "text-primary-foreground"
|
||||
}`}
|
||||
>
|
||||
{isSigningIn ? "Signing in..." : "Sign in"}
|
||||
</Text>
|
||||
</Pressable>
|
||||
<Pressable
|
||||
onPress={onBack}
|
||||
className="rounded-lg px-4 py-3.5 items-center"
|
||||
>
|
||||
<Text className="text-muted-foreground text-base font-medium">
|
||||
Back
|
||||
</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user