mirror of
https://github.com/MetaFam/TheGame.git
synced 2026-04-24 03:00:09 -04:00
231 lines
6.8 KiB
TypeScript
231 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,
|
|
owner: true,
|
|
});
|
|
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;
|
|
};
|