Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
|
ad7313363b |
|
@ -1,371 +0,0 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import MDEditor, { commands } from '@uiw/react-md-editor';
|
|
||||||
import { Card } from "./ui/card";
|
|
||||||
import { Label } from "./ui/label";
|
|
||||||
import { Button } from "./ui/button";
|
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./ui/dialog";
|
|
||||||
import { Input } from "./ui/input";
|
|
||||||
import { useIsLoggedIn } from '../lib/isLoggedIn';
|
|
||||||
|
|
||||||
const COMMENTS_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/comments/';
|
|
||||||
const MINIO_UPLOAD_URL = 'https://hostapi2.cs1.hz.siliconpin.com/api/storage/upload';
|
|
||||||
|
|
||||||
export default function Comment(props) {
|
|
||||||
const [comments, setComments] = useState([]);
|
|
||||||
const [newComment, setNewComment] = useState({ comment: '' });
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
||||||
const [submitSuccess, setSubmitSuccess] = useState(false);
|
|
||||||
const [submitError, setSubmitError] = useState('');
|
|
||||||
const [isLoadingComments, setIsLoadingComments] = useState(true);
|
|
||||||
const [editorMode, setEditorMode] = useState('edit');
|
|
||||||
const [imageDialogOpen, setImageDialogOpen] = useState(false);
|
|
||||||
const [imageUrlInput, setImageUrlInput] = useState('');
|
|
||||||
const [imageUploadFile, setImageUploadFile] = useState(null);
|
|
||||||
const [imageUploadPreview, setImageUploadPreview] = useState('');
|
|
||||||
const [uploadProgress, setUploadProgress] = useState(0);
|
|
||||||
const { isLoggedIn, loading, error, sessionData } = useIsLoggedIn();
|
|
||||||
|
|
||||||
// Custom image command for MDEditor
|
|
||||||
const customImageCommand = {
|
|
||||||
name: 'image',
|
|
||||||
keyCommand: 'image',
|
|
||||||
buttonProps: { 'aria-label': 'Insert image' },
|
|
||||||
icon: (
|
|
||||||
<svg width="12" height="12" viewBox="0 0 20 20">
|
|
||||||
<path fill="currentColor" d="M15 9c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm4-7H1c-.55 0-1 .45-1 1v14c0 .55.45 1 1 1h18c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 13l-6-5-2 2-4-5-4 8V4h16v11z"/>
|
|
||||||
</svg>
|
|
||||||
),
|
|
||||||
execute: () => {
|
|
||||||
setImageDialogOpen(true);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get all default commands and replace the image command
|
|
||||||
const allCommands = commands.getCommands().map(cmd => {
|
|
||||||
if (cmd.name === 'image') {
|
|
||||||
return customImageCommand;
|
|
||||||
}
|
|
||||||
return cmd;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Load comments when component mounts
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.topicId) {
|
|
||||||
fetchComments(props.topicId);
|
|
||||||
}
|
|
||||||
}, [props.topicId]);
|
|
||||||
|
|
||||||
const fetchComments = async (topicId) => {
|
|
||||||
setIsLoadingComments(true);
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${COMMENTS_API_URL}?topicId=${topicId}`, {
|
|
||||||
method: 'GET',
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.message || 'Failed to fetch comments');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.success && data.comments) {
|
|
||||||
setComments(data.comments);
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid response format');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching comments:', error);
|
|
||||||
setSubmitError('Failed to load comments');
|
|
||||||
} finally {
|
|
||||||
setIsLoadingComments(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Upload file to MinIO
|
|
||||||
const uploadToMinIO = async (file) => {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', file);
|
|
||||||
formData.append('api_key', 'wweifwehfwfhwhtuyegbvijvbfvegfreyf');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(MINIO_UPLOAD_URL, {
|
|
||||||
method: 'POST',
|
|
||||||
body: formData,
|
|
||||||
credentials: 'include'
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error('Upload failed');
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
return data.publicUrl;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Upload error:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle image URL insertion
|
|
||||||
const handleInsertImageUrl = () => {
|
|
||||||
if (imageUrlInput) {
|
|
||||||
const imgMarkdown = ``;
|
|
||||||
setNewComment(prev => ({
|
|
||||||
...prev,
|
|
||||||
comment: prev.comment ? `${prev.comment}\n${imgMarkdown}` : imgMarkdown
|
|
||||||
}));
|
|
||||||
setImageDialogOpen(false);
|
|
||||||
setImageUrlInput('');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Handle image file selection
|
|
||||||
const handleImageFileSelect = (e) => {
|
|
||||||
const file = e.target.files[0];
|
|
||||||
if (file) {
|
|
||||||
setImageUploadFile(file);
|
|
||||||
|
|
||||||
// Create preview
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.onload = () => {
|
|
||||||
setImageUploadPreview(reader.result);
|
|
||||||
};
|
|
||||||
reader.readAsDataURL(file);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Upload image file to MinIO and insert into editor
|
|
||||||
const handleImageUpload = async () => {
|
|
||||||
if (!imageUploadFile) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
setIsSubmitting(true);
|
|
||||||
setUploadProgress(0);
|
|
||||||
|
|
||||||
const uploadedUrl = await uploadToMinIO(imageUploadFile);
|
|
||||||
|
|
||||||
// Insert markdown for the uploaded image
|
|
||||||
const imgMarkdown = ``;
|
|
||||||
setNewComment(prev => ({
|
|
||||||
...prev,
|
|
||||||
comment: prev.comment ? `${prev.comment}\n${imgMarkdown}` : imgMarkdown
|
|
||||||
}));
|
|
||||||
|
|
||||||
setImageDialogOpen(false);
|
|
||||||
setImageUploadFile(null);
|
|
||||||
setImageUploadPreview('');
|
|
||||||
} catch (error) {
|
|
||||||
setSubmitError('Failed to upload image: ' + error.message);
|
|
||||||
} finally {
|
|
||||||
setIsSubmitting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmitComment = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsSubmitting(true);
|
|
||||||
setSubmitError('');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${COMMENTS_API_URL}?query=new-comment`, {
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'include',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
...newComment,
|
|
||||||
topicId: props.topicId
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
setNewComment({ comment: '' });
|
|
||||||
setSubmitSuccess(true);
|
|
||||||
setTimeout(() => setSubmitSuccess(false), 3000);
|
|
||||||
|
|
||||||
// Refresh comments after successful submission
|
|
||||||
await fetchComments(props.topicId);
|
|
||||||
} else {
|
|
||||||
const errorData = await response.json();
|
|
||||||
setSubmitError(errorData.message || 'Failed to submit comment');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setSubmitError('Network error. Please try again.');
|
|
||||||
} finally {
|
|
||||||
setIsSubmitting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUserInitials = (name) => {
|
|
||||||
if (!name) return 'U';
|
|
||||||
const words = name.trim().split(' ');
|
|
||||||
return words
|
|
||||||
.slice(0, 2)
|
|
||||||
.map(word => word[0].toUpperCase())
|
|
||||||
.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* Comments List */}
|
|
||||||
<div className="space-y-6 border-t">
|
|
||||||
<h2 className="text-2xl font-bold text-[#6d9e37]">Comments ({comments.length})</h2>
|
|
||||||
|
|
||||||
{isLoadingComments ? (
|
|
||||||
<div className="flex justify-center py-4">
|
|
||||||
<div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-[#6d9e37]"></div>
|
|
||||||
</div>
|
|
||||||
) : comments.length === 0 ? (
|
|
||||||
<p className="text-gray-500">No comments yet. Be the first to comment!</p>
|
|
||||||
) : (
|
|
||||||
comments.map(comment => (
|
|
||||||
<div key={comment.id} className="border-b pb-6 last:border-b-0">
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="flex-shrink-0">
|
|
||||||
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center text-gray-500 font-bold">
|
|
||||||
{getUserInitials(comment.userName)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center gap-2 mb-1">
|
|
||||||
<h4 className="font-medium text-[#6d9e37]">
|
|
||||||
{comment.userName || 'User'}
|
|
||||||
</h4>
|
|
||||||
<span className="text-xs text-gray-500">
|
|
||||||
{new Date(comment.created_at).toLocaleDateString('en-US', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric'
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div data-color-mode="light" className="markdown-body">
|
|
||||||
<MDEditor.Markdown source={comment.comment} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Comments Section with MDEditor */}
|
|
||||||
<Card className="mt-16 border-t">
|
|
||||||
<div className="relative">
|
|
||||||
<div className="px-6 pb-6 rounded-lg">
|
|
||||||
<h3 className="text-lg font-medium mb-4 pt-8">Leave a Comment</h3>
|
|
||||||
{submitSuccess && (
|
|
||||||
<div className="mb-4 p-3 bg-green-100 text-green-700 rounded">
|
|
||||||
Thank you for your comment!
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{submitError && (
|
|
||||||
<div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
|
|
||||||
{submitError}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<form onSubmit={handleSubmitComment}>
|
|
||||||
<div className="mb-4">
|
|
||||||
<Label htmlFor="comment">Comment *</Label>
|
|
||||||
<div data-color-mode="light">
|
|
||||||
<MDEditor
|
|
||||||
placeholder="Write your comment (markdown supported)"
|
|
||||||
value={newComment.comment}
|
|
||||||
onChange={(value) => setNewComment(prev => ({ ...prev, comment: value || '' }))}
|
|
||||||
height={300}
|
|
||||||
preview={editorMode}
|
|
||||||
commands={allCommands}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="flex justify-end mt-2">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setEditorMode(editorMode === 'edit' ? 'preview' : 'edit')}
|
|
||||||
className={`text-sm ${editorMode !== 'edit' ? 'bg-[#6d9e37] text-white' : 'text-[#6d9e37]'} px-2 py-1 rounded-md border border-[#6d9e37]`}
|
|
||||||
>
|
|
||||||
{editorMode === 'edit' ? 'Preview' : 'Edit'}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button type="submit" disabled={isSubmitting || !isLoggedIn}>
|
|
||||||
{isSubmitting ? 'Submitting...' : 'Post Comment'}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Image Upload Dialog */}
|
|
||||||
<Dialog open={imageDialogOpen} onOpenChange={setImageDialogOpen}>
|
|
||||||
<DialogContent className="sm:max-w-[425px]">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Insert Image</DialogTitle>
|
|
||||||
</DialogHeader>
|
|
||||||
<div className="grid gap-4 py-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="image-url">From URL</Label>
|
|
||||||
<Input
|
|
||||||
id="image-url"
|
|
||||||
type="text"
|
|
||||||
placeholder="Enter image URL"
|
|
||||||
value={imageUrlInput}
|
|
||||||
onChange={(e) => setImageUrlInput(e.target.value)}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={handleInsertImageUrl}
|
|
||||||
disabled={!imageUrlInput}
|
|
||||||
className="mt-2"
|
|
||||||
>
|
|
||||||
Insert Image
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="image-upload">Upload Image</Label>
|
|
||||||
<Input
|
|
||||||
id="image-upload"
|
|
||||||
type="file"
|
|
||||||
accept="image/*"
|
|
||||||
onChange={handleImageFileSelect}
|
|
||||||
/>
|
|
||||||
{imageUploadPreview && (
|
|
||||||
<div className="mt-2">
|
|
||||||
<img
|
|
||||||
src={imageUploadPreview}
|
|
||||||
alt="Preview"
|
|
||||||
className="max-h-40 rounded-md border"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
onClick={handleImageUpload}
|
|
||||||
disabled={!imageUploadFile || isSubmitting}
|
|
||||||
className="mt-2"
|
|
||||||
>
|
|
||||||
{isSubmitting ? 'Uploading...' : 'Upload & Insert'}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
|
|
||||||
{loading ? (
|
|
||||||
<>
|
|
||||||
<div className="w-full h-full absolute bg-black inset-0 opacity-70 backdrop-blur-2xl rounded-lg" />
|
|
||||||
<div className="w-10 h-10 rounded-full border-2 border-dotted border-[#6d9e37] absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2" role="status">
|
|
||||||
<span className="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : !isLoggedIn ? (
|
|
||||||
<>
|
|
||||||
<div className="w-full h-full absolute bg-black inset-0 opacity-70 backdrop-blur-2xl rounded-lg" />
|
|
||||||
<p className="text-gray-100 bg-gray-700 p-2 rounded-md shadow-xl italic absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
|
||||||
Join the conversation! Log in or sign up to post a comment
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,200 +0,0 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import { Card } from "./ui/card";
|
|
||||||
import { Label } from "./ui/label";
|
|
||||||
import { Textarea } from "./ui/textarea";
|
|
||||||
import { Button } from "./ui/button";
|
|
||||||
import { useIsLoggedIn } from '../lib/isLoggedIn';
|
|
||||||
|
|
||||||
const COMMENTS_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/comments/';
|
|
||||||
|
|
||||||
export default function Comment(props) {
|
|
||||||
const [comments, setComments] = useState([]);
|
|
||||||
const [newComment, setNewComment] = useState({ comment: '' });
|
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
||||||
const [submitSuccess, setSubmitSuccess] = useState(false);
|
|
||||||
const [submitError, setSubmitError] = useState('');
|
|
||||||
const [isLoadingComments, setIsLoadingComments] = useState(true);
|
|
||||||
const { isLoggedIn, loading, error, sessionData } = useIsLoggedIn();
|
|
||||||
|
|
||||||
// Load comments when component mounts or when topicId changes
|
|
||||||
useEffect(() => {
|
|
||||||
if (props.topicId) {
|
|
||||||
fetchComments(props.topicId);
|
|
||||||
}
|
|
||||||
}, [props.topicId]);
|
|
||||||
|
|
||||||
const fetchComments = async (topicId) => {
|
|
||||||
setIsLoadingComments(true);
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${COMMENTS_API_URL}?topicId=${topicId}`, {
|
|
||||||
method: 'GET',
|
|
||||||
credentials: 'include',
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(data.message || 'Failed to fetch comments');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.success && data.comments) {
|
|
||||||
setComments(data.comments);
|
|
||||||
} else {
|
|
||||||
throw new Error('Invalid response format');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching comments:', error);
|
|
||||||
setSubmitError('Failed to load comments');
|
|
||||||
} finally {
|
|
||||||
setIsLoadingComments(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleInputChange = (e) => {
|
|
||||||
const { name, value } = e.target;
|
|
||||||
setNewComment(prev => ({
|
|
||||||
...prev,
|
|
||||||
[name]: value
|
|
||||||
}));
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleSubmitComment = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
setIsSubmitting(true);
|
|
||||||
setSubmitError('');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`${COMMENTS_API_URL}?query=new-comment`, {
|
|
||||||
method: 'POST',
|
|
||||||
credentials: 'include',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
...newComment,
|
|
||||||
topicId: props.topicId
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
setNewComment({ comment: '' });
|
|
||||||
setSubmitSuccess(true);
|
|
||||||
setTimeout(() => setSubmitSuccess(false), 3000);
|
|
||||||
|
|
||||||
// Refresh comments after successful submission
|
|
||||||
await fetchComments(props.topicId);
|
|
||||||
} else {
|
|
||||||
const errorData = await response.json();
|
|
||||||
setSubmitError(errorData.message || 'Failed to submit comment');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
setSubmitError('Network error. Please try again.');
|
|
||||||
} finally {
|
|
||||||
setIsSubmitting(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const getUserInitials = (name) => {
|
|
||||||
if (!name) return 'U';
|
|
||||||
const words = name.trim().split(' ');
|
|
||||||
return words
|
|
||||||
.slice(0, 2)
|
|
||||||
.map(word => word[0].toUpperCase())
|
|
||||||
.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* Comments List */}
|
|
||||||
<div className="space-y-6 border-t">
|
|
||||||
<h2 className="text-2xl font-bold text-[#6d9e37]">Comments ({comments.length})</h2>
|
|
||||||
|
|
||||||
{isLoadingComments ? (
|
|
||||||
<div className="flex justify-center py-4">
|
|
||||||
{/* <div className="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-[#6d9e37]"></div> */}
|
|
||||||
</div>
|
|
||||||
) : comments.length === 0 ? (
|
|
||||||
<p className="text-gray-500">No comments yet. Be the first to comment!</p>
|
|
||||||
) : (
|
|
||||||
comments.map(comment => (
|
|
||||||
<div key={comment.id} className="border-b pb-6 last:border-b-0">
|
|
||||||
<div className="flex items-start gap-4">
|
|
||||||
<div className="flex-shrink-0">
|
|
||||||
<div className="w-10 h-10 rounded-full bg-gray-200 flex items-center justify-center text-gray-500 font-bold">
|
|
||||||
{getUserInitials(comment.userName)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<div className="flex items-center gap-2 mb-1">
|
|
||||||
<h4 className="font-medium text-[#6d9e37]">
|
|
||||||
{comment.userName || 'User'}
|
|
||||||
</h4>
|
|
||||||
<span className="text-xs text-gray-500">
|
|
||||||
{new Date(comment.created_at).toLocaleDateString('en-US', {
|
|
||||||
year: 'numeric',
|
|
||||||
month: 'short',
|
|
||||||
day: 'numeric'
|
|
||||||
})}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<p className="">{comment.comment}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Comments Section */}
|
|
||||||
<Card className="mt-16 border-t">
|
|
||||||
<div className="relative">
|
|
||||||
<div className="px-6 pb-6 rounded-lg">
|
|
||||||
<h3 className="text-lg font-medium mb-4 pt-8">Leave a Comment</h3>
|
|
||||||
{submitSuccess && (
|
|
||||||
<div className="mb-4 p-3 bg-green-100 text-green-700 rounded">
|
|
||||||
Thank you for your comment!
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{submitError && (
|
|
||||||
<div className="mb-4 p-3 bg-red-100 text-red-700 rounded">
|
|
||||||
{submitError}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<form onSubmit={handleSubmitComment}>
|
|
||||||
<div className="mb-4">
|
|
||||||
<Label htmlFor="comment">Comment *</Label>
|
|
||||||
<Textarea
|
|
||||||
className="mt-2"
|
|
||||||
id="comment"
|
|
||||||
name="comment"
|
|
||||||
rows="4"
|
|
||||||
value={newComment.comment}
|
|
||||||
onChange={handleInputChange}
|
|
||||||
required
|
|
||||||
disabled={!isLoggedIn}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<Button type="submit" disabled={isSubmitting || !isLoggedIn}>
|
|
||||||
{isSubmitting ? 'Submitting...' : 'Post Comment'}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
{loading ? (
|
|
||||||
<>
|
|
||||||
<div className="w-full h-full absolute bg-black inset-0 opacity-70 backdrop-blur-2xl rounded-lg" />
|
|
||||||
<div className="w-10 h-10 rounded-full border-2 border-dotted border-[#6d9e37] absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2" role="status">
|
|
||||||
<span className="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
) : !isLoggedIn ? (
|
|
||||||
<>
|
|
||||||
<div className="w-full h-full absolute bg-black inset-0 opacity-70 backdrop-blur-2xl rounded-lg" />
|
|
||||||
<p className="text-gray-100 bg-gray-700 p-2 rounded-md shadow-xl italic absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2">
|
|
||||||
Join the conversation! Log in or sign up to post a comment
|
|
||||||
</p>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</div>
|
|
||||||
</Card>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
|
@ -1,15 +1,9 @@
|
||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { marked } from 'marked';
|
import { marked } from 'marked';
|
||||||
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "./ui/card";
|
|
||||||
import { Label } from "./ui/label";
|
|
||||||
import { Textarea } from "./ui/textarea";
|
|
||||||
import { Button } from "./ui/button";
|
|
||||||
import { useIsLoggedIn } from '../lib/isLoggedIn';
|
|
||||||
import Comment from './Comment';
|
|
||||||
const COMMENTS_API_URL = 'https://host-api.cs1.hz.siliconpin.com/v1/comments/';
|
|
||||||
export default function TopicDetail(props) {
|
export default function TopicDetail(props) {
|
||||||
const [showCopied, setShowCopied] = useState(false);
|
const [showCopied, setShowCopied] = useState(false);
|
||||||
|
|
||||||
if (!props.topic) {
|
if (!props.topic) {
|
||||||
return <div>Topic not found</div>;
|
return <div>Topic not found</div>;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +12,31 @@ export default function TopicDetail(props) {
|
||||||
const title = props.topic.title;
|
const title = props.topic.title;
|
||||||
const text = `Check out this article: ${title}`;
|
const text = `Check out this article: ${title}`;
|
||||||
|
|
||||||
|
// const shareOnFacebook = () => {
|
||||||
|
// window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}`, '_blank');
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const shareOnTwitter = () => {
|
||||||
|
// window.open(`https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(text)}`, '_blank');
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const shareOnLinkedIn = () => {
|
||||||
|
// window.open(`https://www.linkedin.com/shareArticle?mini=true&url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(title)}`, '_blank');
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const shareOnReddit = () => {
|
||||||
|
// window.open(`https://www.reddit.com/submit?url=${encodeURIComponent(shareUrl)}&title=${encodeURIComponent(title)}`, '_blank');
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const shareOnWhatsApp = () => {
|
||||||
|
// window.open(`https://wa.me/?text=${encodeURIComponent(`${text} ${shareUrl}`)}`, '_blank');
|
||||||
|
// };
|
||||||
|
|
||||||
|
// const shareViaEmail = () => {
|
||||||
|
// window.open(`mailto:?subject=${encodeURIComponent(title)}&body=${encodeURIComponent(`${text}\n\n${shareUrl}`)}`);
|
||||||
|
// };
|
||||||
|
|
||||||
|
|
||||||
const shareOnSocialMedia = (platform) => {
|
const shareOnSocialMedia = (platform) => {
|
||||||
switch (platform){
|
switch (platform){
|
||||||
case 'facebook':
|
case 'facebook':
|
||||||
|
@ -106,7 +125,7 @@ export default function TopicDetail(props) {
|
||||||
return (
|
return (
|
||||||
<div className="container mx-auto px-4 py-12">
|
<div className="container mx-auto px-4 py-12">
|
||||||
<article className="max-w-4xl mx-auto">
|
<article className="max-w-4xl mx-auto">
|
||||||
<img src={props.topic.img ? props.topic.img : '/assets/images/thumb-place.jpg'} alt={props.topic.title} className="w-full h-[400px] aspect-video object-cover rounded-lg mb-8 shadow-md" />
|
<img src={props.topic.img ? props.topic.img : '/assets/images/thumb-place.jpg'} alt={props.topic.title} className="w-full h-[400px] aspect-video object-cover rounded-lg mb-8 shadow-md" />
|
||||||
<h1 className="text-4xl font-bold text-[#6d9e37] mb-6">{props.topic.title}</h1>
|
<h1 className="text-4xl font-bold text-[#6d9e37] mb-6">{props.topic.title}</h1>
|
||||||
|
|
||||||
{/* Enhanced Social Share Buttons */}
|
{/* Enhanced Social Share Buttons */}
|
||||||
|
@ -192,11 +211,11 @@ export default function TopicDetail(props) {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="font-light mb-8 text-justify prose max-w-none" dangerouslySetInnerHTML={{ __html: marked.parse(props.topic.content || '') }} ></div>
|
<div
|
||||||
<Comment topicId={props.topic.id}/>
|
className="font-light mb-8 text-justify prose max-w-none"
|
||||||
|
dangerouslySetInnerHTML={{ __html: marked.parse(props.topic.content || '') }}
|
||||||
|
></div>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// bg-[#6d9e37]
|
|
|
@ -4,5 +4,7 @@ import SignupPage from "../components/SignupPage";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="">
|
<Layout title="">
|
||||||
|
<div id="sample">
|
||||||
|
<?php eho "this gets commented, i need this to be as it is after build";?>
|
||||||
<SignupPage client:load />
|
<SignupPage client:load />
|
||||||
</Layout>
|
</Layout>
|
Loading…
Reference in New Issue