Compare commits

..

1 Commits
main ... tst2

Author SHA1 Message Date
Kar ad7313363b php support 2025-06-16 16:06:19 +00:00
4 changed files with 35 additions and 585 deletions

View File

@ -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 = `![Image](${imageUrlInput})`;
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 = `![Image](${uploadedUrl})`;
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>
</>
);
}

View File

@ -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>
</>
);
}

View File

@ -1,12 +1,6 @@
import React, { useState, useEffect } from "react";
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) {
const [showCopied, setShowCopied] = useState(false);
@ -18,6 +12,31 @@ export default function TopicDetail(props) {
const title = props.topic.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) => {
switch (platform){
case 'facebook':
@ -106,7 +125,7 @@ export default function TopicDetail(props) {
return (
<div className="container mx-auto px-4 py-12">
<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>
{/* Enhanced Social Share Buttons */}
@ -192,11 +211,11 @@ export default function TopicDetail(props) {
</div>
</div>
<div className="font-light mb-8 text-justify prose max-w-none" dangerouslySetInnerHTML={{ __html: marked.parse(props.topic.content || '') }} ></div>
<Comment topicId={props.topic.id}/>
<div
className="font-light mb-8 text-justify prose max-w-none"
dangerouslySetInnerHTML={{ __html: marked.parse(props.topic.content || '') }}
></div>
</article>
</div>
);
}
// bg-[#6d9e37]

View File

@ -4,5 +4,7 @@ import SignupPage from "../components/SignupPage";
---
<Layout title="">
<div id="sample">
<?php eho "this gets commented, i need this to be as it is after build";?>
<SignupPage client:load />
</Layout>