## New Features - **Animated Morpho Butterfly**: Vector-based butterfly with realistic wing flapping animation - **Personalized AI Assistant**: Welcomes users by first name from invitation flow - **Post-inquiry Action Buttons**: Interactive buttons for network import, security setup, and platform features - **Enhanced Invitation System**: Complete invite form with relationship type selection - **Improved Invitation Messages**: First-person language and simplified security guidance ## UI/UX Improvements - **80% Dialog Size**: AI assistant now uses 80% of viewport for better usability - **Mobile-Responsive Layout**: Action buttons stack properly on mobile group pages - **Optimized Input Position**: AI input field moved to bottom for more conversation space - **Visual Consistency**: White backgrounds with blue borders for group avatars ## Technical Enhancements - **NAOG1 Group**: Added as first group with custom butterfly logo and governance description - **Parameter Passing**: Fixed invitation flow to properly pass user first names - **TypeScript Fixes**: Resolved all build errors across components - **Component Architecture**: New reusable components for AI rating, invite forms, and group tours ## Files Changed - Created: AnimatedMorphoButterfly, AIResponseRating, InviteForm, GroupTour components - Enhanced: GroupDetailPage with full AI assistant integration and mobile layout - Updated: SocialContractPage, OnboardingPage, InvitationPage with personalized messaging - Added: NAOG1 group data and custom butterfly logo SVG 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>main
parent
0aa92fdf3a
commit
40f66144fc
@ -0,0 +1,193 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>Simple Blue Butterfly - Matching Reference Frames</title> |
||||||
|
<style> |
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
padding: 50px; |
||||||
|
background: linear-gradient(135deg, #e8f4fd 0%, #f0f8ff 100%); |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
min-height: 100vh; |
||||||
|
font-family: Arial, sans-serif; |
||||||
|
} |
||||||
|
|
||||||
|
.container { |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.butterfly { |
||||||
|
width: 120px; |
||||||
|
height: 80px; |
||||||
|
position: relative; |
||||||
|
margin: 50px auto; |
||||||
|
transform: scale(2); |
||||||
|
} |
||||||
|
|
||||||
|
/* Wing animations - simple up/down like reference images */ |
||||||
|
@keyframes wingFlap { |
||||||
|
0% { |
||||||
|
transform: rotateZ(0deg); |
||||||
|
} |
||||||
|
25% { |
||||||
|
transform: rotateZ(-30deg); |
||||||
|
} |
||||||
|
50% { |
||||||
|
transform: rotateZ(-50deg); |
||||||
|
} |
||||||
|
75% { |
||||||
|
transform: rotateZ(-30deg); |
||||||
|
} |
||||||
|
100% { |
||||||
|
transform: rotateZ(0deg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.wing-left { |
||||||
|
transform-origin: 48px 40px; |
||||||
|
animation: wingFlap 0.8s ease-in-out infinite; |
||||||
|
} |
||||||
|
|
||||||
|
.wing-right { |
||||||
|
transform-origin: 72px 40px; |
||||||
|
animation: wingFlap 0.8s ease-in-out infinite; |
||||||
|
} |
||||||
|
|
||||||
|
/* Controls */ |
||||||
|
.controls { |
||||||
|
margin: 20px 0; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
margin: 0 10px; |
||||||
|
padding: 10px 20px; |
||||||
|
border: 2px solid #1976d2; |
||||||
|
background: white; |
||||||
|
color: #1976d2; |
||||||
|
border-radius: 5px; |
||||||
|
cursor: pointer; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
button.active { |
||||||
|
background: #1976d2; |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
.butterfly.slow .wing-left, |
||||||
|
.butterfly.slow .wing-right { |
||||||
|
animation-duration: 1.5s; |
||||||
|
} |
||||||
|
|
||||||
|
.butterfly.fast .wing-left, |
||||||
|
.butterfly.fast .wing-right { |
||||||
|
animation-duration: 0.4s; |
||||||
|
} |
||||||
|
|
||||||
|
h1 { |
||||||
|
color: #333; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
p { |
||||||
|
color: #666; |
||||||
|
margin: 5px 0; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div class="container"> |
||||||
|
<h1>🦋 Simple Blue Butterfly</h1> |
||||||
|
<p>Replicating the reference frame animation</p> |
||||||
|
|
||||||
|
<div class="butterfly" id="butterfly"> |
||||||
|
<svg viewBox="0 0 120 80" width="120" height="80"> |
||||||
|
<!-- Simple gradients --> |
||||||
|
<defs> |
||||||
|
<linearGradient id="blueWing" x1="0%" y1="0%" x2="100%" y2="100%"> |
||||||
|
<stop offset="0%" style="stop-color: #4FC3F7; stop-opacity: 1" /> |
||||||
|
<stop offset="50%" style="stop-color: #2196F3; stop-opacity: 1" /> |
||||||
|
<stop offset="100%" style="stop-color: #1565C0; stop-opacity: 1" /> |
||||||
|
</linearGradient> |
||||||
|
|
||||||
|
<linearGradient id="darkBlue" x1="0%" y1="0%" x2="100%" y2="100%"> |
||||||
|
<stop offset="0%" style="stop-color: #1976D2; stop-opacity: 1" /> |
||||||
|
<stop offset="100%" style="stop-color: #0D47A1; stop-opacity: 1" /> |
||||||
|
</linearGradient> |
||||||
|
</defs> |
||||||
|
|
||||||
|
<!-- Left Wing --> |
||||||
|
<g class="wing-left"> |
||||||
|
<!-- Upper left wing --> |
||||||
|
<ellipse cx="30" cy="25" rx="18" ry="12" fill="url(#blueWing)" stroke="#0D47A1" stroke-width="0.5"/> |
||||||
|
<!-- Lower left wing --> |
||||||
|
<ellipse cx="35" cy="50" rx="13" ry="10" fill="url(#darkBlue)" stroke="#0D47A1" stroke-width="0.5"/> |
||||||
|
|
||||||
|
<!-- White spots --> |
||||||
|
<circle cx="25" cy="22" r="2" fill="white" opacity="0.8" /> |
||||||
|
<circle cx="35" cy="28" r="1.5" fill="white" opacity="0.7" /> |
||||||
|
<circle cx="30" cy="47" r="1.8" fill="white" opacity="0.8" /> |
||||||
|
</g> |
||||||
|
|
||||||
|
<!-- Right Wing --> |
||||||
|
<g class="wing-right"> |
||||||
|
<!-- Upper right wing --> |
||||||
|
<ellipse cx="90" cy="25" rx="18" ry="12" fill="url(#blueWing)" stroke="#0D47A1" stroke-width="0.5"/> |
||||||
|
<!-- Lower right wing --> |
||||||
|
<ellipse cx="85" cy="50" rx="13" ry="10" fill="url(#darkBlue)" stroke="#0D47A1" stroke-width="0.5"/> |
||||||
|
|
||||||
|
<!-- White spots --> |
||||||
|
<circle cx="95" cy="22" r="2" fill="white" opacity="0.8" /> |
||||||
|
<circle cx="85" cy="28" r="1.5" fill="white" opacity="0.7" /> |
||||||
|
<circle cx="90" cy="47" r="1.8" fill="white" opacity="0.8" /> |
||||||
|
</g> |
||||||
|
|
||||||
|
<!-- Body --> |
||||||
|
<ellipse cx="60" cy="40" rx="3" ry="25" fill="#212121" stroke="#000" stroke-width="0.5"/> |
||||||
|
|
||||||
|
<!-- Head --> |
||||||
|
<circle cx="60" cy="20" r="4" fill="#424242" stroke="#000" stroke-width="0.5"/> |
||||||
|
|
||||||
|
<!-- Simple antennae --> |
||||||
|
<line x1="58" y1="18" x2="55" y2="12" stroke="#212121" stroke-width="1.5" stroke-linecap="round"/> |
||||||
|
<line x1="62" y1="18" x2="65" y2="12" stroke="#212121" stroke-width="1.5" stroke-linecap="round"/> |
||||||
|
<circle cx="55" cy="12" r="1" fill="#212121"/> |
||||||
|
<circle cx="65" cy="12" r="1" fill="#212121"/> |
||||||
|
|
||||||
|
<!-- Simple eyes --> |
||||||
|
<circle cx="57" cy="18" r="1" fill="white" opacity="0.3"/> |
||||||
|
<circle cx="63" cy="18" r="1" fill="white" opacity="0.3"/> |
||||||
|
</svg> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="controls"> |
||||||
|
<p>Wing Flapping Speed:</p> |
||||||
|
<button class="active" onclick="setSpeed('normal')">Normal (0.8s)</button> |
||||||
|
<button onclick="setSpeed('slow')">Slow (1.5s)</button> |
||||||
|
<button onclick="setSpeed('fast')">Fast (0.4s)</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<p>Simple up/down wing flapping motion</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<script> |
||||||
|
function setSpeed(speed) { |
||||||
|
const butterfly = document.getElementById('butterfly'); |
||||||
|
const buttons = document.querySelectorAll('button'); |
||||||
|
|
||||||
|
butterfly.classList.remove('slow', 'fast'); |
||||||
|
buttons.forEach(btn => btn.classList.remove('active')); |
||||||
|
|
||||||
|
if (speed !== 'normal') { |
||||||
|
butterfly.classList.add(speed); |
||||||
|
} |
||||||
|
|
||||||
|
event.target.classList.add('active'); |
||||||
|
} |
||||||
|
</script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,275 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html lang="en"> |
||||||
|
<head> |
||||||
|
<meta charset="UTF-8"> |
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||||
|
<title>Butterfly Wing Flapping Test</title> |
||||||
|
<style> |
||||||
|
body { |
||||||
|
margin: 0; |
||||||
|
padding: 50px; |
||||||
|
background: linear-gradient(135deg, #e3f2fd 0%, #f3e5f5 100%); |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
align-items: center; |
||||||
|
min-height: 100vh; |
||||||
|
font-family: Arial, sans-serif; |
||||||
|
} |
||||||
|
|
||||||
|
.container { |
||||||
|
text-align: center; |
||||||
|
} |
||||||
|
|
||||||
|
.butterfly { |
||||||
|
width: 200px; |
||||||
|
height: 160px; |
||||||
|
position: relative; |
||||||
|
margin: 50px auto; |
||||||
|
} |
||||||
|
|
||||||
|
/* Simple synchronized wing flapping - like reference images */ |
||||||
|
@keyframes wingFlapLeft { |
||||||
|
0% { |
||||||
|
transform: rotateZ(-20deg); |
||||||
|
} |
||||||
|
50% { |
||||||
|
transform: rotateZ(-70deg); |
||||||
|
} |
||||||
|
100% { |
||||||
|
transform: rotateZ(-20deg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@keyframes wingFlapRight { |
||||||
|
0% { |
||||||
|
transform: rotateZ(20deg); |
||||||
|
} |
||||||
|
50% { |
||||||
|
transform: rotateZ(70deg); |
||||||
|
} |
||||||
|
100% { |
||||||
|
transform: rotateZ(20deg); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
.wing-left { |
||||||
|
transform-origin: 70px 80px; /* Where wing attaches to body */ |
||||||
|
animation: wingFlapLeft 0.6s ease-in-out infinite; |
||||||
|
} |
||||||
|
|
||||||
|
.wing-right { |
||||||
|
transform-origin: 130px 80px; /* Where wing attaches to body */ |
||||||
|
animation: wingFlapRight 0.6s ease-in-out infinite; |
||||||
|
} |
||||||
|
|
||||||
|
/* Test different speeds */ |
||||||
|
.butterfly.slow .wing-left, |
||||||
|
.butterfly.slow .wing-right { |
||||||
|
animation-duration: 1.2s; |
||||||
|
} |
||||||
|
|
||||||
|
.butterfly.fast .wing-left, |
||||||
|
.butterfly.fast .wing-right { |
||||||
|
animation-duration: 0.3s; |
||||||
|
} |
||||||
|
|
||||||
|
.controls { |
||||||
|
margin: 20px 0; |
||||||
|
} |
||||||
|
|
||||||
|
button { |
||||||
|
margin: 0 10px; |
||||||
|
padding: 10px 20px; |
||||||
|
border: 2px solid #1976d2; |
||||||
|
background: white; |
||||||
|
color: #1976d2; |
||||||
|
border-radius: 5px; |
||||||
|
cursor: pointer; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
button.active { |
||||||
|
background: #1976d2; |
||||||
|
color: white; |
||||||
|
} |
||||||
|
|
||||||
|
h1 { |
||||||
|
color: #333; |
||||||
|
margin-bottom: 10px; |
||||||
|
} |
||||||
|
|
||||||
|
p { |
||||||
|
color: #666; |
||||||
|
margin: 5px 0; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div class="container"> |
||||||
|
<h1>🦋 Butterfly Wing Flapping Test</h1> |
||||||
|
<p>Testing different wing flapping animations with 3D rotation</p> |
||||||
|
|
||||||
|
<div class="butterfly" id="butterfly"> |
||||||
|
<svg viewBox="0 0 200 160" width="200" height="160"> |
||||||
|
<!-- Wing gradients --> |
||||||
|
<defs> |
||||||
|
<radialGradient id="morphoBlue" cx="50%" cy="30%" r="70%"> |
||||||
|
<stop offset="0%" style="stop-color: #00D4FF; stop-opacity: 1" /> |
||||||
|
<stop offset="40%" style="stop-color: #0099CC; stop-opacity: 1" /> |
||||||
|
<stop offset="80%" style="stop-color: #003366; stop-opacity: 1" /> |
||||||
|
<stop offset="100%" style="stop-color: #001122; stop-opacity: 1" /> |
||||||
|
</radialGradient> |
||||||
|
|
||||||
|
<radialGradient id="morphoBlueSecondary" cx="50%" cy="30%" r="70%"> |
||||||
|
<stop offset="0%" style="stop-color: #33AAFF; stop-opacity: 1" /> |
||||||
|
<stop offset="40%" style="stop-color: #0077AA; stop-opacity: 1" /> |
||||||
|
<stop offset="80%" style="stop-color: #002244; stop-opacity: 1" /> |
||||||
|
<stop offset="100%" style="stop-color: #000D1A; stop-opacity: 1" /> |
||||||
|
</radialGradient> |
||||||
|
|
||||||
|
<linearGradient id="bodyGradient" x1="0%" y1="0%" x2="0%" y2="100%"> |
||||||
|
<stop offset="0%" style="stop-color: #2D1B1B; stop-opacity: 1" /> |
||||||
|
<stop offset="50%" style="stop-color: #1A0F0F; stop-opacity: 1" /> |
||||||
|
<stop offset="100%" style="stop-color: #0D0505; stop-opacity: 1" /> |
||||||
|
</linearGradient> |
||||||
|
|
||||||
|
<filter id="glow"> |
||||||
|
<feGaussianBlur stdDeviation="2" result="coloredBlur"/> |
||||||
|
<feMerge> |
||||||
|
<feMergeNode in="coloredBlur"/> |
||||||
|
<feMergeNode in="SourceGraphic"/> |
||||||
|
</feMerge> |
||||||
|
</filter> |
||||||
|
</defs> |
||||||
|
|
||||||
|
<!-- Left wings --> |
||||||
|
<g class="wing-left"> |
||||||
|
<!-- Left upper wing --> |
||||||
|
<path |
||||||
|
d="M70 80 Q30 50 15 30 Q10 20 15 15 Q30 10 50 25 Q64 40 70 60 Z" |
||||||
|
fill="url(#morphoBlue)" |
||||||
|
stroke="#001122" |
||||||
|
stroke-width="1" |
||||||
|
filter="url(#glow)" |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- Left lower wing --> |
||||||
|
<path |
||||||
|
d="M70 80 Q40 100 25 120 Q15 130 20 135 Q30 145 50 125 Q64 105 70 90 Z" |
||||||
|
fill="url(#morphoBlueSecondary)" |
||||||
|
stroke="#001122" |
||||||
|
stroke-width="1" |
||||||
|
filter="url(#glow)" |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- Wing spots --> |
||||||
|
<circle cx="45" cy="40" r="4" fill="#00AAFF" opacity="0.7" /> |
||||||
|
<circle cx="35" cy="55" r="3" fill="#33BBFF" opacity="0.6" /> |
||||||
|
<circle cx="50" cy="110" r="3.5" fill="#00AAFF" opacity="0.7" /> |
||||||
|
</g> |
||||||
|
|
||||||
|
<!-- Right wings --> |
||||||
|
<g class="wing-right"> |
||||||
|
<!-- Right upper wing --> |
||||||
|
<path |
||||||
|
d="M130 80 Q170 50 185 30 Q190 20 185 15 Q170 10 150 25 Q136 40 130 60 Z" |
||||||
|
fill="url(#morphoBlue)" |
||||||
|
stroke="#001122" |
||||||
|
stroke-width="1" |
||||||
|
filter="url(#glow)" |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- Right lower wing --> |
||||||
|
<path |
||||||
|
d="M130 80 Q160 100 175 120 Q185 130 180 135 Q170 145 150 125 Q136 105 130 90 Z" |
||||||
|
fill="url(#morphoBlueSecondary)" |
||||||
|
stroke="#001122" |
||||||
|
stroke-width="1" |
||||||
|
filter="url(#glow)" |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- Wing spots --> |
||||||
|
<circle cx="155" cy="40" r="4" fill="#00AAFF" opacity="0.7" /> |
||||||
|
<circle cx="165" cy="55" r="3" fill="#33BBFF" opacity="0.6" /> |
||||||
|
<circle cx="150" cy="110" r="3.5" fill="#00AAFF" opacity="0.7" /> |
||||||
|
</g> |
||||||
|
|
||||||
|
<!-- Butterfly body --> |
||||||
|
<ellipse |
||||||
|
cx="100" |
||||||
|
cy="80" |
||||||
|
rx="6" |
||||||
|
ry="50" |
||||||
|
fill="url(#bodyGradient)" |
||||||
|
stroke="#0D0505" |
||||||
|
stroke-width="1" |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- Head --> |
||||||
|
<circle |
||||||
|
cx="100" |
||||||
|
cy="40" |
||||||
|
r="8" |
||||||
|
fill="#2D1B1B" |
||||||
|
stroke="#0D0505" |
||||||
|
stroke-width="1" |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- Antennae --> |
||||||
|
<path |
||||||
|
d="M95 35 Q90 25 85 15" |
||||||
|
stroke="#2D1B1B" |
||||||
|
stroke-width="2" |
||||||
|
fill="none" |
||||||
|
stroke-linecap="round" |
||||||
|
/> |
||||||
|
<path |
||||||
|
d="M105 35 Q110 25 115 15" |
||||||
|
stroke="#2D1B1B" |
||||||
|
stroke-width="2" |
||||||
|
fill="none" |
||||||
|
stroke-linecap="round" |
||||||
|
/> |
||||||
|
|
||||||
|
<!-- Antennae tips --> |
||||||
|
<circle cx="85" cy="15" r="2" fill="#2D1B1B" /> |
||||||
|
<circle cx="115" cy="15" r="2" fill="#2D1B1B" /> |
||||||
|
|
||||||
|
<!-- Eyes --> |
||||||
|
<circle cx="93" cy="35" r="2" fill="#000" /> |
||||||
|
<circle cx="107" cy="35" r="2" fill="#000" /> |
||||||
|
<circle cx="93" cy="35" r="1" fill="#FFF" opacity="0.6" /> |
||||||
|
<circle cx="107" cy="35" r="1" fill="#FFF" opacity="0.6" /> |
||||||
|
</svg> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="controls"> |
||||||
|
<p>Wing Flapping Speed:</p> |
||||||
|
<button class="active" onclick="setSpeed('normal')">Normal (0.6s)</button> |
||||||
|
<button onclick="setSpeed('slow')">Slow (1.2s)</button> |
||||||
|
<button onclick="setSpeed('fast')">Fast (0.3s)</button> |
||||||
|
</div> |
||||||
|
|
||||||
|
<p>Watch the wings! They should flap up and down with 3D rotation.</p> |
||||||
|
</div> |
||||||
|
|
||||||
|
<script> |
||||||
|
function setSpeed(speed) { |
||||||
|
const butterfly = document.getElementById('butterfly'); |
||||||
|
const buttons = document.querySelectorAll('button'); |
||||||
|
|
||||||
|
// Remove all speed classes |
||||||
|
butterfly.classList.remove('slow', 'fast'); |
||||||
|
buttons.forEach(btn => btn.classList.remove('active')); |
||||||
|
|
||||||
|
// Add new speed class |
||||||
|
if (speed !== 'normal') { |
||||||
|
butterfly.classList.add(speed); |
||||||
|
} |
||||||
|
|
||||||
|
// Mark active button |
||||||
|
event.target.classList.add('active'); |
||||||
|
} |
||||||
|
</script> |
||||||
|
</body> |
||||||
|
</html> |
After Width: | Height: | Size: 3.9 KiB |
@ -0,0 +1,249 @@ |
|||||||
|
import { useState } from 'react'; |
||||||
|
import { |
||||||
|
Box, |
||||||
|
Typography, |
||||||
|
Rating, |
||||||
|
Button, |
||||||
|
TextField, |
||||||
|
Collapse, |
||||||
|
IconButton, |
||||||
|
Chip, |
||||||
|
Paper, |
||||||
|
} from '@mui/material'; |
||||||
|
import { |
||||||
|
ThumbUp, |
||||||
|
ThumbDown, |
||||||
|
Feedback, |
||||||
|
Send, |
||||||
|
ExpandLess, |
||||||
|
} from '@mui/icons-material'; |
||||||
|
|
||||||
|
interface AIResponseRatingProps { |
||||||
|
responseId: string; |
||||||
|
onRatingSubmit: (rating: AIResponseRating) => void; |
||||||
|
existingRating?: AIResponseRating; |
||||||
|
} |
||||||
|
|
||||||
|
export interface AIResponseRating { |
||||||
|
responseId: string; |
||||||
|
rating: number; // 1-5 stars
|
||||||
|
feedback?: string; |
||||||
|
helpfulVote?: 'helpful' | 'not-helpful'; |
||||||
|
categories?: string[]; // e.g., ['accurate', 'comprehensive', 'actionable']
|
||||||
|
userId: string; |
||||||
|
timestamp: Date; |
||||||
|
} |
||||||
|
|
||||||
|
const AIResponseRatingComponent: React.FC<AIResponseRatingProps> = ({ |
||||||
|
responseId, |
||||||
|
onRatingSubmit, |
||||||
|
existingRating, |
||||||
|
}) => { |
||||||
|
const [rating, setRating] = useState<number>(existingRating?.rating || 0); |
||||||
|
const [feedback, setFeedback] = useState<string>(existingRating?.feedback || ''); |
||||||
|
const [helpfulVote, setHelpfulVote] = useState<'helpful' | 'not-helpful' | undefined>( |
||||||
|
existingRating?.helpfulVote |
||||||
|
); |
||||||
|
const [selectedCategories, setSelectedCategories] = useState<string[]>( |
||||||
|
existingRating?.categories || [] |
||||||
|
); |
||||||
|
const [showDetailedRating, setShowDetailedRating] = useState(false); |
||||||
|
const [hasSubmitted, setHasSubmitted] = useState(!!existingRating); |
||||||
|
|
||||||
|
const ratingCategories = [ |
||||||
|
{ id: 'accurate', label: 'Accurate', color: 'success' as const }, |
||||||
|
{ id: 'comprehensive', label: 'Comprehensive', color: 'info' as const }, |
||||||
|
{ id: 'actionable', label: 'Actionable', color: 'primary' as const }, |
||||||
|
{ id: 'relevant', label: 'Relevant', color: 'secondary' as const }, |
||||||
|
{ id: 'clear', label: 'Clear', color: 'default' as const }, |
||||||
|
{ id: 'timely', label: 'Timely', color: 'warning' as const }, |
||||||
|
]; |
||||||
|
|
||||||
|
const handleCategoryToggle = (categoryId: string) => { |
||||||
|
setSelectedCategories(prev =>
|
||||||
|
prev.includes(categoryId) |
||||||
|
? prev.filter(id => id !== categoryId) |
||||||
|
: [...prev, categoryId] |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleQuickVote = (vote: 'helpful' | 'not-helpful') => { |
||||||
|
setHelpfulVote(vote); |
||||||
|
|
||||||
|
// For quick votes, submit immediately with minimal data
|
||||||
|
const quickRating: AIResponseRating = { |
||||||
|
responseId, |
||||||
|
rating: vote === 'helpful' ? 4 : 2, // Default ratings for quick votes
|
||||||
|
helpfulVote: vote, |
||||||
|
categories: vote === 'helpful' ? ['relevant'] : [], |
||||||
|
userId: 'current-user', // Would be actual user ID
|
||||||
|
timestamp: new Date(), |
||||||
|
}; |
||||||
|
|
||||||
|
onRatingSubmit(quickRating); |
||||||
|
setHasSubmitted(true); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleDetailedSubmit = () => { |
||||||
|
if (rating === 0) return; |
||||||
|
|
||||||
|
const detailedRating: AIResponseRating = { |
||||||
|
responseId, |
||||||
|
rating, |
||||||
|
feedback: feedback.trim() || undefined, |
||||||
|
helpfulVote, |
||||||
|
categories: selectedCategories, |
||||||
|
userId: 'current-user', // Would be actual user ID
|
||||||
|
timestamp: new Date(), |
||||||
|
}; |
||||||
|
|
||||||
|
onRatingSubmit(detailedRating); |
||||||
|
setHasSubmitted(true); |
||||||
|
setShowDetailedRating(false); |
||||||
|
}; |
||||||
|
|
||||||
|
if (hasSubmitted && !showDetailedRating) { |
||||||
|
return ( |
||||||
|
<Paper sx={{ p: 2, mt: 2, bgcolor: 'success.50', border: 1, borderColor: 'success.200' }}> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> |
||||||
|
<ThumbUp sx={{ color: 'success.main', fontSize: 20 }} /> |
||||||
|
<Typography variant="body2" color="success.dark"> |
||||||
|
Thank you for rating this response! |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
onClick={() => setShowDetailedRating(true)} |
||||||
|
sx={{ color: 'success.dark' }} |
||||||
|
> |
||||||
|
Edit Rating |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return ( |
||||||
|
<Box sx={{ mt: 2 }}> |
||||||
|
{/* Quick Rating Buttons */} |
||||||
|
{!showDetailedRating && !hasSubmitted && ( |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
Was this response helpful? |
||||||
|
</Typography> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}> |
||||||
|
<Button |
||||||
|
size="small" |
||||||
|
variant={helpfulVote === 'helpful' ? 'contained' : 'outlined'} |
||||||
|
startIcon={<ThumbUp />} |
||||||
|
onClick={() => handleQuickVote('helpful')} |
||||||
|
color="success" |
||||||
|
> |
||||||
|
Yes |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
size="small" |
||||||
|
variant={helpfulVote === 'not-helpful' ? 'contained' : 'outlined'} |
||||||
|
startIcon={<ThumbDown />} |
||||||
|
onClick={() => handleQuickVote('not-helpful')} |
||||||
|
color="error" |
||||||
|
> |
||||||
|
No |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<Button |
||||||
|
size="small" |
||||||
|
startIcon={<Feedback />} |
||||||
|
onClick={() => setShowDetailedRating(true)} |
||||||
|
sx={{ ml: 'auto' }} |
||||||
|
> |
||||||
|
Detailed Rating |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
)} |
||||||
|
|
||||||
|
{/* Detailed Rating Panel */} |
||||||
|
<Collapse in={showDetailedRating}> |
||||||
|
<Paper sx={{ p: 3, border: 1, borderColor: 'divider' }}> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}> |
||||||
|
<Typography variant="h6">Rate This Response</Typography> |
||||||
|
<IconButton
|
||||||
|
size="small"
|
||||||
|
onClick={() => setShowDetailedRating(false)} |
||||||
|
> |
||||||
|
<ExpandLess /> |
||||||
|
</IconButton> |
||||||
|
</Box> |
||||||
|
|
||||||
|
{/* Star Rating */} |
||||||
|
<Box sx={{ mb: 3 }}> |
||||||
|
<Typography variant="body2" gutterBottom> |
||||||
|
Overall Rating |
||||||
|
</Typography> |
||||||
|
<Rating |
||||||
|
value={rating} |
||||||
|
onChange={(_, newValue) => setRating(newValue || 0)} |
||||||
|
size="large" |
||||||
|
/> |
||||||
|
</Box> |
||||||
|
|
||||||
|
{/* Categories */} |
||||||
|
<Box sx={{ mb: 3 }}> |
||||||
|
<Typography variant="body2" gutterBottom> |
||||||
|
What made this response good? (optional) |
||||||
|
</Typography> |
||||||
|
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}> |
||||||
|
{ratingCategories.map((category) => ( |
||||||
|
<Chip |
||||||
|
key={category.id} |
||||||
|
label={category.label} |
||||||
|
variant={selectedCategories.includes(category.id) ? 'filled' : 'outlined'} |
||||||
|
color={selectedCategories.includes(category.id) ? category.color : 'default'} |
||||||
|
onClick={() => handleCategoryToggle(category.id)} |
||||||
|
size="small" |
||||||
|
/> |
||||||
|
))} |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
|
||||||
|
{/* Feedback */} |
||||||
|
<Box sx={{ mb: 3 }}> |
||||||
|
<TextField |
||||||
|
fullWidth |
||||||
|
multiline |
||||||
|
rows={3} |
||||||
|
placeholder="Any additional feedback to help improve AI responses? (optional)" |
||||||
|
value={feedback} |
||||||
|
onChange={(e) => setFeedback(e.target.value)} |
||||||
|
variant="outlined" |
||||||
|
size="small" |
||||||
|
/> |
||||||
|
</Box> |
||||||
|
|
||||||
|
{/* Submit Button */} |
||||||
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', gap: 1 }}> |
||||||
|
<Button
|
||||||
|
onClick={() => setShowDetailedRating(false)} |
||||||
|
variant="outlined" |
||||||
|
> |
||||||
|
Cancel |
||||||
|
</Button> |
||||||
|
<Button |
||||||
|
onClick={handleDetailedSubmit} |
||||||
|
variant="contained" |
||||||
|
startIcon={<Send />} |
||||||
|
disabled={rating === 0} |
||||||
|
> |
||||||
|
Submit Rating |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</Collapse> |
||||||
|
</Box> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default AIResponseRatingComponent; |
@ -0,0 +1,226 @@ |
|||||||
|
import { useState } from 'react'; |
||||||
|
import { |
||||||
|
Dialog, |
||||||
|
DialogTitle, |
||||||
|
DialogContent, |
||||||
|
DialogActions, |
||||||
|
Button, |
||||||
|
TextField, |
||||||
|
Box, |
||||||
|
Typography, |
||||||
|
Avatar, |
||||||
|
alpha, |
||||||
|
useTheme, |
||||||
|
} from '@mui/material'; |
||||||
|
import { |
||||||
|
Business, |
||||||
|
PersonOutline, |
||||||
|
Groups, |
||||||
|
FamilyRestroom, |
||||||
|
Favorite, |
||||||
|
Home, |
||||||
|
PersonAdd, |
||||||
|
} from '@mui/icons-material'; |
||||||
|
import { DEFAULT_RCARDS } from '../../types/notification'; |
||||||
|
import type { Group } from '../../types/group'; |
||||||
|
|
||||||
|
interface InviteFormProps { |
||||||
|
open: boolean; |
||||||
|
onClose: () => void; |
||||||
|
onSubmit: (inviteData: InviteFormData) => void; |
||||||
|
group: Group; |
||||||
|
} |
||||||
|
|
||||||
|
export interface InviteFormData { |
||||||
|
inviteeName: string; |
||||||
|
inviteeEmail: string; |
||||||
|
relationshipType: string; |
||||||
|
relationshipData: { |
||||||
|
name: string; |
||||||
|
description: string; |
||||||
|
color: string; |
||||||
|
icon: string; |
||||||
|
}; |
||||||
|
inviterName: string; // Will be set from current user
|
||||||
|
} |
||||||
|
|
||||||
|
interface InviteFormState { |
||||||
|
inviteeName?: string; |
||||||
|
inviteeEmail?: string; |
||||||
|
relationshipType?: string; |
||||||
|
inviterName?: string; |
||||||
|
} |
||||||
|
|
||||||
|
const InviteForm: React.FC<InviteFormProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
onSubmit,
|
||||||
|
group
|
||||||
|
}) => { |
||||||
|
const theme = useTheme(); |
||||||
|
const [formData, setFormData] = useState<InviteFormState>({ |
||||||
|
inviteeName: '', |
||||||
|
inviteeEmail: '', |
||||||
|
relationshipType: '', |
||||||
|
inviterName: 'Oli S-B', // Current user
|
||||||
|
}); |
||||||
|
const [selectedRelationship, setSelectedRelationship] = useState<string>(''); |
||||||
|
|
||||||
|
const getRCardIcon = (iconName: string) => { |
||||||
|
const iconMap: Record<string, React.ReactElement> = { |
||||||
|
Business: <Business />, |
||||||
|
PersonOutline: <PersonOutline />, |
||||||
|
Groups: <Groups />, |
||||||
|
FamilyRestroom: <FamilyRestroom />, |
||||||
|
Favorite: <Favorite />, |
||||||
|
Home: <Home />, |
||||||
|
}; |
||||||
|
return iconMap[iconName] || <PersonOutline />; |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSubmit = () => { |
||||||
|
if (!formData.inviteeName || !formData.inviteeEmail || !selectedRelationship) { |
||||||
|
return; // TODO: Add validation feedback
|
||||||
|
} |
||||||
|
|
||||||
|
const selectedRCard = DEFAULT_RCARDS.find(card => card.name === selectedRelationship); |
||||||
|
|
||||||
|
if (selectedRCard && formData.inviteeName && formData.inviteeEmail) { |
||||||
|
const inviteData: InviteFormData = { |
||||||
|
inviteeName: formData.inviteeName, |
||||||
|
inviteeEmail: formData.inviteeEmail, |
||||||
|
relationshipType: selectedRelationship, |
||||||
|
relationshipData: { |
||||||
|
name: selectedRCard.name || 'Unknown', |
||||||
|
description: selectedRCard.description || 'No description', |
||||||
|
color: selectedRCard.color || '#2563eb', |
||||||
|
icon: selectedRCard.icon || 'PersonOutline', |
||||||
|
}, |
||||||
|
inviterName: formData.inviterName || 'Current User', |
||||||
|
}; |
||||||
|
|
||||||
|
onSubmit(inviteData); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleRelationshipSelect = (relationshipName: string) => { |
||||||
|
setSelectedRelationship(relationshipName); |
||||||
|
setFormData(prev => ({ |
||||||
|
...prev, |
||||||
|
relationshipType: relationshipName |
||||||
|
})); |
||||||
|
}; |
||||||
|
|
||||||
|
return ( |
||||||
|
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth> |
||||||
|
<DialogTitle> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> |
||||||
|
<PersonAdd /> |
||||||
|
<Typography variant="h6"> |
||||||
|
Invite Someone to {group.name} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</DialogTitle> |
||||||
|
|
||||||
|
<DialogContent sx={{ p: 3 }}> |
||||||
|
{/* Basic Info */} |
||||||
|
<Box sx={{ mb: 4 }}> |
||||||
|
<Typography variant="h6" gutterBottom> |
||||||
|
Who are you inviting? |
||||||
|
</Typography> |
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', gap: 2, flexDirection: { xs: 'column', sm: 'row' } }}> |
||||||
|
<TextField |
||||||
|
sx={{ flex: 1 }} |
||||||
|
label="First Name" |
||||||
|
value={formData.inviteeName || ''} |
||||||
|
onChange={(e) => setFormData(prev => ({ |
||||||
|
...prev, |
||||||
|
inviteeName: e.target.value |
||||||
|
}))} |
||||||
|
required |
||||||
|
/> |
||||||
|
<TextField |
||||||
|
sx={{ flex: 1 }} |
||||||
|
label="Email Address" |
||||||
|
type="email" |
||||||
|
value={formData.inviteeEmail || ''} |
||||||
|
onChange={(e) => setFormData(prev => ({ |
||||||
|
...prev, |
||||||
|
inviteeEmail: e.target.value |
||||||
|
}))} |
||||||
|
required |
||||||
|
/> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
|
||||||
|
{/* Relationship Selection */} |
||||||
|
<Box sx={{ mb: 3 }}> |
||||||
|
<Typography variant="h6" gutterBottom> |
||||||
|
What's your relationship with them? |
||||||
|
</Typography> |
||||||
|
|
||||||
|
<Box sx={{
|
||||||
|
display: 'flex',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: 1, |
||||||
|
'@media (max-width: 600px)': { |
||||||
|
flexDirection: 'column' |
||||||
|
} |
||||||
|
}}> |
||||||
|
{DEFAULT_RCARDS.map((rCard) => ( |
||||||
|
<Button |
||||||
|
key={rCard.name} |
||||||
|
variant={selectedRelationship === rCard.name ? "contained" : "outlined"} |
||||||
|
onClick={() => handleRelationshipSelect(rCard.name)} |
||||||
|
startIcon={ |
||||||
|
<Avatar |
||||||
|
sx={{ |
||||||
|
bgcolor: selectedRelationship === rCard.name ? 'white' : rCard.color, |
||||||
|
color: selectedRelationship === rCard.name ? rCard.color : 'white', |
||||||
|
width: 24, |
||||||
|
height: 24, |
||||||
|
'& .MuiSvgIcon-root': { fontSize: 16 } |
||||||
|
}} |
||||||
|
> |
||||||
|
{getRCardIcon(rCard.icon || 'PersonOutline')} |
||||||
|
</Avatar> |
||||||
|
} |
||||||
|
sx={{ |
||||||
|
flex: { xs: 'none', sm: '1 1 auto' }, |
||||||
|
minWidth: 'fit-content', |
||||||
|
borderRadius: 2, |
||||||
|
textTransform: 'none', |
||||||
|
py: 1.5, |
||||||
|
px: 2, |
||||||
|
'&:hover': { |
||||||
|
transform: 'translateY(-1px)', |
||||||
|
boxShadow: theme.shadows[4], |
||||||
|
}, |
||||||
|
}} |
||||||
|
> |
||||||
|
{rCard.name} |
||||||
|
</Button> |
||||||
|
))} |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
|
||||||
|
</DialogContent> |
||||||
|
|
||||||
|
<DialogActions sx={{ p: 3, justifyContent: 'space-between' }}> |
||||||
|
<Button onClick={onClose} variant="outlined"> |
||||||
|
Cancel |
||||||
|
</Button> |
||||||
|
<Button
|
||||||
|
onClick={handleSubmit}
|
||||||
|
variant="contained" |
||||||
|
disabled={!formData.inviteeName || !formData.inviteeEmail || !selectedRelationship} |
||||||
|
> |
||||||
|
Create Invite |
||||||
|
</Button> |
||||||
|
</DialogActions> |
||||||
|
</Dialog> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default InviteForm; |
@ -0,0 +1,330 @@ |
|||||||
|
import { useState } from 'react'; |
||||||
|
import { |
||||||
|
Dialog, |
||||||
|
DialogTitle, |
||||||
|
DialogContent, |
||||||
|
DialogActions, |
||||||
|
Button, |
||||||
|
Box, |
||||||
|
Typography, |
||||||
|
Stepper, |
||||||
|
Step, |
||||||
|
StepLabel, |
||||||
|
Chip, |
||||||
|
Avatar, |
||||||
|
List, |
||||||
|
ListItem, |
||||||
|
Paper, |
||||||
|
Rating, |
||||||
|
} from '@mui/material'; |
||||||
|
import { |
||||||
|
AutoAwesome, |
||||||
|
RssFeed, |
||||||
|
People, |
||||||
|
Chat, |
||||||
|
Folder, |
||||||
|
Link as LinkIcon, |
||||||
|
TipsAndUpdates, |
||||||
|
ThumbUp, |
||||||
|
QuestionAnswer, |
||||||
|
CheckCircle, |
||||||
|
} from '@mui/icons-material'; |
||||||
|
import type { Group } from '../../types/group'; |
||||||
|
|
||||||
|
interface GroupTourProps { |
||||||
|
open: boolean; |
||||||
|
onClose: () => void; |
||||||
|
group: Group; |
||||||
|
onStartAIAssistant: (prompt?: string) => void; |
||||||
|
} |
||||||
|
|
||||||
|
interface TourStep { |
||||||
|
title: string; |
||||||
|
description: string; |
||||||
|
icon: React.ReactNode; |
||||||
|
target?: string; |
||||||
|
} |
||||||
|
|
||||||
|
interface PopularPrompt { |
||||||
|
id: string; |
||||||
|
prompt: string; |
||||||
|
averageRating: number; |
||||||
|
responseCount: number; |
||||||
|
category: string; |
||||||
|
} |
||||||
|
|
||||||
|
const GroupTour: React.FC<GroupTourProps> = ({
|
||||||
|
open,
|
||||||
|
onClose,
|
||||||
|
group,
|
||||||
|
onStartAIAssistant
|
||||||
|
}) => { |
||||||
|
const [currentStep, setCurrentStep] = useState(0); |
||||||
|
const [showPopularPrompts, setShowPopularPrompts] = useState(false); |
||||||
|
|
||||||
|
const tourSteps: TourStep[] = [ |
||||||
|
{ |
||||||
|
title: `Welcome to ${group.name}!`, |
||||||
|
description: `Great! You've joined ${group.name}. Let me give you a quick tour of what you can do here.`, |
||||||
|
icon: <CheckCircle sx={{ fontSize: 40, color: 'success.main' }} />, |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: 'Group Feed', |
||||||
|
description: 'This is where group members share updates, discussions, and collaborate. You can post, comment, and engage with other members here.', |
||||||
|
icon: <RssFeed sx={{ fontSize: 40, color: 'primary.main' }} />, |
||||||
|
target: 'feed-tab', |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: 'Members', |
||||||
|
description: `See all ${group.memberCount} members of the group, their roles, and activity levels. Great for networking and finding collaborators.`, |
||||||
|
icon: <People sx={{ fontSize: 40, color: 'info.main' }} />, |
||||||
|
target: 'members-tab', |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: 'Group Chat', |
||||||
|
description: 'Real-time messaging with the entire group. Perfect for quick discussions and staying connected.', |
||||||
|
icon: <Chat sx={{ fontSize: 40, color: 'secondary.main' }} />, |
||||||
|
target: 'chat-tab', |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: 'Collaborative Files', |
||||||
|
description: 'Share documents, spreadsheets, and other files with the group. Work together on projects in real-time.', |
||||||
|
icon: <Folder sx={{ fontSize: 40, color: 'warning.main' }} />, |
||||||
|
target: 'files-tab', |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: 'Useful Links', |
||||||
|
description: 'Important resources, websites, and references shared by group members. Bookmark and discover valuable content.', |
||||||
|
icon: <LinkIcon sx={{ fontSize: 40, color: 'success.main' }} />, |
||||||
|
target: 'links-tab', |
||||||
|
}, |
||||||
|
{ |
||||||
|
title: 'AI Assistant', |
||||||
|
description: 'Your smart companion for this group! Ask questions about members, projects, or get insights about group activity.', |
||||||
|
icon: <AutoAwesome sx={{ fontSize: 40, color: 'primary.main' }} />, |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
// Mock data for popular prompts - in real app, this would come from API
|
||||||
|
const popularPrompts: PopularPrompt[] = [ |
||||||
|
{ |
||||||
|
id: '1', |
||||||
|
prompt: "Who's highly engaged in this project?", |
||||||
|
averageRating: 4.8, |
||||||
|
responseCount: 23, |
||||||
|
category: 'Members & Engagement', |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: '2', |
||||||
|
prompt: "Who's working on which tasks and needs help?", |
||||||
|
averageRating: 4.6, |
||||||
|
responseCount: 18, |
||||||
|
category: 'Project Management', |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: '3', |
||||||
|
prompt: "What are the most important discussions happening right now?", |
||||||
|
averageRating: 4.5, |
||||||
|
responseCount: 15, |
||||||
|
category: 'Group Activity', |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: '4', |
||||||
|
prompt: "Show me recent files and documents shared by the team", |
||||||
|
averageRating: 4.4, |
||||||
|
responseCount: 12, |
||||||
|
category: 'Resources', |
||||||
|
}, |
||||||
|
{ |
||||||
|
id: '5', |
||||||
|
prompt: "Who are the subject matter experts I should connect with?", |
||||||
|
averageRating: 4.7, |
||||||
|
responseCount: 20, |
||||||
|
category: 'Networking', |
||||||
|
}, |
||||||
|
]; |
||||||
|
|
||||||
|
const handleNext = () => { |
||||||
|
if (currentStep < tourSteps.length - 1) { |
||||||
|
setCurrentStep(currentStep + 1); |
||||||
|
} else { |
||||||
|
setShowPopularPrompts(true); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleBack = () => { |
||||||
|
if (currentStep > 0) { |
||||||
|
setCurrentStep(currentStep - 1); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
const handleSkipTour = () => { |
||||||
|
setShowPopularPrompts(true); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleFinishTour = () => { |
||||||
|
onClose(); |
||||||
|
}; |
||||||
|
|
||||||
|
const handleUsePrompt = (prompt: string) => { |
||||||
|
onClose(); |
||||||
|
onStartAIAssistant(prompt); |
||||||
|
}; |
||||||
|
|
||||||
|
const renderTourStep = () => ( |
||||||
|
<Box sx={{ textAlign: 'center', py: 3 }}> |
||||||
|
<Box sx={{ mb: 3 }}> |
||||||
|
{tourSteps[currentStep].icon} |
||||||
|
</Box> |
||||||
|
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600 }}> |
||||||
|
{tourSteps[currentStep].title} |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 3, maxWidth: 400, mx: 'auto' }}> |
||||||
|
{tourSteps[currentStep].description} |
||||||
|
</Typography> |
||||||
|
|
||||||
|
{/* Progress indicator */} |
||||||
|
<Stepper activeStep={currentStep} sx={{ mt: 4, mb: 3 }}> |
||||||
|
{tourSteps.map((_, index) => ( |
||||||
|
<Step key={index}> |
||||||
|
<StepLabel /> |
||||||
|
</Step> |
||||||
|
))} |
||||||
|
</Stepper> |
||||||
|
</Box> |
||||||
|
); |
||||||
|
|
||||||
|
const renderPopularPrompts = () => ( |
||||||
|
<Box> |
||||||
|
<Box sx={{ textAlign: 'center', mb: 3 }}> |
||||||
|
<AutoAwesome sx={{ fontSize: 48, color: 'primary.main', mb: 2 }} /> |
||||||
|
<Typography variant="h5" gutterBottom sx={{ fontWeight: 600 }}> |
||||||
|
Try the AI Assistant! |
||||||
|
</Typography> |
||||||
|
<Typography variant="body1" color="text.secondary" sx={{ mb: 3 }}> |
||||||
|
Here are some popular questions other members have asked the AI assistant.
|
||||||
|
These prompts received high ratings from the community: |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
|
||||||
|
<List sx={{ maxHeight: 400, overflow: 'auto' }}> |
||||||
|
{popularPrompts.map((prompt) => ( |
||||||
|
<ListItem key={prompt.id} sx={{ mb: 1 }}> |
||||||
|
<Paper
|
||||||
|
sx={{
|
||||||
|
p: 2,
|
||||||
|
width: '100%',
|
||||||
|
cursor: 'pointer', |
||||||
|
transition: 'all 0.2s', |
||||||
|
'&:hover': { |
||||||
|
boxShadow: 2, |
||||||
|
transform: 'translateY(-1px)', |
||||||
|
} |
||||||
|
}} |
||||||
|
onClick={() => handleUsePrompt(prompt.prompt)} |
||||||
|
> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 1 }}> |
||||||
|
<Chip
|
||||||
|
label={prompt.category}
|
||||||
|
size="small"
|
||||||
|
variant="outlined"
|
||||||
|
color="primary" |
||||||
|
/> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> |
||||||
|
<Rating value={prompt.averageRating} readOnly precision={0.1} size="small" /> |
||||||
|
<Typography variant="caption" color="text.secondary"> |
||||||
|
({prompt.responseCount}) |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
<Typography variant="body1" sx={{ fontWeight: 500, mb: 1 }}> |
||||||
|
"{prompt.prompt}" |
||||||
|
</Typography> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> |
||||||
|
<ThumbUp sx={{ fontSize: 16, color: 'success.main' }} /> |
||||||
|
<Typography variant="caption" color="text.secondary"> |
||||||
|
{prompt.averageRating.toFixed(1)}/5.0 average rating • Click to try this prompt |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</Paper> |
||||||
|
</ListItem> |
||||||
|
))} |
||||||
|
</List> |
||||||
|
|
||||||
|
<Box sx={{ textAlign: 'center', mt: 3, p: 2, bgcolor: 'grey.50', borderRadius: 2 }}> |
||||||
|
<TipsAndUpdates sx={{ color: 'info.main', mb: 1 }} /> |
||||||
|
<Typography variant="body2" color="text.secondary"> |
||||||
|
You can also ask your own questions! The AI assistant knows about group members,
|
||||||
|
recent activity, shared files, and can help you get oriented. |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</Box> |
||||||
|
); |
||||||
|
|
||||||
|
return ( |
||||||
|
<Dialog
|
||||||
|
open={open}
|
||||||
|
onClose={onClose}
|
||||||
|
maxWidth="md"
|
||||||
|
fullWidth |
||||||
|
PaperProps={{ |
||||||
|
sx: { minHeight: 500 } |
||||||
|
}} |
||||||
|
> |
||||||
|
<DialogTitle> |
||||||
|
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> |
||||||
|
<Avatar sx={{ bgcolor: 'primary.main' }}> |
||||||
|
<AutoAwesome /> |
||||||
|
</Avatar> |
||||||
|
<Typography variant="h6"> |
||||||
|
{showPopularPrompts ? 'AI Assistant Examples' : 'Group Tour'} |
||||||
|
</Typography> |
||||||
|
</Box> |
||||||
|
</DialogTitle> |
||||||
|
|
||||||
|
<DialogContent sx={{ p: 3 }}> |
||||||
|
{showPopularPrompts ? renderPopularPrompts() : renderTourStep()} |
||||||
|
</DialogContent> |
||||||
|
|
||||||
|
<DialogActions sx={{ p: 3, justifyContent: 'space-between' }}> |
||||||
|
{!showPopularPrompts ? ( |
||||||
|
<> |
||||||
|
<Button onClick={handleSkipTour} color="inherit"> |
||||||
|
Skip Tour |
||||||
|
</Button> |
||||||
|
<Box sx={{ display: 'flex', gap: 1 }}> |
||||||
|
<Button
|
||||||
|
onClick={handleBack}
|
||||||
|
disabled={currentStep === 0} |
||||||
|
variant="outlined" |
||||||
|
> |
||||||
|
Back |
||||||
|
</Button> |
||||||
|
<Button
|
||||||
|
onClick={handleNext}
|
||||||
|
variant="contained" |
||||||
|
> |
||||||
|
{currentStep === tourSteps.length - 1 ? 'Continue to AI Assistant' : 'Next'} |
||||||
|
</Button> |
||||||
|
</Box> |
||||||
|
</> |
||||||
|
) : ( |
||||||
|
<> |
||||||
|
<Button
|
||||||
|
onClick={() => onStartAIAssistant()}
|
||||||
|
variant="outlined" |
||||||
|
startIcon={<QuestionAnswer />} |
||||||
|
> |
||||||
|
Ask My Own Question |
||||||
|
</Button> |
||||||
|
<Button onClick={handleFinishTour} variant="contained"> |
||||||
|
Finish Tour |
||||||
|
</Button> |
||||||
|
</> |
||||||
|
)} |
||||||
|
</DialogActions> |
||||||
|
</Dialog> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default GroupTour; |
@ -0,0 +1,297 @@ |
|||||||
|
import React from 'react'; |
||||||
|
import { Box, keyframes } from '@mui/material'; |
||||||
|
|
||||||
|
const wingFlapLeft = keyframes` |
||||||
|
0%, 100% { |
||||||
|
transform: rotateZ(-5deg); |
||||||
|
} |
||||||
|
25% { |
||||||
|
transform: rotateZ(-45deg); |
||||||
|
} |
||||||
|
75% { |
||||||
|
transform: rotateZ(-60deg); |
||||||
|
} |
||||||
|
`;
|
||||||
|
|
||||||
|
const wingFlapRight = keyframes` |
||||||
|
0%, 100% { |
||||||
|
transform: rotateZ(5deg); |
||||||
|
} |
||||||
|
25% { |
||||||
|
transform: rotateZ(45deg); |
||||||
|
} |
||||||
|
75% { |
||||||
|
transform: rotateZ(60deg); |
||||||
|
} |
||||||
|
`;
|
||||||
|
|
||||||
|
const butterflyFlightPath = keyframes` |
||||||
|
0% { |
||||||
|
top: 25vh; |
||||||
|
left: 15vw; |
||||||
|
} |
||||||
|
8% { |
||||||
|
top: 20vh; |
||||||
|
left: 25vw; |
||||||
|
} |
||||||
|
16% { |
||||||
|
top: 35vh; |
||||||
|
left: 45vw; |
||||||
|
} |
||||||
|
24% { |
||||||
|
top: 15vh; |
||||||
|
left: 65vw; |
||||||
|
} |
||||||
|
32% { |
||||||
|
top: 40vh; |
||||||
|
left: 75vw; |
||||||
|
} |
||||||
|
40% { |
||||||
|
top: 60vh; |
||||||
|
left: 80vw; |
||||||
|
} |
||||||
|
48% { |
||||||
|
top: 70vh; |
||||||
|
left: 65vw; |
||||||
|
} |
||||||
|
56% { |
||||||
|
top: 75vh; |
||||||
|
left: 45vw; |
||||||
|
} |
||||||
|
64% { |
||||||
|
top: 60vh; |
||||||
|
left: 25vw; |
||||||
|
} |
||||||
|
72% { |
||||||
|
top: 45vh; |
||||||
|
left: 10vw; |
||||||
|
} |
||||||
|
80% { |
||||||
|
top: 30vh; |
||||||
|
left: 5vw; |
||||||
|
} |
||||||
|
88% { |
||||||
|
top: 20vh; |
||||||
|
left: 8vw; |
||||||
|
} |
||||||
|
96% { |
||||||
|
top: 22vh; |
||||||
|
left: 12vw; |
||||||
|
} |
||||||
|
100% { |
||||||
|
top: 25vh; |
||||||
|
left: 15vw; |
||||||
|
} |
||||||
|
`;
|
||||||
|
|
||||||
|
const bodyPulse = keyframes` |
||||||
|
0%, 100% { |
||||||
|
transform: scale(1); |
||||||
|
} |
||||||
|
50% { |
||||||
|
transform: scale(1.05); |
||||||
|
} |
||||||
|
`;
|
||||||
|
|
||||||
|
const shimmer = keyframes` |
||||||
|
0%, 100% { |
||||||
|
opacity: 0.8; |
||||||
|
} |
||||||
|
50% { |
||||||
|
opacity: 1; |
||||||
|
} |
||||||
|
`;
|
||||||
|
|
||||||
|
interface AnimatedMorphoButterflyProps { |
||||||
|
size?: number; |
||||||
|
className?: string; |
||||||
|
variant?: 'static' | 'floating'; |
||||||
|
} |
||||||
|
|
||||||
|
const AnimatedMorphoButterfly: React.FC<AnimatedMorphoButterflyProps> = ({
|
||||||
|
size = 48,
|
||||||
|
className, |
||||||
|
variant = 'static' |
||||||
|
}) => { |
||||||
|
return ( |
||||||
|
<Box |
||||||
|
className={className} |
||||||
|
sx={{ |
||||||
|
display: variant === 'floating' ? 'block' : 'inline-block', |
||||||
|
width: size, |
||||||
|
height: size, |
||||||
|
...(variant === 'floating' && { |
||||||
|
position: 'fixed', |
||||||
|
animation: `${butterflyFlightPath} 20s ease-in-out infinite`, |
||||||
|
zIndex: 1000, |
||||||
|
pointerEvents: 'none', |
||||||
|
}) |
||||||
|
}} |
||||||
|
> |
||||||
|
<svg |
||||||
|
viewBox="0 0 100 80" |
||||||
|
width={size} |
||||||
|
height={size * 0.8} |
||||||
|
style={{ overflow: 'visible' }} |
||||||
|
> |
||||||
|
{/* Wing gradients */} |
||||||
|
<defs> |
||||||
|
<radialGradient id="morphoBlue" cx="50%" cy="30%" r="70%"> |
||||||
|
<stop offset="0%" style={{ stopColor: '#00D4FF', stopOpacity: 1 }} /> |
||||||
|
<stop offset="40%" style={{ stopColor: '#0099CC', stopOpacity: 1 }} /> |
||||||
|
<stop offset="80%" style={{ stopColor: '#003366', stopOpacity: 1 }} /> |
||||||
|
<stop offset="100%" style={{ stopColor: '#001122', stopOpacity: 1 }} /> |
||||||
|
</radialGradient> |
||||||
|
|
||||||
|
<radialGradient id="morphoBlueSecondary" cx="50%" cy="30%" r="70%"> |
||||||
|
<stop offset="0%" style={{ stopColor: '#33AAFF', stopOpacity: 1 }} /> |
||||||
|
<stop offset="40%" style={{ stopColor: '#0077AA', stopOpacity: 1 }} /> |
||||||
|
<stop offset="80%" style={{ stopColor: '#002244', stopOpacity: 1 }} /> |
||||||
|
<stop offset="100%" style={{ stopColor: '#000D1A', stopOpacity: 1 }} /> |
||||||
|
</radialGradient> |
||||||
|
|
||||||
|
<linearGradient id="bodyGradient" x1="0%" y1="0%" x2="0%" y2="100%"> |
||||||
|
<stop offset="0%" style={{ stopColor: '#2D1B1B', stopOpacity: 1 }} /> |
||||||
|
<stop offset="50%" style={{ stopColor: '#1A0F0F', stopOpacity: 1 }} /> |
||||||
|
<stop offset="100%" style={{ stopColor: '#0D0505', stopOpacity: 1 }} /> |
||||||
|
</linearGradient> |
||||||
|
|
||||||
|
<filter id="glow"> |
||||||
|
<feGaussianBlur stdDeviation="2" result="coloredBlur"/> |
||||||
|
<feMerge>
|
||||||
|
<feMergeNode in="coloredBlur"/> |
||||||
|
<feMergeNode in="SourceGraphic"/> |
||||||
|
</feMerge> |
||||||
|
</filter> |
||||||
|
</defs> |
||||||
|
|
||||||
|
{/* Left wings */} |
||||||
|
<g |
||||||
|
style={{ |
||||||
|
transformOrigin: '35px 40px', |
||||||
|
animation: `${wingFlapLeft} 0.3s ease-in-out infinite`, |
||||||
|
}} |
||||||
|
> |
||||||
|
{/* Left upper wing */} |
||||||
|
<path |
||||||
|
d="M35 40 Q15 25 8 15 Q5 10 8 8 Q15 5 25 12 Q32 20 35 30 Z" |
||||||
|
fill="url(#morphoBlue)" |
||||||
|
stroke="#001122" |
||||||
|
strokeWidth="0.5" |
||||||
|
filter="url(#glow)" |
||||||
|
style={{ |
||||||
|
animation: `${shimmer} 2s ease-in-out infinite`, |
||||||
|
}} |
||||||
|
/> |
||||||
|
|
||||||
|
{/* Left lower wing */} |
||||||
|
<path |
||||||
|
d="M35 40 Q20 50 12 60 Q8 65 10 68 Q15 72 25 65 Q32 55 35 45 Z" |
||||||
|
fill="url(#morphoBlueSecondary)" |
||||||
|
stroke="#001122" |
||||||
|
strokeWidth="0.5" |
||||||
|
filter="url(#glow)" |
||||||
|
style={{ |
||||||
|
animation: `${shimmer} 2s ease-in-out infinite 0.3s`, |
||||||
|
}} |
||||||
|
/> |
||||||
|
|
||||||
|
{/* Wing spots/patterns */} |
||||||
|
<circle cx="22" cy="20" r="2" fill="#00AAFF" opacity="0.7" /> |
||||||
|
<circle cx="18" cy="28" r="1.5" fill="#33BBFF" opacity="0.6" /> |
||||||
|
<circle cx="25" cy="55" r="1.8" fill="#00AAFF" opacity="0.7" /> |
||||||
|
</g> |
||||||
|
|
||||||
|
{/* Right wings */} |
||||||
|
<g |
||||||
|
style={{ |
||||||
|
transformOrigin: '65px 40px', |
||||||
|
animation: `${wingFlapRight} 0.3s ease-in-out infinite`, |
||||||
|
}} |
||||||
|
> |
||||||
|
{/* Right upper wing */} |
||||||
|
<path |
||||||
|
d="M65 40 Q85 25 92 15 Q95 10 92 8 Q85 5 75 12 Q68 20 65 30 Z" |
||||||
|
fill="url(#morphoBlue)" |
||||||
|
stroke="#001122" |
||||||
|
strokeWidth="0.5" |
||||||
|
filter="url(#glow)" |
||||||
|
style={{ |
||||||
|
animation: `${shimmer} 2s ease-in-out infinite 0.1s`, |
||||||
|
}} |
||||||
|
/> |
||||||
|
|
||||||
|
{/* Right lower wing */} |
||||||
|
<path |
||||||
|
d="M65 40 Q80 50 88 60 Q92 65 90 68 Q85 72 75 65 Q68 55 65 45 Z" |
||||||
|
fill="url(#morphoBlueSecondary)" |
||||||
|
stroke="#001122" |
||||||
|
strokeWidth="0.5" |
||||||
|
filter="url(#glow)" |
||||||
|
style={{ |
||||||
|
animation: `${shimmer} 2s ease-in-out infinite 0.4s`, |
||||||
|
}} |
||||||
|
/> |
||||||
|
|
||||||
|
{/* Wing spots/patterns */} |
||||||
|
<circle cx="78" cy="20" r="2" fill="#00AAFF" opacity="0.7" /> |
||||||
|
<circle cx="82" cy="28" r="1.5" fill="#33BBFF" opacity="0.6" /> |
||||||
|
<circle cx="75" cy="55" r="1.8" fill="#00AAFF" opacity="0.7" /> |
||||||
|
</g> |
||||||
|
|
||||||
|
{/* Butterfly body */} |
||||||
|
<ellipse |
||||||
|
cx="50" |
||||||
|
cy="40" |
||||||
|
rx="3" |
||||||
|
ry="25" |
||||||
|
fill="url(#bodyGradient)" |
||||||
|
stroke="#0D0505" |
||||||
|
strokeWidth="0.5" |
||||||
|
style={{ |
||||||
|
transformOrigin: '50px 40px', |
||||||
|
animation: `${bodyPulse} 2s ease-in-out infinite`, |
||||||
|
}} |
||||||
|
/> |
||||||
|
|
||||||
|
{/* Head */} |
||||||
|
<circle |
||||||
|
cx="50" |
||||||
|
cy="20" |
||||||
|
r="4" |
||||||
|
fill="#2D1B1B" |
||||||
|
stroke="#0D0505" |
||||||
|
strokeWidth="0.5" |
||||||
|
/> |
||||||
|
|
||||||
|
{/* Antennae */} |
||||||
|
<path |
||||||
|
d="M48 18 Q45 12 42 8" |
||||||
|
stroke="#2D1B1B" |
||||||
|
strokeWidth="1" |
||||||
|
fill="none" |
||||||
|
strokeLinecap="round" |
||||||
|
/> |
||||||
|
<path |
||||||
|
d="M52 18 Q55 12 58 8" |
||||||
|
stroke="#2D1B1B" |
||||||
|
strokeWidth="1" |
||||||
|
fill="none" |
||||||
|
strokeLinecap="round" |
||||||
|
/> |
||||||
|
|
||||||
|
{/* Antennae tips */} |
||||||
|
<circle cx="42" cy="8" r="1" fill="#2D1B1B" /> |
||||||
|
<circle cx="58" cy="8" r="1" fill="#2D1B1B" /> |
||||||
|
|
||||||
|
{/* Eyes */} |
||||||
|
<circle cx="47" cy="18" r="1" fill="#000" /> |
||||||
|
<circle cx="53" cy="18" r="1" fill="#000" /> |
||||||
|
<circle cx="47" cy="18" r="0.5" fill="#FFF" opacity="0.6" /> |
||||||
|
<circle cx="53" cy="18" r="0.5" fill="#FFF" opacity="0.6" /> |
||||||
|
</svg> |
||||||
|
</Box> |
||||||
|
); |
||||||
|
}; |
||||||
|
|
||||||
|
export default AnimatedMorphoButterfly; |
Loading…
Reference in new issue