protocol-wizard.tsx 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. 'use client';
  2. import { useState } from 'react';
  3. import Link from 'next/link';
  4. import { Sparkles } from 'lucide-react';
  5. import {
  6. recommend,
  7. type UseCase,
  8. type CensorshipLevel,
  9. type ClientSupport,
  10. } from '@/lib/xray/protocols';
  11. import { ToolFrame } from './tool-frame';
  12. import { SelectField } from './shared/fields';
  13. const cap = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
  14. export function ProtocolWizard() {
  15. const [useCase, setUseCase] = useState<UseCase>('general');
  16. const [censorship, setCensorship] = useState<CensorshipLevel>('medium');
  17. const [clientSupport, setClientSupport] = useState<ClientSupport>('modern');
  18. const result = recommend({ useCase, censorship, clientSupport });
  19. return (
  20. <ToolFrame
  21. title="Protocol wizard"
  22. description="Answer a few questions to get a recommended protocol and transport."
  23. >
  24. <div className="grid grid-cols-1 gap-4 sm:grid-cols-3">
  25. <SelectField
  26. label="Primary goal"
  27. value={cap(useCase)}
  28. onChange={(v) => setUseCase(v.toLowerCase() as UseCase)}
  29. options={['Censorship', 'General', 'Speed']}
  30. />
  31. <SelectField
  32. label="Censorship level"
  33. value={cap(censorship)}
  34. onChange={(v) => setCensorship(v.toLowerCase() as CensorshipLevel)}
  35. options={['High', 'Medium', 'Low']}
  36. />
  37. <SelectField
  38. label="Client support"
  39. value={cap(clientSupport)}
  40. onChange={(v) => setClientSupport(v.toLowerCase() as ClientSupport)}
  41. options={['Modern', 'Broad']}
  42. />
  43. </div>
  44. <div className="mt-4 rounded-xl border bg-fd-background p-4">
  45. <div className="flex items-center gap-2 text-brand">
  46. <Sparkles className="size-4" aria-hidden />
  47. <span className="text-sm font-medium">Recommended</span>
  48. </div>
  49. <div className="mt-2 flex flex-wrap gap-2">
  50. <Badge>{result.protocol}</Badge>
  51. <Badge>{result.transport}</Badge>
  52. <Badge>{result.security}</Badge>
  53. </div>
  54. <p className="mt-3 text-sm text-fd-muted-foreground">{result.rationale}</p>
  55. <div className="mt-3 flex flex-wrap gap-3 text-sm">
  56. {result.links.map((link) => (
  57. <Link key={link.href} href={link.href} className="text-fd-primary hover:underline">
  58. {link.title} →
  59. </Link>
  60. ))}
  61. </div>
  62. </div>
  63. </ToolFrame>
  64. );
  65. }
  66. function Badge({ children }: { children: React.ReactNode }) {
  67. return (
  68. <span className="rounded-lg bg-brand/10 px-2.5 py-1 text-sm font-medium text-brand">
  69. {children}
  70. </span>
  71. );
  72. }