main.jsx — 완전히 작동하는 예제
로컬에서 오디언스를 구성하고 검토합니다. 아래 소스는 상태 관리와 유효성 검사를 모두 포함한 전체 React 엔트리입니다.
import React, { useState } from "react";
import { createRoot } from "react-dom/client";
import {
ThemeProvider,
defaultTheme,
Heading,
Text,
Paragraph,
Button,
TagInput,
TextInputField,
SelectField,
Alert,
Dialog,
Badge,
majorScale,
} from "evergreen-ui";
import "./evergreen.css";
const people = [
{ name: "Avery Stone", plan: "Pro", tags: ["active", "newsletter"] },
{ name: "Taylor Reed", plan: "Free", tags: ["newsletter"] },
{ name: "Jordan Park", plan: "Pro", tags: ["active"] },
{ name: "Morgan Lee", plan: "Team", tags: ["active", "newsletter"] },
{ name: "Casey Chen", plan: "Free", tags: ["trial"] },
];
function App() {
const [tags, setTags] = useState(["active"]),
[plan, setPlan] = useState("Any"),
[name, setName] = useState("Active customers"),
[error, setError] = useState(""),
[open, setOpen] = useState(false),
[saved, setSaved] = useState(null);
const matches = people.filter(
(p) =>
(plan === "Any" || p.plan === plan) &&
tags.every((t) => p.tags.includes(t)),
);
function changeTags(values) {
const unique = [
...new Set(values.map((v) => v.trim().toLowerCase()).filter(Boolean)),
];
if (unique.length > 4) {
setError("Use at most four tags.");
return false;
}
setTags(unique);
setError("");
setSaved(null);
}
return (
<ThemeProvider value={defaultTheme}>
<main className="audience">
<header>
<Text size={300}>WORKSPACE / AUDIENCES</Text>
<Heading size={700} marginTop={8}>
Build a focused audience
</Heading>
<Paragraph marginTop={8}>
Combine a plan and tags against five fictional records. No customer
data is loaded.
</Paragraph>
</header>
<div className="audience-grid">
<section className="rule-panel">
<Heading size={500} marginBottom={majorScale(3)}>
Audience rules
</Heading>
<TextInputField
label="Audience name"
value={name}
onChange={(e) => {
setName(e.target.value);
setSaved(null);
}}
/>
<SelectField
label="Plan"
value={plan}
onChange={(e) => {
setPlan(e.target.value);
setSaved(null);
}}
>
<option>Any</option>
<option>Free</option>
<option>Pro</option>
<option>Team</option>
</SelectField>
<label htmlFor="audience-tags">Required tags</label>
<Paragraph size={300} marginBottom={8}>
Press Enter or comma to add a tag. Every tag must match.
</Paragraph>
<TagInput
values={tags}
onChange={changeTags}
width="100%"
inputProps={{ id: "audience-tags", placeholder: "Add tag…" }}
/>
<Paragraph size={300} marginTop={8}>
Available sample tags: active, newsletter, trial.
</Paragraph>
{error && <Alert intent="danger" title={error} marginTop={16} />}
<Button
appearance="primary"
marginTop={24}
onClick={() => {
if (!name.trim()) {
setError("Name the audience before reviewing.");
return;
}
setError("");
setOpen(true);
}}
>
Review audience
</Button>
</section>
<section className="results-panel">
<div className="results-title">
<Heading size={500}>Matching sample records</Heading>
<Badge color="blue">
{matches.length} of {people.length}
</Badge>
</div>
{matches.length ? (
<div className="record-list">
{matches.map((p) => (
<article key={p.name}>
<div>
<Heading size={400}>{p.name}</Heading>
<Text size={300}>{p.plan} plan</Text>
</div>
<div className="tag-list">
{p.tags.map((t) => (
<Badge key={t} color="neutral">
{t}
</Badge>
))}
</div>
</article>
))}
</div>
) : (
<div className="empty">
<Heading size={500}>No matching sample records</Heading>
<Paragraph marginTop={8}>
Remove a tag or change the plan to broaden the selection.
</Paragraph>
<Button
marginTop={16}
onClick={() => {
setTags([]);
setPlan("Any");
setSaved(null);
}}
>
Clear rules
</Button>
</div>
)}
{saved && (
<Alert
intent="success"
title="Local audience saved"
marginTop={20}
>
{saved.name} · {saved.count} sample records. Nothing was sent to
Segment.
</Alert>
)}
</section>
</div>
<Dialog
isShown={open}
title="Review local audience"
onCloseComplete={() => setOpen(false)}
confirmLabel="Save locally"
onConfirm={() => {
setSaved({ name: name.trim(), count: matches.length });
setOpen(false);
}}
width={560}
>
<Paragraph>
<strong>{name}</strong> matches {matches.length} of {people.length}{" "}
fictional records.
</Paragraph>
<Paragraph marginTop={12}>
Plan: {plan}. Required tags: {tags.join(", ") || "none"}.
</Paragraph>
<Paragraph marginTop={12}>
This saves only a local snapshot; it does not create a Segment
audience.
</Paragraph>
</Dialog>
<footer>
Native Evergreen 7.1.9 React components. Original audience logic; no
analytics or customer service connection.
</footer>
</main>
</ThemeProvider>
);
}
createRoot(document.getElementById("root")).render(<App />);
- Vite React 프로젝트를 만들고 <div id="root"></div>를 유지한 다음, 이 파일을 src/main.jsx로 불러오세요.


