Skip to content

Commit 3497adb

Browse files
authored
Merge pull request #1 from incial/develop
feat: add avatarUrl to User model and implement avatar display
2 parents 2c8f078 + 2d5acf4 commit 3497adb

5 files changed

Lines changed: 37 additions & 18 deletions

File tree

client/components/MainLayout.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
import React from 'react';
2+
import React, { useState } from 'react';
33
import { User } from '../types';
44
import Sidebar from './Sidebar';
55
import { LayoutProvider, useLayout } from '../context/LayoutContext';
@@ -14,6 +14,7 @@ interface MainLayoutProps {
1414
// Separate internal component to consume context
1515
const LayoutContent: React.FC<MainLayoutProps> = ({ user, onLogout, children }) => {
1616
const { isSidebarOpen, toggleSidebar, isMobile } = useLayout();
17+
const [imageError, setImageError] = useState(false);
1718

1819
return (
1920
<div className="min-h-screen flex relative">
@@ -45,8 +46,20 @@ const LayoutContent: React.FC<MainLayoutProps> = ({ user, onLogout, children })
4546
<span className="font-black text-slate-800 tracking-tight">MEOWENDI</span>
4647
</div>
4748
</div>
48-
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center text-white font-bold text-xs shadow-md">
49-
{user.name.charAt(0)}
49+
{/* Mobile header avatar */}
50+
<div className="relative w-8 h-8">
51+
{user.avatarUrl && user.avatarUrl.trim() !== '' && !imageError ? (
52+
<img
53+
src={user.avatarUrl}
54+
alt={user.name}
55+
className="w-8 h-8 rounded-full object-cover shadow-md"
56+
onError={() => setImageError(true)}
57+
/>
58+
) : (
59+
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-indigo-500 to-violet-600 flex items-center justify-center text-white font-bold text-xs shadow-md">
60+
{user.name.charAt(0)}
61+
</div>
62+
)}
5063
</div>
5164
</div>
5265
)}

client/components/Sidebar.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11

2-
import React from 'react';
2+
import React, { useState } from 'react';
33
import { Link, useLocation } from 'react-router-dom';
44
import { User, UserRole } from '../types';
55
import { useLayout } from '../context/LayoutContext';
@@ -24,6 +24,7 @@ interface SidebarProps {
2424
const Sidebar: React.FC<SidebarProps> = ({ user, onLogout }) => {
2525
const { isSidebarOpen, toggleSidebar, isMobile } = useLayout();
2626
const location = useLocation();
27+
const [imageError, setImageError] = useState(false);
2728

2829
const navItems = user.role === UserRole.ADMIN
2930
? [
@@ -124,12 +125,23 @@ const Sidebar: React.FC<SidebarProps> = ({ user, onLogout }) => {
124125
);
125126
})}
126127
</nav>
127-
128128
{/* User Footer */}
129129
<div className="p-4 border-t border-white/5 bg-black/20">
130130
<div className={`flex items-center gap-3 ${isSidebarOpen ? '' : 'justify-center'}`}>
131-
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-indigo-400 to-indigo-600 ring-2 ring-white/10 flex items-center justify-center text-white font-bold shadow-lg shrink-0">
132-
{user.name.charAt(0)}
131+
{/* Avatar - show profile picture if available, otherwise show initials */}
132+
<div className="relative w-10 h-10 shrink-0">
133+
{user.avatarUrl && user.avatarUrl.trim() !== '' && !imageError ? (
134+
<img
135+
src={user.avatarUrl}
136+
alt={user.name}
137+
className="w-10 h-10 rounded-full ring-2 ring-white/10 object-cover shadow-lg"
138+
onError={() => setImageError(true)}
139+
/>
140+
) : (
141+
<div className="w-10 h-10 rounded-full bg-gradient-to-br from-indigo-400 to-indigo-600 ring-2 ring-white/10 flex items-center justify-center text-white font-bold shadow-lg">
142+
{user.name.charAt(0)}
143+
</div>
144+
)}
133145
</div>
134146

135147
{isSidebarOpen && (

client/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ export interface User {
2323
email: string;
2424
role: UserRole;
2525
outletId?: string | null; // null for ADMIN, UUID for REFILLER
26+
avatarUrl?: string | null; // Profile picture URL (from Google login or manual upload)
2627
}
2728

2829
export interface StockEntry {

server/src/main/java/com/incial/stockflow/dto/response/UserResponse.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,5 @@ public class UserResponse {
1818
private String email;
1919
private UserRole role;
2020
private UUID outletId;
21+
private String avatarUrl;
2122
}

server/src/main/java/com/incial/stockflow/service/AuthService.java

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
package com.incial.stockflow.service;
22

3-
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
4-
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
5-
import com.google.api.client.http.javanet.NetHttpTransport;
6-
import com.google.api.client.json.gson.GsonFactory;
7-
import com.incial.stockflow.dto.request.GoogleLoginRequest;
83
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
94
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
105
import com.google.api.client.http.javanet.NetHttpTransport;
@@ -20,8 +15,7 @@
2015
import lombok.RequiredArgsConstructor;
2116
import org.springframework.beans.factory.annotation.Value;
2217
import org.springframework.security.core.userdetails.UsernameNotFoundException;
23-
import org.springframework.beans.factory.annotation.Value;
24-
import org.springframework.security.core.userdetails.UsernameNotFoundException;
18+
2519
import org.springframework.security.crypto.password.PasswordEncoder;
2620
import org.springframework.stereotype.Service;
2721
import org.springframework.transaction.annotation.Transactional;
@@ -30,10 +24,6 @@
3024
import java.security.GeneralSecurityException;
3125
import java.util.Collections;
3226

33-
import java.io.IOException;
34-
import java.security.GeneralSecurityException;
35-
import java.util.Collections;
36-
3727
@Service
3828
@RequiredArgsConstructor
3929
public class AuthService {
@@ -66,6 +56,7 @@ public LoginResponse login(LoginRequest request) {
6656
.email(user.getEmail())
6757
.role(user.getRole())
6858
.outletId(user.getOutlet() != null ? user.getOutlet().getId() : null)
59+
.avatarUrl(user.getAvatarUrl())
6960
.build();
7061

7162
return LoginResponse.builder()
@@ -129,6 +120,7 @@ public LoginResponse loginWithGoogle(GoogleLoginRequest request) {
129120
.email(user.getEmail())
130121
.role(user.getRole())
131122
.outletId(user.getOutlet() != null ? user.getOutlet().getId() : null)
123+
.avatarUrl(user.getAvatarUrl())
132124
.build();
133125

134126
return LoginResponse.builder()

0 commit comments

Comments
 (0)