Skip to content

Commit 6239201

Browse files
committed
chore: Apply application layout to register snap page
1 parent 717115b commit 6239201

16 files changed

Lines changed: 587 additions & 450 deletions

File tree

static/js/publisher/pages/Listing/Listing.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { useParams } from "react-router-dom";
1+
import { useParams, Link } from "react-router-dom";
22
import { useQuery } from "react-query";
3-
import { Strip } from "@canonical/react-components";
3+
import { Strip, Notification } from "@canonical/react-components";
44

55
import SectionNav from "../../components/SectionNav";
66
import ListingForm from "./ListingForm";
@@ -9,7 +9,7 @@ import { setPageTitle } from "../../utils";
99

1010
function Listing(): JSX.Element {
1111
const { snapId } = useParams();
12-
const { data, isLoading, refetch } = useQuery({
12+
const { data, isLoading, refetch, status } = useQuery({
1313
queryKey: ["listing"],
1414
queryFn: async () => {
1515
const response = await fetch(`/api/${snapId}/listing`);
@@ -29,6 +29,7 @@ function Listing(): JSX.Element {
2929
});
3030

3131
setPageTitle(`Listing data for ${snapId}`);
32+
console.log("status", status);
3233

3334
return (
3435
<>
@@ -48,6 +49,15 @@ function Listing(): JSX.Element {
4849
</Strip>
4950
)}
5051

52+
{!isLoading && status === "error" && (
53+
<Strip shallow>
54+
<Notification severity="information">
55+
Editing listing data is not available for this snap. Make sure{" "}
56+
<Link to="/snaps">this snap is published</Link>.
57+
</Notification>
58+
</Strip>
59+
)}
60+
5161
{data && <ListingForm data={data} refetch={refetch} />}
5262
</>
5363
);
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { ReactNode, useState } from "react";
2+
import { Link } from "react-router-dom";
3+
import { Notification } from "@canonical/react-components";
4+
5+
import RegisterSnapForm from "./RegisterSnapForm";
6+
7+
import { filterAvailableStores } from "../../utils";
8+
9+
import { useBrandStores } from "../../hooks";
10+
11+
import type { RegistrationResponse } from "./local-types";
12+
13+
function RegisterSnap(): ReactNode {
14+
const [isSending, setIsSending] = useState<boolean>(false);
15+
const [registrationResponse, setRegistrationResponse] =
16+
useState<RegistrationResponse>();
17+
const { data, isLoading } = useBrandStores();
18+
19+
const availableStores = filterAvailableStores(data || []);
20+
21+
return (
22+
<div className="u-fixed-width">
23+
{registrationResponse?.error_code === "already_registered" ||
24+
registrationResponse?.error_code === "already_claimed" ? (
25+
<>
26+
<h1 className="p-heading--2">
27+
<strong>{registrationResponse.snap_name}</strong> is already taken
28+
</h1>
29+
30+
<Notification severity="caution">
31+
Another publisher already registered{" "}
32+
<strong>{registrationResponse.snap_name}</strong>. You can{" "}
33+
<Link
34+
to={`/register-name-dispute?snap-name=${registrationResponse.snap_name}&store=${registrationResponse.store}`}
35+
>
36+
file a dispute
37+
</Link>{" "}
38+
to request a transfer of ownership or register a new name below.
39+
</Notification>
40+
</>
41+
) : (
42+
<h1 className="p-heading--2">Register snap</h1>
43+
)}
44+
45+
{registrationResponse?.error_code === "name-review-required" && (
46+
<Notification severity="positive">
47+
<strong>{registrationResponse?.snap_name}</strong> has been submitted
48+
for review
49+
</Notification>
50+
)}
51+
52+
{registrationResponse?.error_code === "already_owned" && (
53+
<Notification severity="caution">
54+
You already own '
55+
<Link to={`/${registrationResponse.snap_name}/listing`}>
56+
<strong>{registrationResponse.snap_name}</strong>
57+
</Link>
58+
'.
59+
</Notification>
60+
)}
61+
62+
{registrationResponse?.error_code === "reserved_name" && (
63+
<Notification severity="caution">
64+
'<strong>{registrationResponse.snap_name}</strong>' is reserved. You
65+
can{" "}
66+
<a
67+
href={`/request-reserved-name?snap_name=${registrationResponse.snap_name}&store=${registrationResponse.store}&is_private=${registrationResponse.is_private}`}
68+
>
69+
request a reserved name
70+
</a>{" "}
71+
or register a new name below.
72+
</Notification>
73+
)}
74+
75+
{registrationResponse?.error_code === "no-permission" && (
76+
<Notification severity="caution">
77+
You do not have permission to register snaps to this store. Contact
78+
the store administrator.
79+
</Notification>
80+
)}
81+
82+
{registrationResponse?.error_code !== "already_registered" && (
83+
<p>
84+
Before you can push your snap to the store, its name must be
85+
registered
86+
</p>
87+
)}
88+
89+
<Notification severity="information">
90+
Snap name registrations are subject to manual review. You will be able
91+
to upload your snap and update its metadata, but you will not be able to
92+
make the Snap public until the review has been completed. We aim to
93+
review all registrations within 30 days
94+
</Notification>
95+
96+
{!isLoading && availableStores.length > 0 && (
97+
<RegisterSnapForm
98+
isSending={isSending}
99+
setIsSending={setIsSending}
100+
setRegistrationResponse={setRegistrationResponse}
101+
availableStores={availableStores}
102+
/>
103+
)}
104+
</div>
105+
);
106+
}
107+
108+
export default RegisterSnap;
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import { ReactNode, useState, Dispatch, SetStateAction } from "react";
2+
import { Link, useNavigate } from "react-router-dom";
3+
import {
4+
Form,
5+
Select,
6+
Input,
7+
Button,
8+
Icon,
9+
Row,
10+
Col,
11+
} from "@canonical/react-components";
12+
13+
import type { RegistrationResponse } from "./local-types";
14+
15+
type Props = {
16+
isSending: boolean;
17+
setIsSending: Dispatch<SetStateAction<boolean>>;
18+
setRegistrationResponse: Dispatch<
19+
SetStateAction<RegistrationResponse | undefined>
20+
>;
21+
availableStores: { name: string; id: string }[];
22+
};
23+
24+
function RegisterSnapForm({
25+
isSending,
26+
setIsSending,
27+
setRegistrationResponse,
28+
availableStores,
29+
}: Props): ReactNode {
30+
const [snapName, setSnapName] = useState<string>();
31+
const [selectedStore, setSelectedStore] = useState<string>("ubuntu");
32+
const [isPrivate, setIsPrivate] = useState<string>("private");
33+
34+
const navigate = useNavigate();
35+
36+
return (
37+
<Form
38+
onSubmit={async (e) => {
39+
e.preventDefault();
40+
41+
setIsSending(true);
42+
43+
const formData = new FormData();
44+
formData.set("csrf_token", window.CSRF_TOKEN);
45+
formData.set("snap_name", snapName || "");
46+
formData.set("store", selectedStore);
47+
48+
const response = await fetch("/api/register-snap", {
49+
method: "POST",
50+
headers: {
51+
"X-CSRFToken": window.CSRF_TOKEN,
52+
},
53+
body: formData,
54+
});
55+
56+
if (!response.ok) {
57+
setIsSending(false);
58+
throw new Error("Unable to register snap name");
59+
}
60+
61+
const responseData = await response.json();
62+
63+
setTimeout(() => {
64+
if (responseData.success) {
65+
navigate("/snaps");
66+
} else {
67+
setRegistrationResponse(responseData.data);
68+
setIsSending(false);
69+
}
70+
}, 1000);
71+
}}
72+
>
73+
<Row>
74+
<Col size={8}>
75+
<Select
76+
label="Store"
77+
options={availableStores.map((store) => ({
78+
label: store.name,
79+
value: store.id,
80+
}))}
81+
onChange={(e) => {
82+
setSelectedStore(e.target.value);
83+
84+
if (e.target.value === "ubuntu") {
85+
setIsPrivate("private");
86+
}
87+
}}
88+
required
89+
/>
90+
<Input
91+
type="text"
92+
label="Snap name"
93+
defaultValue={snapName}
94+
onChange={(e) => {
95+
setSnapName(e.target.value);
96+
}}
97+
required
98+
/>
99+
<label htmlFor="public">Snap privacy</label>
100+
<p className="p-form-help-text">
101+
This can be changed at any time after the initial upload
102+
</p>
103+
<Input
104+
type="radio"
105+
label="Public"
106+
name="public"
107+
disabled={selectedStore === "ubuntu"}
108+
value="public"
109+
checked={isPrivate === "public"}
110+
onChange={(e) => {
111+
setIsPrivate(e.target.value);
112+
}}
113+
/>
114+
<Input
115+
type="radio"
116+
label="Private"
117+
name="public"
118+
help="Snap is hidden in stores and only accessible by the publisher and collaborators"
119+
disabled={selectedStore === "ubuntu"}
120+
value="private"
121+
checked={isPrivate === "private" || selectedStore === "ubuntu"}
122+
onChange={(e) => {
123+
setIsPrivate(e.target.value);
124+
}}
125+
/>
126+
</Col>
127+
</Row>
128+
<hr />
129+
<div className="u-align--right">
130+
<Button
131+
type="submit"
132+
appearance="positive"
133+
disabled={!snapName || isSending}
134+
>
135+
{isSending ? (
136+
<>
137+
<Icon name="spinner" className="u-animation--spin" light />
138+
&nbsp;Registering
139+
</>
140+
) : (
141+
<>Register</>
142+
)}
143+
</Button>
144+
<Link className="p-button" to="/snaps">
145+
Cancel
146+
</Link>
147+
</div>
148+
</Form>
149+
);
150+
}
151+
152+
export default RegisterSnapForm;

0 commit comments

Comments
 (0)