Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functionality to edit or create profile #1071

Merged
merged 8 commits into from
Jan 6, 2025
Merged
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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"@jest/globals": "^29.7.0",
"@tsconfig/docusaurus": "^2.0.3",
"@types/jest": "^29.5.14",
"@types/lodash": "^4.17.14",
"@typescript-eslint/eslint-plugin": "^8.11.0",
"concurrently": "^8.2.1",
"eslint": "^8.0.1",
Expand Down
2 changes: 1 addition & 1 deletion src/cadence/contracts/GoldStar.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ contract GoldStar {

access(all)
resource Profile {
access(all)
access(mapping Identity)
var handle: ProfileHandle

access(all)
Expand Down
8 changes: 6 additions & 2 deletions src/cadence/transactions/GoldStar/UpdateProfile.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ transaction(
deployedContracts: {Address: [String]},
socials: {String: String}
) {
let profile: auth(GoldStar.UpdateSocials, GoldStar.UpdateDeployedContracts, GoldStar.UpdateReferralSource) &GoldStar.Profile
let profile: auth(GoldStar.UpdateHandle, GoldStar.UpdateSocials, GoldStar.UpdateDeployedContracts, GoldStar.UpdateReferralSource) &GoldStar.Profile

prepare(signer: auth(BorrowValue) &Account) {
self.profile = signer.storage
.borrow<auth(GoldStar.UpdateSocials, GoldStar.UpdateDeployedContracts, GoldStar.UpdateReferralSource) &GoldStar.Profile>(from: GoldStar.profileStoragePath)
.borrow<auth(GoldStar.UpdateHandle, GoldStar.UpdateSocials, GoldStar.UpdateDeployedContracts, GoldStar.UpdateReferralSource) &GoldStar.Profile>(from: GoldStar.profileStoragePath)
?? panic("missing profile")
}

execute {
// Update the handle
self.profile.handle.update(newHandle: handle)

// Update the referral source
if let referralSource = referralSource {
self.profile.updateReferralSource(source: referralSource)
}
Expand Down
16 changes: 13 additions & 3 deletions src/components/ConnectButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,20 @@ const ConnectButton: React.FC = () => {
return (
<>
<Dropdown buttonLabel={displayAddress} items={dropdownItems} />
<ProgressModal isOpen={isProgressModalOpen} onClose={handleCloseProgressModal} />
<ProfileModal isOpen={isProfileModalOpen} onClose={handleCloseProfileModal} />
<ProgressModal
isOpen={isProgressModalOpen}
onClose={handleCloseProgressModal}
onOpenProfileModal={() => {
handleCloseProgressModal();
handleOpenProfileModal();
}}
/>
<ProfileModal
isOpen={isProfileModalOpen}
onClose={handleCloseProfileModal}
/>
</>
)
);
};

export default ConnectButton;
108 changes: 97 additions & 11 deletions src/components/ProfileModal.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React, { useState } from 'react';
import React, { useEffect, useState } from 'react';
import Input from '@site/src/ui/design-system/src/lib/Components/Input';
import Field from '@site/src/ui/design-system/src/lib/Components/Field';
import Modal from '@site/src/ui/design-system/src/lib/Components/Modal';
import RadioGroup from '@site/src/ui/design-system/src/lib/Components/RadioGroup';
import { Button } from '@site/src/ui/design-system/src/lib/Components/Button';
import { ProfileSettings, SocialType } from '../types/gold-star';
import { useProfile } from '../hooks/use-profile';
import { useCurrentUser } from '../hooks/use-current-user';
import { createProfile, setProfile } from '../utils/gold-star';
import { isEqual } from 'lodash';
import RemovableTag from '@site/src/ui/design-system/src/lib/Components/RemovableTag';

interface ProfileModalProps {
Expand All @@ -21,10 +26,36 @@ const flowSources = [
];

const ProfileModal: React.FC<ProfileModalProps> = ({ isOpen, onClose }) => {
const [selectedSource, setSelectedSource] = useState(flowSources[0].name);
const { user } = useCurrentUser();
const {
profile,
isLoading,
error,
mutate: mutateProfile,
} = useProfile(user.addr);
const [settings, setSettings] = useState<ProfileSettings>({
handle: '',
socials: {},
referralSource: '',
deployedContracts: {},
});
const [loaded, setLoaded] = useState(false);
const [isSaving, setIsSaving] = useState(false);
const [tags, setTags] = useState<string[]>([]);
const [tagInput, setTagInput] = useState('');

useEffect(() => {
if (profile && !loaded && !isLoading && !error) {
setSettings({
handle: profile.handle,
socials: profile.socials,
referralSource: profile.referralSource,
deployedContracts: profile.deployedContracts,
});
setLoaded(true);
}
}, [profile, settings, loaded, isLoading, error]);

const handleAddTag = () => {
if (tagInput.trim() && !tags.includes(tagInput.trim())) {
setTags([...tags, tagInput.trim()]);
Expand All @@ -36,19 +67,66 @@ const ProfileModal: React.FC<ProfileModalProps> = ({ isOpen, onClose }) => {
setTags(tags.filter((tag) => tag !== tagToRemove));
};

async function handleSave() {
if (!settings) return;

setIsSaving(true);
try {
if (profile) {
await setProfile(settings);
} else {
await createProfile(settings);
}
} catch (e) {
console.error(e);
} finally {
setIsSaving(false);
mutateProfile();
}
}

function hasChanges() {
return (
!isEqual(profile?.handle, settings?.handle) ||
!isEqual(profile?.socials, settings?.socials) ||
!isEqual(profile?.referralSource, settings?.referralSource) ||
!isEqual(profile?.deployedContracts, settings?.deployedContracts)
);
}

return (
<Modal isOpen={isOpen} onClose={onClose} title="Profile">
<div className="space-y-6">
<div className="space-y-4">
<Field label="Username" description="What should we call you?">
<Input name="username" placeholder="johndoe" />
<Input
name="username"
placeholder="johndoe"
value={settings?.handle || ''}
onChange={(e) =>
setSettings({ ...settings, handle: e.target.value })
}
/>
</Field>
<Field label="Github Handle" description="What's your Github handle?">
<Input name="profile_handle" placeholder="joedoecodes" />
<Input
name="profile_handle"
placeholder="joedoecodes"
value={settings?.socials?.[SocialType.GITHUB] || ''}
onChange={(e) =>
setSettings({
...settings,
socials: { [SocialType.GITHUB]: e.target.value },
})
}
/>
</Field>
</div>

<Field label="Contracts Deployed" description="Add your contracts in the canonical format (A.0x123.Foobar).">
<Field
label="Contracts Deployed"
description="Add your contracts in the canonical format (A.0x123.Foobar)."
>
<div className="space-y-2">
<div className="flex items-center gap-2">
<Input
Expand Down Expand Up @@ -77,18 +155,26 @@ const ProfileModal: React.FC<ProfileModalProps> = ({ isOpen, onClose }) => {
<div className="max-w-sm mx-auto">
<RadioGroup
options={flowSources.map((source) => source.name)}
value={selectedSource}
onChange={setSelectedSource}
value={settings?.referralSource || ''}
onChange={(value) =>
setSettings({ ...settings, referralSource: value })
}
label="How did you find Flow?"
getDescription={(option) =>
flowSources.find((source) => source.name === option)?.description || ''
flowSources.find((source) => source.name === option)
?.description || ''
}
/>
</div>

<div className="flex justify-center">
<Button size="sm" className="w-full max-w-md" onClick={onClose}>
Close
<div className="flex flex-col space-y-2">
<Button
size="sm"
className="w-full max-w-md"
onClick={handleSave}
disabled={!hasChanges() || !settings || isSaving}
>
Save
</Button>
</div>
</div>
Expand Down
13 changes: 7 additions & 6 deletions src/components/ProgressModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,14 @@ import { SocialType } from '../types/gold-star';
interface ProgressModalProps {
isOpen: boolean;
onClose: () => void;
onOpenProfileModal: () => void;
}

const ProgressModal: React.FC<ProgressModalProps> = ({ isOpen, onClose }) => {
const ProgressModal: React.FC<ProgressModalProps> = ({
isOpen,
onClose,
onOpenProfileModal,
}) => {
const user = useCurrentUser();
const { profile } = useProfile(user.user.addr);

Expand All @@ -38,10 +43,6 @@ const ProgressModal: React.FC<ProgressModalProps> = ({ isOpen, onClose }) => {
{ label: 'Complete first challenge', completed: true },
];

const onProfileAction = () => {
console.log('TODO: Profile action');
};

const onChallengeAction = () => {
console.log('TODO: Challenge action');
};
Expand All @@ -56,7 +57,7 @@ const ProgressModal: React.FC<ProgressModalProps> = ({ isOpen, onClose }) => {
<Button
size="sm"
className="w-full max-w-md"
onClick={onProfileAction}
onClick={onOpenProfileModal}
>
Update Profile to Complete Items
</Button>
Expand Down
34 changes: 30 additions & 4 deletions src/utils/gold-star.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,22 @@ export const createProfile = async (profile: ProfileSettings) => {
args: (arg, t) => [
arg(profile.handle, t.String),
arg(profile.referralSource, t.Optional(t.String)),
arg(profile.socials, t.Dictionary(t.String, t.String)),
arg(
profile.deployedContracts,
profile.socials
? Object.entries(profile.socials).map(([key, value]) => ({
key,
value,
}))
: [],
t.Dictionary(t.Address, t.String),
),
arg(
profile.deployedContracts
? Object.entries(profile.deployedContracts).map(([key, value]) => ({
key,
value,
}))
: [],
t.Dictionary(t.Address, t.Array(t.String)),
),
],
Expand All @@ -77,9 +90,22 @@ export const setProfile = async (profile: ProfileSettings) => {
args: (arg, t) => [
arg(profile.handle, t.String),
arg(profile.referralSource, t.Optional(t.String)),
arg(profile.socials, t.Dictionary(t.String, t.String)),
arg(
profile.deployedContracts,
profile.socials
? Object.entries(profile.socials).map(([key, value]) => ({
key,
value,
}))
: [],
t.Dictionary(t.Address, t.String),
),
arg(
profile.deployedContracts
? Object.entries(profile.deployedContracts).map(([key, value]) => ({
key,
value,
}))
: [],
t.Dictionary(t.Address, t.Array(t.String)),
),
],
Expand Down
10 changes: 5 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -5113,6 +5113,11 @@
resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.11.1.tgz#34de04477dcf79e2ef6c8d23b41a3d81f9ebeaf5"
integrity sha512-DUlIj2nk0YnJdlWgsFuVKcX27MLW0KbKmGVoUHmFr+74FYYNUDAaj9ZqTADvsbE8rfxuVmSFc7KczYn5Y09ozg==

"@types/lodash@^4.17.14":
version "4.17.14"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.17.14.tgz#bafc053533f4cdc5fcc9635af46a963c1f3deaff"
integrity sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==

"@types/mdast@^3.0.0":
version "3.0.15"
resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-3.0.15.tgz#49c524a263f30ffa28b71ae282f813ed000ab9f5"
Expand Down Expand Up @@ -17086,11 +17091,6 @@ use-sync-external-store@^1.2.0, use-sync-external-store@^1.4.0:
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc"
integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==

use-sync-external-store@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz#adbc795d8eeb47029963016cefdf89dc799fcebc"
integrity sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==

util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
Expand Down