phone-number-card.tsx3.6 KBView on GitHub
import { SettingsCard } from '@/modules/userSettings/components/settings-card';
import { Check, Copy, MessageSquare, Smartphone } from 'lucide-react';
import { useMutation, useQuery } from '@tanstack/react-query';
import { useTRPC } from '@/providers/query-provider';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { useEffect, useState } from 'react';
import { toast } from 'sonner';

const CEDAR_CHANNELS = [
  { label: 'iMessage', number: '+13105971445', display: '+1 (310) 597-1445' },
  { label: 'SMS', number: '+14452854652', display: '+1 (445) 285-4652' },
  { label: 'WhatsApp', number: '+15559822517', display: '+1 (555) 982-2517' },
] as const;

function CopyableNumber({
  label,
  number,
  display,
}: {
  label: string;
  number: string;
  display: string;
}) {
  const [copied, setCopied] = useState(false);

  function handleCopy() {
    navigator.clipboard.writeText(number);
    setCopied(true);
    setTimeout(() => setCopied(false), 2000);
  }

  return (
    <div className="flex items-center justify-between rounded-lg border px-3 py-2">
      <div className="flex items-center gap-2">
        <MessageSquare className="text-muted-foreground h-3.5 w-3.5" />
        <span className="text-muted-foreground text-xs">{label}</span>
        <span className="font-mono text-sm font-medium">{display}</span>
      </div>
      <Button variant="ghost" size="sm" className="h-7 cursor-pointer px-2" onClick={handleCopy}>
        {copied ? (
          <Check className="h-3.5 w-3.5 text-green-500" />
        ) : (
          <Copy className="h-3.5 w-3.5" />
        )}
      </Button>
    </div>
  );
}

export function PhoneNumberCard() {
  const trpc = useTRPC();
  const [phone, setPhone] = useState('');
  const [isSaving, setIsSaving] = useState(false);

  const { data } = useQuery(trpc.user.getPhoneNumber.queryOptions());
  useEffect(() => {
    if (data?.phoneNumber != null) setPhone(data.phoneNumber);
  }, [data?.phoneNumber]);

  const { mutateAsync: updatePhoneNumber } = useMutation(
    trpc.user.updatePhoneNumber.mutationOptions(),
  );

  async function handleSave() {
    setIsSaving(true);
    try {
      await updatePhoneNumber({ phoneNumber: phone });
    } catch {
      toast.error('Invalid phone number — use E.164 format, e.g. +14155551234');
    } finally {
      setIsSaving(false);
    }
  }

  return (
    <SettingsCard
      title="Chat with Cedar on your phone"
      description="Text Cedar from iMessage, SMS, or WhatsApp."
      footer={
        <Button onClick={handleSave} disabled={isSaving} className="cursor-pointer">
          {isSaving ? 'Saving...' : 'Save Changes'}
        </Button>
      }
    >
      <div className="flex max-w-sm flex-col gap-4">
        <div className="flex flex-col gap-2">
          <p className="text-muted-foreground text-xs font-medium uppercase tracking-wide">
            Cedar's numbers
          </p>
          {CEDAR_CHANNELS.map((ch) => (
            <CopyableNumber key=[redacted] {...ch} />
          ))}
        </div>
        <div className="flex flex-col gap-2">
          <div className="flex items-center gap-2 text-sm font-medium">
            <Smartphone className="h-4 w-4" />
            Your phone number
          </div>
          <Input
            placeholder="+14155551234"
            value={phone}
            onChange={(e) => setPhone(e.target.value)}
          />
          <p className="text-muted-foreground text-xs">
            E.164 format including country code. Save your number to link it to your Cedar account.
          </p>
        </div>
      </div>
    </SettingsCard>
  );
}