All files / src/components/attendee CommunityTopicSuggestPanel.tsx

100% Statements 22/22
86.66% Branches 13/15
100% Functions 6/6
100% Lines 21/21

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111                                          1x 1x 1x   1x 85x 85x 85x 85x   85x 2x           1x 1x 1x       85x 85x 85x   85x                             2x 2x                     64x                         15x                                            
/**
 * CommunityTopicSuggestPanel — Story 7.1 "Topics From the Floor".
 *
 * Login-gated attendee surface: a logged-in attendee suggests a future event topic.
 * Submit-only (no browse). Flows into the existing topic pool tagged source=COMMUNITY.
 *
 * Tailwind-only by design — it lives on the PublicLayout-based AttendeeDashboardPage, which
 * must not import MUI (public-page bundle boundary). The route is login-gated upstream.
 */
 
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useMutation } from '@tanstack/react-query';
import { Lightbulb } from 'lucide-react';
import { Card } from '@/components/public/ui/card';
import { Button } from '@/components/public/ui/button';
import { Input } from '@/components/public/ui/input';
import { Textarea } from '@/components/public/ui/textarea';
import { Label } from '@/components/public/ui/label';
import { suggestTopicAsAttendee } from '@/services/api/partnerTopicsApi';
 
const MIN_TITLE = 5;
const MAX_TITLE = 255;
const MAX_DESC = 500;
 
export const CommunityTopicSuggestPanel = () => {
  const { t } = useTranslation('common');
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [submitted, setSubmitted] = useState(false);
 
  const mutation = useMutation({
    mutationFn: () =>
      suggestTopicAsAttendee({
        title: title.trim(),
        description: description.trim() || null,
      }),
    onSuccess: () => {
      setSubmitted(true);
      setTitle('');
      setDescription('');
    },
  });
 
  const titleValid = title.trim().length >= MIN_TITLE && title.trim().length <= MAX_TITLE;
  const descValid = description.length <= MAX_DESC;
  const canSubmit = titleValid && descValid && !mutation.isPending;
 
  return (
    <Card className="p-6 text-left" data-testid="community-topic-suggest-panel">
      <div className="flex items-center gap-2 mb-2">
        <Lightbulb className="h-5 w-5 text-blue-400" />
        <h2 className="text-sm font-medium text-zinc-300">{t('attendee.suggestTopic.heading')}</h2>
      </div>
      <p className="text-sm text-zinc-500 mb-4">{t('attendee.suggestTopic.intro')}</p>
 
      {submitted ? (
        <p className="text-sm text-green-400" data-testid="community-topic-success" role="status">
          {t('attendee.suggestTopic.success')}
        </p>
      ) : (
        <form
          onSubmit={(e) => {
            e.preventDefault();
            Eif (canSubmit) mutation.mutate();
          }}
          className="space-y-4"
        >
          <div className="space-y-1.5">
            <Label htmlFor="community-topic-title">{t('attendee.suggestTopic.titleLabel')}</Label>
            <Input
              id="community-topic-title"
              data-testid="community-topic-title"
              value={title}
              maxLength={MAX_TITLE}
              onChange={(e) => setTitle(e.target.value)}
              placeholder={t('attendee.suggestTopic.titlePlaceholder')}
            />
          </div>
          <div className="space-y-1.5">
            <Label htmlFor="community-topic-description">
              {t('attendee.suggestTopic.descriptionLabel')}
            </Label>
            <Textarea
              id="community-topic-description"
              data-testid="community-topic-description"
              value={description}
              maxLength={MAX_DESC}
              onChange={(e) => setDescription(e.target.value)}
              placeholder={t('attendee.suggestTopic.descriptionPlaceholder')}
              rows={3}
            />
          </div>
          {mutation.isError && (
            <p className="text-sm text-red-400" data-testid="community-topic-error" role="alert">
              {t('attendee.suggestTopic.error')}
            </p>
          )}
          <Button type="submit" disabled={!canSubmit} data-testid="community-topic-submit">
            {mutation.isPending
              ? t('attendee.suggestTopic.submitting')
              : t('attendee.suggestTopic.submit')}
          </Button>
        </form>
      )}
    </Card>
  );
};
 
export default CommunityTopicSuggestPanel;