Need an account?{" "}
diff --git a/frontend/src/routes/pricing.tsx b/frontend/src/routes/pricing.tsx
index 88142c8be..4702a76ac 100644
--- a/frontend/src/routes/pricing.tsx
+++ b/frontend/src/routes/pricing.tsx
@@ -152,8 +152,18 @@ function PricingPage() {
useEffect(() => {
const checkPlatform = async () => {
try {
- const platform = await type();
- setIsIOS(platform === "ios");
+ // First check if we're in a Tauri environment
+ const isTauriEnv = await import("@tauri-apps/api/core")
+ .then((m) => m.isTauri())
+ .catch(() => false);
+
+ if (isTauriEnv) {
+ // Only check platform type if we're in a Tauri environment
+ const platform = await type();
+ setIsIOS(platform === "ios");
+ } else {
+ setIsIOS(false);
+ }
} catch (error) {
console.error("Error checking platform:", error);
setIsIOS(false);
diff --git a/frontend/src/routes/signup.tsx b/frontend/src/routes/signup.tsx
index 5c11c514d..26dfb6b61 100644
--- a/frontend/src/routes/signup.tsx
+++ b/frontend/src/routes/signup.tsx
@@ -7,8 +7,14 @@ import { Label } from "@/components/ui/label";
import { AlertDestructive } from "@/components/AlertDestructive";
import { Loader2, Github, Mail } from "lucide-react";
import { Google } from "@/components/icons/Google";
+import { Apple } from "@/components/icons/Apple";
import { AuthMain } from "@/components/AuthMain";
import { isTauri, invoke } from "@tauri-apps/api/core";
+import { type } from "@tauri-apps/plugin-os";
+import { v4 as uuidv4 } from "uuid";
+import type { AppleCredential } from "@/types/apple-sign-in";
+import { sha256 } from "@noble/hashes/sha256";
+import { bytesToHex } from "@noble/hashes/utils";
type SignupSearchParams = {
next?: string;
@@ -23,7 +29,7 @@ export const Route = createFileRoute("/signup")({
})
});
-type SignUpMethod = "email" | "github" | "google" | null;
+type SignUpMethod = "email" | "github" | "google" | "apple" | null;
function SignupPage() {
const navigate = useNavigate();
@@ -32,6 +38,37 @@ function SignupPage() {
const [signUpMethod, setSignUpMethod] = useState
(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
+ const [isIOS, setIsIOS] = useState(false);
+
+ // Check if running on iOS
+ useEffect(() => {
+ const checkPlatform = async () => {
+ try {
+ // First check if we're in a Tauri environment
+ let isTauriEnv = false;
+ try {
+ isTauriEnv = await isTauri();
+ } catch {
+ // Not in Tauri environment
+ isTauriEnv = false;
+ }
+
+ // Only check platform type if we're in a Tauri environment
+ if (isTauriEnv) {
+ const platform = await type();
+ setIsIOS(platform === "ios");
+ } else {
+ // Not in Tauri environment, definitely not iOS
+ setIsIOS(false);
+ }
+ } catch (error) {
+ console.error("Error checking platform:", error);
+ setIsIOS(false);
+ }
+ };
+
+ checkPlatform();
+ }, []);
// Redirect if already logged in
useEffect(() => {
@@ -150,9 +187,88 @@ function SignupPage() {
}
};
+ const handleAppleSignup = async () => {
+ try {
+ // Only iOS supports native Apple Sign In
+ console.log("[OAuth] Initiating native Sign in with Apple for iOS");
+
+ try {
+ // Generate random UUIDs for state and nonce
+ const state = uuidv4();
+ const rawNonce = uuidv4();
+
+ // SHA-256 hash the nonce before sending to Apple
+ // Apple requires the nonce to be hashed with SHA-256
+ const hashedNonce = bytesToHex(sha256(new TextEncoder().encode(rawNonce)));
+
+
+ // Invoke the Apple Sign in plugin
+ // This will show the native Apple authentication UI
+ const result = await invoke(
+ "plugin:sign-in-with-apple|get_apple_id_credential",
+ {
+ payload: {
+ scope: ["email", "fullName"],
+ state,
+ nonce: hashedNonce, // Send the hashed nonce to Apple
+ // Disable debug mode in production
+ options: {
+ debug: false
+ }
+ }
+ }
+ );
+
+ console.log("[OAuth] Apple Sign-In result:", result);
+
+ // Format the response for the API
+ const appleUser = {
+ user_identifier: result.user,
+ identity_token: result.identityToken,
+ email: result.email,
+ given_name: result.fullName?.givenName,
+ family_name: result.fullName?.familyName,
+ nonce: rawNonce // Pass the original raw nonce to backend
+ };
+
+ // Send to backend via SDK
+ try {
+ await os.handleAppleNativeSignIn(appleUser, "");
+ // Redirect after successful signup
+ if (selected_plan) {
+ navigate({
+ to: "/pricing",
+ search: { selected_plan }
+ });
+ } else {
+ navigate({ to: next || "/" });
+ }
+ } catch (backendError) {
+ console.error("[OAuth] Backend processing failed:", backendError);
+ setError(
+ backendError instanceof Error
+ ? backendError.message
+ : "Failed to process Apple authentication"
+ );
+ }
+ } catch (error) {
+ console.error("[OAuth] Failed to authenticate with Apple:", error);
+ const errorMessage =
+ error instanceof Error
+ ? `Apple Sign In error: ${error.message}`
+ : "Failed to authenticate with Apple. Please try again.";
+ setError(errorMessage);
+ }
+ } catch (error) {
+ console.error("Failed to initiate Apple signup:", error);
+ setError("Failed to initiate Apple signup. Please try again.");
+ }
+ };
+
if (!signUpMethod) {
return (
+ {error && }
+ {isIOS && (
+
+ )}
Already have an account?{" "}
diff --git a/frontend/src/types/apple-sign-in.d.ts b/frontend/src/types/apple-sign-in.d.ts
new file mode 100644
index 000000000..406949156
--- /dev/null
+++ b/frontend/src/types/apple-sign-in.d.ts
@@ -0,0 +1,13 @@
+// Type definitions for native iOS Apple Sign In plugin
+
+// For native Apple Sign In plugin
+export interface AppleCredential {
+ user: string;
+ identityToken: string;
+ email?: string;
+ fullName?: {
+ givenName?: string;
+ familyName?: string;
+ };
+ state?: string;
+}