genkit/postcard-generator/app/components/PostcardForm.tsx (134 lines of code) (raw):

/** * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ "use strict"; "use client"; import { APILoader } from "@googlemaps/extended-component-library/react"; import PlaceAutoComplete from "./PlaceAutoComplete"; import { PostcardFlow } from "@/libs/genkit/schema"; import { useEffect, useState } from "react"; import { callPostcardFlow } from "@/libs/genkit/flows"; import PostcardImage from "./Postcard"; import Stack from "@mui/material/Stack"; import LoadingButton from "@mui/lab/LoadingButton"; import Container from "@mui/material/Container"; import { UserAuth } from "./AuthContext"; import CircularProgress from "@mui/material/CircularProgress"; import Alert from "@mui/material/Alert"; import Button from "@mui/material/Button"; import AlertTitle from "@mui/material/AlertTitle"; import TextField from "@mui/material/TextField"; import { firebaseConfig } from "@/libs/firebase/config"; export default function PostcardForm() { const [start, setStart] = useState<string>(""); const [end, setEnd] = useState<string>(""); const [sender, setSender] = useState<string>(""); const [recipient, setRecpient] = useState<string>(""); const [loading, setLoading] = useState<boolean>(true); const [postcardImage, setPostcardImage] = useState<string | null>(null); const [mapImage, setMapImage] = useState<string | null>(null); const [description, setDescription] = useState<string>(""); const [story, setStory] = useState<string>(""); const [error, setError] = useState<string | null>(null); const [loginError, setLoginError] = useState<string | null>(null); const [generating, setGenerating] = useState<boolean>(false); function updateStartAddress(e: Event) { if (e.target) { const eventValue = (e.target as HTMLSelectElement).value; const place = eventValue as unknown as google.maps.places.Place; setStart(`${place.displayName}, ${place.formattedAddress}`); } } function updateEndAddress(e: Event) { if (e.target) { const eventValue = (e.target as HTMLSelectElement).value; const place = eventValue as unknown as google.maps.places.Place; setEnd(`${place.displayName}, ${place.formattedAddress}`); } } async function generatePostcard(event: React.FormEvent) { try { event.preventDefault(); setGenerating(true); const image = await callPostcardFlow(r); setError(null); setPostcardImage(image.image); setDescription(image.description); setStory(image.story); setMapImage(image.map); } catch { // TODO - show an error in the UI setError("An error occurred generating your postcard. Please try again."); } finally { setGenerating(false); } } // This page requires authentication, check we have that const { user, googleSignIn, enabled } = UserAuth(); useEffect(() => { const checkAuthentication = async () => { await new Promise(resolve => setTimeout(resolve, 50)); setLoading(false); }; checkAuthentication(); }, [user]); const handleSignIn = async () => { try { await googleSignIn(); setLoginError(null); } catch (error) { setLoginError(`Login failed with error: ${error}`); } }; const r = { start: start, end: end, stops: [], sender: sender, recipient: recipient, } as PostcardFlow; if (loading) { return <CircularProgress />; } if (!enabled) { <Alert severity="warning"> <AlertTitle>Login is disabled in this project.</AlertTitle> {loginError} </Alert>; } if ((!user) && (enabled)) { // Install a proxying service worker if Firebase is configured if (("serviceWorker" in navigator) && (firebaseConfig) && (firebaseConfig.apiKey) && (firebaseConfig.apiKey !== "")) { navigator.serviceWorker.register("/auth-service-worker.js", { scope: "/" }); } return ( <> {loginError && !loading && ( <Alert severity="error"> <AlertTitle>An Error Occurred</AlertTitle> {loginError} </Alert> )} <Alert severity="warning" variant="outlined">Only logged-in users may view this page.</Alert> <Button color="inherit" onClick={handleSignIn}> Login with Google </Button> </> ); } return ( <Container maxWidth="lg"> <PostcardImage postcardImage={postcardImage} generating={generating} description={description} start={start} end={end} error={error} mapImage={mapImage} story={story} /> <form> <APILoader apiKey={process.env.NEXT_PUBLIC_GOOGLE_MAPS_PUBLIC_API_KEY} solutionChannel="GMP_GCC_placepicker_v1" /> <Stack spacing={2} direction="row"> <TextField required sx={{ width: "50%" }} label="Sender" variant="outlined" value={sender} onChange={e => setSender(e.target.value)} /> <TextField required sx={{ width: "50%" }} label="Recipient" variant="outlined" value={recipient} onChange={e => setRecpient(e.target.value)} /> </Stack> <Stack spacing={2} direction="column"> <PlaceAutoComplete description="Starting Point" value="Search for a location" id="start" handleChange={updateStartAddress} /> <PlaceAutoComplete description="Ending Point" value="Search for a location" id="end" handleChange={updateEndAddress} /> <LoadingButton variant="contained" loading={generating} onClick={generatePostcard}> Generate Postcard </LoadingButton> </Stack> </form> </Container> ); }