-
-
- Edit src/App.tsx and save to test HMR
-
+
+
+ {!user&& }
+
+ }>
+ }/>
+
+ }/>
+ {/*}>*/}
+ {/* }/>*/}
+ {/**/}
+
+
-
- Click on the Vite and React logos to learn more
-
>
)
-}
-
-export default App
+}
\ No newline at end of file
diff --git a/frontend/src/components/Card.tsx b/frontend/src/components/Card.tsx
new file mode 100644
index 0000000..d503616
--- /dev/null
+++ b/frontend/src/components/Card.tsx
@@ -0,0 +1,16 @@
+import type {DiscountInfoType} from "../type/DiscountInfoType.ts";
+
+export default function Card(props:Readonly
) {
+ return (
+
+
+

+
{props.name}
+
+
+
{props.price}
+ {props.provider}
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/Dashboard.tsx
new file mode 100644
index 0000000..2e70d39
--- /dev/null
+++ b/frontend/src/components/Dashboard.tsx
@@ -0,0 +1,48 @@
+import type {userInfoType} from "../type/UserInfoType.ts";
+import Login from "./Login.tsx";
+import type {DiscountInfoType} from "../type/DiscountInfoType.ts";
+import Card from "./Card.tsx";
+
+type DashboardProps = {
+ user: userInfoType | null,
+ discounts: DiscountInfoType[],
+ filteredDiscounts: DiscountInfoType[]
+}
+
+export default function Dashboard({user,discounts,filteredDiscounts}: Readonly) {
+
+ return (
+
+ {user ?
+
+
Welcome {user?.userName}
+

+
This weeks discounts:
+
+
+ {filteredDiscounts.length>0 ?
+ filteredDiscounts.map(
+ (discount: DiscountInfoType)=>(
+ )):
+ discounts.length>0&&
+ discounts.map(
+ (discount: DiscountInfoType)=>(
+ ))
+ }
+
+
:
+ (
)
+ }
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/Login.tsx b/frontend/src/components/Login.tsx
new file mode 100644
index 0000000..efe7927
--- /dev/null
+++ b/frontend/src/components/Login.tsx
@@ -0,0 +1,32 @@
+function loginWithGithub() {
+ const host: string = window.location.host === "localhost:5173" ?
+ "http://localhost:8080" :
+ window.location.origin;
+ window.open(host + "/oauth2/authorization/github", "_self");
+}
+
+function loginWithGoogle() {
+ const host: string = window.location.host === "localhost:5173" ?
+ "http://localhost:8080" :
+ window.location.origin;
+ window.open(host + "/oauth2/authorization/google", "_self");
+}
+function loginWithFacebook() {
+ const host: string = window.location.host === "localhost:5173" ?
+ "http://localhost:8080" :
+ window.location.origin;
+ window.open(host + "/oauth2/authorization/facebook", "_self");
+}
+
+export default function Login() {
+ return (
+
+
Please login
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/Navbar.tsx b/frontend/src/components/Navbar.tsx
new file mode 100644
index 0000000..c9e77ee
--- /dev/null
+++ b/frontend/src/components/Navbar.tsx
@@ -0,0 +1,48 @@
+import type {userInfoType} from "../type/UserInfoType.ts";
+import type {DiscountInfoType} from "../type/DiscountInfoType.ts";
+import {useEffect, useState} from "react";
+import SearchBar from "./SearchBar.tsx";
+
+
+interface NavbarProps {
+ user: userInfoType | null,
+ setUser: (user: userInfoType | null) => void,
+ discounts: DiscountInfoType[],
+ setDiscounts: (discounts: DiscountInfoType[]) => void,
+ setFilteredDiscounts:(filteredDiscounts: DiscountInfoType[]) => void
+}
+
+
+export function Navbar({user, setUser, discounts, setFilteredDiscounts}: NavbarProps) {
+//scroll animation
+ const [prevScrollPosition, setPrevScrollPosition] = useState(0);
+ const [visible, setVisible] = useState(true);
+ useEffect(() => {
+ function handleScroll() {
+ const currentScrollPosition = window.pageYOffset;
+ setVisible(prevScrollPosition > currentScrollPosition || currentScrollPosition < 10);
+ setPrevScrollPosition(currentScrollPosition);
+ }
+
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, [prevScrollPosition]);
+
+ function logout() {
+ const host: string = window.location.host === "localhost:5173" ?
+ "http://localhost:8080" :
+ window.location.origin;
+ window.open(host + "/logout", "_self");
+ setUser(null)
+ }
+
+ return (
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/ProtectedRoute.tsx b/frontend/src/components/ProtectedRoute.tsx
new file mode 100644
index 0000000..f039df2
--- /dev/null
+++ b/frontend/src/components/ProtectedRoute.tsx
@@ -0,0 +1,11 @@
+import {Navigate, Outlet} from "react-router-dom";
+import type {userInfoType} from "../type/UserInfoType.ts";
+
+type ProtectedRouteProps={
+ user:userInfoType|null
+}
+export default function ProtectedRoute(props:Readonly) {
+ return (
+ props.user? :
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/components/SearchBar.tsx b/frontend/src/components/SearchBar.tsx
new file mode 100644
index 0000000..4073728
--- /dev/null
+++ b/frontend/src/components/SearchBar.tsx
@@ -0,0 +1,26 @@
+import type {DiscountInfoType} from "../type/DiscountInfoType.ts";
+
+type SearchBarProps = {
+ discounts: DiscountInfoType[],
+ setFilteredDiscounts: (filteredDiscounts: DiscountInfoType[]) => void
+}
+export default function SearchBar({discounts, setFilteredDiscounts}: Readonly) {
+ function filterDiscounts(e:string){
+ const searchArray: DiscountInfoType[] = [];
+ if(e.trim().length>0){
+ discounts.forEach((discount:DiscountInfoType)=>(discount.name.toLowerCase().includes(e.toLowerCase()) && searchArray.push(discount)));
+ setFilteredDiscounts(searchArray);
+ }else{
+ setFilteredDiscounts(discounts);
+ }
+
+ }
+ return (
+
+ {discounts.length > 0 && (
+ filterDiscounts(e.target.value) }/>
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/index.css b/frontend/src/index.css
index 08a3ac9..bf4f166 100644
--- a/frontend/src/index.css
+++ b/frontend/src/index.css
@@ -57,7 +57,7 @@ button:focus-visible {
@media (prefers-color-scheme: light) {
:root {
color: #213547;
- background-color: #ffffff;
+ background-color: lightgray;
}
a:hover {
color: #747bff;
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index bef5202..a24970e 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -2,9 +2,12 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.tsx'
+import {BrowserRouter} from "react-router-dom";
createRoot(document.getElementById('root')!).render(
-
+
+
+
,
)
diff --git a/frontend/src/type/DiscountInfoType.ts b/frontend/src/type/DiscountInfoType.ts
new file mode 100644
index 0000000..2484c14
--- /dev/null
+++ b/frontend/src/type/DiscountInfoType.ts
@@ -0,0 +1,7 @@
+export type DiscountInfoType={
+ id: string,
+ image: string,
+ name: string,
+ price: string,
+ provider: string
+}
\ No newline at end of file
diff --git a/frontend/src/type/UserInfoType.ts b/frontend/src/type/UserInfoType.ts
new file mode 100644
index 0000000..0edb414
--- /dev/null
+++ b/frontend/src/type/UserInfoType.ts
@@ -0,0 +1,5 @@
+export type userInfoType={
+ id:string,
+ userName: string,
+ avatarUrl: string
+}
\ No newline at end of file
diff --git a/frontend/tsconfig.app.json b/frontend/tsconfig.app.json
index 227a6c6..68735d8 100644
--- a/frontend/tsconfig.app.json
+++ b/frontend/tsconfig.app.json
@@ -23,5 +23,5 @@
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
- "include": ["src"]
+ "include": ["src/**/*"]
}
diff --git a/sonar-project.properties b/sonar-project.properties
index 319b37d..5145477 100644
--- a/sonar-project.properties
+++ b/sonar-project.properties
@@ -8,8 +8,8 @@ sonar.organization=defhex
# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows.
-sonar.sources=./frontend/src
-sonar.coverage.exclusions=./frontend/src/**
+sonar.sources=frontend/src
+sonar.coverage.exclusions=frontend/src/**
# Encoding of the source code. Default is default system encoding
#sonar.sourceEncoding=UTF-8
\ No newline at end of file