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
303 changes: 114 additions & 189 deletions Examples/Examples.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions Examples/Examples/ActionState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ enum ActionState<Success, Failure: Error> {
case result(Result<Success, Failure>)

var success: Success? {
if case let .result(.success(success)) = self { return success }
if case .result(.success(let success)) = self { return success }
return nil
}
}
Expand All @@ -34,9 +34,9 @@ struct ActionStateView<Success: Sendable, SuccessContent: View>: View {
Color.clear
case .inFlight:
ProgressView()
case let .result(.success(value)):
case .result(.success(let value)):
content(value)
case let .result(.failure(error)):
case .result(.failure(let error)):
VStack {
ErrorText(error)
Button("Retry") {
Expand Down
18 changes: 9 additions & 9 deletions Examples/Examples/AnyJSONView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ struct AnyJSONView: View {
var body: some View {
switch value {
case .null: Text("<nil>")
case let .bool(value): Text(value.description)
case let .double(value): Text(value.description)
case let .integer(value): Text(value.description)
case let .string(value): Text(value)
case let .array(value):
ForEach(0 ..< value.count, id: \.self) { index in
case .bool(let value): Text(value.description)
case .double(let value): Text(value.description)
case .integer(let value): Text(value.description)
case .string(let value): Text(value)
case .array(let value):
ForEach(0..<value.count, id: \.self) { index in
if value[index].isPrimitive {
LabeledContent("\(index)") {
AnyJSONView(value: value[index])
Expand All @@ -33,7 +33,7 @@ struct AnyJSONView: View {
}
}
}
case let .object(object):
case .object(let object):
let elements = Array(object).sorted(by: { $0.key < $1.key })
ForEach(elements, id: \.key) { element in
if element.value.isPrimitive {
Expand Down Expand Up @@ -77,7 +77,7 @@ extension AnyJSONView {
"app_metadata": [
"provider": "email",
"providers": [
"email",
"email"
],
],
"aud": "authenticated",
Expand All @@ -102,7 +102,7 @@ extension AnyJSONView {
"provider": "email",
"updated_at": "2024-03-21T03:19:10.146262Z",
"user_id": "06f83324-e553-4d39-a609-fd30682ee127",
],
]
],
"last_sign_in_at": "2024-03-21T03:19:10.149557Z",
"phone": "",
Expand Down
112 changes: 112 additions & 0 deletions Examples/Examples/Auth/AuthExamplesView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
//
// AuthExamplesView.swift
// Examples
//
// Demonstrates all authentication methods available in Supabase Auth
//

import SwiftUI

struct AuthExamplesView: View {
var body: some View {
List {
Section {
Text("Explore authentication methods and user management with Supabase Auth")
.font(.subheadline)
.foregroundColor(.secondary)
}

Section("Email Authentication") {
NavigationLink(destination: AuthWithEmailAndPassword()) {
ExampleRow(
title: "Email & Password",
description: "Sign up and sign in with email and password",
icon: "envelope.fill"
)
}

NavigationLink(destination: AuthWithMagicLink()) {
ExampleRow(
title: "Magic Link",
description: "Passwordless authentication via email",
icon: "link.circle.fill"
)
}
}

Section("Phone Authentication") {
NavigationLink(destination: SignInWithPhone()) {
ExampleRow(
title: "Phone OTP",
description: "Sign in with phone number and verification code",
icon: "phone.fill"
)
}
}

Section("Social Authentication") {
NavigationLink(destination: SignInWithApple()) {
ExampleRow(
title: "Sign in with Apple",
description: "Apple ID authentication",
icon: "apple.logo"
)
}

NavigationLink(destination: SignInWithFacebook()) {
ExampleRow(
title: "Sign in with Facebook",
description: "Facebook social authentication",
icon: "f.circle.fill"
)
}

NavigationLink(destination: SignInWithOAuth()) {
ExampleRow(
title: "OAuth Providers",
description: "Generic OAuth flow for various providers",
icon: "person.crop.circle.badge.checkmark"
)
}

#if canImport(UIKit)
NavigationLink(
destination: UIViewControllerWrapper(SignInWithOAuthViewController())
.edgesIgnoringSafeArea(.all)
) {
ExampleRow(
title: "OAuth with UIKit",
description: "OAuth authentication using UIKit",
icon: "rectangle.portrait.and.arrow.right"
)
}
#endif

NavigationLink(destination: GoogleSignInSDKFlow()) {
ExampleRow(
title: "Google Sign-In SDK",
description: "Google authentication using official SDK",
icon: "g.circle.fill"
)
}
}

Section("Guest Access") {
NavigationLink(destination: SignInAnonymously()) {
ExampleRow(
title: "Anonymous Sign In",
description: "Create temporary anonymous sessions",
icon: "person.fill.questionmark"
)
}
}
}
.navigationTitle("Authentication")
}
}

#Preview {
NavigationStack {
AuthExamplesView()
}
}
7 changes: 4 additions & 3 deletions Examples/Examples/Auth/AuthView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ struct AuthView: View {
.navigationTitle(options.title)
}
#if !os(macOS)
.navigationBarTitleDisplayMode(.inline)
.navigationBarTitleDisplayMode(.inline)
#endif
}
}
Expand All @@ -66,8 +66,9 @@ extension AuthView.Option: View {
case .signInWithFacebook: SignInWithFacebook()
case .signInWithOAuth: SignInWithOAuth()
#if canImport(UIKit)
case .signInWithOAuthUsingUIKit: UIViewControllerWrapper(SignInWithOAuthViewController())
.edgesIgnoringSafeArea(.all)
case .signInWithOAuthUsingUIKit:
UIViewControllerWrapper(SignInWithOAuthViewController())
.edgesIgnoringSafeArea(.all)
#endif
case .googleSignInSDKFlow: GoogleSignInSDKFlow()
case .signInAnonymously: SignInAnonymously()
Expand Down
80 changes: 63 additions & 17 deletions Examples/Examples/Auth/AuthWithEmailAndPassword.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// AuthWithEmailAndPassword.swift
// Examples
//
// Created by Guilherme Souza on 15/12/23.
// Demonstrates email and password authentication with sign up and sign in
//

import SwiftUI
Expand All @@ -26,44 +26,60 @@ struct AuthWithEmailAndPassword: View {
@State var isPresentingResetPassword: Bool = false

var body: some View {
Form {
List {
Section {
Text(
mode == .signIn
? "Sign in with your email and password"
: "Create a new account with email and password"
)
.font(.caption)
.foregroundColor(.secondary)
}

Section("Credentials") {
TextField("Email", text: $email)
.textContentType(.emailAddress)
.autocorrectionDisabled()
#if !os(macOS)
.keyboardType(.emailAddress)
.textInputAutocapitalization(.never)
#endif
#if !os(macOS)
.keyboardType(.emailAddress)
.textInputAutocapitalization(.never)
#endif

SecureField("Password", text: $password)
.textContentType(.password)
.autocorrectionDisabled()
#if !os(macOS)
.textInputAutocapitalization(.never)
#endif
#if !os(macOS)
.textInputAutocapitalization(.never)
#endif
}

Section {
Button(mode == .signIn ? "Sign in" : "Sign up") {
Button(mode == .signIn ? "Sign In" : "Sign Up") {
Task {
await primaryActionButtonTapped()
}
}
.disabled(email.isEmpty || password.isEmpty)
}

switch actionState {
case .idle:
EmptyView()
case .inFlight:
ProgressView()
case let .result(.failure(error)):
ErrorText(error)
case .result(.success(.needsEmailConfirmation)):
Section {
Text("Check you inbox.")
ProgressView(mode == .signIn ? "Signing in..." : "Creating account...")
}
case .result(.failure(let error)):
Section {
ErrorText(error)
}
case .result(.success(.needsEmailConfirmation)):
Section("Email Confirmation Required") {
Text("Check your inbox for a confirmation email.")
.foregroundColor(.green)

Button("Resend confirmation") {
Button("Resend Confirmation") {
Task {
await resendConfirmationButtonTapped()
}
Expand All @@ -73,7 +89,9 @@ struct AuthWithEmailAndPassword: View {

Section {
Button(
mode == .signIn ? "Don't have an account? Sign up." : "Already have an account? Sign in."
mode == .signIn
? "Don't have an account? Sign up."
: "Already have an account? Sign in."
) {
mode = mode == .signIn ? .signUp : .signIn
actionState = .idle
Expand All @@ -87,7 +105,35 @@ struct AuthWithEmailAndPassword: View {
}
}
}

Section("About") {
VStack(alignment: .leading, spacing: 8) {
Text("Email & Password Authentication")
.font(.headline)

Text(
"Email and password authentication is the most common method. Users can sign up with their email and a secure password, then sign in with those credentials."
)
.font(.caption)
.foregroundColor(.secondary)

Text("Features:")
.font(.subheadline)
.padding(.top, 4)

VStack(alignment: .leading, spacing: 4) {
Label("Email confirmation via link", systemImage: "checkmark.circle")
Label("Password requirements enforcement", systemImage: "checkmark.circle")
Label("Password reset functionality", systemImage: "checkmark.circle")
Label("Secure session management", systemImage: "checkmark.circle")
}
.font(.caption)
.foregroundColor(.secondary)
}
}
}
.navigationTitle("Email & Password")
.gitHubSourceLink()
.onOpenURL { url in
Task {
await onOpenURL(url)
Expand Down
Loading
Loading