TasksStep.tsx4.3 KBView on GitHub
import { useState } from 'react';
import { Check, Loader2, Plus } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { taskGroupIcon } from '@/modules/userTasks/utils/task-group-icons';
import { cn } from '@/lib/utils';
import { SetupPreviewFrame } from '../components/SetupPreviewFrame';
import { useSetupTaskGroups } from '../hooks/use-setup-task-groups';

/**
 * What a task is, then the lanes to file them into.
 *
 * The explainer is short on purpose — the useful half of this step is the group
 * picker, because groups are the one thing about tasks a user must decide up
 * front. Everything else about the surface is discoverable by opening it.
 */
export function TasksStepBody() {
  const { starters, has, create } = useSetupTaskGroups();
  const [customName, setCustomName] = useState('');

  const submitCustom = () => {
    const name = customName.trim();
    if (!name) return;
    void create({ name });
    setCustomName('');
  };

  return (
    <div className="flex flex-col gap-5">
      <div className="space-y-2">
        <p className="text-foreground text-base font-semibold">Groups</p>
        <div className="grid grid-cols-2 gap-3">
          {starters.map((group) => {
            const Icon = taskGroupIcon(group.icon);
            const added = has(group.name);
            return (
              <button
                key=[redacted]
                type="button"
                disabled={added}
                onClick={() => void create(group)}
                className={cn(
                  'border-border flex items-center gap-2 rounded-md border p-3 text-left transition-colors',
                  added
                    ? 'bg-sunken cursor-default'
                    : 'bg-background hover:border-foreground/30 cursor-pointer',
                )}
              >
                <Icon className="h-4 w-4 shrink-0" style={{ color: group.color }} />
                <span className="min-w-0 flex-1 text-sm font-medium">{group.name}</span>
                {added && <Check className="h-3.5 w-3.5 shrink-0 text-emerald-600" />}
              </button>
            );
          })}
        </div>
      </div>

      <div className="flex items-center gap-2">
        <Input
          value={customName}
          onChange={(e) => setCustomName(e.target.value)}
          onKeyDown={(e) => {
            if (e.key === 'Enter') {
              e.preventDefault();
              submitCustom();
            }
          }}
          placeholder="New group"
          className="h-9"
        />
        <Button
          variant="outline"
          className="h-9 shrink-0 cursor-pointer"
          disabled={!customName.trim()}
          onClick={submitCustom}
        >
          <Plus className="mr-1 h-4 w-4" />
          Add
        </Button>
      </div>
    </div>
  );
}

/** The task-group rail as it will look on /tasks, live from `listGroups`. */
export function TasksStepPreview() {
  const { groups, isLoading } = useSetupTaskGroups();

  return (
    <SetupPreviewFrame label="Your task groups">
      <div className="flex h-full flex-col gap-0.5 p-3">
        {isLoading ? (
          <div className="flex flex-1 items-center justify-center">
            <Loader2 className="text-muted-foreground h-4 w-4 animate-spin" />
          </div>
        ) : (
          groups.map((group) => {
            const row = group as {
              id: string | null;
              name: string;
              color: string | null;
              icon: string | null;
              dueTaskCount?: number;
            };
            const Icon = taskGroupIcon(row.icon);
            return (
              <div
                key=[redacted] ?? 'misc'}
                className="flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm"
              >
                <Icon
                  className={cn('h-4 w-4 shrink-0', !row.color && 'text-muted-foreground')}
                  style={row.color ? { color: row.color } : undefined}
                />
                <span className="min-w-0 flex-1 truncate">{row.name}</span>
                {!!row.dueTaskCount && (
                  <span className="text-muted-foreground text-xs tabular-nums">
                    {row.dueTaskCount}
                  </span>
                )}
              </div>
            );
          })
        )}
      </div>
    </SetupPreviewFrame>
  );
}