Compare commits
5 Commits
fix-wa-otp
...
fix-tombol
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b8c65dd1e | |||
| 159fb3cec6 | |||
| 4821934224 | |||
| ee39b88b00 | |||
| ce46d3b5f7 |
@@ -23,8 +23,9 @@ const ApbdesFormSchema = z.object({
|
||||
name: z.string().optional(),
|
||||
deskripsi: z.string().optional(),
|
||||
jumlah: z.string().optional(),
|
||||
imageId: z.string().min(1, "Gambar wajib diunggah"),
|
||||
fileId: z.string().min(1, "File wajib diunggah"),
|
||||
// Image dan file opsional (bisa kosong)
|
||||
imageId: z.string().optional(),
|
||||
fileId: z.string().optional(),
|
||||
items: z.array(ApbdesItemSchema).min(1, "Minimal ada 1 item"),
|
||||
});
|
||||
|
||||
|
||||
@@ -205,7 +205,6 @@ function EditAPBDes() {
|
||||
|
||||
// Upload file baru jika ada perubahan
|
||||
if (imageFile) {
|
||||
// Hapus file lama dari form jika ada file baru
|
||||
const res = await ApiFetch.api.fileStorage.create.post({
|
||||
file: imageFile,
|
||||
name: imageFile.name,
|
||||
@@ -217,7 +216,6 @@ function EditAPBDes() {
|
||||
}
|
||||
|
||||
if (docFile) {
|
||||
// Hapus file lama dari form jika ada file baru
|
||||
const res = await ApiFetch.api.fileStorage.create.post({
|
||||
file: docFile,
|
||||
name: docFile.name,
|
||||
@@ -228,15 +226,7 @@ function EditAPBDes() {
|
||||
}
|
||||
}
|
||||
|
||||
// Jika tidak ada file baru, gunakan ID lama (sudah ada di form)
|
||||
// Pastikan imageId dan fileId tetap ada
|
||||
if (!apbdesState.edit.form.imageId) {
|
||||
return toast.warn('Gambar wajib diunggah');
|
||||
}
|
||||
if (!apbdesState.edit.form.fileId) {
|
||||
return toast.warn('Dokumen wajib diunggah');
|
||||
}
|
||||
|
||||
// Image dan file sekarang opsional, tidak perlu validasi
|
||||
const success = await apbdesState.edit.update();
|
||||
if (success) {
|
||||
router.push('/admin/landing-page/apbdes');
|
||||
@@ -343,11 +333,11 @@ function EditAPBDes() {
|
||||
required
|
||||
/>
|
||||
|
||||
{/* Gambar & Dokumen */}
|
||||
{/* Gambar & Dokumen (Opsional) */}
|
||||
<Stack gap="xs">
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Gambar APBDes
|
||||
Gambar APBDes (Opsional)
|
||||
</Text>
|
||||
<Dropzone
|
||||
onDrop={handleDrop('image')}
|
||||
@@ -387,6 +377,7 @@ function EditAPBDes() {
|
||||
onClick={() => {
|
||||
setPreviewImage(null);
|
||||
setImageFile(null);
|
||||
apbdesState.edit.form.imageId = ''; // Clear imageId from form
|
||||
}}
|
||||
>
|
||||
<IconX size={14} />
|
||||
@@ -397,7 +388,7 @@ function EditAPBDes() {
|
||||
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Dokumen APBDes
|
||||
Dokumen APBDes (Opsional)
|
||||
</Text>
|
||||
<Dropzone
|
||||
onDrop={handleDrop('doc')}
|
||||
@@ -446,6 +437,7 @@ function EditAPBDes() {
|
||||
onClick={() => {
|
||||
setPreviewDoc(null);
|
||||
setDocFile(null);
|
||||
apbdesState.edit.form.fileId = ''; // Clear fileId from form
|
||||
}}
|
||||
>
|
||||
<IconX size={14} />
|
||||
|
||||
@@ -46,13 +46,9 @@ function CreateAPBDes() {
|
||||
const [docFile, setDocFile] = useState<File | null>(null);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Check if form is valid
|
||||
// Check if form is valid - hanya cek items, gambar dan file opsional
|
||||
const isFormValid = () => {
|
||||
return (
|
||||
imageFile !== null &&
|
||||
docFile !== null &&
|
||||
stateAPBDes.create.form.items.length > 0
|
||||
);
|
||||
return stateAPBDes.create.form.items.length > 0;
|
||||
};
|
||||
|
||||
// Form sementara untuk input item baru
|
||||
@@ -84,28 +80,34 @@ function CreateAPBDes() {
|
||||
};
|
||||
|
||||
const handleSubmit = async () => {
|
||||
if (!imageFile || !docFile) {
|
||||
return toast.warn("Pilih gambar dan dokumen terlebih dahulu");
|
||||
}
|
||||
if (stateAPBDes.create.form.items.length === 0) {
|
||||
return toast.warn("Minimal tambahkan 1 item APBDes");
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const [uploadImageRes, uploadDocRes] = await Promise.all([
|
||||
ApiFetch.api.fileStorage.create.post({ file: imageFile, name: imageFile.name }),
|
||||
ApiFetch.api.fileStorage.create.post({ file: docFile, name: docFile.name }),
|
||||
]);
|
||||
|
||||
const imageId = uploadImageRes?.data?.data?.id;
|
||||
const fileId = uploadDocRes?.data?.data?.id;
|
||||
// Upload files hanya jika ada file yang dipilih
|
||||
let imageId = '';
|
||||
let fileId = '';
|
||||
|
||||
if (!imageId || !fileId) {
|
||||
return toast.error("Gagal mengupload file");
|
||||
if (imageFile) {
|
||||
const uploadImageRes = await ApiFetch.api.fileStorage.create.post({
|
||||
file: imageFile,
|
||||
name: imageFile.name,
|
||||
});
|
||||
imageId = uploadImageRes?.data?.data?.id || '';
|
||||
}
|
||||
|
||||
// Update form dengan ID file
|
||||
if (docFile) {
|
||||
const uploadDocRes = await ApiFetch.api.fileStorage.create.post({
|
||||
file: docFile,
|
||||
name: docFile.name,
|
||||
});
|
||||
fileId = uploadDocRes?.data?.data?.id || '';
|
||||
}
|
||||
|
||||
// Update form dengan ID file (bisa kosong)
|
||||
stateAPBDes.create.form.imageId = imageId;
|
||||
stateAPBDes.create.form.fileId = fileId;
|
||||
|
||||
@@ -174,12 +176,16 @@ function CreateAPBDes() {
|
||||
style={{ border: '1px solid #e0e0e0' }}
|
||||
>
|
||||
<Stack gap="md">
|
||||
{/* Gambar & Dokumen (dipendekkan untuk fokus pada items) */}
|
||||
{/* Info: File opsional */}
|
||||
<Text fz="sm" c="dimmed" mb="xs">
|
||||
* Upload gambar dan dokumen bersifat opsional. Bisa dikosongkan jika belum ada.
|
||||
</Text>
|
||||
|
||||
<Stack gap={"xs"}>
|
||||
{/* Gambar APBDes */}
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Gambar APBDes
|
||||
Gambar APBDes (Opsional)
|
||||
</Text>
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
@@ -249,10 +255,10 @@ function CreateAPBDes() {
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Dokumen APBDes */}
|
||||
{/* Dokumen APBDes (Opsional) */}
|
||||
<Box>
|
||||
<Text fw="bold" fz="sm" mb={6}>
|
||||
Dokumen APBDes
|
||||
Dokumen APBDes (Opsional)
|
||||
</Text>
|
||||
<Dropzone
|
||||
onDrop={(files) => {
|
||||
|
||||
@@ -17,8 +17,8 @@ type FormCreate = {
|
||||
name?: string;
|
||||
deskripsi?: string;
|
||||
jumlah?: string;
|
||||
imageId: string;
|
||||
fileId: string;
|
||||
imageId?: string | null; // Opsional
|
||||
fileId?: string | null; // Opsional
|
||||
items: APBDesItemInput[];
|
||||
};
|
||||
|
||||
@@ -32,12 +32,7 @@ export default async function apbdesCreate(context: Context) {
|
||||
if (!body.tahun) {
|
||||
throw new Error('Tahun is required');
|
||||
}
|
||||
if (!body.imageId) {
|
||||
throw new Error('Image ID is required');
|
||||
}
|
||||
if (!body.fileId) {
|
||||
throw new Error('File ID is required');
|
||||
}
|
||||
// Image dan file sekarang opsional
|
||||
if (!body.items || body.items.length === 0) {
|
||||
throw new Error('At least one item is required');
|
||||
}
|
||||
@@ -50,8 +45,8 @@ export default async function apbdesCreate(context: Context) {
|
||||
name: body.name || `APBDes Tahun ${body.tahun}`,
|
||||
deskripsi: body.deskripsi,
|
||||
jumlah: body.jumlah,
|
||||
imageId: body.imageId,
|
||||
fileId: body.fileId,
|
||||
imageId: body.imageId || null, // null jika tidak ada
|
||||
fileId: body.fileId || null, // null jika tidak ada
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -36,8 +36,8 @@ const APBDes = new Elysia({
|
||||
name: t.Optional(t.String()),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
jumlah: t.Optional(t.String()),
|
||||
imageId: t.String(),
|
||||
fileId: t.String(),
|
||||
imageId: t.Optional(t.String()),
|
||||
fileId: t.Optional(t.String()),
|
||||
items: t.Array(ApbdesItemSchema),
|
||||
}),
|
||||
})
|
||||
@@ -50,8 +50,8 @@ const APBDes = new Elysia({
|
||||
name: t.Optional(t.String()),
|
||||
deskripsi: t.Optional(t.String()),
|
||||
jumlah: t.Optional(t.String()),
|
||||
imageId: t.String(),
|
||||
fileId: t.String(),
|
||||
imageId: t.Optional(t.String()),
|
||||
fileId: t.Optional(t.String()),
|
||||
items: t.Array(ApbdesItemSchema),
|
||||
}),
|
||||
})
|
||||
|
||||
@@ -15,8 +15,8 @@ type FormUpdateBody = {
|
||||
name?: string;
|
||||
deskripsi?: string;
|
||||
jumlah?: string;
|
||||
imageId: string;
|
||||
fileId: string;
|
||||
imageId?: string | null;
|
||||
fileId?: string | null;
|
||||
items: APBDesItemInput[];
|
||||
};
|
||||
|
||||
|
||||
@@ -82,6 +82,12 @@ export function MusicProvider({ children }: { children: ReactNode }) {
|
||||
const audioRef = useRef<HTMLAudioElement | null>(null);
|
||||
const isSeekingRef = useRef(false);
|
||||
const animationFrameRef = useRef<number | null>(null);
|
||||
const isRepeatRef = useRef(false); // Ref untuk avoid stale closure
|
||||
|
||||
// Sync ref dengan state
|
||||
useEffect(() => {
|
||||
isRepeatRef.current = isRepeat;
|
||||
}, [isRepeat]);
|
||||
|
||||
// Load musik data
|
||||
const loadMusikData = useCallback(async () => {
|
||||
@@ -111,7 +117,8 @@ export function MusicProvider({ children }: { children: ReactNode }) {
|
||||
});
|
||||
|
||||
audioRef.current.addEventListener('ended', () => {
|
||||
if (isRepeat) {
|
||||
// Gunakan ref untuk avoid stale closure
|
||||
if (isRepeatRef.current) {
|
||||
audioRef.current!.currentTime = 0;
|
||||
audioRef.current!.play();
|
||||
} else {
|
||||
@@ -132,7 +139,7 @@ export function MusicProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- playNext is intentionally not in deps to avoid circular dependency
|
||||
}, [loadMusikData, isRepeat]);
|
||||
}, [loadMusikData]); // Remove isRepeat dari deps karena sudah pakai ref
|
||||
|
||||
// Update time with requestAnimationFrame for smooth progress
|
||||
const updateTime = useCallback(() => {
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
ActionIcon,
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
Flex,
|
||||
Group,
|
||||
Paper,
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from '@mantine/core';
|
||||
import {
|
||||
IconArrowsShuffle,
|
||||
IconMusic,
|
||||
IconPlayerPauseFilled,
|
||||
IconPlayerPlayFilled,
|
||||
IconPlayerSkipBackFilled,
|
||||
@@ -45,7 +47,7 @@ export default function FixedPlayerBar() {
|
||||
} = useMusic();
|
||||
|
||||
const [showVolume, setShowVolume] = useState(false);
|
||||
const [isPlayerVisible, setIsPlayerVisible] = useState(true);
|
||||
const [isMinimized, setIsMinimized] = useState(false);
|
||||
|
||||
// Format time
|
||||
const formatTime = (seconds: number) => {
|
||||
@@ -69,12 +71,55 @@ export default function FixedPlayerBar() {
|
||||
toggleShuffle();
|
||||
};
|
||||
|
||||
// Handle close player
|
||||
const handleClosePlayer = () => {
|
||||
setIsPlayerVisible(false);
|
||||
// Handle minimize player (show floating icon)
|
||||
const handleMinimizePlayer = () => {
|
||||
setIsMinimized(true);
|
||||
};
|
||||
|
||||
if (!currentSong || !isPlayerVisible) {
|
||||
// Handle restore player from floating icon
|
||||
const handleRestorePlayer = () => {
|
||||
setIsMinimized(false);
|
||||
};
|
||||
|
||||
// If minimized, show floating icon instead of player bar
|
||||
if (isMinimized) {
|
||||
return (
|
||||
<>
|
||||
{/* Floating Music Icon - Shows when player is minimized */}
|
||||
<Button
|
||||
color="#0B4F78"
|
||||
variant="filled"
|
||||
size="md"
|
||||
mt="md"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
top: '50%', // Menempatkan titik atas ikon di tengah layar
|
||||
left: '0px',
|
||||
transform: 'translateY(-50%)', // Menggeser ikon ke atas sebesar setengah tingginya sendiri agar benar-benar di tengah
|
||||
borderBottomRightRadius: '20px',
|
||||
borderTopRightRadius: '20px',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.2s ease',
|
||||
zIndex: 1
|
||||
}}
|
||||
onClick={handleRestorePlayer}
|
||||
onMouseEnter={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-50%) scale(1.1)';
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
e.currentTarget.style.transform = 'translateY(-50%)';
|
||||
}}
|
||||
>
|
||||
<IconMusic size={28} color="white" />
|
||||
</Button>
|
||||
|
||||
{/* Spacer to prevent content from being hidden behind player */}
|
||||
<Box h={20} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
if (!currentSong) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -89,12 +134,12 @@ export default function FixedPlayerBar() {
|
||||
p="sm"
|
||||
shadow="lg"
|
||||
style={{
|
||||
zIndex: 1000,
|
||||
zIndex: 1,
|
||||
borderTop: '1px solid rgba(0,0,0,0.1)',
|
||||
}}
|
||||
>
|
||||
<Flex align="center" gap="md" justify="space-between">
|
||||
{/* Song Info */}
|
||||
{/* Song Info - Left */}
|
||||
<Group gap="sm" flex={1} style={{ minWidth: 0 }}>
|
||||
<Avatar
|
||||
src={currentSong.coverImage?.link || ''}
|
||||
@@ -113,78 +158,81 @@ export default function FixedPlayerBar() {
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
{/* Controls */}
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant={isShuffle ? 'filled' : 'subtle'}
|
||||
color={isShuffle ? 'blue' : 'gray'}
|
||||
size="lg"
|
||||
onClick={handleToggleShuffle}
|
||||
title="Shuffle"
|
||||
>
|
||||
<IconArrowsShuffle size={18} />
|
||||
</ActionIcon>
|
||||
{/* Controls + Progress - Center */}
|
||||
<Group gap="xs" flex={2} justify="center">
|
||||
{/* Control Buttons */}
|
||||
<Group gap="xs">
|
||||
<ActionIcon
|
||||
variant={isShuffle ? 'filled' : 'subtle'}
|
||||
color={isShuffle ? 'blue' : 'gray'}
|
||||
size="lg"
|
||||
onClick={handleToggleShuffle}
|
||||
title="Shuffle"
|
||||
>
|
||||
<IconArrowsShuffle size={18} />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="lg"
|
||||
onClick={playPrev}
|
||||
title="Previous"
|
||||
>
|
||||
<IconPlayerSkipBackFilled size={20} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="lg"
|
||||
onClick={playPrev}
|
||||
title="Previous"
|
||||
>
|
||||
<IconPlayerSkipBackFilled size={20} />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
variant="filled"
|
||||
color={isPlaying ? 'blue' : 'gray'}
|
||||
size="xl"
|
||||
radius="xl"
|
||||
onClick={togglePlayPause}
|
||||
title={isPlaying ? 'Pause' : 'Play'}
|
||||
>
|
||||
{isPlaying ? (
|
||||
<IconPlayerPauseFilled size={24} />
|
||||
) : (
|
||||
<IconPlayerPlayFilled size={24} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="filled"
|
||||
color={isPlaying ? 'blue' : 'gray'}
|
||||
size="xl"
|
||||
radius="xl"
|
||||
onClick={togglePlayPause}
|
||||
title={isPlaying ? 'Pause' : 'Play'}
|
||||
>
|
||||
{isPlaying ? (
|
||||
<IconPlayerPauseFilled size={24} />
|
||||
) : (
|
||||
<IconPlayerPlayFilled size={24} />
|
||||
)}
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="lg"
|
||||
onClick={playNext}
|
||||
title="Next"
|
||||
>
|
||||
<IconPlayerSkipForwardFilled size={20} />
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="lg"
|
||||
onClick={playNext}
|
||||
title="Next"
|
||||
>
|
||||
<IconPlayerSkipForwardFilled size={20} />
|
||||
</ActionIcon>
|
||||
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color={isRepeat ? 'blue' : 'gray'}
|
||||
size="lg"
|
||||
onClick={toggleRepeat}
|
||||
title={isRepeat ? 'Repeat On' : 'Repeat Off'}
|
||||
>
|
||||
{isRepeat ? <IconRepeat size={18} /> : <IconRepeatOff size={18} />}
|
||||
</ActionIcon>
|
||||
<ActionIcon
|
||||
variant="subtle"
|
||||
color={isRepeat ? 'blue' : 'gray'}
|
||||
size="lg"
|
||||
onClick={toggleRepeat}
|
||||
title={isRepeat ? 'Repeat On' : 'Repeat Off'}
|
||||
>
|
||||
{isRepeat ? <IconRepeat size={18} /> : <IconRepeatOff size={18} />}
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
|
||||
{/* Progress Bar - Desktop */}
|
||||
<Box w={200} display={{ base: 'none', md: 'block' }}>
|
||||
<Slider
|
||||
value={currentTime}
|
||||
max={duration || 100}
|
||||
onChange={handleSeek}
|
||||
size="sm"
|
||||
color="blue"
|
||||
label={(value) => formatTime(value)}
|
||||
/>
|
||||
</Box>
|
||||
</Group>
|
||||
|
||||
{/* Progress Bar - Desktop */}
|
||||
<Box w={200} display={{ base: 'none', md: 'block' }}>
|
||||
<Slider
|
||||
value={currentTime}
|
||||
max={duration || 100}
|
||||
onChange={handleSeek}
|
||||
size="sm"
|
||||
color="blue"
|
||||
label={(value) => formatTime(value)}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
{/* Right Controls */}
|
||||
<Group gap="xs">
|
||||
{/* Right Controls - Volume + Close */}
|
||||
<Group gap="xs" flex={1} justify="flex-end">
|
||||
<Box
|
||||
onMouseEnter={() => setShowVolume(true)}
|
||||
onMouseLeave={() => setShowVolume(false)}
|
||||
@@ -241,8 +289,8 @@ export default function FixedPlayerBar() {
|
||||
variant="subtle"
|
||||
color="gray"
|
||||
size="lg"
|
||||
onClick={handleClosePlayer}
|
||||
title="Close player"
|
||||
onClick={handleMinimizePlayer}
|
||||
title="Minimize player"
|
||||
>
|
||||
<IconX size={18} />
|
||||
</ActionIcon>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
'use client';
|
||||
import { Button } from '@mantine/core';
|
||||
import { IconMusic, IconMusicOff } from '@tabler/icons-react';
|
||||
import { IconDisabled, IconDisabledOff } from '@tabler/icons-react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
const NewsReaderLanding = () => {
|
||||
@@ -95,15 +95,17 @@ const NewsReaderLanding = () => {
|
||||
mt="md"
|
||||
style={{
|
||||
position: 'fixed',
|
||||
bottom: '350px',
|
||||
top: '50%', // Menempatkan titik atas ikon di tengah layar
|
||||
left: '0px',
|
||||
transform: 'translateY(80%)', // Menggeser ikon ke atas sebesar setengah tingginya sendiri agar benar-benar di tengah
|
||||
borderBottomRightRadius: '20px',
|
||||
borderTopRightRadius: '20px',
|
||||
transition: 'all 0.3s ease',
|
||||
cursor: 'pointer',
|
||||
transition: 'transform 0.2s',
|
||||
zIndex: 1
|
||||
}}
|
||||
>
|
||||
{isPointerMode ? <IconMusicOff /> : <IconMusic />}
|
||||
{isPointerMode ? <IconDisabledOff /> : <IconDisabled />}
|
||||
</Button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -113,7 +113,7 @@ export default function GrafikRealisasi({ apbdesData }: any) {
|
||||
<Summary title="📊 Pembiayaan" data={pembiayaan} />
|
||||
</Stack>
|
||||
|
||||
{/* Summary Total Keseluruhan */}
|
||||
{/* Summary Total Keseluruhan
|
||||
<Box p="md" bg="gray.0">
|
||||
<>
|
||||
<Group justify="space-between" mb="xs">
|
||||
@@ -132,7 +132,7 @@ export default function GrafikRealisasi({ apbdesData }: any) {
|
||||
color={persenSemua >= 100 ? 'teal' : persenSemua >= 80 ? 'blue' : 'red'}
|
||||
/>
|
||||
</>
|
||||
</Box>
|
||||
</Box> */}
|
||||
</Paper>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user