mirror of
https://github.com/aljazceru/landscape-template.git
synced 2025-12-18 06:44:22 +01:00
feat: basic nostr-settings profile section
This commit is contained in:
@@ -8,6 +8,7 @@ const getUserByPubKey = (pubKey) => {
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
getUserByPubKey,
|
||||
}
|
||||
@@ -198,6 +198,8 @@ export interface NexusGenObjects {
|
||||
linkedin?: string | null; // String
|
||||
location?: string | null; // String
|
||||
name: string; // String!
|
||||
nostr_prv_key?: string | null; // String
|
||||
nostr_pub_key?: string | null; // String
|
||||
role?: string | null; // String
|
||||
twitter?: string | null; // String
|
||||
website?: string | null; // String
|
||||
@@ -405,6 +407,8 @@ export interface NexusGenFieldTypes {
|
||||
linkedin: string | null; // String
|
||||
location: string | null; // String
|
||||
name: string; // String!
|
||||
nostr_prv_key: string | null; // String
|
||||
nostr_pub_key: string | null; // String
|
||||
role: string | null; // String
|
||||
stories: NexusGenRootTypes['Story'][]; // [Story!]!
|
||||
twitter: string | null; // String
|
||||
@@ -611,6 +615,8 @@ export interface NexusGenFieldTypeNames {
|
||||
linkedin: 'String'
|
||||
location: 'String'
|
||||
name: 'String'
|
||||
nostr_prv_key: 'String'
|
||||
nostr_pub_key: 'String'
|
||||
role: 'String'
|
||||
stories: 'Story'
|
||||
twitter: 'String'
|
||||
|
||||
@@ -242,6 +242,8 @@ type User {
|
||||
linkedin: String
|
||||
location: String
|
||||
name: String!
|
||||
nostr_prv_key: String
|
||||
nostr_pub_key: String
|
||||
role: String
|
||||
stories: [Story!]!
|
||||
twitter: String
|
||||
|
||||
@@ -23,6 +23,8 @@ const User = objectType({
|
||||
t.string('linkedin')
|
||||
t.string('bio')
|
||||
t.string('location')
|
||||
t.string('nostr_prv_key')
|
||||
t.string('nostr_pub_key')
|
||||
|
||||
t.nonNull.list.nonNull.field('stories', {
|
||||
type: "Story",
|
||||
@@ -55,10 +57,15 @@ const profile = extendType({
|
||||
args: {
|
||||
id: nonNull(intArg())
|
||||
},
|
||||
async resolve(parent, { id }) {
|
||||
return prisma.user.findFirst({
|
||||
where: { id }
|
||||
})
|
||||
async resolve(parent, { id }, ctx) {
|
||||
const user = await getUserByPubKey(ctx.userPubKey);
|
||||
const isSelf = user?.id === id;
|
||||
const profile = await prisma.user.findFirst({
|
||||
where: { id },
|
||||
});
|
||||
if (!isSelf)
|
||||
profile.nostr_prv_key = null;
|
||||
return profile;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ const { createExpressApp } = require('../../modules');
|
||||
const express = require('express');
|
||||
const jose = require('jose');
|
||||
const { JWT_SECRET } = require('../../utils/consts');
|
||||
const { generatePrivateKey, getPublicKey } = require('../../utils/nostr-tools');
|
||||
|
||||
|
||||
|
||||
@@ -28,11 +29,17 @@ const loginHandler = async (req, res) => {
|
||||
//Create user if not already existing
|
||||
const user = await prisma.user.findFirst({ where: { pubKey: key } })
|
||||
if (user === null) {
|
||||
|
||||
const nostr_prv_key = generatePrivateKey();
|
||||
const nostr_pub_key = getPublicKey(nostr_prv_key);
|
||||
|
||||
await prisma.user.create({
|
||||
data: {
|
||||
pubKey: key,
|
||||
name: key,
|
||||
avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg`
|
||||
avatar: `https://avatars.dicebear.com/api/bottts/${key}.svg`,
|
||||
nostr_prv_key,
|
||||
nostr_pub_key,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
const { PrismaClient } = require("@prisma/client");
|
||||
const { generatePrivateKey, getPublicKey } = require("../../api/utils/nostr-tools");
|
||||
const { categories, projects } = require("./data");
|
||||
|
||||
|
||||
@@ -14,6 +15,29 @@ async function purge() {
|
||||
}
|
||||
|
||||
|
||||
async function generateNostrKeys() {
|
||||
const allUsers = await prisma.user.findMany({
|
||||
where: {
|
||||
nostr_prv_key: null
|
||||
}
|
||||
})
|
||||
for (const user of allUsers) {
|
||||
|
||||
const prvkey = generatePrivateKey();
|
||||
const pubkey = getPublicKey(prvkey);
|
||||
|
||||
await prisma.user.update({
|
||||
where: {
|
||||
id: user.id,
|
||||
},
|
||||
data: {
|
||||
nostr_prv_key: prvkey,
|
||||
nostr_pub_key: pubkey
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function main() {
|
||||
|
||||
@@ -9,16 +9,10 @@ type Author = NonNullable<Comment['author']>
|
||||
|
||||
const pool = relayPool();
|
||||
|
||||
const RELAYS = [
|
||||
'wss://nostr.drss.io',
|
||||
'wss://nostr-relay.freeberty.net',
|
||||
'wss://nostr.unknown.place',
|
||||
'wss://nostr-relay.untethr.me',
|
||||
'wss://relay.damus.io'
|
||||
];
|
||||
|
||||
|
||||
export function connect() {
|
||||
RELAYS.forEach(url => {
|
||||
CONSTS.DEFAULT_RELAYS.forEach(url => {
|
||||
pool.addRelay(url, { read: true, write: true })
|
||||
})
|
||||
pool.onNotice((notice: string, relay: any) => {
|
||||
@@ -99,7 +93,7 @@ export async function post({ content, filter, parentId }: {
|
||||
const tags = [];
|
||||
tags.push(['r', filter]);
|
||||
if (parentId)
|
||||
tags.push(['e', `${parentId} ${RELAYS[0]} reply`])
|
||||
tags.push(['e', `${parentId} ${CONSTS.DEFAULT_RELAYS[0]} reply`])
|
||||
|
||||
let event: NostrEvent;
|
||||
try {
|
||||
|
||||
@@ -16,7 +16,6 @@ import styles from './styles.module.scss'
|
||||
|
||||
|
||||
export default function PostDetailsPage() {
|
||||
|
||||
const { type: _type, id } = useParams();
|
||||
const type = capitalize(_type);
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@ import { User } from "src/graphql"
|
||||
import { trimText, withHttp } from "src/utils/helperFunctions"
|
||||
import { FiGithub, FiGlobe, FiLinkedin, FiTwitter } from 'react-icons/fi'
|
||||
import Button from "src/Components/Button/Button";
|
||||
import { useState } from "react";
|
||||
import { useToggle } from "@react-hookz/web";
|
||||
import UpdateAboutForm from "./UpdateAboutForm";
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { MOCK_DATA } from 'src/mocks/data';
|
||||
import CommentsSettingsCard from './CommentsSettingsCard';
|
||||
|
||||
export default {
|
||||
title: 'Profiles/Profile Page/Comments Settings Card',
|
||||
component: CommentsSettingsCard,
|
||||
argTypes: {
|
||||
backgroundColor: { control: 'color' },
|
||||
},
|
||||
|
||||
} as ComponentMeta<typeof CommentsSettingsCard>;
|
||||
|
||||
|
||||
const Template: ComponentStory<typeof CommentsSettingsCard> = (args) => <CommentsSettingsCard {...args} ></CommentsSettingsCard>
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { useToggle } from '@react-hookz/web'
|
||||
import { Nullable } from 'remirror';
|
||||
import Button from 'src/Components/Button/Button';
|
||||
import { CONSTS } from 'src/utils';
|
||||
|
||||
interface Props {
|
||||
isOwner?: boolean;
|
||||
nostr_pub_key: Nullable<string>;
|
||||
nostr_prv_key: Nullable<string>;
|
||||
|
||||
}
|
||||
|
||||
export default function CommentsSettingsCard({ nostr_prv_key, nostr_pub_key, isOwner }: Props) {
|
||||
|
||||
|
||||
return (
|
||||
<div className="rounded-16 bg-white border-2 border-gray-200 p-24">
|
||||
<p className="text-body2 font-bold">Nostr Settings (experimental)</p>
|
||||
<p className="mt-8 text-body4 text-gray-600">
|
||||
Our commenting system is experimental and uses Nostr to store and relay your messages and replies to our own relay, as well as relays ran by other people in the community.
|
||||
We generate Nostr keys for you since there are no popular wallets which support it.
|
||||
</p>
|
||||
|
||||
{nostr_prv_key && <>
|
||||
<p className="text-body4 font-bold mt-24">
|
||||
Your Nostr Private Key
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
type={'text'}
|
||||
className="input-text"
|
||||
value={nostr_prv_key}
|
||||
/>
|
||||
</div>
|
||||
</>}
|
||||
<p className="text-body4 font-bold mt-24">
|
||||
Your Nostr Public Key
|
||||
</p>
|
||||
<div className="input-wrapper mt-8 relative">
|
||||
<input
|
||||
|
||||
type='text'
|
||||
className="input-text"
|
||||
value={nostr_pub_key!}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-body4 font-bold mt-24">
|
||||
Connect your Nostr identity
|
||||
</p>
|
||||
<div className="mt-8 py-12 relative">
|
||||
<p className="text-body4 text-gray-400 font-bold">
|
||||
Coming Soon 🚧
|
||||
</p>
|
||||
</div>
|
||||
<p className="text-body4 font-bold mt-24">
|
||||
Connected Relays
|
||||
</p>
|
||||
<ul className="mt-8 relative flex flex-col gap-8">
|
||||
{CONSTS.DEFAULT_RELAYS.map((url, idx) => <li key={idx} className="text-body4 border-b py-12 px-16 bg-gray-100 border-2 border-gray-200 rounded-16">{url}</li>)}
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import { Helmet } from 'react-helmet'
|
||||
import { useAppSelector } from 'src/utils/hooks';
|
||||
import styles from './styles.module.scss'
|
||||
import StoriesCard from "./StoriesCard/StoriesCard"
|
||||
import CommentsSettingsCard from "./CommentsSettingsCard/CommentsSettingsCard"
|
||||
|
||||
export default function ProfilePage() {
|
||||
|
||||
@@ -43,6 +44,10 @@ export default function ProfilePage() {
|
||||
<main className="flex flex-col gap-24">
|
||||
<AboutCard user={profileQuery.data.profile} isOwner={isOwner} />
|
||||
<StoriesCard stories={profileQuery.data.profile.stories} isOwner={isOwner} />
|
||||
{
|
||||
isOwner &&
|
||||
<CommentsSettingsCard nostr_prv_key={profileQuery.data.profile.nostr_prv_key} nostr_pub_key={profileQuery.data.profile.nostr_pub_key} isOwner={isOwner} />
|
||||
}
|
||||
</main>
|
||||
<aside></aside>
|
||||
</div>
|
||||
|
||||
@@ -24,5 +24,7 @@ query profile($profileId: Int!) {
|
||||
icon
|
||||
}
|
||||
}
|
||||
nostr_prv_key
|
||||
nostr_pub_key
|
||||
}
|
||||
}
|
||||
|
||||
@@ -329,6 +329,7 @@ export type Story = PostBase & {
|
||||
__typename?: 'Story';
|
||||
author: Author;
|
||||
body: Scalars['String'];
|
||||
comments: Array<PostComment>;
|
||||
cover_image: Maybe<Scalars['String']>;
|
||||
createdAt: Scalars['Date'];
|
||||
excerpt: Scalars['String'];
|
||||
@@ -386,6 +387,8 @@ export type User = {
|
||||
linkedin: Maybe<Scalars['String']>;
|
||||
location: Maybe<Scalars['String']>;
|
||||
name: Scalars['String'];
|
||||
nostr_prv_key: Maybe<Scalars['String']>;
|
||||
nostr_pub_key: Maybe<Scalars['String']>;
|
||||
role: Maybe<Scalars['String']>;
|
||||
stories: Array<Story>;
|
||||
twitter: Maybe<Scalars['String']>;
|
||||
@@ -516,7 +519,7 @@ export type ProfileQueryVariables = Exact<{
|
||||
}>;
|
||||
|
||||
|
||||
export type ProfileQuery = { __typename?: 'Query', profile: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, role: string | null, email: string | null, jobTitle: string | null, lightning_address: string | null, website: string | null, twitter: string | null, github: string | null, linkedin: string | null, bio: string | null, location: string | null, stories: Array<{ __typename?: 'Story', id: number, title: string, createdAt: any, tags: Array<{ __typename?: 'Tag', id: number, title: string, icon: string | null }> }> } | null };
|
||||
export type ProfileQuery = { __typename?: 'Query', profile: { __typename?: 'User', id: number, name: string, avatar: string, join_date: any, role: string | null, email: string | null, jobTitle: string | null, lightning_address: string | null, website: string | null, twitter: string | null, github: string | null, linkedin: string | null, bio: string | null, location: string | null, nostr_prv_key: string | null, nostr_pub_key: string | null, stories: Array<{ __typename?: 'Story', id: number, title: string, createdAt: any, tags: Array<{ __typename?: 'Tag', id: number, title: string, icon: string | null }> }> } | null };
|
||||
|
||||
export type UpdateProfileAboutMutationVariables = Exact<{
|
||||
data: InputMaybe<UpdateProfileInput>;
|
||||
@@ -1328,6 +1331,8 @@ export const ProfileDocument = gql`
|
||||
icon
|
||||
}
|
||||
}
|
||||
nostr_prv_key
|
||||
nostr_pub_key
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -16,5 +16,7 @@ export const user: User = {
|
||||
role: "user",
|
||||
twitter: "john-doe",
|
||||
website: "https://mtg-dev.tech",
|
||||
stories: posts.stories
|
||||
stories: posts.stories,
|
||||
nostr_prv_key: "123123124asdfsadfsa8d7fsadfasdf",
|
||||
nostr_pub_key: "123124123123dfsadfsa8d7f11sadfasdf",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
|
||||
const DEFAULT_RELAYS = [
|
||||
'wss://nostr.drss.io',
|
||||
'wss://nostr-relay.freeberty.net',
|
||||
'wss://nostr.unknown.place',
|
||||
'wss://nostr-relay.untethr.me',
|
||||
'wss://relay.damus.io'
|
||||
];
|
||||
|
||||
const CONSTS = {
|
||||
apiEndpoint: process.env.REACT_APP_API_END_POINT ?? '/.netlify/functions',
|
||||
defaultLightningAddress: 'johns@getalby.com'
|
||||
defaultLightningAddress: 'johns@getalby.com',
|
||||
DEFAULT_RELAYS
|
||||
}
|
||||
|
||||
export default CONSTS;
|
||||
@@ -20,10 +20,12 @@ export const FallbackProvider: React.FC<React.PropsWithChildren<FabllbackProvide
|
||||
const [fallback, setFallback] = React.useState<FallbackType>(null);
|
||||
|
||||
const updateFallback = React.useCallback((fallback: FallbackType) => {
|
||||
setFallback(() => <>
|
||||
setFallback(() =>
|
||||
<>
|
||||
<LoadingPage />
|
||||
{fallback}
|
||||
</>);
|
||||
</>
|
||||
);
|
||||
}, []);
|
||||
|
||||
const renderChildren = React.useMemo(() => {
|
||||
|
||||
Reference in New Issue
Block a user