diff --git a/ArtisanConnect/api/notices.jsx b/ArtisanConnect/api/notices.jsx index 1153ea0..3a7bb9a 100644 --- a/ArtisanConnect/api/notices.jsx +++ b/ArtisanConnect/api/notices.jsx @@ -4,23 +4,19 @@ import { useAuthStore } from "@/store/authStore"; const API_URL = "https://hopp.zikor.pl/api/v1"; -//const API_URL = "http://10.0.2.2:8080/api/v1"; export async function listNotices() { const { token } = useAuthStore.getState(); const headers = token ? { Authorization: `Bearer ${token}` } : {}; - console.log(token); - - const response = await fetch(`${API_URL}/notices/get/all`, { - headers: headers, - }); - // console.log(response);r - const data = await response.json(); - if (!response.ok) { - throw new Error(response.toString()); - } - return data; + const response = await fetch(`${API_URL}/notices/get/all`, { + headers: headers + }); + const data = await response.json(); + if (!response.ok) { + throw new Error(response.toString()); + } + return data; } export async function getNoticeById(noticeId) { @@ -62,27 +58,32 @@ export async function getImageByNoticeId(noticeId) { const imageName = listResponse.data[0]; imageUrl = `${API_URL}/images/get/${imageName}`; - console.log(`Pobrano zdjęcie o nazwie: ${imageName}`); - - return imageUrl; - } catch (err) { - console.log(`Zdjęcie nie istnieje dla notice o id: ${noticeId}`); - imageUrl = "https://http.cat/404.jpg"; - return imageUrl; - } + return imageUrl; + } catch (err) { + console.log(`Zdjęcie nie istnieje dla notice o id: ${noticeId}`); + imageUrl = "https://http.cat/404.jpg"; + return imageUrl; + } } export async function getAllImagesByNoticeId(noticeId) { try { const listResponse = await axios.get(`${API_URL}/images/list/${noticeId}`); - if (listResponse.data && listResponse.data.length > 0) { - const imageUrls = listResponse.data.map( - (imageName) => `${API_URL}/images/get/${imageName}` - ); + if (listResponse.data && listResponse.data.length > 0) { + return listResponse.data.map(imageName => + `${API_URL}/images/get/${imageName}` + ); + } - // console.log(`Pobrano ${imageUrls.length} zdjęć dla ogłoszenia o id: ${noticeId}`); - return imageUrls; + return ["https://http.cat/404.jpg"]; + } catch (err) { + if(err.response.status === 404) { + console.info(`Ogłoszenie o id: ${noticeId} nie posiada zdjęć.`); + return ["https://http.cat/404.jpg"]; + } + console.warn(`Nie udało się pobrać listy zdjęć dla ogłoszenia o id: ${noticeId}`, err); + return ["https://http.cat/404.jpg"]; } // console.log(`Brak zdjęć dla ogłoszenia o id: ${noticeId}`); @@ -94,9 +95,7 @@ export async function getAllImagesByNoticeId(noticeId) { } export const uploadImage = async (noticeId, imageUri) => { - console.log(imageUri); - - const formData = new FormData(); + const formData = new FormData(); const filename = imageUri.split("/").pop(); diff --git a/ArtisanConnect/app.json b/ArtisanConnect/app.json index 71e8089..bbe332e 100644 --- a/ArtisanConnect/app.json +++ b/ArtisanConnect/app.json @@ -2,7 +2,7 @@ "expo": { "name": "ArtisanConnect", "slug": "ArtisanConnect", - "scheme": "Artisanconnect", + "scheme": "com.hamx.artisanconnect", "version": "1.0.0", "orientation": "portrait", "icon": "./assets/icon.png", @@ -14,13 +14,19 @@ "backgroundColor": "#ffffff" }, "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.hamx.artisanconnect" }, "android": { "adaptiveIcon": { "foregroundImage": "./assets/adaptive-icon.png", "backgroundColor": "#ffffff" - } + }, + "permissions": [ + "android.permission.RECORD_AUDIO", + "android.permission.CAMERA" + ], + "package": "com.hamx.artisanconnect" }, "web": { "favicon": "./assets/favicon.png" @@ -40,6 +46,12 @@ } ], "expo-web-browser" - ] + ], + "extra": { + "router": {}, + "eas": { + "projectId": "7a0d8bc8-938f-4d2a-babb-945faee13429" + } + } } } diff --git a/ArtisanConnect/app/(auth)/login.jsx b/ArtisanConnect/app/(auth)/login.jsx index a494ec1..ec33970 100644 --- a/ArtisanConnect/app/(auth)/login.jsx +++ b/ArtisanConnect/app/(auth)/login.jsx @@ -1,5 +1,5 @@ -import React, {useState} from 'react'; -import {StyleSheet, ActivityIndicator, SafeAreaView, View} from 'react-native'; +import React, {useEffect, useState} from 'react'; +import {StyleSheet, ActivityIndicator, SafeAreaView, View, Platform} from 'react-native'; import {useAuthStore} from '@/store/authStore'; import {useRouter, Link} from 'expo-router'; @@ -15,12 +15,37 @@ import {ArrowRightIcon} from "@/components/ui/icon" import {Divider} from '@/components/ui/divider'; import {Ionicons} from "@expo/vector-icons"; +import * as WebBrowser from 'expo-web-browser'; +import * as Google from "expo-auth-session/providers/google"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import {makeRedirectUri} from "expo-auth-session"; + +import Constants from 'expo-constants'; + +WebBrowser.maybeCompleteAuthSession(); + +// client_id ios 936418008320-ohefdfcebd41f6oa2o8phh1mgj9s49sl.apps.googleusercontent.com +// android 936418008320-d8dfjph5e4r28fcm1rbdfbh5phmbg03d.apps.googleusercontent.com + export default function Login() { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); - const {signIn, isLoading} = useAuthStore(); + const {signIn, isLoading, signInWithGoogle} = useAuthStore(); const router = useRouter(); + const [request, response, promptAsync] = Google.useAuthRequest({ + androidClientId: "936418008320-d8dfjph5e4r28fcm1rbdfbh5phmbg03d.apps.googleusercontent.com", + iosClientId: "936418008320-ohefdfcebd41f6oa2o8phh1mgj9s49sl.apps.googleusercontent.com", + webClientId: "936418008320-btdngtlfnjac1p67guje72m9el5q59a7.apps.googleusercontent.com", + redirectUri: + Platform.OS === 'android' + ? makeRedirectUri({ + scheme: Constants.expoConfig.android.package, + path: '/', + }) + : undefined, + }) + const handleInternalLogin = async () => { if (!email || !password) { alert('Proszę wprowadzić email i hasło.'); @@ -36,6 +61,47 @@ export default function Login() { } } + useEffect(() => { + handleGoogleLogin(); + }, [response]); + + const handleGoogleLogin = async () => { + // const user = await AsyncStorage.getItem("@user"); + let user = null; + if (!user) { + if(response.type === "success") { + user = await getUserInfo(response.authentication.accessToken) + await signInWithGoogle(response.authentication.accessToken); + alert(`Zalogowano jako ${user.email}`); + } + + } else { + console.info("Pobrano użytkownika z AsyncStorage:", JSON.parse(user)); + alert(`Zalogowano jako ${user.email}`); + } + }; + + const getUserInfo = async (token) => { + if(!token) { + return + } + try { + const response = await fetch("https://www.googleapis.com/userinfo/v2/me", + { + headers: { + Authorization: `Bearer ${token}` + }, + } + ); + const user = await response.json(); + await AsyncStorage.setItem("@user", JSON.stringify(user)); + return user; + } catch (error) { + console.error("Błąd podczas pobierania informacji o użytkowniku:", error); + throw error; + } + } + if (isLoading) { return ( @@ -82,7 +148,7 @@ export default function Login() { - diff --git a/ArtisanConnect/eas.json b/ArtisanConnect/eas.json new file mode 100644 index 0000000..a614f19 --- /dev/null +++ b/ArtisanConnect/eas.json @@ -0,0 +1,21 @@ +{ + "cli": { + "version": ">= 16.8.0", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal" + }, + "preview": { + "distribution": "internal" + }, + "production": { + "autoIncrement": true + } + }, + "submit": { + "production": {} + } +} diff --git a/ArtisanConnect/package.json b/ArtisanConnect/package.json index 60be00d..7578a23 100644 --- a/ArtisanConnect/package.json +++ b/ArtisanConnect/package.json @@ -39,7 +39,7 @@ "expo": "^53.0.0", "expo-auth-session": "~6.1.5", "expo-camera": "~16.1.6", - "expo-constants": "~17.1.5", + "expo-constants": "~17.1.6", "expo-image-picker": "~16.1.4", "expo-linking": "~7.1.4", "expo-router": "~5.0.5", @@ -57,11 +57,12 @@ "react-native-keyboard-aware-scroll-view": "^0.9.5", "react-native-reanimated": "~3.17.4", "react-native-safe-area-context": "5.4.0", - "react-native-screens": "~4.10.0", + "react-native-screens": "~4.11.1", "react-native-svg": "15.11.2", "react-native-web": "~0.20.0", "tailwindcss": "^3.4.17", - "zustand": "^5.0.3" + "zustand": "^5.0.3", + "expo-crypto": "~14.1.4" }, "devDependencies": { "@babel/core": "^7.20.0", diff --git a/ArtisanConnect/store/authStore.jsx b/ArtisanConnect/store/authStore.jsx index db4a099..4d81fa3 100644 --- a/ArtisanConnect/store/authStore.jsx +++ b/ArtisanConnect/store/authStore.jsx @@ -7,126 +7,113 @@ const API_URL = "https://hopp.zikor.pl/api/v1"; export const useAuthStore = create( persist( - (set, get) => { - if (!axios.interceptors.response.handlers.length) { - axios.interceptors.response.use( - (response) => response, - (error) => { - if ( - (error.response && error.response.status === 401) || - error.response.status === 403 - ) { - set({ user: null, token: null, isLoading: false }); - delete axios.defaults.headers.common["Authorization"]; + (set) => ({ + user_id: null, + token: null, + isLoading: false, + error: null, + + signIn: async (email, password) => { + set({ isLoading: true, error: null }); + try { + const response = await axios.post(`${API_URL}/auth/login`, { + email, + password, + }); + + const user_id = response.data.user_id; + const token = response.data.token; + set({ user_id: user_id, token: token, isLoading: false }); + + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + } catch (error) { + set({ + error: error.response?.data?.message || error.message, + isLoading: false, + }); + throw error; + } + }, + + signUp: async (userData) => { + set({ isLoading: true, error: null }); + try { + const response = await axios.post( + `${API_URL}/auth/register`, + userData, + { + headers: { "Content-Type": "application/json" }, } - return Promise.reject(error); - } - ); - } + ); - return { - user: null, - token: null, - isLoading: false, - error: null, + const user_id = response.data.user_id; + const token = response.data.token; + set({ user_id: user_id, token: token, isLoading: false }); - signIn: async (email, password) => { - set({ isLoading: true, error: null }); - try { - const response = await axios.post(`${API_URL}/auth/login`, { - email, - password, - }); + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + } catch (error) { + set({ + error: error.response?.data?.message || error.message, + isLoading: false, + }); + throw error; + } + }, - const user = response.data.user; - const token = response.data.token; - set({ user, token, isLoading: false }); - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - } catch (error) { - set({ - error: error.response?.data?.message || error.message, - isLoading: false, - }); - throw error; - } - }, + signInWithGoogle: async (googleToken) => { + set({ isLoading: true, error: null }); + try { + const response = await axios.post( + `${API_URL}/auth/google`, + { googleToken: googleToken }, + { + headers: { "Content-Type": "application/json" }, + } + ); + const user_id = response.data.user_id; + const token = response.data.token; + set({ user_id: user_id, token: token, isLoading: false }); - signUp: async (userData) => { - set({ isLoading: true, error: null }); - try { - const response = await axios.post( - `${API_URL}/auth/register`, - userData, - { - headers: { "Content-Type": "application/json" }, - } - ); + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; + } catch (error) { + set({ + error: error.response?.data?.message || error.message, + isLoading: false, + }); + throw error; + } + }, - const user = response.data.user; - const token = response.data.token; - set({ user, token, isLoading: false }); - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - return user; - } catch (error) { - set({ - error: error.response?.data?.message || error.message, - isLoading: false, - }); - throw error; - } - }, + signOut: async () => { + try { + await axios.post(`${API_URL}/auth/logout`); + } catch (error) { + console.error("Logout error:", error); + } finally { + delete axios.defaults.headers.common["Authorization"]; + set({ user_id: null, token: null }); + } + }, - signInWithGoogle: async (googleToken) => { - set({ isLoading: true, error: null }); - try { - const response = await axios.post(`${API_URL}/auth/google`, { - token: googleToken, - }); + checkAuth: async () => { + const { token } = useAuthStore.getState(); + if (!token) return null; - const { user, token } = response.data; - set({ user, token, isLoading: false }); - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - return user; - } catch (error) { - set({ - error: error.response?.data?.message || error.message, - isLoading: false, - }); - throw error; - } - }, + set({ isLoading: true }); + try { + axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - signOut: async () => { - try { - const { token } = get(); - const headers = token ? { Authorization: `Bearer ${token}` } : {}; - await axios.post(`${API_URL}/auth/logout`, {}, { headers }); - } catch (error) { - // console.error("Logout error:", error); - } finally { - delete axios.defaults.headers.common["Authorization"]; - set({ user: null, token: null, isLoading: false }); - } - }, + const response = await axios.get(`${API_URL}/auth/me`); - checkAuth: async () => { - const { token } = get(); - if (!token) return null; - - set({ isLoading: true }); - try { - axios.defaults.headers.common["Authorization"] = `Bearer ${token}`; - const response = await axios.get(`${API_URL}/auth/me`); - set({ user: response.data, isLoading: false }); - return response.data; - } catch (error) { - delete axios.defaults.headers.common["Authorization"]; - set({ user: null, token: null, isLoading: false }); - return null; - } - }, - }; - }, + set({ user_id: response.data, isLoading: false }); + return response.data; + } catch (error) { + delete axios.defaults.headers.common["Authorization"]; + set({ user_id: null, token: null, isLoading: false }); + return null; + } + }, + }), { name: "auth-storage", storage: createJSONStorage(() => AsyncStorage),