eli02 commited on
Commit
c2adf08
·
1 Parent(s): 3299552

feat: Update Dockerfile, README, and Nginx configuration for improved deployment and documentation; enhance search functionality and UI components

Browse files
Dockerfile CHANGED
@@ -2,16 +2,26 @@
2
  FROM node:20-alpine as build
3
 
4
  WORKDIR /app
 
 
5
  COPY package*.json ./
6
- RUN npm install
 
 
7
  COPY . .
 
 
8
  RUN npm run build
9
 
10
- # Production stage
11
- FROM nginx:stable
12
 
 
13
  COPY --from=build /app/build /usr/share/nginx/html
14
  COPY nginx.conf /etc/nginx/conf.d/default.conf
15
 
16
- EXPOSE 80
 
 
 
17
  CMD ["nginx", "-g", "daemon off;"]
 
2
  FROM node:20-alpine as build
3
 
4
  WORKDIR /app
5
+
6
+ # Copy package.json and install dependencies
7
  COPY package*.json ./
8
+ RUN npm ci
9
+
10
+ # Copy the rest of the application code
11
  COPY . .
12
+
13
+ # Build the app
14
  RUN npm run build
15
 
16
+ # Production stage with nginx
17
+ FROM nginx:alpine
18
 
19
+ # Copy built app to nginx
20
  COPY --from=build /app/build /usr/share/nginx/html
21
  COPY nginx.conf /etc/nginx/conf.d/default.conf
22
 
23
+ # Expose port
24
+ EXPOSE 3000
25
+
26
+ # Start nginx
27
  CMD ["nginx", "-g", "daemon off;"]
README.md CHANGED
@@ -8,5 +8,30 @@ app_port: 3000
8
  pinned: false
9
  license: mit
10
  duplicated_from: HumbleBeeAI/al-ghazali-rag-retrieval-api
11
- enable_emotes: true # Allows reactions on your Space
12
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
  pinned: false
9
  license: mit
10
  duplicated_from: HumbleBeeAI/al-ghazali-rag-retrieval-api
11
+ ---
12
+
13
+ # EnlightenQalb: Al-Ghazali's Wisdom Search
14
+
15
+ A React application that allows users to search through "The Alchemy of Happiness" by Imam Al-Ghazali using modern AI-powered semantic search technology.
16
+
17
+ ## Features
18
+
19
+ - Semantic search across Al-Ghazali's text
20
+ - Compare results from different embedding models
21
+ - Simple emoji-based feedback system
22
+ - Light/dark theme support
23
+
24
+ ## Technical Details
25
+
26
+ This application uses:
27
+ - React with Material UI for the frontend
28
+ - Sentence Transformer models (BGE-Large and UAE-Large) on the backend
29
+ - Docker for containerization
30
+
31
+ ## Development
32
+
33
+ To run locally:
34
+
35
+ ```bash
36
+ npm install
37
+ npm start
nginx.conf CHANGED
@@ -1,5 +1,5 @@
1
  server {
2
- listen 80;
3
  server_name localhost;
4
 
5
  location / {
@@ -10,10 +10,11 @@ server {
10
 
11
  # Proxy API requests to your backend
12
  location /api/ {
13
- proxy_pass https://huggingface.co/spaces/HumbleBeeAI/al-ghazali-rag-retrieval-api/;
14
  proxy_set_header Host $host;
15
  proxy_set_header X-Real-IP $remote_addr;
16
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 
17
  }
18
 
19
  error_page 500 502 503 504 /50x.html;
 
1
  server {
2
+ listen 3000; # Changed from 80 to 3000 for HF Spaces
3
  server_name localhost;
4
 
5
  location / {
 
10
 
11
  # Proxy API requests to your backend
12
  location /api/ {
13
+ proxy_pass https://humblebeeai-al-ghazali-rag-retrieval-api.hf.space/;
14
  proxy_set_header Host $host;
15
  proxy_set_header X-Real-IP $remote_addr;
16
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
17
+ proxy_set_header X-Forwarded-Proto $scheme;
18
  }
19
 
20
  error_page 500 502 503 504 /50x.html;
src/components/Search/FeedbackForm.jsx CHANGED
@@ -31,16 +31,10 @@ const FeedbackForm = ({ searchResult, query, onFeedbackSubmitted }) => {
31
  const [isSubmitting, setIsSubmitting] = useState(false);
32
  const [error, setError] = useState(null);
33
  const [success, setSuccess] = useState(false);
34
-
35
- // Debug to check what searchResult contains
36
- console.log('SearchResult:', searchResult);
37
 
38
- // Ensure we have a proper searchResult object
39
  if (!searchResult || typeof searchResult !== 'object') {
40
  return (
41
- <StyledPaper elevation={2}>
42
- <Alert severity="error">Invalid search result data</Alert>
43
- </StyledPaper>
44
  );
45
  }
46
 
@@ -50,15 +44,17 @@ const FeedbackForm = ({ searchResult, query, onFeedbackSubmitted }) => {
50
  setError(null);
51
 
52
  try {
53
- // Extract only the needed text properties safely
54
  const feedbackData = {
55
  user_type: 'user',
56
  username: user || 'anonymous',
57
  query: query || '',
58
  retrieved_text: searchResult.content || searchResult.text || JSON.stringify(searchResult),
59
  model_type: searchResult.model_type || 'unknown',
 
60
  reaction,
61
- confidence_score: confidence,
 
62
  rating
63
  };
64
 
@@ -148,4 +144,4 @@ const FeedbackForm = ({ searchResult, query, onFeedbackSubmitted }) => {
148
  );
149
  };
150
 
151
- export default FeedbackForm;
 
31
  const [isSubmitting, setIsSubmitting] = useState(false);
32
  const [error, setError] = useState(null);
33
  const [success, setSuccess] = useState(false);
 
 
 
34
 
 
35
  if (!searchResult || typeof searchResult !== 'object') {
36
  return (
37
+ <Alert severity="error">Invalid search result data</Alert>
 
 
38
  );
39
  }
40
 
 
44
  setError(null);
45
 
46
  try {
47
+ // Extract only the needed properties for the backend
48
  const feedbackData = {
49
  user_type: 'user',
50
  username: user || 'anonymous',
51
  query: query || '',
52
  retrieved_text: searchResult.content || searchResult.text || JSON.stringify(searchResult),
53
  model_type: searchResult.model_type || 'unknown',
54
+ result_label: searchResult.resultLabel || '',
55
  reaction,
56
+ confidence_score: searchResult.similarity || 0, // Send the model confidence score
57
+ user_confidence: confidence, // User's confidence in their rating
58
  rating
59
  };
60
 
 
144
  );
145
  };
146
 
147
+ export default FeedbackForm;
src/components/Search/SearchForm.jsx CHANGED
@@ -5,10 +5,7 @@ import {
5
  Button,
6
  Typography,
7
  Paper,
8
- CircularProgress,
9
- Grid,
10
- ToggleButton,
11
- ToggleButtonGroup
12
  } from '@mui/material';
13
  import SearchIcon from '@mui/icons-material/Search';
14
  import styled from '@emotion/styled';
@@ -20,12 +17,11 @@ const StyledPaper = styled(Paper)(({ theme }) => ({
20
 
21
  const SearchForm = ({ onSearch, isLoading }) => {
22
  const [query, setQuery] = useState('');
23
- const [modelPreference, setModelPreference] = useState('both');
24
 
25
  const handleSubmit = (e) => {
26
  e.preventDefault();
27
  if (query.trim()) {
28
- onSearch(query, modelPreference);
29
  }
30
  };
31
 
@@ -59,28 +55,6 @@ const SearchForm = ({ onSearch, isLoading }) => {
59
  ),
60
  }}
61
  />
62
-
63
- <Box sx={{ mt: 2 }}>
64
- <Typography variant="subtitle2" gutterBottom>
65
- Model Preference:
66
- </Typography>
67
- <ToggleButtonGroup
68
- value={modelPreference}
69
- exclusive
70
- onChange={(e, newValue) => newValue && setModelPreference(newValue)}
71
- aria-label="model preference"
72
- >
73
- <ToggleButton value="uae" aria-label="uae model">
74
- UAE-Large
75
- </ToggleButton>
76
- <ToggleButton value="bge" aria-label="bge model">
77
- BGE-Large
78
- </ToggleButton>
79
- <ToggleButton value="both" aria-label="both models">
80
- Both
81
- </ToggleButton>
82
- </ToggleButtonGroup>
83
- </Box>
84
  </Box>
85
  </StyledPaper>
86
  );
 
5
  Button,
6
  Typography,
7
  Paper,
8
+ CircularProgress
 
 
 
9
  } from '@mui/material';
10
  import SearchIcon from '@mui/icons-material/Search';
11
  import styled from '@emotion/styled';
 
17
 
18
  const SearchForm = ({ onSearch, isLoading }) => {
19
  const [query, setQuery] = useState('');
 
20
 
21
  const handleSubmit = (e) => {
22
  e.preventDefault();
23
  if (query.trim()) {
24
+ onSearch(query, 'both'); // Always use both models
25
  }
26
  };
27
 
 
55
  ),
56
  }}
57
  />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  </Box>
59
  </StyledPaper>
60
  );
src/components/Search/SearchResults.jsx CHANGED
@@ -1,49 +1,65 @@
1
- import React, { useState } from 'react';
2
  import {
3
  Box,
4
  Paper,
5
  Typography,
6
- Divider,
7
- Chip,
8
- CircularProgress,
9
- Accordion,
10
- AccordionSummary,
11
- AccordionDetails,
12
- Alert
13
  } from '@mui/material';
14
- import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
15
  import styled from '@emotion/styled';
16
- import FeedbackForm from './FeedbackForm';
 
17
 
18
- const StyledPaper = styled(Paper)(({ theme }) => ({
19
- padding: theme.spacing(2),
20
- marginBottom: theme.spacing(2),
21
- backgroundColor: theme.palette.background.paper,
 
 
22
  }));
23
 
24
- const ModelChip = styled(Chip)(({ theme, modeltype }) => ({
25
- marginLeft: theme.spacing(1),
26
- backgroundColor: modeltype === 'WhereIsAI_UAE_Large_V1'
27
- ? theme.palette.primary.light
28
- : theme.palette.secondary.light,
29
- color: theme.palette.getContrastText(
30
- modeltype === 'WhereIsAI_UAE_Large_V1'
31
- ? theme.palette.primary.light
32
- : theme.palette.secondary.light
33
- ),
34
  }));
35
 
36
  const SearchResults = ({ results, query, isLoading, error }) => {
37
- const [expandedResult, setExpandedResult] = useState(null);
38
- const [feedbackSubmitted, setFeedbackSubmitted] = useState(false);
 
 
39
 
40
- if (isLoading) {
41
- return (
42
- <Box display="flex" justifyContent="center" my={4}>
43
- <CircularProgress />
44
- </Box>
45
- );
46
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
 
48
  if (error) {
49
  return (
@@ -61,49 +77,101 @@ const SearchResults = ({ results, query, isLoading, error }) => {
61
  );
62
  }
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  return (
65
  <Box>
66
  <Typography variant="h6" gutterBottom>
67
  Search Results
68
  </Typography>
69
 
70
- {results.map((result, index) => (
71
- <Accordion
72
- key={index}
73
- expanded={expandedResult === index}
74
- onChange={() => setExpandedResult(expandedResult === index ? null : index)}
75
- >
76
- <AccordionSummary expandIcon={<ExpandMoreIcon />}>
77
- <Box width="100%">
78
- <Box display="flex" alignItems="center">
79
- <Typography variant="subtitle1" noWrap sx={{ flexGrow: 1 }}>
80
- Excerpt from "{result.model_type.includes('UAE') ? 'UAE' : 'BGE'} Model"
81
- </Typography>
82
- <ModelChip
83
- label={`${(result.similarity * 100).toFixed(1)}% match`}
84
- modeltype={result.model_type}
85
- />
86
- </Box>
87
- <Typography variant="caption" color="textSecondary">
88
- Click to {expandedResult === index ? 'collapse' : 'expand'}
89
  </Typography>
90
- </Box>
91
- </AccordionSummary>
92
- <AccordionDetails>
93
- <Typography variant="body1" paragraph>
94
- {result.text}
95
- </Typography>
96
-
97
- <Divider sx={{ my: 2 }} />
98
-
99
- <FeedbackForm
100
- searchResult={result}
101
- query={query}
102
- onFeedbackSubmitted={() => setFeedbackSubmitted(true)}
103
- />
104
- </AccordionDetails>
105
- </Accordion>
106
- ))}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  </Box>
108
  );
109
  };
 
1
+ import React, { useState, useEffect } from 'react';
2
  import {
3
  Box,
4
  Paper,
5
  Typography,
6
+ IconButton,
7
+ Alert,
8
+ Grid
 
 
 
 
9
  } from '@mui/material';
 
10
  import styled from '@emotion/styled';
11
+ import { saveFeedback } from '../../services/search';
12
+ import { useAuth } from '../../hooks/useAuth';
13
 
14
+ const ResultCard = styled(Paper)(({ theme }) => ({
15
+ padding: theme.spacing(3),
16
+ height: '100%',
17
+ display: 'flex',
18
+ flexDirection: 'column',
19
+ position: 'relative',
20
  }));
21
 
22
+ const EmojiButton = styled(IconButton)(({ theme }) => ({
23
+ fontSize: '1.5rem',
24
+ padding: theme.spacing(1),
 
 
 
 
 
 
 
25
  }));
26
 
27
  const SearchResults = ({ results, query, isLoading, error }) => {
28
+ const { user, authTokens } = useAuth();
29
+ const [shuffledResults, setShuffledResults] = useState([]);
30
+ const [feedbackStatus, setFeedbackStatus] = useState({});
31
+ const [savingFeedback, setSavingFeedback] = useState(false);
32
 
33
+ // Shuffle and limit to 2 results when results change
34
+ useEffect(() => {
35
+ if (results && results.length) {
36
+ // Get one result from each model type if possible
37
+ const bgeResult = results.find(r => r.model_type?.includes('BGE'));
38
+ const uaeResult = results.find(r => r.model_type?.includes('UAE'));
39
+
40
+ let selectedResults = [];
41
+ if (bgeResult && uaeResult) {
42
+ selectedResults = [bgeResult, uaeResult];
43
+ } else {
44
+ // If we don't have both models, take top 2 (or all if less than 2)
45
+ selectedResults = results.slice(0, 2);
46
+ }
47
+
48
+ // Randomly shuffle the order
49
+ const shuffled = [...selectedResults].sort(() => Math.random() - 0.5);
50
+
51
+ // Assign labels (A and B)
52
+ setShuffledResults(shuffled.map((result, index) => ({
53
+ ...result,
54
+ resultLabel: index === 0 ? 'A' : 'B'
55
+ })));
56
+
57
+ // Reset feedback status
58
+ setFeedbackStatus({});
59
+ } else {
60
+ setShuffledResults([]);
61
+ }
62
+ }, [results]);
63
 
64
  if (error) {
65
  return (
 
77
  );
78
  }
79
 
80
+ const handleReaction = async (result, reaction) => {
81
+ // Prevent multiple submissions
82
+ if (feedbackStatus[result.resultLabel] || savingFeedback) return;
83
+
84
+ setSavingFeedback(true);
85
+
86
+ try {
87
+ // Prepare feedback data
88
+ const feedbackData = {
89
+ user_type: 'user',
90
+ username: user || 'anonymous',
91
+ query: query || '',
92
+ retrieved_text: result.content || result.text || JSON.stringify(result),
93
+ model_type: result.model_type || 'unknown',
94
+ result_label: result.resultLabel || '',
95
+ reaction: reaction === '👍' ? 'relevant' : reaction === '👎' ? 'not_relevant' : 'somewhat_relevant',
96
+ confidence_score: result.similarity || 0, // Send the model confidence score
97
+ };
98
+
99
+ // Save the feedback
100
+ await saveFeedback(feedbackData, authTokens.access);
101
+
102
+ // Update UI to show feedback was saved
103
+ setFeedbackStatus(prev => ({
104
+ ...prev,
105
+ [result.resultLabel]: { status: 'success', reaction }
106
+ }));
107
+
108
+ } catch (err) {
109
+ console.error("Feedback submission error:", err);
110
+ setFeedbackStatus(prev => ({
111
+ ...prev,
112
+ [result.resultLabel]: { status: 'error', message: 'Failed to save feedback' }
113
+ }));
114
+ } finally {
115
+ setSavingFeedback(false);
116
+ }
117
+ };
118
+
119
  return (
120
  <Box>
121
  <Typography variant="h6" gutterBottom>
122
  Search Results
123
  </Typography>
124
 
125
+ <Grid container spacing={3} sx={{ mb: 4 }}>
126
+ {shuffledResults.map((result) => (
127
+ <Grid item xs={12} md={6} key={result.resultLabel}>
128
+ <ResultCard elevation={3}>
129
+ <Typography variant="h6" gutterBottom>
130
+ Result {result.resultLabel}
131
+ </Typography>
132
+ <Typography variant="body1" paragraph sx={{ flexGrow: 1 }}>
133
+ {result.text}
 
 
 
 
 
 
 
 
 
 
134
  </Typography>
135
+
136
+ <Box mt={2}>
137
+ {feedbackStatus[result.resultLabel]?.status === 'success' ? (
138
+ <Typography variant="body2" color="success.main" align="center">
139
+ Thank you for your feedback! You selected {feedbackStatus[result.resultLabel].reaction}
140
+ </Typography>
141
+ ) : feedbackStatus[result.resultLabel]?.status === 'error' ? (
142
+ <Typography variant="body2" color="error" align="center">
143
+ {feedbackStatus[result.resultLabel].message}
144
+ </Typography>
145
+ ) : (
146
+ <Box display="flex" justifyContent="center" gap={2}>
147
+ <EmojiButton
148
+ onClick={() => handleReaction(result, '👍')}
149
+ disabled={savingFeedback}
150
+ aria-label="Good result"
151
+ >
152
+ 👍
153
+ </EmojiButton>
154
+ <EmojiButton
155
+ onClick={() => handleReaction(result, '🤷‍♂️')}
156
+ disabled={savingFeedback}
157
+ aria-label="Somewhat relevant"
158
+ >
159
+ 🤷‍♂️
160
+ </EmojiButton>
161
+ <EmojiButton
162
+ onClick={() => handleReaction(result, '👎')}
163
+ disabled={savingFeedback}
164
+ aria-label="Not relevant"
165
+ >
166
+ 👎
167
+ </EmojiButton>
168
+ </Box>
169
+ )}
170
+ </Box>
171
+ </ResultCard>
172
+ </Grid>
173
+ ))}
174
+ </Grid>
175
  </Box>
176
  );
177
  };
src/components/UI/ThemeToggle.jsx CHANGED
@@ -1,23 +1,32 @@
1
- import React from 'react';
2
- import { IconButton, Tooltip } from '@mui/material';
3
  import { Brightness4, Brightness7 } from '@mui/icons-material';
4
- import { useTheme as useMuiTheme } from '@mui/material/styles';
5
  import { useTheme } from '../../hooks/useTheme';
6
 
7
  const ThemeToggle = ({ size = 'medium' }) => {
8
  const { toggleTheme, isDark } = useTheme();
9
  const theme = useMuiTheme();
10
 
 
 
 
 
11
  return (
12
  <Tooltip title={`Switch to ${isDark ? 'light' : 'dark'} mode`}>
13
  <IconButton
14
- onClick={toggleTheme}
15
  color="inherit"
16
  size={size}
17
  aria-label="toggle theme"
 
 
 
 
 
 
18
  >
19
  {isDark ? (
20
- <Brightness7 sx={{ color: theme.palette.warning?.main || theme.palette.primary.main }} />
21
  ) : (
22
  <Brightness4 sx={{ color: theme.palette.grey[600] }} />
23
  )}
 
1
+ import React, { useCallback } from 'react';
2
+ import { IconButton, Tooltip, useTheme as useMuiTheme } from '@mui/material';
3
  import { Brightness4, Brightness7 } from '@mui/icons-material';
 
4
  import { useTheme } from '../../hooks/useTheme';
5
 
6
  const ThemeToggle = ({ size = 'medium' }) => {
7
  const { toggleTheme, isDark } = useTheme();
8
  const theme = useMuiTheme();
9
 
10
+ const handleToggle = useCallback(() => {
11
+ toggleTheme();
12
+ }, [toggleTheme]);
13
+
14
  return (
15
  <Tooltip title={`Switch to ${isDark ? 'light' : 'dark'} mode`}>
16
  <IconButton
17
+ onClick={handleToggle}
18
  color="inherit"
19
  size={size}
20
  aria-label="toggle theme"
21
+ sx={{
22
+ transition: 'transform 0.3s ease',
23
+ '&:hover': {
24
+ transform: 'rotate(15deg)'
25
+ }
26
+ }}
27
  >
28
  {isDark ? (
29
+ <Brightness7 sx={{ color: theme.palette.warning?.main || '#ffc107' }} />
30
  ) : (
31
  <Brightness4 sx={{ color: theme.palette.grey[600] }} />
32
  )}
src/hooks/useSearch.js CHANGED
@@ -1,6 +1,5 @@
1
- // src/hooks/useSearch.js
2
  import { useState, useCallback } from 'react';
3
- import { searchQuery } from '../services/search';
4
  import { useAuth } from './useAuth';
5
 
6
  export const useSearch = () => {
@@ -8,6 +7,7 @@ export const useSearch = () => {
8
  const [results, setResults] = useState([]);
9
  const [isLoading, setIsLoading] = useState(false);
10
  const [error, setError] = useState(null);
 
11
 
12
  const handleSearch = useCallback(async (query) => {
13
  if (!query.trim()) {
@@ -15,16 +15,13 @@ export const useSearch = () => {
15
  return;
16
  }
17
 
 
18
  setIsLoading(true);
19
  setError(null);
20
 
21
  try {
22
- const searchResults = await searchQuery(query, authTokens.access);
23
-
24
- // Combine and sort results by similarity (highest first)
25
- const sortedResults = [...searchResults].sort((a, b) => b.similarity - a.similarity);
26
-
27
- setResults(sortedResults);
28
  } catch (err) {
29
  setError(err.message || 'Failed to perform search');
30
  setResults([]);
@@ -44,5 +41,6 @@ export const useSearch = () => {
44
  error,
45
  handleSearch,
46
  clearResults,
 
47
  };
48
  };
 
 
1
  import { useState, useCallback } from 'react';
2
+ import { searchQuery as searchQueryApi } from '../services/search';
3
  import { useAuth } from './useAuth';
4
 
5
  export const useSearch = () => {
 
7
  const [results, setResults] = useState([]);
8
  const [isLoading, setIsLoading] = useState(false);
9
  const [error, setError] = useState(null);
10
+ const [searchQuery, setSearchQuery] = useState('');
11
 
12
  const handleSearch = useCallback(async (query) => {
13
  if (!query.trim()) {
 
15
  return;
16
  }
17
 
18
+ setSearchQuery(query);
19
  setIsLoading(true);
20
  setError(null);
21
 
22
  try {
23
+ const searchResults = await searchQueryApi(query, authTokens.access);
24
+ setResults(searchResults);
 
 
 
 
25
  } catch (err) {
26
  setError(err.message || 'Failed to perform search');
27
  setResults([]);
 
41
  error,
42
  handleSearch,
43
  clearResults,
44
+ searchQuery,
45
  };
46
  };
src/hooks/useTheme.js CHANGED
@@ -23,7 +23,7 @@ const useThemeState = () => {
23
  const [themeLoading, setThemeLoading] = useState(true);
24
 
25
  useEffect(() => {
26
- // Apply the theme class to document body
27
  if (isDark) {
28
  document.body.classList.add('dark-mode');
29
  document.body.classList.remove('light-mode');
 
23
  const [themeLoading, setThemeLoading] = useState(true);
24
 
25
  useEffect(() => {
26
+ // Apply the theme class to document body immediately
27
  if (isDark) {
28
  document.body.classList.add('dark-mode');
29
  document.body.classList.remove('light-mode');
src/pages/Dashboard.jsx CHANGED
@@ -1,40 +1,21 @@
1
- import { useState, useEffect } from 'react';
2
- import { useNavigate } from 'react-router-dom';
3
  import { useAuth } from '../hooks/useAuth';
4
  import { useSearch } from '../hooks/useSearch';
5
  import SearchForm from '../components/Search/SearchForm';
6
  import SearchResults from '../components/Search/SearchResults';
7
- import FeedbackForm from '../components/Search/FeedbackForm';
8
  import LoadingSpinner from '../components/UI/LoadingSpinner';
9
  import ErrorMessage from '../components/UI/ErrorMessage';
10
  import { Box, Typography, Container } from '@mui/material';
11
 
12
  const Dashboard = () => {
13
- const { isAuthenticated, user } = useAuth();
14
  const {
15
- searchQuery,
16
  results,
17
- loading,
18
  error,
19
- handleSearch,
20
- handleFeedbackSubmit
21
  } = useSearch();
22
- const [selectedResult, setSelectedResult] = useState(null);
23
- const navigate = useNavigate();
24
-
25
- useEffect(() => {
26
- if (!isAuthenticated) {
27
- navigate('/login');
28
- }
29
- }, [isAuthenticated, navigate]);
30
-
31
- const handleResultSelect = (result) => {
32
- setSelectedResult(result);
33
- };
34
-
35
- if (!isAuthenticated) {
36
- return null;
37
- }
38
 
39
  return (
40
  <Container maxWidth="lg" sx={{ py: 4 }}>
@@ -43,28 +24,22 @@ const Dashboard = () => {
43
  </Typography>
44
 
45
  <Box sx={{ mb: 4 }}>
46
- <SearchForm onSearch={handleSearch} />
 
 
 
47
  </Box>
48
 
49
- {loading && <LoadingSpinner />}
50
  {error && <ErrorMessage message={error} />}
51
 
52
- {results && (
53
  <Box sx={{ mb: 4 }}>
54
  <SearchResults
55
  results={results}
56
  query={searchQuery}
57
- onSelect={handleResultSelect}
58
- />
59
- </Box>
60
- )}
61
-
62
- {selectedResult && (
63
- <Box sx={{ mb: 4 }}>
64
- <FeedbackForm
65
- searchResult={selectedResult}
66
- query={searchQuery}
67
- onFeedbackSubmitted={handleFeedbackSubmit}
68
  />
69
  </Box>
70
  )}
 
1
+ import { useState } from 'react';
 
2
  import { useAuth } from '../hooks/useAuth';
3
  import { useSearch } from '../hooks/useSearch';
4
  import SearchForm from '../components/Search/SearchForm';
5
  import SearchResults from '../components/Search/SearchResults';
 
6
  import LoadingSpinner from '../components/UI/LoadingSpinner';
7
  import ErrorMessage from '../components/UI/ErrorMessage';
8
  import { Box, Typography, Container } from '@mui/material';
9
 
10
  const Dashboard = () => {
11
+ const { user } = useAuth();
12
  const {
 
13
  results,
14
+ isLoading,
15
  error,
16
+ handleSearch,
17
+ searchQuery
18
  } = useSearch();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
 
20
  return (
21
  <Container maxWidth="lg" sx={{ py: 4 }}>
 
24
  </Typography>
25
 
26
  <Box sx={{ mb: 4 }}>
27
+ <SearchForm
28
+ onSearch={handleSearch}
29
+ isLoading={isLoading}
30
+ />
31
  </Box>
32
 
33
+ {isLoading && <LoadingSpinner />}
34
  {error && <ErrorMessage message={error} />}
35
 
36
+ {results && results.length > 0 && (
37
  <Box sx={{ mb: 4 }}>
38
  <SearchResults
39
  results={results}
40
  query={searchQuery}
41
+ isLoading={isLoading}
42
+ error={error}
 
 
 
 
 
 
 
 
 
43
  />
44
  </Box>
45
  )}
src/styles/theme.js CHANGED
@@ -1,5 +1,5 @@
1
  import { createTheme, ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
2
- import React, { useMemo } from 'react';
3
  import useTheme from '../hooks/useTheme';
4
 
5
  const getTheme = (isDark) => createTheme({
@@ -8,16 +8,89 @@ const getTheme = (isDark) => createTheme({
8
  primary: {
9
  main: isDark ? '#7986cb' : '#3f51b5',
10
  },
 
 
 
11
  background: {
12
  default: isDark ? '#121212' : '#f9f9f9',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
13
  },
14
  },
15
  });
16
 
 
17
  export const ThemeProvider = ({ children }) => {
18
  const { isDark } = useTheme();
19
-
 
20
  const theme = useMemo(() => getTheme(isDark), [isDark]);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  return <MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>;
23
  };
 
1
  import { createTheme, ThemeProvider as MuiThemeProvider } from '@mui/material/styles';
2
+ import React, { useMemo, useEffect } from 'react';
3
  import useTheme from '../hooks/useTheme';
4
 
5
  const getTheme = (isDark) => createTheme({
 
8
  primary: {
9
  main: isDark ? '#7986cb' : '#3f51b5',
10
  },
11
+ secondary: {
12
+ main: isDark ? '#ff9800' : '#f50057',
13
+ },
14
  background: {
15
  default: isDark ? '#121212' : '#f9f9f9',
16
+ paper: isDark ? '#1e1e1e' : '#ffffff',
17
+ },
18
+ text: {
19
+ primary: isDark ? '#ffffff' : '#121212',
20
+ secondary: isDark ? '#b0bec5' : '#666666',
21
+ },
22
+ // Add spiritual palette for specialized components
23
+ spiritual: {
24
+ light: isDark ? '#303f9f' : '#e8eaf6',
25
+ dark: isDark ? '#1a237e' : '#c5cae9',
26
+ accent: isDark ? '#ffcc80' : '#7986cb',
27
+ quoteBorder: isDark ? '#5c6bc0' : '#3f51b5',
28
+ },
29
+ },
30
+ typography: {
31
+ fontFamily: '"Poppins", "Roboto", "Helvetica", "Arial", sans-serif',
32
+ h1: {
33
+ fontWeight: 700,
34
+ },
35
+ h2: {
36
+ fontWeight: 600,
37
+ },
38
+ h3: {
39
+ fontWeight: 600,
40
+ },
41
+ h4: {
42
+ fontWeight: 600,
43
+ },
44
+ h5: {
45
+ fontWeight: 500,
46
+ },
47
+ h6: {
48
+ fontWeight: 500,
49
+ },
50
+ },
51
+ shape: {
52
+ borderRadius: 8,
53
+ },
54
+ components: {
55
+ MuiButton: {
56
+ styleOverrides: {
57
+ root: {
58
+ textTransform: 'none',
59
+ borderRadius: 8,
60
+ },
61
+ },
62
+ },
63
+ MuiCard: {
64
+ styleOverrides: {
65
+ root: {
66
+ borderRadius: 12,
67
+ },
68
+ },
69
  },
70
  },
71
  });
72
 
73
+ // Enhanced ThemeProvider that forces re-render on theme change
74
  export const ThemeProvider = ({ children }) => {
75
  const { isDark } = useTheme();
76
+
77
+ // Create a new theme whenever isDark changes
78
  const theme = useMemo(() => getTheme(isDark), [isDark]);
79
+
80
+ // Force a style update when theme changes
81
+ useEffect(() => {
82
+ // This helps force Material-UI to reapply styles
83
+ document.documentElement.style.setProperty(
84
+ '--mui-palette-mode',
85
+ isDark ? 'dark' : 'light'
86
+ );
87
+
88
+ // Apply additional custom CSS variables if needed
89
+ document.documentElement.style.setProperty(
90
+ '--app-background',
91
+ isDark ? '#121212' : '#f9f9f9'
92
+ );
93
+ }, [isDark]);
94
 
95
  return <MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>;
96
  };