From c78845f4119478e7e6fca13e5bed5bd4e33a62d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sedat=20=C3=96zt=C3=BCrk?= Date: Sat, 18 Oct 2025 23:04:24 +0300 Subject: [PATCH] Sosyal Duvar --- ui/.env | 3 +- ui/package-lock.json | 50 +- ui/package.json | 2 + .../intranet/SocialWall/CreatePost.tsx | 468 ++++++++++++++++++ .../intranet/SocialWall/LocationMap.tsx | 97 ++++ .../intranet/SocialWall/LocationPicker.tsx | 386 +++++++++++++++ .../intranet/SocialWall/MediaLightbox.tsx | 72 +++ .../intranet/SocialWall/MediaManager.tsx | 243 +++++++++ .../intranet/SocialWall/PostItem.tsx | 433 ++++++++++++++++ .../intranet/SocialWall/UserProfileCard.tsx | 101 ++++ .../components/intranet/SocialWall/index.tsx | 236 +++++++++ ui/src/mocks/mockSocialPosts.ts | 312 ++++++++++++ ui/src/views/Dashboard.tsx | 16 +- ui/src/vite-env.d.ts | 1 + 14 files changed, 2412 insertions(+), 8 deletions(-) create mode 100644 ui/src/components/intranet/SocialWall/CreatePost.tsx create mode 100644 ui/src/components/intranet/SocialWall/LocationMap.tsx create mode 100644 ui/src/components/intranet/SocialWall/LocationPicker.tsx create mode 100644 ui/src/components/intranet/SocialWall/MediaLightbox.tsx create mode 100644 ui/src/components/intranet/SocialWall/MediaManager.tsx create mode 100644 ui/src/components/intranet/SocialWall/PostItem.tsx create mode 100644 ui/src/components/intranet/SocialWall/UserProfileCard.tsx create mode 100644 ui/src/components/intranet/SocialWall/index.tsx create mode 100644 ui/src/mocks/mockSocialPosts.ts diff --git a/ui/.env b/ui/.env index edbad71c..25df0891 100644 --- a/ui/.env +++ b/ui/.env @@ -1,4 +1,5 @@ VITE_API_URL='https://localhost:44344' VITE_CDN_URL='http://localhost:4005' VITE_REACT_APP_VERSION=$npm_package_version -VITE_AI_URL='https://ai.sozsoft.com/webhook/' \ No newline at end of file +VITE_AI_URL='https://ai.sozsoft.com/webhook/' +VITE_GOOGLE_MAPS_API_KEY='AIzaSyAefS2rvF-xwq7OHpZ27UYxXPbMo6OwACc' \ No newline at end of file diff --git a/ui/package-lock.json b/ui/package-lock.json index e73f3f58..eea067c5 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -1,12 +1,12 @@ { "name": "kurs-platform-ui", - "version": "1.0.4", + "version": "1.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "kurs-platform-ui", - "version": "1.0.4", + "version": "1.0.1", "dependencies": { "@babel/generator": "^7.28.3", "@babel/parser": "^7.28.0", @@ -32,6 +32,7 @@ "devextreme": "^23.2.11", "devextreme-react": "^23.2.11", "easy-peasy": "^6.0.5", + "emoji-picker-react": "^4.14.1", "exceljs": "^4.4.0", "file-saver": "^2.0.2", "formik": "^2.4.6", @@ -52,6 +53,7 @@ "react-router-dom": "^6.14.1", "react-select": "^5.9.0", "redux-state-sync": "^3.1.4", + "yet-another-react-lightbox": "^3.25.0", "yup": "^1.6.1" }, "devDependencies": { @@ -6107,6 +6109,21 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-picker-react": { + "version": "4.14.1", + "resolved": "https://registry.npmjs.org/emoji-picker-react/-/emoji-picker-react-4.14.1.tgz", + "integrity": "sha512-4N6dCHr8Z5P52ICdncSxrY0ZcgUtb9Y3o4eBsy6HLpOMhka9AoiWMZ4/hJK0SQNYMIb7MQ+/NT5dqotpo9OXVA==", + "license": "MIT", + "dependencies": { + "flairup": "1.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -7337,6 +7354,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/flairup": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/flairup/-/flairup-1.0.0.tgz", + "integrity": "sha512-IKlE+pNvL2R+kVL1kEhUYqRxVqeFnjiIvHWDMLFXNaqyUdFXQM2wte44EfMYJNHkW16X991t2Zg8apKkhv7OBA==", + "license": "MIT" + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -13998,6 +14021,29 @@ "node": ">= 14.6" } }, + "node_modules/yet-another-react-lightbox": { + "version": "3.25.0", + "resolved": "https://registry.npmjs.org/yet-another-react-lightbox/-/yet-another-react-lightbox-3.25.0.tgz", + "integrity": "sha512-NaCeEXCpdwoTvoOpxNK9gdW8+oHs79yVH+D2YeVQWRjH5i32e5CoXndAAFP2p8awzVYfSonherrE9JMTpfD3EA==", + "license": "MIT", + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "@types/react": "^16 || ^17 || ^18 || ^19", + "@types/react-dom": "^16 || ^17 || ^18 || ^19", + "react": "^16.8.0 || ^17 || ^18 || ^19", + "react-dom": "^16.8.0 || ^17 || ^18 || ^19" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/ui/package.json b/ui/package.json index 0a8b7a17..ca2a2b0a 100644 --- a/ui/package.json +++ b/ui/package.json @@ -40,6 +40,7 @@ "devextreme": "^23.2.11", "devextreme-react": "^23.2.11", "easy-peasy": "^6.0.5", + "emoji-picker-react": "^4.14.1", "exceljs": "^4.4.0", "file-saver": "^2.0.2", "formik": "^2.4.6", @@ -60,6 +61,7 @@ "react-router-dom": "^6.14.1", "react-select": "^5.9.0", "redux-state-sync": "^3.1.4", + "yet-another-react-lightbox": "^3.25.0", "yup": "^1.6.1" }, "devDependencies": { diff --git a/ui/src/components/intranet/SocialWall/CreatePost.tsx b/ui/src/components/intranet/SocialWall/CreatePost.tsx new file mode 100644 index 00000000..45830434 --- /dev/null +++ b/ui/src/components/intranet/SocialWall/CreatePost.tsx @@ -0,0 +1,468 @@ +import React, { useState, useRef } from 'react' +import { motion, AnimatePresence } from 'framer-motion' +import classNames from 'classnames' +import EmojiPicker, { EmojiClickData } from 'emoji-picker-react' +import { + HiOutlineChartBar, + HiOutlineEmojiHappy, + HiX, + HiOutlineCollection, + HiOutlineLocationMarker +} from 'react-icons/hi' +import MediaManager, { MediaItem } from './MediaManager' +import LocationPicker, { Location } from './LocationPicker' + +interface CreatePostProps { + onCreatePost: (post: { + content: string + location?: Location + media?: { + type: 'mixed' | 'poll' + mediaItems?: MediaItem[] + poll?: { + question: string + options: Array<{ text: string }> + } + } + }) => void +} + +const CreatePost: React.FC = ({ onCreatePost }) => { + const [content, setContent] = useState('') + const [mediaType, setMediaType] = useState<'media' | 'poll' | null>(null) + const [mediaItems, setMediaItems] = useState([]) + const [location, setLocation] = useState(null) + const [pollQuestion, setPollQuestion] = useState('') + const [pollOptions, setPollOptions] = useState(['', '']) + const [isExpanded, setIsExpanded] = useState(false) + const [showEmojiPicker, setShowEmojiPicker] = useState(false) + const [showMediaManager, setShowMediaManager] = useState(false) + const [showLocationPicker, setShowLocationPicker] = useState(false) + const textareaRef = useRef(null) + const emojiPickerRef = useRef(null) + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault() + + if (!content.trim() && mediaItems.length === 0 && !mediaType) return + + let media = undefined + + if (mediaType === 'media' && mediaItems.length > 0) { + media = { + type: 'mixed' as const, + mediaItems + } + } else if (mediaType === 'poll' && pollQuestion && pollOptions.filter(o => o.trim()).length >= 2) { + media = { + type: 'poll' as const, + poll: { + question: pollQuestion, + options: pollOptions.filter(o => o.trim()).map(text => ({ text })) + } + } + } + + onCreatePost({ + content, + media, + location: location || undefined + }) + + // Reset form + setContent('') + setMediaType(null) + setMediaItems([]) + setLocation(null) + setPollQuestion('') + setPollOptions(['', '']) + setIsExpanded(false) + setShowEmojiPicker(false) + } + + const handleEmojiClick = (emojiData: EmojiClickData) => { + const emoji = emojiData.emoji + const textarea = textareaRef.current + if (!textarea) return + + const start = textarea.selectionStart + const end = textarea.selectionEnd + const text = content + const before = text.substring(0, start) + const after = text.substring(end) + + setContent(before + emoji + after) + + // Set cursor position after emoji + setTimeout(() => { + textarea.selectionStart = textarea.selectionEnd = start + emoji.length + textarea.focus() + }, 0) + } + + const addPollOption = () => { + if (pollOptions.length < 6) { + setPollOptions([...pollOptions, '']) + } + } + + const removePollOption = (index: number) => { + if (pollOptions.length > 2) { + setPollOptions(pollOptions.filter((_, i) => i !== index)) + } + } + + const updatePollOption = (index: number, value: string) => { + const newOptions = [...pollOptions] + newOptions[index] = value + setPollOptions(newOptions) + } + + const clearMedia = () => { + setMediaType(null) + setMediaItems([]) + setPollQuestion('') + setPollOptions(['', '']) + } + + const removeMediaItem = (id: string) => { + setMediaItems(mediaItems.filter((m) => m.id !== id)) + } + + // Close emoji picker when clicking outside + React.useEffect(() => { + const handleClickOutside = (event: MouseEvent) => { + if (emojiPickerRef.current && !emojiPickerRef.current.contains(event.target as Node)) { + setShowEmojiPicker(false) + } + } + + if (showEmojiPicker) { + document.addEventListener('mousedown', handleClickOutside) + } + + return () => { + document.removeEventListener('mousedown', handleClickOutside) + } + }, [showEmojiPicker]) + + return ( +
+
+ {/* Text Input */} +
+ Your avatar +
+