Skip to content

Commit c1316d6

Browse files
committed
Merge remote-tracking branch 'origin/Catherine's-Branch-for-Sign-Up-Page'
2 parents f43b39f + 0e16fbc commit c1316d6

9 files changed

Lines changed: 512 additions & 36 deletions

File tree

package-lock.json

Lines changed: 22 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"lucide-react": "^1.8.0",
1414
"react": "^19.2.4",
1515
"react-dom": "^19.2.4",
16+
"react-easy-crop": "^5.5.7",
1617
"react-router": "^7.14.1"
1718
},
1819
"devDependencies": {

src/App.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import { Routes, Route, Link, useLocation } from 'react-router';
2-
import Home from './pages/home/Home';
32
import Signup from './pages/Signup';
3+
import Preferences from './pages/Preferences';
4+
import Home from './pages/home/Home';
45
import Explore from './pages/Explore';
56
import Chat from './pages/Chat';
67
import { Rocket } from 'lucide-react';
78

89
function App() {
910
const location = useLocation();
11+
const hideExploreLink = location.pathname === '/' || location.pathname === '/preferences';
1012
const isHomePage = location.pathname === '/';
1113

1214
return (
@@ -16,7 +18,7 @@ function App() {
1618
<Rocket className="inline-block mr-2" size={24} style={{ verticalAlign: 'text-bottom', color: 'var(--color-secondary)' }} />
1719
SSTRUK
1820
</Link>
19-
{!isHomePage && (
21+
{!hideExploreLink && (
2022
<div style={{ display: 'flex', gap: '16px' }}>
2123
<Link to="/explore" className="btn-outline" style={{ padding: '6px 16px', fontSize: '0.9rem' }}>Explore</Link>
2224
</div>
@@ -27,6 +29,7 @@ function App() {
2729
<Routes>
2830
<Route path="/" element={<Home />} />
2931
<Route path="/signup" element={<Signup />} />
32+
<Route path="/preferences" element={<Preferences />} />
3033
<Route path="/explore" element={<Explore />} />
3134
<Route path="/chat/:id" element={<Chat />} />
3235
</Routes>

src/components/OrbitSystem.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ export default function OrbitSystem() {
107107
className="orbit-item"
108108
onClick={() => setSelectedAlien(alien)}
109109
style={{
110+
backgroundImage: `url(${alien.profilePic})`,
110111
// @ts-ignore
111112
'--radius': `${radius}px`,
112113
'--duration': `${duration}s`,

src/components/PhotoCropper.tsx

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { useState, useCallback } from 'react';
2+
import Cropper from 'react-easy-crop';
3+
import type { Area } from 'react-easy-crop';
4+
5+
// Helper function to create an HTML image element from a URL
6+
const createImage = (url: string): Promise<HTMLImageElement> =>
7+
new Promise((resolve, reject) => {
8+
const image = new Image();
9+
image.addEventListener('load', () => resolve(image));
10+
image.addEventListener('error', (error) => reject(error));
11+
image.src = url;
12+
});
13+
14+
// Extract cropped portion of image using HTML5 Canvas
15+
async function getCroppedImg(
16+
imageSrc: string,
17+
pixelCrop: Area
18+
): Promise<string> {
19+
const image = await createImage(imageSrc);
20+
const canvas = document.createElement('canvas');
21+
const ctx = canvas.getContext('2d');
22+
23+
if (!ctx) {
24+
throw new Error('No 2d context');
25+
}
26+
27+
// Draw full image to canvas
28+
canvas.width = image.width;
29+
canvas.height = image.height;
30+
ctx.drawImage(image, 0, 0);
31+
32+
// Extract the cropped portion
33+
const croppedCanvas = document.createElement('canvas');
34+
const croppedCtx = croppedCanvas.getContext('2d');
35+
36+
if (!croppedCtx) {
37+
throw new Error('No 2d context');
38+
}
39+
40+
croppedCanvas.width = pixelCrop.width;
41+
croppedCanvas.height = pixelCrop.height;
42+
43+
croppedCtx.drawImage(
44+
canvas,
45+
pixelCrop.x,
46+
pixelCrop.y,
47+
pixelCrop.width,
48+
pixelCrop.height,
49+
0,
50+
0,
51+
pixelCrop.width,
52+
pixelCrop.height
53+
);
54+
55+
return croppedCanvas.toDataURL('image/jpeg', 0.9);
56+
}
57+
58+
interface PhotoCropperProps {
59+
imageSrc: string;
60+
onConfirm: (croppedImageBase64: string) => void;
61+
onCancel: () => void;
62+
}
63+
64+
export default function PhotoCropper({ imageSrc, onConfirm, onCancel }: PhotoCropperProps) {
65+
const [crop, setCrop] = useState({ x: 0, y: 0 });
66+
const [zoom, setZoom] = useState(1);
67+
const [croppedAreaPixels, setCroppedAreaPixels] = useState<Area | null>(null);
68+
69+
const onCropComplete = useCallback((_croppedArea: Area, croppedAreaPixels: Area) => {
70+
setCroppedAreaPixels(croppedAreaPixels);
71+
}, []);
72+
73+
const handleConfirm = async () => {
74+
if (croppedAreaPixels) {
75+
try {
76+
const croppedImage = await getCroppedImg(imageSrc, croppedAreaPixels);
77+
onConfirm(croppedImage);
78+
} catch (e) {
79+
console.error("Failed to crop image", e);
80+
}
81+
}
82+
};
83+
84+
return (
85+
<div style={{
86+
position: 'absolute',
87+
top: 0, left: 0, right: 0, bottom: 0,
88+
backgroundColor: '#1a1025',
89+
zIndex: 100,
90+
display: 'flex',
91+
flexDirection: 'column',
92+
padding: '20px'
93+
}}>
94+
<h2 style={{ textAlign: 'center', marginBottom: '16px', color: 'var(--color-secondary)' }}>Adjust Profile Photo</h2>
95+
96+
<div style={{ position: 'relative', flex: 1, backgroundColor: '#000', borderRadius: '16px', overflow: 'hidden' }}>
97+
<Cropper
98+
image={imageSrc}
99+
crop={crop}
100+
zoom={zoom}
101+
aspect={1}
102+
cropShape="round"
103+
showGrid={false}
104+
onCropChange={setCrop}
105+
onCropComplete={onCropComplete}
106+
onZoomChange={setZoom}
107+
/>
108+
</div>
109+
110+
<div style={{ padding: '24px 0 16px 0' }}>
111+
<label style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem', color: 'white' }}>Zoom</label>
112+
<input
113+
type="range"
114+
value={zoom}
115+
min={1}
116+
max={3}
117+
step={0.1}
118+
aria-labelledby="Zoom"
119+
onChange={(e) => {
120+
setZoom(Number(e.target.value));
121+
}}
122+
style={{ width: '100%' }}
123+
/>
124+
</div>
125+
126+
<div style={{ display: 'flex', gap: '16px', marginTop: '16px' }}>
127+
<button onClick={onCancel} className="btn-outline" style={{ flex: 1 }}>Cancel</button>
128+
<button onClick={handleConfirm} className="btn-primary" style={{ flex: 1 }}>Confirm</button>
129+
</div>
130+
</div>
131+
);
132+
}

src/context/AppContext.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,16 @@ import type { AlienProfile } from '../data/mockAliens';
33

44
export interface UserPreferences {
55
name: string;
6+
age: string;
7+
species: string;
8+
planet: string;
9+
bio: string;
10+
interests: string[];
611
limbs: number;
712
alienType: string;
813
size: string;
914
maxDistanceAU: number;
15+
goals: string;
1016
profilePic: string; // URL or base64
1117
}
1218

src/index.css

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,12 @@ input, select, textarea {
105105
transition: border-color 0.2s ease, background 0.2s ease;
106106
}
107107

108+
input[type="range"] {
109+
padding: 0;
110+
border: none;
111+
background: transparent;
112+
}
113+
108114
input:focus, select:focus, textarea:focus {
109115
outline: none;
110116
border-color: var(--color-secondary);

src/pages/Preferences.tsx

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import { useState } from 'react';
2+
import { useNavigate } from 'react-router';
3+
import { useAppContext } from '../context/AppContext';
4+
5+
export default function Preferences() {
6+
const navigate = useNavigate();
7+
const { preferences, setPreferences } = useAppContext();
8+
9+
const [formData, setFormData] = useState({
10+
alienType: preferences?.alienType === 'Humanoid' || !preferences?.alienType ? 'Open to all' : preferences.alienType,
11+
size: preferences?.size || 'No preference',
12+
maxDistanceAU: preferences?.maxDistanceAU || 10,
13+
goals: preferences?.goals || 'Long term fusion',
14+
});
15+
16+
const handleSubmit = (e: React.FormEvent) => {
17+
e.preventDefault();
18+
if (preferences) {
19+
setPreferences({
20+
...preferences,
21+
...formData
22+
});
23+
}
24+
navigate('/explore');
25+
};
26+
27+
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLSelectElement>) => {
28+
const { name, value } = e.target;
29+
setFormData(prev => ({
30+
...prev,
31+
[name]: name === 'maxDistanceAU' ? Number(value) : value
32+
}));
33+
};
34+
35+
return (
36+
<div style={{ maxWidth: '500px', margin: '40px auto', padding: '0 20px' }}>
37+
<div className="glass-panel" style={{ padding: '32px' }}>
38+
<h1 style={{ textAlign: 'center', marginBottom: '24px', color: 'var(--color-secondary)' }}>Your Preferences</h1>
39+
<p style={{ textAlign: 'center', marginBottom: '32px', color: 'rgba(234, 222, 218, 0.8)' }}>
40+
What kind of being are you looking for?
41+
</p>
42+
43+
<form onSubmit={handleSubmit} style={{ display: 'flex', flexDirection: 'column', gap: '16px' }}>
44+
45+
<div>
46+
<h3 style={{ color: 'var(--color-secondary)', marginBottom: '16px', fontSize: '1.2rem', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '8px' }}>Biological Preferences</h3>
47+
48+
<div style={{ marginBottom: '16px' }}>
49+
<label style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem' }}>Preferred Biological Type</label>
50+
<select name="alienType" value={formData.alienType} onChange={handleChange}>
51+
<option value="Human">Human</option>
52+
<option value="Alien">Alien</option>
53+
<option value="Hybrid">Hybrid</option>
54+
<option value="Open to all">Open to all</option>
55+
</select>
56+
</div>
57+
58+
<div style={{ marginBottom: '16px' }}>
59+
<label style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem' }}>Preferred Size</label>
60+
<select name="size" value={formData.size} onChange={handleChange}>
61+
<option value="No preference">No preference</option>
62+
<option value="Small">Small</option>
63+
<option value="Medium">Medium</option>
64+
<option value="Large">Large</option>
65+
<option value="Colossal">Colossal</option>
66+
</select>
67+
</div>
68+
</div>
69+
70+
<div>
71+
<h3 style={{ color: 'var(--color-secondary)', marginBottom: '16px', fontSize: '1.2rem', borderBottom: '1px solid rgba(255,255,255,0.1)', paddingBottom: '8px' }}>Relationship Goals</h3>
72+
73+
<div style={{ marginBottom: '16px' }}>
74+
<label style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem' }}>What are you seeking?</label>
75+
<select name="goals" value={formData.goals} onChange={handleChange}>
76+
<option value="Short term orbit">Short term orbit</option>
77+
<option value="Long term fusion">Long term fusion</option>
78+
<option value="Galactic friendship">Galactic friendship</option>
79+
<option value="Exploration">Exploration</option>
80+
</select>
81+
</div>
82+
83+
<div>
84+
<label style={{ display: 'block', marginBottom: '8px', fontSize: '0.9rem' }}>Max Distance (AU)</label>
85+
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
86+
<input type="range" name="maxDistanceAU" min="0" max="100" value={formData.maxDistanceAU} onChange={handleChange} style={{ flex: 1 }} />
87+
<span style={{ width: '40px', textAlign: 'right' }}>{formData.maxDistanceAU}</span>
88+
</div>
89+
</div>
90+
</div>
91+
92+
<div style={{ marginTop: '16px', display: 'flex', gap: '16px' }}>
93+
<button type="button" onClick={() => navigate('/')} className="btn-outline" style={{ flex: 1 }}>Back</button>
94+
<button type="submit" className="btn-primary" style={{ flex: 2 }}>Launch Search</button>
95+
</div>
96+
</form>
97+
</div>
98+
</div>
99+
);
100+
}

0 commit comments

Comments
 (0)