refactor(web): rename Organisation to Instance
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
import { api, auth as authApi, type OrgUser, type Role } from "@/lib/api";
|
||||
import { api, auth as authApi, type InstanceUser, type Role } from "@/lib/api";
|
||||
import { useAuth } from "@/components/AuthProvider";
|
||||
import { Badge, Button, Card, Modal, Table, Tbody, Td, Th, Thead, Tr } from "@/components/ui";
|
||||
|
||||
@@ -35,12 +35,12 @@ function MembersCard() {
|
||||
const [password, setPassword] = useState("");
|
||||
const [role, setRole] = useState<Role>("member");
|
||||
|
||||
const { data: users, isLoading, error } = useQuery({ queryKey: ["org-users"], queryFn: api.listOrgUsers });
|
||||
const { data: users, isLoading, error } = useQuery({ queryKey: ["instance-users"], queryFn: api.listInstanceUsers });
|
||||
|
||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["org-users"] });
|
||||
const invalidate = () => queryClient.invalidateQueries({ queryKey: ["instance-users"] });
|
||||
|
||||
const { mutate: createUser, isPending: creating, error: createError } = useMutation({
|
||||
mutationFn: () => api.createOrgUser({ email, password, role }),
|
||||
mutationFn: () => api.createInstanceUser({ email, password, role }),
|
||||
onSuccess: () => {
|
||||
invalidate();
|
||||
setAddOpen(false);
|
||||
@@ -51,7 +51,7 @@ function MembersCard() {
|
||||
});
|
||||
|
||||
const { mutate: changeRole, error: roleError } = useMutation({
|
||||
mutationFn: ({ userId, next }: { userId: string; next: Role }) => api.updateOrgUserRole(userId, next),
|
||||
mutationFn: ({ userId, next }: { userId: string; next: Role }) => api.updateInstanceUserRole(userId, next),
|
||||
onSuccess: invalidate,
|
||||
|
||||
|
||||
@@ -59,7 +59,7 @@ function MembersCard() {
|
||||
});
|
||||
|
||||
const { mutate: removeUser, error: removeError } = useMutation({
|
||||
mutationFn: (userId: string) => api.deleteOrgUser(userId),
|
||||
mutationFn: (userId: string) => api.deleteInstanceUser(userId),
|
||||
onSuccess: invalidate,
|
||||
});
|
||||
|
||||
@@ -76,7 +76,7 @@ function MembersCard() {
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-text-primary">Members</h2>
|
||||
<p className="mt-0.5 text-sm text-text-secondary">
|
||||
People with access to this organization. Owners and admins can manage settings.
|
||||
People with access to this instance. Owners and admins can manage settings.
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="primary" size="sm" onClick={() => setAddOpen(true)}>
|
||||
@@ -110,7 +110,7 @@ function MembersCard() {
|
||||
</Tr>
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{users.map((u: OrgUser) => {
|
||||
{users.map((u: InstanceUser) => {
|
||||
const isSelf = u.user_id === user?.user_id;
|
||||
|
||||
const locked = isSelf || (u.role === "owner" && !isOwner);
|
||||
@@ -149,7 +149,7 @@ function MembersCard() {
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (confirm(`Remove ${u.email} from this organization?`)) removeUser(u.user_id);
|
||||
if (confirm(`Remove ${u.email} from this instance?`)) removeUser(u.user_id);
|
||||
}}
|
||||
>
|
||||
Remove
|
||||
@@ -226,7 +226,7 @@ function MembersCard() {
|
||||
|
||||
function OIDCCard() {
|
||||
const queryClient = useQueryClient();
|
||||
const { data: cfg, isLoading } = useQuery({ queryKey: ["org-oidc"], queryFn: api.getOrgOIDC });
|
||||
const { data: cfg, isLoading } = useQuery({ queryKey: ["instance-oidc"], queryFn: api.getInstanceOIDC });
|
||||
|
||||
const [issuer, setIssuer] = useState("");
|
||||
const [clientId, setClientId] = useState("");
|
||||
@@ -247,9 +247,9 @@ function OIDCCard() {
|
||||
}, [cfg]);
|
||||
|
||||
const { mutate: save, isPending, error } = useMutation({
|
||||
mutationFn: () => api.saveOrgOIDC({ issuer, client_id: clientId, client_secret: clientSecret, enabled }),
|
||||
mutationFn: () => api.saveInstanceOIDC({ issuer, client_id: clientId, client_secret: clientSecret, enabled }),
|
||||
onSuccess: () => {
|
||||
queryClient.invalidateQueries({ queryKey: ["org-oidc"] });
|
||||
queryClient.invalidateQueries({ queryKey: ["instance-oidc"] });
|
||||
setClientSecret("");
|
||||
setSaved(true);
|
||||
setTimeout(() => setSaved(false), 3000);
|
||||
@@ -279,7 +279,7 @@ function OIDCCard() {
|
||||
<div className="mb-4">
|
||||
<h2 className="text-base font-semibold text-text-primary">Single Sign-On (OIDC)</h2>
|
||||
<p className="mt-0.5 text-sm text-text-secondary">
|
||||
Let members sign in with your identity provider. Users are provisioned into this organization on
|
||||
Let members sign in with your identity provider. Users are provisioned into this instance on
|
||||
first sign-in.
|
||||
</p>
|
||||
</div>
|
||||
@@ -357,7 +357,7 @@ function OIDCCard() {
|
||||
onChange={(e) => setEnabled(e.target.checked)}
|
||||
className="h-4 w-4 rounded border-border bg-surface-2 accent-accent"
|
||||
/>
|
||||
Enable SSO sign-in for this organization
|
||||
Enable SSO sign-in for this instance
|
||||
</label>
|
||||
|
||||
{enabled && !secretSet && !clientSecret && (
|
||||
@@ -383,8 +383,8 @@ function OIDCCard() {
|
||||
);
|
||||
}
|
||||
|
||||
export default function OrgSettingsPage() {
|
||||
const { org, isAdmin } = useAuth();
|
||||
export default function InstanceSettingsPage() {
|
||||
const { instance, isAdmin } = useAuth();
|
||||
|
||||
if (!isAdmin) {
|
||||
return (
|
||||
@@ -392,7 +392,7 @@ export default function OrgSettingsPage() {
|
||||
<Card className="max-w-lg">
|
||||
<h1 className="text-base font-semibold text-text-primary">You don't have access</h1>
|
||||
<p className="mt-1 text-sm text-text-secondary">
|
||||
Organization settings are available to owners and admins only. Ask an administrator if you need
|
||||
Instance settings are available to owners and admins only. Ask an administrator if you need
|
||||
access.
|
||||
</p>
|
||||
</Card>
|
||||
@@ -403,9 +403,9 @@ export default function OrgSettingsPage() {
|
||||
return (
|
||||
<div className="p-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-text-primary">Organization</h1>
|
||||
<h1 className="text-2xl font-bold text-text-primary">Instance</h1>
|
||||
<p className="mt-1 text-sm text-text-secondary">
|
||||
{org ? `Manage members and sign-in for ${org.name}.` : "Manage members and sign-in."}
|
||||
{instance ? `Manage members and sign-in for ${instance.name}.` : "Manage members and sign-in."}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,7 @@ import { NetworkBackground } from "@/components/NetworkBackground";
|
||||
export default function LoginPage() {
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [orgName, setOrgName] = useState("");
|
||||
const [instanceName, setInstanceName] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
@@ -20,7 +20,7 @@ export default function LoginPage() {
|
||||
window.location.href = "/setup";
|
||||
return;
|
||||
}
|
||||
if (s.org_name) setOrgName(s.org_name);
|
||||
if (s.instance_name) setInstanceName(s.instance_name);
|
||||
} catch {}
|
||||
try {
|
||||
await auth.me();
|
||||
@@ -52,7 +52,7 @@ export default function LoginPage() {
|
||||
<div className="mb-8 flex flex-col items-center gap-3">
|
||||
<Logo className="h-11 w-auto text-text-primary" />
|
||||
<h1 className="text-2xl font-semibold text-text-primary">Sign in to Vantage</h1>
|
||||
<h4 className="font-semibold text-text-secondary">Organisation: {orgName}</h4>
|
||||
<h4 className="font-semibold text-text-secondary">Instance: {instanceName}</h4>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
@@ -102,10 +102,10 @@ export default function LoginPage() {
|
||||
|
||||
<a href="/auth/oidc/start" className="block">
|
||||
<Button type="button" variant="secondary" className="w-full justify-center">
|
||||
Sign in with your organization's SSO
|
||||
Sign in with your instance's SSO
|
||||
</Button>
|
||||
</a>
|
||||
<p className="mt-3 text-center text-xs text-text-tertiary">SSO must be enabled for this organization by an administrator.</p>
|
||||
<p className="mt-3 text-center text-xs text-text-tertiary">SSO must be enabled for this instance by an administrator.</p>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+17
-17
@@ -8,17 +8,17 @@ import { Button, Card } from "@/components/ui";
|
||||
const MIN_PASSWORD_LENGTH = 8;
|
||||
|
||||
/**
|
||||
* Org hosts are `<slug>.vantage.<rest>` and the apex is `vantage.<rest>` (see
|
||||
* auth.hostSlug on the server). Build the new org's URL by prepending or
|
||||
* Instance hosts are `<slug>.vantage.<rest>` and the apex is `vantage.<rest>` (see
|
||||
* auth.hostSlug on the server). Build the new instance's URL by prepending or
|
||||
* replacing the leftmost label. Hosts that don't match that shape (localhost,
|
||||
* bare IPs) have no per-org subdomain, so stay put.
|
||||
* bare IPs) have no per-instance subdomain, so stay put.
|
||||
*
|
||||
* Setup runs on the apex, and the session cookie it sets is scoped to that
|
||||
* exact host by design org hosts must not share cookies. So the new owner is
|
||||
* sent to the org host's *login* page to sign in there, which is what puts a
|
||||
* session cookie on the host their org actually lives on.
|
||||
* exact host by design instance hosts must not share cookies. So the new owner is
|
||||
* sent to the instance host's *login* page to sign in there, which is what puts a
|
||||
* session cookie on the host their instance actually lives on.
|
||||
*/
|
||||
function orgLoginUrlForSlug(slug: string): string {
|
||||
function instanceLoginUrlForSlug(slug: string): string {
|
||||
if (typeof window === "undefined") return "/login";
|
||||
const { protocol, host } = window.location;
|
||||
const [hostname, port] = host.split(":");
|
||||
@@ -34,7 +34,7 @@ function orgLoginUrlForSlug(slug: string): string {
|
||||
}
|
||||
|
||||
export default function SetupPage() {
|
||||
const [orgName, setOrgName] = useState("");
|
||||
const [instanceName, setInstanceName] = useState("");
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
const [confirm, setConfirm] = useState("");
|
||||
@@ -54,9 +54,9 @@ export default function SetupPage() {
|
||||
isPending,
|
||||
error,
|
||||
} = useMutation({
|
||||
mutationFn: () => auth.bootstrap({ org_name: orgName, email, password }),
|
||||
mutationFn: () => auth.bootstrap({ instance_name: instanceName, email, password }),
|
||||
onSuccess: (res) => {
|
||||
setCreated({ slug: res.slug, loginUrl: orgLoginUrlForSlug(res.slug) });
|
||||
setCreated({ slug: res.slug, loginUrl: instanceLoginUrlForSlug(res.slug) });
|
||||
},
|
||||
});
|
||||
|
||||
@@ -83,13 +83,13 @@ export default function SetupPage() {
|
||||
<div className="flex min-h-screen items-center justify-center bg-background p-4">
|
||||
<div className="w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-text-primary">Organization created</h1>
|
||||
<h1 className="text-xl font-semibold text-text-primary">Instance created</h1>
|
||||
<p className="mt-1 text-sm text-text-secondary">Your owner account is ready. One more step to finish signing in.</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<p className="text-sm text-text-secondary">
|
||||
{created.slug} has its own address, and sign-in is kept separate per organization. Continue to your organization's sign-in page and log in with the email and password
|
||||
{created.slug} has its own address, and sign-in is kept separate per instance. Continue to your instance's sign-in page and log in with the email and password
|
||||
you just chose.
|
||||
</p>
|
||||
<code className="mt-3 block overflow-x-auto rounded bg-surface-2 px-2 py-1.5 font-mono text-xs text-text-primary">{created.loginUrl}</code>
|
||||
@@ -109,17 +109,17 @@ export default function SetupPage() {
|
||||
<div className="w-full max-w-md">
|
||||
<div className="mb-8 text-center">
|
||||
<h1 className="text-xl font-semibold text-text-primary">Welcome to Vantage</h1>
|
||||
<p className="mt-1 text-sm text-text-secondary">Create your organization and its owner account to get started.</p>
|
||||
<p className="mt-1 text-sm text-text-secondary">Create your instance and its owner account to get started.</p>
|
||||
</div>
|
||||
|
||||
<Card>
|
||||
<form onSubmit={handleSubmit} className="space-y-4">
|
||||
<div>
|
||||
<label htmlFor="org" className="mb-1.5 block text-sm font-medium text-text-secondary">
|
||||
Organization name
|
||||
<label htmlFor="instance" className="mb-1.5 block text-sm font-medium text-text-secondary">
|
||||
Instance name
|
||||
</label>
|
||||
<input id="org" type="text" required value={orgName} onChange={(e) => setOrgName(e.target.value)} className={inputClass} />
|
||||
<p className="mt-1 text-xs text-text-tertiary">Used to derive your organization's subdomain.</p>
|
||||
<input id="instance" type="text" required value={instanceName} onChange={(e) => setInstanceName(e.target.value)} className={inputClass} />
|
||||
<p className="mt-1 text-xs text-text-tertiary">Used to derive your instance's subdomain.</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
Reference in New Issue
Block a user