Skip to content
Open

6 #29

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
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
VITE_HR_PORTAL_API_URL=https://hr-portal.example.com/api
VITE_APP_NAME=QuickMeet
VITE_SUPPORTED_BROWSERS=chrome,firefox,safari,edge
58 changes: 32 additions & 26 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,43 @@ import { ROUTES } from './config/routes';
import Settings from '@/pages/Settings';
import BaseLayout from '@/pages/BaseLayout';
import OAuth from '@/pages/Oauth';
import { LanguageProvider } from '@/context/LanguageContext';
import { UserProvider } from '@/context/UserContext';

function App() {
return (
<AppTheme>
<Routes>
<Route
element={
<BaseLayout>
<Outlet />
</BaseLayout>
}
>
<Route path={ROUTES.home} element={<Home />} />
<Route path={ROUTES.signIn} element={<Login />} />
<Route path={ROUTES.oauth} element={<OAuth />} />
<Route path={ROUTES.settings} element={<Settings />} />
</Route>
</Routes>
<Toaster
position="top-center"
containerStyle={{
fontFamily: FONT_PRIMARY,
}}
toastOptions={{
error: {
duration: 5000,
},
}}
/>
<UserProvider>
<LanguageProvider>
<Routes>
<Route
element={
<BaseLayout>
<Outlet />
</BaseLayout>
}
>
<Route path={ROUTES.home} element={<Home />} />
<Route path={ROUTES.signIn} element={<Login />} />
<Route path={ROUTES.oauth} element={<OAuth />} />
<Route path={ROUTES.settings} element={<Settings />} />
</Route>
</Routes>
<Toaster
position="top-center"
containerStyle={{
fontFamily: FONT_PRIMARY,
}}
toastOptions={{
error: {
duration: 5000,
},
}}
/>
</LanguageProvider>
</UserProvider>
</AppTheme>
);
}

export default App;
export default App;
85 changes: 85 additions & 0 deletions client/src/api/hrportal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { ResourceType, Resource } from '@/config/resourceTypes';

const HR_PORTAL_BASE_URL = process.env.VITE_HR_PORTAL_API_URL;

export interface HREvent {
id: string;
title: string;
description: string;
startTime: string;
endTime: string;
resourceId: string;
organizer: {
id: string;
name: string;
email: string;
};
}

export const hrPortalApi = {
// Get events for a specific resource type
async getEvents(resourceType: string): Promise<HREvent[]> {
const response = await fetch(`${HR_PORTAL_BASE_URL}/events?type=${resourceType}`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`,
'Content-Type': 'application/json',
},
});

if (!response.ok) {
throw new Error('Failed to fetch events');
}

return response.json();
},

// Create a new event
async createEvent(eventData: Partial<HREvent>): Promise<HREvent> {
const response = await fetch(`${HR_PORTAL_BASE_URL}/events`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`,
'Content-Type': 'application/json',
},
body: JSON.stringify(eventData),
});

if (!response.ok) {
throw new Error('Failed to create event');
}

return response.json();
},

// Get available resources
async getResources(resourceType: string): Promise<Resource[]> {
const response = await fetch(`${HR_PORTAL_BASE_URL}/resources?type=${resourceType}`, {
headers: {
'Authorization': `Bearer ${localStorage.getItem('auth_token')}`,
'Content-Type': 'application/json',
},
});

if (!response.ok) {
throw new Error('Failed to fetch resources');
}

return response.json();
},

// Verify user session
async verifySession(token: string): Promise<any> {
const response = await fetch(`${HR_PORTAL_BASE_URL}/auth/verify`, {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});

if (!response.ok) {
throw new Error('Session verification failed');
}

return response.json();
}
};
31 changes: 31 additions & 0 deletions client/src/components/LanguageSelector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import { useLanguage } from '@/context/LanguageContext';
import { SUPPORTED_LANGUAGES } from '@/config/i18n';
import { Select, MenuItem, FormControl, InputLabel, SelectChangeEvent } from '@mui/material';

const LanguageSelector: React.FC = () => {
const { language, setLanguage } = useLanguage();

const handleLanguageChange = (event: SelectChangeEvent) => {
setLanguage(event.target.value);
};

return (
<FormControl size="small" sx={{ minWidth: 120 }}>
<InputLabel>Language</InputLabel>
<Select
value={language}
label="Language"
onChange={handleLanguageChange}
>
{Object.entries(SUPPORTED_LANGUAGES).map(([code, name]) => (
<MenuItem key={code} value={code}>
{name}
</MenuItem>
))}
</Select>
</FormControl>
);
};

export default LanguageSelector;
45 changes: 45 additions & 0 deletions client/src/components/UserProfile.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { useUser } from '@/context/UserContext';
import { useLanguage } from '@/context/LanguageContext';
import { styled } from '@mui/material/styles';
import { Avatar, Box, Typography, Chip } from '@mui/material';

const ProfileContainer = styled(Box)(({ theme }) => ({
display: 'flex',
alignItems: 'center',
gap: theme.spacing(1),
padding: theme.spacing(1),
borderRadius: theme.shape.borderRadius,
backgroundColor: theme.palette.background.paper,
}));

const UserProfile: React.FC = () => {
const { user } = useUser();
const { t } = useLanguage();

if (!user) return null;

return (
<ProfileContainer>
<Avatar src={user.avatar} alt={user.name}>
{user.name.charAt(0).toUpperCase()}
</Avatar>
<Box>
<Typography variant="subtitle2" noWrap>
{user.name}
</Typography>
<Typography variant="caption" color="text.secondary" noWrap>
{user.email}
</Typography>
<Chip
label={user.organization}
size="small"
variant="outlined"
sx={{ mt: 0.5 }}
/>
</Box>
</ProfileContainer>
);
};

export default UserProfile;
40 changes: 40 additions & 0 deletions client/src/components/resourceTypeselector.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import React from 'react';
import { useLanguage } from '@/context/LanguageContext';
import { RESOURCE_TYPES } from '@/config/resourceTypes';
import { ToggleButtonGroup, ToggleButton, Typography } from '@mui/material';

interface ResourceTypeSelectorProps {
value: string;
onChange: (resourceType: string) => void;
}

const ResourceTypeSelector: React.FC<ResourceTypeSelectorProps> = ({ value, onChange }) => {
const { t } = useLanguage();

const handleChange = (event: React.MouseEvent<HTMLElement>, newValue: string) => {
if (newValue !== null) {
onChange(newValue);
}
};

return (
<ToggleButtonGroup
value={value}
exclusive
onChange={handleChange}
aria-label="resource type"
sx={{ mb: 3 }}
>
{RESOURCE_TYPES.map((type) => (
<ToggleButton key={type.id} value={type.id} sx={{ px: 3, py: 1 }}>
<Typography variant="body2" sx={{ mr: 1 }}>
{type.icon}
</Typography>
{t(type.name.toLowerCase())}
</ToggleButton>
))}
</ToggleButtonGroup>
);
};

export default ResourceTypeSelector;
17 changes: 17 additions & 0 deletions client/src/config/i18n.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export const SUPPORTED_LANGUAGES = {
en: 'English',
es: 'Español',
fr: 'Français',
de: 'Deutsch',
it: 'Italiano',
ja: '日本語',
ko: '한국어',
zh: '中文'
};

export const DEFAULT_LANGUAGE = 'en';

export const getBrowserLanguage = (): string => {
const lang = navigator.language.split('-')[0];
return Object.keys(SUPPORTED_LANGUAGES).includes(lang) ? lang : DEFAULT_LANGUAGE;
};
51 changes: 51 additions & 0 deletions client/src/config/resourceTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
export interface ResourceType {
id: string;
name: string;
icon: string;
resources: Resource[];
}

export interface Resource {
id: string;
name: string;
description?: string;
capacity?: number;
features?: string[];
}

export const RESOURCE_TYPES: ResourceType[] = [
{
id: 'rooms',
name: 'Rooms',
icon: '🏢',
resources: [
{ id: 'room-1', name: 'Conference Room A', capacity: 10, features: ['Projector', 'Whiteboard'] },
{ id: 'room-2', name: 'Conference Room B', capacity: 6, features: ['TV', 'Video Conferencing'] },
{ id: 'room-3', name: 'Meeting Room C', capacity: 4, features: ['Whiteboard'] }
]
},
{
id: 'cars',
name: 'Cars',
icon: '🚗',
resources: [
{ id: 'car-1', name: 'Company Car 1', description: 'Toyota Camry' },
{ id: 'car-2', name: 'Company Car 2', description: 'Honda Accord' },
{ id: 'car-3', name: 'Company Car 3', description: 'Ford Fusion' }
]
},
{
id: 'devices',
name: 'Devices',
icon: '💻',
resources: [
{ id: 'device-1', name: 'Laptop 1', description: 'MacBook Pro' },
{ id: 'device-2', name: 'Laptop 2', description: 'Dell XPS' },
{ id: 'device-3', name: 'Projector 1', description: 'Epson EB-U42' }
]
}
];

export const getResourceTypeById = (id: string): ResourceType | undefined => {
return RESOURCE_TYPES.find(type => type.id === id);
};
Loading