UIコンポーネントの生成サービス v0 (+ shadcn/ui TailwindCSS Next.js Supabase)

## v0で私が使っている技術、ツール

shadcn/ui
TailwindCSS
Next.js
Supabase

👆これ用のコンポーネントを作ってもらっています。

v0は他にも認証などのSupabase機能に対応予定



# v0

https://v0.dev/chat

無料の利用可能 (2024年10月19日 現在)

現在は1回のチャットで複数のファイルが生成可能です。
プロンプトをどう書くかの工夫が必要です。

## リセット時間

世界標準時0時 = 日本標準時午前9時

無料の場合、1日10回まで会話(プロンプトの送信)が出来ます。
これは、リセット時間で回復します。



## 有料機能

大幅に会話の回数が大幅に増えます。
(何画面もあるWebアプリで利用する場合課金したほうが便利です。)

プロジェクト単位でUIの生成が可能です。



## 左サイドのメニューとチャット画面とプレビュー画面とコード画面

v0は
左サイドのメニュー
チャット画面
プレビュー画面
コード画面
の4つに分かれています。

### 左サイドのメニュー
* トップ画面へ
* 新規チャット
* チャット履歴
* プロジェクト一覧 (有料機能)
* フィードバック (運営への報告)

### チャット画面

ここでプロンプトを送信します。

プロンプト、チャットによっては複数のファイルが一度に出来ることがあります。

:::note info
コードの切り替えはここで表示されるファイルのボタンを押します。
:::


新しく更新されたコードは **v1 -> v2 -> v3** と上がっていきます。


### プレビュー画面 ( タブで切り替え ＝＞ コード画面 )

文字入力などの操作は出来ます。
インタラクティブなドラッグアンドドロップのような操作はできません。


### コード画面 ( タブで切り替え ＝＞ プレビュー画面 )


:::note info
コード画面上では、コードを直接編集することが可能です。
変更したら「保存ボタン」を押してください。
:::



## ドキュメント v0.dev docs

`https://v0.dev/docs`

https://v0.dev/docs

## Explore - v0

 👇他の人の公開している制作物

`https://v0.dev/explore`

https://v0.dev/explore




AIと相談しながらTailwindCSSで装飾されたフロントエンドを作るのを手伝ってくれます。
無料でも毎日クレジットが復活するので少しつづ進めていけます。
うまくすれば複雑なものでも1日1コンポーネント、簡単なものなら(手動でやったほうが早いが)3つぐらい出来ます。

本当に便利ですが本格的に使うとなると課金が必要です。



https://x.com/v0/status/1840944045964869898

v0 では、Supabase、Drizzle、Prisma に関する質問に答えられるようになりました。

v0を日本語で利用できるようになりました。

https://x.com/v0/status/1841313318101037391

v0はあくまでも静的なUIレイアウトとスタイルを表示に特化しています。

ドラッグアンドドロップのようなインタラクティブな機能は、実際のブラウザ環境でJavaScriptが動作することで初めて動作の確認が出来ます。

文字を入力して、リストに追加し、それを表示する程度のことは出来ました。

既存のコンポーネントのコードを貼り付けて指示をして編集することも出来ます。



## 現在の私の使い方

Webアプリの設計書を書きます。
Supabaseのテーブル設計書を書きます。

https://database.build/

👆ここでAIにテーブル設計書を書いてもらいます。

その設計書をv0にアップロードします。

Webアプリの画面設計をします。

画面設計に沿ってページ単位で作るように指示します。

もしも複雑な画面の場合は
最小単位の画面設計をし直し、もしくは共通のコンポーネントを切り出して設計します。
その単位でコンポーネントを作ってもらって
コンポーネントを組み合わせて画面をつくります。

以下、繰り返します。



## 使う時に気をつけること

事前に設定書を書いておいて、v0に教えておくことでAIの生成物に反映してくれます。
例えば2つのコンポーネントを作る時、事前に共通の名前や設定(propsや配列、テーブル名等)にしておくことでDBに保存する時など便利です。

※v0はUIの生成のみに利用したほうが良いので、
設計書や名前の設定は👇他のAIで作っておくと良いと思います。

https://qiita.com/masakinihirota/items/dc260e90f43a833d0e98




## v0に聞いた共有できるコンポーネントの作り方。

質問：
Next.jsとSupabaseを利用します。 Next.jsで利用するコンポーネントをv0に生成してもらう時にSupabaseのテーブルを共有したいと思います。その場合テーブルのデータはどうしたら同じにして生成してもらえますか？

<details><summary>回答：</summary>

はい、Next.jsとSupabaseを利用する際に、Supabaseのテーブル構造を共有してコンポーネントを生成することは可能です。以下の方法で実現できます：

1. Supabaseのテーブル構造をSQL形式で共有する：
テーブルの作成に使用したSQLコマンドを提供してください。これにより、テーブルの正確な構造を把握できます。
2. テーブル構造に基づいてTypeScriptの型定義を作成する：
SQLの構造から、TypeScriptの型定義を作成します。これにより、型安全性を確保しつつ、正確なデータ構造を反映したコンポーネントを生成できます。
3. 型定義を使用してNext.jsコンポーネントを生成する：
作成した型定義を使用して、Next.jsのコンポーネントを生成します。


例えば、以下のようなSQLでテーブルを作成したとします：

```sql
CREATE TABLE users (
  id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
  username TEXT NOT NULL UNIQUE,
  email TEXT NOT NULL UNIQUE,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
```

このSQLを共有していただければ、以下のようなTypeScript型定義を作成し、それに基づいてコンポーネントを生成できます：

```typescript
type User = {
  id: string;
  username: string;
  email: string;
  created_at: string;
};
```

そして、この型定義を使用してNext.jsコンポーネントを生成します。例えば：

```typescriptreact
import { useState, useEffect } from 'react'
import { createClient } from '@supabase/supabase-js'

// Supabaseクライアントの初期化
const supabase = createClient('YOUR_SUPABASE_URL', 'YOUR_SUPABASE_ANON_KEY')

type User = {
  id: string;
  username: string;
  email: string;
  created_at: string;
};

export default function UserList() {
  const [users, setUsers] = useState<User[]>([])

  useEffect(() => {
    async function fetchUsers() {
      const { data, error } = await supabase
        .from('users')
        .select('*')

      if (error) {
        console.error('Error fetching users:', error)
      } else {
        setUsers(data || [])
      }
    }

    fetchUsers()
  }, [])

  return (
    <div>
      <h1>ユーザーリスト</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.username} ({user.email}) - 作成日: {new Date(user.created_at).toLocaleDateString()}
          </li>
        ))}
      </ul>
    </div>
  )
}
```

このようにして、Supabaseのテーブル構造を共有し、それに基づいてTypeScriptの型定義を作成し、その型定義を使用してNext.jsコンポーネントを生成することができます。テーブル構造を共有する際は、CREATE TABLE文やテーブルのスキーマ情報を提供していただければ、それに基づいて適切なコンポーネントを生成できます。

</details>




## v0で生成したコンポーネントからSupabaseで利用できるSQL文を作るには？

普通にv0に質問すれば、SQL文を出力してくれます。


## v0が読み込めるファイル数は？

複数のファイルをアップロードして読み込ませることが出来ます。



## v0はUIのコンポーネントを作るのに特化している

v0はUIのコンポーネントを作るのに特化しています、なのでロジックを考えさせたりテストを書かせたりすることも出来と言えば出来るのですが、それだったらVSCodeのGitHub Copilot(有料)やCursor(有料)で自分で直接コードを書いたほうが良いと思います。

軽く土台を作ってもらったらVSCode等で編集したほうが良いと思います。



## Tips

v0は通常のUIを作成するとダミーデータが2,3個だったり、0個だったりします。
なので見栄えを確認するためにダミーデータを追加するようお願いします。

例
20のダミーデータを追加してください。




## Supabaseとの連結

v0はSupabaseと連携しているので、v0に提案をすれば対応してくれます。

プロンプトと出力の例

### プロンプト

>Next.jsとSupabaseを使います。 Supabaseのクライアントを作っておいてください。 UIは自分の所持している漫画作品を表示します。データはSupabaseからフェッチして、UIで更新したり、削除できるようにしてください。Supabaseに登録済みのダミーデータは10作品ほど追加してください。

#### 出力

まず、`lib/supabase.ts`ファイルを作成し、Supabaseクライアントを設定します：

```lib/supabase.ts
import { createClient } from '@supabase/supabase-js'

const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!

export const supabase = createClient(supabaseUrl, supabaseAnonKey)

```

次に、漫画作品を管理するためのコンポーネントを作成します：

```components/manga-list.tsx
'use client'

import { useState, useEffect } from 'react'
import { supabase } from '../lib/supabase'
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { Pencil, Trash2 } from "lucide-react"

type Manga = {
  id: number
  title: string
  author: string
}

export default function MangaList() {
  const [mangas, setMangas] = useState<Manga[]>([])
  const [newManga, setNewManga] = useState({ title: '', author: '' })
  const [editingId, setEditingId] = useState<number | null>(null)

  useEffect(() => {
    fetchMangas()
  }, [])

  async function fetchMangas() {
    const { data, error } = await supabase
      .from('mangas')
      .select('*')
      .order('id', { ascending: true })
    if (error) console.error('エラー:', error)
    else setMangas(data || [])
  }

  async function addManga() {
    const { data, error } = await supabase
      .from('mangas')
      .insert([newManga])
      .select()
    if (error) console.error('エラー:', error)
    else {
      setMangas([...mangas, ...data])
      setNewManga({ title: '', author: '' })
    }
  }

  async function updateManga(id: number) {
    const mangaToUpdate = mangas.find(manga => manga.id === id)
    if (!mangaToUpdate) return

    const { error } = await supabase
      .from('mangas')
      .update({ title: mangaToUpdate.title, author: mangaToUpdate.author })
      .eq('id', id)
    if (error) console.error('エラー:', error)
    else {
      setEditingId(null)
      fetchMangas()
    }
  }

  async function deleteManga(id: number) {
    const { error } = await supabase
      .from('mangas')
      .delete()
      .eq('id', id)
    if (error) console.error('エラー:', error)
    else {
      setMangas(mangas.filter(manga => manga.id !== id))
    }
  }

  return (
    <div className="container mx-auto p-4">
      <h1 className="text-2xl font-bold mb-4">私の漫画コレクション</h1>
      <div className="grid gap-4 mb-4">
        <Input
          placeholder="タイトル"
          value={newManga.title}
          onChange={(e) => setNewManga({ ...newManga, title: e.target.value })}
        />
        <Input
          placeholder="作者"
          value={newManga.author}
          onChange={(e) => setNewManga({ ...newManga, author: e.target.value })}
        />
        <Button onClick={addManga}>漫画を追加</Button>
      </div>
      <div className="grid gap-4">
        {mangas.map(manga => (
          <Card key={manga.id}>
            <CardHeader>
              <CardTitle>{editingId === manga.id ?
                <Input
                  value={manga.title}
                  onChange={(e) => setMangas(mangas.map(m => m.id === manga.id ? { ...m, title: e.target.value } : m))}
                /> :
                manga.title}
              </CardTitle>
            </CardHeader>
            <CardContent>
              {editingId === manga.id ?
                <Input
                  value={manga.author}
                  onChange={(e) => setMangas(mangas.map(m => m.id === manga.id ? { ...m, author: e.target.value } : m))}
                /> :
                <p>{manga.author}</p>}
            </CardContent>
            <CardFooter className="flex justify-between">
              {editingId === manga.id ? (
                <Button onClick={() => updateManga(manga.id)}>更新</Button>
              ) : (
                <Button onClick={() => setEditingId(manga.id)}><Pencil className="mr-2 h-4 w-4" />編集</Button>
              )}
              <Button variant="destructive" onClick={() => deleteManga(manga.id)}><Trash2 className="mr-2 h-4 w-4" />削除</Button>
            </CardFooter>
          </Card>
        ))}
      </div>
    </div>
  )
}

```

このコンポーネントは、漫画のリストを表示し、新しい漫画の追加、既存の漫画の編集と削除ができるようになっています。

最後に、Supabaseにダミーデータを追加するためのSQLクエリを作成します：

```dummy-data.sql
INSERT INTO mangas (title, author) VALUES
('進撃の巨人', '諫山創'),
('ワンピース', '尾田栄一郎'),
('鬼滅の刃', '吾峠呼世晴'),
('ナルト', '岸本斉史'),
('ドラゴンボール', '鳥山明'),
('ハンターハンター', '冨樫義博'),
('ジョジョの奇妙な冒険', '荒木飛呂彦'),
('ブリーチ', '久保帯人'),
('スラムダンク', '井上雄彦'),
('デスノート', '大場つぐみ・小畑健');

```

このSQLクエリをSupabaseのSQL Editorで実行することで、10作品のダミーデータが追加されます。

これで、Next.jsとSupabaseを使用した漫画作品管理UIの基本的な実装が完了しました。このコンポーネントを適切なページに配置することで、ユーザーは自分の所持している漫画作品を表示、更新、削除することができます。

※RLSなども頼めば書いてくれるはず・・・



# Supabaseの追加

```チャット欄
Add Supabase.

```

v0のチャット欄に入力する。
そうするとインストール方法の指示が出てくる。

必要なテーブルの情報とかSQL文とかも。
(生成したコンポーネントがDBへの保存を必要としていた場合)




# Fork

分岐

例えば、住所を表示するUIを生成してもらった場合、
これまでの情報をそのままで住所を入力するUIを生成したいとします。

そこでFork機能を使って分岐をさせて新しいUI生成画面に分けます。

そうすることで、入力用のUIと出力用のUI共通のフォーマットを用いたUIが生成できます。



# 参考

Using v0 and Supabase to build a CRM app with AI - YouTube

https://www.youtube.com/watch?v=jOcG9NgtoGM

私の使用しているAI関連サイト  #GEMINI - Qiita

https://qiita.com/masakinihirota/items/dc260e90f43a833d0e98



# v0に聞いてみた。 追記 2024年10月22日

## v0でのデザインの指定の仕方はどうやってやればいいの？

v0でのデザイン指定は、主に自然言語を使って行います。以下に、デザインを指定する際の一般的なアプローチをいくつか紹介します：

具体的なコンポーネントを要求する： 例えば、「青いボタンを作成してください」や「ナビゲーションバーを作成してください」のように指定できます。

レイアウトを説明する： 「3列のグリッドレイアウトを作成してください」のように、全体的なレイアウトを指定できます。

スタイルを詳細に指定する： 「角が丸い、影付きのカードコンポーネントを作成してください」のように、具体的なスタイルを指定できます。

特定のデザインシステムやフレームワークを指定する： v0はデフォルトでshadcn/uiコンポーネントを使用しますが、「Material-UIスタイルのボタンを作成してください」のように特定のデザインシステムを参照することもできます。

レスポンシブデザインを要求する： 「モバイルとデスクトップの両方に対応したレスポンシブなヘッダーを作成してください」のように指定できます。



## 色はどんな指定ができるの？

v0では、色の指定に関して柔軟なオプションがあります。主に以下の方法で色を指定できます：

### セマンティックカラー：
v0はデフォルトでTailwind CSSのセマンティックカラーシステムを使用しています。これらは意味を持つ色名で、以下のように指定できます：

* `bg-primary`: プライマリカラーの背景
* `text-secondary`: セカンダリカラーのテキスト
* `border-accent`: アクセントカラーの境界線



### 具体的な色名：
一般的な色名を使用して指定することもできます。例えば：

* `bg-red-500`: 中程度の赤色の背景
* `text-blue-700`: 濃い青色のテキスト
* `border-green-300`: 薄い緑色の境界線



### カスタムカラー：
特定のカラーコードを使用したい場合は、以下のように指定できます：

* `bg-[#ff0000]`: 赤色の背景（16進数コード）
* `text-[rgb(0,255,0)]`: 緑色のテキスト（RGB値）
* `border-[hsl(240,100%,50%)]`: 青色の境界線（HSL値）





具体例として、これらの色指定を使用したボタンコンポーネントを作成してみましょう：

`typescriptreact project="Color Buttons" file="color-buttons.tsx"`

このコンポーネントでは、セマンティックカラー、Tailwindの色名、そしてカスタムカラーコードを使用しています。

v0では、これらの色指定方法を組み合わせて使用することができます。また、`hover:`、`focus:`、`active:` などの擬似クラスを使用して、インタラクション時の色変更も指定できます。

色のコントラストや可読性を考慮することも重要です。v0は自動的にアクセシビリティを考慮しますが、特定の色の組み合わせを要求する場合は、それらが十分なコントラスト比を持っていることを確認してください。

カラーパレットやテーマの作成に関する具体的な質問がありましたら、お気軽にお聞きください。



## Supabaseのテーブルに合わせたUIを作りたい時はどうすればいいの？

Supabaseのテーブルに合わせたUIを作成する際の一般的なアプローチを説明します。

例として、ユーザー情報を管理するテーブルがあると仮定しましょう。

### 1 テーブル構造の反映:
まず、Supabaseのテーブル構造をReactコンポーネントに反映させます。

例えば、ユーザーテーブルが以下のような構造だとします：

```table
id: UUID
name: string
email: string
created_at: timestamp

```


### 2 データの取得と表示:
Supabaseクライアントを使用してデータを取得し、UIに表示します。以下は基本的な実装例です：


`typescriptreact project="Supabase User Table" file="user-table.tsx"`

### 3 CRUD操作の実装:
データの作成、更新、削除機能を追加するには、フォームコンポーネントと対応するSupabase関数を実装します。以下は新規ユーザー追加フォームの例です：


`typescriptreact project="Supabase User Form" file="user-form.tsx"`

### 4 セキュリティとパフォーマンスの考慮事項:

1. Supabaseの行レベルセキュリティ（RLS）を使用して、データアクセスを制御します。
2. 大量のデータを扱う場合は、ページネーションや無限スクロールを実装します。
3. センシティブな操作（更新、削除など）には確認ダイアログを追加します。





これらのコンポーネントを組み合わせることで、Supabaseのテーブルに合わせた基本的なUIを作成できます。

実際の実装では、エラーハンドリング、ローディング状態の管理、そしてユーザー認証なども考慮する必要があります。



また、Supabaseの提供するリアルタイム機能を活用すれば、データの変更をリアルタイムでUIに反映させることもできます。

具体的なテーブル構造やUIの要件がありましたら、それに合わせてさらに詳細なガイダンスを提供できます。



## SupabaseのRLSも作ってくれるの？

はい、v0はSupabaseのRow Level Security (RLS)ポリシーの作成をサポートできます。

RLSは、データベースレベルでセキュリティを実装する強力な方法です。以下に、RLSポリシーの作成方法と、それをUIと組み合わせる方法を説明します。

まず、簡単なRLSポリシーの例を見てみましょう。ユーザーテーブルに対して、ユーザーが自分のデータのみを見ることができるポリシーを作成します：

`sql project="Supabase RLS Policy" file="user_policy.sql"`

このSQLスクリプトは、ユーザーテーブルに対して2つのRLSポリシーを作成します：

1. ユーザーが自分のデータのみを閲覧できるポリシー
2. ユーザーが自分のデータのみを更新できるポリシー


次に、このRLSポリシーを考慮したReactコンポーネントを作成しましょう。このコンポーネントは、ログインしているユーザーの情報のみを表示し、編集を許可します：

`typescriptreact project="Supabase User Profile" file="user-profile.tsx"`

このコンポーネントは以下のように動作します：

1. ユーザーがログインすると、`supabase.auth.getUser()`で現在のユーザー情報を取得します。
2. ユーザーのIDを使用して、`users`テーブルから該当するユーザーの情報を取得します。
3. RLSポリシーにより、ユーザーは自分のデータのみを取得・更新できます。
4. ユーザーがフォームを送信すると、`update`クエリが実行されます。RLSポリシーにより、ユーザーは自分のデータのみを更新できます。


このアプローチにより、アプリケーションレベルとデータベースレベルの両方でセキュリティが確保されます。RLSを使用することで、誤ってクライアントサイドでセキュリティチェックを忘れた場合でも、データベースレベルで保護されます。