mirror of
https://github.com/MetaFam/TheGame.git
synced 2026-04-24 03:00:09 -04:00
* incorporating configuration panes from #1078 * standardizing player hero subcomponents & removing `owner` var from `useProfileField` 🚪 * switching box type, ambiguating overly-specific names, & massaging heights 📱 * reordering profile details sections, updating deployment environment, & conditionally wrapping the hero elements 🎬 * fixing render of color disposition selector 🕍 * self code review changes: removed some `;`s 🎋 * getting yarn typecheck && yarn lint to pass post rebase 🏇🏾 * handling "missing <div> in <button>" error with mounted check & setting HTTP return codes for OpenSea API endpoint 🕺 * `ProfileWizard` extends `Wizard`, roles display better on mobile, & pronouns use `ProfileWizardPane` 🍊 * properly encapsulating ETH address regex 🚲 * adding some tasks 🏥 * fixed skills layou * handling default values? ❔ * corrections for revision comments from @dan13ram (UI bugs to follow) 🌋 * cleaning up memberships & explorer type 🧫 * refactoring player roles configuration and display to use `WizardPane` 🔮 * bunches of mobile fixes & repairing the display of deserialized skills 📟 * removing redirect in static props & formatting memberships display for responsiveness 🪆 * improving comprehensability of `/me` & more mobile tweaking 🍦 * various spacing fixes & a “try again” button for connecting 🫕 * maybe fixed issue with saving images for fields with default values 🇩🇿 * switched roles selection to a bounded `SimpleGrid` to fix sizing weirdness 🧰 * fix: removed verify on brightId button * fix: clean up username page * formatting & fixing skills issues 🥩 * reformatting NFT display 🚂 * adding `/join` 🚉 * style: peth's suggestions * added mainnet required message * style: slight tweak of megamenu item positions * chore(release): 0.2.0 Co-authored-by: tenfinney <scott@1box.onmicrosoft.com> Co-authored-by: dan13ram <dan13ram@gmail.com> Co-authored-by: vidvidvid <weetopol@gmail.com>
230 lines
6.8 KiB
TypeScript
230 lines
6.8 KiB
TypeScript
/* eslint-disable no-console */
|
|
|
|
import {
|
|
BasicProfile,
|
|
model as basicProfileModel,
|
|
} from '@datamodels/identity-profile-basic';
|
|
import { ModelManager } from '@glazed/devtools';
|
|
import { DIDDataStore } from '@glazed/did-datastore';
|
|
import { TileLoader } from '@glazed/tile-loader';
|
|
import {
|
|
AllProfileFields,
|
|
BasicProfileImages,
|
|
BasicProfileStrings,
|
|
ExtendedProfile,
|
|
ExtendedProfileImages,
|
|
extendedProfileModel,
|
|
ExtendedProfileObjects,
|
|
ExtendedProfileStrings,
|
|
HasuraEPObjects,
|
|
HasuraImageSourcedProps,
|
|
HasuraProfileProps,
|
|
HasuraStringProps,
|
|
Maybe,
|
|
Values,
|
|
} from '@metafam/utils';
|
|
import { useProfileField } from 'lib/hooks/useField';
|
|
import { useUser } from 'lib/hooks/useUser';
|
|
import { useWeb3 } from 'lib/hooks/useWeb3';
|
|
import { useRouter } from 'next/router';
|
|
import { ReactElement, useCallback } from 'react';
|
|
import { isEmpty } from 'utils/objectHelpers';
|
|
import { dispositionFor } from 'utils/playerHelpers';
|
|
|
|
export class CeramicError extends Error {
|
|
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
|
constructor(message: string) {
|
|
super(message);
|
|
}
|
|
}
|
|
|
|
export const useSaveCeramicProfile = ({
|
|
debug = false,
|
|
setStatus = () => {},
|
|
fields = Object.keys(AllProfileFields),
|
|
}: {
|
|
debug?: boolean;
|
|
setStatus?: (msg?: Maybe<ReactElement | string>) => void;
|
|
fields?: Array<string>;
|
|
} = {}) => {
|
|
const router = useRouter();
|
|
const { ceramic } = useWeb3();
|
|
const { user } = useUser();
|
|
|
|
debug ||= !!router.query.debug; // eslint-disable-line no-param-reassign
|
|
|
|
const setters = Object.fromEntries(
|
|
fields.map((key) => {
|
|
const { setter = null } =
|
|
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
useProfileField({
|
|
field: key,
|
|
player: user,
|
|
});
|
|
return [key, setter];
|
|
}),
|
|
);
|
|
|
|
const save = useCallback(
|
|
async ({
|
|
values = {},
|
|
images = {},
|
|
}: {
|
|
values?: HasuraStringProps & HasuraEPObjects;
|
|
images?: HasuraImageSourcedProps;
|
|
}) => {
|
|
if (!ceramic) {
|
|
throw new CeramicError(
|
|
'Unable to connect to the Ceramic API to save changes.',
|
|
);
|
|
}
|
|
|
|
if (!ceramic.did?.authenticated) {
|
|
try {
|
|
setStatus('Authenticating DID…');
|
|
await ceramic.did?.authenticate();
|
|
} catch (err) {
|
|
if (
|
|
(err as Error).message ===
|
|
'Unexpected token u in JSON at position 0'
|
|
) {
|
|
throw new CeramicError('User canceled authentication.');
|
|
}
|
|
throw err;
|
|
}
|
|
}
|
|
|
|
const vals: HasuraProfileProps = { ...values };
|
|
|
|
const cache = new Map();
|
|
const loader = new TileLoader({ ceramic, cache });
|
|
const manager = new ModelManager(ceramic);
|
|
manager.addJSONModel(basicProfileModel);
|
|
manager.addJSONModel(extendedProfileModel);
|
|
|
|
const store = new DIDDataStore({
|
|
ceramic,
|
|
loader,
|
|
model: await manager.toPublished(),
|
|
});
|
|
|
|
// empty string fails validation
|
|
['countryCode', 'birthDate'].forEach((prop) => {
|
|
const key = prop as keyof typeof BasicProfileStrings;
|
|
if (vals[key] === '') {
|
|
delete vals[key];
|
|
}
|
|
});
|
|
|
|
const { countryCode: code }: { countryCode?: string } = vals;
|
|
if (code?.length === 2) {
|
|
vals.countryCode = code.toUpperCase();
|
|
} else if (code) {
|
|
throw new CeramicError(
|
|
`Country Code “${code}” is not the required two letters.`,
|
|
);
|
|
}
|
|
|
|
const basic: BasicProfile = {};
|
|
const extended: ExtendedProfile = {};
|
|
|
|
setStatus('Updating Basic Profile…');
|
|
|
|
Object.entries(BasicProfileStrings).forEach(([hasuraId, ceramicId]) => {
|
|
const fromKey = ceramicId as Values<typeof BasicProfileStrings>;
|
|
const toKey = hasuraId as keyof typeof BasicProfileStrings;
|
|
if (vals[toKey] !== undefined) {
|
|
basic[fromKey] = vals[toKey] ?? undefined;
|
|
}
|
|
});
|
|
|
|
Object.entries(BasicProfileImages).forEach(([hasuraId, ceramicId]) => {
|
|
const fromKey = ceramicId as Values<typeof BasicProfileImages>;
|
|
const toKey = hasuraId as keyof typeof BasicProfileImages;
|
|
if (images[toKey] !== undefined) {
|
|
basic[fromKey] = images[toKey] ?? undefined;
|
|
vals[toKey] = images[toKey]?.original.src;
|
|
}
|
|
});
|
|
|
|
if (!isEmpty(basic)) {
|
|
const basRes = await store.merge('basicProfile', basic);
|
|
if (debug) {
|
|
console.debug('Basic Profile:', basRes.toUrl());
|
|
}
|
|
}
|
|
|
|
setStatus('Updating Extended Profile…');
|
|
|
|
Object.entries(ExtendedProfileStrings).forEach(
|
|
([hasuraId, ceramicId]) => {
|
|
const fromKey = ceramicId as Values<typeof ExtendedProfileStrings>;
|
|
const toKey = hasuraId as keyof typeof ExtendedProfileStrings;
|
|
if (vals[toKey] !== undefined) {
|
|
extended[fromKey] = vals[toKey];
|
|
}
|
|
},
|
|
);
|
|
|
|
Object.entries(ExtendedProfileImages).forEach(([hasuraId, ceramicId]) => {
|
|
const fromKey = ceramicId as Values<typeof ExtendedProfileImages>;
|
|
const toKey = hasuraId as keyof typeof ExtendedProfileImages;
|
|
if (images[toKey] !== undefined) {
|
|
extended[fromKey] = images[toKey];
|
|
vals[toKey] = images[toKey]?.original.src;
|
|
}
|
|
});
|
|
|
|
Object.entries(ExtendedProfileObjects).forEach(
|
|
([hasuraId, ceramicId]) => {
|
|
const fromKey = ceramicId as Values<typeof ExtendedProfileObjects>;
|
|
const toKey = hasuraId as keyof typeof ExtendedProfileObjects;
|
|
if (vals[toKey] !== undefined) {
|
|
switch (fromKey) {
|
|
case 'availableHours': {
|
|
extended[fromKey] = vals[toKey] as number;
|
|
break;
|
|
}
|
|
case 'magicDisposition': {
|
|
extended[fromKey] = dispositionFor(vals.colorMask) ?? undefined;
|
|
break;
|
|
}
|
|
default: {
|
|
console.warn(`Unknown Profile Key: "${fromKey}"`);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
);
|
|
|
|
if (debug) {
|
|
console.debug({ values, images, basic, extended });
|
|
}
|
|
|
|
if (!isEmpty(extended)) {
|
|
const extRes = await store.merge('extendedProfile', extended);
|
|
if (debug) {
|
|
console.debug('Extended Profile:', extRes.toUrl());
|
|
}
|
|
}
|
|
|
|
setStatus('Updating Local State…');
|
|
Object.entries(vals).forEach(([hasuraId, value]) => {
|
|
if (setters[hasuraId] != null) {
|
|
if (debug) {
|
|
console.debug(`Setting ${hasuraId} to “${value}”.`);
|
|
}
|
|
setters[hasuraId]?.(value);
|
|
} else {
|
|
console.warn(`Missing setter for ${hasuraId}.`, { setters });
|
|
}
|
|
});
|
|
|
|
return vals;
|
|
},
|
|
[ceramic, debug, setStatus, setters],
|
|
);
|
|
|
|
return save;
|
|
};
|