build: fce83541-8c54-465f-a1fc-71388f99db57
This commit is contained in:
@@ -0,0 +1,330 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import {
|
||||
IonPage,
|
||||
IonContent,
|
||||
IonHeader,
|
||||
IonToolbar,
|
||||
IonTitle,
|
||||
IonButtons,
|
||||
IonButton,
|
||||
IonIcon,
|
||||
IonSpinner,
|
||||
IonItem,
|
||||
IonLabel,
|
||||
IonInput,
|
||||
IonTextarea,
|
||||
} from '@ionic/react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { supabase } from '../supabase';
|
||||
import { useAuth } from '../contexts/AuthContext';
|
||||
import { chevronBackOutline, saveOutline, trashOutline } from 'ionicons/icons';
|
||||
|
||||
const AdminNewsForm: React.FC = () => {
|
||||
const history = useHistory();
|
||||
const { id } = useParams<{ id: string }>();
|
||||
const isEdit = !!id && id !== 'new';
|
||||
const { user, profile } = useAuth();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [initialLoading, setInitialLoading] = useState(isEdit);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const [title, setTitle] = useState('');
|
||||
const [content, setContent] = useState('');
|
||||
const [imageUrl, setImageUrl] = useState('');
|
||||
|
||||
const showError = (msg: string) => {
|
||||
setError(msg);
|
||||
setTimeout(() => setError(null), 4000);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (profile?.role !== 'admin') {
|
||||
history.replace('/profile');
|
||||
return;
|
||||
}
|
||||
|
||||
if (isEdit) {
|
||||
loadNewsItem();
|
||||
}
|
||||
}, [profile, isEdit]);
|
||||
|
||||
const loadNewsItem = async () => {
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('news')
|
||||
.select('*')
|
||||
.eq('id', id)
|
||||
.single();
|
||||
if (error) throw error;
|
||||
if (data) {
|
||||
setTitle(data.title);
|
||||
setContent(data.content);
|
||||
setImageUrl(data.image_url || '');
|
||||
}
|
||||
} catch (err: any) {
|
||||
showError(err.message);
|
||||
} finally {
|
||||
setInitialLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!title.trim() || !content.trim()) {
|
||||
showError('Title and content are required.');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
if (isEdit) {
|
||||
const { error } = await supabase
|
||||
.from('news')
|
||||
.update({
|
||||
title,
|
||||
content,
|
||||
image_url: imageUrl.trim() || null,
|
||||
})
|
||||
.eq('id', id);
|
||||
if (error) throw error;
|
||||
} else {
|
||||
const { error } = await supabase.from('news').insert({
|
||||
title,
|
||||
content,
|
||||
image_url: imageUrl.trim() || null,
|
||||
created_by: user!.id,
|
||||
});
|
||||
if (error) throw error;
|
||||
}
|
||||
history.goBack();
|
||||
} catch (err: any) {
|
||||
showError(err.message);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async () => {
|
||||
if (!window.confirm('Delete this news item?')) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
const { error } = await supabase.from('news').delete().eq('id', id);
|
||||
if (error) throw error;
|
||||
history.goBack();
|
||||
} catch (err: any) {
|
||||
showError(err.message);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
if (initialLoading) {
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader className="ion-no-border">
|
||||
<IonToolbar style={{ '--background': '#f5f7f4' }}>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => history.goBack()}>
|
||||
<IonIcon icon={chevronBackOutline} slot="icon-only" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
<IonContent style={{ '--background': '#f5f7f4' }}>
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
marginTop: '40px',
|
||||
}}
|
||||
>
|
||||
<IonSpinner name="crescent" />
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<IonPage>
|
||||
<IonHeader className="ion-no-border">
|
||||
<IonToolbar style={{ '--background': '#f5f7f4' }}>
|
||||
<IonButtons slot="start">
|
||||
<IonButton onClick={() => history.goBack()}>
|
||||
<IonIcon icon={chevronBackOutline} slot="icon-only" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
<IonTitle style={{ fontWeight: '700', fontSize: '18px' }}>
|
||||
{isEdit ? 'Edit News' : 'Create News'}
|
||||
</IonTitle>
|
||||
{isEdit && (
|
||||
<IonButtons slot="end">
|
||||
<IonButton onClick={handleDelete} color="danger">
|
||||
<IonIcon icon={trashOutline} slot="icon-only" />
|
||||
</IonButton>
|
||||
</IonButtons>
|
||||
)}
|
||||
</IonToolbar>
|
||||
</IonHeader>
|
||||
|
||||
<IonContent style={{ '--background': '#f5f7f4' }}>
|
||||
<div style={{ padding: '20px', maxWidth: '600px', margin: '0 auto' }}>
|
||||
{error && (
|
||||
<div
|
||||
style={{
|
||||
background: '#fee2e2',
|
||||
color: '#991b1b',
|
||||
padding: '12px',
|
||||
borderRadius: '8px',
|
||||
fontSize: '14px',
|
||||
marginBottom: '20px',
|
||||
}}
|
||||
>
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
background: '#ffffff',
|
||||
borderRadius: '24px',
|
||||
padding: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: '16px',
|
||||
boxShadow: '0 2px 4px rgba(0,0,0,0.02)',
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: 'block',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600',
|
||||
color: '#4b5563',
|
||||
marginBottom: '8px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
}}
|
||||
>
|
||||
Title
|
||||
</label>
|
||||
<IonItem
|
||||
lines="none"
|
||||
style={{
|
||||
'--background': '#f9faf9',
|
||||
'--border-radius': '12px',
|
||||
'--padding-start': '16px',
|
||||
}}
|
||||
>
|
||||
<IonInput
|
||||
value={title}
|
||||
onIonChange={(e) => setTitle(e.detail.value!)}
|
||||
placeholder="Enter news title"
|
||||
style={{ fontWeight: '500' }}
|
||||
/>
|
||||
</IonItem>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: 'block',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600',
|
||||
color: '#4b5563',
|
||||
marginBottom: '8px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
}}
|
||||
>
|
||||
Image URL (Optional)
|
||||
</label>
|
||||
<IonItem
|
||||
lines="none"
|
||||
style={{
|
||||
'--background': '#f9faf9',
|
||||
'--border-radius': '12px',
|
||||
'--padding-start': '16px',
|
||||
}}
|
||||
>
|
||||
<IonInput
|
||||
value={imageUrl}
|
||||
onIonChange={(e) => setImageUrl(e.detail.value!)}
|
||||
placeholder="https://example.com/image.jpg"
|
||||
/>
|
||||
</IonItem>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label
|
||||
style={{
|
||||
display: 'block',
|
||||
fontSize: '12px',
|
||||
fontWeight: '600',
|
||||
color: '#4b5563',
|
||||
marginBottom: '8px',
|
||||
textTransform: 'uppercase',
|
||||
letterSpacing: '0.05em',
|
||||
}}
|
||||
>
|
||||
Content
|
||||
</label>
|
||||
<IonItem
|
||||
lines="none"
|
||||
style={{
|
||||
'--background': '#f9faf9',
|
||||
'--border-radius': '12px',
|
||||
'--padding-start': '16px',
|
||||
alignItems: 'flex-start',
|
||||
}}
|
||||
>
|
||||
<IonTextarea
|
||||
value={content}
|
||||
onIonChange={(e) => setContent(e.detail.value!)}
|
||||
placeholder="Write your news update here..."
|
||||
autoGrow
|
||||
style={{ minHeight: '150px' }}
|
||||
/>
|
||||
</IonItem>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={loading}
|
||||
style={{
|
||||
width: '100%',
|
||||
background: '#2f6b4f',
|
||||
color: '#ffffff',
|
||||
border: 'none',
|
||||
borderRadius: '999px',
|
||||
padding: '16px',
|
||||
fontSize: '16px',
|
||||
fontWeight: '600',
|
||||
marginTop: '24px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '8px',
|
||||
cursor: loading ? 'not-allowed' : 'pointer',
|
||||
opacity: loading ? 0.7 : 1,
|
||||
}}
|
||||
>
|
||||
{loading ? (
|
||||
<IonSpinner
|
||||
name="crescent"
|
||||
style={{ width: '24px', height: '24px' }}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<IonIcon icon={saveOutline} />
|
||||
Save News
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</IonContent>
|
||||
</IonPage>
|
||||
);
|
||||
};
|
||||
|
||||
export default AdminNewsForm;
|
||||
Reference in New Issue
Block a user