Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
import com.barbershop.api.security.UserPrincipal;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
Expand All @@ -20,9 +18,6 @@
@Component
public class CustomOAuth2SuccessHandler implements AuthenticationSuccessHandler {

@Value("${spring.security.oauth2.client.registration.google.redirect-uri}")
private String googleRedirectUri;

@Autowired
private UserRepository userRepository;

Expand Down Expand Up @@ -62,7 +57,7 @@ public void onAuthenticationSuccess(HttpServletRequest request,
UserPrincipal userPrincipal = UserPrincipal.create(user);
String token = jwtService.generateToken(userPrincipal);

response.sendRedirect(googleRedirectUri + "?token=" + token);
response.sendRedirect("http://localhost:5173/oauth2/success?token=" + token);
}
}

7 changes: 2 additions & 5 deletions backend/src/main/resources/application-prod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ spring:
google:
client-id: ${GOOGLE_AUTH_CLIENT_ID}
client-secret: ${GOOGLE_AUTH_CLIENT_SECRET}
redirect-uri: ${GOOGLE_REDIRECT_URI}
scope:
- email
- profile
Expand All @@ -55,10 +54,8 @@ jwt:
expiration: ${JWT_EXPIRATION}

cors:
allowed-origins:
- ""
allowed-methods:
- "GET"
allowed-origins: http://localhost:5173,http://127.0.0.1:5173
allowed-methods: GET,POST,PUT,DELETE,OPTIONS

spring-doc:
api-docs:
Expand Down
23 changes: 12 additions & 11 deletions backend/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ server:

spring:
config:
import: optional:file:.env[.properties]
import: optional:file:./.env[.properties]

datasource:
url: jdbc:postgresql://localhost:3000/barber_token_db
username: postgres
password: admin
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}

jpa:
hibernate:
Expand All @@ -21,8 +21,8 @@ spring:
mail:
host: smtp.gmail.com
port: 587
username: [email protected]
password: idlg bcep xlty xxbi
username: ${EMAIL_USERNAME}
password: ${EMAIL_APP_PASSWORD}
properties:
mail:
smtp:
Expand All @@ -35,9 +35,8 @@ spring:
client:
registration:
google:
client-id: 70964715954-comus2knhoh30fponnlu9lfh27on6ama.apps.googleusercontent.com
client-secret: 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
redirect-uri: http://localhost:5173/oauth2/callback
client-id: ${GOOGLE_AUTH_CLIENT_ID}
client-secret: ${GOOGLE_AUTH_CLIENT_SECRET}
scope:
- email
- profile
Expand All @@ -49,9 +48,10 @@ spring:
user-info-uri: https://www.googleapis.com/oauth2/v3/userinfo
user-name-attribute: sub


jwt:
secret: 3xAmPl3SeCuR3KeYth@t_iS_AtLeAst32Ch@rs
expiration: 3600000
secret: ${JWT_SECRET}
expiration: ${JWT_EXPIRATION}

cors:
allowed-origins: http://localhost:5173,http://127.0.0.1:5173
Expand All @@ -64,3 +64,4 @@ spring-doc:
swagger-ui:
enabled: true
path: /swagger-ui.html

6 changes: 3 additions & 3 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
import AuthContainer from "./components/organisms/AuthContainer";
import AuthTemplate from "./templates/AuthTemplate";
import { theme } from "./styles/theme";
import CustomerDashboard from "./pages/Dashboard/Customer";
import BarberDashboard from "./pages/Dashboard/Barber";
import ProtectedRoute from "./routes/protectedRoutes";
import NotFoundPage from "./pages/NotFound";
import BarberProfileForm from "./components/molecules/BarberProfileForm";
import OAuthCallback from "./pages/OAuthCallback";
import { CustomerDashboard } from "./pages/Dashboard/Customer";
import { BarberDashboard } from "./pages/Dashboard/Barber";

const router = createBrowserRouter([
{
Expand All @@ -34,7 +34,7 @@
path: "/customer/dashboard",
element: (
<ProtectedRoute role="customer">
<CustomerDashboard />

Check failure on line 37 in frontend/src/App.tsx

View workflow job for this annotation

GitHub Actions / SonarCloud Analysis

src/App.test.tsx

Error: [vitest] No "CustomerDashboard" export is defined on the "./pages/Dashboard/Customer" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("./pages/Dashboard/Customer"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/App.tsx:37:10 ❯ src/App.test.tsx:176:25
</ProtectedRoute>
),
},
Expand All @@ -55,7 +55,7 @@
),
},
{
path: "/oauth2/callback/*",
path: "/oauth2/success/*",
element: <OAuthCallback />
},
{
Expand Down
80 changes: 80 additions & 0 deletions frontend/src/components/atoms/ConnectionIndicator/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import type React from "react"
import { theme } from "../../../styles/theme"
import Typography from "../Typography"

interface ConnectionIndicatorProps {
status: "connected" | "connecting" | "disconnected"
lastUpdated: Date
onReconnect?: () => void
}

const ConnectionIndicator: React.FC<ConnectionIndicatorProps> = ({ status, lastUpdated, onReconnect }) => {
const getStatusColor = () => {
switch (status) {
case "connected":
return theme.colors.success
case "connecting":
return theme.colors.highlight
case "disconnected":
return theme.colors.error
default:
return theme.colors.textSecondary
}
}

const getStatusText = () => {
switch (status) {
case "connected":
return "Connected"
case "connecting":
return "Connecting..."
case "disconnected":
return "Disconnected"
default:
return "Unknown"
}
}

return (
<div
style={{
display: "flex",
alignItems: "center",
gap: "0.5rem",
padding: "0.5rem",
backgroundColor: status === "connected" ? `${theme.colors.success}10` : `${getStatusColor()}10`,
borderRadius: theme.borderRadius.md,
border: `1px solid ${getStatusColor()}30`,
}}
>
<div
style={{
width: "8px",
height: "8px",
borderRadius: "50%",
backgroundColor: getStatusColor(),
animation: status === "connecting" ? "pulse 1.5s infinite" : "none",
}}
/>
<Typography text={getStatusText()} variant="xs" color={getStatusColor()} />
<Typography text={`β€’ ${lastUpdated.toLocaleTimeString()}`} variant="xs" color={theme.colors.textSecondary} />
{status === "disconnected" && onReconnect && (
<button
onClick={onReconnect}
style={{
background: "none",
border: "none",
color: theme.colors.primary,
cursor: "pointer",
fontSize: "0.75rem",
textDecoration: "underline",
}}
>
Retry
</button>
)}
</div>
)
}

export default ConnectionIndicator
68 changes: 68 additions & 0 deletions frontend/src/components/atoms/Notification/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import type React from "react"
import { useEffect, useState } from "react"
import { theme } from "../../../styles/theme"
import Typography from "../Typography"

interface NotificationProps {
message: string
onClose: () => void
duration?: number
}

const Notification: React.FC<NotificationProps> = ({ message, onClose, duration = 5000 }) => {
const [isVisible, setIsVisible] = useState(true)

useEffect(() => {
const timer = setTimeout(() => {
setIsVisible(false)
setTimeout(onClose, 300)
}, duration)

return () => clearTimeout(timer)
}, [duration, onClose])

if (!isVisible) return null

return (
<div
style={{
position: "fixed",
top: "1rem",
right: "1rem",
backgroundColor: theme.colors.primary,
color: "white",
padding: "1rem",
borderRadius: theme.borderRadius.lg,
boxShadow: "0 4px 6px -1px rgba(0, 0, 0, 0.1)",
zIndex: 1000,
maxWidth: "300px",
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "0.5rem",
animation: isVisible ? "slideIn 0.3s ease-out" : "slideOut 0.3s ease-in",
}}
>
<Typography text={message} variant="sm" color="white" />
<button
onClick={() => {
setIsVisible(false)
setTimeout(onClose, 300)
}}
style={{
background: "none",
border: "none",
color: "white",
cursor: "pointer",
fontSize: "1.2rem",
padding: "0",
lineHeight: 1,
}}
>
Γ—
</button>
</div>
)
}

export default Notification
7 changes: 3 additions & 4 deletions frontend/src/components/molecules/LoginForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,14 @@ const LoginForm: React.FC<LoginFormProps> = ({
password,
isLoading,
error,
role,
onChange,
onSubmit,
onToggleMode,
onForgotPassword,
}) => {
const inputFields = LOGIN_FORM_INPUT_FIELDS(email, password);
const handleGoogleLogin = (role: string) => {
window.location.href = `http://localhost:8080/api/v1/oauth/google/init?role=${role}`;
const handleGoogleLogin = () => {
window.location.href = "http://localhost:8080/oauth2/authorization/google";
};

return (
Expand Down Expand Up @@ -79,7 +78,7 @@ const LoginForm: React.FC<LoginFormProps> = ({
/>

<SocialLoginButtons
onGoogleClick={() => handleGoogleLogin(role)}
onGoogleClick={() => handleGoogleLogin()}
onGithubClick={() => console.log("GitHub Login")}
onTwitterClick={() => console.log("Twitter Login")}
/>
Expand Down
Loading
Loading