1use crate::utils::Time;
4use chrono::Datelike;
5use dioxus::prelude::*;
6#[allow(unused_imports)]
7use dioxus_router::prelude::*;
8
9use crate::ui::router::Route;
10
11#[component]
13pub fn Footer() -> Element {
14 let current_year = Time::now().year();
15 let build = option_env!("BUILD_HASH").unwrap_or("dev");
16
17 rsx! {
18 footer {
19 class: "bg-white border-t border-gray-200 mt-auto",
20 div {
21 class: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-6",
22
23 div {
25 class: "md:flex md:items-center md:justify-between",
26
27 div {
29 class: "flex flex-wrap justify-center md:justify-start space-x-6 md:order-2",
30
31 Link {
33 to: Route::Dashboard {},
34 class: "text-sm text-gray-500 hover:text-gray-600 transition-colors",
35 "Dashboard"
36 }
37
38 a {
40 href: "https://docs.qorzen.com",
41 target: "_blank",
42 rel: "noopener noreferrer",
43 class: "text-sm text-gray-500 hover:text-gray-600 transition-colors",
44 "Documentation"
45 }
46
47 a {
48 href: "https://github.com/qorzen/qorzen-oxide",
49 target: "_blank",
50 rel: "noopener noreferrer",
51 class: "text-sm text-gray-500 hover:text-gray-600 transition-colors",
52 "GitHub"
53 }
54
55 a {
56 href: "mailto:support@qorzen.com",
57 class: "text-sm text-gray-500 hover:text-gray-600 transition-colors",
58 "Support"
59 }
60
61 a {
62 href: "/privacy",
63 class: "text-sm text-gray-500 hover:text-gray-600 transition-colors",
64 "Privacy"
65 }
66
67 a {
68 href: "/terms",
69 class: "text-sm text-gray-500 hover:text-gray-600 transition-colors",
70 "Terms"
71 }
72 }
73
74 div {
76 class: "mt-4 md:mt-0 md:order-1",
77 div {
78 class: "flex flex-col items-center md:items-start space-y-1",
79 p {
80 class: "text-sm text-gray-500",
81 "© {current_year} Qorzen. All rights reserved."
82 }
83 p {
84 class: "text-xs text-gray-400",
85 "Version {crate::VERSION}"
86 }
87 }
88 }
89 }
90
91 div {
93 class: "mt-6 pt-6 border-t border-gray-200",
94 div {
95 class: "flex flex-col sm:flex-row sm:items-center sm:justify-between text-xs text-gray-400 space-y-2 sm:space-y-0",
96
97 div {
99 class: "flex items-center space-x-4",
100 SystemStatus {}
101 div {
102 class: "flex items-center",
103 span {
104 class: "inline-block w-2 h-2 bg-green-400 rounded-full mr-1"
105 }
106 "All systems operational"
107 }
108 }
109
110 div {
112 class: "flex items-center space-x-4",
113 span { "Built with ❤️ and 🦀" }
114 span {
115 "Build: {build}",
116 }
117 }
118 }
119 }
120 }
121 }
122 }
123}
124
125#[component]
127fn SystemStatus() -> Element {
128 let status = use_signal(|| "healthy");
130 let last_check = use_signal(Time::now);
131
132 use_effect({
134 let mut status = status;
135 let mut last_check = last_check;
136
137 move || {
138 spawn(async move {
139 loop {
140 #[cfg(not(target_arch = "wasm32"))]
141 tokio::time::sleep(std::time::Duration::from_secs(30)).await;
142 #[cfg(target_arch = "wasm32")]
143 gloo_timers::future::TimeoutFuture::new(30000).await;
144
145 let health_good = rand::random::<f32>() > 0.1; status.set(if health_good { "healthy" } else { "degraded" });
148 last_check.set(Time::now());
149 }
150 });
151 }
152 });
153
154 let (status_color, status_text) = match status() {
155 "healthy" => ("bg-green-400", "Healthy"),
156 "degraded" => ("bg-yellow-400", "Degraded"),
157 "down" => ("bg-red-400", "Down"),
158 _ => ("bg-gray-400", "Unknown"),
159 };
160
161 fn fmt_time(ts: chrono::DateTime<chrono::Utc>) -> String {
162 ts.format("%H:%M:%S UTC").to_string()
163 }
164
165 rsx! {
166 div {
167 class: "flex items-center space-x-1",
168 title: "{fmt_time(last_check())}",
169 span {
170 class: format!("inline-block w-2 h-2 rounded-full {}", status_color)
171 }
172 span { "System: {status_text}" }
173 }
174 }
175}
176
177#[component]
179pub fn ExpandedFooter() -> Element {
180 let current_year = Time::now().year();
181
182 rsx! {
183 footer {
184 class: "bg-gray-50 border-t border-gray-200 mt-auto",
185 div {
186 class: "mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12",
187
188 div {
190 class: "grid grid-cols-1 md:grid-cols-4 gap-8",
191
192 div {
194 class: "col-span-1 md:col-span-2",
195 div {
196 class: "flex items-center mb-4",
197 div {
198 class: "h-8 w-8 bg-blue-600 rounded-lg flex items-center justify-center mr-3",
199 span {
200 class: "text-white font-bold text-sm",
201 "Q"
202 }
203 }
204 span {
205 class: "text-xl font-bold text-gray-900",
206 "Qorzen"
207 }
208 }
209 p {
210 class: "text-sm text-gray-600 mb-4 max-w-md",
211 "A modular, cross-platform application framework built with Rust and Dioxus.
212 Extensible through plugins and designed for modern development workflows."
213 }
214 div {
215 class: "flex space-x-4",
216 a {
218 href: "https://github.com/qorzen",
219 target: "_blank",
220 rel: "noopener noreferrer",
221 class: "text-gray-400 hover:text-gray-600 transition-colors",
222 "GitHub"
223 }
224 a {
225 href: "https://twitter.com/qorzen",
226 target: "_blank",
227 rel: "noopener noreferrer",
228 class: "text-gray-400 hover:text-gray-600 transition-colors",
229 "Twitter"
230 }
231 }
232 }
233
234 div {
236 h3 {
237 class: "text-sm font-semibold text-gray-900 tracking-wider uppercase mb-4",
238 "Product"
239 }
240 ul {
241 class: "space-y-2",
242 li {
243 Link {
244 to: Route::Dashboard {},
245 class: "text-sm text-gray-600 hover:text-gray-900 transition-colors",
246 "Dashboard"
247 }
248 }
249 li {
250 Link {
251 to: Route::Plugins {},
252 class: "text-sm text-gray-600 hover:text-gray-900 transition-colors",
253 "Plugins"
254 }
255 }
256 li {
257 Link {
258 to: Route::Settings {},
259 class: "text-sm text-gray-600 hover:text-gray-900 transition-colors",
260 "Settings"
261 }
262 }
263 li {
264 a {
265 href: "/api/docs",
266 class: "text-sm text-gray-600 hover:text-gray-900 transition-colors",
267 "API Documentation"
268 }
269 }
270 }
271 }
272
273 div {
275 h3 {
276 class: "text-sm font-semibold text-gray-900 tracking-wider uppercase mb-4",
277 "Support"
278 }
279 ul {
280 class: "space-y-2",
281 li {
282 a {
283 href: "https://docs.qorzen.com",
284 target: "_blank",
285 rel: "noopener noreferrer",
286 class: "text-sm text-gray-600 hover:text-gray-900 transition-colors",
287 "Documentation"
288 }
289 }
290 li {
291 a {
292 href: "https://docs.qorzen.com/guides",
293 target: "_blank",
294 rel: "noopener noreferrer",
295 class: "text-sm text-gray-600 hover:text-gray-900 transition-colors",
296 "Guides"
297 }
298 }
299 li {
300 a {
301 href: "mailto:support@qorzen.com",
302 class: "text-sm text-gray-600 hover:text-gray-900 transition-colors",
303 "Contact Support"
304 }
305 }
306 li {
307 a {
308 href: "https://status.qorzen.com",
309 target: "_blank",
310 rel: "noopener noreferrer",
311 class: "text-sm text-gray-600 hover:text-gray-900 transition-colors",
312 "System Status"
313 }
314 }
315 }
316 }
317 }
318
319 div {
321 class: "mt-12 pt-8 border-t border-gray-200",
322 div {
323 class: "md:flex md:items-center md:justify-between",
324 div {
325 class: "flex space-x-6 md:order-2",
326 a {
327 href: "/privacy",
328 class: "text-sm text-gray-500 hover:text-gray-600 transition-colors",
329 "Privacy Policy"
330 }
331 a {
332 href: "/terms",
333 class: "text-sm text-gray-500 hover:text-gray-600 transition-colors",
334 "Terms of Service"
335 }
336 a {
337 href: "/cookies",
338 class: "text-sm text-gray-500 hover:text-gray-600 transition-colors",
339 "Cookie Policy"
340 }
341 }
342 p {
343 class: "mt-4 text-sm text-gray-500 md:mt-0 md:order-1",
344 "© {current_year} Qorzen. All rights reserved. Version {crate::VERSION}"
345 }
346 }
347 }
348 }
349 }
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::*;
356 use dioxus::prelude::*;
357
358 #[test]
359 fn test_footer_component_creation() {
360 let _footer = rsx! { Footer {} };
361 }
362
363 #[test]
364 fn test_expanded_footer_component_creation() {
365 let _footer = rsx! { ExpandedFooter {} };
366 }
367
368 #[test]
369 fn test_system_status_component_creation() {
370 let _status = rsx! { SystemStatus {} };
371 }
372}