diff --git a/ArtisanConnect/api/auth.jsx b/ArtisanConnect/api/auth.jsx
new file mode 100644
index 0000000..e69de29
diff --git a/ArtisanConnect/api/notices.jsx b/ArtisanConnect/api/notices.jsx
index f9ef8b0..42776df 100644
--- a/ArtisanConnect/api/notices.jsx
+++ b/ArtisanConnect/api/notices.jsx
@@ -1,12 +1,21 @@
import axios from "axios";
import FormData from 'form-data'
+import {useAuthStore} from "@/store/authStore";
-const API_URL = "https://testowe.zikor.pl/api/v1";
+// const API_URL = "https://testowe.zikor.pl/api/v1";
-// const API_URL = "http://172.20.10.2:8080/api/v1";
+const API_URL = "http://10.0.2.2:8080/api/v1";
export async function listNotices() {
- const response = await fetch(`${API_URL}/notices/get/all`);
+ 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);
const data = await response.json();
if (!response.ok) {
throw new Error(response.toString());
@@ -17,11 +26,11 @@ export async function listNotices() {
export async function getNoticeById(noticeId) {
const response = await fetch(`${API_URL}/notices/get/${noticeId}`);
- const data = await response.json();
- if (!response.ok) {
- throw new Error("Error");
- }
- return data;
+ const data = await response.json();
+ if (!response.ok) {
+ throw new Error("Error");
+ }
+ return data;
}
export async function createNotice(notice) {
diff --git a/ArtisanConnect/app.json b/ArtisanConnect/app.json
index 3f7b0a0..71e8089 100644
--- a/ArtisanConnect/app.json
+++ b/ArtisanConnect/app.json
@@ -38,7 +38,8 @@
{
"cameraPermission": "Please allow $(PRODUCT_NAME) to access your camera"
}
- ]
+ ],
+ "expo-web-browser"
]
}
-}
\ No newline at end of file
+}
diff --git a/ArtisanConnect/app/(tabs)/_layout.jsx b/ArtisanConnect/app/(tabs)/_layout.jsx
index 1dc6711..3ff6e26 100644
--- a/ArtisanConnect/app/(tabs)/_layout.jsx
+++ b/ArtisanConnect/app/(tabs)/_layout.jsx
@@ -1,64 +1,75 @@
-import { Tabs } from "expo-router";
-import { Ionicons } from "@expo/vector-icons";
+import {Tabs} from "expo-router";
+import {Ionicons} from "@expo/vector-icons";
export default function TabLayout() {
- return (
-
- (
-
- ),
- }}
- />
- (
-
- ),
- }}
- />
- (
-
- ),
- }}
- />
- (
-
- ),
- }}
- />
- (
-
- ),
- }}
- />
-
- );
+ return (
+
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+ (
+
+ ),
+ }}
+ />
+
+ );
}
diff --git a/ArtisanConnect/app/(tabs)/login.jsx b/ArtisanConnect/app/(tabs)/login.jsx
new file mode 100644
index 0000000..0fbbf82
--- /dev/null
+++ b/ArtisanConnect/app/(tabs)/login.jsx
@@ -0,0 +1,114 @@
+import React, {useState} from 'react';
+import {StyleSheet, ActivityIndicator, SafeAreaView, View} from 'react-native';
+import {useAuthStore} from '@/store/authStore';
+import {useRouter, Link} from 'expo-router';
+
+import {Box} from "@/components/ui/box"
+import {Button, ButtonText, ButtonIcon} from "@/components/ui/button"
+import {Center} from "@/components/ui/center"
+import {Heading} from "@/components/ui/heading"
+import {Input, InputField} from "@/components/ui/input"
+import {Text} from "@/components/ui/text"
+import {VStack} from "@/components/ui/vstack"
+import {HStack} from "@/components/ui/hstack"
+import {ArrowRightIcon} from "@/components/ui/icon"
+import {Divider} from '@/components/ui/divider';
+import {Ionicons} from "@expo/vector-icons";
+
+export default function Login() {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const {signIn, isLoading} = useAuthStore();
+ const router = useRouter();
+
+ const handleInternalLogin = async () => {
+ if (!email || !password) {
+ alert('Proszę wprowadzić email i hasło.');
+ return;
+ }
+
+ try {
+ await signIn(email, password);
+ alert(`Zalogowano jako ${email}`);
+ router.replace('/');
+ } catch (e) {
+ alert("Błąd logowania: " + (e.response?.data?.message || e.message));
+ }
+ }
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ Logowanie
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ lub
+
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ padding: 20,
+ },
+ input: {
+ borderWidth: 1,
+ borderColor: '#ddd',
+ borderRadius: 5,
+ marginBottom: 15,
+ padding: 10,
+ },
+ errorText: {
+ color: 'red',
+ marginBottom: 10,
+ },
+ signupbutton: {
+ fontWeight: '300',
+ },
+});
\ No newline at end of file
diff --git a/ArtisanConnect/app/registration.jsx b/ArtisanConnect/app/registration.jsx
new file mode 100644
index 0000000..555750c
--- /dev/null
+++ b/ArtisanConnect/app/registration.jsx
@@ -0,0 +1,96 @@
+import React, {useState} from 'react';
+import {StyleSheet, ActivityIndicator, SafeAreaView, View} from 'react-native';
+import {useAuthStore} from '@/store/authStore';
+import {useRouter} from 'expo-router';
+
+import {Box} from "@/components/ui/box"
+import {Button, ButtonText} from "@/components/ui/button"
+import {Center} from "@/components/ui/center"
+import {Heading} from "@/components/ui/heading"
+import {Input, InputField} from "@/components/ui/input"
+import {VStack} from "@/components/ui/vstack"
+
+export default function Registration() {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [firstName, setFirstName] = useState('');
+ const [lastName, setLastName] = useState('');
+ const {signUp, isLoading} = useAuthStore();
+ const router = useRouter();
+
+ const handleInternalRegistration = async () => {
+ if (!email || !password || !firstName || !lastName) {
+ alert('Proszę uzupełnić wszystkie pola');
+ return;
+ }
+
+ try {
+ await signUp({email, password, firstName, lastName});
+ alert(`Zalogowano jako ${email}`);
+ router.replace('/');
+ } catch (e) {
+ alert("Błąd logowania: " + (e.response?.data?.message || e.message));
+ }
+ }
+
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+
+
+
+ Rejestracja
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ flex: 1,
+ justifyContent: 'center',
+ },
+ formContainer: {
+ width: '80%',
+ },
+ input: {
+ borderWidth: 1,
+ borderColor: '#ddd',
+ borderRadius: 5,
+ marginBottom: 15,
+ padding: 10,
+ },
+ errorText: {
+ color: 'red',
+ marginBottom: 10,
+ },
+});
\ No newline at end of file
diff --git a/ArtisanConnect/package.json b/ArtisanConnect/package.json
index 2e21222..f44ffd7 100644
--- a/ArtisanConnect/package.json
+++ b/ArtisanConnect/package.json
@@ -14,6 +14,7 @@
"@gluestack-style/react": "^1.0.57",
"@gluestack-ui/actionsheet": "^0.2.53",
"@gluestack-ui/button": "^1.0.14",
+ "@gluestack-ui/divider": "^0.1.10",
"@gluestack-ui/form-control": "^0.1.19",
"@gluestack-ui/hstack": "^0.1.17",
"@gluestack-ui/icon": "^0.1.27",
@@ -26,18 +27,24 @@
"@gluestack-ui/themed": "^1.1.73",
"@gluestack-ui/toast": "^1.0.9",
"@legendapp/motion": "^2.4.0",
+ "@react-native-async-storage/async-storage": "2.1.2",
+ "@react-native-community/cli-link-assets": "^18.0.0",
"@react-navigation/drawer": "^7.3.11",
"@tanstack/react-query": "^5.74.4",
"axios": "^1.9.0",
"babel-plugin-module-resolver": "^5.0.2",
"expo": "^53.0.0",
+ "expo-auth-session": "~6.1.5",
+ "expo-camera": "~16.1.6",
"expo-constants": "~17.1.5",
"expo-image-picker": "~16.1.4",
"expo-linking": "~7.1.4",
"expo-router": "~5.0.5",
"expo-status-bar": "~2.2.3",
+ "expo-web-browser": "~14.1.6",
"form-data": "^4.0.2",
"fs": "^0.0.1-security",
+ "lucide-react-native": "^0.511.0",
"nativewind": "^4.1.23",
"react": "19.0.0",
"react-dom": "19.0.0",
@@ -50,8 +57,7 @@
"react-native-svg": "15.11.2",
"react-native-web": "~0.20.0",
"tailwindcss": "^3.4.17",
- "zustand": "^5.0.3",
- "expo-camera": "~16.1.6"
+ "zustand": "^5.0.3"
},
"devDependencies": {
"@babel/core": "^7.20.0",
diff --git a/ArtisanConnect/store/authStore.jsx b/ArtisanConnect/store/authStore.jsx
new file mode 100644
index 0000000..f205790
--- /dev/null
+++ b/ArtisanConnect/store/authStore.jsx
@@ -0,0 +1,108 @@
+import {create} from "zustand";
+import {createJSONStorage, persist} from "zustand/middleware";
+import AsyncStorage from "@react-native-async-storage/async-storage";
+import axios from "axios";
+
+const API_URL = "http://10.0.2.2:8080/api/v1";
+
+export const useAuthStore = create(
+ persist(
+ (set) => ({
+ user: 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 = response.data.user;
+ const token = response.data.token;
+ set({user, token, isLoading: false});
+ } catch (error) {
+ set({error: error.response?.data?.message || error.message, isLoading: false});
+ throw error;
+ }
+ },
+
+ signUp: async (userData) => {
+ set({isLoading: true, error: null});
+ try {
+ console.log(userData);
+
+ const response = await axios.post(`${API_URL}/auth/register`, userData, {
+ headers: {'Content-Type': 'application/json'}
+ });
+
+ console.log(response.data);
+
+ const user = response.data.user;
+ const token = response.data.token;
+ set({user, token, isLoading: false});
+
+ return user;
+ } 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`, {token: googleToken});
+
+ 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;
+ }
+ },
+
+ 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: null, token: null});
+ }
+ },
+
+ checkAuth: async () => {
+ const {token} = useAuthStore.getState();
+ 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;
+ }
+ },
+ }),
+ {
+ name: "auth-storage",
+ storage: createJSONStorage(() => AsyncStorage),
+ }
+ )
+);
\ No newline at end of file