Laravel 고객 견적 요청 폼 - 자동 이메일 발송
quote-request-form-laravel
문제 설명
프로젝트 개요
소규모 비즈니스(인테리어, 웹 개발, 디자인 등)를 운영하는 창업자를 위한 고객 견적 요청 폼을 만듭니다. 고객이 웹사이트에서 서비스 종류, 예산, 연락처 등을 입력하면 자동으로 관리자 이메일로 전송되고, 고객에게는 감사 메시지가 표시됩니다.
Laravel 13.x + Blade 템플릿 + Tailwind CSS로 구성하며, SQLite 데이터베이스에 견적 요청 내역을 저장하고, Laravel의 Mail 기능으로 이메일을 발송합니다. 프로그래밍 초보자도 AI 도구와 함께라면 충분히 완성할 수 있는 실용적인 프로젝트입니다.
필수 기능
-
견적 요청 폼 페이지
- 고객 이름, 이메일, 전화번호 입력
- 서비스 종류 선택 (드롭다운: 웹 개발, 앱 개발, 디자인, 마케팅 등)
- 예산 범위 선택 (라디오 버튼: 100만원 미만, 100
300만원, 300500만원, 500만원 이상) - 프로젝트 설명 (텍스트 영역)
- 제출 버튼
-
데이터 저장 및 검증
- Laravel Validation으로 필수 항목 검증 (이메일 형식, 전화번호 형식 등)
- SQLite 데이터베이스에 견적 요청 저장 (quotes 테이블)
- 제출 시간 자동 기록
-
자동 이메일 발송
- 관리자 이메일로 견적 요청 내용 발송 (Laravel Mail + Mailtrap/SMTP)
- 이메일 본문에 고객 정보, 서비스 종류, 예산, 프로젝트 설명 포함
-
감사 페이지
- 제출 완료 후 "견적 요청이 접수되었습니다. 빠른 시일 내에 연락드리겠습니다." 메시지 표시
- 홈으로 돌아가기 버튼
-
견적 요청 목록 페이지 (관리자용)
- 모든 견적 요청을 테이블 형태로 표시 (이름, 이메일, 서비스 종류, 예산, 날짜)
- 최신 순으로 정렬
- 각 항목 클릭 시 상세 내용 모달 팝업
보너스 기능 (선택)
- 견적 요청 폼에 파일 첨부 기능 (참고 자료 업로드)
- 견적 요청 상태 관리 (대기 중, 검토 중, 완료)
- 고객에게도 자동 확인 이메일 발송
- 다크 모드 지원
- 견적 요청 통계 (서비스 종류별, 예산 범위별 차트)
기술 스택
- 백엔드: Laravel 13.x
- 프론트엔드: Blade 템플릿, Tailwind CSS
- 데이터베이스: SQLite
- 이메일: Laravel Mail (Mailtrap 또는 Gmail SMTP)
학습 목표
- Laravel 라우팅, 컨트롤러, 모델 기본 구조 이해
- Blade 템플릿 문법과 컴포넌트 사용법 익히기
- Laravel Validation으로 폼 데이터 검증하기
- Laravel Mail로 이메일 발송 기능 구현하기
- Tailwind CSS로 반응형 UI 디자인하기
- SQLite 데이터베이스와 마이그레이션 활용하기
제약 조건
- • Laravel 13.x 버전 사용 필수
- • Blade 템플릿 엔진 사용 (React/Vue 등 SPA 프레임워크 금지)
- • Tailwind CSS로 스타일링 (CDN 또는 npm 설치)
- • SQLite 데이터베이스 사용 (.env 파일에서 DB_CONNECTION=sqlite 설정)
- • Laravel Mail 기능 사용 (Mailtrap 또는 Gmail SMTP 설정)
- • 견적 요청 폼은 POST 메서드로 제출
- • CSRF 토큰 필수 (@csrf 디렉티브 사용)
- • 반응형 디자인 (모바일, 태블릿, 데스크탑 대응)
- • Laravel Validation 규칙 사용 (required, email, max, min 등)
- • 성공 메시지는 세션 플래시 메시지 또는 리다이렉트로 표시
프롬프트 레시피
아래 프롬프트를 순서대로 AI에게 보내면 됩니다. 복사 버튼을 눌러 바로 사용하세요.
mkdir quote-request-form-laravel && cd quote-request-form-laravel
이 폴더에서 Laravel 13.x 프로젝트를 생성하고 기본 설정을 완료합니다.
1. Laravel 13.x 프로젝트 생성:
```bash
composer create-project laravel/laravel . "^13.0"
```
2. .env 파일에서 데이터베이스를 SQLite로 설정:
```
DB_CONNECTION=sqlite
# DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD 주석 처리
```
3. database/database.sqlite 파일 생성:
```bash
touch database/database.sqlite
```
4. Tailwind CSS 설치 및 설정:
```bash
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
```
5. tailwind.config.js 파일 수정:
```javascript
content: [
"./resources/**/*.blade.php",
"./resources/**/*.js",
],
```
6. resources/css/app.css 파일에 Tailwind 디렉티브 추가:
```css
@tailwind base;
@tailwind components;
@tailwind utilities;
```
7. npm 빌드 실행:
```bash
npm install && npm run build
```
8. 개발 서버 실행:
```bash
php artisan serve
```
프로젝트가 http://localhost:8000 에서 실행되고, Laravel 기본 화면이 표시되면 성공입니다.
기대 결과: Laravel 13.x 프로젝트가 생성되고, SQLite 데이터베이스와 Tailwind CSS가 설정되었습니다. 개발 서버가 실행 중이며, 브라우저에서 Laravel 기본 화면을 확인할 수 있습니다.
견적 요청 데이터를 저장할 데이터베이스 테이블과 모델을 생성합니다.
1. Quote 모델과 마이그레이션 파일 생성:
```bash
php artisan make:model Quote -m
```
2. database/migrations/xxxx_create_quotes_table.php 파일을 열고 up() 메서드 수정:
```php
public function up()
{
Schema::create('quotes', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email');
$table->string('phone');
$table->string('service_type');
$table->string('budget_range');
$table->text('description');
$table->timestamps();
});
}
```
3. 마이그레이션 실행:
```bash
php artisan migrate
```
4. app/Models/Quote.php 파일을 열고 fillable 속성 추가:
```php
protected $fillable = [
'name',
'email',
'phone',
'service_type',
'budget_range',
'description',
];
```
마이그레이션이 성공적으로 실행되고, database/database.sqlite 파일에 quotes 테이블이 생성되었는지 확인하세요.
기대 결과: Quote 모델과 quotes 테이블이 생성되었습니다. 테이블에는 name, email, phone, service_type, budget_range, description, timestamps 컬럼이 포함되어 있습니다.
견적 요청 폼 페이지를 Blade 템플릿으로 만들고, 라우트와 컨트롤러를 설정합니다.
1. QuoteController 생성:
```bash
php artisan make:controller QuoteController
```
2. app/Http/Controllers/QuoteController.php 파일을 열고 다음 메서드 추가:
```php
public function create()
{
return view('quotes.create');
}
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|max:255',
'email' => 'required|email|max:255',
'phone' => 'required|max:20',
'service_type' => 'required',
'budget_range' => 'required',
'description' => 'required|max:1000',
]);
Quote::create($validated);
return redirect()->route('quotes.thanks')->with('success', '견적 요청이 접수되었습니다.');
}
public function thanks()
{
return view('quotes.thanks');
}
```
3. routes/web.php 파일에 라우트 추가:
```php
use App\Http\Controllers\QuoteController;
Route::get('/', [QuoteController::class, 'create'])->name('quotes.create');
Route::post('/quotes', [QuoteController::class, 'store'])->name('quotes.store');
Route::get('/thanks', [QuoteController::class, 'thanks'])->name('quotes.thanks');
```
4. resources/views/quotes 폴더 생성 후 create.blade.php 파일 생성:
```blade
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>견적 요청</title>
@vite('resources/css/app.css')
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-8 max-w-2xl">
<h1 class="text-3xl font-bold text-center mb-8 text-gray-800">견적 요청하기</h1>
<form action="{{ route('quotes.store') }}" method="POST" class="bg-white shadow-md rounded-lg p-6">
@csrf
<div class="mb-4">
<label class="block text-gray-700 font-semibold mb-2">이름 *</label>
<input type="text" name="name" value="{{ old('name') }}" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
@error('name')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
</div>
<div class="mb-4">
<label class="block text-gray-700 font-semibold mb-2">이메일 *</label>
<input type="email" name="email" value="{{ old('email') }}" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
@error('email')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
</div>
<div class="mb-4">
<label class="block text-gray-700 font-semibold mb-2">전화번호 *</label>
<input type="text" name="phone" value="{{ old('phone') }}" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
@error('phone')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
</div>
<div class="mb-4">
<label class="block text-gray-700 font-semibold mb-2">서비스 종류 *</label>
<select name="service_type" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>
<option value="">선택하세요</option>
<option value="웹 개발" {{ old('service_type') == '웹 개발' ? 'selected' : '' }}>웹 개발</option>
<option value="앱 개발" {{ old('service_type') == '앱 개발' ? 'selected' : '' }}>앱 개발</option>
<option value="디자인" {{ old('service_type') == '디자인' ? 'selected' : '' }}>디자인</option>
<option value="마케팅" {{ old('service_type') == '마케팅' ? 'selected' : '' }}>마케팅</option>
<option value="기타" {{ old('service_type') == '기타' ? 'selected' : '' }}>기타</option>
</select>
@error('service_type')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
</div>
<div class="mb-4">
<label class="block text-gray-700 font-semibold mb-2">예산 범위 *</label>
<div class="space-y-2">
<label class="flex items-center">
<input type="radio" name="budget_range" value="100만원 미만" {{ old('budget_range') == '100만원 미만' ? 'checked' : '' }} class="mr-2" required>
100만원 미만
</label>
<label class="flex items-center">
<input type="radio" name="budget_range" value="100~300만원" {{ old('budget_range') == '100~300만원' ? 'checked' : '' }} class="mr-2">
100~300만원
</label>
<label class="flex items-center">
<input type="radio" name="budget_range" value="300~500만원" {{ old('budget_range') == '300~500만원' ? 'checked' : '' }} class="mr-2">
300~500만원
</label>
<label class="flex items-center">
<input type="radio" name="budget_range" value="500만원 이상" {{ old('budget_range') == '500만원 이상' ? 'checked' : '' }} class="mr-2">
500만원 이상
</label>
</div>
@error('budget_range')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
</div>
<div class="mb-6">
<label class="block text-gray-700 font-semibold mb-2">프로젝트 설명 *</label>
<textarea name="description" rows="5" class="w-full px-4 py-2 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" required>{{ old('description') }}</textarea>
@error('description')<p class="text-red-500 text-sm mt-1">{{ $message }}</p>@enderror
</div>
<button type="submit" class="w-full bg-blue-600 text-white font-semibold py-3 rounded-lg hover:bg-blue-700 transition duration-200">
견적 요청 제출하기
</button>
</form>
</div>
</body>
</html>
```
5. resources/views/quotes/thanks.blade.php 파일 생성:
```blade
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>감사합니다</title>
@vite('resources/css/app.css')
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-16 max-w-2xl text-center">
<div class="bg-white shadow-md rounded-lg p-8">
<svg class="w-16 h-16 text-green-500 mx-auto mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path>
</svg>
<h1 class="text-3xl font-bold text-gray-800 mb-4">견적 요청이 접수되었습니다!</h1>
<p class="text-gray-600 mb-6">빠른 시일 내에 연락드리겠습니다. 감사합니다.</p>
<a href="{{ route('quotes.create') }}" class="inline-block bg-blue-600 text-white font-semibold px-6 py-3 rounded-lg hover:bg-blue-700 transition duration-200">
홈으로 돌아가기
</a>
</div>
</div>
</body>
</html>
```
브라우저에서 http://localhost:8000 을 열어 견적 요청 폼이 표시되는지 확인하고, 폼을 제출하면 감사 페이지로 이동하는지 테스트하세요.
기대 결과: 견적 요청 폼 페이지가 Tailwind CSS로 스타일링되어 표시됩니다. 폼을 제출하면 데이터가 검증되고 데이터베이스에 저장된 후, 감사 페이지로 리다이렉트됩니다. 검증 오류가 있으면 에러 메시지가 표시됩니다.
견적 요청이 제출되면 관리자에게 이메일을 자동으로 발송하는 기능을 추가합니다.
1. .env 파일에서 메일 설정 (Mailtrap 사용 예시):
```
MAIL_MAILER=smtp
MAIL_HOST=sandbox.smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=your_mailtrap_username
MAIL_PASSWORD=your_mailtrap_password
MAIL_ENCRYPTION=tls
MAIL_FROM_ADDRESS="noreply@example.com"
MAIL_FROM_NAME="${APP_NAME}"
ADMIN_EMAIL=admin@example.com
```
(Mailtrap 계정이 없다면 https://mailtrap.io 에서 무료 가입 후 SMTP 설정 정보를 가져오세요)
2. QuoteSubmitted Mailable 클래스 생성:
```bash
php artisan make:mail QuoteSubmitted
```
3. app/Mail/QuoteSubmitted.php 파일을 열고 다음과 같이 수정:
```php
<?php
namespace App\Mail;
use App\Models\Quote;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
class QuoteSubmitted extends Mailable
{
use Queueable, SerializesModels;
public function __construct(public Quote $quote)
{
}
public function envelope(): Envelope
{
return new Envelope(
subject: '새로운 견적 요청이 도착했습니다',
);
}
public function content(): Content
{
return new Content(
view: 'emails.quote-submitted',
);
}
}
```
4. resources/views/emails 폴더 생성 후 quote-submitted.blade.php 파일 생성:
```blade
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background-color: #2563eb; color: white; padding: 20px; text-align: center; }
.content { background-color: #f9fafb; padding: 20px; }
.field { margin-bottom: 15px; }
.label { font-weight: bold; color: #4b5563; }
.value { color: #1f2937; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>새로운 견적 요청</h1>
</div>
<div class="content">
<p>새로운 견적 요청이 접수되었습니다.</p>
<div class="field">
<span class="label">이름:</span>
<span class="value">{{ $quote->name }}</span>
</div>
<div class="field">
<span class="label">이메일:</span>
<span class="value">{{ $quote->email }}</span>
</div>
<div class="field">
<span class="label">전화번호:</span>
<span class="value">{{ $quote->phone }}</span>
</div>
<div class="field">
<span class="label">서비스 종류:</span>
<span class="value">{{ $quote->service_type }}</span>
</div>
<div class="field">
<span class="label">예산 범위:</span>
<span class="value">{{ $quote->budget_range }}</span>
</div>
<div class="field">
<span class="label">프로젝트 설명:</span>
<div class="value">{{ $quote->description }}</div>
</div>
<div class="field">
<span class="label">요청 시간:</span>
<span class="value">{{ $quote->created_at->format('Y-m-d H:i:s') }}</span>
</div>
</div>
</div>
</body>
</html>
```
5. QuoteController의 store() 메서드를 수정하여 이메일 발송 추가:
```php
use App\Mail\QuoteSubmitted;
use Illuminate\Support\Facades\Mail;
public function store(Request $request)
{
$validated = $request->validate([
'name' => 'required|max:255',
'email' => 'required|email|max:255',
'phone' => 'required|max:20',
'service_type' => 'required',
'budget_range' => 'required',
'description' => 'required|max:1000',
]);
$quote = Quote::create($validated);
// 관리자에게 이메일 발송
Mail::to(env('ADMIN_EMAIL'))->send(new QuoteSubmitted($quote));
return redirect()->route('quotes.thanks')->with('success', '견적 요청이 접수되었습니다.');
}
```
견적 요청 폼을 다시 제출해보고, Mailtrap 받은편지함에서 이메일이 도착했는지 확인하세요.
기대 결과: 견적 요청이 제출되면 관리자 이메일(ADMIN_EMAIL)로 견적 요청 내용이 포함된 이메일이 자동으로 발송됩니다. Mailtrap에서 이메일을 확인할 수 있으며, 이메일에는 고객 정보, 서비스 종류, 예산 범위, 프로젝트 설명이 포함되어 있습니다.
관리자가 모든 견적 요청을 확인할 수 있는 목록 페이지를 만듭니다.
1. QuoteController에 index() 메서드 추가:
```php
public function index()
{
$quotes = Quote::latest()->get();
return view('quotes.index', compact('quotes'));
}
```
2. routes/web.php 파일에 라우트 추가:
```php
Route::get('/admin/quotes', [QuoteController::class, 'index'])->name('quotes.index');
```
3. resources/views/quotes/index.blade.php 파일 생성:
```blade
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>견적 요청 목록</title>
@vite('resources/css/app.css')
</head>
<body class="bg-gray-100">
<div class="container mx-auto px-4 py-8">
<div class="flex justify-between items-center mb-6">
<h1 class="text-3xl font-bold text-gray-800">견적 요청 목록</h1>
<a href="{{ route('quotes.create') }}" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700 transition duration-200">
새 견적 요청
</a>
</div>
@if($quotes->isEmpty())
<div class="bg-white shadow-md rounded-lg p-8 text-center">
<p class="text-gray-500">아직 견적 요청이 없습니다.</p>
</div>
@else
<div class="bg-white shadow-md rounded-lg overflow-hidden">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">이름</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">이메일</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">서비스</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">예산</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">요청일</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">상세</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach($quotes as $quote)
<tr class="hover:bg-gray-50">
<td class="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{{ $quote->name }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $quote->email }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $quote->service_type }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $quote->budget_range }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">{{ $quote->created_at->format('Y-m-d') }}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm">
<button onclick="showModal({{ $quote->id }})" class="text-blue-600 hover:text-blue-800 font-semibold">
보기
</button>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
<!-- 모달 -->
<div id="modal" class="hidden fixed inset-0 bg-gray-600 bg-opacity-50 overflow-y-auto h-full w-full">
<div class="relative top-20 mx-auto p-5 border w-11/12 md:w-3/4 lg:w-1/2 shadow-lg rounded-md bg-white">
<div class="flex justify-between items-center mb-4">
<h3 class="text-xl font-bold text-gray-800">견적 요청 상세</h3>
<button onclick="closeModal()" class="text-gray-400 hover:text-gray-600">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div id="modal-content" class="space-y-4">
<!-- 동적으로 내용 추가 -->
</div>
</div>
</div>
<script>
const quotes = @json($quotes);
function showModal(quoteId) {
const quote = quotes.find(q => q.id === quoteId);
if (!quote) return;
const content = `
<div class="border-b pb-3">
<p class="text-sm text-gray-500">이름</p>
<p class="text-gray-900 font-semibold">${quote.name}</p>
</div>
<div class="border-b pb-3">
<p class="text-sm text-gray-500">이메일</p>
<p class="text-gray-900">${quote.email}</p>
</div>
<div class="border-b pb-3">
<p class="text-sm text-gray-500">전화번호</p>
<p class="text-gray-900">${quote.phone}</p>
</div>
<div class="border-b pb-3">
<p class="text-sm text-gray-500">서비스 종류</p>
<p class="text-gray-900">${quote.service_type}</p>
</div>
<div class="border-b pb-3">
<p class="text-sm text-gray-500">예산 범위</p>
<p class="text-gray-900">${quote.budget_range}</p>
</div>
<div class="border-b pb-3">
<p class="text-sm text-gray-500">프로젝트 설명</p>
<p class="text-gray-900">${quote.description}</p>
</div>
<div>
<p class="text-sm text-gray-500">요청 시간</p>
<p class="text-gray-900">${new Date(quote.created_at).toLocaleString('ko-KR')}</p>
</div>
`;
document.getElementById('modal-content').innerHTML = content;
document.getElementById('modal').classList.remove('hidden');
}
function closeModal() {
document.getElementById('modal').classList.add('hidden');
}
// 모달 외부 클릭 시 닫기
document.getElementById('modal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
</script>
</body>
</html>
```
브라우저에서 http://localhost:8000/admin/quotes 를 열어 견적 요청 목록이 표시되는지 확인하고, '보기' 버튼을 클릭하면 모달 팝업으로 상세 내용이 표시되는지 테스트하세요.
기대 결과: 관리자용 견적 요청 목록 페이지가 생성됩니다. 모든 견적 요청이 테이블 형태로 표시되며, 각 항목의 '보기' 버튼을 클릭하면 모달 팝업으로 상세 내용(이름, 이메일, 전화번호, 서비스 종류, 예산 범위, 프로젝트 설명, 요청 시간)이 표시됩니다. 반응형 디자인이 적용되어 모바일에서도 잘 보입니다.
제출된 작품 (0)
로그인 후 제출아직 제출된 작품이 없습니다. 첫 번째 도전자가 되어보세요!