Skip to content

Commit 709fee6

Browse files
authored
Merge pull request #4 from rivenshell/development
v0.3 update
2 parents 5119dc4 + 661af1a commit 709fee6

File tree

14 files changed

+1143
-173
lines changed

14 files changed

+1143
-173
lines changed

outvoice.xcodeproj/project.pbxproj

+11-39
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,7 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10-
BE3E1CD92D92D747004BB4C6 /* Auth in Frameworks */ = {isa = PBXBuildFile; productRef = BE3E1CD82D92D747004BB4C6 /* Auth */; };
11-
BE3E1CDB2D92D747004BB4C6 /* PostgREST in Frameworks */ = {isa = PBXBuildFile; productRef = BE3E1CDA2D92D747004BB4C6 /* PostgREST */; };
12-
BE3E1CDD2D92D747004BB4C6 /* Realtime in Frameworks */ = {isa = PBXBuildFile; productRef = BE3E1CDC2D92D747004BB4C6 /* Realtime */; };
13-
BE3E1CDF2D92D747004BB4C6 /* Storage in Frameworks */ = {isa = PBXBuildFile; productRef = BE3E1CDE2D92D747004BB4C6 /* Storage */; };
14-
BE3E1CE12D92D747004BB4C6 /* Supabase in Frameworks */ = {isa = PBXBuildFile; productRef = BE3E1CE02D92D747004BB4C6 /* Supabase */; };
10+
BE6A04DF2DB128AA0054C6F9 /* Supabase in Frameworks */ = {isa = PBXBuildFile; productRef = BE6A04DE2DB128AA0054C6F9 /* Supabase */; };
1511
/* End PBXBuildFile section */
1612

1713
/* Begin PBXContainerItemProxy section */
@@ -73,11 +69,7 @@
7369
isa = PBXFrameworksBuildPhase;
7470
buildActionMask = 2147483647;
7571
files = (
76-
BE3E1CE12D92D747004BB4C6 /* Supabase in Frameworks */,
77-
BE3E1CDF2D92D747004BB4C6 /* Storage in Frameworks */,
78-
BE3E1CDD2D92D747004BB4C6 /* Realtime in Frameworks */,
79-
BE3E1CDB2D92D747004BB4C6 /* PostgREST in Frameworks */,
80-
BE3E1CD92D92D747004BB4C6 /* Auth in Frameworks */,
72+
BE6A04DF2DB128AA0054C6F9 /* Supabase in Frameworks */,
8173
);
8274
runOnlyForDeploymentPostprocessing = 0;
8375
};
@@ -138,11 +130,7 @@
138130
);
139131
name = outvoice;
140132
packageProductDependencies = (
141-
BE3E1CD82D92D747004BB4C6 /* Auth */,
142-
BE3E1CDA2D92D747004BB4C6 /* PostgREST */,
143-
BE3E1CDC2D92D747004BB4C6 /* Realtime */,
144-
BE3E1CDE2D92D747004BB4C6 /* Storage */,
145-
BE3E1CE02D92D747004BB4C6 /* Supabase */,
133+
BE6A04DE2DB128AA0054C6F9 /* Supabase */,
146134
);
147135
productName = outvoice;
148136
productReference = BE0B66662D616C37009B709F /* outvoice.app */;
@@ -227,7 +215,7 @@
227215
mainGroup = BE0B665D2D616C37009B709F;
228216
minimizedProjectReferenceProxies = 1;
229217
packageReferences = (
230-
BE3E1CD72D92D747004BB4C6 /* XCRemoteSwiftPackageReference "supabase-swift" */,
218+
BE6A04DD2DB128AA0054C6F9 /* XCRemoteSwiftPackageReference "supabase-swift" */,
231219
);
232220
preferredProjectObjectVersion = 77;
233221
productRefGroup = BE0B66672D616C37009B709F /* Products */;
@@ -438,8 +426,10 @@
438426
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
439427
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
440428
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
429+
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
441430
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
442431
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
432+
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
443433
LD_RUNPATH_SEARCH_PATHS = (
444434
"$(inherited)",
445435
"@executable_path/Frameworks",
@@ -469,8 +459,10 @@
469459
INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;
470460
INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;
471461
INFOPLIST_KEY_UILaunchScreen_Generation = YES;
462+
INFOPLIST_KEY_UILaunchStoryboardName = "Launch Screen";
472463
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
473464
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
465+
INFOPLIST_KEY_UIUserInterfaceStyle = Light;
474466
LD_RUNPATH_SEARCH_PATHS = (
475467
"$(inherited)",
476468
"@executable_path/Frameworks",
@@ -598,7 +590,7 @@
598590
/* End XCConfigurationList section */
599591

600592
/* Begin XCRemoteSwiftPackageReference section */
601-
BE3E1CD72D92D747004BB4C6 /* XCRemoteSwiftPackageReference "supabase-swift" */ = {
593+
BE6A04DD2DB128AA0054C6F9 /* XCRemoteSwiftPackageReference "supabase-swift" */ = {
602594
isa = XCRemoteSwiftPackageReference;
603595
repositoryURL = "https://github.com/supabase/supabase-swift.git";
604596
requirement = {
@@ -609,29 +601,9 @@
609601
/* End XCRemoteSwiftPackageReference section */
610602

611603
/* Begin XCSwiftPackageProductDependency section */
612-
BE3E1CD82D92D747004BB4C6 /* Auth */ = {
604+
BE6A04DE2DB128AA0054C6F9 /* Supabase */ = {
613605
isa = XCSwiftPackageProductDependency;
614-
package = BE3E1CD72D92D747004BB4C6 /* XCRemoteSwiftPackageReference "supabase-swift" */;
615-
productName = Auth;
616-
};
617-
BE3E1CDA2D92D747004BB4C6 /* PostgREST */ = {
618-
isa = XCSwiftPackageProductDependency;
619-
package = BE3E1CD72D92D747004BB4C6 /* XCRemoteSwiftPackageReference "supabase-swift" */;
620-
productName = PostgREST;
621-
};
622-
BE3E1CDC2D92D747004BB4C6 /* Realtime */ = {
623-
isa = XCSwiftPackageProductDependency;
624-
package = BE3E1CD72D92D747004BB4C6 /* XCRemoteSwiftPackageReference "supabase-swift" */;
625-
productName = Realtime;
626-
};
627-
BE3E1CDE2D92D747004BB4C6 /* Storage */ = {
628-
isa = XCSwiftPackageProductDependency;
629-
package = BE3E1CD72D92D747004BB4C6 /* XCRemoteSwiftPackageReference "supabase-swift" */;
630-
productName = Storage;
631-
};
632-
BE3E1CE02D92D747004BB4C6 /* Supabase */ = {
633-
isa = XCSwiftPackageProductDependency;
634-
package = BE3E1CD72D92D747004BB4C6 /* XCRemoteSwiftPackageReference "supabase-swift" */;
606+
package = BE6A04DD2DB128AA0054C6F9 /* XCRemoteSwiftPackageReference "supabase-swift" */;
635607
productName = Supabase;
636608
};
637609
/* End XCSwiftPackageProductDependency section */
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"images" : [
3+
{
4+
"filename" : "google-icon.svg",
5+
"idiom" : "universal",
6+
"scale" : "1x"
7+
},
8+
{
9+
"idiom" : "universal",
10+
"scale" : "2x"
11+
},
12+
{
13+
"idiom" : "universal",
14+
"scale" : "3x"
15+
}
16+
],
17+
"info" : {
18+
"author" : "xcode",
19+
"version" : 1
20+
}
21+
}
Loading

outvoice/Info.plist

+16-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,21 @@
22
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
33
<plist version="1.0">
44
<dict>
5-
<key>UIUserInterfaceStyle</key>
6-
<string>Light</string>
5+
<key>CFBundleURLTypes</key>
6+
<array>
7+
<dict>
8+
<key>CFBundleTypeRole</key>
9+
<string>Editor</string>
10+
<key>CFBundleURLIconFile</key>
11+
<string></string>
12+
<key>CFBundleURLName</key>
13+
<string>supabase auth</string>
14+
<key>CFBundleURLSchemes</key>
15+
<array>
16+
<string>com.outvoiceios</string>
17+
</array>
18+
</dict>
19+
<dict/>
20+
</array>
721
</dict>
822
</plist>

outvoice/Launch Screen.storyboard

+3-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
1818
<subviews>
1919
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" image="logo-svg" translatesAutoresizingMaskIntoConstraints="NO" id="yrd-DC-RFS">
20-
<rect key="frame" x="76" y="190" width="240" height="128"/>
20+
<rect key="frame" x="32" y="191" width="339" height="166"/>
2121
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
22+
<color key="tintColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
2223
</imageView>
2324
</subviews>
2425
<viewLayoutGuide key="safeArea" id="aNK-SP-zv9"/>
@@ -27,7 +28,7 @@
2728
</viewController>
2829
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
2930
</objects>
30-
<point key="canvasLocation" x="53" y="375"/>
31+
<point key="canvasLocation" x="52.671755725190835" y="374.64788732394368"/>
3132
</scene>
3233
</scenes>
3334
<resources>

outvoice/Services/AuthService.swift

+107-14
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ protocol AuthServiceProtocol {
66
var currentUser: User? { get }
77
var isAuthenticated: Bool { get }
88
func signIn(email: String, password: String) async throws -> User
9+
func signUp(email: String, password: String, firstName: String) async throws -> User
10+
func signInWithGoogle() async throws -> User
911
func signOut() async throws
1012
func getCurrentUser() async throws -> User?
1113
}
@@ -18,21 +20,34 @@ class AuthService: AuthServiceProtocol, ObservableObject {
1820
private let supabase: SupabaseClient?
1921

2022
init() {
21-
// Safe initialization with proper error handling
22-
if let supabaseURL = URL(string: "YOUR_SUPABASE_URL"),
23-
!supabaseURL.absoluteString.contains("YOUR_SUPABASE_URL") {
24-
self.supabase = SupabaseClient(
25-
supabaseURL: supabaseURL,
26-
supabaseKey: "YOUR_SUPABASE_KEY"
27-
)
28-
29-
// Only refresh session if we have a valid client
30-
Task {
31-
try? await refreshSession()
32-
}
33-
} else {
34-
// For previews or development, use nil client
23+
// Replace the hard‑coded string with a value from configuration/Info.plist in production.
24+
let supabaseKey = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Imp3cXZzc2VodG5ldnhtYWxqZmtoIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NDI5MDcxNjQsImV4cCI6MjA1ODQ4MzE2NH0.KXr7lKsLaWu82S5G39MOXIsqaSt-wcbqyCT8zhKDt2g"
25+
26+
guard let supabaseURL = URL(string: "https://jwqvssehtnevxmaljfkh.supabase.co") else {
27+
// For previews or development, use a nil client
28+
print("[AuthService] Invalid Supabase URL – client not initialized.")
3529
self.supabase = nil
30+
return
31+
}
32+
33+
// Provide a default redirect URL so that auth flows (sign‑up, magic links, OAuth, etc.)
34+
// always have a valid deep‑link to return to. This must correspond to a URL scheme that
35+
// is registered in Info.plist (see the `CFBundleURLSchemes` entry).
36+
let clientOptions = SupabaseClientOptions(
37+
auth: .init(
38+
redirectToURL: URL(string: "com.outvoiceios://login-callback")
39+
)
40+
)
41+
42+
self.supabase = SupabaseClient(
43+
supabaseURL: supabaseURL,
44+
supabaseKey: supabaseKey,
45+
options: clientOptions
46+
)
47+
48+
// Kick‑off a background refresh of any existing session.
49+
Task {
50+
try? await refreshSession()
3651
}
3752
}
3853

@@ -123,4 +138,82 @@ class AuthService: AuthServiceProtocol, ObservableObject {
123138
let userData = try decoder.decode(User.self, from: response.data)
124139
return userData
125140
}
141+
142+
func signUp(email: String, password: String, firstName: String) async throws -> User {
143+
// If Supabase client isn't configured (e.g. previews), return a mock user
144+
guard let supabase = supabase else {
145+
let mockUser = User(
146+
id: UUID(),
147+
email: email,
148+
firstName: firstName,
149+
lastName: "",
150+
createdAt: Date()
151+
)
152+
await MainActor.run {
153+
self.currentUser = mockUser
154+
}
155+
return mockUser
156+
}
157+
158+
// Attempt to sign up the user with Supabase GoTrue
159+
// We attach the first name as user metadata so it can be stored in the `profiles` table with the edge function/trigger
160+
let metadata: [String: AnyJSON] = [
161+
"first_name": .string(firstName)
162+
]
163+
let _ = try await supabase.auth.signUp(
164+
email: email,
165+
password: password,
166+
data: metadata
167+
)
168+
169+
// After sign‑up, Supabase automatically authenticates the session. Fetch the user profile just like sign‑in.
170+
guard let session = try? await supabase.auth.session else {
171+
throw NSError(domain: "AuthService", code: 2, userInfo: [NSLocalizedDescriptionKey: "Unable to retrieve session after sign‑up."])
172+
}
173+
174+
let user = try await fetchUserProfile(userId: session.user.id)
175+
await MainActor.run {
176+
self.currentUser = user
177+
}
178+
return user
179+
}
180+
181+
func signInWithGoogle() async throws -> User {
182+
guard let supabase = supabase else {
183+
// Return a mock Google user in preview
184+
let mockUser = User(
185+
id: UUID(),
186+
email: "google_user@example.com",
187+
firstName: "Google",
188+
lastName: "User",
189+
createdAt: Date()
190+
)
191+
await MainActor.run {
192+
self.currentUser = mockUser
193+
}
194+
return mockUser
195+
}
196+
197+
// Launch the OAuth sign‑in flow. The Supabase Swift SDK will handle the deep‑link back into the app automatically.
198+
let _ = try await supabase.auth.signInWithOAuth(provider: .google)
199+
200+
// After the OAuth flow completes, attempt to get the current session/user.
201+
guard let session = try? await supabase.auth.session else {
202+
throw NSError(domain: "AuthService", code: 3, userInfo: [NSLocalizedDescriptionKey: "Google sign‑in did not return a session."])
203+
}
204+
205+
let user = try await fetchUserProfile(userId: session.user.id)
206+
await MainActor.run {
207+
self.currentUser = user
208+
}
209+
return user
210+
}
211+
212+
// MARK: - Deep‑link handling
213+
/// Call this from the App's `onOpenURL` modifier so that Supabase can
214+
/// exchange the OAuth redirect code for a session.
215+
func handleDeepLink(_ url: URL) {
216+
guard let supabase = supabase else { return }
217+
Task { try? await supabase.auth.session(from: url) }
218+
}
126219
}

0 commit comments

Comments
 (0)