Giter Site home page Giter Site logo

boostcampwm2023 / web13_boarlog Goto Github PK

View Code? Open in Web Editor NEW
49.0 3.0 5.0 12.2 MB

πŸ™‚ 기둝으둜 λ‚¨κΈ°λŠ” μ‹€μ‹œκ°„ ν™”μ΄νŠΈλ³΄λ“œ κ°•μ˜, Boarlog

Home Page: https://boarlog.netlify.app/

JavaScript 4.22% HTML 3.37% CSS 1.07% TypeScript 91.29% Dockerfile 0.05%
fabricjs mongodb nestjs react redis webrtc typescript

web13_boarlog's Introduction

boarlog1

배포 νŒ¨μ΄μ§€λ‘œ 이동


λ…Έμ…˜ Β  | Β  백둜그 Β  | Β  ν”Όκ·Έλ§ˆ Β  | Β  μœ„ν‚€

μ‹œμ—° μ˜μƒ Β  | Β  μ„œλΉ„μŠ€ 링크



Boarlog의 μ£Όμš” κΈ°λŠ₯

πŸ§‘β€πŸ«Β κ°•μ˜ 생성 및 진행

κ°•μ˜μžκ°€ κ°•μ˜λ₯Ό μƒμ„±ν•˜κ³ , μŒμ„±μœΌλ‘œ κ°•μ˜ν•˜λ©° ν™”μ΄νŠΈλ³΄λ“œλ₯Ό μ‹€μ‹œκ°„μœΌλ‘œ νŽΈμ§‘ν•  수 μžˆμŠ΅λ‹ˆλ‹€. λ˜ν•œ, μ°Έμ—¬μžμ˜ μ§ˆλ¬Έμ„ λ°›μ•„ ν™”μ΄νŠΈλ³΄λ“œμ— ν¬μŠ€νŠΈμž‡ ν˜•νƒœλ‘œ μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

1.mp4
2.mp4

κ°•μ˜ 생성 및 마이크 μ„€μ •

ν™”μ΄νŠΈλ³΄λ“œ νŽΈμ§‘ 및 질문 확인


πŸ§‘β€πŸŽ“Β κ°•μ˜ μ°Έμ—¬ 및 ν”Όλ“œλ°±

κ°•μ˜μžκ°€ κ³΅μœ ν•œ κ°•μ˜λ²ˆν˜Έλ₯Ό μž…λ ₯ν•˜μ—¬ κ°•μ˜μ— μ°Έμ—¬ν•˜κ³ , κ°•μ˜μžμ˜ μŒμ„±κ³Ό ν™”μ΄νŠΈλ³΄λ“œ λ‚΄μš©μ„ μ‹€μ‹œκ°„μœΌλ‘œ λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. κ°•μ˜μžμ—κ²Œ μ§ˆλ¬Έμ„ 보낼 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

3.mp4
4.mp4

κ°•μ˜μ‹€ μž…μž₯ 및 κ°•μ˜ λ‚΄μš© 확인

μŠ€ν”Όμ»€ μ„€μ • 및 μ§ˆλ¬Έν•˜κΈ°


πŸŽ₯Β κ°•μ˜ λ‹€μ‹œλ³΄κΈ°

κ°•μ˜ μ’…λ£Œ ν›„μ—λŠ” μ„œλ²„μ—μ„œ ν™”μ΄νŠΈλ³΄λ“œμ™€ μŒμ„±, AIλ₯Ό μ΄μš©ν•œ μŒμ„± 슀크립트λ₯Ό μ €μž₯ν•˜μ—¬ μ–Έμ œλ“ μ§€ λ‹€μ‹œλ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€.

5.mp4
6.mp4

μ°Έμ—¬ν•œ κ°•μ˜ λ‚΄μ—­ 확인

κ°•μ˜ λ‹€μ‹œλ³΄κΈ°


ν΄λΌμ΄μ–ΈνŠΈ μš”μ²­ 흐름도

μ„œλ²„ μ•„ν‚€ν…μ²˜


Boarlog의 기술 μŠ€νƒ

category stack

Common

Frontend

Backend

CI/CD

Deployment

Collaboration


핡심 개발 일지

πŸ”— ν”„λ‘œμ νŠΈ 진행 κ³Όμ •μ—μ„œ κ³ λ―Όν•˜κ³  λ„μ „ν•œ 과정이 λ‹΄κΈ΄ 핡심 개발 μΌμ§€μž…λ‹ˆλ‹€.


νŒ€ μ†Œκ°œ: TEAM_528

const camperIdArray = ["J063", "J095", "J105", "J124", "J141"];

const teamName = camperIdArray.reduce((team, camperId) => {
  return (team += parseInt(camperId.replace(/[^0-9]/g, "")));
}, 0);

5λͺ…이 λͺ¨μ—¬ ν•˜λ‚˜μ˜ 개발자처럼 : J063 + J095 + J105 + J124 + J141

528은 νŒ€μ› 5λͺ…μ˜ 캠퍼 번호λ₯Ό λ”ν•œ μˆ«μžμž…λ‹ˆλ‹€. 5λͺ…μ˜ 캠퍼가 ν•˜λ‚˜μ˜ 번호λ₯Ό 가지고 μ„œλ‘œ λ‹€λ₯Έ 기술, κ²½ν—˜, 및 아이디어λ₯Ό κ²°ν•©ν•˜μ—¬ μ‹œλ„ˆμ§€λ₯Ό λ°œνœ˜ν•˜κ² λ‹€λŠ” μ˜λ―Έμž…λ‹ˆλ‹€.


변진상 (FE) 이동령 (FE) μ΄μŠΉμ—° (BE) μ΄ν˜„μ’… (BE) μ •μ£Όμ™„ (FE)
@Byeonjin @LellowMellow @tmddus2 @platinouss @Jw705
깊이있게 κ³ λ―Όν•˜κ³  κΈ°λ‘ν•˜κΈ° κ΄œν•œ κ±±μ • ν•˜μ§€ μ•Šκ³  μ°¨κ·Όμ°¨κ·Ό μŒ“μ•„μ˜¬λ¦¬κΈ° λ‘λ €μ›Œν•˜μ§€ μ•Šκ³  문제 ν•΄κ²°ν•˜κΈ° λͺ¨λ“  선택에 λ‚˜λ§Œμ˜ κ·Όκ±° λ§Œλ“€κΈ° μž‘μ„±ν•œ μ½”λ“œμ— λŒ€ν•œ μ˜λ„λ₯Ό 잘 κΈ°λ‘ν•˜κΈ°

λ°”λ‘œκ°€κΈ°

web13_boarlog's People

Contributors

byeonjin avatar jw705 avatar lellowmellow avatar platinouss avatar tmddus2 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

web13_boarlog's Issues

Feature: μœ νš¨ν•˜μ§€ μ•Šμ€ μ½”λ“œλΌλ©΄ νŽ˜μ΄μ§€λ₯Ό μ΄λ™ν•˜μ§€ μ•Šκ³  μ•ˆλ‚΄ 문ꡬλ₯Ό ν‘œμ‹œν•œλ‹€.

Feature Description

μœ νš¨ν•˜μ§€ μ•Šμ€ μ½”λ“œλΌλ©΄ νŽ˜μ΄μ§€λ₯Ό μ΄λ™ν•˜μ§€ μ•Šκ³  μ•ˆλ‚΄ 문ꡬλ₯Ό ν‘œμ‹œν•œλ‹€.

To-Do

  • μœ νš¨ν•˜μ§€ μ•Šμ€ μ½”λ“œμΌ 경우, Toastλ₯Ό ν‘œμ‹œν•˜λ„λ‘ 처리

μΆ”κ°€ 사항

2023-12-07.20-57-26.mp4

Feature: λ‹‰λ„€μž„μ΄ λ‹‰λ„€μž„ κ·œμΉ™μ— λ§žλŠ”μ§€ ν™•μΈν•œλ‹€.

Feature Description

λ‹‰λ„€μž„μ΄ λ‹‰λ„€μž„ κ·œμΉ™μ— λ§žλŠ”μ§€ ν™•μΈν•œλ‹€ #10

To-Do

  • 초기 정보 μž…λ ₯ νŽ˜μ΄μ§€ UI μž‘μ—…
  • λ‹‰λ„€μž„ κ·œμΉ™ 확인 μ •κ·œν‘œν˜„μ‹ μž‘μ„±
  • λ‹‰λ„€μž„ κ·œμΉ™ 여뢀에 따라 λΆ„κΈ° 처리
  • μΆ”κ°€: 둜그인 νŽ˜μ΄μ§€ UI μˆ˜μ •
  • μΆ”κ°€: 둜그인 νŽ˜μ΄μ§€μ™€ 메인 νŽ˜μ΄μ§€ μ—°κ²°

μΆ”κ°€ 사항

둜그인 νŽ˜μ΄μ§€, 초기 정보 μž…λ ₯ νŽ˜μ΄μ§€ UI ꡬ성

메인 νŽ˜μ΄μ§€μ™€ λ”λΆˆμ–΄ 초기 정보 μž…λ ₯ νŽ˜μ΄μ§€μ˜ UIλ₯Ό κ΅¬μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€. λ°˜μ‘ν˜•μ„ κ³ λ €ν•˜μ—¬ νŠΉμ • λ„ˆλΉ„λ₯Ό κΈ°μ€€μœΌλ‘œ 크기가 μ‘°μ ˆλ˜λ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€. λ˜ν•œ 일뢀 UIμ™€μ˜ 톡일성을 κ³ λ €ν•˜μ—¬ κΈ€μž 크기λ₯Ό μˆ˜μ •ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

λ‹‰λ„€μž„ κ·œμΉ™ 확인

μ •κ·œν‘œν˜„μ‹μ„ μž‘μ„±ν•˜μ—¬ μž…λ ₯ν•œ λ‹‰λ„€μž„μ΄ μ˜¬λ°”λ₯Έμ§€λ₯Ό νŒλ‹¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€. λ‹‰λ„€μž„μ˜ κ·œμΉ™μ€ μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

  • μ΅œμ†Œ 1자리, μ΅œλŒ€ 10자리
  • 영문, ν•œκΈ€, 숫자, -, _, . μ‚¬μš© κ°€λŠ₯
  • μˆ«μžλ‚˜ 특수 문자만 λ‹¨λ…μœΌλ‘œ μ‚¬μš© λΆˆκ°€λŠ₯
  • 영문 ν˜Ήμ€ ν•œκΈ€μ€ λ‹¨λ…μœΌλ‘œ μ‚¬μš© κ°€λŠ₯
const NICKNAME_REGEXP = /^(?![0-9-_.]+$)[κ°€-힣A-Za-z0-9-_.]{1,10}$/;
  • (?![0-9-_.]+$) : 숫자, -, _, .만으둜 이루어진 λ¬Έμžμ—΄μ„ μ œμ™Έν•©λ‹ˆλ‹€.
  • [κ°€-힣A-Za-z0-9-_.] : ν•œκΈ€, 영문 λŒ€μ†Œλ¬Έμž, 숫자, -, _, . 쀑 ν•˜λ‚˜λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€.
  • {1,10} : μ•žμ˜ 문자 집합이 μ΅œμ†Œ 1번, μ΅œλŒ€ 10번 λ°˜λ³΅λ¨μ„ μ˜λ―Έν•©λ‹ˆλ‹€.

ν•΄λ‹Ή μ •κ·œν‘œν˜„μ‹μ„ κΈ°μ€€μœΌλ‘œ λ²„νŠΌμ΄ ν™œμ„±ν™”λ˜λ„λ‘ μ²˜λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

둜그인 νŽ˜μ΄μ§€, 초기 정보 μž…λ ₯ νŽ˜μ΄μ§€ UI ꡬ성

2023-11-28.18-05-33.mp4

λ‹‰λ„€μž„ κ·œμΉ™ 확인

2023-11-28.18-05-53.mp4

νŽ˜μ΄μ§€ 이동

2023-11-28.18-06-49.mp4

Chore: node-webrtc λͺ¨λ“ˆ Typescript μ–Έμ–΄λ‘œ λΉŒλ“œν•˜κΈ°

Feature Description

ν˜„μž¬ node-webrtc λͺ¨λ“ˆμ΄ νƒ€μž…μŠ€ν¬λ¦½νŠΈ μ–Έμ–΄λ₯Ό μ§€μ›ν•˜μ§€ μ•Šμ•„, νƒ€μž… μ •μ˜ νŒŒμΌμ„ ν•„μš”λ‘œ ν–ˆμŠ΅λ‹ˆλ‹€.
node-webrtc의 repository issue듀을 μ°Έκ³ ν•˜μ—¬ νƒ€μž… μ •μ˜ νŒŒμΌμ„ λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€.

To-Do

λ‹€μŒ νƒ€μž… μ •μ˜ νŒŒμΌμ„ node_modules/wrtc/lib/index.d.ts에 μΆ”κ°€ν•œλ‹€.

export interface RTCAnswerOptions extends RTCOfferAnswerOptions {
}

export interface RTCCertificateExpiration {
  expires?: number;
}

export interface RTCConfiguration {
  bundlePolicy?: RTCBundlePolicy;
  certificates?: RTCCertificate[];
  iceCandidatePoolSize?: number;
  iceServers?: RTCIceServer[];
  iceTransportPolicy?: RTCIceTransportPolicy;
  rtcpMuxPolicy?: RTCRtcpMuxPolicy;
}

export interface RTCDTMFToneChangeEventInit extends EventInit {
  tone?: string;
}

export interface RTCDataChannelEventInit extends EventInit {
  channel: RTCDataChannel;
}

export interface RTCDataChannelInit {
  id?: number;
  maxPacketLifeTime?: number;
  maxRetransmits?: number;
  negotiated?: boolean;
  ordered?: boolean;
  protocol?: string;
}

export interface RTCDtlsFingerprint {
  algorithm?: string;
  value?: string;
}

export interface RTCEncodedAudioFrameMetadata {
  contributingSources?: number[];
  synchronizationSource?: number;
}

export interface RTCEncodedVideoFrameMetadata {
  contributingSources?: number[];
  dependencies?: number[];
  frameId?: number;
  height?: number;
  spatialIndex?: number;
  synchronizationSource?: number;
  temporalIndex?: number;
  width?: number;
}

export interface RTCErrorEventInit extends EventInit {
  error: RTCError;
}

export interface RTCErrorInit {
  errorDetail: RTCErrorDetailType;
  httpRequestStatusCode?: number;
  receivedAlert?: number;
  sctpCauseCode?: number;
  sdpLineNumber?: number;
  sentAlert?: number;
}

export interface RTCIceCandidateInit {
  candidate?: string;
  sdpMLineIndex?: number | null;
  sdpMid?: string | null;
  usernameFragment?: string | null;
}

export interface RTCIceCandidatePairStats extends RTCStats {
  availableIncomingBitrate?: number;
  availableOutgoingBitrate?: number;
  bytesReceived?: number;
  bytesSent?: number;
  currentRoundTripTime?: number;
  localCandidateId: string;
  nominated?: boolean;
  remoteCandidateId: string;
  requestsReceived?: number;
  requestsSent?: number;
  responsesReceived?: number;
  responsesSent?: number;
  state: RTCStatsIceCandidatePairState;
  totalRoundTripTime?: number;
  transportId: string;
}

export interface RTCIceServer {
  credential?: string;
  credentialType?: RTCIceCredentialType;
  urls: string | string[];
  username?: string;
}

export interface RTCInboundRtpStreamStats extends RTCReceivedRtpStreamStats {
  firCount?: number;
  framesDecoded?: number;
  nackCount?: number;
  pliCount?: number;
  qpSum?: number;
  remoteId?: string;
}

export interface RTCLocalSessionDescriptionInit {
  sdp?: string;
  type?: RTCSdpType;
}

export interface RTCOfferAnswerOptions {
}

export interface RTCOfferOptions extends RTCOfferAnswerOptions {
  iceRestart?: boolean;
  offerToReceiveAudio?: boolean;
  offerToReceiveVideo?: boolean;
}

export interface RTCOutboundRtpStreamStats extends RTCSentRtpStreamStats {
  firCount?: number;
  framesEncoded?: number;
  nackCount?: number;
  pliCount?: number;
  qpSum?: number;
  remoteId?: string;
}

export interface RTCPeerConnectionIceErrorEventInit extends EventInit {
  address?: string | null;
  errorCode: number;
  errorText?: string;
  port?: number | null;
  url?: string;
}

export interface RTCPeerConnectionIceEventInit extends EventInit {
  candidate?: RTCIceCandidate | null;
  url?: string | null;
}

export interface RTCReceivedRtpStreamStats extends RTCRtpStreamStats {
  jitter?: number;
  packetsDiscarded?: number;
  packetsLost?: number;
  packetsReceived?: number;
}

export interface RTCRtcpParameters {
  cname?: string;
  reducedSize?: boolean;
}

export interface RTCRtpCapabilities {
  codecs: RTCRtpCodecCapability[];
  headerExtensions: RTCRtpHeaderExtensionCapability[];
}

export interface RTCRtpCodecCapability {
  channels?: number;
  clockRate: number;
  mimeType: string;
  sdpFmtpLine?: string;
}

export interface RTCRtpCodecParameters {
  channels?: number;
  clockRate: number;
  mimeType: string;
  payloadType: number;
  sdpFmtpLine?: string;
}

export interface RTCRtpCodingParameters {
  rid?: string;
}

export interface RTCRtpContributingSource {
  audioLevel?: number;
  rtpTimestamp: number;
  source: number;
  timestamp: DOMHighResTimeStamp;
}

export interface RTCRtpEncodingParameters extends RTCRtpCodingParameters {
  active?: boolean;
  maxBitrate?: number;
  priority?: RTCPriorityType;
  scaleResolutionDownBy?: number;
}

export interface RTCRtpHeaderExtensionCapability {
  uri?: string;
}

export interface RTCRtpHeaderExtensionParameters {
  encrypted?: boolean;
  id: number;
  uri: string;
}

export interface RTCRtpParameters {
  codecs: RTCRtpCodecParameters[];
  headerExtensions: RTCRtpHeaderExtensionParameters[];
  rtcp: RTCRtcpParameters;
}

export interface RTCRtpReceiveParameters extends RTCRtpParameters {
}

export interface RTCRtpSendParameters extends RTCRtpParameters {
  degradationPreference?: RTCDegradationPreference;
  encodings: RTCRtpEncodingParameters[];
  transactionId: string;
}

export interface RTCRtpStreamStats extends RTCStats {
  codecId?: string;
  kind: string;
  ssrc: number;
  transportId?: string;
}

export interface RTCRtpSynchronizationSource extends RTCRtpContributingSource {
}

export interface RTCRtpTransceiverInit {
  direction?: RTCRtpTransceiverDirection;
  sendEncodings?: RTCRtpEncodingParameters[];
  streams?: MediaStream[];
}

export interface RTCSentRtpStreamStats extends RTCRtpStreamStats {
  bytesSent?: number;
  packetsSent?: number;
}

export interface RTCSessionDescriptionInit {
  sdp?: string;
  type: RTCSdpType;
}

export interface RTCStats {
  id: string;
  timestamp: DOMHighResTimeStamp;
  type: RTCStatsType;
}

export interface RTCTrackEventInit extends EventInit {
  receiver: RTCRtpReceiver;
  streams?: MediaStream[];
  track: MediaStreamTrack;
  transceiver: RTCRtpTransceiver;
}

export interface RTCTransportStats extends RTCStats {
  bytesReceived?: number;
  bytesSent?: number;
  dtlsCipher?: string;
  dtlsState: RTCDtlsTransportState;
  localCertificateId?: string;
  remoteCertificateId?: string;
  rtcpTransportStatsId?: string;
  selectedCandidatePairId?: string;
  srtpCipher?: string;
  tlsVersion?: string;
}
export interface RTCCertificate {
  readonly expires: EpochTimeStamp;
  getFingerprints(): RTCDtlsFingerprint[];
}

declare var RTCCertificate: {
  prototype: RTCCertificate;
  new(): RTCCertificate;
};

export interface RTCDTMFSenderEventMap {
  "tonechange": RTCDTMFToneChangeEvent;
}

export interface RTCDTMFSender extends EventTarget {
  readonly canInsertDTMF: boolean;
  ontonechange: ((this: RTCDTMFSender, ev: RTCDTMFToneChangeEvent) => any) | null;
  readonly toneBuffer: string;
  insertDTMF(tones: string, duration?: number, interToneGap?: number): void;
  addEventListener<K extends keyof RTCDTMFSenderEventMap>(type: K, listener: (this: RTCDTMFSender, ev: RTCDTMFSenderEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
  addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
  removeEventListener<K extends keyof RTCDTMFSenderEventMap>(type: K, listener: (this: RTCDTMFSender, ev: RTCDTMFSenderEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
  removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

declare var RTCDTMFSender: {
  prototype: RTCDTMFSender;
  new(): RTCDTMFSender;
};

/** Events sent to indicate that DTMF tones have started or finished playing. This interface is used by the tonechange event. */
export interface RTCDTMFToneChangeEvent extends Event {
  readonly tone: string;
}

declare var RTCDTMFToneChangeEvent: {
  prototype: RTCDTMFToneChangeEvent;
  new(type: string, eventInitDict?: RTCDTMFToneChangeEventInit): RTCDTMFToneChangeEvent;
};

export interface RTCDataChannelEventMap {
  "bufferedamountlow": Event;
  "close": Event;
  "closing": Event;
  "error": Event;
  "message": MessageEvent;
  "open": Event;
}

export interface RTCDataChannel extends EventTarget {
  binaryType: BinaryType;
  readonly bufferedAmount: number;
  bufferedAmountLowThreshold: number;
  readonly id: number | null;
  readonly label: string;
  readonly maxPacketLifeTime: number | null;
  readonly maxRetransmits: number | null;
  readonly negotiated: boolean;
  onbufferedamountlow: ((this: RTCDataChannel, ev: Event) => any) | null;
  onclose: ((this: RTCDataChannel, ev: Event) => any) | null;
  onclosing: ((this: RTCDataChannel, ev: Event) => any) | null;
  onerror: ((this: RTCDataChannel, ev: Event) => any) | null;
  onmessage: ((this: RTCDataChannel, ev: MessageEvent) => any) | null;
  onopen: ((this: RTCDataChannel, ev: Event) => any) | null;
  readonly ordered: boolean;
  readonly protocol: string;
  readonly readyState: RTCDataChannelState;
  close(): void;
  send(data: string): void;
  send(data: Blob): void;
  send(data: ArrayBuffer): void;
  send(data: ArrayBufferView): void;
  addEventListener<K extends keyof RTCDataChannelEventMap>(type: K, listener: (this: RTCDataChannel, ev: RTCDataChannelEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
  addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
  removeEventListener<K extends keyof RTCDataChannelEventMap>(type: K, listener: (this: RTCDataChannel, ev: RTCDataChannelEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
  removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

declare var RTCDataChannel: {
  prototype: RTCDataChannel;
  new(): RTCDataChannel;
};

export interface RTCDataChannelEvent extends Event {
  readonly channel: RTCDataChannel;
}

declare var RTCDataChannelEvent: {
  prototype: RTCDataChannelEvent;
  new(type: string, eventInitDict: RTCDataChannelEventInit): RTCDataChannelEvent;
};

export interface RTCDtlsTransportEventMap {
  "error": Event;
  "statechange": Event;
}

export interface RTCDtlsTransport extends EventTarget {
  readonly iceTransport: RTCIceTransport;
  onerror: ((this: RTCDtlsTransport, ev: Event) => any) | null;
  onstatechange: ((this: RTCDtlsTransport, ev: Event) => any) | null;
  readonly state: RTCDtlsTransportState;
  getRemoteCertificates(): ArrayBuffer[];
  addEventListener<K extends keyof RTCDtlsTransportEventMap>(type: K, listener: (this: RTCDtlsTransport, ev: RTCDtlsTransportEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
  addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
  removeEventListener<K extends keyof RTCDtlsTransportEventMap>(type: K, listener: (this: RTCDtlsTransport, ev: RTCDtlsTransportEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
  removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

declare var RTCDtlsTransport: {
  prototype: RTCDtlsTransport;
  new(): RTCDtlsTransport;
};

export interface RTCEncodedAudioFrame {
  data: ArrayBuffer;
  readonly timestamp: number;
  getMetadata(): RTCEncodedAudioFrameMetadata;
}

declare var RTCEncodedAudioFrame: {
  prototype: RTCEncodedAudioFrame;
  new(): RTCEncodedAudioFrame;
};

export interface RTCEncodedVideoFrame {
  data: ArrayBuffer;
  readonly timestamp: number;
  readonly type: RTCEncodedVideoFrameType;
  getMetadata(): RTCEncodedVideoFrameMetadata;
}

declare var RTCEncodedVideoFrame: {
  prototype: RTCEncodedVideoFrame;
  new(): RTCEncodedVideoFrame;
};

export interface RTCError extends DOMException {
  readonly errorDetail: RTCErrorDetailType;
  readonly receivedAlert: number | null;
  readonly sctpCauseCode: number | null;
  readonly sdpLineNumber: number | null;
  readonly sentAlert: number | null;
}

declare var RTCError: {
  prototype: RTCError;
  new(init: RTCErrorInit, message?: string): RTCError;
};

export interface RTCErrorEvent extends Event {
  readonly error: RTCError;
}

declare var RTCErrorEvent: {
  prototype: RTCErrorEvent;
  new(type: string, eventInitDict: RTCErrorEventInit): RTCErrorEvent;
};

/** The RTCIceCandidate interfaceβ€”part of the WebRTC APIβ€”represents a candidate Internet Connectivity Establishment (ICE) configuration which may be used to establish an RTCPeerConnection. */
export interface RTCIceCandidate {
  readonly address: string | null;
  readonly candidate: string;
  readonly component: RTCIceComponent | null;
  readonly foundation: string | null;
  readonly port: number | null;
  readonly priority: number | null;
  readonly protocol: RTCIceProtocol | null;
  readonly relatedAddress: string | null;
  readonly relatedPort: number | null;
  readonly sdpMLineIndex: number | null;
  readonly sdpMid: string | null;
  readonly tcpType: RTCIceTcpCandidateType | null;
  readonly type: RTCIceCandidateType | null;
  readonly usernameFragment: string | null;
  toJSON(): RTCIceCandidateInit;
}

declare var RTCIceCandidate: {
  prototype: RTCIceCandidate;
  new(candidateInitDict?: RTCIceCandidateInit): RTCIceCandidate;
};

export interface RTCIceTransportEventMap {
  "gatheringstatechange": Event;
  "statechange": Event;
}

/** Provides access to information about the ICE transport layer over which the data is being sent and received. */
export interface RTCIceTransport extends EventTarget {
  readonly gatheringState: RTCIceGathererState;
  ongatheringstatechange: ((this: RTCIceTransport, ev: Event) => any) | null;
  onstatechange: ((this: RTCIceTransport, ev: Event) => any) | null;
  readonly state: RTCIceTransportState;
  addEventListener<K extends keyof RTCIceTransportEventMap>(type: K, listener: (this: RTCIceTransport, ev: RTCIceTransportEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
  addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
  removeEventListener<K extends keyof RTCIceTransportEventMap>(type: K, listener: (this: RTCIceTransport, ev: RTCIceTransportEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
  removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

declare var RTCIceTransport: {
  prototype: RTCIceTransport;
  new(): RTCIceTransport;
};

export interface RTCPeerConnectionEventMap {
  "connectionstatechange": Event;
  "datachannel": RTCDataChannelEvent;
  "icecandidate": RTCPeerConnectionIceEvent;
  "icecandidateerror": Event;
  "iceconnectionstatechange": Event;
  "icegatheringstatechange": Event;
  "negotiationneeded": Event;
  "signalingstatechange": Event;
  "track": RTCTrackEvent;
}

/** A WebRTC connection between the local computer and a remote peer. It provides methods to connect to a remote peer, maintain and monitor the connection, and close the connection once it's no longer needed. */
export interface RTCPeerConnection extends EventTarget {
  readonly canTrickleIceCandidates: boolean | null;
  readonly connectionState: RTCPeerConnectionState;
  readonly currentLocalDescription: RTCSessionDescription | null;
  readonly currentRemoteDescription: RTCSessionDescription | null;
  readonly iceConnectionState: RTCIceConnectionState;
  readonly iceGatheringState: RTCIceGatheringState;
  readonly localDescription: RTCSessionDescription | null;
  onconnectionstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null;
  ondatachannel: ((this: RTCPeerConnection, ev: RTCDataChannelEvent) => any) | null;
  onicecandidate: ((this: RTCPeerConnection, ev: RTCPeerConnectionIceEvent) => any) | null;
  onicecandidateerror: ((this: RTCPeerConnection, ev: Event) => any) | null;
  oniceconnectionstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null;
  onicegatheringstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null;
  onnegotiationneeded: ((this: RTCPeerConnection, ev: Event) => any) | null;
  onsignalingstatechange: ((this: RTCPeerConnection, ev: Event) => any) | null;
  ontrack: ((this: RTCPeerConnection, ev: RTCTrackEvent) => any) | null;
  readonly pendingLocalDescription: RTCSessionDescription | null;
  readonly pendingRemoteDescription: RTCSessionDescription | null;
  readonly remoteDescription: RTCSessionDescription | null;
  readonly sctp: RTCSctpTransport | null;
  readonly signalingState: RTCSignalingState;
  addIceCandidate(candidate?: RTCIceCandidateInit): Promise<void>;
  /** @deprecated */
  addIceCandidate(candidate: RTCIceCandidateInit, successCallback: VoidFunction, failureCallback: RTCPeerConnectionErrorCallback): Promise<void>;
  addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender;
  addTransceiver(trackOrKind: MediaStreamTrack | string, init?: RTCRtpTransceiverInit): RTCRtpTransceiver;
  close(): void;
  createAnswer(options?: RTCAnswerOptions): Promise<RTCSessionDescriptionInit>;
  /** @deprecated */
  createAnswer(successCallback: RTCSessionDescriptionCallback, failureCallback: RTCPeerConnectionErrorCallback): Promise<void>;
  createDataChannel(label: string, dataChannelDict?: RTCDataChannelInit): RTCDataChannel;
  createOffer(options?: RTCOfferOptions): Promise<RTCSessionDescriptionInit>;
  /** @deprecated */
  createOffer(successCallback: RTCSessionDescriptionCallback, failureCallback: RTCPeerConnectionErrorCallback, options?: RTCOfferOptions): Promise<void>;
  getConfiguration(): RTCConfiguration;
  getReceivers(): RTCRtpReceiver[];
  getSenders(): RTCRtpSender[];
  getStats(selector?: MediaStreamTrack | null): Promise<RTCStatsReport>;
  getTransceivers(): RTCRtpTransceiver[];
  removeTrack(sender: RTCRtpSender): void;
  restartIce(): void;
  setConfiguration(configuration?: RTCConfiguration): void;
  setLocalDescription(description?: RTCLocalSessionDescriptionInit): Promise<void>;
  /** @deprecated */
  setLocalDescription(description: RTCLocalSessionDescriptionInit, successCallback: VoidFunction, failureCallback: RTCPeerConnectionErrorCallback): Promise<void>;
  setRemoteDescription(description: RTCSessionDescriptionInit): Promise<void>;
  /** @deprecated */
  setRemoteDescription(description: RTCSessionDescriptionInit, successCallback: VoidFunction, failureCallback: RTCPeerConnectionErrorCallback): Promise<void>;
  addEventListener<K extends keyof RTCPeerConnectionEventMap>(type: K, listener: (this: RTCPeerConnection, ev: RTCPeerConnectionEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
  addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
  removeEventListener<K extends keyof RTCPeerConnectionEventMap>(type: K, listener: (this: RTCPeerConnection, ev: RTCPeerConnectionEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
  removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

declare var RTCPeerConnection: {
  prototype: RTCPeerConnection;
  new(configuration?: RTCConfiguration): RTCPeerConnection;
  generateCertificate(keygenAlgorithm: AlgorithmIdentifier): Promise<RTCCertificate>;
};

export interface RTCPeerConnectionIceErrorEvent extends Event {
  readonly address: string | null;
  readonly errorCode: number;
  readonly errorText: string;
  readonly port: number | null;
  readonly url: string;
}

declare var RTCPeerConnectionIceErrorEvent: {
  prototype: RTCPeerConnectionIceErrorEvent;
  new(type: string, eventInitDict: RTCPeerConnectionIceErrorEventInit): RTCPeerConnectionIceErrorEvent;
};

/** Events that occurs in relation to ICE candidates with the target, usually an RTCPeerConnection. Only one event is of this type: icecandidate. */
export interface RTCPeerConnectionIceEvent extends Event {
  readonly candidate: RTCIceCandidate | null;
}

declare var RTCPeerConnectionIceEvent: {
  prototype: RTCPeerConnectionIceEvent;
  new(type: string, eventInitDict?: RTCPeerConnectionIceEventInit): RTCPeerConnectionIceEvent;
};

/** This WebRTC API interface manages the reception and decoding of data for aΒ MediaStreamTrack on anΒ RTCPeerConnection. */
export interface RTCRtpReceiver {
  readonly track: MediaStreamTrack;
  readonly transport: RTCDtlsTransport | null;
  getContributingSources(): RTCRtpContributingSource[];
  getParameters(): RTCRtpReceiveParameters;
  getStats(): Promise<RTCStatsReport>;
  getSynchronizationSources(): RTCRtpSynchronizationSource[];
}

declare var RTCRtpReceiver: {
  prototype: RTCRtpReceiver;
  new(): RTCRtpReceiver;
  getCapabilities(kind: string): RTCRtpCapabilities | null;
};

/** Provides the ability to control and obtain details about how a particular MediaStreamTrack is encoded and sent to a remote peer. */
export interface RTCRtpSender {
  readonly dtmf: RTCDTMFSender | null;
  readonly track: MediaStreamTrack | null;
  readonly transport: RTCDtlsTransport | null;
  getParameters(): RTCRtpSendParameters;
  getStats(): Promise<RTCStatsReport>;
  replaceTrack(withTrack: MediaStreamTrack | null): Promise<void>;
  setParameters(parameters: RTCRtpSendParameters): Promise<void>;
  setStreams(...streams: MediaStream[]): void;
}

declare var RTCRtpSender: {
  prototype: RTCRtpSender;
  new(): RTCRtpSender;
  getCapabilities(kind: string): RTCRtpCapabilities | null;
};

export interface RTCRtpTransceiver {
  readonly currentDirection: RTCRtpTransceiverDirection | null;
  direction: RTCRtpTransceiverDirection;
  readonly mid: string | null;
  readonly receiver: RTCRtpReceiver;
  readonly sender: RTCRtpSender;
  setCodecPreferences(codecs: RTCRtpCodecCapability[]): void;
  stop(): void;
}

declare var RTCRtpTransceiver: {
  prototype: RTCRtpTransceiver;
  new(): RTCRtpTransceiver;
};

export interface RTCSctpTransportEventMap {
  "statechange": Event;
}

export interface RTCSctpTransport extends EventTarget {
  readonly maxChannels: number | null;
  readonly maxMessageSize: number;
  onstatechange: ((this: RTCSctpTransport, ev: Event) => any) | null;
  readonly state: RTCSctpTransportState;
  readonly transport: RTCDtlsTransport;
  addEventListener<K extends keyof RTCSctpTransportEventMap>(type: K, listener: (this: RTCSctpTransport, ev: RTCSctpTransportEventMap[K]) => any, options?: boolean | AddEventListenerOptions): void;
  addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void;
  removeEventListener<K extends keyof RTCSctpTransportEventMap>(type: K, listener: (this: RTCSctpTransport, ev: RTCSctpTransportEventMap[K]) => any, options?: boolean | EventListenerOptions): void;
  removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void;
}

declare var RTCSctpTransport: {
  prototype: RTCSctpTransport;
  new(): RTCSctpTransport;
};

/** One end of a connectionβ€”or potential connectionβ€”and how it's configured. Each RTCSessionDescription consists of a description type indicating which part of the offer/answer negotiation process it describes and of the SDP descriptor of the session. */
export interface RTCSessionDescription {
  readonly sdp: string;
  readonly type: RTCSdpType;
  toJSON(): any;
}

declare var RTCSessionDescription: {
  prototype: RTCSessionDescription;
  new(descriptionInitDict: RTCSessionDescriptionInit): RTCSessionDescription;
};

export interface RTCStatsReport {
  forEach(callbackfn: (value: any, key: string, parent: RTCStatsReport) => void, thisArg?: any): void;
}

declare var RTCStatsReport: {
  prototype: RTCStatsReport;
  new(): RTCStatsReport;
};

export interface RTCTrackEvent extends Event {
  readonly receiver: RTCRtpReceiver;
  readonly streams: ReadonlyArray<MediaStream>;
  readonly track: MediaStreamTrack;
  readonly transceiver: RTCRtpTransceiver;
}

declare var RTCTrackEvent: {
  prototype: RTCTrackEvent;
  new(type: string, eventInitDict: RTCTrackEventInit): RTCTrackEvent;
};

export interface RTCPeerConnectionErrorCallback {
  (error: DOMException): void;
}

export interface RTCSessionDescriptionCallback {
  (description: RTCSessionDescriptionInit): void;
}

type RTCBundlePolicy = "balanced" | "max-bundle" | "max-compat";
type RTCDataChannelState = "closed" | "closing" | "connecting" | "open";
type RTCDegradationPreference = "balanced" | "maintain-framerate" | "maintain-resolution";
type RTCDtlsTransportState = "closed" | "connected" | "connecting" | "failed" | "new";
type RTCEncodedVideoFrameType = "delta" | "empty" | "key";
type RTCErrorDetailType = "data-channel-failure" | "dtls-failure" | "fingerprint-failure" | "hardware-encoder-error" | "hardware-encoder-not-available" | "sctp-failure" | "sdp-syntax-error";
type RTCIceCandidateType = "host" | "prflx" | "relay" | "srflx";
type RTCIceComponent = "rtcp" | "rtp";
type RTCIceConnectionState = "checking" | "closed" | "completed" | "connected" | "disconnected" | "failed" | "new";
type RTCIceCredentialType = "password";
type RTCIceGathererState = "complete" | "gathering" | "new";
type RTCIceGatheringState = "complete" | "gathering" | "new";
type RTCIceProtocol = "tcp" | "udp";
type RTCIceTcpCandidateType = "active" | "passive" | "so";
type RTCIceTransportPolicy = "all" | "relay";
type RTCIceTransportState = "checking" | "closed" | "completed" | "connected" | "disconnected" | "failed" | "new";
type RTCPeerConnectionState = "closed" | "connected" | "connecting" | "disconnected" | "failed" | "new";
type RTCPriorityType = "high" | "low" | "medium" | "very-low";
type RTCRtcpMuxPolicy = "require";
type RTCRtpTransceiverDirection = "inactive" | "recvonly" | "sendonly" | "sendrecv" | "stopped";
type RTCSctpTransportState = "closed" | "connected" | "connecting";
type RTCSdpType = "answer" | "offer" | "pranswer" | "rollback";
type RTCSignalingState = "closed" | "have-local-offer" | "have-local-pranswer" | "have-remote-offer" | "have-remote-pranswer" | "stable";
type RTCStatsIceCandidatePairState = "failed" | "frozen" | "in-progress" | "inprogress" | "succeeded" | "waiting";
type RTCStatsType = "candidate-pair" | "certificate" | "codec" | "csrc" | "data-channel" | "inbound-rtp" | "local-candidate" | "media-source" | "outbound-rtp" | "peer-connection" | "remote-candidate" | "remote-inbound-rtp" | "remote-outbound-rtp" | "track" | "transport";


// nonstandard
export declare var nonstandard: {
  RTCAudioSource: {
    prototype: RTCAudioSource,
    new(): RTCAudioSource
  },
  RTCAudioSink: {
    prototype: RTCAudioSink,
    new(track: MediaStreamTrack): RTCAudioSink
  },
  RTCVideoSource: {
    prototype: RTCVideoSource,
    new(init?: RTCVideoSourceInit): RTCVideoSource
  },
  RTCVideoSink: {
    prototype: RTCVideoSink,
    new(track: MediaStreamTrack): RTCVideoSink
  },
  i420ToRgba(
    i420Frame: { width: number, height: number, data: Uint8ClampedArray },
    rgbaFrame: { width: number, height: number, data: Uint8ClampedArray },
  ): void,
  rgbaToI420(
    i420Frame: { width: number, height: number, data: Uint8ClampedArray },
    rgbaFrame: { width: number, height: number, data: Uint8ClampedArray },
  ): void,
}

export interface RTCAudioSource {
  createTrack(): MediaStreamTrack;
  onData(data: RTCAudioData): void;
}

export interface RTCAudioData {
  samples: Int16Array;
  sampleRate: number;
  bitsPerSample?: 16;
  channelCount?: 1;
  numberOfFrames?: number;
}

export interface RTCAudioSink extends EventTarget {
  stop(): void;
  readonly stopped: boolean;
  ondata: ((this: RTCAudioSink, ev: RTCAudioDataEvent) => any) | null;
  addEventListener(type: "data", listener: DataEventListener | DataEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
  removeEventListener(type: "data", callback: DataEventListener | DataEventListenerObject | null, options?: EventListenerOptions | boolean): void;
}

export interface RTCAudioDataEvent extends RTCAudioData, Event {
  type: 'data';
}

interface DataEventListener extends EventListener {
  (data: RTCAudioDataEvent): void
}

interface DataEventListenerObject extends EventListenerObject {
  handleEvent(evt: RTCAudioDataEvent): void;
}

export interface RTCVideoSourceInit {
  isScreencast?: boolean;
  needsDenoising?: boolean;
}

export interface RTCVideoSource {
  readonly isScreencast: boolean;
  readonly needsDenoising?: boolean;
  createTrack(): MediaStreamTrack;
  onFrame(frame: RTCVideoFrame): void;
}

export interface RTCVideoFrame {
  width: number;
  height: number;
  data: Uint8ClampedArray;
  rotation?: number;
}

export interface RTCVideoSink {
  stop(): void;
  readonly stopped: boolean;
  onframe: ((this: RTCVideoSink, ev: RTCVideoFrameEvent) => any) | null;
  addEventListener(type: "data", listener: FrameEventListener | FrameEventListenerObject | null, options?: boolean | AddEventListenerOptions): void;
  removeEventListener(type: "data", callback: FrameEventListener | FrameEventListenerObject | null, options?: EventListenerOptions | boolean): void;
}

export interface RTCVideoFrameEvent extends Event {
  type: 'frame';
  frame: RTCVideoFrame;
}

interface FrameEventListener extends EventListener {
  (data: RTCVideoFrameEvent): void
}

interface FrameEventListenerObject extends EventListenerObject {
  handleEvent(evt: RTCVideoFrameEvent): void;
}

Feature: μ„œλ²„λŠ” μ μ ˆν•œ μž…μž₯μ½”λ“œλ₯Ό λ°˜ν™˜ν•˜κ³  DB에 μ €μž₯ν•œλ‹€.

Feature Description

μ‚¬μš© 쀑인 μž…μž₯ μ½”λ“œ λ°˜ν™˜μ„ λ°©μ§€ν•˜κΈ° μœ„ν•΄, ν˜„μž¬ μ‚¬μš© 쀑인 μž…μž₯ μ½”λ“œλ₯Ό DBμ—μ„œ κ΄€λ¦¬ν•œλ‹€.

To-Do

  • κ°•μ˜ μ°Έμ—¬ μ½”λ“œ 생성 μ‹œ, DBμ—μ„œ μ‚¬μš© 쀑인 μ°Έμ—¬ μ½”λ“œμΈμ§€ 확인
  • κ°•μ˜ 생성 ν›„, μ°Έμ—¬ μ½”λ“œλ₯Ό DB에 μΆ”κ°€

μΆ”κ°€ 사항

Feature: μ‚¬μš©μžκ°€ μž…λ ₯ν•œ μ½”λ“œκ°€ μœ νš¨ν•œ μ½”λ“œμΈμ§€ ν™•μΈν•œλ‹€.

Feature Description

μ‚¬μš©μžκ°€ μž…λ ₯ν•œ μ½”λ“œκ°€ μœ νš¨ν•œ μ½”λ“œμΈμ§€ ν™•μΈν•œλ‹€.

To-Do

  • κ°•μ˜ μ°Έμ—¬ API, κ°•μ˜ 쑰회 APIμ—μ„œ code 값에 ν•΄λ‹Ήν•˜λŠ” κ°•μ˜κ°€ μžˆλŠ”μ§€ 확인
  • κ°•μ˜κ°€ μ—†λ‹€λ©΄ 404 Not Found μ—λŸ¬ λ°˜ν™˜

Feature: λ°œν‘œμžμ˜ ν™”μ΄νŠΈλ³΄λ“œ μˆ˜μ •μ‚¬ν•­μ„ κ°•μ˜μ‹€ μ°Έμ—¬μžμ—κ²Œ λΈŒλ‘œλ“œμΊμŠ€νŒ…ν•œλ‹€.

Feature Description

μ΄ˆκΈ°μ—λŠ” λ³€κ²½ 둜그 μ „λ‹¬μ΄μ˜€μœΌλ‚˜, ν˜„μž¬λŠ” ν™”μ΄νŠΈλ³΄λ“œμ˜ μ˜μƒμ„ μ „λ‹¬ν•˜λŠ” λ°©μ‹μœΌλ‘œ λ³€κ²½λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
μΆ”ν›„ ν™”μ΄νŠΈλ³΄λ“œ λ³€κ²½ 둜그둜 λ³€κ²½ κ°€λŠ₯μ„± μžˆμŠ΅λ‹ˆλ‹€.

To-Do

  • λ―Έλ””μ–΄ μ„œλ²„μ—μ„œ λ°œν‘œμžμ˜ ν™”μ΄νŠΈλ³΄λ“œ μ˜μƒμ„ μ°Έμ—¬μžμ—κ²Œ μ „λ‹¬ν•œλ‹€.

μΆ”κ°€ 사항

Feature: 404 νŽ˜μ΄μ§€λ₯Ό 확인할 수 있고, λ²„νŠΌμ„ ν΄λ¦­ν•˜μ—¬ 메인 νŽ˜μ΄μ§€λ‘œ 이동할 수 μžˆλ‹€.(UI μž‘μ—… 포함)

Feature Description

404 Error νŽ˜μ΄μ§€ μž‘μ—… #24

To-Do

  • Error νŽ˜μ΄μ§€ UI μž‘μ—… (λ°˜μ‘ν˜•)
  • Error νŽ˜μ΄μ§€ Redirect μž‘μ—…
  • Error νŽ˜μ΄μ§€ λ°”νƒ•μœΌλ‘œ κ°•μ˜ μ’…λ£Œ νŽ˜μ΄μ§€ UI μž‘μ—…

μΆ”κ°€ 사항

Feature: μœ νš¨ν•œ μ½”λ“œλΌλ©΄ κ°•μ˜ 정보와 κ°•μ˜ μ°Έμ—¬ λ²„νŠΌμ΄ μžˆλŠ” νŽ˜μ΄μ§€λ₯Ό 보여주고, κ°•μ˜ μ°Έμ—¬ λ²„νŠΌμ„ 눌러 κ°•μ˜μ‹€μ— μž…μž₯ν•  수 μžˆλ‹€.

Feature Description

μœ νš¨ν•œ μ½”λ“œλΌλ©΄ κ°•μ˜ 정보와 κ°•μ˜ μ°Έμ—¬ λ²„νŠΌμ΄ μžˆλŠ” νŽ˜μ΄μ§€λ₯Ό 보여주고, κ°•μ˜ μ°Έμ—¬ λ²„νŠΌμ„ 눌러 κ°•μ˜μ‹€μ— μž…μž₯ν•  수 μžˆλ‹€.

To-Do

  • API μš”μ²­ 결과에 따라 state둜 titleκ³Ό description을 ν‘œμ‹œ
  • κ°•μ˜ μ°Έμ—¬ λ²„νŠΌ 클릭 μ‹œ, μ˜¬λ°”λ₯Έ κ°•μ˜ μ°Έμ—¬ URL둜 이동

μΆ”κ°€ 사항

2023-12-07.20-57-26.mp4

Feature: ν¬μŠ€νŠΈμž‡μ„ λ”λΈ”ν΄λ¦­ν–ˆμ„ λ•Œ κΈ€μžλ₯Ό νŽΈμ§‘ν•  수 μžˆλ‹€.

Feature Description

  • λ”λΈ”ν΄λ¦­ν•˜μ—¬ λ©”λͺ¨μ˜ ν…μŠ€νŠΈλ₯Ό μˆ˜μ • κ°€λŠ₯ν•˜λ‹€

To-Do

  • 섀계
  • κ΅¬ν˜„

μΆ”κ°€ 사항

οΏ½κΈ€μžκ°€ λŠ˜μ–΄λ‚¨μ— 따라 λ©”λͺ¨μ§€κ°€ λŠ˜μ–΄λ‚˜λ„λ‘ κ΅¬ν˜„μ„ ν•΄μ•Όν•œλ‹€.

Feature: ν¬μŠ€νŠΈμž‡μ˜ 색상, κΈ€κΌ΄ 등을 νŽΈμ§‘ν•  수 μžˆλ‹€.

Feature Description

ν¬μŠ€νŠΈμž‡ 색, κΈ€κΌ΄ νŽΈμ§‘ κΈ°λŠ₯ close #49

To-Do

  • κΈ°μ‘΄ λ©”λͺ¨μ§€κ°€ κ·Έλƒ₯ μ‚¬κ°ν˜•μ΄κΈ° λ•Œλ¬Έμ— ν…μŠ€νŠΈλ₯Ό μž…λ ₯ν•  수 μžˆλ„λ‘ κ΅¬ν˜„(일단 μˆ˜μ •μ€ λΆˆκ°€)
  • νŽΈμ§‘ νŒ¨λ„ UI μž‘μ—…
    Image
    • λ©”λͺ¨ νŽΈμ§‘ νŒ¨λ„ UI μž‘μ—…
    • λ©”λͺ¨μ§€ 색상 λ³€κ²½ νŒ¨λ„ UIμž‘μ—…
    • 각 λ²„νŠΌ 선택 κΈ°λŠ₯ κ΅¬ν˜„
    • 색상 νŒ”λ ˆνŠΈ ν† κΈ€κΈ°λŠ₯κ΅¬ν˜„
    • μΆ”κ°€: 접근성을 μœ„ν•΄ aria-pressed, aria-expanded μΆ”κ°€
  • ν¬μŠ€νŠΈμž‡ 색 λ³€κ²½ κΈ°λŠ₯ κ΅¬ν˜„
default.mov
  • κΈ€κΌ΄(폰트 μ‚¬μ΄μ¦ˆ, 폰트 μ •λ ¬ κΈ°λŠ₯)κ΄€λ ¨ κΈ°λŠ₯ κ΅¬ν˜„
default.mov
  • ν¬μŠ€νŠΈμž‡ μ‚­μ œ κΈ°λŠ₯ κ΅¬ν˜„
default.mov

μΆ”κ°€ 사항

Feature: β€˜λ©”λͺ¨μ§€β€™λ²„νŠΌμ„ λˆ„λ₯Έ ν›„ μ‚¬μš©μžκ°€ ν΄λ¦­ν•œ μœ„μΉ˜μ— λ©”λͺ¨μ§€κ°€ μƒμ„±λœλ‹€.

Feature Description

λ©”λͺ¨μ§€ λ²„νŠΌμ„ λˆ„λ₯Έν›„ ν΄λ¦­ν•œ μœ„μΉ˜μ— λ©”λͺ¨μ§€κ°€ μƒμ„±λœλ‹€.

To-Do

  • λ©”λͺ¨μ§€ λ²„νŠΌμ„ λˆ„λ₯Έν›„ ν΄λ¦­ν•œ μœ„μΉ˜μ— λ©”λͺ¨μ§€κ°€ μƒμ„±λœλ‹€.
  • λ©”λͺ¨μ§€ 생성 ν›„ λͺ¨λ“œλ₯Ό λ°”κΎΌλ‹€.(일단은 선택 λͺ¨λ“œ)
  • κΈ°μ‘΄ λ©”λͺ¨μ§€κ°€ μƒμ„±λ˜μ–΄ μžˆλŠ” μœ„μΉ˜μ—μ„œ μƒˆ λ©”λͺ¨μ§€λ₯Ό μƒμ„±μ‹œ κΈ°μ‘΄ λ©”λͺ¨μ§€κ°€ μ„ νƒλ˜λ˜ 버그 ν•΄κ²°
  • μΆ”κ°€: λ©”λͺ¨ λ²„νŠΌμ„ ν΄λ¦­μ‹œ μ»€μ„œλ₯Ό λ³€κ²½ν•΄ UI에 μ‚¬μš©μžκ°€ λ©”λͺ¨ λ²„νŠΌμ΄ λˆŒλ €μžˆμŒμ„ 확인할 수 있게 ν•œλ‹€.

μΆ”κ°€ 사항

여기에 μž‘μ„±ν•˜μ„Έμš”

Feature: 마우슀둜 ν™”μ΄νŠΈλ³΄λ“œμ— 선을 그릴 수 μžˆλ‹€.

Feature Description

여기에 μž‘μ„±ν•˜μ„Έμš”

To-Do

  • Fabric.js둜 ν™”μ΄λ“œλ³΄λ“œλ₯Ό 생성할 수 μžˆλ‹€.
  • ν™”μ΄νŠΈλ³΄λ“œκ°€ μ‚¬μš©μžμ˜ λΈŒλΌμš°μ € μ°½ 크기에 맞좰 μž¬μ‘°μ • λœλ‹€.
  • ν™”μ΄νŠΈλ³΄λ“œ μ™Όμͺ½ 상단에 νˆ΄λ°”λ₯Ό λ„μ›Œ 놓을 수 μžˆλ‹€.
  • 펜 λ²„νŠΌμ„ 눌러 그리기λͺ¨λ“œλ‘œ μ „ν™˜, 펜 κΈ°λŠ₯을 μ‚¬μš© ν•  수 μžˆλ‹€.
  • 그렀진 펜의 λ‚΄μš©λ“€μ„ 선택 ν›„ delete ν‚€λ₯Ό 눌러 μ‚­μ œν•  수 μžˆλ‹€.

μΆ”κ°€ 사항

여기에 μž‘μ„±ν•˜μ„Έμš”

Feature: κ·œμΉ™μ— λ§žλŠ” 제λͺ©κ³Ό μ„€λͺ…을 μž…λ ₯ν•˜λ©΄ λ²„νŠΌμ„ ν™œμ„±ν™”ν•˜κ³ , ν™œμ„±ν™”λœ λ²„νŠΌμ„ ν΄λ¦­μ‹œ μž…λ ₯값을 μ„œλ²„λ‘œ μ „λ‹¬ν•œλ‹€.

Feature Description

κ·œμΉ™μ— λ§žλŠ” 제λͺ©κ³Ό μ„€λͺ…을 μž…λ ₯ν•˜λ©΄ λ²„νŠΌμ„ ν™œμ„±ν™”ν•˜κ³ , ν™œμ„±ν™”λœ λ²„νŠΌμ„ ν΄λ¦­μ‹œ μž…λ ₯값을 μ„œλ²„λ‘œ μ „λ‹¬ν•œλ‹€.

To-Do

  • 제λͺ©κ³Ό μ„€λͺ… μž…λ ₯ state μ—°κ²°
  • λ²„νŠΌ 클릭 μ‹œμ— API 연동 (νŽ˜μ΄μ§€ 이동)

μΆ”κ°€ 사항

2023-12-07.20-57-26.mp4

Feature: λ°œν‘œμžμ˜ μŒμ„±μ„ λ―Έλ””μ–΄ μ„œλ²„λ‘œ μ „λ‹¬ν•œλ‹€.

Feature Description

여기에 μž‘μ„±ν•˜μ„Έμš”

To-Do

  • λ°œν‘œμžκ°€ λ―Έλ””μ–΄ μ„œλ²„μ™€ μ—°κ²°ν•œλ‹€.
  • λ°œν‘œμžμ˜ μŒμ„±μ„ λ―Έλ””μ–΄ μ„œλ²„μ— μ „λ‹¬ν•œλ‹€.
  • 잘 μ „λ‹¬λ˜μ—ˆλŠ”μ§€ ν™•μΈν•œλ‹€.
  • 헀더 μ»΄ν¬λ„ŒνŠΈμ—μ„œ ν•΄λ‹Ή 역할을 μˆ˜ν–‰ν•˜λ„λ‘ ν•œλ‹€.

μΆ”κ°€ 사항

여기에 μž‘μ„±ν•˜μ„Έμš”

Feature: HTTPS ν”„λ‘œν† μ½œ μ μš©ν•œλ‹€.

Feature Description

배포된 ν”„λ‘œμ νŠΈμ— HTTPS μ μš©μ„ μ§„ν–‰ν–ˆμŠ΅λ‹ˆλ‹€. webRTC μ‚¬μš©ν•˜λŠ” κ³Όμ •μ—μ„œ HTTPS 적용이 ν•„μš”ν•˜λ‹€κ³  νŒλ‹¨ν•΄ λ¨Όμ € ν•΄λ‹Ή μž‘μ—…μ„ μˆ˜ν–‰ν–ˆμŠ΅λ‹ˆλ‹€.

To-Do

  • λ°±μ—”λ“œ μ„œλ²„κ°€ μ‹€ν–‰ 쀑인 ν΄λΌμš°λ“œ μ„œλ²„ λ‚΄μ—μ„œ Nginx μ„€μΉ˜
  • SSL μΈμ¦μ„œ λ°œκΈ‰
  • Nginx둜 HTTP 접근은 HTTPS둜 redirect

μΆ”κ°€ 사항

ν΄λΌμš°λ“œ μ„œλ²„μ—μ„œ μž‘μ—…ν•΄μ€€ λ‚΄μš©μ΄λΌ μ½”λ“œ 상 νŠΉλ³„ν•œ μ—…λ°μ΄νŠΈλŠ” μ—†μŠ΅λ‹ˆλ‹€. μ΄λŒ€λ‘œ issue close ν•˜κ² μŠ΅λ‹ˆλ‹€!

Feature: κ°•μ˜ μ’…λ£Œ μ‹œ ν•΄λ‹Ή κ°•μ˜μ‹€ 정보와 λ°œν‘œμžμ™€ μ°Έμ—¬μžμ˜ μ†ŒμΌ“ 정보λ₯Ό μ œκ±°ν•œλ‹€.

Feature Description

κ°•μ˜ μ’…λ£Œ μ‹œ ν•΄λ‹Ή κ°•μ˜μ‹€ 정보와 λ°œν‘œμžμ™€ μ°Έμ—¬μžμ˜ μ†ŒμΌ“ 정보λ₯Ό μ œκ±°ν•œλ‹€

To-Do

  • λ―Έλ””μ–΄ μ„œλ²„μ—μ„œ ν•΄λ‹Ή κ°•μ˜μ‹€ 정보λ₯Ό μ œκ±°ν•œλ‹€.

μΆ”κ°€ 사항

Feature: κ°•μ˜μ‹€ 생성 μš”μ²­μ„ λ°›μœΌλ©΄, κ°•μ˜ 정보λ₯Ό DB에 μ €μž₯ν•˜κ³  μ°Έμ—¬μ½”λ“œλ₯Ό λ°˜ν™˜ν•œλ‹€.

Feature Description

κ°•μ˜μ‹€ 정보와 ν•¨κ»˜ κ°•μ˜μ‹€ 생성 μš”μ²­μ„ λ°›μœΌλ©΄ DB에 κ°•μ˜μ‹€ 정보λ₯Ό μ €μž₯ν•˜κ³ , κ°•μ˜μ‹€ id와 κ°•μ˜ μ°Έμ—¬ μ½”λ“œλ₯Ό λ°˜ν™˜ν•œλ‹€.

To-Do

  • κ°•μ˜μ‹€ 정보(κ°•μ˜ 제λͺ©, κ°•μ˜ λ‚΄μš©, λ°œν‘œμž)λ₯Ό DB에 μ €μž₯ν•œλ‹€.
  • κ°•μ˜ μ°Έμ—¬ μ½”λ“œλ₯Ό μƒμ„±ν•˜κ³  λ°˜ν™˜ν•œλ‹€.

μΆ”κ°€ 사항

여기에 μž‘μ„±ν•˜μ„Έμš”

Feature: μ„ μ˜ 색상을 λ³€κ²½ν•  수 μžˆλ‹€.

Feature Description

여기에 μž‘μ„±ν•˜μ„Έμš”

To-Do

  • CanvasSection μ»΄ν¬λ„ŒνŠΈμ—μ„œ νˆ΄λ°” μ»΄ν¬λ„ŒνŠΈμ˜ state와 μ‚¬μ΄λ“œμ΄νŽ™νŠΈ 뢄리
  • 펜 색 선택 μ»¬λŸ¬νŒ¨λ„ UI κ΅¬ν˜„
  • recoil둜 fabric.Canvas μΈμŠ€ν„΄μŠ€ μ „μ—­ state둜 관리
  • 체크 UI κ΅¬ν˜„
  • 펜 색 선택 κΈ°λŠ₯ κ΅¬ν˜„

μΆ”κ°€ 사항

CanvasSection μ»΄ν¬λ„ŒνŠΈμ—μ„œ νˆ΄λ°” μ»΄ν¬λ„ŒνŠΈμ˜ state와 μ‚¬μ΄λ“œμ΄νŽ™νŠΈ 뢄리λ₯Ό μ§„ν–‰ν–ˆμŠ΅λ‹ˆλ‹€.

μ»΄ν¬λ„ŒνŠΈμ—μ„œ μ»΄ν¬λ„ŒνŠΈμ™€μ˜ props drilling을 ν•΄κ²°ν•˜κ³  state 결합도 뢄리λ₯Ό μœ„ν•΄ ν•΄λ‹Ή μž‘μ—…μ„ μ§„ν–‰ν–ˆμŠ΅λ‹ˆλ‹€. μ•„λž˜ 사진과 같이 CanvasSection μ»΄ν¬λ„ŒνŠΈμ—μ„œλŠ” activeToolμ΄λΌλŠ” stateλ₯Ό κ°€μ§€κ³ λŠ” μžˆμ§€λ§Œ μ‹€μ§ˆμ μœΌλ‘œ μƒνƒœλ₯Ό set, getν•˜κ³  μžˆμ§€λŠ” μ•Šμ•˜μŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ μΆ”ν›„ 진행할 νˆ΄λ“€μ˜ κΈ°λŠ₯(λ©”λͺ¨μ§€ 생성, 펜 λͺ¨λ“œ ν™œμ„±ν™” λ“±λ“±)의 ν•¨μˆ˜ν™” λ˜λŠ” λͺ¨λ“ˆν™”λ₯Ό ν†΅ν•œ κ΄€μ‹¬μ‚¬μ˜ 뢄리와 props drilling을 λ°©μ§€ν•˜κΈ° μœ„ν•΄ state의 뢄리λ₯Ό μ§„ν–‰ν–ˆμŠ΅λ‹ˆλ‹€.

image

펜 색 선택 λͺ¨λ‹¬ UI κ΅¬ν˜„

ezgif com-video-to-gif (11)

Feature: 메인 νŽ˜μ΄μ§€ κ°•μ˜ 생성/μ°Έμ—¬ν•˜κΈ° μΉ΄λ“œλ₯Ό μ‚¬μš©μžκ°€ 확인할 수 μžˆλ‹€ (UI μž‘μ—…)

Feature Description

메인 νŽ˜μ΄μ§€ κΈ°λ³Έ UI μž‘μ—… close #16

To-Do

  • 메인 νŽ˜μ΄μ§€ κΈ°λ³Έ UI ꡬ성
  • 메인 νŽ˜μ΄μ§€ μΉ΄λ“œ flip μ• λ‹ˆλ©”μ΄μ…˜ κ΅¬ν˜„
  • 메인 νŽ˜μ΄μ§€ μƒˆλ‘œμš΄ κ°•μ˜ μΉ΄λ“œ λ’·λ©΄ κ΅¬ν˜„
  • 메인 νŽ˜μ΄μ§€ κ°•μ˜ μ°Έμ—¬ν•˜κΈ° μΉ΄λ“œ λ’·λ©΄ κ΅¬ν˜„

μΆ”κ°€ 사항

μΉ΄λ“œ flip μ• λ‹ˆλ©”μ΄μ…˜ κ΅¬ν˜„

메인 νŽ˜μ΄μ§€μ—μ„œ μΉ΄λ“œλ₯Ό λ’€μ§‘λŠ” μ• λ‹ˆλ©”μ΄μ…˜μ„ κ΅¬ν˜„ν•˜κΈ° μœ„ν•΄ λ‹€μŒκ³Ό 같은 방법을 μ‚¬μš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

<div id="wrapper">
  <div id="card">
    <div id="front"/>
    <div id="back"/>
  </div>
</div>
  • wrapper : μΉ΄λ“œμ˜ μ‚¬μ΄μ¦ˆλ₯Ό κ²°μ •ν•©λ‹ˆλ‹€. ν•΄λ‹Ή μš”μ†Œμ˜ 크기λ₯Ό κΈ°μ€€μœΌλ‘œ μΉ΄λ“œ 크기λ₯Ό κ²°μ •ν•©λ‹ˆλ‹€. μΆ”κ°€μ μœΌλ‘œ ν•΄λ‹Ή μš”μ†Œλ₯Ό hoverμ‹œμ— ν•˜μœ„μ˜ cardλ₯Ό 뒀집도둝 ν•  수 μžˆμŠ΅λ‹ˆλ‹€. (card μš”μ†Œλ₯Ό hoverμ‹œμ— 뒀집도둝 ν•  경우, λ’€μ§‘ν•˜λŠ” κ³Όμ •μ—μ„œ ν•΄λ‹Ή μš”μ†Œμ˜ hoverκ°€ μ •μƒμ μœΌλ‘œ μž‘λ™ν•˜μ§€ μ•Šμ„ κ°€λŠ₯성이 있음)
  • card : ν•΄λ‹Ή μš”μ†Œ 내뢀에 front와 back이 겹쳐 μ‘΄μž¬ν•˜μ—¬ 이λ₯Ό λ’€μ§‘λŠ” κ²ƒμœΌλ‘œ μΉ΄λ“œλ₯Ό λ’€μ§‘λŠ” μ• λ‹ˆλ©”μ΄μ…˜μ„ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

각 idλ³„λ‘œ ν•„μš”ν•œ css 속성은 μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

.wrapper {
  // μš”μ†Œμ˜ 크기λ₯Ό μ—¬κΈ°μ„œ κ²°μ •
  perspective: 1000px;
}

.card {
  width: 100%;
  height: 100%;
  position: relative;
  transition: all 0.5s;
  perspective-origin: center;
  transform-style: preserve-3d;
}

.front {
  width: 100%;
  height: 100%;
  z-index: 2;
  position: absolute;
  backface-visibility: hidden;
}

.back {
  width: 100%;
  height: 100%;
  z-index: 1;
  transform: rotateY(180deg);
}
  • μƒμœ„μ— relativeλ₯Ό μ§€μ •ν•˜κ³  ν•˜μœ„ μš”μ†Œ 2κ°œμ— absoluteλ₯Ό μ§€μ •ν•˜λŠ” κ²ƒμœΌλ‘œ μΉ΄λ“œκ°€ κ²ΉμΉ˜λ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • back은 transform: rotateY(180deg);λ₯Ό μ μš©ν•˜μ—¬ λ’€μ§‘νžŒ μƒνƒœλ‘œ 겹쳐지도둝 ν•˜μ˜€μœΌλ©°, μΉ΄λ“œκ°€ λ’€μ§‘ν˜”μ„ λ•Œ μ •μƒμ μœΌλ‘œ 보이기 μœ„ν•΄ ν•΄λ‹Ή 속성을 μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • frontκ°€ back에 λΉ„ν•΄ μ•žμ— μœ„μΉ˜ν•΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ— z-indexλ₯Ό λ‹¬λ¦¬ν•˜μ—¬ frontκ°€ 더 높은 값을 가지도둝 ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • front에 backface-visibility: hidden;속성을 μ§€μ •ν•˜μ—¬ λ’€μ§‘μ—ˆμ„ λ•Œ, 뒷면이 front의 뒷면이 보이지 μ•Šλ„λ‘ ν•˜μ—¬ back이 μ •μƒμ μœΌλ‘œ ν‘œμ‹œλ  수 μžˆλ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
  • μΉ΄λ“œκ°€ λ’€μ§‘νžˆλŠ” μ• λ‹ˆλ©”μ΄μ…˜μ„ μ‚¬μ‹€μ μœΌλ‘œ ν‘œν˜„ν•˜κΈ° μœ„ν•΄ transform-style: preserve-3d;λ₯Ό card에 μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€. ν•΄λ‹Ή 속성은 ν•΄λ‹Ή μ»¨ν…Œμ΄λ„ˆλ₯Ό 3D κ³΅κ°„μ—μ„œ λ Œλ”λ§ν•˜λ„λ‘ μ§€μ •ν•©λ‹ˆλ‹€.
  • κ΄€λ ¨ν•˜μ—¬ 원근감을 쀄 수 μžˆλŠ” perspective: 1000px;λ₯Ό μ§€μ •ν•˜μ˜€μŠ΅λ‹ˆλ‹€. perspective의 값이 높을 수둝 λ”μš± λ©€λ¦¬μ„œ λ³΄λŠ” 효과λ₯Ό 쀄 수 μžˆμŠ΅λ‹ˆλ‹€.
  • perspective와 같이 μ‚¬μš©λ˜λŠ” perspective-origin: center;λŠ” perspective의 기쀀점을 μ„€μ •ν•΄μ€λ‹ˆλ‹€. 쀑앙을 κΈ°μ€€μœΌλ‘œ 효과λ₯Ό 쀄 수 μžˆλ„λ‘ μΆ”κ°€ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

μ—¬κΈ°μ„œ μš”μ†Œ λ‚΄λΆ€μ˜ νŠΉμ • λ²„νŠΌμ„ ν΄λ¦­ν•˜κ±°λ‚˜ μš”μ†Œλ₯Ό hoverν•  κ²½μš°μ— card에 μ•„λž˜ 속성을 μΆ”κ°€ν•˜λŠ” κ²ƒμœΌλ‘œ μΉ΄λ“œλ₯Ό 뒀집을 수 μžˆμŠ΅λ‹ˆλ‹€.

// wrapper hover μ‹œλ‚˜ νŠΉμ • 이벀트 λ°œμƒ μ‹œ μΉ΄λ“œ 뒀집기
transform: rotateY(180deg);

ν•΄λ‹Ή ꡬ쑰λ₯Ό μ‹œκ°ν™”ν•˜λ©΄ μ•„λž˜μ™€ κ°™μŠ΅λ‹ˆλ‹€.

image

메인 νŽ˜μ΄μ§€μ—λŠ” μΉ΄λ“œκ°€ 2개 μ‘΄μž¬ν•˜λ©°, 각 μΉ΄λ“œλ³„λ‘œ stateλ₯Ό 두어 λ’€μ§‘ν˜”λŠ”μ§€λ₯Ό νŒλ‹¨ν•˜μ˜€μŠ΅λ‹ˆλ‹€. ν•œ μͺ½μ΄ λ’€μ§‘νžŒλ‹€λ©΄, λ‹€λ₯Έ ν•œ μͺ½μ€ λ‹€μ‹œ μ•žλ©΄μ„ 보이도둝 ν•˜λ©°, κ΄€λ ¨λœ state듀을 μ΄ˆκΈ°ν™”ν•˜λ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

κ°•μ˜ μ½”λ“œ μž…λ ₯μ°½ κ΅¬ν˜„

총 6자리의 숫자둜 κ΅¬μ„±λœ κ°•μ˜ μ½”λ“œλ₯Ό μž…λ ₯λ°›κΈ° μœ„ν•΄ 총 6개의 input을 μΆ”κ°€ν•˜μ—¬ 이λ₯Ό κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. type을 number둜 지정할 경우, 기본값이 0으둜 지정될 λΏλ”λŸ¬ 각 inputλ§ˆλ‹€μ˜ μž…λ ₯ 자릿수λ₯Ό μ œν•œν•  수 μ—†κΈ° λ•Œλ¬Έμ— 이λ₯Ό μœ„ν•΄ string으둜 μ§€μ •ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

<div className="w-full h-16 flex flex-row justify-between gap-2 mt-2">
      {inputs.map((value, index) => (
        <input
          key={index}
          type="string"
          maxLength={1}
          value={value}
          onChange={handleChange(index)}
          onKeyDown={handleBackspace(index)}
          ref={(element) => (inputRefs.current[index] = element)}
          className="border-black flex-grow w-full rounded-xl text-center align-middle medium-32"
        />
      ))}
    </div>

각 μžλ¦¬λ³„λ‘œ 숫자λ₯Ό μž…λ ₯ν–ˆμ„ κ²½μš°μ— λ‹€μŒ input으둜 λ„˜μ–΄κ°€λ„λ‘ μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄ indexλ₯Ό ν™œμš©ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
const [inputs, setInputs] = useState<string[]>(Array(6).fill(""));

 const handleChange = (index: number) => (e: React.ChangeEvent<HTMLInputElement>) => {
    const value = e.target.value;

    if (value === "" || /^[0-9]$/i.test(value)) {
      const newInputs = [...inputs];
      newInputs[index] = value;
      setInputs(newInputs);

      if (value && index < inputs.length - 1) {
        inputRefs.current[index + 1]?.focus();
      }
    }
  };

  const handleBackspace = (index: number) => (e: React.KeyboardEvent<HTMLInputElement>) => {
    if (e.key === "Backspace" && !inputs[index] && index > 0) {
      inputRefs.current[index - 1]?.focus();
    }
  };

λ°°μ—΄μ˜ ν˜•νƒœλ‘œ stateλ₯Ό κ΄€λ¦¬ν•˜μ—¬ 각 μžλ¦¬μ— μž…λ ₯된 값을 λ°˜μ˜ν•˜μ˜€μŠ΅λ‹ˆλ‹€. μž…λ ₯λ˜μ—ˆμ„ 경우, λ‹€μŒ index에 μœ„μΉ˜ν•˜λŠ” input에 focusλ˜λ„λ‘ μ²˜λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€. λ°˜λŒ€λ‘œ Backspaceκ°€ μž…λ ₯된 경우, 이전 index에 focusλ˜λ„λ‘ μ²˜λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

focus μ‹œμ— inputλ°•μŠ€ ν…Œλ‘λ¦¬ λ³€κ²½

tailwind의 λ™μΌν•œ 속성과 κ΄€λ ¨ν•˜μ—¬ μ€‘λ³΅ν•΄μ„œ 지정할 경우, μ •μƒμ μœΌλ‘œ μž‘λ™ν•˜μ§€ μ•ŠλŠ” κ²½μš°κ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ•„λž˜μ™€ 같은 λ°©μ‹μœΌλ‘œ ν•΄κ²°ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

<input
            type="text"
            className="rounded-xl border-black w-full flex-grow medium-12 p-3 focus:outline-none focus:ring-1 focus:ring-boarlog-100 focus:border-transparent"
            placeholder="κ°•μ˜ 제λͺ©μ„ μž…λ ₯ν•΄μ£Όμ„Έμš”"
            maxLength={50}
          />
  • border-black으둜 κΈ°λ³Έ border의 색상을 μ§€μ •ν•©λ‹ˆλ‹€.
  • focusλ˜μ—ˆμ„ 경우, μ•„λž˜ 속성이 μ μš©λ©λ‹ˆλ‹€.
  • outline-none : focusμ‹œμ˜ λΈŒλΌμš°μ €μ˜ κΈ°λ³Έ outline을 μ œκ±°ν•©λ‹ˆλ‹€.
  • ring-1, ring-boarlog-100 : focus μ‹œμ— ring을 μΆ”κ°€ν•˜κ³  색상을 μ§€μ •ν•©λ‹ˆλ‹€.
  • border-transparent : μš”μ†Œκ°€ focus될 λ•Œ ν…Œλ‘λ¦¬λ₯Ό 투λͺ…ν•˜κ²Œ μ²˜λ¦¬ν•©λ‹ˆλ‹€.

border λŒ€μ‹ μ— ring만 ν‘œμ‹œν•˜λŠ” κ²ƒμœΌλ‘œ focusμ‹œμ— border 색상이 λ³€κ²½λ˜λŠ” κ²ƒμ²˜λŸΌ μ²˜λ¦¬ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

μΉ΄λ“œ flip μ• λ‹ˆλ©”μ΄μ…˜

2023-11-28.11-53-54.mp4

κ°•μ˜ μ½”λ“œ μž…λ ₯

2023-11-28.11-54-09.mp4

inputμ°½ focus

2023-11-28.11-54-27.mp4

Feature: μ‚¬μš©μžκ°€ μ½”λ“œλ₯Ό μž…λ ₯(6자리)ν•˜λ©΄ κ°•μ˜ μ‘°νšŒν•˜κΈ° λ²„νŠΌμ„ ν™œμ„±ν™”ν•˜κ³ , 이λ₯Ό ν΄λ¦­ν•˜λ©΄ ν•΄λ‹Ή μ½”λ“œλ‘œ μ„œλ²„μ— κ°•μ˜ 정보 μš”μ²­μ„ 보낸닀.

Feature Description

μ‚¬μš©μžκ°€ μ½”λ“œλ₯Ό μž…λ ₯(6자리)ν•˜λ©΄ κ°•μ˜ μ‘°νšŒν•˜κΈ° λ²„νŠΌμ„ ν™œμ„±ν™”ν•˜κ³ , 이λ₯Ό ν΄λ¦­ν•˜λ©΄ ν•΄λ‹Ή μ½”λ“œλ‘œ μ„œλ²„μ— κ°•μ˜ 정보 μš”μ²­μ„ 보낸닀.

To-Do

  • μž…λ ₯받은 κ°•μ˜μ½”λ“œ λ°”νƒ•μœΌλ‘œ API μš”μ²­

μΆ”κ°€ 사항

2023-12-07.20-57-26.mp4

Feature: μ„œλ²„λŠ” ꡬ글 계정 정보λ₯Ό ν™œμš©ν•˜μ—¬ ꡬ글 ν”„λ‘œν•„ 이미지와 λ‹‰λ„€μž„μ„ μ „λ‹¬ν•œλ‹€.

Feature Description

OAuthλ₯Ό ν™œμš©ν•œ ꡬ글 둜그인 κ΅¬ν˜„μ„ μœ„ν•΄μ„œ ꡬ글 μ‚¬μš©μž 정보λ₯Ό κ°€μ Έμ˜€λŠ” κΈ°λŠ₯을 κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€. μ‚¬μš©μžκ°€ 졜초 둜그인 ν•œ νšŒμ›μ΄λΌλ©΄ νšŒμ›κ°€μž…μ„ μ§„ν–‰μ‹œν‚€κΈ° μœ„ν•΄ νšŒμ›κ°€μž… κΈ°λŠ₯도 μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€.

To-Do

  • ꡬ글 λ‘œκ·ΈμΈμ„ ν†΅ν•œ μ‚¬μš©μž 정보 κ°€μ Έμ˜€κΈ°
  • 졜초 둜그인 ν•œ μœ μ €λ₯Ό μœ„ν•œ νšŒμ›κ°€μž… κΈ°λŠ₯
  • νšŒμ›κ°€μž…μ„ μœ„ν•œ DB μ—°κ²° 및 User μŠ€ν‚€λ§ˆ 생성

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.