# Project export: Notic3.

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 11.0
- Tagline: The blockchain solution for content creators offering a decentralized platform for creators and users, powered by Sui.
- Devpost: https://devpost.com/software/notic3
- GitHub: https://github.com/kevinru2023/notic3
- Demo: https://notic3.vercel.app/
- Video: https://www.youtube.com/embed/t1BEB-irnqU?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Result: winner (Sui: Reimagine the internet with Sui)
- Team: 4 GitHub contributor(s) — owenkrause (42 commits), Cassius Villareal (40 commits), Seba (36 commits), Kevin R (26 commits)

## Devpost submission (written by the team)

### Overview

Overview Notic3 is a decentralized platform designed to empower content creators, offering a blockchain-based alternative to platforms like Patreon (and that one unholy platform). Our mission is to eliminate intermediaries, giving creators direct access to their supporters while ensuring secure, transparent, and tamper-proof data. Built fully on-chain, Notic3 exemplifies the spirit of decentralization by providing trustless interactions and immutable records. While many blockchain-based projects use off-chain solutions to sidestep technical challenges, our team committed to going all-in on decentralization. This approach presented unique obstacles, but it also set us apart, reinforcing our belief in the transformative potential of blockchain for creative industries.

### Inspiration

Blockchain technology offers more than just financial innovation—it provides a new way to manage ownership, access, and rewards. Inspired by these capabilities, we wanted to build something that could give content creators control over their work and revenue streams without relying on centralized platforms that charge high fees or control the distribution of content. While the Web3 space has already seen some early experiments in creator tools, many platforms are still hybrids—leveraging blockchain partially but retaining centralized components. We wanted to explore what would happen if we stayed true to the core ethos of decentralization. Notic3 was born from that idea: a fully on-chain platform that doesn't compromise on its principles. Our Approach We chose to build Notic3 entirely on-chain to guarantee transparency, immutability, and censorship resistance. This meant every interaction—from subscription payments to content access—would be recorded directly on the blockchain (available to the general public). This decision came with trade-offs: Secure data storage: Managing private files like videos or audio on-chain is insecure if unencrypted, so we had to creatively manage metadata to ensure users are truly accessing content they own. Novelty of Move and Sui: The Move programming language, native to blockchains like Aptos and Sui, was completely new to our team. Learning it on the fly was one of the biggest challenges we faced, in addition to learning about the Sui SDK. Smart Contract Design: Writing complex smart contracts to handle subscription models, creator payouts, and access permissions directly on-chain was difficult. Key Features Subscription-based Payments: Creators can set up recurring subscriptions that allow supporters to access premium content. Payments are processed seamlessly on-chain, ensuring transparency and immediate distribution of funds. Web3 Storage: Everything about our app is in the chain, including the file storage. We utilized Sui's newest Data Storage solution, Walrus, to store encrypted files on the chain using blobs allowing creators to leverage the chain for distribution of their content. Conclusion Working on Notic3 has been an incredible experience. We took on ambitious challenges, and despite the difficulties, we believe we succeeded in delivering a powerful product. Along the way, we learned new technologies, embraced decentralized principles, and grew both as developers and as a team. Notic3 is proof of what can be achieved when you stay true to your mission, even when easier paths are available. We are excited to continue developing the platform beyond the hackathon and see how it can empower creators around the world. Thank you for the opportunity to present Notic3, and we look forward to feedback and collaboration as we continue this journey!

## README (from the GitHub repository)

This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

-   [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
-   [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.


## Detected evidence (automated analysis)

Indexed codebase: 68 recognized source files, 490 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code

## Codebase structure (from repository index)

### Files (80 of 80)

```
.env.example
.gitignore
.husky/pre-commit
.prettierrc
components.json
LICENSE
move/Move.toml
move/sources/subscription.move
move/tests/subscription_tests.move
next-env.d.ts
next.config.mjs
package.json
postcss.config.mjs
README.md
src/app/(dashboard)/create/components/CreateProfileForm.tsx
src/app/(dashboard)/create/components/ImageUploadForm.tsx
src/app/(dashboard)/create/components/PollUploadForm.tsx
src/app/(dashboard)/create/components/SubscriptionUploadForm.tsx
src/app/(dashboard)/create/components/TextUploadForm.tsx
src/app/(dashboard)/create/components/ui/ContentOption.tsx
src/app/(dashboard)/create/components/ui/SubscriptionDropdown.tsx
src/app/(dashboard)/create/components/VideoUploadForm.tsx
src/app/(dashboard)/create/page.tsx
src/app/(dashboard)/creator/[address]/page.tsx
src/app/(dashboard)/creator/[address]/posts/page.tsx
src/app/(dashboard)/creator/components/CheckoutForm.tsx
src/app/(dashboard)/creator/components/ui/ContentOption.tsx
src/app/(dashboard)/creator/components/ui/SubscriptionDropdown.tsx
src/app/(dashboard)/explore/components/creator-card.tsx
src/app/(dashboard)/explore/components/creator-list.tsx
src/app/(dashboard)/explore/page.tsx
src/app/(dashboard)/layout.tsx
src/app/(dashboard)/providers.tsx
src/app/(dashboard)/settings/page.tsx
src/app/(dashboard)/subscriptions/page.tsx
src/app/(dashboard)/test/page.tsx
src/app/(dashboard)/testdownloadfile/page.tsx
src/app/(dashboard)/testrsakey/page.tsx
src/app/globals.css
src/app/layout.tsx
src/app/page.tsx
src/components/app-sidebar.tsx
src/components/BlogPost.tsx
src/components/ImagePost.tsx
src/components/logo.tsx
src/components/ui/alert-dialog.tsx
src/components/ui/alert.tsx
src/components/ui/avatar.tsx
src/components/ui/badge.tsx
src/components/ui/button.tsx
src/components/ui/card.tsx
src/components/ui/carousel.tsx
src/components/ui/command.tsx
src/components/ui/dialog.tsx
src/components/ui/dropdown-menu.tsx
src/components/ui/form.tsx
src/components/ui/input.tsx
src/components/ui/label.tsx
src/components/ui/popover.tsx
src/components/ui/separator.tsx
src/components/ui/sheet.tsx
src/components/ui/sidebar.tsx
src/components/ui/skeleton.tsx
src/components/ui/tabs.tsx
src/components/ui/textarea.tsx
src/components/ui/toast.tsx
src/components/ui/toaster.tsx
src/components/ui/tooltip.tsx
src/components/VideoPost.tsx
src/constants.ts
src/hooks/getdata.ts
src/hooks/queries.ts
src/hooks/sussyencypt.ts
src/hooks/use-creators.tsx
src/hooks/use-key-pair.tsx
src/hooks/use-mobile.tsx
src/hooks/use-toast.ts
src/lib/utils.ts
tailwind.config.ts
tsconfig.json
```

### Dependencies

- package.json: @dotlottie/react-player@^1.6.19, @hookform/resolvers@^3.9.0, @mysten/dapp-kit@^0.14.27, @mysten/sui@^1.13.0, @mysten/wallet-standard@^0.13.8, @mysten/zklogin@^0.7.23, @radix-ui/react-alert-dialog@^1.1.2, @radix-ui/react-avatar@^1.1.1, @radix-ui/react-dialog@^1.1.2, @radix-ui/react-dropdown-menu@^2.1.2, @radix-ui/react-label@^2.1.0, @radix-ui/react-popover@^1.1.2, @radix-ui/react-separator@^1.1.0, @radix-ui/react-slot@^1.1.0, @radix-ui/react-tabs@^1.1.1, @radix-ui/react-toast@^1.2.2, @radix-ui/react-tooltip@^1.1.3, @tanstack/react-query@^5.59.15, @types/file-saver@^2.0.7, @types/node@^20, @types/react@^18, @types/react-dom@^18, class-variance-authority@^0.7.0, clsx@^2.1.1, cmdk@^1.0.0, embla-carousel-react@^8.3.0, eslint@^8, eslint-config-next@14.2.15, file-saver@^2.0.5, file-type@^19.6.0, geist@^1.3.1, husky@^9.1.6, jwt-decode@^4.0.0, lucide-react@^0.453.0, next@14.2.15, postcss@^8, prettier@^3.3.3, prettier-plugin-tailwindcss@^0.6.8, react@^18, react-dom@^18, react-hook-form@^7.53.1, react-query@^3.39.3, read-chunk@^5.0.0, tailwind-merge@^2.5.4, tailwindcss@^3.4.1, tailwindcss-animate@^1.0.7, typescript@^5, zod@^3.23.8

### Recent commits (newest first)

- New posts page and change ordering
- Clean up sub UI
- Add empty posts handling and polish sub page
- LFG
- stupid prettier
- subscriptions work
- Merge pull request #20 from kevinru2023/error-page
- idk
- idk
- please
- added route protection
- added route protection
- added route protection
- Clean up subscription upload form
- Remove always unregistered bool
- Add loading indication to submit form
- Fix pfp display
- remove secrets viewer from explore
- settings on layout
- Merge pull request #19 from kevinru2023/great-landing-page

## Key source files (fetched from GitHub, selected and truncated for size)

### package.json

```
{
    "name": "notic3",
    "version": "0.1.0",
    "private": true,
    "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start",
        "lint": "next lint",
        "prepare": "husky"
    },
    "dependencies": {
        "@dotlottie/react-player": "^1.6.19",
        "@hookform/resolvers": "^3.9.0",
        "@mysten/dapp-kit": "^0.14.27",
        "@mysten/sui": "^1.13.0",
        "@mysten/wallet-standard": "^0.13.8",
        "@mysten/zklogin": "^0.7.23",
        "@radix-ui/react-alert-dialog": "^1.1.2",
        "@radix-ui/react-avatar": "^1.1.1",
        "@radix-ui/react-dialog": "^1.1.2",
        "@radix-ui/react-dropdown-menu": "^2.1.2",
        "@radix-ui/react-label": "^2.1.0",
        "@radix-ui/react-popover": "^1.1.2",
        "@radix-ui/react-separator": "^1.1.0",
        "@radix-ui/react-slot": "^1.1.0",
        "@radix-ui/react-tabs": "^1.1.1",
        "@radix-ui/react-toast": "^1.2.2",
        "@radix-ui/react-tooltip": "^1.1.3",
        "@tanstack/react-query": "^5.59.15",
        "class-variance-authority": "^0.7.0",
        "clsx": "^2.1.1",
        "cmdk": "^1.0.0",
        "embla-carousel-react": "^8.3.0",
        "file-saver": "^2.0.5",
        "file-type": "^19.6.0",
        "geist": "^1.3.1",
        "jwt-decode": "^4.0.0",
        "lucide-react": "^0.453.0",
        "next": "14.2.15",
        "react": "^18",
        "react-dom": "^18",
        "react-hook-form": "^7.53.1",
        "react-query": "^3.39.3",
        "read-chunk": "^5.0.0",
        "tailwind-merge": "^2.5.4",
        "tailwindcss-animate": "^1.0.7",
        "zod": "^3.23.8"
    },
    "devDependencies": {
        "@types/file-saver": "^2.0.7",
        "@types/node": "^20",
        "@types/react": "^18",
        "@types/react-dom": "^18",
        "eslint": "^8",
        "eslint-config-next": "14.2.15",
        "husky": "^9.1.6",
        "postcss": "^8",
        "prettier": "^3.3.3",
        "prettier-plugin-tailwindcss": "^0.6.8",
        "tailwindcss": "^3.4.1",
        "typescript": "^5"
    }
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from 'next';
import './globals.css';
import { GeistSans } from 'geist/font/sans';
import { GeistMono } from 'geist/font/mono';
import { Toaster } from '@/components/ui/toaster';

export const metadata: Metadata = {
    title: 'notic3',
    description: 'you are notic3d.',
};

export default function RootLayout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return (
        <html lang="en">
            <body className={`${GeistSans.variable} ${GeistMono.variable} antialiased`}>
                {children}
                <Toaster />
            </body>
        </html>
    );
}

```

### src/app/page.tsx

```typescript
'use client';
import React from 'react';
import { Logo } from '@/components/logo';
import { Button } from '@/components/ui/button';
import Link from 'next/link';
import { ChevronRight, Lock, Globe, Wallet } from 'lucide-react';

export default function Login() {
    return (
        <div className="flex min-h-screen w-screen items-center justify-center bg-gradient-to-br from-gray-50 to-white p-8 text-gray-900">
            <div className="w-full max-w-4xl space-y-16 text-center">
                <div className="space-y-6">
                    <Logo className="mx-auto text-[8vw]" />
                </div>

                <div className="space-y-8">
                    <p className="mx-auto max-w-2xl pb-4 text-xl font-light leading-relaxed">
                        Empower your creativity with the future of content monetization. n̈otic3 is
                        the Web3 revolution for content creators, offering a decentralized platform
                        with unparalleled security and transparency.
                    </p>
                    <div className="flex justify-center space-x-12">
                        <Feature
                            icon={<Lock className="h-10 w-10 text-blue-600" />}
                            text="No Backend, Maximum Privacy"
                        />
                        <Feature
                            icon={<Globe className="h-10 w-10 text-blue-600" />}
                            text="Fully Decentralized"
                        />
                        <Feature
                            icon={<Wallet className="h-10 w-10 text-blue-600" />}
                            text="Powered by SUI Blockchain"
                        />
                    </div>
                </div>

                <Button
                    asChild
                    className="transform rounded-full bg-blue-600 px-8 py-4 text-lg font-bold text-white shadow-lg transition duration-300 ease-in-out hover:scale-105 hover:bg-blue-700"
                >
                    <Link href="/explore" className="flex items-center">
                        Start Creating <ChevronRight className="ml-2" />
                    </Link>
                </Button>
            </div>
        </div>
    );
}

const Feature = ({ icon, text }) => (
    <div className="flex flex-col items-center space-y-3">
        {icon}
        <p className="text-lg font-semibold">{text}</p>
    </div>
);

```

### src/app/(dashboard)/layout.tsx

```typescript
import { Providers } from './providers';

export default function Layout({
    children,
}: Readonly<{
    children: React.ReactNode;
}>) {
    return <Providers>{children}</Providers>;
}

```

### src/app/(dashboard)/explore/page.tsx

```typescript
'use client';

import React from 'react';
import { CreatorList } from './components/creator-list';

function Creators() {
    return (
        <div className="my-12 w-full px-10">
            <h1 className="mb-6 text-center text-5xl font-bold">Featured Creators</h1>
            <CreatorList />
        </div>
    );
}

export default Creators;

```

### src/app/(dashboard)/testrsakey/page.tsx

```typescript
'use client';

import { useState, useEffect } from 'react';
import { useEncryptKey } from '@/hooks/sussyencypt';

function EncryptionComponent() {
    const [keyToEncrypt, setKeyToEncrypt] = useState(
        '08d22650c2169adedc102b4e604a48e790857dc948308833df053dcbd638691a'
    );
    const [encryptedKey, setEncryptedKey] = useState('');
    const [publicKey, setPublicKey] = useState('');

    useEffect(() => {
        // Access localStorage here
        const storedPublicKey = localStorage.getItem('notic3-pubkey');
        setPublicKey(storedPublicKey);
    }, []);

    // Call the hook, passing the publicKey state
    const encryptKeyMutation = useEncryptKey(publicKey);

    const handleEncrypt = async () => {
        try {
            const result = await encryptKeyMutation.mutateAsync(keyToEncrypt);
            setEncryptedKey(result);
        } catch (error) {
            console.error('Encryption failed:', error);
        }
    };

    return (
        <div>
            <input
                type="text"
                value={keyToEncrypt}
                onChange={(e) => setKeyToEncrypt(e.target.value)}
                placeholder="Enter key to encrypt"
            />
            <button onClick={handleEncrypt} disabled={encryptKeyMutation.isPending}>
                {encryptKeyMutation.isPending ? 'Encrypting...' : 'Encrypt'}
            </button>
            {encryptedKey && <div>Encrypted Key: {encryptedKey}</div>}
            {encryptKeyMutation.isError && <div>Error: {encryptKeyMutation.error.message}</div>}
        </div>
    );
}

export default EncryptionComponent;

```

### src/app/(dashboard)/test/page.tsx

```typescript
'use client';

import { useState } from 'react';
import { Transaction } from '@mysten/sui/transactions';
import { useSignTransaction, useCurrentAccount, useSuiClient } from '@mysten/dapp-kit';

const Test = () => {
    const { mutateAsync: signTransaction } = useSignTransaction();
    const [signature, setSignature] = useState('');
    const [creatorSubscriptionId, setCreatorSubscriptionId] = useState<string | null>(null);
    const client = useSuiClient();
    const account = useCurrentAccount();

    return (
        <div>
            {account && (
                <>
                    <div>
                        <button
                            onClick={async () => {
                                const tx = new Transaction();

                                tx.moveCall({
                                    target: `${process.env.NEXT_PUBLIC_PACKAGE_ID}::subscription::initialize`,
                                    arguments: [
                                        tx.object(
                                            process.env.NEXT_PUBLIC_CREATOR_SUBSCRIPTION_REGISTRY_ID
                                        ),
                                        tx.pure.u64(100),
                                        tx.pure.u64(30 * 24 * 60 * 60 * 1000),
                                    ],
                                });

                                const { bytes, signature, reportTransactionEffects } =
                                    await signTransaction({
                                        transaction: tx,
                                        chain: 'sui:testnet',
                                    });

                                setSignature(signature);

                                const executeResult = await client.executeTransactionBlock({
                                    transactionBlock: bytes,
                                    signature,
                                    options: {
                                        showRawEffects: true,
                                        showObjectChanges: true,
                                    },
                                });

                                const createdObject = executeResult.objectChanges?.find(
                                    (obj) => obj.type == 'created'
                                );
                                if (createdObject) setCreatorSubscriptionId(createdObject.objectId);

                                // Always report transaction effects to the wallet after execution
                                reportTransactionEffects(executeResult.rawEffects!);

                                console.log(executeResult);
                            }}
                        >
                            create sub
                        </button>
                        <button
                            onClick={async () => {
                                if (creatorSubscriptionId == null) return;
                                const tx = new Transaction();

                                tx.moveCall({
                                    target: `${process.env.NEXT_PUBLIC_PACKAGE_ID}::subscription::subscribe`,
                                    arguments: [tx.object(creatorSubscriptionId), tx.object('0x6')],
                                });
                                const { bytes, signature, reportTransactionEffects } =
                                    await signTransaction({
                                        transaction: tx,
                                        chain: 'sui:testnet',
                                    });

                                setSignature(signature);

                                const executeResult = await client.executeTransactionBlock({
                                    transactionBlock: bytes,
                                    signature,
                                    options: {
                                        showRawEffects: true,
                                    },
                                });

                                // Always report transaction effects to the wallet after execution
                                reportTransactionEffects(executeResult.rawEffects!);

                                console.log(executeResult);
                            }}
                        >
                            sub
                        </button>
                    </div>
                    <div>Signature: {signature}</div>
                </>
            )}
        </div>
    );
};

export default Test;

```

### src/app/(dashboard)/testdownloadfile/page.tsx

```typescript
'use client';
import { useDownloadFile } from '@/hooks/getdata';
import { useState } from 'react';
import { useKeyPair } from '@/hooks/use-key-pair';

// Text File Component
const TextFileViewer = ({ content }) => (
    <pre className="max-h-96 overflow-x-auto rounded bg-gray-100 p-4">
        {content.substring(0, 1000)}...
    </pre>
);

// Image File Component
const ImageFileViewer = ({ content }) => {
    const blob = new Blob([content]);
    const url = URL.createObjectURL(blob);
    return <img src={url} alt="Downloaded image" className="h-auto max-w-full" />;
};

// PDF File Component
const PDFFileViewer = ({ content }) => {
    const blob = new Blob([content], { type: 'application/pdf' });
    const url = URL.createObjectURL(blob);
    return <iframe src={url} className="h-96 w-full" title="PDF Viewer" />;
};

// Default File Component
const DefaultFileViewer = ({ type, content }) => (
    <div>
        <p>File type: {type}</p>
        <p>File size: {content.byteLength} bytes</p>
        <a
            href={URL.createObjectURL(new Blob([content], { type }))}
            download="file"
            className="text-blue-500 hover:underline"
        >
            Download File
        </a>
    </div>
);
// Video File Component
const VideoFileViewer = ({ content }) => {
    const blob = new Blob([content], { type: 'video/mp4' });
    const url = URL.createObjectURL(blob);
    return (
        <video controls className="h-auto max-w-full">
            <source src={url} type="video/mp4" />
            Your browser does not support the video tag.
        </video>
    );
};

export default function TestDownloadFile() {
    const [blobId, setBlobId] = useState('');
    const [key, setKey] = useState('');
    const [iv, setIv] = useState('');
    const [result, setResult] = useState(null);
    const { data, isPending, isFetching } = useKeyPair();

    const { mutate: downloadFile, error } = useDownloadFile(key, iv);

    const handleSubmit = (e) => {
        e.preventDefault();
        downloadFile(blobId, {
            onSuccess: (data) => {
                setResult(data);
            },
            onError: (error) => {
                console.error('Download failed:', error);
            },
        });
    };

    const renderFileContent = () => {
        if (!result) return null;

        switch (result.type) {
            case 'text/plain':
            case 'application/json':
            case 'text/html':
            case 'text/css':
            case 'text/javascript':
                return <TextFileViewer content={result.content} />;
            case 'image/jpeg':
            case 'image/png':
            case 'image/gif':
            case 'image/svg+xml':
                return <ImageFileViewer content={result.content} />;
            case 'application/pdf':
                return <PDFFileViewer content={result.content} />;
            case 'video/mp4':
            case 'video/quicktime':
            case 'video/webm':
                return <VideoFileViewer content={result.content} />;
            default:
                return <DefaultFileViewer type={result.type} content={result.content} />;
        }
    };

    return (
        <div className="p-4">
            <h1 className="mb-4 text-2xl font-bold">Test Download File Function</h1>
            <form onSubmit={handleSubmit} className="space-y-4">
                <div>
                    <label htmlFor="blobId" className="mb-1 block">
                        Blob ID:
                    </label>
                    <input
                        id="blobId"
                        type="text"
                        value={blobId}
                        onChange={(e) => setBlobId(e.target.value)}
                        className="w-full rounded border p-2"
                        required
                    />
                </div>
                <div>
                    <label htmlFor="key" className="mb-1 block">
                        Key:
                    </label>
                    <input
                        id="key"
                        type="text"
                        value={key}
                        onChange={(e) => setKey(e.target.value)}
                        className="w-full rounded border p-2"
                        required
                    />
                </div>
                <div>
                    <label htmlFor="iv" className="mb-1 block">
                        IV:
                    </label>
                    <input
                        id="iv"
                        type="text"
                        value={iv}
                        onChange={(e) => setIv(e.target.value)}
                        className="w-full rounded border p-2"
                        required
                    />
                </div>
                <button
                    type="submit"
                    className="rounded bg-blue-500 px-4 py-2 text-white hover:bg-blue-600"
                >
                    Download and Decrypt
                </button>
            </form>

            {error && <div className="mt-4 text-red-500">Error: {error.message}</div>}

            {result && (
                <div className="mt-4">
                    <h2 className="mb-2 text-xl font-semibold">Result:</h2>
                    {renderFileContent()}
                </div>
            )}
        </div>
    );
}

```

### src/app/(dashboard)/settings/page.tsx

```typescript
'use client';

import { useKeyPair } from '@/hooks/use-key-pair';
import { Copy, Eye, EyeOff } from 'lucide-react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { useState } from 'react';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';

export const Settings = () => {
    const { data, isPending } = useKeyPair();
    const [showPrivateKey, setShowPrivateKey] = useState(false);

    const publicKey = data?.rawPublicKey || 'loading...';
    const privateKey = data?.rawPrivateKey || 'loading...';

    const copyToClipboard = async (text: string) => {
        try {
            await navigator.clipboard.writeText(text);
        } catch (err) {
            console.error('Failed to copy text: ', err);
        }
    };

    return (
        <div className="my-12 w-full px-10">
            <h1 className="mb-6 text-center text-5xl font-bold">Settings</h1>
            <TooltipProvider>
                <Card className="mx-auto w-full max-w-2xl">
                    <CardHeader>
                        <CardTitle className="text-2xl">Your Secrets</CardTitle>
                        <CardDescription>
                            View your public and private keys. These cannot be edited.
                        </CardDescription>
                    </CardHeader>
                    <CardContent className="space-y-6">
                        <div className="space-y-2">
                            <Label htmlFor="public-key" className="text-base">
                                Public Key
                            </Label>
                            <div className="flex space-x-2">
                                <div className="relative flex-grow">
                                    <Textarea
                                        id="public-key"
                                        value={publicKey}
                                        readOnly
                                        className="min-h-[100px] px-6 font-mono text-sm"
                                    />
                                </div>
                                <Tooltip>
                                    <TooltipTrigger asChild>
                                        <Button
                                            onClick={() => copyToClipboard(publicKey)}
                                            size="icon"
                                            variant="outline"
                                        >
                                            <Copy className="h-4 w-4" />
                                        </Button>
                                    </TooltipTrigger>
                                    <TooltipContent>Copy public key</TooltipContent>
                                </Tooltip>
                            </div>
                        </div>
                        <div className="space-y-2">
                            <Label htmlFor="private-key" className="text-base">
                                Private Key
                            </Label>
                            <div className="flex space-x-2">
                                <div className="relative flex-grow">
                                    <Textarea
                                        id="private-key"
                                        value={
                                            showPrivateKey || isPending
                                                ? privateKey
                                                : '*'.repeat(privateKey.length)
                                        }
                                        readOnly
                                        className="min-h-[200px] px-6 font-mono text-sm"
                                    />
                                </div>
                                <div className="flex flex-col gap-2">
                                    <Button
                                        onClick={() => copyToClipboard(privateKey)}
                                        size="icon"
                                        variant="outline"
                                        className="h-10 w-10"
                                    >
                                        <Copy className="h-4 w-4" />
                                    </Button>
                                    <Tooltip>
                                        <TooltipTrigger asChild>
                                            <Button
                                                onClick={() => setShowPrivateKey(!showPrivateKey)}
                                                size="sm"
                                                variant="outline"
                                                className="h-10 w-10"
                                            >
                                                {showPrivateKey ? (
                                                    <>
                                                        <EyeOff className="h-4 w-4" />
                                                    </>
                                                ) : (
                                                    <>
                                                        <Eye className="h-4 w-4" />
                                                    </>
                                                )}
                                            </Button>
                                        </TooltipTrigger>
                                        <TooltipContent>
                                            {showPrivateKey
                                                ? 'Hide private key'
                                                : 'Show private key'}
     
[truncated — 346 more characters]
```

### src/app/(dashboard)/create/page.tsx

```typescript
'use client';

import React, { useEffect, useState } from 'react';
import { useCurrentAccount, useSuiClient } from '@mysten/dapp-kit';
import { Button } from '@/components/ui/button';
import {
    Card,
    CardContent,
    CardDescription,
    CardFooter,
    CardHeader,
    CardTitle,
} from '@/components/ui/card';
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
import { Image, Video, Type, ChartNoAxesColumn } from 'lucide-react';
import ContentOption from './components/ui/ContentOption';
import Link from 'next/link';
import { TextUploadForm } from './components/TextUploadForm';
import { SubscriptionUploadForm } from './components/SubscriptionUploadForm';
import ImageUploadForm from './components/ImageUploadForm';
import VideoUploadForm from './components/VideoUploadForm';
import CreateProfileForm from './components/CreateProfileForm';
import { useKeyPair } from '@/hooks/use-key-pair';
import { useDownloadUnencryptedFile } from '@/hooks/getdata';
import { saveAs } from 'file-saver';
import { Creator, useCreators } from '@/hooks/use-creators';

const CreatePage = () => {
    const [key, setKey] = useState(null);
    const [iv, setIv] = useState(null);
    const [blobId, setBlobId] = useState(null);
    const [registered, setRegistered] = useState(false);
    const [creator, setCreator] = useState(null);

    const account = useCurrentAccount();
    const { data: profileImage } = useDownloadUnencryptedFile(creator?.image);
    const creators = useCreators();
    const { data: keyPairData } = useKeyPair();

    useEffect(() => {
        if (!account) return;
        const currentCreator = creators.find((c) => c.address === account.address);
        if (currentCreator) {
            setRegistered(true);
            setCreator(currentCreator);
        }
    }, [account, creators]);

    if (!account) {
        return (
            <Alert className="m-4 text-4xl">
                <AlertTitle>No Account Connected</AlertTitle>
                <AlertDescription>
                    Please connect your wallet to access the Create Page and manage your profile.
                </AlertDescription>
            </Alert>
        );
    }

    if (!registered) {
        return (
            <div className="mx-auto my-10 w-full max-w-lg">
                <p className="text-center text-4xl font-bold">Create Profile</p>
                <CreateProfileForm setOpen={() => {}} />
            </div>
        );
    }

    const downloadKeypair = () => {
        const publicKeyHex = localStorage.getItem('notic3-pubkey');
        const privateKeyHex = localStorage.getItem('notic3-privkey');
        const keypairContent = `Public Key: ${publicKeyHex}\n\nPrivate Key: ${privateKeyHex}`;
        const blob = new Blob([keypairContent], { type: 'text/plain;charset=utf-8' });
        saveAs(blob, 'keypair.txt');
    };

    return (
        <div className="flex w-full flex-col items-center justify-between gap-4 p-10">
            <ProfilePreviewCard creator={creator} profileImage={profileImage} account={account} />
            <div className="flex w-full items-center justify-between gap-4 lg:flex-row lg:items-start">
                <SubscriptionCard />
                <ContentCreationCard setIv={setIv} setKey={setKey} setBlobId={setBlobId} />
            </div>
            <EncryptionDetailsCard key={key} iv={iv} blobId={blobId} />
        </div>
    );
};

const ProfilePreviewCard = ({ creator, profileImage, account }) => (
    <Card className="h-full w-full p-2">
        <CardHeader>
            <CardTitle>Profile Preview</CardTitle>
            <CardDescription>Your public profile</CardDescription>
        </CardHeader>
        <CardContent>
            <div className="flex flex-col items-center">
                <img
                    src={profileImage || 'https://i.pravatar.cc/300'}
                    alt="Profile Picture"
                    className="h-24 w-24 rounded-full"
                />
                <h1 className="mt-2 text-2xl font-semibold">{creator?.name || 'Your Name'}</h1>
                <p className="text-sm text-muted-foreground">{creator?.bio || 'Your Bio'}</p>
            </div>
        </CardContent>
        <CardFooter>
            <Button asChild>
                <Link className="w-full" href={`creator/${account?.address}`} size="lg">
                    Edit Profile
                </Link>
            </Button>
        </CardFooter>
    </Card>
);

const SubscriptionCard = () => (
    <Card className="h-full w-full p-3 lg:w-[50%]">
        <CardHeader>
            <CardTitle>Create Subscription</CardTitle>
            <CardDescription>Create subscription tiers for specific content</CardDescription>
        </CardHeader>
        <CardContent>
            <SubscriptionUploadForm />
        </CardContent>
        <CardFooter>
            <p className="text-center text-sm text-muted-foreground">
                Note: Subscriptions are immutable
            </p>
        </CardFooter>
    </Card>
);

const ContentCreationCard = ({ setIv, setKey, setBlobId }) => (
    <Card className="h-full w-full p-3 lg:w-[50%]">
        <CardHeader>
            <CardTitle>Create Content</CardTitle>
            <CardDescription>Upload any content of your choosing</CardDescription>
        </CardHeader>
        <CardContent>
            <div className="grid grid-cols-2 gap-2">
                <ContentOption
                    Form={ImageUploadForm}
                    Icon={Image}
                    tooltipText="Upload Image"
                    formTitle="Image"
                    setIv={setIv}
                    setKey={setKey}
                    setBlobId={setBlobId}
                />
                <ContentOption
                    Form={VideoUploadForm}
                    Icon={Video}
                    tooltipText="Upload Video"
                    formTitle="Video"
                    setIv={setIv}
                    setKey={setKey}
                    setBlobId
[truncated — 2270 more characters]
```

[57 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]