Compare commits

..
Author SHA1 Message Date
Hafid Nur aa4686254b Merge branch 'develop' of github.com:IMPHNEN/imphnen-frontend-service into backoffice/for-hackathon 2025-12-02 18:13:03 +07:00
Hafid Nur 2d80ed05db feat(backoffice): little adjustment in hackathon users management UI and API contract 2025-12-02 18:11:34 +07:00
Hafid Nur d927b95457 feat(backoffice): add API contract for hackathon users 2025-12-01 11:41:56 +07:00
Hafid Nur 2dc9dd985a feat(backoffice): update page hackathon user management
- update data table component
- update filtering & pagination
- add modal display to edit and add user
- hide notification icon in backoffice wrapper
2025-12-01 11:41:18 +07:00
Hafid Nur 80a6aa9fb5 update endpoint 2025-11-30 18:57:04 +07:00
Hafid Nur 1343404e01 feat(hackathon): draft data table column & API Contract 2025-11-30 18:54:34 +07:00
Hafid Nur 5faba43728 base UI for Hackathon backoffice
TODO:
- Organize table schema for users, teams, and submissions management
- Create API Contract for additional back-end endpoint
2025-11-30 17:22:47 +07:00
Hafid Nur a20f5eff85 test datatable with mock data 2025-11-30 16:55:11 +07:00
Hafid Nur 18e835450a feat(backoffice): Create a nested/dropdown sidebar list
- Create a dropdown sidebar list
- Show back the old navigation and group them
- Make the sidebar responsive for mobile view
2025-11-30 12:59:18 +07:00
Hafid Nur f5ad55235c feat(backoffice): create a boilerplate page for Hackathon dashboard
- Create an empty page for Hackathon dashboard
- Comment out and hide the existing backoffice sidebar
2025-11-30 12:29:58 +07:00
2 changed files with 2 additions and 231 deletions
-91
View File
@@ -14,9 +14,6 @@ import ProfilePage from '../profile/page';
// Team features deadline: 2025-11-30 23:59:00 WIB (UTC+7) // Team features deadline: 2025-11-30 23:59:00 WIB (UTC+7)
const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z'); const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z');
// Submission deadline: 2025-12-07 23:59:00 WIB (UTC+7)
const SUBMISSION_DEADLINE = new Date('2025-12-07T16:59:00Z');
type Invitation = { type Invitation = {
id: string; id: string;
team: { team: {
@@ -40,46 +37,9 @@ type Invitation = {
const DashboardPage: FC = (): ReactElement => { const DashboardPage: FC = (): ReactElement => {
const { session } = useAuthStore(); const { session } = useAuthStore();
const [showProfileModal, setShowProfileModal] = useState(false); const [showProfileModal, setShowProfileModal] = useState(false);
const [timeLeft, setTimeLeft] = useState<{
days: number;
hours: number;
minutes: number;
seconds: number;
} | null>(null);
// Check if team features are closed // Check if team features are closed
const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE; const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE;
// Check if submission deadline passed
const isSubmissionDeadlinePassed = new Date() >= SUBMISSION_DEADLINE;
// Countdown timer
useEffect(() => {
if (isSubmissionDeadlinePassed) return;
const calculateTimeLeft = () => {
const now = new Date();
const difference = SUBMISSION_DEADLINE.getTime() - now.getTime();
if (difference <= 0) {
setTimeLeft(null);
return;
}
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);
const minutes = Math.floor((difference / 1000 / 60) % 60);
const seconds = Math.floor((difference / 1000) % 60);
setTimeLeft({ days, hours, minutes, seconds });
};
calculateTimeLeft();
const timer = setInterval(calculateTimeLeft, 1000);
return () => clearInterval(timer);
}, [isSubmissionDeadlinePassed]);
// Lock background scroll when profile modal is open // Lock background scroll when profile modal is open
useEffect(() => { useEffect(() => {
if (showProfileModal) { if (showProfileModal) {
@@ -134,57 +94,6 @@ const DashboardPage: FC = (): ReactElement => {
)} )}
</div> </div>
{/* Countdown Timer */}
{timeLeft && !isSubmissionDeadlinePassed && (
<div className="mb-8 bg-blue-50 dark:bg-blue-900/20 border-2 border-blue-500 rounded-lg p-6">
<div className="flex items-start space-x-3">
<span className="text-3xl"></span>
<div className="flex-1">
<h3 className="font-bold text-blue-900 dark:text-blue-100 text-lg">
Submission Deadline
</h3>
<p className="text-blue-800 dark:text-blue-200 mt-2 text-sm font-sans">
Project submissions close on December 7, 2025 at 23:59 WIB
</p>
<div className="mt-4 grid grid-cols-4 gap-4">
<div className="bg-white dark:bg-gray-800 rounded-lg p-3 text-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{timeLeft.days}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
Days
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-3 text-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{timeLeft.hours.toString().padStart(2, '0')}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
Hours
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-3 text-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{timeLeft.minutes.toString().padStart(2, '0')}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
Minutes
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-3 text-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{timeLeft.seconds.toString().padStart(2, '0')}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
Seconds
</div>
</div>
</div>
</div>
</div>
</div>
)}
{invitations.length > 0 && ( {invitations.length > 0 && (
<div className="mb-8 bg-primary-50 dark:bg-blue-900/20 border border-primary-200 dark:border-blue-800 rounded-lg p-6"> <div className="mb-8 bg-primary-50 dark:bg-blue-900/20 border border-primary-200 dark:border-blue-800 rounded-lg p-6">
<h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4"> <h2 className="text-xl font-bold text-gray-900 dark:text-white mb-4">
@@ -1,4 +1,4 @@
import { FC, ReactElement, useState, useEffect } from 'react'; import { FC, ReactElement, useState } from 'react';
import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms'; import { ControlledInputField } from '@imphnen-frontend-service/ui/organisms';
import { Button, Textarea } from '@imphnen-frontend-service/ui/atoms'; import { Button, Textarea } from '@imphnen-frontend-service/ui/atoms';
import { useNavigate, useParams } from 'react-router'; import { useNavigate, useParams } from 'react-router';
@@ -14,14 +14,10 @@ import {
} from '@imphnen-frontend-service/service'; } from '@imphnen-frontend-service/service';
import { zodResolver } from '@hookform/resolvers/zod'; import { zodResolver } from '@hookform/resolvers/zod';
import { toast } from 'sonner'; import { toast } from 'sonner';
import { Icon } from '@iconify/react';
const MIN_TEAM_MEMBERS = 2; // Minimum members required to submit (including leader) const MIN_TEAM_MEMBERS = 2; // Minimum members required to submit (including leader)
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
// Submission deadline: 2025-12-07 23:59:00 WIB (UTC+7)
const SUBMISSION_DEADLINE = new Date('2025-12-07T16:59:00Z');
const SubmitProjectPage: FC = (): ReactElement => { const SubmitProjectPage: FC = (): ReactElement => {
const { teamId } = useParams<{ teamId: string }>(); const { teamId } = useParams<{ teamId: string }>();
const navigate = useNavigate(); const navigate = useNavigate();
@@ -29,42 +25,6 @@ const SubmitProjectPage: FC = (): ReactElement => {
const [showConfirmModal, setShowConfirmModal] = useState(false); const [showConfirmModal, setShowConfirmModal] = useState(false);
const [confirmText, setConfirmText] = useState(''); const [confirmText, setConfirmText] = useState('');
const [screenshots, setScreenshots] = useState<string[]>([]); const [screenshots, setScreenshots] = useState<string[]>([]);
const [timeLeft, setTimeLeft] = useState<{
days: number;
hours: number;
minutes: number;
seconds: number;
} | null>(null);
// Check if deadline passed
const isDeadlinePassed = new Date() >= SUBMISSION_DEADLINE;
// Countdown timer
useEffect(() => {
if (isDeadlinePassed) return;
const calculateTimeLeft = () => {
const now = new Date();
const difference = SUBMISSION_DEADLINE.getTime() - now.getTime();
if (difference <= 0) {
setTimeLeft(null);
return;
}
const days = Math.floor(difference / (1000 * 60 * 60 * 24));
const hours = Math.floor((difference / (1000 * 60 * 60)) % 24);
const minutes = Math.floor((difference / 1000 / 60) % 60);
const seconds = Math.floor((difference / 1000) % 60);
setTimeLeft({ days, hours, minutes, seconds });
};
calculateTimeLeft();
const timer = setInterval(calculateTimeLeft, 1000);
return () => clearInterval(timer);
}, [isDeadlinePassed]);
const { data: teamData } = useTeamById(teamId || ''); const { data: teamData } = useTeamById(teamId || '');
const { data: submissionData } = useTeamSubmission(teamId || '', !!teamId); const { data: submissionData } = useTeamSubmission(teamId || '', !!teamId);
@@ -125,50 +85,6 @@ const SubmitProjectPage: FC = (): ReactElement => {
); );
} }
// Show deadline passed screen
if (isDeadlinePassed) {
return (
<div className="flex justify-center items-center min-h-screen bg-gray-50 dark:bg-gray-950 p-4">
<div className="bg-white dark:bg-gray-900 w-full max-w-md p-8 rounded-2xl shadow-lg border border-gray-200 dark:border-gray-700 text-center">
<div className="mb-6">
<div className="mx-auto w-16 h-16 bg-red-100 dark:bg-red-900/30 rounded-full flex items-center justify-center mb-4">
<Icon
icon="mdi:clock-alert"
className="text-3xl text-red-600 dark:text-red-400"
/>
</div>
<h2 className="text-3xl font-bold text-gray-900 dark:text-white mb-2">
Submission Closed
</h2>
<p className="text-gray-600 dark:text-gray-400">
Project submissions are no longer accepted.
</p>
</div>
<div className="space-y-4">
<p className="text-sm text-gray-600 dark:text-gray-400">
The submission deadline was December 7, 2025 at 23:59 WIB.
</p>
<button
onClick={() => navigate(`/teams/${teamId}`)}
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 transition-colors cursor-pointer"
>
Back to Team
</button>
<button
onClick={() => navigate('/dashboard')}
className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer"
>
Back to Dashboard
</button>
</div>
</div>
</div>
);
}
const handleScreenshotUpload = async ( const handleScreenshotUpload = async (
e: React.ChangeEvent<HTMLInputElement> e: React.ChangeEvent<HTMLInputElement>
) => { ) => {
@@ -222,57 +138,6 @@ const SubmitProjectPage: FC = (): ReactElement => {
</div> </div>
<div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <div className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
{/* Countdown Timer */}
{timeLeft && (
<div className="bg-blue-50 dark:bg-blue-900/20 border-2 border-blue-500 rounded-lg p-6 mb-6">
<div className="flex items-start space-x-3">
<span className="text-3xl"></span>
<div className="flex-1">
<h3 className="font-bold text-blue-900 dark:text-blue-100 text-lg">
Submission Deadline
</h3>
<p className="text-blue-800 dark:text-blue-200 mt-2 text-sm font-sans">
Submissions close on December 7, 2025 at 23:59 WIB
</p>
<div className="mt-4 grid grid-cols-4 gap-4">
<div className="bg-white dark:bg-gray-800 rounded-lg p-3 text-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{timeLeft.days}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
Days
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-3 text-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{timeLeft.hours.toString().padStart(2, '0')}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
Hours
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-3 text-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{timeLeft.minutes.toString().padStart(2, '0')}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
Minutes
</div>
</div>
<div className="bg-white dark:bg-gray-800 rounded-lg p-3 text-center">
<div className="text-2xl font-bold text-blue-600 dark:text-blue-400">
{timeLeft.seconds.toString().padStart(2, '0')}
</div>
<div className="text-xs text-gray-600 dark:text-gray-400 mt-1">
Seconds
</div>
</div>
</div>
</div>
</div>
</div>
)}
{/* Minimum Members Warning */} {/* Minimum Members Warning */}
{!hasEnoughMembers && ( {!hasEnoughMembers && (
<div className="bg-amber-50 dark:bg-amber-900/20 border-2 border-amber-500 rounded-lg p-6 mb-6"> <div className="bg-amber-50 dark:bg-amber-900/20 border-2 border-amber-500 rounded-lg p-6 mb-6">
@@ -335,9 +200,6 @@ const SubmitProjectPage: FC = (): ReactElement => {
<label className="block text-[15px] font-medium text-gray-700 dark:text-gray-300"> <label className="block text-[15px] font-medium text-gray-700 dark:text-gray-300">
Project Description <span className="text-red-500">*</span> Project Description <span className="text-red-500">*</span>
</label> </label>
<p className="text-sm text-gray-500 dark:text-gray-400 mb-2">
Describe your project, its features, and what problem it solves. You can also paste your demo video link here.
</p>
<Controller <Controller
control={form.control} control={form.control}
name="description" name="description"
@@ -345,7 +207,7 @@ const SubmitProjectPage: FC = (): ReactElement => {
<div> <div>
<Textarea <Textarea
{...field} {...field}
placeholder="Describe your project, its features, and what problem it solves... You can paste your demo video link (YouTube, Loom, etc.) here as well." placeholder="Describe your project, its features, and what problem it solves..."
rows={6} rows={6}
className="w-full" className="w-full"
size="lg" size="lg"