URL 단축기 만들기 - 12. URL 단축기 UI 만들어보기

2022년 12월 21일

이번에는 프론트엔드 코드만 다룰 거에요. 그중에서도 메인페이지네요.

메인 페이지 만들기

대충 간단하게 메인페이지 코드를 만들어 줬어요. 나중에 단축 결과를 표시하는 창도 만들어 줘야해요.

import {
  Box,
  Button,
  Container,
  Stack,
  TextField,
  Typography,
} from '@mui/material'
import React, { FormEvent } from 'react'

export const MainPage: React.FC = () => {
  const [url, setUrl] = React.useState('')

  const onSubmit = React.useCallback(
    (ev: FormEvent<HTMLFormElement>) => {
      ev.preventDefault()

      alert(url)
    },
    [url],
  )

  return (
    <Box>
      <Container>
        <Stack
          sx={{ minHeight: '100vh', py: 8 }}
          direction="column"
          justifyContent="center"
        >
          <Typography variant="h2" textAlign="center" fontWeight={800}>
            URL 단축기
          </Typography>
          <Stack
            sx={{ mt: 2 }}
            direction="row"
            spacing={2}
            component="form"
            onSubmit={onSubmit}
          >
            <TextField
              value={url}
              onChange={(e) => setUrl(e.target.value)}
              type="url"
              required
              sx={{ flexGrow: 1 }}
              label="단축할 URL을 입력해 주세요."
            />
            <Button
              type="submit"
              variant="contained"
              disableElevation
              sx={{ px: 3 }}
            >
              단축하기
            </Button>
          </Stack>
        </Stack>
      </Container>
    </Box>
  )
}

이제 API에 요청을 보내볼 거에요. axios를 설치해 볼게요.

import axios from 'axios'

export const api = axios.create()
api.tsx

이제 요청을 보내볼게요.

const { data } = await api.post<UrlCreateResponse>('/urls', { url })

이거만으론 부족하니 UI를 좀 더 쓰고 에러 처리도 한번 해볼게요.

+ 여기서 LoadingButton 컴포넌트 사용을 위해 @mui/lab 패키지를 설치했어요.

import {
  Alert,
  Box,
  Button,
  Container,
  Dialog,
  DialogActions,
  DialogContent,
  DialogTitle,
  Stack,
  TextField,
  Tooltip,
  Typography,
} from '@mui/material'
import React, { FormEvent } from 'react'
import { LoadingButton } from '@mui/lab'
import { api } from '../api'
import { UrlCreateResponse } from '../types'

export const MainPage: React.FC = () => {
  const [url, setUrl] = React.useState('')

  const [processing, setProcessing] = React.useState(false)

  const [error, setError] = React.useState<string | null>(null)

  const [createdSlug, setCreatedSlug] = React.useState<string | null>(null)

  const shortenedUrl = React.useMemo(() => {
    if (!createdSlug) return ''

    return `${window.location.protocol}//${window.location.host}/${createdSlug}`
  }, [createdSlug])

  const onSubmit = React.useCallback(
    async (ev: FormEvent<HTMLFormElement>) => {
      ev.preventDefault()

      setProcessing(true)
      setError(null)

      try {
        const { data } = await api.post<UrlCreateResponse>('/urls', { url })

        setCreatedSlug(data.slug)
      } catch (e) {
        setError(`URL 단축 실패: ${e}`)
      } finally {
        setProcessing(false)
      }
    },
    [url],
  )

  return (
    <Box>
      <Dialog open={!!createdSlug} fullWidth maxWidth="xs">
        <DialogTitle>URL 단축이 완료되었습니다</DialogTitle>
        <DialogContent>
          <TextField
            fullWidth
            variant="standard"
            label="단축된 URL"
            InputProps={{
              readOnly: true,
              onClick: (e) => {
                ;(e.target as HTMLInputElement).select()
              },
            }}
            value={shortenedUrl}
          />
          <Button
            sx={{ mt: 2 }}
            onClick={() => setCreatedSlug(null)}
            fullWidth
            variant="contained"
            disableElevation
          >
            닫기
          </Button>
        </DialogContent>
      </Dialog>
      <Container>
        <Stack
          sx={{ minHeight: '100vh', py: 8 }}
          direction="column"
          justifyContent="center"
        >
          <Typography variant="h2" textAlign="center" fontWeight={800}>
            URL 단축기
          </Typography>
          <Stack
            sx={{ mt: 2 }}
            direction="row"
            spacing={2}
            component="form"
            onSubmit={onSubmit}
          >
            <TextField
              value={url}
              onChange={(e) => setUrl(e.target.value)}
              type="url"
              required
              disabled={processing}
              sx={{ flexGrow: 1 }}
              label="단축할 URL을 입력해 주세요."
            />
            <LoadingButton
              type="submit"
              variant="contained"
              disableElevation
              loading={processing}
              sx={{ px: 3 }}
            >
              단축하기
            </LoadingButton>
          </Stack>
          {error && (
            <Alert sx={{ mt: 2 }} severity="error">
              {error}
            </Alert>
          )}
        </Stack>
      </Container>
    </Box>
  )
}

결과물이에요.

이정도면 UI는 완성이겠죠? 그럼 오늘은 여기까지...

GitHub - paringparing/url-shortener: something
something. Contribute to paringparing/url-shortener development by creating an account on GitHub.

파링

바보