feat: pyush
This commit is contained in:
@@ -11,6 +11,9 @@ import { Button } from '@imphnen-frontend-service/ui/atoms';
|
|||||||
import { Icon } from '@iconify/react';
|
import { Icon } from '@iconify/react';
|
||||||
import ProfilePage from '../profile/page';
|
import ProfilePage from '../profile/page';
|
||||||
|
|
||||||
|
// Team features deadline: 2025-11-30 23:59:00 WIB (UTC+7)
|
||||||
|
const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z');
|
||||||
|
|
||||||
type Invitation = {
|
type Invitation = {
|
||||||
id: string;
|
id: string;
|
||||||
team: {
|
team: {
|
||||||
@@ -34,6 +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);
|
||||||
|
|
||||||
|
// Check if team features are closed
|
||||||
|
const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE;
|
||||||
// Lock background scroll when profile modal is open
|
// Lock background scroll when profile modal is open
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showProfileModal) {
|
if (showProfileModal) {
|
||||||
@@ -93,6 +99,14 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
<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">
|
||||||
Team Invitations ({invitations.length})
|
Team Invitations ({invitations.length})
|
||||||
</h2>
|
</h2>
|
||||||
|
{isTeamFeaturesClosed && (
|
||||||
|
<div className="mb-4 bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800 rounded-lg p-3">
|
||||||
|
<p className="text-sm text-amber-700 dark:text-amber-300 flex items-center gap-2">
|
||||||
|
<Icon icon="mdi:clock-alert" className="text-lg shrink-0" />
|
||||||
|
Team features are closed. You can no longer accept invitations.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="space-y-3 font-sans">
|
<div className="space-y-3 font-sans">
|
||||||
{invitations.map((invitation) => (
|
{invitations.map((invitation) => (
|
||||||
<div
|
<div
|
||||||
@@ -108,21 +122,23 @@ const DashboardPage: FC = (): ReactElement => {
|
|||||||
{invitation.inviter?.fullname ?? 'Unknown User'}
|
{invitation.inviter?.fullname ?? 'Unknown User'}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex space-x-2">
|
{!isTeamFeaturesClosed && (
|
||||||
<Button
|
<div className="flex space-x-2">
|
||||||
size="sm"
|
<Button
|
||||||
onClick={() => handleAcceptInvitation(invitation.id)}
|
size="sm"
|
||||||
>
|
onClick={() => handleAcceptInvitation(invitation.id)}
|
||||||
Accept
|
>
|
||||||
</Button>
|
Accept
|
||||||
<Button
|
</Button>
|
||||||
size="sm"
|
<Button
|
||||||
variant="secondary"
|
size="sm"
|
||||||
onClick={() => handleRejectInvitation(invitation.id)}
|
variant="secondary"
|
||||||
>
|
onClick={() => handleRejectInvitation(invitation.id)}
|
||||||
Decline
|
>
|
||||||
</Button>
|
Decline
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ import { Icon } from '@iconify/react';
|
|||||||
|
|
||||||
const MAX_TEAM_MEMBERS = 5;
|
const MAX_TEAM_MEMBERS = 5;
|
||||||
|
|
||||||
|
// Team features deadline: 2025-11-30 23:59:00 WIB (UTC+7)
|
||||||
|
const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z');
|
||||||
|
|
||||||
// Image component with loading state
|
// Image component with loading state
|
||||||
const ImageWithLoader: FC<{
|
const ImageWithLoader: FC<{
|
||||||
src: string;
|
src: string;
|
||||||
@@ -52,6 +55,9 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
const { teamId } = useParams<{ teamId: string }>();
|
const { teamId } = useParams<{ teamId: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { session } = useAuthStore();
|
const { session } = useAuthStore();
|
||||||
|
|
||||||
|
// Check if team features are closed
|
||||||
|
const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE;
|
||||||
const [showInviteModal, setShowInviteModal] = useState(false);
|
const [showInviteModal, setShowInviteModal] = useState(false);
|
||||||
const [showJoinRequestsModal, setShowJoinRequestsModal] = useState(false);
|
const [showJoinRequestsModal, setShowJoinRequestsModal] = useState(false);
|
||||||
const [showLeaveModal, setShowLeaveModal] = useState(false);
|
const [showLeaveModal, setShowLeaveModal] = useState(false);
|
||||||
@@ -369,8 +375,8 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
Team Management
|
Team Management
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid gap-3 sm:grid-cols-2">
|
<div className="grid gap-3 sm:grid-cols-2">
|
||||||
{/* Hide invite/join management after submission */}
|
{/* Hide invite/join management after submission or deadline */}
|
||||||
{!team.has_submission && (
|
{!team.has_submission && !isTeamFeaturesClosed && (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
className="w-full"
|
className="w-full"
|
||||||
@@ -397,7 +403,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{!team.has_submission && (
|
{!team.has_submission && !isTeamFeaturesClosed && (
|
||||||
<Link to={`/teams/${teamId}/edit`}>
|
<Link to={`/teams/${teamId}/edit`}>
|
||||||
<Button className="w-full" variant="secondary">
|
<Button className="w-full" variant="secondary">
|
||||||
<Icon icon="mdi:pencil" className="inline-block mr-2" />
|
<Icon icon="mdi:pencil" className="inline-block mr-2" />
|
||||||
@@ -441,8 +447,8 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
Maximum team size reached ({MAX_TEAM_MEMBERS} members)
|
Maximum team size reached ({MAX_TEAM_MEMBERS} members)
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
{/* Danger Zone - Only show when leader is alone and no submission */}
|
{/* Danger Zone - Only show when leader is alone, no submission, and features not closed */}
|
||||||
{members.length === 1 && !team.has_submission && (
|
{members.length === 1 && !team.has_submission && !isTeamFeaturesClosed && (
|
||||||
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
|
<div className="mt-6 pt-4 border-t border-gray-200 dark:border-gray-700">
|
||||||
<h3 className="text-sm font-medium text-red-600 dark:text-red-400 mb-3">
|
<h3 className="text-sm font-medium text-red-600 dark:text-red-400 mb-3">
|
||||||
Danger Zone
|
Danger Zone
|
||||||
@@ -480,7 +486,7 @@ const TeamDashboardPage: FC = (): ReactElement => {
|
|||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
{!team.has_submission && (
|
{!team.has_submission && !isTeamFeaturesClosed && (
|
||||||
<Button
|
<Button
|
||||||
className="w-full bg-red-600 hover:bg-red-700 text-white"
|
className="w-full bg-red-600 hover:bg-red-700 text-white"
|
||||||
onClick={() => setShowLeaveModal(true)}
|
onClick={() => setShowLeaveModal(true)}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ import { Icon } from '@iconify/react';
|
|||||||
const DEFAULT_PER_PAGE = 12;
|
const DEFAULT_PER_PAGE = 12;
|
||||||
const PER_PAGE_OPTIONS = [6, 12, 24, 48];
|
const PER_PAGE_OPTIONS = [6, 12, 24, 48];
|
||||||
|
|
||||||
|
// Team features deadline: 2025-11-30 23:59:00 WIB (UTC+7)
|
||||||
|
const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z');
|
||||||
|
|
||||||
// Member filter options
|
// Member filter options
|
||||||
const MEMBER_FILTER_OPTIONS = [
|
const MEMBER_FILTER_OPTIONS = [
|
||||||
{ label: 'All Teams', value: '', minMembers: undefined, maxMembers: undefined },
|
{ label: 'All Teams', value: '', minMembers: undefined, maxMembers: undefined },
|
||||||
@@ -57,6 +60,9 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
|
// Check if team features are closed
|
||||||
|
const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE;
|
||||||
|
|
||||||
// Initialize state from URL params
|
// Initialize state from URL params
|
||||||
const initialPage = parseInt(searchParams.get('page') || '1', 10);
|
const initialPage = parseInt(searchParams.get('page') || '1', 10);
|
||||||
const initialPerPage = parseInt(
|
const initialPerPage = parseInt(
|
||||||
@@ -416,7 +422,8 @@ const BrowseTeamsPage: FC = (): ReactElement => {
|
|||||||
<>
|
<>
|
||||||
{myTeams.length === 0 &&
|
{myTeams.length === 0 &&
|
||||||
(team.member_count || 0) < 5 &&
|
(team.member_count || 0) < 5 &&
|
||||||
!team.has_submission && (
|
!team.has_submission &&
|
||||||
|
!isTeamFeaturesClosed && (
|
||||||
<Button
|
<Button
|
||||||
className="w-full"
|
className="w-full"
|
||||||
onClick={() => handleJoinRequest(team.id)}
|
onClick={() => handleJoinRequest(team.id)}
|
||||||
|
|||||||
@@ -6,13 +6,21 @@ import { useForm, Controller } from 'react-hook-form';
|
|||||||
import { teamCreateSchema, TTeamCreateForm, useCreateTeam, ETeamVisibility, useUploadFile } from '@imphnen-frontend-service/service';
|
import { teamCreateSchema, TTeamCreateForm, useCreateTeam, ETeamVisibility, useUploadFile } 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';
|
||||||
|
|
||||||
import { CitySelect } from '../../../components/city-select';
|
import { CitySelect } from '../../../components/city-select';
|
||||||
|
|
||||||
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
|
const MAX_FILE_SIZE = 2 * 1024 * 1024; // 2MB
|
||||||
|
|
||||||
|
// Team features deadline: 2025-11-30 23:59:00 WIB (UTC+7)
|
||||||
|
const TEAM_FEATURES_DEADLINE = new Date('2025-11-30T16:59:00Z');
|
||||||
|
|
||||||
const CreateTeamPage: FC = (): ReactElement => {
|
const CreateTeamPage: FC = (): ReactElement => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
// Check if team features are closed
|
||||||
|
const isTeamFeaturesClosed = new Date() >= TEAM_FEATURES_DEADLINE;
|
||||||
|
|
||||||
const [logoFile, setLogoFile] = useState<File | null>(null);
|
const [logoFile, setLogoFile] = useState<File | null>(null);
|
||||||
const [logoPreview, setLogoPreview] = useState<string>('');
|
const [logoPreview, setLogoPreview] = useState<string>('');
|
||||||
const [bannerFile, setBannerFile] = useState<File | null>(null);
|
const [bannerFile, setBannerFile] = useState<File | null>(null);
|
||||||
@@ -103,6 +111,50 @@ const CreateTeamPage: FC = (): ReactElement => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Show closed screen if team features are closed
|
||||||
|
if (isTeamFeaturesClosed) {
|
||||||
|
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">
|
||||||
|
Team Features Closed
|
||||||
|
</h2>
|
||||||
|
<p className="text-gray-600 dark:text-gray-400">
|
||||||
|
Team creation is no longer available.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-4">
|
||||||
|
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||||
|
The deadline for team features was November 30, 2025 at 23:59 WIB.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/dashboard')}
|
||||||
|
className="w-full py-3 bg-primary-600 text-white rounded-lg font-semibold hover:bg-primary-700 transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
Back to Dashboard
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/teams/browse')}
|
||||||
|
className="w-full py-3 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors cursor-pointer"
|
||||||
|
>
|
||||||
|
Browse Teams
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
|
<div className="min-h-screen bg-gray-50 dark:bg-neutral-950">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
|
|||||||
Reference in New Issue
Block a user