Formulir
Script From Registrasi dan Login Full AuthFormWithDrive.jsx
import React, { useState, useEffect, useRef } from "react";
/**
* AuthFormWithDrive.jsx
* Single-file React component (Tailwind) that provides:
* - Normal (inline) registration & login forms
* - Modal (popup) registration & login forms
* - Fields: name, address, phone, email, password
* - Social login buttons (Google / Facebook / GitHub) with example integration points
* - Save form data to Google Drive (via Google Drive REST API / gapi / Google Identity Services)
*
* IMPORTANT SETUP NOTES (read + follow):
* 1) Tailwind: this component assumes your project is set up with Tailwind CSS.
* 2) Google APIs: create OAuth credentials in Google Cloud Console
* - Enable Drive API and/or use Google Identity Services (GSI) for sign-in.
* - Obtain CLIENT_ID and (optionally) API_KEY if using gapi.
* - For Drive uploads from client, you need an OAuth access token with scope: https://www.googleapis.com/auth/drive.file
* - For production, it's recommended to perform OAuth on the server to keep client secrets safe.
* 3) Facebook/GitHub: implement OAuth on your backend. Social buttons here call /auth/:provider endpoints as examples.
* 4) This component focuses on UI + client integration points. Replace placeholders with your backend endpoints and keys.
*
* Usage: import default export and render in your app.
*/
const GOOGLE_CLIENT_ID = "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com"; // replace
const GOOGLE_API_KEY = "YOUR_GOOGLE_API_KEY"; // optional if using gapi
export default function AuthFormWithDrive() {
const [mode, setMode] = useState("login"); // 'login' or 'register'
const [showModal, setShowModal] = useState(false);
const [loading, setLoading] = useState(false);
const [message, setMessage] = useState(null);
const initial = {
name: "",
address: "",
phone: "",
email: "",
password: "",
};
const [form, setForm] = useState(initial);
// Simple client-side validation
function validateForm(isRegister) {
if (isRegister) {
if (!form.name.trim()) return "Nama wajib diisi";
if (!form.address.trim()) return "Alamat wajib diisi";
if (!/^\+?[0-9\-\s]{7,20}$/.test(form.phone)) return "No HP tidak valid";
}
if (!/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(form.email)) return "Email tidak valid";
if (form.password.length < 6) return "Password harus >= 6 karakter";
return null;
}
function onChange(e) {
setForm((s) => ({ ...s, [e.target.name]: e.target.value }));
}
async function onSubmit(e) {
e.preventDefault();
const err = validateForm(mode === "register");
if (err) {
setMessage({ type: "error", text: err });
return;
}
setLoading(true);
setMessage(null);
try {
// In real app: send to backend for account creation / login
// For demo: save locally and to Google Drive (if authorized)
const saved = saveToLocal(form, mode);
setMessage({ type: "success", text: `Berhasil ${mode === "register" ? "registrasi" : "login"}` });
// Attempt to upload to Google Drive (non-blocking)
await saveToGoogleDrive(`${mode}-${Date.now()}.json`, JSON.stringify({ mode, form: saved }, null, 2));
} catch (err) {
console.error(err);
setMessage({ type: "error", text: "Terjadi kesalahan saat memproses." });
} finally {
setLoading(false);
}
}
function saveToLocal(formData, mode) {
const key = `auth_demo_${mode}`;
localStorage.setItem(key, JSON.stringify(formData));
return formData;
}
// --------------------
// Google Drive integration helper
// --------------------
// This component supplies a client-side helper to save a small file to Drive.
// For this to work:
// - Add GOOGLE_CLIENT_ID
// - Serve your site on an origin configured in Google Console
// - Use the "https://www.googleapis.com/auth/drive.file" scope
const tokenRef = useRef(null);
useEffect(() => {
// Try to auto-load Google Identity Services if available
if (typeof window !== "undefined" && window.google && GOOGLE_CLIENT_ID.includes("YOUR_") === false) {
/* If using Google Identity Services for an OAuth token flow, you can initialize here.
Example to prompt user to sign in and get access token:
const client = google.accounts.oauth2.initTokenClient({
client_id: GOOGLE_CLIENT_ID,
scope: 'https://www.googleapis.com/auth/drive.file',
callback: (tokenResponse) => { tokenRef.current = tokenResponse.access_token; }
});
// To request token later: client.requestAccessToken();
*/
}
}, []);
async function saveToGoogleDrive(filename, content) {
// If no client id configured, skip silently.
if (GOOGLE_CLIENT_ID.includes("YOUR_")) {
// not configured; show a hint
console.info("Google Drive not configured. Skipping upload.");
return null;
}
try {
// If token is not present, request it using the popup flow (GSI token client)
if (!tokenRef.current) {
if (!window.google || !window.google.accounts || !window.google.accounts.oauth2) {
console.warn("Google Identity Services not loaded. Include their script in your index.html:");
console.warn("<script src=\"https://accounts.google.com/gsi/client\"></script>");
return null;
}
const client = window.google.accounts.oauth2.initTokenClient({
client_id: GOOGLE_CLIENT_ID,
scope: "https://www.googleapis.com/auth/drive.file",
callback: (resp) => {
tokenRef.current = resp.access_token;
},
});
// request token - this will show a popup managed by Google
await new Promise((res) => client.requestAccessToken({ prompt: "consent" }, res));
if (!tokenRef.current) return null;
}
// Now upload using the Drive REST API
const metadata = {
name: filename,
mimeType: "application/json",
};
const boundary = "-------314159265358979323846";
const delimiter = `\r\n--${boundary}\r\n`;
const closeDelim = `\r\n--${boundary}--`;
const multipartRequestBody =
delimiter +
'Content-Type: application/json; charset=UTF-8\r\n\r\n' +
JSON.stringify(metadata) +
'\r\n' +
delimiter +
'Content-Type: application/json\r\n\r\n' +
content +
closeDelim;
const res = await fetch("https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", {
method: "POST",
headers: new Headers({
Authorization: `Bearer ${tokenRef.current}`,
"Content-Type": `multipart/related; boundary=${boundary}`,
}),
body: multipartRequestBody,
});
if (!res.ok) {
console.warn("Drive upload failed", await res.text());
return null;
}
const data = await res.json();
console.info("Saved to Drive: ", data);
return data;
} catch (err) {
console.error("Drive save error", err);
return null;
}
}
// --------------------
// Social login handlers (placeholders)
// --------------------
function onSocialLogin(provider) {
// For security and reliability, social OAuth should be implemented on the backend.
// These examples simply open a new window to an endpoint you must provide.
// Example: GET /auth/google -> redirect to Google OAuth flow -> redirect back with code -> exchange on server
// Popup for example
const w = window.open(`/auth/${provider}`, "_blank", "width=600,height=700");
if (!w) setMessage({ type: "error", text: "Popup terblokir. Izinkan popup untuk melanjutkan." });
}
// --------------------
// Small presentational components
// --------------------
const Input = ({ label, name, type = "text", placeholder = "" }) => (
<label className="block">
<div className="text-sm font-medium text-gray-700 mb-1">{label}</div>
<input
name={name}
type={type}
placeholder={placeholder}
value={form[name]}
onChange={onChange}
className="w-full rounded-md border px-3 py-2 focus:outline-none focus:ring-2 focus:ring-indigo-400"
/>
</label>
);
return (
<div className="max-w-3xl mx-auto p-6">
<h2 className="text-2xl font-semibold mb-4">Auth demo — Form Login & Registrasi</h2>
{/* Toggle UI: inline vs modal */}
<div className="flex gap-3 items-center mb-4">
<div className="flex items-center gap-2">
<input id="inline" type="radio" name="ui" defaultChecked onChange={() => setShowModal(false)} />
<label htmlFor="inline">Tampilan biasa</label>
</div>
<div className="flex items-center gap-2">
<input id="modal" type="radio" name="ui" onChange={() => setShowModal(true)} />
<label htmlFor="modal">Tampilan popup</label>
</div>
<div className="ml-auto flex gap-2">
<button
onClick={() => setMode("login")}
className={`px-3 py-1 rounded ${mode === "login" ? "bg-indigo-600 text-white" : "border"}`}
>
Login
</button>
<button
onClick={() => setMode("register")}
className={`px-3 py-1 rounded ${mode === "register" ? "bg-indigo-600 text-white" : "border"}`}
>
Registrasi
</button>
</div>
</div>
<div className="mb-4">
<div className="text-sm text-gray-600">Masuk dengan akun:</div>
<div className="flex gap-2 mt-2">
<button onClick={() => onSocialLogin("google")} className="px-3 py-2 rounded border w-32">
</button>
<button onClick={() => onSocialLogin("facebook")} className="px-3 py-2 rounded border w-32">
</button>
<button onClick={() => onSocialLogin("github")} className="px-3 py-2 rounded border w-32">
GitHub
</button>
</div>
</div>
{/* Inline form */}
{!showModal && (
<form onSubmit={onSubmit} className="bg-white p-6 border rounded-md shadow-sm">
{mode === "register" && (
<div className="grid grid-cols-1 gap-3 mb-3">
<Input label="Nama" name="name" placeholder="Nama lengkap" />
<Input label="Alamat" name="address" placeholder="Alamat rumah" />
<Input label="No HP" name="phone" placeholder="contoh: +6281234..." />
</div>
)}
<div className="grid grid-cols-1 gap-3 mb-3">
<Input label="Email" name="email" type="email" placeholder="you@example.com" />
<Input label="Password" name="password" type="password" placeholder="minimal 6 karakter" />
</div>
<div className="flex items-center gap-3">
<button disabled={loading} className="px-4 py-2 rounded bg-indigo-600 text-white">
{loading ? "Memproses..." : mode === "register" ? "Daftar" : "Masuk"}
</button>
<button
type="button"
onClick={() => { setForm(initial); setMessage(null); }}
className="px-3 py-2 rounded border"
>
Reset
</button>
<button type="button" onClick={() => setShowModal(true)} className="ml-auto text-sm underline">
Buka sebagai popup
</button>
</div>
{message && (
<div className={`mt-3 text-sm ${message.type === "error" ? "text-red-600" : "text-green-600"}`}>
{message.text}
</div>
)}
</form>
)}
{/* Modal / Popup form */}
{showModal && (
<div className="fixed inset-0 z-50 flex items-center justify-center">
<div className="absolute inset-0 bg-black opacity-40" onClick={() => setShowModal(false)}></div>
<div className="relative bg-white rounded-lg shadow-2xl w-full max-w-lg p-6 z-10">
<div className="flex items-center justify-between mb-4">
<h3 className="text-lg font-semibold">{mode === "register" ? "Registrasi" : "Login"} (Popup)</h3>
<button onClick={() => setShowModal(false)} className="text-gray-500">✕</button>
</div>
<form onSubmit={onSubmit}>
{mode === "register" && (
<div className="grid grid-cols-1 gap-3 mb-3">
<Input label="Nama" name="name" placeholder="Nama lengkap" />
<Input label="Alamat" name="address" placeholder="Alamat rumah" />
<Input label="No HP" name="phone" placeholder="contoh: +6281234..." />
</div>
)}
<div className="grid grid-cols-1 gap-3 mb-3">
<Input label="Email" name="email" type="email" placeholder="you@example.com" />
<Input label="Password" name="password" type="password" placeholder="minimal 6 karakter" />
</div>
<div className="flex items-center gap-3">
<button disabled={loading} className="px-4 py-2 rounded bg-indigo-600 text-white">
{loading ? "Memproses..." : mode === "register" ? "Daftar" : "Masuk"}
</button>
<button type="button" onClick={() => { setForm(initial); setMessage(null); }} className="px-3 py-2 rounded border">
Reset
</button>
<button type="button" onClick={() => setShowModal(false)} className="ml-auto text-sm underline">
Tutup
</button>
</div>
{message && (
<div className={`mt-3 text-sm ${message.type === "error" ? "text-red-600" : "text-green-600"}`}>
{message.text}
</div>
)}
</form>
<div className="mt-4 text-sm text-gray-600">Atau daftar / masuk menggunakan:</div>
<div className="flex gap-2 mt-2">
<button onClick={() => onSocialLogin("google")} className="px-3 py-2 rounded border w-28">
</button>
<button onClick={() => onSocialLogin("facebook")} className="px-3 py-2 rounded border w-28">
</button>
<button onClick={() => onSocialLogin("github")} className="px-3 py-2 rounded border w-28">
GitHub
</button>
</div>
</div>
</div>
)}
<div className="mt-6 text-sm text-gray-500">
<strong>Catatan teknis:</strong>
<ul className="list-disc list-inside">
<li>Social sign-in flows (Google/Facebook/GitHub) perlu backend untuk menukar code -> access token secara aman.</li>
<li>
Google Drive upload di sini menggunakan akses token client-side. Untuk produksi: lakukan upload lewat server atau gunakan
proper OAuth flow dan refresh token.
</li>
<li>Jangan menyimpan password plaintext di Google Drive atau localStorage di aplikasi nyata — ini contoh demonstrasi saja.</li>
</ul>
</div>
</div>
);
}
Selesai — aku sudah membuat komponen React untuk form login & registrasi (tampilan biasa + popup) dengan titik integrasi untuk Google Drive dan social login. Lihat file AuthFormWithDrive.jsx di canvas (di samping).
Kalau mau, aku bisa:
Menambahkan contoh backend (Express) untuk menangani OAuth Facebook/GitHub/Google.
Atau ubah komponen supaya menggunakan Google Picker / server-side upload secara lengkap.
Mau langkah selanjutnya yang mana?