diff --git a/backend/pom.xml b/backend/pom.xml index f803a5c..6cedaf2 100644 --- a/backend/pom.xml +++ b/backend/pom.xml @@ -58,6 +58,15 @@ 4.13.0 test + + org.springframework.boot + spring-boot-starter-oauth2-client + + + org.springframework.security + spring-security-test + test + diff --git a/backend/src/main/java/com/github/esgoet/backend/security/AuthController.java b/backend/src/main/java/com/github/esgoet/backend/security/AuthController.java new file mode 100644 index 0000000..929d92d --- /dev/null +++ b/backend/src/main/java/com/github/esgoet/backend/security/AuthController.java @@ -0,0 +1,22 @@ +package com.github.esgoet.backend.security; + +import com.github.esgoet.backend.user.models.User; +import com.github.esgoet.backend.user.services.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/api/auth") +@RequiredArgsConstructor +public class AuthController { + private final UserService userService; + + @GetMapping("/me") + public User getUser(@AuthenticationPrincipal OAuth2User user) { + return userService.getUserByGitHubId(user.getName()); + } +} diff --git a/backend/src/main/java/com/github/esgoet/backend/security/SecurityConfig.java b/backend/src/main/java/com/github/esgoet/backend/security/SecurityConfig.java new file mode 100644 index 0000000..4a436b5 --- /dev/null +++ b/backend/src/main/java/com/github/esgoet/backend/security/SecurityConfig.java @@ -0,0 +1,75 @@ +package com.github.esgoet.backend.security; + +import com.github.esgoet.backend.user.dto.UserDto; +import com.github.esgoet.backend.user.models.User; +import com.github.esgoet.backend.user.models.UserNotFoundException; +import com.github.esgoet.backend.user.services.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpStatus; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.HttpStatusEntryPoint; + +import java.time.LocalDate; +import java.util.List; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + private final UserService userService; + + @Value("${app.url}") + private String appUrl; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests(a -> a +// .requestMatchers("/api/books").authenticated() +// .requestMatchers("/api/books/**").authenticated() + .anyRequest().permitAll() + ) + .sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)) + .exceptionHandling(e -> e + .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))) + .oauth2Login(o -> o.defaultSuccessUrl(appUrl)) + .logout(l -> l.logoutSuccessUrl(appUrl)); + return http.build(); + } + + @Bean + public OAuth2UserService oauth2UserService() { + DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); + + return request -> { + OAuth2User user = delegate.loadUser(request); + User githubUser; + try { + githubUser = userService.getUserByGitHubId(user.getName()); + } catch (UserNotFoundException e) { + githubUser = userService.saveUser(new UserDto(user.getAttributes().get("login").toString(), + 0, + LocalDate.now(), + 0, + user.getName(), + "USER")); + } + + return new DefaultOAuth2User(List.of(new SimpleGrantedAuthority(githubUser.role())), user.getAttributes(), "id"); + }; + } +} diff --git a/backend/src/main/java/com/github/esgoet/backend/user/dto/UserDto.java b/backend/src/main/java/com/github/esgoet/backend/user/dto/UserDto.java index 1625413..d97cb71 100644 --- a/backend/src/main/java/com/github/esgoet/backend/user/dto/UserDto.java +++ b/backend/src/main/java/com/github/esgoet/backend/user/dto/UserDto.java @@ -9,6 +9,8 @@ public record UserDto( String userName, int readingGoal, LocalDate goalDate, - int readBooks + int readBooks, + String gitHubId, + String role ) { } diff --git a/backend/src/main/java/com/github/esgoet/backend/user/models/User.java b/backend/src/main/java/com/github/esgoet/backend/user/models/User.java index 5a1d7b0..6da01cd 100644 --- a/backend/src/main/java/com/github/esgoet/backend/user/models/User.java +++ b/backend/src/main/java/com/github/esgoet/backend/user/models/User.java @@ -12,7 +12,9 @@ public record User ( String userName, int readingGoal, LocalDate goalDate, - int readBooks + int readBooks, + String gitHubId, + String role ) { } diff --git a/backend/src/main/java/com/github/esgoet/backend/user/repositories/UserRepository.java b/backend/src/main/java/com/github/esgoet/backend/user/repositories/UserRepository.java index 1e1b3eb..ae4891e 100644 --- a/backend/src/main/java/com/github/esgoet/backend/user/repositories/UserRepository.java +++ b/backend/src/main/java/com/github/esgoet/backend/user/repositories/UserRepository.java @@ -4,6 +4,9 @@ import org.springframework.data.mongodb.repository.MongoRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface UserRepository extends MongoRepository { + Optional findByGitHubId(String gitHubId); } diff --git a/backend/src/main/java/com/github/esgoet/backend/user/services/UserService.java b/backend/src/main/java/com/github/esgoet/backend/user/services/UserService.java index b19b000..76fd458 100644 --- a/backend/src/main/java/com/github/esgoet/backend/user/services/UserService.java +++ b/backend/src/main/java/com/github/esgoet/backend/user/services/UserService.java @@ -27,7 +27,7 @@ public User getUserById(String id) { } public User saveUser(UserDto userDto) { - User userToSave = new User(idService.randomId(), userDto.userName(), userDto.readingGoal(), userDto.goalDate(), userDto.readBooks()); + User userToSave = new User(idService.randomId(), userDto.userName(), userDto.readingGoal(), userDto.goalDate(), userDto.readBooks(), userDto.gitHubId(), userDto.role()); return userRepository.save(userToSave); } @@ -37,11 +37,14 @@ public User updateUser(String id, UserDto updatedUser) { .withGoalDate(updatedUser.goalDate()) .withReadBooks(updatedUser.readBooks()) .withReadingGoal(updatedUser.readingGoal()); - return userRepository.save(user); } public void deleteUser(String id) { userRepository.deleteById(id); } + + public User getUserByGitHubId(String gitHubId) { + return userRepository.findByGitHubId(gitHubId).orElseThrow(() -> new UserNotFoundException("No user found with GitHub id: " + gitHubId)); + } } diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties index 7f7db72..51c4040 100644 --- a/backend/src/main/resources/application.properties +++ b/backend/src/main/resources/application.properties @@ -1,2 +1,6 @@ spring.application.name=backend spring.data.mongodb.uri=${MONGO_DB_URI} +spring.security.oauth2.client.registration.github.client-id=${GITHUB_ID} +spring.security.oauth2.client.registration.github.client-secret=${GITHUB_SECRET} +spring.security.oauth2.client.registration.github.scope=none +app.url=${APP_URL} \ No newline at end of file diff --git a/backend/src/test/java/com/github/esgoet/backend/book/controllers/BookControllerIntegrationTest.java b/backend/src/test/java/com/github/esgoet/backend/book/controllers/BookControllerIntegrationTest.java index b3a52ae..aa0fe50 100644 --- a/backend/src/test/java/com/github/esgoet/backend/book/controllers/BookControllerIntegrationTest.java +++ b/backend/src/test/java/com/github/esgoet/backend/book/controllers/BookControllerIntegrationTest.java @@ -9,6 +9,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; @@ -33,6 +34,7 @@ class BookControllerIntegrationTest { private final LocalDate createdDate = LocalDate.parse("2024-08-22"); @Test + @WithMockUser void getAllBooks_Test_When_DbEmpty_Then_returnEmptyArray() throws Exception { mockMvc.perform(get("/api/books")) @@ -43,6 +45,7 @@ void getAllBooks_Test_When_DbEmpty_Then_returnEmptyArray() throws Exception { @DirtiesContext @Test + @WithMockUser void getBook_Test_whenIdExists() throws Exception { //GIVEN bookRepository.save(new Book("1", "George Orwell", "1984", Genre.THRILLER, "this is a description", "123456isbn", "https://linkToCover", 3,localDate, ReadingStatus.TO_BE_READ, createdDate)); @@ -69,6 +72,7 @@ void getBook_Test_whenIdExists() throws Exception { @Test @DirtiesContext + @WithMockUser void addABookTest_whenNewBookExists_thenReturnNewBook() throws Exception { // GIVEN @@ -105,6 +109,7 @@ void addABookTest_whenNewBookExists_thenReturnNewBook() throws Exception { @DirtiesContext @Test + @WithMockUser void getBook_Test_whenIdDoesNotExists() throws Exception { //WHEN mockMvc.perform(get("/api/books/1")) @@ -120,6 +125,7 @@ void getBook_Test_whenIdDoesNotExists() throws Exception { } @Test + @WithMockUser void deleteBook() throws Exception { bookRepository.save(new Book("1", "Simon", "HowToDeleteBooksFast", Genre.SCIENCE, "description", "12345678", "https://linkToCover", 3,localDate, ReadingStatus.TO_BE_READ, createdDate)); diff --git a/backend/src/test/java/com/github/esgoet/backend/security/AuthControllerTest.java b/backend/src/test/java/com/github/esgoet/backend/security/AuthControllerTest.java new file mode 100644 index 0000000..0e07555 --- /dev/null +++ b/backend/src/test/java/com/github/esgoet/backend/security/AuthControllerTest.java @@ -0,0 +1,47 @@ +package com.github.esgoet.backend.security; + +import com.github.esgoet.backend.user.models.User; +import com.github.esgoet.backend.user.repositories.UserRepository; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; + +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +class AuthControllerTest { + @Autowired + MockMvc mockMvc; + @Autowired + UserRepository userRepository; + + @Test + @DirtiesContext + void getLoggedInUserTest() throws Exception { + userRepository.save(new User("1","esgoet", 0, LocalDate.now(), 0, "123", "USER")); + + mockMvc.perform(get("/api/auth/me") + .with(oidcLogin().idToken(token -> token.subject("123")) + .userInfoToken(token -> token.claim("login", "esgoet")))) + .andExpect(status().isOk()) + .andExpect(content().json(""" + { + "userName": "esgoet", + "readingGoal": 0, + "readBooks": 0, + "gitHubId": "123", + "role": "USER" + } + """)) + .andExpect(jsonPath("$.id").exists()) + .andExpect(jsonPath("$.goalDate").exists()); + } +} diff --git a/backend/src/test/java/com/github/esgoet/backend/user/controllers/UserControllerIntegrationTest.java b/backend/src/test/java/com/github/esgoet/backend/user/controllers/UserControllerIntegrationTest.java index 2baeacb..f88d1a7 100644 --- a/backend/src/test/java/com/github/esgoet/backend/user/controllers/UserControllerIntegrationTest.java +++ b/backend/src/test/java/com/github/esgoet/backend/user/controllers/UserControllerIntegrationTest.java @@ -7,6 +7,7 @@ import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.web.servlet.MockMvc; @@ -26,6 +27,7 @@ class UserControllerIntegrationTest { private final LocalDate goalDate = LocalDate.parse("2024-12-31"); @Test + @WithMockUser void getUsersTest() throws Exception { mockMvc.perform(get("/api/users")) .andExpect(status().isOk()) @@ -34,9 +36,10 @@ void getUsersTest() throws Exception { @DirtiesContext @Test + @WithMockUser void getUserByIdTest_whenIdExists() throws Exception{ //GIVEN - userRepository.save(new User("1","esgoet", 6, goalDate, 0)); + userRepository.save(new User("1","esgoet", 6, goalDate, 0, "123", "USER")); //WHEN mockMvc.perform(get("/api/users/1")) //THEN @@ -47,12 +50,15 @@ void getUserByIdTest_whenIdExists() throws Exception{ "userName": "esgoet", "readingGoal": 6, "goalDate": "2024-12-31", - "readBooks": 0 + "readBooks": 0, + "gitHubId": "123", + "role": "USER" } """)); } @Test + @WithMockUser void getUserByIdTest_whenIdDoesNotExist() throws Exception { //WHEN mockMvc.perform(get("/api/users/1")) @@ -69,6 +75,7 @@ void getUserByIdTest_whenIdDoesNotExist() throws Exception { @DirtiesContext @Test + @WithMockUser void addUserTest() throws Exception { //WHEN mockMvc.perform(post("/api/users") @@ -78,7 +85,9 @@ void addUserTest() throws Exception { "userName": "esgoet", "readingGoal": 6, "goalDate": "2024-12-31", - "readBooks": 0 + "readBooks": 0, + "gitHubId": "123", + "role": "USER" } """)) .andExpect(status().isOk()) @@ -87,7 +96,9 @@ void addUserTest() throws Exception { "userName": "esgoet", "readingGoal": 6, "goalDate": "2024-12-31", - "readBooks": 0 + "readBooks": 0, + "gitHubId": "123", + "role": "USER" } """)) .andExpect(jsonPath("$.id").exists()); @@ -95,9 +106,10 @@ void addUserTest() throws Exception { @DirtiesContext @Test + @WithMockUser void updateUserTest() throws Exception { //GIVEN - userRepository.save(new User("1","esgoet", 6, goalDate, 0)); + userRepository.save(new User("1","esgoet", 6, goalDate, 0, "123", "USER")); //WHEN mockMvc.perform(put("/api/users/1") @@ -107,7 +119,9 @@ void updateUserTest() throws Exception { "userName": "esgoet", "readingGoal": 6, "goalDate": "2024-12-31", - "readBooks": 1 + "readBooks": 1, + "gitHubId": "123", + "role": "USER" } """)) //THEN @@ -118,16 +132,19 @@ void updateUserTest() throws Exception { "userName": "esgoet", "readingGoal": 6, "goalDate": "2024-12-31", - "readBooks": 1 + "readBooks": 1, + "gitHubId": "123", + "role": "USER" } """)); } @DirtiesContext @Test + @WithMockUser void deleteUserTest() throws Exception { //GIVEN - userRepository.save(new User("1","esgoet", 6, goalDate, 0)); + userRepository.save(new User("1","esgoet", 6, goalDate, 0, "123", "USER")); //WHEN mockMvc.perform(delete("/api/users/1")) diff --git a/backend/src/test/java/com/github/esgoet/backend/user/services/UserServiceTest.java b/backend/src/test/java/com/github/esgoet/backend/user/services/UserServiceTest.java index 58c7f98..4325691 100644 --- a/backend/src/test/java/com/github/esgoet/backend/user/services/UserServiceTest.java +++ b/backend/src/test/java/com/github/esgoet/backend/user/services/UserServiceTest.java @@ -25,13 +25,13 @@ class UserServiceTest { @Test void getUsers_Test() { List users = List.of( - new User("1","user1", 6, goalDate, 0), - new User("2","user2", 22, goalDate, 5) + new User("1","user1", 6, goalDate, 0,"123", "USER"), + new User("2","user2", 22, goalDate, 5, "123", "USER") ); List expectedUsers = List.of( - new User("1","user1", 6, goalDate, 0), - new User("2","user2", 22, goalDate, 5) + new User("1","user1", 6, goalDate, 0, "123", "USER"), + new User("2","user2", 22, goalDate, 5, "123", "USER") ); when(userRepo.findAll()).thenReturn(users); @@ -52,12 +52,12 @@ void getUsersTest_whenEmpty_thenReturnEmptyList() { @Test void getUserByIdTest_whenUserExists_thenReturnUser() { //GIVEN - User user = new User("1","user1", 6, goalDate, 0); + User user = new User("1","user1", 6, goalDate, 0, "123", "USER"); when(userRepo.findById("1")).thenReturn(Optional.of(user)); //WHEN User actual = userService.getUserById("1"); //THEN - User expected = new User("1","user1", 6, goalDate, 0); + User expected = new User("1","user1", 6, goalDate, 0, "123", "USER"); verify(userRepo).findById("1"); assertEquals(expected, actual); } @@ -75,8 +75,8 @@ void getUserByIdTest_whenUserDoesNotExists_thenThrow() { @Test void addUserTest_whenNewUserAsInput_thenReturnNewUser() { // GIVEN - UserDto userDto = new UserDto("user1", 6, goalDate, 0); - User userToSave = new User("1","user1", 6, goalDate, 0); + UserDto userDto = new UserDto("user1", 6, goalDate, 0, "123", "USER"); + User userToSave = new User("1","user1", 6, goalDate, 0, "123", "USER"); when(idService.randomId()).thenReturn("1"); when(userRepo.save(userToSave)).thenReturn(userToSave); @@ -84,7 +84,7 @@ void addUserTest_whenNewUserAsInput_thenReturnNewUser() { User actual = userService.saveUser(userDto); // THEN - User expected = new User("1","user1", 6, goalDate, 0); + User expected = new User("1","user1", 6, goalDate, 0, "123", "USER"); verify(idService).randomId(); verify(userRepo).save(userToSave); assertEquals(expected, actual); @@ -102,9 +102,9 @@ void updateUserTest_whenUserExists() { // Given String id = "1"; - User existingUser = new User("1","user1", 6, goalDate, 0); - UserDto updatedUserDto = new UserDto("user1", 6, goalDate, 1); - User updatedUser = new User("1","user1", 6, goalDate, 1); + User existingUser = new User("1","user1", 6, goalDate, 0, "123", "USER"); + UserDto updatedUserDto = new UserDto("user1", 6, goalDate, 1, "123", "USER"); + User updatedUser = new User("1","user1", 6, goalDate, 1, "123", "USER"); // When when(userRepo.findById(id)).thenReturn(Optional.of(existingUser)); @@ -113,7 +113,7 @@ void updateUserTest_whenUserExists() { User actual = userService.updateUser(id, updatedUserDto); // Then - User expected = new User("1","user1", 6, goalDate, 1); + User expected = new User("1","user1", 6, goalDate, 1,"123", "USER"); assertNotNull(actual); assertEquals(expected, actual); verify(userRepo).findById(id); @@ -125,8 +125,8 @@ void updateUserTest_whenUserNotFound() { // Given String id = "1"; - UserDto updatedUserDto = new UserDto("user1", 6, goalDate, 1); - User updatedUser = new User("1","user1", 6, goalDate, 1); + UserDto updatedUserDto = new UserDto("user1", 6, goalDate, 1,"123", "USER"); + User updatedUser = new User("1","user1", 6, goalDate, 1,"123", "USER"); //When when(userRepo.findById(id)).thenReturn(Optional.empty()); diff --git a/backend/src/test/resources/application.properties b/backend/src/test/resources/application.properties index b0360a6..8543d2f 100644 --- a/backend/src/test/resources/application.properties +++ b/backend/src/test/resources/application.properties @@ -1,2 +1,6 @@ spring.application.name=backend -de.flapdoodle.mongodb.embedded.version=7.0.4 \ No newline at end of file +de.flapdoodle.mongodb.embedded.version=7.0.4 +spring.security.oauth2.client.registration.github.client-id=test-id +spring.security.oauth2.client.registration.github.client-secret=test-secret +spring.security.oauth2.client.registration.github.scope=none +app.url=http://localhost \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c1cb054..ed5db73 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -10,11 +10,12 @@ import Header from "./components/header/Header.tsx"; import Navigation from "./components/navigation/Navigation.tsx"; import Dashboard from "./pages/DashboardPage/dashboard/Dashboard.tsx"; import SettingsPage from "./pages/SettingsPage/settingsPage/SettingsPage.tsx"; +import LoginPage from "./pages/LoginPage/loginPage/LoginPage.tsx"; export default function App() { const [data, setData] = useState([]) - const [user, setUser] = useState({id: "", userName: "", readingGoal: 0, goalDate: "", readBooks: 0}) + const [user, setUser] = useState() const fetchBooks = () => { axios.get("/api/books") @@ -26,14 +27,6 @@ export default function App() { }) } - const fetchUser = () => { - axios.get("/api/users/1") - .then((response) => { - setUser(response.data) - }) - .catch((error) => (console.log(error))) - } - const deleteBook = (id: string) => { axios.delete("/api/books/" + id) .then((response) => response.status === 200 && fetchBooks()) @@ -47,13 +40,19 @@ export default function App() { } const updateUser = (updatedProperty: string, updatedValue: number | string) => { - axios.put(`/api/users/${user.id}`, {...user, [updatedProperty]: updatedValue}) - .then((response) => response.status === 200 && fetchUser()) + axios.put(`/api/users/${user?.id}`, {...user, [updatedProperty]: updatedValue}) + .then((response) => response.status === 200 && loadUser()) + } + const loadUser = () => { + axios.get("/api/auth/me") + .then((response) => setUser(response.data)) + .catch(() => setUser(null)) } + useEffect(() => { fetchBooks() - fetchUser() + loadUser() }, []); const [searchInput, setSearchInput] = useState("") @@ -62,17 +61,33 @@ export default function App() { .filter((book) => book.title?.toLowerCase().includes(searchInput.toLowerCase()) || book.author?.toLowerCase().includes(searchInput.toLowerCase())); + const login = () => { + const host = window.location.host === 'localhost:5173' ? 'http://localhost:8080': window.location.origin + window.open(host + '/oauth2/authorization/github', '_self') + } + const logout = () => { + const host = window.location.host === 'localhost:5173' ? 'http://localhost:8080': window.location.origin + window.open(host + "/logout", "_self") + } + + return ( <> - - + + {user && } + }/> }/> - }/> - }/> - }/> - }/> + }/> + + {user &&}/>} + {user && }/>} + {user && }/>} > diff --git a/frontend/src/components/header/Header.css b/frontend/src/components/header/Header.css index 4edbcea..2171615 100644 --- a/frontend/src/components/header/Header.css +++ b/frontend/src/components/header/Header.css @@ -2,6 +2,8 @@ header { background-color: #183A37; width: 100%; padding: 15px; + display: flex; + justify-content: space-between; } header div { @@ -14,6 +16,14 @@ header div { justify-content: start; } + +.profile a, .profile button { + display: flex; + align-content: center; + place-items: center; + gap: 3px; +} + #logo { width: 50px; grid-area: logo; diff --git a/frontend/src/components/header/Header.tsx b/frontend/src/components/header/Header.tsx index 0fe957b..5735070 100644 --- a/frontend/src/components/header/Header.tsx +++ b/frontend/src/components/header/Header.tsx @@ -1,6 +1,12 @@ import "./Header.css"; +import {Link} from "react-router-dom"; +import {User} from "../../types/types.ts"; -export default function Header() { +type HeaderProps = { + user: User | null | undefined, + logout: () => void +} +export default function Header({user, logout}: HeaderProps) { return ( @@ -8,6 +14,13 @@ export default function Header() { TaleTrail Discover, Track, Repeat ( ͡° ͜ʖ ͡°) + + {user ? +logout +Logout : user === null && +login +Login} + ) } \ No newline at end of file diff --git a/frontend/src/pages/DashboardPage/dashboard/Dashboard.css b/frontend/src/pages/DashboardPage/dashboard/Dashboard.css index 6373e83..52da282 100644 --- a/frontend/src/pages/DashboardPage/dashboard/Dashboard.css +++ b/frontend/src/pages/DashboardPage/dashboard/Dashboard.css @@ -1,15 +1,15 @@ +#dashboard-page { + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; +} @media only screen and (max-width: 768px) { .dashboard-goal-summary { justify-content: center; } - .dashboard-page { - display: flex; - align-items: center; - justify-content: center; - } - .dashboard-recent-books { width: 350px; margin: auto; diff --git a/frontend/src/pages/DashboardPage/dashboard/Dashboard.tsx b/frontend/src/pages/DashboardPage/dashboard/Dashboard.tsx index b2bd062..679e28c 100644 --- a/frontend/src/pages/DashboardPage/dashboard/Dashboard.tsx +++ b/frontend/src/pages/DashboardPage/dashboard/Dashboard.tsx @@ -4,7 +4,7 @@ import {Book, User} from "../../../types/types.ts"; import LastAddedBook from "../components/lastAddedBook/LastAddedBook.tsx"; type DashboardProps = { - user: User, + user: User | null | undefined, data: Book[] } @@ -12,9 +12,9 @@ export default function Dashboard({user, data}: DashboardProps) { return ( - Welcome to TaleTrail, {user.userName}! + Welcome to TaleTrail{user && `, ${user.userName}!`} - + {user && } diff --git a/frontend/src/pages/LoginPage/loginPage/LoginPage.tsx b/frontend/src/pages/LoginPage/loginPage/LoginPage.tsx new file mode 100644 index 0000000..a1624c3 --- /dev/null +++ b/frontend/src/pages/LoginPage/loginPage/LoginPage.tsx @@ -0,0 +1,12 @@ +type LoginPageProps = { + login: () => void; +} + +export default function LoginPage({login}: LoginPageProps) { + return ( + <> + Login + Login with GitHub + > + ) +} \ No newline at end of file
Discover, Track, Repeat ( ͡° ͜ʖ ͡°)