alaselababatunde commited on
Commit
13f3cce
·
1 Parent(s): 8806dd4

style: replicate Domino's UI layout with Theo AI branding

Browse files
Files changed (1) hide show
  1. frontend/src/App.tsx +125 -129
frontend/src/App.tsx CHANGED
@@ -1,5 +1,5 @@
1
  import React, { useState, useEffect, useRef } from 'react';
2
- import { Send, User, Bot, Loader2 } from 'lucide-react';
3
  import ReactMarkdown from 'react-markdown';
4
  import axios from 'axios';
5
 
@@ -24,35 +24,38 @@ const App: React.FC = () => {
24
  const [isLoading, setIsLoading] = useState(false);
25
  const [sessionId] = useState<string>(() => Math.random().toString(36).substring(7));
26
  const [verse, setVerse] = useState(VERSES[0]);
27
- const chatEndRef = useRef<HTMLDivElement>(null);
 
 
28
 
29
  useEffect(() => {
30
  const dayOfYear = Math.floor((new Date().getTime() - new Date(new Date().getFullYear(), 0, 0).getTime()) / 86400000);
31
  setVerse(VERSES[dayOfYear % VERSES.length]);
32
  }, []);
33
 
34
- const isFirstRender = useRef(true);
35
-
36
  useEffect(() => {
37
  if (isFirstRender.current) {
38
  isFirstRender.current = false;
39
  return;
40
  }
41
- if (messages.length > 0) {
42
- chatEndRef.current?.scrollIntoView({ behavior: 'smooth' });
 
 
 
43
  }
44
- }, [messages]);
45
 
46
  const handleSend = async () => {
47
  if (!input.trim() || isLoading) return;
48
 
49
  const userMessage = input.trim();
50
  setInput('');
 
51
  setMessages(prev => [...prev, { role: 'user', content: userMessage }]);
52
  setIsLoading(true);
53
 
54
  try {
55
- // Note: Use relative path for Hugging Face deployment so it hits the same origin backend
56
  const response = await axios.post('/ai-chat', {
57
  query: userMessage,
58
  session_id: sessionId,
@@ -76,142 +79,135 @@ const App: React.FC = () => {
76
  }
77
  };
78
 
 
 
 
 
 
 
 
 
 
 
 
 
79
  return (
80
- <div className="h-[100dvh] bg-[#002B22] flex flex-col items-center justify-center p-0 md:p-4 font-outfit text-white overflow-hidden relative">
81
- {/* Dynamic Background Elements */}
82
- <div className="absolute top-[-20%] left-[-10%] w-[60%] h-[60%] bg-[#007055]/20 rounded-full blur-[150px] animate-pulse pointer-events-none"></div>
83
- <div className="absolute bottom-[-20%] right-[-10%] w-[60%] h-[60%] bg-[#FDFBD4]/5 rounded-full blur-[150px] pointer-events-none"></div>
84
-
85
- <div className="w-full max-w-5xl h-full md:h-[92vh] flex flex-col md:flex-row relative z-10 bg-black/20 backdrop-blur-2xl md:rounded-[2.5rem] overflow-hidden border border-white/5 shadow-[0_32px_64px_-12px_rgba(0,0,0,0.5)]">
86
-
87
- {/* Modern Sidebar-style Header (Desktop) / Top Header (Mobile) */}
88
- <aside className="w-full md:w-80 bg-[#00362B]/80 p-4 md:p-8 flex flex-row md:flex-col items-center md:items-start space-x-4 md:space-x-0 md:space-y-8 border-b md:border-b-0 md:border-r border-white/5 shrink-0">
89
- <div className="relative group shrink-0">
90
- <div className="absolute -inset-1 bg-gradient-to-tr from-[#FDFBD4] to-[#007055] rounded-2xl md:rounded-3xl blur opacity-25 group-hover:opacity-50 transition duration-1000 group-hover:duration-200"></div>
91
- <div className="relative w-12 h-12 md:w-32 md:h-32 rounded-xl md:rounded-[2rem] overflow-hidden border border-white/10 shadow-2xl transform transition-transform group-hover:scale-105 duration-500">
92
  <img src="/assets/logo.jpg" alt="CLCC Logo" className="w-full h-full object-cover" />
93
  </div>
94
- </div>
95
- <div className="flex-1 md:flex-none text-left space-y-1 md:space-y-2">
96
- <h1 className="text-xl md:text-4xl font-black tracking-tighter bg-clip-text text-transparent bg-gradient-to-br from-white via-[#FDFBD4] to-[#007055]">
97
- Theo AI
98
- </h1>
99
- <div className="inline-flex items-center px-2 py-0.5 md:px-3 md:py-1 bg-[#FDFBD4]/10 rounded-full border border-[#FDFBD4]/20">
100
- <span className="text-[8px] md:text-[10px] font-bold text-[#FDFBD4] uppercase tracking-widest whitespace-nowrap">
101
- CLCC - TECHDISCIPLES
102
- </span>
103
  </div>
104
  </div>
105
- <div className="hidden md:block flex-1 w-full pt-8">
106
- <div className="p-4 rounded-2xl bg-white/5 border border-white/5 backdrop-blur-sm animate-in fade-in slide-in-from-top-4 duration-1000">
107
- <h3 className="text-[10px] font-bold text-[#FDFBD4]/40 uppercase tracking-widest mb-3">Verse of the Day</h3>
108
- <p className="text-[#FDFBD4]/80 text-xs leading-relaxed font-light italic">
109
- "{verse.text}"
110
- <span className="block mt-2 font-bold not-italic opacity-100 text-[#FDFBD4]">— {verse.ref}</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  </p>
112
- </div>
113
- </div>
114
- </aside>
115
-
116
- {/* Main Interaction Area */}
117
- <main className="flex-1 flex flex-col min-w-0 bg-transparent relative">
118
- {/* Chat Messages */}
119
- <div className="flex-1 overflow-y-auto p-4 md:p-10 space-y-8 scroll-smooth scrollbar-hide h-full">
120
- {messages.length === 0 && (
121
- <div className="h-full flex flex-col items-center justify-center space-y-6 text-center animate-in fade-in zoom-in duration-700">
122
- <div className="w-16 h-16 bg-[#FDFBD4]/5 rounded-2xl flex items-center justify-center border border-[#FDFBD4]/10 shadow-inner">
123
- <Bot size={32} className="text-[#FDFBD4]" />
124
- </div>
125
- <div className="space-y-2 max-w-sm px-4">
126
- <h2 className="text-2xl font-bold text-white">Shalom, Beloved</h2>
127
- <p className="text-white/40 text-sm leading-relaxed mb-4">
128
- I am centered in Christ to provide you with scriptural guidance and faithful encouragement.
129
- </p>
130
- <div className="md:hidden p-4 rounded-2xl bg-white/5 border border-white/5 text-left">
131
- <h3 className="text-[8px] font-bold text-[#FDFBD4]/30 uppercase tracking-[0.2em] mb-2">Today's Word</h3>
132
- <p className="text-[#FDFBD4]/70 text-xs italic">
133
- "{verse.text}"
134
- <span className="block mt-1 font-bold not-italic text-[10px]">— {verse.ref}</span>
135
- </p>
136
- </div>
137
- </div>
138
  </div>
139
- )}
140
-
141
- {messages.map((msg, index) => (
142
- <div key={index}
143
- className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'} animate-in fade-in slide-in-from-bottom-6 duration-500 fill-mode-both`}
144
- style={{ animationDelay: `${index * 50}ms` }}
145
- >
146
- <div className={`group flex flex-col max-w-[95%] md:max-w-[80%] ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
147
- <div className={`flex items-center gap-2 mb-2 px-2 ${msg.role === 'user' ? 'flex-row-reverse' : 'flex-row'}`}>
148
- <div className={`text-[10px] font-bold uppercase tracking-widest ${msg.role === 'user' ? 'text-[#007055]' : 'text-[#FDFBD4]/60'}`}>
149
- {msg.role === 'user' ? 'You' : 'Theo'}
150
- </div>
151
- </div>
152
- <div className={`relative p-5 md:p-6 rounded-[2rem] text-sm leading-relaxed shadow-xl transition-all duration-300 ${msg.role === 'user'
153
- ? 'bg-gradient-to-br from-[#007055] to-[#004D3C] text-white rounded-tr-none border border-white/5'
154
- : 'bg-white/5 backdrop-blur-md text-white/90 rounded-tl-none border border-white/5 hover:bg-white/10'
155
- }`}>
156
- <div className={`markdown-content prose prose-invert prose-sm max-w-none
157
- prose-p:leading-relaxed prose-headings:text-[#FDFBD4] prose-strong:text-[#FDFBD4]
158
- prose-li:text-white/90 prose-ul:my-2
159
- ${msg.role === 'assistant' ? 'selection:bg-[#FDFBD4]/30' : 'selection:bg-white/20'}
160
- `}>
161
- <ReactMarkdown>{msg.content}</ReactMarkdown>
162
  </div>
 
 
 
163
  </div>
164
  </div>
165
  </div>
166
- ))}
167
-
168
- {isLoading && (
169
- <div className="flex justify-start animate-in fade-in duration-300">
170
- <div className="bg-white/5 p-4 rounded-3xl border border-white/5 flex items-center gap-3">
171
- <div className="flex gap-1.5">
172
- <div className="w-1.5 h-1.5 bg-[#FDFBD4] rounded-full animate-bounce"></div>
173
- <div className="w-1.5 h-1.5 bg-[#FDFBD4]/60 rounded-full animate-bounce [animation-delay:0.2s]"></div>
174
- <div className="w-1.5 h-1.5 bg-[#FDFBD4]/30 rounded-full animate-bounce [animation-delay:0.4s]"></div>
175
- </div>
176
  </div>
177
  </div>
178
- )}
179
- <div ref={chatEndRef} />
180
- </div>
181
-
182
- {/* Sophisticated Input Box */}
183
- <div className="p-4 md:p-10 shrink-0">
184
- <div className="relative group max-w-3xl mx-auto">
185
- <div className="absolute -inset-0.5 bg-gradient-to-r from-[#007055] via-[#FDFBD4]/20 to-[#004D3C] rounded-3xl blur opacity-20 group-focus-within:opacity-40 transition duration-500"></div>
186
- <div className="relative flex items-center bg-[#00362B]/60 backdrop-blur-xl border border-white/10 rounded-3xl p-1.5 md:p-2 transition-all duration-300 group-focus-within:border-[#FDFBD4]/30 group-focus-within:bg-[#00362B]/80 shadow-2xl">
187
- <input
188
- type="text"
189
- value={input}
190
- onChange={(e) => setInput(e.target.value)}
191
- onKeyPress={(e) => e.key === 'Enter' && handleSend()}
192
- placeholder="Type your spiritual inquiry here..."
193
- className="flex-1 bg-transparent py-3 md:py-4 px-4 md:px-6 focus:outline-none text-white placeholder:text-white/20 text-sm md:text-base"
194
- disabled={isLoading}
195
- />
196
- <button
197
- onClick={handleSend}
198
- disabled={isLoading || !input.trim()}
199
- className={`p-3 md:p-4 rounded-2xl transition-all duration-500 ${input.trim()
200
- ? 'bg-[#FDFBD4] text-[#002B22] shadow-[0_0_20px_rgba(253,251,212,0.3)] hover:scale-105 active:scale-95'
201
- : 'bg-white/5 text-white/20 shadow-none'
202
- }`}
203
- >
204
- <Send size={18} className={isLoading ? 'animate-pulse' : ''} />
205
- </button>
206
- </div>
207
- <div className="flex justify-center mt-3 md:mt-4">
208
- <p className="text-[8px] md:text-[9px] uppercase tracking-[0.4em] text-white/20 font-black">
209
- Faith-based Intelligence • CLCC
210
- </p>
211
- </div>
212
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  </div>
214
- </main>
215
  </div>
216
  </div>
217
  );
 
1
  import React, { useState, useEffect, useRef } from 'react';
2
+ import { Send, User, Bot, Loader2, Trash2 } from 'lucide-react';
3
  import ReactMarkdown from 'react-markdown';
4
  import axios from 'axios';
5
 
 
24
  const [isLoading, setIsLoading] = useState(false);
25
  const [sessionId] = useState<string>(() => Math.random().toString(36).substring(7));
26
  const [verse, setVerse] = useState(VERSES[0]);
27
+ const chatBoxRef = useRef<HTMLDivElement>(null);
28
+ const textareaRef = useRef<HTMLTextAreaElement>(null);
29
+ const isFirstRender = useRef(true);
30
 
31
  useEffect(() => {
32
  const dayOfYear = Math.floor((new Date().getTime() - new Date(new Date().getFullYear(), 0, 0).getTime()) / 86400000);
33
  setVerse(VERSES[dayOfYear % VERSES.length]);
34
  }, []);
35
 
 
 
36
  useEffect(() => {
37
  if (isFirstRender.current) {
38
  isFirstRender.current = false;
39
  return;
40
  }
41
+ if (chatBoxRef.current) {
42
+ chatBoxRef.current.scrollTo({
43
+ top: chatBoxRef.current.scrollHeight,
44
+ behavior: 'smooth'
45
+ });
46
  }
47
+ }, [messages, isLoading]);
48
 
49
  const handleSend = async () => {
50
  if (!input.trim() || isLoading) return;
51
 
52
  const userMessage = input.trim();
53
  setInput('');
54
+ if (textareaRef.current) textareaRef.current.style.height = 'auto';
55
  setMessages(prev => [...prev, { role: 'user', content: userMessage }]);
56
  setIsLoading(true);
57
 
58
  try {
 
59
  const response = await axios.post('/ai-chat', {
60
  query: userMessage,
61
  session_id: sessionId,
 
79
  }
80
  };
81
 
82
+ const clearChat = () => {
83
+ if (window.confirm('Are you sure you want to clear our conversation, beloved?')) {
84
+ setMessages([]);
85
+ }
86
+ };
87
+
88
+ const handleTextareaChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
89
+ setInput(e.target.value);
90
+ e.target.style.height = 'auto';
91
+ e.target.style.height = `${e.target.scrollHeight}px`;
92
+ };
93
+
94
  return (
95
+ <div className="h-[100dvh] w-full bg-[#002B22] flex items-center justify-center overflow-hidden font-outfit text-white p-0 sm:p-4">
96
+ {/* Background Orbs */}
97
+ <div className="absolute top-[-10%] left-[-10%] w-[50%] h-[50%] bg-[#007055]/20 rounded-full blur-[120px] pointer-events-none"></div>
98
+ <div className="absolute bottom-[-10%] right-[-10%] w-[50%] h-[50%] bg-[#FDFBD4]/5 rounded-full blur-[120px] pointer-events-none"></div>
99
+
100
+ <div className="w-full max-w-4xl h-full sm:h-[90vh] bg-white/[0.03] backdrop-blur-3xl sm:rounded-[2rem] flex flex-col overflow-hidden border border-white/10 shadow-[0_32px_64px_-12px_rgba(0,0,0,0.5)]">
101
+
102
+ {/* Header - Domino's Style */}
103
+ <header className="flex items-center justify-between px-6 py-4 md:px-10 md:py-6 bg-[#00362B]/60 border-b border-white/10 z-20">
104
+ <div className="flex items-center gap-4">
105
+ <div className="relative w-10 h-10 md:w-12 md:h-12 rounded-xl overflow-hidden border border-[#FDFBD4]/20 shadow-lg">
 
106
  <img src="/assets/logo.jpg" alt="CLCC Logo" className="w-full h-full object-cover" />
107
  </div>
108
+ <div className="flex flex-col">
109
+ <h1 className="text-xl md:text-2xl font-black tracking-tight text-[#FDFBD4]">Theo AI</h1>
110
+ <span className="text-[9px] md:text-[11px] font-bold text-[#FDFBD4]/40 uppercase tracking-[0.2em]">CLCC - TECHDISCIPLES</span>
 
 
 
 
 
 
111
  </div>
112
  </div>
113
+ <button
114
+ onClick={clearChat}
115
+ className="p-2 transition-all hover:bg-white/5 rounded-full border border-white/5 text-[#FDFBD4]/60 hover:text-[#FDFBD4]"
116
+ title="Clear Conversation"
117
+ >
118
+ <Trash2 size={20} />
119
+ </button>
120
+ </header>
121
+
122
+ {/* Chat Container */}
123
+ <div
124
+ ref={chatBoxRef}
125
+ className="flex-1 overflow-y-auto px-4 py-8 md:px-10 space-y-6 scroll-smooth scrollbar-hide"
126
+ >
127
+ {messages.length === 0 && (
128
+ <div className="h-full flex flex-col items-center justify-center text-center p-6 animate-in fade-in zoom-in duration-700">
129
+ <div className="w-20 h-20 bg-[#FDFBD4]/5 rounded-3xl flex items-center justify-center border border-[#FDFBD4]/10 shadow-inner mb-6">
130
+ <Bot size={40} className="text-[#FDFBD4]" />
131
+ </div>
132
+ <h2 className="text-3xl font-bold text-white mb-2">Shalom, Beloved</h2>
133
+ <p className="text-white/60 text-base max-w-md leading-relaxed mb-8">
134
+ I am Theo, centered in Christ to walk with you in faith. How may the Lord's wisdom guide you today?
135
  </p>
136
+
137
+ <div className="w-full max-w-sm p-5 rounded-2xl bg-[#00362B]/40 border border-[#FDFBD4]/10 backdrop-blur-sm text-left">
138
+ <h3 className="text-[10px] font-bold text-[#FDFBD4]/50 uppercase tracking-widest mb-3">Verse of the Day</h3>
139
+ <p className="text-[#FDFBD4]/90 text-sm italic leading-relaxed">
140
+ "{verse.text}"
141
+ <span className="block mt-2 font-bold not-italic opacity-100 text-[#FDFBD4]">-- {verse.ref}</span>
142
+ </p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  </div>
144
+ </div>
145
+ )}
146
+
147
+ {messages.map((msg, index) => (
148
+ <div key={index} className={`flex ${msg.role === 'user' ? 'justify-end' : 'justify-start'} animate-in slide-in-from-bottom-4 duration-500 fill-mode-both`}>
149
+ <div className={`flex flex-col max-w-[85%] md:max-w-[75%] ${msg.role === 'user' ? 'items-end' : 'items-start'}`}>
150
+ <div className={`relative px-5 py-4 rounded-2xl shadow-lg ${msg.role === 'user'
151
+ ? 'bg-[#007055] text-white rounded-br-none border border-white/10'
152
+ : 'bg-[#FDFBD4]/95 text-[#002B22] rounded-bl-none border-l-4 border-[#007055]'
153
+ }`}>
154
+ {msg.role === 'assistant' && (
155
+ <div className="flex items-center gap-2 mb-2 opacity-40">
156
+ <Bot size={14} />
157
+ <span className="text-[10px] font-black uppercase tracking-tighter">Theo</span>
 
 
 
 
 
 
 
 
 
158
  </div>
159
+ )}
160
+ <div className={`prose prose-sm max-w-none ${msg.role === 'user' ? 'prose-invert' : 'prose-teal'}`}>
161
+ <ReactMarkdown>{msg.content}</ReactMarkdown>
162
  </div>
163
  </div>
164
  </div>
165
+ </div>
166
+ ))}
167
+
168
+ {isLoading && (
169
+ <div className="flex justify-start animate-in slide-in-from-bottom-2 duration-300">
170
+ <div className="bg-[#FDFBD4]/95 p-4 rounded-2xl rounded-bl-none flex items-center gap-2">
171
+ <div className="flex gap-1">
172
+ <div className="w-2 h-2 bg-[#007055] rounded-full animate-bounce [animation-delay:-0.3s]"></div>
173
+ <div className="w-2 h-2 bg-[#007055] rounded-full animate-bounce [animation-delay:-0.15s]"></div>
174
+ <div className="w-2 h-2 bg-[#007055] rounded-full animate-bounce"></div>
175
  </div>
176
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  </div>
178
+ )}
179
+ </div>
180
+
181
+ {/* Input Area - Domino's Style */}
182
+ <div className="p-4 md:p-8 bg-white/5 border-top border-white/10 backdrop-blur-md">
183
+ <div className="max-w-3xl mx-auto flex items-end gap-3 md:gap-4 relative group">
184
+ <textarea
185
+ ref={textareaRef}
186
+ value={input}
187
+ onChange={handleTextareaChange}
188
+ onKeyDown={(e) => {
189
+ if (e.key === 'Enter' && !e.shiftKey) {
190
+ e.preventDefault();
191
+ handleSend();
192
+ }
193
+ }}
194
+ placeholder="How can Theo help you today?"
195
+ className="flex-1 bg-[#00362B]/60 border-2 border-white/5 rounded-2xl p-4 md:px-6 md:py-4 focus:outline-none focus:border-[#FDFBD4]/30 text-white placeholder:text-white/20 text-sm md:text-base resize-none max-h-[150px] transition-all scrollbar-hide"
196
+ rows={1}
197
+ disabled={isLoading}
198
+ />
199
+ <button
200
+ onClick={handleSend}
201
+ disabled={isLoading || !input.trim()}
202
+ className="w-12 h-12 md:w-14 md:h-14 bg-[#FDFBD4] text-[#002B22] rounded-full flex items-center justify-center shrink-0 shadow-[0_8px_20px_-4px_rgba(253,251,212,0.4)] hover:scale-105 active:scale-95 transition-all disabled:opacity-20 disabled:grayscale disabled:scale-100"
203
+ >
204
+ <Send size={24} className={isLoading ? 'animate-pulse' : ''} />
205
+ </button>
206
+ </div>
207
+ <div className="text-center mt-4">
208
+ <p className="text-[10px] font-black uppercase tracking-[0.4em] text-white/20">Faith-Based Intelligence</p>
209
  </div>
210
+ </div>
211
  </div>
212
  </div>
213
  );