diff --git a/shatter-backend/src/controllers/auth_controller.ts b/shatter-backend/src/controllers/auth_controller.ts index d1c3f4c..7aa3c11 100644 --- a/shatter-backend/src/controllers/auth_controller.ts +++ b/shatter-backend/src/controllers/auth_controller.ts @@ -199,10 +199,8 @@ export const linkedinAuth = async (req: Request, res: Response) => { // Generate CSRF protection state token const state = crypto.randomBytes(16).toString('hex'); - const platform = req.query.platform === 'mobile' ? 'mobile' : 'web'; - // Encode state as JWT with 5-minute expiration (stateless validation) - const stateToken = jwt.sign({ state, platform }, JWT_SECRET, { expiresIn: '5m' }); + const stateToken = jwt.sign({ state }, JWT_SECRET, { expiresIn: '5m' }); // Build LinkedIn authorization URL and redirect const authUrl = getLinkedInAuthUrl(stateToken); @@ -243,11 +241,9 @@ export const linkedinLink = async (req: Request, res: Response) => { }); } - const platform = req.query.platform === 'mobile' ? 'mobile' : 'web'; - // Encode linking context into the state JWT (signed, tamper-proof) const stateToken = jwt.sign( - { linking: true, userId: user._id.toString(), platform }, + { linking: true, userId: user._id.toString() }, JWT_SECRET, { expiresIn: '5m' } ); @@ -273,18 +269,22 @@ export const linkedinCallback = async (req: Request, res: Response) => { error?: string; }; - // Default to web frontend; mobile gets resolved after state verification below - let frontendUrl = process.env.FRONTEND_URL || 'http://localhost:19006'; + const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:19006'; - // State must always be present (even on cancel, LinkedIn returns it). Verify it first - // so we can resolve the correct redirect target before any branching. - if (!state) { - return res.status(400).json({ error: 'Missing state parameter' }); + // Handle user denial + if (oauthError === 'user_cancelled_authorize') { + return res.redirect(`${frontendUrl}/auth/error?message=Authorization cancelled`); } - let statePayload: { linking?: boolean; userId?: string; platform?: 'mobile' | 'web' }; + // Validate required parameters + if (!code || !state) { + return res.status(400).json({ error: 'Missing code or state parameter' }); + } + + // Verify state token (CSRF protection) and extract payload + let statePayload: { linking?: boolean; userId?: string }; try { - statePayload = jwt.verify(state, JWT_SECRET) as { linking?: boolean; userId?: string; platform?: 'mobile' | 'web' }; + statePayload = jwt.verify(state, JWT_SECRET) as { linking?: boolean; userId?: string }; } catch (e: any) { console.error('LinkedIn state verify failed:', { name: e?.name, @@ -294,21 +294,6 @@ export const linkedinCallback = async (req: Request, res: Response) => { return res.status(401).json({ error: 'Invalid state parameter' }); } - // Resolve redirect target based on the originating client encoded in the signed state JWT - if (statePayload.platform === 'mobile') { - frontendUrl = 'shattermobile://auth'; - } - - // Handle user denial - if (oauthError === 'user_cancelled_authorize') { - return res.redirect(`${frontendUrl}/auth/error?message=Authorization cancelled`); - } - - // Validate code is present for the success path - if (!code) { - return res.status(400).json({ error: 'Missing code parameter' }); - } - // Exchange code for access token const accessToken = await getLinkedInAccessToken(code); @@ -419,19 +404,7 @@ export const linkedinCallback = async (req: Request, res: Response) => { } catch (error: any) { console.error('LinkedIn callback error:', error); - // Best-effort attempt to recover platform from state for the error redirect. - // If state is missing/invalid we fall back to the web URL. - let frontendUrl = process.env.FRONTEND_URL || 'http://localhost:19006'; - try { - const stateParam = (req.query as { state?: string }).state; - if (stateParam) { - const payload = jwt.verify(stateParam, JWT_SECRET) as { platform?: 'mobile' | 'web' }; - if (payload.platform === 'mobile') frontendUrl = 'shattermobile://auth'; - } - } catch { - // ignore — already defaulting to web - } - + const frontendUrl = process.env.FRONTEND_URL || 'http://localhost:19006'; if (error.message?.includes('LinkedIn')) { return res.redirect(`${frontendUrl}/auth/error?message=LinkedIn authentication failed`); } diff --git a/shatter-mobile/app.json b/shatter-mobile/app.json index 2b896aa..a10bf72 100644 --- a/shatter-mobile/app.json +++ b/shatter-mobile/app.json @@ -1,67 +1,58 @@ { - "expo": { - "name": "Shatter", - "slug": "shatter-mobile", - "version": "1.0.0", - "orientation": "portrait", - "icon": "./assets/images/icon.png", - "scheme": "shattermobile", - "userInterfaceStyle": "automatic", - "newArchEnabled": true, - "ios": { - "supportsTablet": true, - "bundleIdentifier": "com.shatter.shattermobile", - "infoPlist": { - "ITSAppUsesNonExemptEncryption": false - } - }, - "android": { - "adaptiveIcon": { - "backgroundColor": "#E6F4FE", - "foregroundImage": "./assets/images/android-icon-foreground.png", - "backgroundImage": "./assets/images/android-icon-background.png", - "monochromeImage": "./assets/images/android-icon-monochrome.png" - }, - "edgeToEdgeEnabled": true, - "predictiveBackGestureEnabled": false, - "permissions": [ - "android.permission.CAMERA" - ], - "package": "com.anonymous.shattermobile" - }, - "web": { - "output": "static", - "favicon": "./assets/images/favicon.png" - }, - "plugins": [ - "expo-router", - [ - "expo-camera", - { - "cameraPermission": "Allow Shatter to access your camera to scan QR codes", - "microphonePermission": false, - "recordAudioAndroid": false - } - ], - [ - "expo-splash-screen", - { - "image": "./assets/images/splash-icon.png", - "imageWidth": 200, - "resizeMode": "contain", - "backgroundColor": "#2C3B5E" - } - ] - ], - "experiments": { - "typedRoutes": true, - "reactCompiler": true - }, - "extra": { - "router": {}, - "eas": { - "projectId": "77d528c2-849b-42ac-819b-0e65da530be2" - } - } - } + "expo": { + "name": "shatter-mobile", + "slug": "shatter-mobile", + "version": "1.0.0", + "orientation": "portrait", + "icon": "./assets/images/icon.png", + "scheme": "shattermobile", + "userInterfaceStyle": "automatic", + "newArchEnabled": true, + "ios": { + "supportsTablet": true + }, + "android": { + "adaptiveIcon": { + "backgroundColor": "#E6F4FE", + "foregroundImage": "./assets/images/android-icon-foreground.png", + "backgroundImage": "./assets/images/android-icon-background.png", + "monochromeImage": "./assets/images/android-icon-monochrome.png" + }, + "edgeToEdgeEnabled": true, + "predictiveBackGestureEnabled": false, + "permissions": [ + "android.permission.CAMERA", + "android.permission.RECORD_AUDIO", + "android.permission.CAMERA", + "android.permission.RECORD_AUDIO" + ], + "package": "com.anonymous.shattermobile" + }, + "web": { + "output": "static", + "favicon": "./assets/images/favicon.png" + }, + "plugins": [ + "expo-router", + [ + "expo-camera", + { + "cameraPermission": "Allow Shatter to access your camera to scan QR codes" + } + ], + [ + "expo-splash-screen", + { + "image": "./assets/images/splash-icon.png", + "imageWidth": 200, + "resizeMode": "contain", + "backgroundColor": "#2C3B5E" + } + ] + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + } + } } diff --git a/shatter-mobile/app/(tabs)/JoinEventPage.tsx b/shatter-mobile/app/(tabs)/JoinEventPage.tsx index 2cb269d..40ee5fc 100644 --- a/shatter-mobile/app/(tabs)/JoinEventPage.tsx +++ b/shatter-mobile/app/(tabs)/JoinEventPage.tsx @@ -6,12 +6,12 @@ import { useState } from "react"; import { ActivityIndicator, ImageBackground, + KeyboardAvoidingView, + Platform, Text, TextInput, TouchableOpacity, View, - KeyboardAvoidingView, - Platform, } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context"; import { useAuth } from "../../src/components/context/AuthContext"; @@ -51,17 +51,19 @@ export default function JoinEventPage() { style={styles.background} resizeMode="cover" > - + Start Shattering + + Hey {user?.name || "there"}, + - Hey {user?.name || "there"}, Ready to Start Shattering Some - Boundaries? + Ready to Start Shattering Some Boundaries? diff --git a/shatter-mobile/app/auth/callback.tsx b/shatter-mobile/app/auth/callback.tsx index bcfd73f..d270f78 100644 --- a/shatter-mobile/app/auth/callback.tsx +++ b/shatter-mobile/app/auth/callback.tsx @@ -11,7 +11,7 @@ import { ActivityIndicator, Text, View } from "react-native"; export default function AuthCallback() { const { code } = useLocalSearchParams<{ code: string }>(); - const { authenticate, authStorage } = useAuth(); + const { authenticate } = useAuth(); const [error, setError] = useState(""); useEffect(() => { @@ -28,11 +28,15 @@ export default function AuthCallback() { // Fetch full user profile using returned userId + token const userData = await userFetch(response.userId, response.token); + const existingSocialLinks = userData.user.socialLinks ?? {}; + const linkedinUrl = existingSocialLinks.linkedin + ?? `https://www.linkedin.com/in/${userData.user.name.toLowerCase().replace(/\s+/g, "-")}/`; + const user: User = { _id: response.userId, name: userData.user.name, email: userData.user.email, - socialLinks: userData.user.socialLinks ?? {}, + socialLinks: { ...existingSocialLinks, linkedin: linkedinUrl }, profilePhoto: userData.user.profilePhoto, isGuest: false, }; @@ -40,9 +44,8 @@ export default function AuthCallback() { // Store user + JWT in auth context await authenticate(user, response.token, false); - // Update stored user with LinkedIn data - const token = authStorage.accessToken; - userUpdate(response.userId, user, token); + // Persist LinkedIn URL to backend using the token from the exchange response + userUpdate(response.userId, user, response.token); router.replace("/JoinEventPage"); } catch (err) { setError((err as Error).message || "Authentication failed."); diff --git a/shatter-mobile/assets/images/favicon.png b/shatter-mobile/assets/images/favicon.png deleted file mode 100644 index e8d5ce8..0000000 Binary files a/shatter-mobile/assets/images/favicon.png and /dev/null differ diff --git a/shatter-mobile/eas.json b/shatter-mobile/eas.json deleted file mode 100644 index 50f1308..0000000 --- a/shatter-mobile/eas.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "cli": { - "version": ">= 18.9.1", - "appVersionSource": "remote" - }, - "build": { - "development": { - "developmentClient": true, - "distribution": "internal" - }, - "preview": { - "distribution": "internal" - }, - "production": { - "autoIncrement": true - } - }, - "submit": { - "production": {} - } -} diff --git a/shatter-mobile/package-lock.json b/shatter-mobile/package-lock.json index b4505de..78ca5cf 100644 --- a/shatter-mobile/package-lock.json +++ b/shatter-mobile/package-lock.json @@ -94,6 +94,7 @@ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.5.tgz", "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -3622,6 +3623,7 @@ "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.1.31.tgz", "integrity": "sha512-+YCUwtfDgsux59Q0LDHc3Zid9ih93ecUCFWZOH6/+eNoUGnWx77wjS6ZfvBO/7E+EiIup11IVShDzCHR4of8hw==", "license": "MIT", + "peer": true, "dependencies": { "@react-navigation/core": "^7.15.1", "escape-string-regexp": "^4.0.0", @@ -3826,6 +3828,7 @@ "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "csstype": "^3.0.2" } @@ -3897,6 +3900,7 @@ "integrity": "sha512-BnOroVl1SgrPLywqxyqdJ4l3S2MsKVLDVxZvjI1Eoe8ev2r3kGDo+PcMihNmDE+6/KjkTubSJnmqGZZjQSBq/g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.46.2", "@typescript-eslint/types": "8.46.2", @@ -4459,6 +4463,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -5167,6 +5172,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -6285,6 +6291,7 @@ "integrity": "sha512-t5aPOpmtJcZcz5UJyY2GbvpDlsK5E8JqRqoKtfiKE3cNh437KIqfJr3A3AKf5k64NPx6d0G3dno6XDY05PqPtw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -6481,6 +6488,7 @@ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@rtsao/scc": "^1.1.0", "array-includes": "^3.1.9", @@ -6713,6 +6721,7 @@ "resolved": "https://registry.npmjs.org/expo/-/expo-54.0.33.tgz", "integrity": "sha512-3yOEfAKqo+gqHcV8vKcnq0uA5zxlohnhA3fu4G43likN8ct5ZZ3LjAh9wDdKteEkoad3tFPvwxmXW711S5OHUw==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.20.0", "@expo/cli": "54.0.23", @@ -6800,6 +6809,7 @@ "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.13.tgz", "integrity": "sha512-FnZn12E1dRYKDHlAdIyNFhBurKTS3F9CrfrBDJI5m3D7U17KBHMQ6JEfYlSj7LG7t+Ulr+IKaj58L1k5gBwTcQ==", "license": "MIT", + "peer": true, "dependencies": { "@expo/config": "~12.0.13", "@expo/env": "~2.0.8" @@ -6824,6 +6834,7 @@ "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-14.0.11.tgz", "integrity": "sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==", "license": "MIT", + "peer": true, "dependencies": { "fontfaceobserver": "^2.1.0" }, @@ -6874,6 +6885,7 @@ "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-8.0.11.tgz", "integrity": "sha512-+VSaNL5om3kOp/SSKO5qe6cFgfSIWnnQDSbA7XLs3ECkYzXRquk5unxNS3pg7eK5kNUmQ4kgLI7MhTggAEUBLA==", "license": "MIT", + "peer": true, "dependencies": { "expo-constants": "~18.0.12", "invariant": "^2.2.4" @@ -10831,6 +10843,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz", "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -10850,6 +10863,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz", "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", "license": "MIT", + "peer": true, "dependencies": { "scheduler": "^0.26.0" }, @@ -10886,6 +10900,7 @@ "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.81.5.tgz", "integrity": "sha512-1w+/oSjEXZjMqsIvmkCRsOc8UBYv163bTWKTI8+1mxztvQPhCRYGTvZ/PL1w16xXHneIj/SLGfxWg2GWN2uexw==", "license": "MIT", + "peer": true, "dependencies": { "@jest/create-cache-key-function": "^29.7.0", "@react-native/assets-registry": "0.81.5", @@ -10943,6 +10958,7 @@ "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.28.0.tgz", "integrity": "sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==", "license": "MIT", + "peer": true, "dependencies": { "@egjs/hammerjs": "^2.0.17", "hoist-non-react-statics": "^3.3.0", @@ -10968,6 +10984,7 @@ "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.1.3.tgz", "integrity": "sha512-GP8wsi1u3nqvC1fMab/m8gfFwFyldawElCcUSBJQgfrXeLmsPPUOpDw44lbLeCpcwUuLa05WTVePdTEwCLTUZg==", "license": "MIT", + "peer": true, "dependencies": { "react-native-is-edge-to-edge": "^1.2.1", "semver": "7.7.2" @@ -10996,6 +11013,7 @@ "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", + "peer": true, "peerDependencies": { "react": "*", "react-native": "*" @@ -11006,6 +11024,7 @@ "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.16.0.tgz", "integrity": "sha512-yIAyh7F/9uWkOzCi1/2FqvNvK6Wb9Y1+Kzn16SuGfN9YFJDTbwlzGRvePCNTOX0recpLQF3kc2FmvMUhyTCH1Q==", "license": "MIT", + "peer": true, "dependencies": { "react-freeze": "^1.0.0", "react-native-is-edge-to-edge": "^1.2.1", @@ -11036,6 +11055,7 @@ "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==", "license": "MIT", + "peer": true, "dependencies": { "@babel/runtime": "^7.18.6", "@react-native/normalize-colors": "^0.74.1", @@ -11068,6 +11088,7 @@ "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.5.1.tgz", "integrity": "sha512-lJG6Uk9YuojjEX/tQrCbcbmpdLCSFxDK1rJlkDhgqkVi1KZzG7cdcBFQRqyNOOzR9Y0CXNuldmtWTGOyM0k0+w==", "license": "MIT", + "peer": true, "dependencies": { "@babel/plugin-transform-arrow-functions": "^7.0.0-0", "@babel/plugin-transform-class-properties": "^7.0.0-0", @@ -11178,6 +11199,7 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz", "integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10.0" } @@ -12530,6 +12552,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -12742,6 +12765,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/shatter-mobile/src/components/context/PusherClient.tsx b/shatter-mobile/src/components/context/PusherClient.tsx index fc51e4c..e910ecc 100644 --- a/shatter-mobile/src/components/context/PusherClient.tsx +++ b/shatter-mobile/src/components/context/PusherClient.tsx @@ -2,7 +2,7 @@ const { Pusher } = require('pusher-js/react-native'); let pusher: any = null; -const API_KEY = process.env.EXPO_PUBLIC_PUSHER_KEY!; +const API_KEY = process.env.EXPO_PUBLIC_PUSHER_KEY!; const API_CLUSTER = process.env.EXPO_PUBLIC_PUSHER_CLUSTER!; export const getPusherClient = () => { diff --git a/shatter-mobile/src/components/login-signup/LoginForm.tsx b/shatter-mobile/src/components/login-signup/LoginForm.tsx index db2b121..9822282 100644 --- a/shatter-mobile/src/components/login-signup/LoginForm.tsx +++ b/shatter-mobile/src/components/login-signup/LoginForm.tsx @@ -1,8 +1,8 @@ //called by Profile.tsx for logging in import { User } from "@/src/interfaces/User"; -import { loginWithLinkedIn } from "@/src/services/linkedin_auth.service"; import { userFetch, userLogin } from "@/src/services/user.service"; import { router, Stack } from "expo-router"; +import * as WebBrowser from "expo-web-browser"; import { useState } from "react"; import { ActivityIndicator, @@ -27,21 +27,9 @@ export default function LoginForm() { const [err, setError] = useState(""); const handleLinkedIn = async () => { - setError(""); - setLoading(true); - try { - const result = await loginWithLinkedIn(); - if (!result) return; - await authenticate(result.user, result.token, false); - router.push("/JoinEventPage"); - } catch (err) { - console.log("LinkedIn login failed:", err); - setError( - (err as Error).message || "LinkedIn login failed. Please try again.", - ); - } finally { - setLoading(false); - } + await WebBrowser.openBrowserAsync( + `${process.env.EXPO_PUBLIC_API_BASE}/api/auth/linkedin`, + ); }; const handleLogin = async () => { diff --git a/shatter-mobile/src/components/login-signup/SignupForm.tsx b/shatter-mobile/src/components/login-signup/SignupForm.tsx index f389df7..eec029d 100644 --- a/shatter-mobile/src/components/login-signup/SignupForm.tsx +++ b/shatter-mobile/src/components/login-signup/SignupForm.tsx @@ -1,8 +1,10 @@ //called by Profile.tsx for signing up import { User } from "@/src/interfaces/User"; -import { loginWithLinkedIn } from "@/src/services/linkedin_auth.service"; +import { SocialLinks } from "@/src/interfaces/User"; import { userSignup, userUpdate } from "@/src/services/user.service"; +import { FontAwesome, Feather } from "@expo/vector-icons"; import { router, Stack } from "expo-router"; +import * as WebBrowser from "expo-web-browser"; import { useState } from "react"; import { ActivityIndicator, @@ -18,6 +20,7 @@ import { import { SafeAreaView } from "react-native-safe-area-context"; import { SignUpFormStyling as styles } from "../../styling/SignUpFormStyling.styles"; import { useAuth } from "../context/AuthContext"; +import { colors } from "@/src/styling/constants"; export default function SignUpForm() { const { authenticate } = useAuth(); @@ -26,23 +29,15 @@ export default function SignUpForm() { const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); const [err, setError] = useState(""); + const [selectedType, setSelectedType] = useState<"linkedin" | "github" | "other" | null>(null); + const [linkedin, setLinkedin] = useState(""); + const [github, setGithub] = useState(""); + const [other, setOther] = useState(""); const handleLinkedIn = async () => { - setError(""); - setLoading(true); - try { - const result = await loginWithLinkedIn(); - if (!result) return; - await authenticate(result.user, result.token, false); - router.push("/(tabs)/JoinEventPage"); - } catch (err) { - console.log("LinkedIn signup failed:", err); - setError( - (err as Error).message || "LinkedIn signup failed. Please try again.", - ); - } finally { - setLoading(false); - } + await WebBrowser.openBrowserAsync( + `${process.env.EXPO_PUBLIC_API_BASE}/api/auth/linkedin`, + ); }; const handleSignup = async () => { @@ -88,11 +83,16 @@ export default function SignUpForm() { setError("User info could not be created. Please try again later."); } + const socialLinks: SocialLinks = {}; + if (linkedin.trim()) socialLinks.linkedin = linkedin.trim(); + if (github.trim()) socialLinks.github = github.trim(); + if (other.trim()) socialLinks.other = [{ label: "Contact Link", url: other.trim() }]; + const user: User = { _id: userResponse.userId, name, email, - socialLinks: {}, + socialLinks, profilePhoto: profilePhoto, isGuest: false, }; @@ -159,6 +159,52 @@ export default function SignUpForm() { placeholderTextColor="#888" /> + Contact Link + + setSelectedType("linkedin")} + style={{ padding: 12, borderRadius: 12, backgroundColor: selectedType === "linkedin" ? "#0A66C2" : colors.lightGrey2, flex: 1, marginRight: 8, alignItems: "center" }} + > + + + setSelectedType("github")} + style={{ padding: 12, borderRadius: 12, backgroundColor: selectedType === "github" ? "#24292e" : colors.lightGrey2, flex: 1, marginRight: 8, alignItems: "center" }} + > + + + setSelectedType("other")} + style={{ padding: 12, borderRadius: 12, backgroundColor: selectedType === "other" ? "#6c63ff" : colors.lightGrey2, flex: 1, alignItems: "center" }} + > + + + + + {selectedType && ( + { + if (selectedType === "linkedin") setLinkedin(text); + else if (selectedType === "github") setGithub(text); + else setOther(text); + }} + autoCapitalize="none" + keyboardType="url" + /> + )} + + + Select a platform above, then enter your profile link. + + (height * percent) / 100; export const JoinEventStyling = StyleSheet.create({ background: { - flex: 1, - width: "100%", - height: "100%", + width, + height, backgroundColor: colors.lightGrey, }, safe: { flex: 1, flexDirection: "column", - alignItems: "center", }, header: { - height: vh(20), + height: vh(28), justifyContent: "center", alignItems: "center", marginTop: 5, + paddingHorizontal: 20, }, pageTitle: { fontSize: vw(8), @@ -31,31 +30,39 @@ export const JoinEventStyling = StyleSheet.create({ color: "#A8C8E8", textAlign: "center", }, - subtitle: { + subtitleName: { marginTop: 6, - fontSize: vw(4), + fontSize: vw(4.2), color: "#ffffff", - opacity: 0.9, + fontWeight: "700", letterSpacing: 1, textAlign: "center", - paddingHorizontal: 10, + }, + subtitle: { + marginTop: 2, + fontSize: vw(3.8), + color: "#ffffff", + opacity: 0.85, + letterSpacing: 0.5, + textAlign: "center", }, container: { position: "absolute", - top: vh(17), - width: "100%", + top: vh(26), + left: 0, + right: 0, bottom: -50, backgroundColor: colors.lightGrey, - borderTopLeftRadius: 12, - borderTopRightRadius: 12, - padding: 20, + borderTopLeftRadius: 20, + borderTopRightRadius: 20, + padding: 16, }, section: { backgroundColor: colors.white, borderRadius: 10, - padding: 15, - marginBottom: 20, + padding: 12, + marginBottom: 12, }, label: { @@ -127,7 +134,7 @@ export const JoinEventStyling = StyleSheet.create({ fontSize: 16, }, divider: { - marginVertical: 15, + marginVertical: 8, alignItems: "center", }, dividerText: { diff --git a/shatter-mobile/src/styling/SignUpFormStyling.styles.ts b/shatter-mobile/src/styling/SignUpFormStyling.styles.ts index 872ee9e..77545f6 100644 --- a/shatter-mobile/src/styling/SignUpFormStyling.styles.ts +++ b/shatter-mobile/src/styling/SignUpFormStyling.styles.ts @@ -11,7 +11,7 @@ export const SignUpFormStyling = StyleSheet.create({ background: { flex: 1, width: "100%", - height: "50%", + height: "100%", }, safe: { flex: 1,