+
+ FriendsPage
+
Login
diff --git a/src/components/NewPoll.jsx b/src/components/NewPoll.jsx
new file mode 100644
index 00000000..c6da25ee
--- /dev/null
+++ b/src/components/NewPoll.jsx
@@ -0,0 +1,231 @@
+import React, { useState } from "react";
+import axios from "axios";
+import { useNavigate } from "react-router-dom";
+
+const NewPoll = ({user}) => {
+ const [title, setTitle] = useState("");
+ const [description, setDescription] = useState("");
+ const [options, setOptions] = useState(["", ""]);
+ const [endDate, setEndDate] = useState("");
+ const [isIndefinite, setIsIndefinite] = useState(true);
+ const [allowAnonymous, setAllowAnonymous] = useState(false);
+ const [error, setError] = useState("");
+ const navigate = useNavigate();
+
+ if (!user) {
+ return (
+
+ );
+ }
+
+ const creator_id = user.id;
+
+ const handleOptionChange = (index, value) => {
+ const updatedOptions = [...options];
+ updatedOptions[index] = value;
+ setOptions(updatedOptions);
+ };
+
+ const addOptionField = () => {
+ setOptions([...options, ""]);
+ };
+
+ const removeOptionField = (index) => {
+ const updatedOptions = options.filter((_, i) => i != index);
+ setOptions(updatedOptions);
+ };
+
+ const getMinDateTime = () => {
+ const now = new Date();
+ now.setHours(now.getHours() + 1);
+ return now.toISOString().slice(0, 16);
+ };
+
+ const handleAddEndDate = () => {
+ setIsIndefinite(false);
+ };
+
+ const handleRemoveEndDate = () => {
+ setIsIndefinite(true);
+ setEndDate("");
+ };
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+
+
+ if (!title.trim()) {
+ return setError("Poll title is required.");
+ }
+
+ if (!isIndefinite) {
+ if (!endDate) {
+ return setError("Please select an end date or remove the end date to make it indefinite.");
+ }
+
+ const selectedEndDate = new Date(endDate);
+ const now = new Date();
+ if (selectedEndDate <= now) {
+ return setError("End date must be in the future.");
+ }
+ }
+
+ const validOptions = options.filter(opt => opt.trim() !== "");
+ if (validOptions.length < 2) {
+ return setError("At least two filled options are required.");
+ }
+
+ try {
+ const pollData = {
+ creator_id,
+ title: title.trim(),
+ description: description.trim(),
+ allowAnonymous,
+ pollOptions: validOptions.map((optionText, index) => ({
+ text: optionText,
+ position: index + 1
+ }))
+ };
+
+ if (!isIndefinite && endDate) {
+ pollData.endAt = new Date(endDate).toISOString();
+ }
+
+ await axios.post("http://localhost:8080/api/polls", pollData);
+
+ navigate("/poll-list");
+ } catch (err) {
+ setError("Failed to create poll.");
+ console.error("Poll creation error:", err);
+ }
+ };
+
+ return (
+
+
Create New Poll
+ {error &&
{error}
}
+
+
+
+ );
+};
+
+export default NewPoll;
\ No newline at end of file
diff --git a/src/components/PollCard.jsx b/src/components/PollCard.jsx
new file mode 100644
index 00000000..4ab2352d
--- /dev/null
+++ b/src/components/PollCard.jsx
@@ -0,0 +1,97 @@
+import React, { useState, useEffect } from 'react';
+import './CSS/PollCardStyles.css';
+
+const PollCard = ({ poll }) => {
+ const [timeLeft, setTimeLeft] = useState('');
+ const [creator, setCreator] = useState(null);
+
+ useEffect(() => {
+ const calculateTimeLeft = () => {
+ if (!poll.endAt) {
+ setTimeLeft('No end date');
+ return;
+ }
+
+ const now = new Date();
+ const endTime = new Date(poll.endAt);
+ const difference = endTime - now;
+
+ if (difference > 0) {
+ const days = Math.floor(difference / (1000 * 60 * 60 * 24));
+ const hours = Math.floor((difference % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+ const minutes = Math.floor((difference % (1000 * 60 * 60)) / (1000 * 60));
+
+ if (days > 0) {
+ setTimeLeft(`${days}d ${hours}h left`);
+ } else if (hours > 0) {
+ setTimeLeft(`${hours}h ${minutes}m left`);
+ } else if (minutes > 0) {
+ setTimeLeft(`${minutes}m left`);
+ } else {
+ setTimeLeft('Less than 1m left');
+ }
+ } else {
+ setTimeLeft('Poll ended');
+ }
+ };
+
+ calculateTimeLeft();
+ const timer = setInterval(calculateTimeLeft, 60000);
+
+ return () => clearInterval(timer);
+ }, [poll.endAt]);
+
+ useEffect(() => {
+ const fetchCreator = async () => {
+ try {
+ const response = await fetch(`http://localhost:8080/api/users/${poll.creator_id}`);
+ if (response.ok) {
+ const userData = await response.json();
+ setCreator(userData);
+ } else {
+ console.error('Failed to fetch creator:', response.status);
+ setCreator({ username: 'Unknown' });
+ }
+ } catch (error) {
+ console.error('Error fetching creator:', error);
+ setCreator({ username: 'Unknown' });
+ }
+ };
+
+ if (poll.creator_id) {
+ fetchCreator();
+ }
+ }, [poll.creator_id]);
+
+ const isPollActive = poll.endAt ? new Date(poll.endAt) > new Date() : true;
+
+ return (
+
+
+
{poll.title}
+
+
+ by {creator ? `@${creator.username}` : 'Loading...'}
+
+
+ {timeLeft}
+
+
+
+
+ {poll.description && (
+
+ )}
+
+ {!isPollActive && (
+
+ Ended
+
+ )}
+
+ );
+};
+
+export default PollCard;
\ No newline at end of file
diff --git a/src/components/PollList.jsx b/src/components/PollList.jsx
new file mode 100644
index 00000000..68cdda3f
--- /dev/null
+++ b/src/components/PollList.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import PollCard from './PollCard';
+
+const PollsList = ({ polls }) => {
+ if (!polls) {
+ return (
+
+ );
+ }
+
+ if (polls.length === 0) {
+ return (
+
+ );
+ }
+
+ return (
+
+ {polls.map(poll => (
+
+ ))}
+
+ );
+};
+
+export default PollsList;
\ No newline at end of file
diff --git a/src/components/Profile.jsx b/src/components/Profile.jsx
new file mode 100644
index 00000000..75855543
--- /dev/null
+++ b/src/components/Profile.jsx
@@ -0,0 +1,59 @@
+import React from 'react';
+import './CSS/ProfileStyles.css';
+
+const ProfilePage = ({ user }) => {
+ if (!user) {
+ return (
+
+ );
+ }
+
+ return (
+
+
+
+

+
+
+
+
{user.username}
+
@{user.username}
+
+
+
+ {user.postsCount || 0}
+ Posts
+
+
+ {user.followersCount || 0}
+ Followers
+
+
+ {user.followingCount || 0}
+ Following
+
+
+
+ {user.bio && (
+
{user.bio}
+ )}
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ProfilePage;
\ No newline at end of file
diff --git a/src/components/Signup.jsx b/src/components/Signup.jsx
index 989fa096..116ffde5 100644
--- a/src/components/Signup.jsx
+++ b/src/components/Signup.jsx
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import { useNavigate, Link } from "react-router-dom";
import axios from "axios";
-import "./AuthStyles.css";
+import "./CSS/AuthStyles.css";
import { API_URL } from "../shared";
const Signup = ({ setUser }) => {
diff --git a/src/components/UserCard.jsx b/src/components/UserCard.jsx
new file mode 100644
index 00000000..431b1a08
--- /dev/null
+++ b/src/components/UserCard.jsx
@@ -0,0 +1,30 @@
+import React, { useState, useEffect } from "react";
+import axios from "axios";
+import { useNavigate, useParams } from "react-router-dom";
+
+const UserCard = () => {
+ const { id } = useParams();
+ const [user, setUser] = useState(null);
+
+ useEffect(() => {
+ axios
+ .get(`http://localhost:8080/api/users/${id}`)
+ .then((response) => {
+ setUser(response.data);
+ })
+ .catch((error) => {
+ console.error("Error fetching user:", error);
+ });
+ }, []);
+
+ if (!user) return
Loading...
;
+
+ return (
+
+
User Profile
+ {user.username}
+
+ );
+};
+
+export default UserCard;
diff --git a/src/components/UsersPage.jsx b/src/components/UsersPage.jsx
new file mode 100644
index 00000000..c0963354
--- /dev/null
+++ b/src/components/UsersPage.jsx
@@ -0,0 +1,43 @@
+import React, { useState, useEffect } from "react";
+import axios from "axios";
+import { useNavigate } from "react-router-dom";
+
+const UsersPage = () => {
+ const [users, setUsers] = useState([]);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ axios
+ .get("http://localhost:8080/api/users")
+ .then((response) => {
+ console.log("Users data:", response.data);
+ setUsers(response.data);
+ })
+ .catch((error) => {
+ console.error("Error fetching users:", error);
+ });
+ }, []);
+
+ const handleUserClick = (id) => {
+ navigate(`/users/${id}`);
+ };
+
+ return (
+
+
All Users
+ {users.length === 0 ? (
+
No users found.
+ ) : (
+
+ {users.map((user) => (
+ - handleUserClick(user.id)}>
+ {user.username}
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default UsersPage;
diff --git a/src/components/css/Friends.css b/src/components/css/Friends.css
new file mode 100644
index 00000000..7c1d539e
--- /dev/null
+++ b/src/components/css/Friends.css
@@ -0,0 +1,57 @@
+.friends-container {
+ padding: 20px;
+}
+
+.friends-outline {
+ background: white;
+ padding: 2rem;
+ border-radius: 8px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ width: 100%;
+ max-width: 1000px;
+}
+
+.profile {
+ justify-content: column;
+ gap: 40px;
+}
+
+.image {
+ justify-content: row;
+}
+
+.image img {
+ width: 150px;
+ height: 150px;
+ padding: 10px;
+}
+
+.top-section {
+ display: flex;
+ justify-content: flex-end;
+ align-items: flex-start;
+ gap: 40px;
+ flex-wrap: wrap;
+}
+
+.friend-name {
+ margin: 0;
+ font-size: 28px;
+}
+
+.stats {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ gap: 20px;
+}
+
+.middle-section,
+.bottom-section {
+ margin-top: 30px;
+}
+
+.middle-section h3,
+.bottom-section h3 {
+ margin: 0;
+}
diff --git a/src/components/poll.jsx b/src/components/poll.jsx
new file mode 100644
index 00000000..83d56f5d
--- /dev/null
+++ b/src/components/poll.jsx
@@ -0,0 +1,29 @@
+import React, { useState } from "react";
+import { useNavigate, Link } from "react-router-dom";
+import axios from "axios";
+import { API_URL } from "../shared";
+
+const Poll = ({ poll }) => {
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+};
+
+export default Poll;