Files
next.js/examples/with-redis/app/form.tsx
Arian Tron 61f56f997c
Some checks failed
Test examples / Test Examples (20) (push) Has been cancelled
Test examples / Test Examples (22) (push) Has been cancelled
Lock Threads / action (push) Has been cancelled
Trigger Release / start (push) Has been cancelled
Stale issue handler / stale (push) Has been cancelled
Update Font Data / create-pull-request (push) Has been cancelled
build-and-deploy / deploy-target (push) Has been cancelled
build-and-deploy / build (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-musl - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-unknown-linux-gnu - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-pc-windows-msvc - node@16 (push) Has been cancelled
build-and-deploy / stable - aarch64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / stable - x86_64-apple-darwin - node@16 (push) Has been cancelled
build-and-deploy / build-wasm (nodejs) (push) Has been cancelled
build-and-deploy / build-wasm (web) (push) Has been cancelled
build-and-deploy / Deploy preview tarball (push) Has been cancelled
build-and-deploy / Potentially publish release (push) Has been cancelled
build-and-deploy / publish-turbopack-npm-packages (push) Has been cancelled
build-and-deploy / Deploy examples (push) Has been cancelled
build-and-deploy / thank you, build (push) Has been cancelled
build-and-deploy / Upload Turbopack Bytesize metrics to Datadog (push) Has been cancelled
Rspack Next.js development integration tests / Rspack integration tests (push) Has been cancelled
Rspack Next.js production integration tests / Rspack integration tests (push) Has been cancelled
Turbopack Next.js development integration tests / Next.js integration tests (push) Has been cancelled
Turbopack Next.js production integration tests / Next.js integration tests (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack development test manifest (push) Has been cancelled
Update Rspack test manifest / Update and upload Rspack production test manifest (push) Has been cancelled
Upload bundler test manifests to areweturboyet.com / Upload test results (push) Has been cancelled
Update React / create-pull-request (push) Has been cancelled
test-e2e-project-reset-cron / reset-test-project (push) Has been cancelled
Notify about the top 15 issues/PRs/feature requests (most reacted) in the last 90 days / run (push) Has been cancelled
first commit
2026-03-10 19:37:31 +03:30

186 lines
5.3 KiB
TypeScript

"use client";
import clsx from "clsx";
import { useOptimistic, useRef, useTransition } from "react";
import { saveFeature, upvote } from "./actions";
import { Feature } from "./types";
function Item({
isFirst,
isLast,
isReleased,
hasVoted,
feature,
pending,
mutate,
}: {
isFirst: boolean;
isLast: boolean;
isReleased: boolean;
hasVoted: boolean;
feature: Feature;
pending: boolean;
mutate: any;
}) {
let upvoteWithId = upvote.bind(null, feature);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let [isPending, startTransition] = useTransition();
return (
<form
action={upvoteWithId}
onSubmit={(event) => {
event.preventDefault();
startTransition(async () => {
mutate({
updatedFeature: {
...feature,
score: Number(feature.score) + 1,
},
pending: true,
});
await upvote(feature);
});
}}
className={clsx(
"p-6 mx-8 flex items-center border-t border-l border-r",
isFirst && "rounded-t-md",
isLast && "border-b rounded-b-md",
)}
>
<button
className={clsx(
"ring-1 ring-gray-200 rounded-full w-8 min-w-[2rem] h-8 mr-4 focus:outline-none focus:ring focus:ring-blue-300",
(isReleased || hasVoted) &&
"bg-green-100 cursor-not-allowed ring-green-300",
pending && "bg-gray-100 cursor-not-allowed",
)}
disabled={isReleased || hasVoted || pending}
type="submit"
>
{isReleased ? "✅" : "👍"}
</button>
<h3 className="text font-semibold w-full text-left">{feature.title}</h3>
<div className="bg-gray-200 text-gray-700 text-sm rounded-xl px-2 ml-2">
{feature.score}
</div>
</form>
);
}
type FeatureState = {
newFeature: Feature;
updatedFeature?: Feature;
pending: boolean;
};
export default function FeatureForm({ features }: { features: Feature[] }) {
let formRef = useRef<HTMLFormElement>(null);
let [state, mutate] = useOptimistic(
{ features, pending: false },
function createReducer(state, newState: FeatureState) {
if (newState.newFeature) {
return {
features: [...state.features, newState.newFeature],
pending: newState.pending,
};
} else {
return {
features: [
...state.features.filter(
(f) => f.id !== newState.updatedFeature!.id,
),
newState.updatedFeature,
] as Feature[],
pending: newState.pending,
};
}
},
);
let sortedFeatures = state.features.sort((a, b) => {
// First, compare by score in descending order
if (Number(a.score) > Number(b.score)) return -1;
if (Number(a.score) < Number(b.score)) return 1;
// If scores are equal, then sort by created_at in ascending order
return new Date(a.created_at).getTime() - new Date(b.created_at).getTime();
});
let featureStub = {
id: crypto.randomUUID(),
title: "", // will used value from form
created_at: new Date().toISOString(),
score: "1",
};
let saveWithNewFeature = saveFeature.bind(null, featureStub);
// eslint-disable-next-line @typescript-eslint/no-unused-vars
let [isPending, startTransition] = useTransition();
return (
<>
<div className="mx-8 w-full">
<form
className="relative my-8"
ref={formRef}
action={saveWithNewFeature}
onSubmit={(event) => {
event.preventDefault();
let formData = new FormData(event.currentTarget);
let newFeature = {
...featureStub,
title: formData.get("feature") as string,
};
formRef.current?.reset();
startTransition(async () => {
mutate({
newFeature,
pending: true,
});
await saveFeature(newFeature, formData);
});
}}
>
<input
aria-label="Suggest a feature for our roadmap"
className="pl-3 pr-28 py-3 mt-1 text-lg block w-full border border-gray-200 rounded-md text-gray-900 placeholder-gray-400 focus:outline-none focus:ring focus:ring-blue-300"
maxLength={150}
placeholder="I want..."
required
type="text"
name="feature"
disabled={state.pending}
/>
<button
className={clsx(
"flex items-center justify-center absolute right-2 top-2 px-4 h-10 text-lg border bg-black text-white rounded-md w-24 focus:outline-none focus:ring focus:ring-blue-300 focus:bg-gray-800",
state.pending && "bg-gray-700 cursor-not-allowed",
)}
type="submit"
disabled={state.pending}
>
Request
</button>
</form>
</div>
<div className="w-full">
{sortedFeatures.map((feature: any, index: number) => (
<Item
key={feature.id}
isFirst={index === 0}
isLast={index === sortedFeatures.length - 1}
isReleased={false}
hasVoted={false}
feature={feature}
pending={state.pending}
mutate={mutate}
/>
))}
</div>
</>
);
}