nnuer
강희태@gmlxo9917

앱끼리 연결할 때 권한을 묻는 허용/거부 화면

로그인/가입 블록

두 앱 아이콘 사이를 점으로 잇고, 어떤 계정으로 어떤 권한을 주는지 목록으로 보여준 뒤 허용 또는 거부를 고르는 연결 동의 화면입니다. 누르면 결과 화면으로 바뀝니다.

#dark#oauth#consent#permissions#connect2026. 10. 16. 업데이트

블록 신고

운영자가 확인한 뒤 필요하면 블록을 숨깁니다.

사유
Claude Code · claude-opus-5-5 · React (TSX)
결과 코드펼치기
import { useState } from "react";

const SCOPES = [
  {
    title: "운동 기록 읽기",
    hint: "날짜, 종목, 세트와 시간을 볼 수 있어요.",
    d: "M2.5 8h2l1.5-3 3 6 1.5-3h3",
  },
  {
    title: "새 운동 기록 추가하기",
    hint: "추가한 기록은 내가 확인해야 저장돼요.",
    d: "M8 3v10M3 8h10",
  },
];

export default function AppConnectConsent() {
  const [state, setState] = useState<"ask" | "pending-accept" | "pending-deny" | "accepted" | "denied">("ask");

  function answer(accept: boolean) {
    setState(accept ? "pending-accept" : "pending-deny");
    setTimeout(() => setState(accept ? "accepted" : "denied"), 900);
  }

  const pending = state === "pending-accept" || state === "pending-deny";

  return (
    <section className="w-full bg-[#0c0c10] px-4 py-16 text-zinc-100 sm:py-24">
      <div className="mx-auto max-w-[420px] rounded-[20px] border border-white/10 bg-[#141419] p-8 shadow-2xl shadow-black/50">
        <div className="flex items-center justify-center gap-3" aria-hidden>
          <span className="flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-br from-sky-400 to-blue-600 text-lg font-bold">D</span>
          <span className="flex gap-1">
            {[0, 1, 2].map((i) => (
              <span key={i} className="h-1.5 w-1.5 rounded-full bg-zinc-600" />
            ))}
          </span>
          <span className="flex h-12 w-12 items-center justify-center rounded-2xl bg-gradient-to-br from-orange-400 to-rose-500 text-lg font-bold">F</span>
        </div>

        {state === "accepted" || state === "denied" ? (
          <div role="status" className="mt-6 text-center">
            <h1 className="text-xl font-semibold tracking-tight">{state === "accepted" ? "연결했어요" : "연결을 취소했어요"}</h1>
            <p className="mt-2 text-sm text-zinc-400">
              {state === "accepted" ? "이 창을 닫고 Draftboard로 돌아가세요." : "Draftboard는 Fitlog 기록에 접근할 수 없어요."}
            </p>
            <button type="button" onClick={() => setState("ask")} className="mt-5 text-xs text-zinc-500 underline-offset-4 hover:text-zinc-300 hover:underline">
              처음 화면 다시 보기
            </button>
          </div>
        ) : (
          <>
            <h1 className="mt-6 text-center text-xl font-semibold tracking-tight">
              Draftboard이(가) Fitlog에
              <br />
              연결하려고 합니다
            </h1>
            <p className="mt-2 text-center text-sm text-zinc-400">
              <span className="text-zinc-200">kim@example.com</span> 계정으로 연결합니다.
            </p>
            <ul className="mt-6 divide-y divide-white/10 rounded-xl border border-white/10">
              {SCOPES.map((s) => (
                <li key={s.title} className="flex gap-3 px-4 py-3">
                  <span className="mt-0.5 flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-white/[0.06] text-orange-300" aria-hidden>
                    <svg width="14" height="14" viewBox="0 0 16 16" fill="none">
                      <path d={s.d} stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
                    </svg>
                  </span>
                  <span>
                    <span className="block text-sm font-medium">{s.title}</span>
                    <span className="block text-xs text-zinc-500">{s.hint}</span>
                  </span>
                </li>
              ))}
            </ul>
            <p className="mt-4 text-center text-xs text-zinc-500">연결은 Fitlog 설정에서 언제든 끊을 수 있어요.</p>
            <div className="mt-6 flex gap-2">
              <button
                type="button"
                disabled={pending}
                onClick={() => answer(false)}
                className="h-11 flex-1 rounded-xl border border-white/10 bg-white/[0.04] text-sm font-medium transition hover:bg-white/[0.08] disabled:opacity-50"
              >
                {state === "pending-deny" ? "취소하는 중..." : "거부"}
              </button>
              <button
                type="button"
                disabled={pending}
                onClick={() => answer(true)}
                className="h-11 flex-1 rounded-xl bg-gradient-to-r from-orange-400 to-rose-500 text-sm font-semibold text-white shadow-[0_0_24px_-6px_rgba(251,113,133,0.7)] transition hover:brightness-110 disabled:opacity-50"
              >
                {state === "pending-accept" ? "연결하는 중..." : "허용"}
              </button>
            </div>
          </>
        )}
      </div>
    </section>
  );
}

적용 사이트 0

이 블록이 실제로 쓰인 사이트입니다.

로그인하고 내 사이트 추가

아직 등록된 사이트가 없습니다. 이 블록을 쓴 사이트가 있다면 알려 주세요.

로그인하고 재현 결과 올리기

재현 후기

v1 · 0개

아직 이 버전의 프롬프트로 직접 만들어 본 사람이 없습니다. 첫 번째로 재현해 보세요.