Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
node_modules
/.pnp
.pnp.js

package-lock.json
# testing
/coverage

Expand Down
4 changes: 4 additions & 0 deletions backend/native/backpack-api/src/db/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,8 @@ const transformUser = (
*/
export const createUser = async (
username: string,
firstName: string,
lastName: string,
blockchainPublicKeys: Array<{ blockchain: Blockchain; publicKey: string }>,
waitlistId?: string | null,
referrerId?: string
Expand Down Expand Up @@ -329,6 +331,8 @@ export const createUser = async (
{
object: {
username: username,
firstname: firstName,
lastname: lastName,
public_keys: {
data: blockchainPublicKeys.map((b) => ({
blockchain: b.blockchain,
Expand Down
4 changes: 3 additions & 1 deletion backend/native/backpack-api/src/routes/v1/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ router.get("/jwt/xnft", extractUserId, async (req, res) => {
* Create a new user.
*/
router.post("/", async (req, res) => {
const { username, waitlistId, blockchainPublicKeys } =
const { username, firstName, lastName, waitlistId, blockchainPublicKeys } =
CreateUserWithPublicKeys.parse(req.body);

// Validate all the signatures
Expand Down Expand Up @@ -182,6 +182,8 @@ router.post("/", async (req, res) => {

const user = await createUser(
username,
firstName,
lastName,
blockchainPublicKeys.map((b) => ({
...b,
// Cast blockchain to correct type
Expand Down
12 changes: 12 additions & 0 deletions backend/native/backpack-api/src/validation/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ export const BaseCreateUser = z.object({
/^[a-z0-9_]{3,15}$/,
"should be between 3-15 characters and can only contain numbers, letters, and underscores."
),
firstName: z
.string()
.regex(
/^[a-z]{1,50}$/,
"should be between 1-50 characters and can only contain letters."
),
lastName: z
.string()
.regex(
/^[a-z]{1,50}$/,
"should be between 1-50 characters and can only contain letters."
),
inviteCode: z
.string()
.regex(
Expand Down
1 change: 1 addition & 0 deletions backend/native/zeus/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"author": "",
"license": "ISC",
"dependencies": {
"graphql": "^16.8.1",
"tsc-alias": "^1.7.1",
"typescript": "~4.9.3"
},
Expand Down
37 changes: 27 additions & 10 deletions backend/native/zeus/src/zeus/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const handleFetchResponse = (response: Response): Promise<GraphQLResponse> => {
.catch(reject);
});
}
return response.json();
return response.json() as Promise<GraphQLResponse>;
};

export const apiFetch =
Expand Down Expand Up @@ -362,12 +362,17 @@ export const traverseResponse = ({
) {
return o;
}
return Object.fromEntries(
Object.entries(o).map(([k, v]) => [
k,
ibb(k, v, [...p, purifyGraphQLKey(k)]),
])
const entries = Object.entries(o).map(
([k, v]) => [k, ibb(k, v, [...p, purifyGraphQLKey(k)])] as const
);
const objectFromEntries = entries.reduce<Record<string, unknown>>(
(a, [k, v]) => {
a[k] = v;
return a;
},
{}
);
return objectFromEntries;
};
return ibb;
};
Expand Down Expand Up @@ -759,8 +764,8 @@ export const resolverFor = <
source: any
) => Z extends keyof ModelTypes[T]
? ModelTypes[T][Z] | Promise<ModelTypes[T][Z]> | X
: any
) => fn as (args?: any, source?: any) => any;
: never
) => fn as (args?: any, source?: any) => ReturnType<typeof fn>;

export type UnwrapPromise<T> = T extends Promise<infer R> ? R : T;
export type ZeusState<T extends (...args: any[]) => Promise<any>> = NonNullable<
Expand Down Expand Up @@ -816,9 +821,13 @@ type IsInterfaced<
: DST[P],
SCLR
>
: Record<string, unknown>
: IsArray<
R,
"__typename" extends keyof DST ? { __typename: true } : never,
SCLR
>
: never;
}[keyof DST] & {
}[keyof SRC] & {
[P in keyof Omit<
Pick<
SRC,
Expand Down Expand Up @@ -4317,6 +4326,7 @@ export type ValueTypes = {
["auth_users_constraint"]: auth_users_constraint;
/** input type for inserting data into table "auth.users" */
["auth_users_insert_input"]: {
firstname?: string | undefined | null | Variable<any, string>;
invitation?:
| ValueTypes["auth_invitations_obj_rel_insert_input"]
| undefined
Expand All @@ -4327,6 +4337,7 @@ export type ValueTypes = {
| undefined
| null
| Variable<any, string>;
lastname?: string | undefined | null | Variable<any, string>;
public_keys?:
| ValueTypes["auth_public_keys_arr_rel_insert_input"]
| undefined
Expand Down Expand Up @@ -11097,11 +11108,13 @@ export type ResolverInputTypes = {
["auth_users_constraint"]: auth_users_constraint;
/** input type for inserting data into table "auth.users" */
["auth_users_insert_input"]: {
firstname?: string | undefined | null;
invitation?:
| ResolverInputTypes["auth_invitations_obj_rel_insert_input"]
| undefined
| null;
invitation_id?: ResolverInputTypes["uuid"] | undefined | null;
lastname?: string | undefined | null;
public_keys?:
| ResolverInputTypes["auth_public_keys_arr_rel_insert_input"]
| undefined
Expand Down Expand Up @@ -16048,10 +16061,12 @@ export type ModelTypes = {
["auth_users_constraint"]: auth_users_constraint;
/** input type for inserting data into table "auth.users" */
["auth_users_insert_input"]: {
firstname?: string | undefined;
invitation?:
| ModelTypes["auth_invitations_obj_rel_insert_input"]
| undefined;
invitation_id?: ModelTypes["uuid"] | undefined;
lastname?: string | undefined;
public_keys?:
| ModelTypes["auth_public_keys_arr_rel_insert_input"]
| undefined;
Expand Down Expand Up @@ -18949,10 +18964,12 @@ export type GraphQLTypes = {
["auth_users_constraint"]: auth_users_constraint;
/** input type for inserting data into table "auth.users" */
["auth_users_insert_input"]: {
firstname?: string | undefined;
invitation?:
| GraphQLTypes["auth_invitations_obj_rel_insert_input"]
| undefined;
invitation_id?: GraphQLTypes["uuid"] | undefined;
lastname?: string | undefined;
public_keys?:
| GraphQLTypes["auth_public_keys_arr_rel_insert_input"]
| undefined;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ insert_permissions:
permission:
check: {}
columns:
- firstname
- invitation_id
- lastname
- referrer_id
- username
- waitlist_id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "auth"."users" add column "firstname" text
-- null;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
alter table "auth"."users" add column "firstname" text
null;
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "auth"."users" add column "lastname" text
-- null;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
alter table "auth"."users" add column "lastname" text
null;
Original file line number Diff line number Diff line change
Expand Up @@ -77,8 +77,8 @@ export const OnboardAccount = ({
<UsernameForm
key="UsernameForm"
inviteCode={inviteCode!}
onNext={(username) => {
setOnboardingData({ username });
onNext={(username, firstName, lastName) => {
setOnboardingData({ username, firstName, lastName });
nextStep();
}}
/>,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { type FormEvent, useCallback, useEffect, useState } from "react";
import { PrimaryButton,TextInput } from "@coral-xyz/react-common";
import { PrimaryButton, TextInput } from "@coral-xyz/react-common";
import { useCustomTheme } from "@coral-xyz/themes";
import { AlternateEmail } from "@mui/icons-material";
import { AccountCircle,AlternateEmail } from "@mui/icons-material";
import { Box, InputAdornment } from "@mui/material";

import { Header, SubtextParagraph } from "../../common";
Expand All @@ -11,21 +11,30 @@ export const UsernameForm = ({
onNext,
}: {
inviteCode: string;
onNext: (username: string) => void;
onNext: (username: string, firstName: string, lastName: string) => void;
}) => {
const [username, setUsername] = useState("");
const [lastName, setLastName] = useState("");
const [firstName, setFirstName] = useState("");
const [errFirstName, setErrFirstName] = useState("");
const [errLastName, setErrLastName] = useState("");
const [error, setError] = useState("");
const theme = useCustomTheme();

useEffect(() => {
setError("");
}, [username]);
setErrFirstName("");
setErrLastName("");
}, [username, firstName, lastName]);

const handleSubmit = useCallback(
async (e: FormEvent) => {
e.preventDefault();

try {
if (firstName === "" || lastName === "" || username === "") {
throw new Error("field empty");
}
const res = await fetch(`https://auth.xnfts.dev/users/${username}`, {
headers: {
"x-backpack-invite-code": String(inviteCode),
Expand All @@ -34,12 +43,29 @@ export const UsernameForm = ({
const json = await res.json();
if (!res.ok) throw new Error(json.message || "There was an error");

onNext(username);
onNext(username, firstName, lastName);
} catch (err: any) {
setError(err.message);
if (err.message === "field empty") {
if (firstName === "" && lastName === "" && username === "") {
setError("can't be empty");
setErrFirstName("can't be empty");
setErrLastName("can't be empty");
} else if (firstName === "" && lastName === "") {
setErrFirstName("can't be empty");
setErrLastName("can't be empty");
} else if (firstName === "" && username === "") {
setError("can't be empty");
setErrFirstName("can't be empty");
} else if (lastName === "" && username === "") {
setError("can't be empty");
setErrLastName("can't be empty");
} else if (firstName === "") setErrFirstName("can't be empty");
else if (lastName === "") setErrLastName("can't be empty");
else setError("can't be empty");
} else setError(err.message);
}
},
[username]
[username, firstName, lastName]
);

return (
Expand All @@ -53,9 +79,15 @@ export const UsernameForm = ({
justifyContent: "space-between",
}}
>
<Box style={{ margin: "24px" }}>
<Box
style={{
marginLeft: "16px",
marginRight: "16px",
marginBottom: "16px",
}}
>
<Header text="Claim your username" />
<SubtextParagraph style={{ margin: "16px 0" }}>
<SubtextParagraph style={{ margin: "8px 0" }}>
Others can see and find you by this username, and it will be
associated with your primary wallet address.
<br />
Expand All @@ -73,6 +105,66 @@ export const UsernameForm = ({
marginBottom: "16px",
}}
>
<Box style={{ marginBottom: "2px" }}>
<TextInput
inputProps={{
name: "firstname",
autoComplete: "off",
spellCheck: "false",
autoFocus: true,
}}
placeholder="First name"
type="text"
value={firstName}
setValue={(e) => {
setFirstName(e.target.value.toLowerCase().replace(/[^a-z]/g, ""));
}}
error={errFirstName ? true : false}
errorMessage={errFirstName}
startAdornment={
<InputAdornment position="start">
<AccountCircle
style={{
color: theme.custom.colors.secondary,
fontSize: 18,
marginRight: -2,
userSelect: "none",
}}
/>
</InputAdornment>
}
/>
</Box>
<Box style={{ marginBottom: "2px" }}>
<TextInput
inputProps={{
name: "lastname",
autoComplete: "off",
spellCheck: "false",
autoFocus: true,
}}
placeholder="Last name"
type="text"
value={lastName}
setValue={(e) => {
setLastName(e.target.value.toLowerCase().replace(/[^a-z]/g, ""));
}}
error={errLastName ? true : false}
errorMessage={errLastName}
startAdornment={
<InputAdornment position="start">
<AccountCircle
style={{
color: theme.custom.colors.secondary,
fontSize: 18,
marginRight: -2,
userSelect: "none",
}}
/>
</InputAdornment>
}
/>
</Box>
<Box style={{ marginBottom: "16px" }}>
<TextInput
inputProps={{
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -579,7 +579,7 @@ export const ALCHEMY_ETHEREUM_MAINNET_API_KEY =
"DlJr6QuBC2EaE-L60-iqQQGq9hi9-XSZ";

export const AVATAR_BASE_URL = "https://swr.xnfts.dev/avatars";
export const BACKEND_API_URL = "https://backpack-api.xnfts.dev";
export const BACKEND_API_URL = "http://localhost:8080";
export const REALTIME_API_URL = "https://backend-ws.xnfts.dev";
export const MESSAGING_COMMUNICATION_PUSH = "MESSAGING_COMMUNICATION_PUSH";
export const MESSAGING_COMMUNICATION_FETCH = "MESSAGING_COMMUNICATION_FETCH";
Expand Down
Loading