みなさん、こんにちは!今回は Create.xyz(以下 Create)と、バックエンド側の機能をノーコードで構築できる人気のサービス Xano を連携していきます。
Xano は過去の記事でも何度か取り上げてきましたが、本記事ではフロントエンド側を Create、バックエンド側を Xano で構築したアプリを作成していきます。なお、Xano については、以前の記事「ncScale を使ったノーコードアプリの管理 Xano 編」で構築したデータベースと API を再利用していきたいと思います。
前述の記事では、フロントエンド側を Bubble で作成していて、今回は Bubble の部分を Create に置き換えるイメージとなります。その為、Xano 側の構築についての詳細は、前述の記事も併せてご参考いただければと思います。
また、2024/9/24のアップデートで、チャットで対話しながらアプリを生成できるようになりました!基本はシンプルな応答ですが、要求すればしっかり説明もしてくれるので、確認と調整がしやすくなっています。今までと同じプロンプトモードも利用できます。(なお本記事はアップデート前のバージョンを使用して作成しています。)
create’s new chat assistant
— Marcus Lowe (@failingbuild) 2024年9月24日
now, anyone can create software:
• chat to build
• ask questions
• everything from UI to APIs, dbs and AI
• instant changes w/ fast AI code gen
• live on the web in 1 click
available now at https://t.co/6TFMATcobU pic.twitter.com/mbGwGrxU27
1. 作成するアプリ
作成するアプリは、簡単なタスク管理を行うアプリです。サインアップ/ログインができて、タスク一覧の表示、タスクの新規作成、更新、削除を行うことができます。
2. Xano の設定
冒頭でお伝えした通り Xano のデータベースと API については、以下の記事を併せてご参考ください。
なお、簡単に補足しておきますと、Xano で構築しているのは以下の内容となっています。
2.1. データベース
データベーステーブルは user
Tasks
TaskStatuses
の3つを用意します。
2.2. APIエンドポイント
認証で使用しているのは auth/login
auth me
auth signup
の3つです。
タスク情報の CRUD(Create/Read/Update/Delete)関連のエンドポイントは5つです。
なお、今回のサンプルでは、タスク一覧を取得する際に Tasks テーブルのリレーション関係にある TaskStatuses テーブルの値も取得しているエンドポイント tasks_getTaskStatusName
を追加で準備しましたので、そちらの設定だけ補足しておきたいと思います。
タスク情報(Tasks テーブル)は、ステータス情報(taskstatuses_id フィールド)を持っていて、このデータはステータス情報のマスタ(TaskStatuses テーブル)と id
を基に紐づいてます。
Xano 上で確認すると、taskstatuses_id フィールドの値は「未完了」や「進行中」といった値が登録されているように見えますが、taskstatuses_id のデータ型は integer であるため、実際の値は数値で登録されています。
tasks_getTaskStatusName
エンドポイントは「未完了」や「進行中」といったステータス名を、タスク情報と一緒に取得するためのもので、GET メソッドの tasks
をコピーしたエンドポイントに対して Output に TaskStatuses テーブルの情報を追加しています。
Xano 側の構築については以上です。
3. Create の設定
次に Create でフロントエンドの作成を行っていきたいと思います。
3.1. ページ生成
Create で作成するページは以下の5ページです。
no | ページタイトル | ページ名(URL) | 詳細 |
---|---|---|---|
1 | Login Page | / | ログインページ |
2 | Signup Page | /signup | サインアップページ |
3 | My Tasks Page | /mytasks | タスク一覧ページ |
4 | Task Creation Page | /newtask | タスク作成ページ |
5 | Task Editor Page | /edittask | タスク編集ページ |
3.2. ユーザー認証について
今回のサンプルアプリでは、サインアップ/ログイン機能を実装しています。認証するユーザー情報に関しては Xano 側にある user テーブルを使用していますので、以前の記事でご紹介した Create のユーザーアカウント機能については使用していません。Create 内の機能を使ってユーザー認証を行いたい場合は、以下の記事をご参考くださいね。
また、認証については Xano API から受け取ったトークンを、ローカルストレージに保存する方法で実装しました。セキュリティ上、トークンをローカルストレージで管理することはベストプラクティスではありませんので、実運用を検討されている方はトークンを取得したらデータベースに保存するなど、より安全な方法をご検討ください。
なお、本アプリでは既存で準備していた Xano データベースを利用しますので、ローカルストレージに保存する形で進めます。
3.3. 各ページのプロンプトとコード
各ページの最終的なプロンプトと、生成されたコードは以下の通りです。ポイントとして、プロンプトでは変数名やページURLなどを、できるだけ具体的に記載することがスムーズな生成に繋がりました。
具体的には、クエリストリングやローカルストレージのキー値、またURLとなるページ名などプログラムのコードにした際に影響を受けそうな部分を固定で指定しています。
3.3.1. Login Page
ログインページは以下のようなプロンプトで作成しています。
メールアドレスを使用したログインページを作成。 入力フォームは以下の2つ email password サインアップページへのリンクがあります。 デザインは以下のイメージ。 [ ログインページのイメージ画像] Xano API を使ったログインを実行します。 エンドポイントは以下のURLを使用します。 [Xano API のログイン用エンドポイントURL] ログインに成功したら、受け取ったトークンを authToken という名前のローカルストレージに保存して、mytasks ページに遷移します。 ログインに失敗したら、受け取ったエラーを表示します。
実際に生成されたコードは、以下のような内容となりました。
function MainComponent() { const [email, setEmail] = React.useState(''); const [password, setPassword] = React.useState(''); const [error, setError] = React.useState(''); const handleLogin = async (e) => { e.preventDefault(); setError(''); try { const response = await fetch('Xano API のログイン用エンドポイントURL', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email, password }), }); const data = await response.json(); if (response.ok) { localStorage.setItem('authToken', data.authToken); window.location.href = '/mytasks'; } else { setError(data.message || 'ログインに失敗しました。'); } } catch (err) { setError('ネットワークエラーが発生しました。'); } }; return ( <div className="min-h-screen bg-gray-100 flex flex-col justify-center items-center font-sans"> <div className="bg-white p-8 rounded-lg shadow-md w-full max-w-md"> <h1 className="text-2xl font-bold text-center mb-6">ログイン</h1> <form onSubmit={handleLogin}> <div className="mb-4"> <label htmlFor="email" className="block text-sm font-medium text-gray-700 mb-1"> メールアドレス </label> <input type="email" id="email" name="email" value={email} onChange={(e) => setEmail(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" required /> </div> <div className="mb-6"> <label htmlFor="password" className="block text-sm font-medium text-gray-700 mb-1"> パスワード </label> <input type="password" id="password" name="password" value={password} onChange={(e) => setPassword(e.target.value)} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-indigo-500" required /> </div> {error && <p className="text-red-500 text-sm mb-4">{error}</p>} <button type="submit" className="w-full bg-indigo-600 text-white py-2 px-4 rounded-md hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2" > ログイン </button> </form> <div className="mt-4 text-center"> <p className="text-sm text-gray-600">アカウントをお持ちでない方</p> <a href="/signup" className="text-indigo-600 hover:text-indigo-800 font-medium"> サインアップ </a> </div> </div> </div> ); }
3.3.2. Signup Page
サインアップページも、ログインページと同じような要領でプロンプトを指定しています。
メールアドレスを使用したサインアップページを作成。 入力フォームは以下3つ name email password ログインページへのリンクがあります。 デザインのイメージは以下。 [ サインアップページのイメージ画像] Xano API を使ったサインアップを実行します。 エンドポイントは以下のURLを使用します。 [Xano API のサインアップ用エンドポイントURL] サインアップに成功したら、受け取ったトークンを authToken という名前のローカルストレージに保存し、mytasks ページ遷移します。 サインアップに失敗したら、受け取ったエラーを表示します。
以下が実際に生成されたコードです。
function MainComponent() { const [error, setError] = React.useState(''); const handleSubmit = async (e) => { e.preventDefault(); const formData = new FormData(e.target); const userData = Object.fromEntries(formData.entries()); try { const response = await fetch('[Xano API のサインアップ用エンドポイントURL]', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(userData), }); const data = await response.json(); if (response.ok) { localStorage.setItem('authToken', data.authToken); setError(''); window.location.href = '/mytasks'; } else { setError(data.message || '不明なエラーが発生しました'); } } catch (error) { setError('サインアップ中にエラーが発生しました'); } }; return ( <div className="min-h-screen bg-gray-100 flex flex-col justify-center py-12 sm:px-6 lg:px-8"> <div className="sm:mx-auto sm:w-full sm:max-w-md"> <h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900"> アカウントを作成 </h2> </div> <div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md"> <div className="bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10"> <form className="space-y-6" onSubmit={handleSubmit}> <div> <label htmlFor="name" className="block text-sm font-medium text-gray-700"> 名前 </label> <div className="mt-1"> <input id="name" name="name" type="text" required className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" /> </div> </div> <div> <label htmlFor="email" className="block text-sm font-medium text-gray-700"> メールアドレス </label> <div className="mt-1"> <input id="email" name="email" type="email" autoComplete="email" required className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" /> </div> </div> <div> <label htmlFor="password" className="block text-sm font-medium text-gray-700"> パスワード </label> <div className="mt-1"> <input id="password" name="password" type="password" autoComplete="new-password" required className="appearance-none block w-full px-3 py-2 border border-gray-300 rounded-md shadow-sm placeholder-gray-400 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 sm:text-sm" /> </div> </div> {error && ( <div className="text-red-600 text-sm">{error}</div> )} <div> <button type="submit" className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-white bg-[#5850EC] hover:bg-[#4c45d4] focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-[#5850EC]" > サインアップ </button> </div> </form> <div className="mt-6"> <div className="relative"> <div className="absolute inset-0 flex items-center"> <div className="w-full border-t border-gray-300"></div> </div> <div className="relative flex justify-center text-sm"> <span className="px-2 bg-white text-gray-500"> 既にアカウントをお持ちですか? </span> </div> </div> <div className="mt-6"> <a href="/login" className="w-full flex justify-center py-2 px-4 border border-transparent rounded-md shadow-sm text-sm font-medium text-[#5850EC] bg-white hover:bg-gray-50" > ログイン </a> </div> </div> </div> </div> </div> ); }
3.3.3. My Tasks
My Tasks はタスク一覧ページです。ここでは、サインアップ/ログイン時に受け取ったトークンをローカルストレージから取り出してデータを取得しています。また、タスクの新規作成や編集画面への遷移、タスク削除時の処理についても指示しています。
mytasks ページを作成。 見出しは「タスク一覧」とします。 ユーザー情報は以下のエンドポイントを使用します。 [Xano API のユーザー情報用エンドポイントURL] ユーザー情報の取得に必要なトークンはローカルストレージ authToken に保存されているトークンを使用します。 取得したユーザー情報の名前を画面に表示します。 以下のエンドポイントを使用して、ユーザーのタスク一覧を取得します。トークンは authToken を使用します。 [Xano API のタスク一覧用エンドポイントURL] ユーザー名の右側に、タスク作成用のシンプルなアイコンを配置します。アイコンをクリックしたら、newtask ページに遷移します。 以下はレスポンスの定義です。参考にしてください。 [XanoのレスポンスJSONイメージ画像] 取得したタスク情報の行番号、タイトル、タスクステータス、締め切り、編集用アイコン、削除アイコンを表示します。なお、タスクステータスは、taskstatuses_id ではなく、_taskstatuses 配列1件目 の name の値を参照してください。 編集用アイコンがクリックされたら、選択しているタスク情報の id を taskid という名前のクエリストリングにセットして、edittask ページに遷移します。 削除アイコンをクリックしたら、以下のエンドポイントを使用して、タスク情報を削除します。 [Xano API のタスク削除用エンドポイントURL] 削除が完了したら、タスク一覧を更新します。
実際に生成されたコードは以下です。
function MainComponent() { const [user, setUser] = React.useState(null); const [tasks, setTasks] = React.useState([]); const [error, setError] = React.useState(null); const fetchTasks = async (token) => { try { const tasksResponse = await fetch( "[Xano API のタスク一覧用エンドポイントURL]", { headers: { Authorization: `Bearer ${token}`, }, } ); if (!tasksResponse.ok) { throw new Error("タスク情報の取得に失敗しました。"); } const tasksData = await tasksResponse.json(); setTasks(tasksData); } catch (err) { setError(err.message); } }; React.useEffect(() => { const fetchData = async () => { const token = localStorage.getItem("authToken"); if (!token) { setError("トークンが見つかりません。ログインしてください。"); return; } try { const userResponse = await fetch( "[Xano API のユーザー情報用エンドポイントURL]", { headers: { Authorization: `Bearer ${token}`, }, } ); if (!userResponse.ok) { throw new Error("ユーザー情報の取得に失敗しました。"); } const userData = await userResponse.json(); setUser(userData); await fetchTasks(token); } catch (err) { setError(err.message); } }; fetchData(); }, []); const handleEditClick = (taskId) => { window.location.href = `/edittask?taskid=${taskId}`; }; const handleNewTaskClick = () => { window.location.href = "/newtask"; }; const handleDeleteClick = async (taskId) => { const token = localStorage.getItem("authToken"); if (!token) { setError("トークンが見つかりません。ログインしてください。"); return; } try { const response = await fetch( `[Xano API のタスク削除用エンドポイントURL]`, { method: 'DELETE', headers: { Authorization: `Bearer ${token}`, }, } ); if (!response.ok) { throw new Error("タスクの削除に失敗しました。"); } await fetchTasks(token); } catch (err) { setError(err.message); } }; return ( <div className="min-h-screen bg-gray-100 py-6 flex flex-col justify-center sm:py-12"> <div className="relative py-3 sm:max-w-4xl sm:mx-auto"> <div className="absolute inset-0 bg-gradient-to-r from-cyan-400 to-light-blue-500 shadow-lg transform -skew-y-6 sm:skew-y-0 sm:-rotate-6 sm:rounded-3xl"></div> <div className="relative px-4 py-10 bg-white shadow-lg sm:rounded-3xl sm:p-20"> <div className="max-w-3xl mx-auto"> <div> <h1 className="text-2xl font-semibold text-center font-roboto mb-6"> タスク一覧 </h1> </div> <div className="divide-y divide-gray-200"> {error ? ( <p className="text-red-500 mt-4">{error}</p> ) : user ? ( <div className="py-8 text-base leading-6 space-y-4 text-gray-700 sm:text-lg sm:leading-7"> <div className="flex justify-between items-center mb-4"> <p className="font-roboto">ようこそ、{user.name}さん</p> <button onClick={handleNewTaskClick} className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded-full shadow-lg" > <i className="fas fa-plus mr-2"></i> 新規タスク </button> </div> <table className="min-w-full divide-y divide-gray-200"> <thead className="bg-gray-50"> <tr> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> 番号 </th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> タイトル </th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> ステータス </th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> 締め切り </th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> 編集 </th> <th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> 削除 </th> </tr> </thead> <tbody className="bg-white divide-y divide-gray-200"> {tasks.map((task, index) => ( <tr key={task.id}> <td className="px-6 py-4 whitespace-nowrap"> {index + 1} </td> <td className="px-6 py-4 whitespace-nowrap"> {task.title} </td> <td className="px-6 py-4 whitespace-nowrap"> {task._taskstatuses[0].name} </td> <td className="px-6 py-4 whitespace-nowrap"> {task.due_date} </td> <td className="px-6 py-4 whitespace-nowrap"> <i className="fas fa-edit text-blue-500 cursor-pointer" onClick={() => handleEditClick(task.id)} ></i> </td> <td className="px-6 py-4 whitespace-nowrap"> <i className="fas fa-trash-alt text-red-500 cursor-pointer" onClick={() => handleDeleteClick(task.id)} ></i> </td> </tr> ))} </tbody> </table> </div> ) : ( <p className="text-gray-500 mt-4"> ユーザー情報を読み込んでいます... </p> )} </div> </div> </div> </div> </div> ); }
3.3.4. Task Creation Page
Task Creation Page はタスクの新規作成画面です。プロンプトではトークンを使用して実行するように指示しています。
newtask ページを作成します。 見出しは「タスク作成」とします。 以下のイメージを参考に、タスクの新規作成画面を作成します。 [ タスク登録ページのイメージ画像] タスクの登録には以下のエンドポイントを使用します。 [Xano API のタスク登録用エンドポイントURL] Bearerトークンは、ローカルストレージ authToken に保存されているトークンを使用します。 編集できる値は以下です。 user_id title description taskstatuses_id due_date user_id は、以下のエンドポイントを使用して取得した id を使用します。 [Xano API のユーザー情報用エンドポイントURL] タスクステータスの値は、以下の固定値から選択することが出来ます。 1:未完了 2:進行中 3:完了 登録に成功したら、mytasks ページに遷移します。 失敗したら、エラーメッセージを表示します。 mytasks ページに遷移するための、戻るボタンを作成します。
生成されたコードは以下です。
function MainComponent() { const [title, setTitle] = React.useState(''); const [description, setDescription] = React.useState(''); const [status, setStatus] = React.useState('1'); const [dueDate, setDueDate] = React.useState(''); const [error, setError] = React.useState(''); const [userId, setUserId] = React.useState(null); React.useEffect(() => { const fetchUserId = async () => { const authToken = localStorage.getItem('authToken'); try { const response = await fetch('[Xano API のユーザー情報用エンドポイントURL]', { headers: { 'Authorization': `Bearer ${authToken}` } }); const data = await response.json(); setUserId(data.id); } catch (error) { setError('ユーザー情報の取得に失敗しました'); } }; fetchUserId(); }, []); const handleSubmit = async (e) => { e.preventDefault(); const authToken = localStorage.getItem('authToken'); try { const response = await fetch('[Xano API のタスク登録用エンドポイントURL]', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify({ user_id: userId, title, description, taskstatuses_id: parseInt(status), due_date: dueDate }) }); if (response.ok) { window.location.href = '/mytasks'; } else { setError('タスクの登録に失敗しました'); } } catch (error) { setError('タスクの登録に失敗しました'); } }; return ( <div className="min-h-screen bg-gray-100 py-6 flex flex-col justify-center sm:py-12"> <div className="relative py-3 sm:max-w-xl sm:mx-auto"> <div className="absolute inset-0 bg-gradient-to-r from-blue-300 to-blue-600 shadow-lg transform -skew-y-6 sm:skew-y-0 sm:-rotate-6 sm:rounded-3xl"></div> <div className="relative px-4 py-10 bg-white shadow-lg sm:rounded-3xl sm:p-20"> <div className="max-w-md mx-auto"> <h1 className="text-2xl font-semibold mb-6 text-center">タスク作成</h1> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label htmlFor="title" className="block text-sm font-medium text-gray-700">タイトル</label> <input type="text" id="title" name="title" required className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" value={title} onChange={(e) => setTitle(e.target.value)} /> </div> <div> <label htmlFor="description" className="block text-sm font-medium text-gray-700">説明</label> <textarea id="description" name="description" rows="3" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" value={description} onChange={(e) => setDescription(e.target.value)}></textarea> </div> <div> <label htmlFor="status" className="block text-sm font-medium text-gray-700">ステータス</label> <select id="status" name="status" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" value={status} onChange={(e) => setStatus(e.target.value)}> <option value="1">未完了</option> <option value="2">進行中</option> <option value="3">完了</option> </select> </div> <div> <label htmlFor="dueDate" className="block text-sm font-medium text-gray-700">期限</label> <input type="date" id="dueDate" name="dueDate" className="mt-1 block w-full border border-gray-300 rounded-md shadow-sm p-2" value={dueDate} onChange={(e) => setDueDate(e.target.value)} /> </div> {error && <p className="text-red-500 text-sm">{error}</p>} <div className="flex justify-between"> <button type="button" onClick={() => window.location.href = '/mytasks'} className="bg-gray-300 hover:bg-gray-400 text-gray-800 font-bold py-2 px-4 rounded">戻る</button> <button type="submit" className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">登録</button> </div> </form> </div> </div> </div> </div> ); }
3.3.5. Task Editor Page
Task Editor Page はタスク編集の画面です。
edittask ページを作成します。 見出しは「タスク編集」とします。 ページのデザインは、下図のデザインと同じものを作成します。 [ タスク編集ページのイメージ画像] タスク情報の取得は以下のエンドポイントを使用します。 [Xano API のタスク情報用エンドポイントURL] tasks_id には、クエリストリングの taskid で指定された値を使用します。Bearerトークンは、ローカルストレージ authToken に保存されているトークンを使用します。 以下の図は Tasksテーブルの定義です。 [XanoのTasksテーブル定義イメージ画像] 編集できる値は以下です。 title description taskstatuses_id due_date なおタスクステータスの値は、以下の固定値から選択することが出来ます。 1:未完了 2:進行中 3:完了 タスクの編集は、以下のエンドポイントをPATCHで実行します。 [Xano API のタスク編集用エンドポイントURL] 編集に成功したら、mytasks ページに遷移します。 失敗したら、エラーメッセージを表示します。 mytasks ページに遷移するための、戻るボタンを作成します。
生成されたコードは以下です。
function MainComponent() { const [task, setTask] = React.useState({ title: '', description: '', taskstatuses_id: '', due_date: '' }); const [errorMessage, setErrorMessage] = React.useState(''); React.useEffect(() => { const fetchTask = async () => { const urlParams = new URLSearchParams(window.location.search); const taskId = urlParams.get('taskid'); const authToken = localStorage.getItem('authToken'); try { const response = await fetch(`[Xano API のタスク情報用エンドポイントURL]`, { headers: { 'Authorization': `Bearer ${authToken}` } }); if (!response.ok) throw new Error('Failed to fetch task'); const data = await response.json(); setTask(data); } catch (error) { setErrorMessage('タスクの取得に失敗しました'); } }; fetchTask(); }, []); const handleInputChange = (e) => { const { name, value } = e.target; setTask(prevTask => ({ ...prevTask, [name]: value })); }; const handleSubmit = async (e) => { e.preventDefault(); const authToken = localStorage.getItem('authToken'); const urlParams = new URLSearchParams(window.location.search); const taskId = urlParams.get('taskid'); try { const response = await fetch(`[Xano API のタスク編集用エンドポイントURL]`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${authToken}` }, body: JSON.stringify(task) }); if (!response.ok) throw new Error('Failed to update task'); window.location.href = '/mytasks'; } catch (error) { setErrorMessage('タスクの更新に失敗しました'); } }; return ( <div className="min-h-screen bg-gradient-to-br from-blue-100 to-white flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8"> <div className="max-w-md w-full bg-white rounded-lg shadow-lg overflow-hidden"> <div className="px-6 py-8"> <h1 className="text-3xl font-bold mb-6 font-sans text-center text-gray-900">タスク編集</h1> <form onSubmit={handleSubmit} className="space-y-4"> <div> <label htmlFor="title" className="block mb-1 font-medium font-sans text-gray-700">タイトル</label> <input type="text" id="title" name="title" value={task.title} onChange={handleInputChange} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div> <label htmlFor="description" className="block mb-1 font-medium font-sans text-gray-700">説明</label> <textarea id="description" name="description" value={task.description} onChange={handleInputChange} rows="4" className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea> </div> <div> <label htmlFor="taskstatuses_id" className="block mb-1 font-medium font-sans text-gray-700">ステータス</label> <select id="taskstatuses_id" name="taskstatuses_id" value={task.taskstatuses_id} onChange={handleInputChange} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"> <option value="1">未完了</option> <option value="2">進行中</option> <option value="3">完了</option> </select> </div> <div> <label htmlFor="due_date" className="block mb-1 font-medium font-sans text-gray-700">期限</label> <input type="date" id="due_date" name="due_date" value={task.due_date} onChange={handleInputChange} className="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" /> </div> <div className="flex justify-between"> <button type="button" onClick={() => window.location.href = '/mytasks'} className="bg-gray-300 text-gray-700 px-6 py-2 rounded-md hover:bg-gray-400 font-sans transition duration-300 ease-in-out transform hover:-translate-y-1 hover:scale-105"> 戻る </button> <button type="submit" className="bg-blue-500 text-white px-6 py-2 rounded-md hover:bg-blue-600 font-sans transition duration-300 ease-in-out transform hover:-translate-y-1 hover:scale-105"> 登録 </button> </div> </form> {errorMessage && <div className="mt-4 text-red-500 font-sans text-center">{errorMessage}</div>} </div> </div> </div> ); }
以上で Create のページ作成は完了です! View live して動作を確認してみてくださいね。
なお、Demo 機能ではページ遷移が上手く動作しない(404エラーが表示される)ため、動作確認は View live で行う必要があります。
プロンプトでは、ローカルストレージ(authToken)、クエリストリング(taskid)といったキー、URLで使用されるページ名(signup、mytasks、newtask、edittask)を具体的に指定しました。プロンプトで曖昧にしている場合は、想定していたような動作ではなく、コードを参照して処理内容を確認するといった作業が増えた為、動作に何かしらの影響を受けそうな部分に関しては、明確に指示することをおススメします。
なお、今回のサンプルでは、以前の記事でご紹介した Function 機能については使用しませんでした。理由としては、Function 内でローカルストレージを使ったテストが実行できなかった為(サーバーサイドでローカルストレージは使えない)ですが、ローカルストレージの使用をページ側の処理で行うなど工夫すれば Function 機能も利用できるかと思います。もし、エラーを受け取るなど、思ったようにアプリが生成されないといった場合は、処理がサーバーサイドとクライアントサイドのどちらで実行されているか?を考慮しておくと良さそうですね!
4. まとめ
今回は、Create.xyz と バックエンドサービスの Xano の連携についてご紹介しました!今回のアプリを作成する際、プロンプトでの指示については、実際に生成されたコードを確認しながら、プロンプトの内容を修正して進めるといった作業もありましたが、基本的には対話による生成で完結することができました。Create は進化も早く、新しい機能が日々追加されていくスピード感がありますね。ぜひ皆さんも、積極的に AI コーディングを試してみてくださいね!
では、次回もどうぞお楽しみに~!
5. 関連記事
以下は、Create.xyz に関する記事となっています。ぜひ、こちらも併せてご参考ください!