Skip to content

Commit be64a81

Browse files
authored
Merge branch 'main' into renovate/major-internal
2 parents 3d7b506 + 72423d2 commit be64a81

17 files changed

Lines changed: 637 additions & 446 deletions

File tree

static/js/publisher/components/RegisteredSnaps/RegisteredSnaps.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function RegisteredSnaps({
4040
width: "25%",
4141
content: (
4242
<>
43-
<Link to={`/${snap.snapName}/listing`}>{snap.snapName}</Link>
43+
{snap.snapName}
4444
{isDisputePending && (
4545
<>
4646
&nbsp;
@@ -62,7 +62,7 @@ function RegisteredSnaps({
6262
{
6363
content: isUsersSnap ? (
6464
<button
65-
className="p-button--base u-no-margin--bottom"
65+
className="p-button--base u-no-margin--bottom is-dense"
6666
onClick={() => {
6767
setUnregisterSnapModal(snap.snapName);
6868
}}
@@ -75,7 +75,7 @@ function RegisteredSnaps({
7575
message={"Snaps can only be unregistered by their owner."}
7676
>
7777
<button
78-
className="u-no-margin--bottom u-no-margin--right"
78+
className="u-no-margin--bottom u-no-margin--right is-dense"
7979
disabled
8080
>
8181
Unregister

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

Lines changed: 12 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`);
@@ -48,6 +48,15 @@ function Listing(): JSX.Element {
4848
</Strip>
4949
)}
5050

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

0 commit comments

Comments
 (0)