├── .env.example ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .prettierignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── assets └── fonts │ ├── CalSans-SemiBold.ttf │ ├── CalSans-SemiBold.woff2 │ ├── Inter-Bold.ttf │ ├── Inter-Regular.ttf │ ├── index.ts │ ├── st-b.woff2 │ ├── st-m.woff2 │ └── st-r.woff2 ├── components.json ├── next.config.js ├── package.json ├── pnpm-lock.yaml ├── postcss.config.js ├── prettier.config.js ├── prisma └── schema.prisma ├── public ├── broad.png ├── broad1.png ├── cover.jpg ├── cover2.png ├── favicon.ico ├── images.png ├── light-1.png ├── light.png ├── map.png ├── obs-1.png ├── obs-2.png ├── obs-3.png ├── obs.png ├── people-01.png ├── people-02.png ├── people-03.png ├── people-04.png ├── people-05.png ├── placeholder-user.jpg ├── spooky.svg ├── stream.png ├── streamlabs.avif ├── visions │ ├── designer.svg │ ├── podcast.svg │ ├── rich.svg │ ├── richs.svg │ ├── rockets.svg │ └── stereo.svg ├── vmix-1.png ├── vmix.png ├── wire.jpg └── wire.png ├── src ├── actions │ ├── block.ts │ ├── follow.ts │ ├── forms.ts │ ├── ingress.ts │ ├── sendmsg.ts │ ├── stream.ts │ ├── token.ts │ └── user.ts ├── app │ ├── (auth) │ │ ├── _components │ │ │ ├── center-wrapper.tsx │ │ │ └── logo.tsx │ │ ├── layout.tsx │ │ ├── sign-in │ │ │ └── [[...sign-in]] │ │ │ │ └── page.tsx │ │ └── sign-up │ │ │ └── [[...sign-up]] │ │ │ └── page.tsx │ ├── (dashboard) │ │ └── u │ │ │ └── [username] │ │ │ ├── (home) │ │ │ ├── _components │ │ │ │ └── container.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ │ ├── _components │ │ │ ├── asides │ │ │ │ ├── index.tsx │ │ │ │ ├── items.tsx │ │ │ │ ├── navigates.tsx │ │ │ │ ├── toogle.tsx │ │ │ │ └── wrapper.tsx │ │ │ ├── container.tsx │ │ │ └── navs │ │ │ │ ├── act.tsx │ │ │ │ ├── index.tsx │ │ │ │ └── logo.tsx │ │ │ ├── chat │ │ │ ├── _components │ │ │ │ └── toogly-chat.tsx │ │ │ ├── loading.tsx │ │ │ └── page.tsx │ │ │ ├── followers │ │ │ └── page.tsx │ │ │ ├── jemaw │ │ │ ├── _components │ │ │ │ ├── columns.tsx │ │ │ │ ├── data-table.tsx │ │ │ │ └── unblock-btn.tsx │ │ │ └── page.tsx │ │ │ ├── keys │ │ │ ├── _components │ │ │ │ ├── copy-btn.tsx │ │ │ │ ├── dailog-key.tsx │ │ │ │ ├── stream-key-gen-card.tsx │ │ │ │ └── url-feild.tsx │ │ │ └── page.tsx │ │ │ ├── layout.tsx │ │ │ ├── messages │ │ │ ├── _components │ │ │ │ ├── delete-msg.tsx │ │ │ │ ├── message-modal.tsx │ │ │ │ ├── mssage-box.tsx │ │ │ │ └── nark-as.tsx │ │ │ └── page.tsx │ │ │ ├── payments │ │ │ ├── _components │ │ │ │ ├── analytics.tsx │ │ │ │ ├── calander.tsx │ │ │ │ ├── overview.tsx │ │ │ │ ├── recent-donation.tsx │ │ │ │ └── search.tsx │ │ │ └── page.tsx │ │ │ └── setting │ │ │ └── page.tsx │ ├── (web) │ │ ├── (home) │ │ │ ├── _components │ │ │ │ ├── custome-text.tsx │ │ │ │ ├── customers.tsx │ │ │ │ ├── for-free.tsx │ │ │ │ ├── forms │ │ │ │ │ ├── form.tsx │ │ │ │ │ ├── forms.module.css │ │ │ │ │ └── submit-btn.tsx │ │ │ │ ├── fuzzy.tsx │ │ │ │ ├── get-started.tsx │ │ │ │ ├── hero.tsx │ │ │ │ ├── horizontal-crsl.tsx │ │ │ │ ├── infinte-scrol.tsx │ │ │ │ ├── people-around.tsx │ │ │ │ ├── shiny-btn │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── shiny.module.css │ │ │ │ ├── steps.tsx │ │ │ │ └── styles │ │ │ │ │ └── index.ts │ │ │ ├── page.tsx │ │ │ ├── signout │ │ │ │ ├── _components │ │ │ │ │ └── logout-btn.tsx │ │ │ │ └── page.tsx │ │ │ └── verify-payment │ │ │ │ ├── loading.tsx │ │ │ │ ├── not-found.tsx │ │ │ │ └── page.tsx │ │ ├── [username] │ │ │ ├── _components │ │ │ │ └── followup.tsx │ │ │ ├── layout.tsx │ │ │ ├── loading.tsx │ │ │ ├── not-found.tsx │ │ │ └── page.tsx │ │ ├── _components │ │ │ ├── contacts │ │ │ │ ├── contact.module.css │ │ │ │ ├── index.tsx │ │ │ │ └── text-3d.tsx │ │ │ ├── container.tsx │ │ │ ├── lauch.tsx │ │ │ ├── nav │ │ │ │ ├── act.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── logo.tsx │ │ │ │ └── search.tsx │ │ │ └── sidebar │ │ │ │ ├── following.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── recomended.tsx │ │ │ │ ├── toogle.tsx │ │ │ │ ├── user-items.tsx │ │ │ │ └── wrapper.tsx │ │ ├── search │ │ │ ├── _components │ │ │ │ ├── SeachLists.tsx │ │ │ │ └── search-card.tsx │ │ │ └── page.tsx │ │ └── streams │ │ │ ├── _components │ │ │ ├── StreamLists.tsx │ │ │ ├── loaded.tsx │ │ │ └── stream-card.tsx │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ ├── api │ │ ├── chapa │ │ │ └── route.ts │ │ ├── uploadthing │ │ │ ├── core.ts │ │ │ └── route.ts │ │ ├── verify │ │ │ └── route.ts │ │ └── webhooks │ │ │ ├── clerk │ │ │ └── route.ts │ │ │ └── livekit │ │ │ └── route.ts │ └── layout.tsx ├── components │ ├── analytics.tsx │ ├── auth │ │ └── user-profile.tsx │ ├── follower-list-card.tsx │ ├── follower-list.tsx │ ├── followr-avatar.tsx │ ├── hint.tsx │ ├── icons.tsx │ ├── live-badge.tsx │ ├── loader-comp.tsx │ ├── message-show.tsx │ ├── mode-toggle.tsx │ ├── page-header.tsx │ ├── poster-img.tsx │ ├── shell.tsx │ ├── skewed-txt.tsx │ ├── skewed │ │ ├── skew.module.css │ │ └── skewed-txt.tsx │ ├── streaming │ │ ├── about-helper.tsx │ │ ├── action.tsx │ │ ├── biography.tsx │ │ ├── featured-modal │ │ │ ├── payment-form.tsx │ │ │ └── payment-modal.tsx │ │ ├── followup.tsx │ │ ├── header.tsx │ │ ├── heads.tsx │ │ ├── index.tsx │ │ ├── info-card.tsx │ │ ├── info-modal.tsx │ │ ├── jema-items.tsx │ │ ├── jema.tsx │ │ ├── live-vid.tsx │ │ ├── loading-vid.tsx │ │ ├── offline-vid.tsx │ │ ├── pending-vid.tsx │ │ ├── screen-control.tsx │ │ ├── talk-form.tsx │ │ ├── talk-headr.tsx │ │ ├── talk-info.tsx │ │ ├── talk-lists.tsx │ │ ├── talk-msg.tsx │ │ ├── talks.tsx │ │ ├── toggle-chat.tsx │ │ ├── toggle-var.tsx │ │ ├── vid.tsx │ │ └── volume-control.tsx │ ├── theme-provider.tsx │ ├── transaction-complete.tsx │ ├── ui │ │ ├── alert.tsx │ │ ├── avatar.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── dialog.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── popover.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ └── tooltip.tsx │ ├── user-avatar.tsx │ ├── user-nav.tsx │ └── verified-mark.tsx ├── config │ └── site.ts ├── env.mjs ├── hooks │ ├── use-mount.tsx │ └── use-view-token.tsx ├── lib │ ├── blocks.tsx │ ├── db.ts │ ├── feed.tsx │ ├── follow-user.tsx │ ├── msg.ts │ ├── recommend-it.ts │ ├── search-service.tsx │ ├── stream.tsx │ ├── uploadthing.ts │ ├── user-helper.tsx │ ├── utils.ts │ └── valid-user.ts ├── middleware.ts ├── store │ ├── use-chat-sidebar.ts │ ├── use-creator-sidebar.ts │ └── use-sidebar.ts ├── styles │ └── globals.css └── types │ └── index.d.ts ├── tailwind.config.js └── tsconfig.json /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_APP_URL=http://localhost:3000 2 | 3 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY= 4 | CLERK_SECRET_KEY= 5 | NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in 6 | NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up 7 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/ 8 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/ 9 | CLERK_WEBHOOK_SECRET=+W6H 10 | 11 | DATABASE_URL= 12 | 13 | 14 | 15 | LIVEKIT_API_URL= 16 | LIVEKIT_API_KEY= 17 | LIVEKIT_API_SECRET= 18 | NEXT_PUBLIC_LIVEKIT_WS_URL= 19 | 20 | UPLOADTHING_SECRET= 21 | UPLOADTHING_APP_ID= 22 | 23 | 24 | 25 | 26 | CHAPA_SECRET_KEY=CHASECK_TEST-.... 27 | CHAPA_PUBLIC_KEY=CHAPUBK_TEST-.... -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | 39 | .env -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .next 4 | package.json 5 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.defaultFormatter": "esbenp.prettier-vscode", 4 | "[typescriptreact]": { 5 | "editor.defaultFormatter": "vscode.typescript-language-features" 6 | }, 7 | "[typescript]": { 8 | "editor.defaultFormatter": "vscode.typescript-language-features" 9 | }, 10 | "[prisma]": { 11 | "editor.defaultFormatter": "Prisma.prisma" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 redpangilinan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Chacha 2 | 3 | ethiopian based multi tenant web streaming platform to reduce the overall hardware cost like servers , instances, disks and stuff by almost by 50% both in cost and operations 4 | ## Tech Stack 5 | 6 | - **Framework:** [Next.js](https://nextjs.org) 7 | - **Styling:** [Tailwind CSS](https://tailwindcss.com) 8 | - **User Management:** [Clerk](https://clerk.com) 9 | - **ORM:** [Drizzle ORM](https://orm.drizzle.team) 10 | - **UI Components:** [shadcn/ui](https://ui.shadcn.com) 11 | - **Email:** [React Email](https://react.email) 12 | - **Content Management:** [Contentlayer](https://www.contentlayer.dev) 13 | - **File Uploads:** [uploadthing](https://uploadthing.com) 14 | 15 | ## Running Locally 16 | 17 | 1. Clone the repository 18 | 19 | ```bash 20 | git clone https://github.com/Kinfe123/chacha.git 21 | ``` 22 | 23 | 2. Change the dir 24 | 25 | ```bash 26 | cd chacha 27 | ``` 28 | 29 | 3. Install dependencies using pnpm 30 | 31 | ```bash 32 | pnpm install 33 | ``` 34 | 35 | 4. Copy the `.env.example` to `.env` and update the variables. 36 | 37 | ```bash 38 | cp .env.example .env 39 | ``` 40 | 41 | 5. Start the development server 42 | 43 | ```bash 44 | pnpm run dev 45 | ``` 46 | 47 | ## How do I deploy this? 48 | 49 | Follow the deployment guides for [Vercel](https://create.t3.gg/en/deployment/vercel), [Netlify](https://create.t3.gg/en/deployment/netlify) and [Docker](https://create.t3.gg/en/deployment/docker) for more information. 50 | 51 | ## Contributing 52 | 53 | Contributions are welcome! Please open an issue if you have any questions or suggestions. Your contributions will be acknowledged. See the [contributing guide](./CONTRIBUTING.md) for more information. 54 | 55 | That's why this existed but i am using turbo just in case :) 56 | -------------------------------------------------------------------------------- /assets/fonts/CalSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/assets/fonts/CalSans-SemiBold.ttf -------------------------------------------------------------------------------- /assets/fonts/CalSans-SemiBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/assets/fonts/CalSans-SemiBold.woff2 -------------------------------------------------------------------------------- /assets/fonts/Inter-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/assets/fonts/Inter-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Inter-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/assets/fonts/Inter-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/index.ts: -------------------------------------------------------------------------------- 1 | import localFont from "next/font/local"; 2 | import { Inter as FontSans, Urbanist } from "next/font/google"; 3 | 4 | export const fontSans = FontSans({ 5 | subsets: ["latin"], 6 | variable: "--font-sans", 7 | }) 8 | 9 | export const fontUrban = Urbanist({ 10 | subsets: ["latin"], 11 | variable: "--font-urban", 12 | }) 13 | 14 | export const fontHeading = localFont({ 15 | src: "./CalSans-SemiBold.woff2", 16 | variable: "--font-heading", 17 | }) 18 | 19 | export const fontSubheading = localFont({ 20 | src: "./st-r.woff2", 21 | variable: "--font-subheading", 22 | }) -------------------------------------------------------------------------------- /assets/fonts/st-b.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/assets/fonts/st-b.woff2 -------------------------------------------------------------------------------- /assets/fonts/st-m.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/assets/fonts/st-m.woff2 -------------------------------------------------------------------------------- /assets/fonts/st-r.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/assets/fonts/st-r.woff2 -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/styles/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | 3 | const nextConfig = { 4 | images: { 5 | domains: ['utfs.io'], 6 | }, 7 | webpack: ( 8 | config, 9 | { buildId, dev, isServer, defaultLoaders, nextRuntime, webpack } 10 | ) => { 11 | config.module.rules.push({ 12 | test: /\.mjs$/, 13 | include: /node_modules/, 14 | type: "javascript/auto", 15 | }); 16 | return config; 17 | }, 18 | }; 19 | 20 | module.exports = nextConfig; -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('prettier').Config} */ 2 | module.exports = { 3 | endOfLine: "lf", 4 | semi: false, 5 | singleQuote: false, 6 | tabWidth: 2, 7 | trailingComma: "es5", 8 | importOrder: [ 9 | "^(react/(.*)$)|^(react$)", 10 | "^(next/(.*)$)|^(next$)", 11 | "", 12 | "", 13 | "^types$", 14 | "^@/env(.*)$", 15 | "^@/types/(.*)$", 16 | "^@/config/(.*)$", 17 | "^@/lib/(.*)$", 18 | "^@/hooks/(.*)$", 19 | "^@/components/ui/(.*)$", 20 | "^@/components/(.*)$", 21 | "^@/styles/(.*)$", 22 | "^@/app/(.*)$", 23 | "", 24 | "^[./]", 25 | ], 26 | importOrderParserPlugins: ["typescript", "jsx", "decorators-legacy"], 27 | plugins: [ 28 | "@ianvs/prettier-plugin-sort-imports", 29 | "prettier-plugin-tailwindcss", 30 | ], 31 | } 32 | -------------------------------------------------------------------------------- /public/broad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/broad.png -------------------------------------------------------------------------------- /public/broad1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/broad1.png -------------------------------------------------------------------------------- /public/cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/cover.jpg -------------------------------------------------------------------------------- /public/cover2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/cover2.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/favicon.ico -------------------------------------------------------------------------------- /public/images.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/images.png -------------------------------------------------------------------------------- /public/light-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/light-1.png -------------------------------------------------------------------------------- /public/light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/light.png -------------------------------------------------------------------------------- /public/map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/map.png -------------------------------------------------------------------------------- /public/obs-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/obs-1.png -------------------------------------------------------------------------------- /public/obs-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/obs-2.png -------------------------------------------------------------------------------- /public/obs-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/obs-3.png -------------------------------------------------------------------------------- /public/obs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/obs.png -------------------------------------------------------------------------------- /public/people-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/people-01.png -------------------------------------------------------------------------------- /public/people-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/people-02.png -------------------------------------------------------------------------------- /public/people-03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/people-03.png -------------------------------------------------------------------------------- /public/people-04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/people-04.png -------------------------------------------------------------------------------- /public/people-05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/people-05.png -------------------------------------------------------------------------------- /public/placeholder-user.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/placeholder-user.jpg -------------------------------------------------------------------------------- /public/spooky.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/stream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/stream.png -------------------------------------------------------------------------------- /public/streamlabs.avif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/streamlabs.avif -------------------------------------------------------------------------------- /public/vmix-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/vmix-1.png -------------------------------------------------------------------------------- /public/vmix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/vmix.png -------------------------------------------------------------------------------- /public/wire.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/wire.jpg -------------------------------------------------------------------------------- /public/wire.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/public/wire.png -------------------------------------------------------------------------------- /src/actions/block.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import { revalidatePath } from "next/cache" 4 | 5 | import { blockUser, unblockUser } from "@/lib/blocks" 6 | import { getSelf } from "@/lib/valid-user" 7 | 8 | // const roomService = new RoomServiceClient( 9 | // process.env.LIVEKIT_API_URL!, 10 | // process.env.LIVEKIT_API_KEY!, 11 | // process.env.LIVEKIT_API_SECRET!, 12 | // ); 13 | 14 | export const onBlock = async (id: string) => { 15 | const self = await getSelf() 16 | 17 | const blockedUser = await blockUser(id) 18 | 19 | try { 20 | // await roomService.removeParticipant(self.id, id); 21 | } catch { 22 | // This means user is not in the room 23 | } 24 | 25 | revalidatePath(`/streams/u/${self.username}/jemaw`) 26 | 27 | return blockedUser 28 | } 29 | 30 | export const onUnblock = async (id: string) => { 31 | const self = await getSelf() 32 | const unblockedUser = await unblockUser(id) 33 | 34 | revalidatePath(`/u/${self.username}/jemaw`) 35 | return unblockedUser 36 | } -------------------------------------------------------------------------------- /src/actions/follow.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { revalidatePath } from "next/cache"; 4 | 5 | import { 6 | followUser, 7 | meFollowing, 8 | mefollowUser, 9 | unfollowUser 10 | } from "@/lib/follow-user"; 11 | 12 | export const onFollow = async (id: string) => { 13 | try { 14 | const followedUser = await followUser(id); 15 | 16 | revalidatePath("/"); 17 | 18 | if (followedUser) { 19 | revalidatePath(`/${followedUser.following.username}`); 20 | revalidatePath(`/${followedUser.following.username}/followers`); 21 | } 22 | 23 | return followedUser; 24 | } catch (error) { 25 | throw new Error("Interal Error"); 26 | }; 27 | }; 28 | export const onMeFollow = async (id: string) => { 29 | try { 30 | const followedUser = await mefollowUser(id); 31 | 32 | revalidatePath("/"); 33 | 34 | if (followedUser) { 35 | revalidatePath(`/${followedUser.following.username}`); 36 | revalidatePath(`/${followedUser.following.username}/followers`); 37 | } 38 | 39 | return followedUser; 40 | } catch (error) { 41 | throw new Error("Interal Error"); 42 | }; 43 | }; 44 | 45 | export const onUnfollow = async (id: string) => { 46 | try { 47 | const unfollowedUser = await unfollowUser(id); 48 | 49 | revalidatePath("/"); 50 | 51 | if (unfollowedUser) { 52 | revalidatePath(`/${unfollowedUser.following.username}`) 53 | revalidatePath(`/${unfollowedUser.following.username}/followers`); 54 | revalidatePath(`/${unfollowedUser.following.username}`) 55 | } 56 | 57 | return unfollowedUser; 58 | } catch (error) { 59 | throw new Error("Internal Error"); 60 | } 61 | } -------------------------------------------------------------------------------- /src/actions/forms.ts: -------------------------------------------------------------------------------- 1 | 'use server' 2 | 3 | import { z } from 'zod' 4 | import { db } from "@/lib/db" 5 | import { revalidatePath } from "next/cache" 6 | 7 | 8 | const formSchema = z.object({ 9 | email: z.string().email(), 10 | msg: z.string().min(3) 11 | }) 12 | 13 | export const submitMsg = async (state: any , formData: FormData) => { 14 | console.log('The form msg is : , ', formData) 15 | const email = formData.get('email') as string 16 | const msg = formData.get('msg') as string 17 | 18 | const formStatus = formSchema.safeParse({ 19 | email: email, 20 | msg: msg 21 | }) 22 | let result = null 23 | if (formStatus.success) { 24 | 25 | result = await db.forms.create({ 26 | data: { 27 | email: email, 28 | msg: msg 29 | } 30 | }) 31 | } 32 | 33 | revalidatePath("/") 34 | return result 35 | 36 | 37 | } -------------------------------------------------------------------------------- /src/actions/sendmsg.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import { deleteMsg, readMessage, sendMessage } from "@/lib/msg" 4 | import { getSelf } from "@/lib/valid-user" 5 | import { revalidatePath } from "next/cache" 6 | 7 | type MsgProps = { 8 | receiverId: string 9 | receiverName: string 10 | msg: string 11 | } 12 | export const onSendMessage = async ({ 13 | receiverId, 14 | receiverName, 15 | msg, 16 | }: MsgProps) => { 17 | const self = await getSelf() 18 | const sendMsg = await sendMessage({ 19 | msg: msg, 20 | receiverId: receiverId, 21 | receiverName: receiverName, 22 | senderName: self.username, 23 | senderId: self.id, 24 | }) 25 | 26 | return sendMsg 27 | } 28 | 29 | export const onReadMessage = async (id: string , read: boolean) => { 30 | const self = await getSelf() 31 | const readmsg = await readMessage(id , read) 32 | revalidatePath(`/u/${self.username}/messages `) 33 | return readmsg 34 | 35 | } 36 | 37 | 38 | export const onDelete = async (id: string) => { 39 | const self = await getSelf() 40 | const delmsg = await deleteMsg(id) 41 | revalidatePath(`/u/${self.username}/messages `) 42 | return delmsg 43 | } -------------------------------------------------------------------------------- /src/actions/stream.ts: -------------------------------------------------------------------------------- 1 | "use server" 2 | 3 | import { revalidatePath } from "next/cache" 4 | import { Stream } from "@prisma/client" 5 | 6 | import { db } from "@/lib/db" 7 | import { getSelf } from "@/lib/valid-user" 8 | 9 | export const updateStream = async (values: Partial) => { 10 | try { 11 | const self = await getSelf() 12 | const selfStream = await db.stream.findUnique({ 13 | where: { 14 | userId: self.id, 15 | }, 16 | }) 17 | 18 | if (!selfStream) { 19 | throw new Error("Stream not found") 20 | } 21 | 22 | const validData = { 23 | thumbnailUrl: values.thumbnailUrl, 24 | name: values.name, 25 | isChatEnabled: values.isChatEnabled, 26 | isChatFollowersOnly: values.isChatFollowersOnly, 27 | isChatDelayed: values.isChatDelayed, 28 | } 29 | 30 | const stream = await db.stream.update({ 31 | where: { 32 | id: selfStream.id, 33 | }, 34 | data: { 35 | ...validData, 36 | }, 37 | }) 38 | 39 | revalidatePath(`/streams/u/${self.username}/chat`) 40 | revalidatePath(`/streams/u/${self.username}`) 41 | revalidatePath(`/streams/${self.username}`) 42 | 43 | return stream 44 | } catch (err) { 45 | console.log("Error: ", err) 46 | throw new Error("Internal Error") 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/actions/token.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { v4 } from "uuid"; 3 | import { AccessToken } from "livekit-server-sdk"; 4 | 5 | import { getSelf } from "@/lib/valid-user"; 6 | import { getUserById } from "@/lib/user-helper"; 7 | import { isBlockedByUser } from "@/lib/blocks"; 8 | 9 | export const createViewerToken = async (hostIdentity: string) => { 10 | let self; 11 | 12 | try { 13 | self = await getSelf(); 14 | } catch { 15 | const id = v4(); 16 | const username = `guest#${Math.floor(Math.random() * 1000)}`; 17 | self = { id, username }; 18 | } 19 | 20 | const host = await getUserById(hostIdentity); 21 | 22 | if (!host) { 23 | throw new Error("User not found"); 24 | } 25 | 26 | const isBlocked = await isBlockedByUser(host.id); 27 | 28 | if (isBlocked) { 29 | throw new Error("User is blocked"); 30 | } 31 | 32 | const isHost = self.id === host.id; 33 | 34 | const token = new AccessToken( 35 | process.env.LIVEKIT_API_KEY!, 36 | process.env.LIVEKIT_API_SECRET!, 37 | { 38 | identity: isHost ? `host-${self.id}` : self.id, 39 | name: self.username, 40 | } 41 | ); 42 | 43 | token.addGrant({ 44 | room: host.id, 45 | roomJoin: true, 46 | canPublish: false, 47 | canPublishData: true, 48 | }); 49 | 50 | return await Promise.resolve(token.toJwt()); 51 | }; -------------------------------------------------------------------------------- /src/actions/user.ts: -------------------------------------------------------------------------------- 1 | "use server"; 2 | 3 | import { User } from "@prisma/client"; 4 | import { revalidatePath } from "next/cache"; 5 | 6 | import { db } from "@/lib/db"; 7 | import { getSelf } from "@/lib/valid-user"; 8 | 9 | export const updateUser = async (values: Partial) => { 10 | const self = await getSelf(); 11 | 12 | const validData = { 13 | bio: values.bio, 14 | }; 15 | 16 | const user = await db.user.update({ 17 | where: { id: self.id }, 18 | data: { ...validData } 19 | }); 20 | 21 | revalidatePath(`/${self.username}`); 22 | revalidatePath(`/u/${self.username}`); 23 | 24 | return user; 25 | }; -------------------------------------------------------------------------------- /src/app/(auth)/_components/center-wrapper.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | 3 | interface CenterWrapperProps { 4 | children: ReactNode 5 | } 6 | 7 | const CenterWrapper = ({children}:CenterWrapperProps) => { 8 | return ( 9 |
10 | {children} 11 | 12 |
13 | ) 14 | 15 | } 16 | export default CenterWrapper -------------------------------------------------------------------------------- /src/app/(auth)/_components/logo.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import { Poppins } from "next/font/google"; 3 | 4 | import { cn } from "@/lib/utils"; 5 | 6 | export const Logo = () => { 7 | return ( 8 |
9 |
10 | {/* Gamehub */} 16 |
17 |
21 |

22 | Cha-Cha 23 |

24 |

25 | Let's Gather - Ethiopia's first streaming services. 26 |

27 |
28 |
29 | ); 30 | }; -------------------------------------------------------------------------------- /src/app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | import { ReactNode } from "react"; 2 | import {Logo} from './_components/logo' 3 | interface CenterWrapperProps { 4 | children: ReactNode 5 | } 6 | 7 | const CenterWrapper = ({children}:CenterWrapperProps) => { 8 | return ( 9 |
10 | 11 | {children} 12 | 13 |
14 | ) 15 | 16 | } 17 | export default CenterWrapper -------------------------------------------------------------------------------- /src/app/(auth)/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from "@clerk/nextjs"; 2 | import CenterWrapper from "../../_components/center-wrapper"; 3 | 4 | export default function Page() { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/app/(auth)/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from "@clerk/nextjs"; 2 | import CenterWrapper from "../../_components/center-wrapper"; 3 | 4 | export default function Page() { 5 | return( 6 |
7 | 8 |
9 | ) 10 | } -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/(home)/_components/container.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { useMediaQuery } from "usehooks-ts"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | import { useCreatorSidebar } from "@/store/use-creator-sidebar"; 8 | 9 | interface ContainerProps { 10 | children: React.ReactNode; 11 | }; 12 | 13 | export const Container = ({ 14 | children, 15 | }: ContainerProps) => { 16 | const { 17 | collapsed, 18 | onCollapse, 19 | onExpand, 20 | } = useCreatorSidebar((state) => state); 21 | const matches = useMediaQuery(`(max-width: 1024px)`); 22 | 23 | useEffect(() => { 24 | if (matches) { 25 | onCollapse(); 26 | } else { 27 | onExpand(); 28 | } 29 | }, [matches, onCollapse, onExpand]); 30 | 31 | return ( 32 |
36 | {children} 37 |
38 | ); 39 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/(home)/loading.tsx: -------------------------------------------------------------------------------- 1 | import { StreamPlayerSkeleton } from "@/components/streaming"; 2 | 3 | const CreatorLoading = () => { 4 | return ( 5 |
6 | 7 |
8 | ); 9 | } 10 | 11 | export default CreatorLoading; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/(home)/page.tsx: -------------------------------------------------------------------------------- 1 | import { currentUser } from "@clerk/nextjs"; 2 | 3 | import { getUserByUsername } from "@/lib/user-helper"; 4 | import { StreamPlayer } from "@/components/streaming"; 5 | 6 | interface CreatorPageProps { 7 | params: { 8 | username: string; 9 | }; 10 | }; 11 | 12 | const CreatorPage = async ({ 13 | params, 14 | }: CreatorPageProps) => { 15 | const externalUser = await currentUser(); 16 | const user = await getUserByUsername(params.username); 17 | 18 | if (!user || user.externalUserId !== externalUser?.id || !user.stream) { 19 | throw new Error("Unauthorized"); 20 | } 21 | 22 | return ( 23 |
24 | 29 | 30 |
31 | ); 32 | } 33 | 34 | export default CreatorPage; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/_components/asides/index.tsx: -------------------------------------------------------------------------------- 1 | import { Toggle } from "./toogle"; 2 | import { Wrapper } from "./wrapper"; 3 | import { Navigation } from "./navigates"; 4 | import { getAllUnread } from "@/lib/msg"; 5 | 6 | export const Sidebar = async () => { 7 | const count = await getAllUnread() 8 | 9 | return ( 10 | 11 | 12 | 13 | 14 | ); 15 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/_components/asides/navigates.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { usePathname } from "next/navigation" 4 | import { useUser } from "@clerk/nextjs" 5 | import { 6 | DollarSign, 7 | Fullscreen, 8 | KeyRound, 9 | MessageSquareDashed, 10 | MessageSquare, 11 | Settings, 12 | Users, 13 | MessageSquareIcon 14 | } from "lucide-react" 15 | import { type Message } from "@prisma/client" 16 | import { NavItem, NavItemSkeleton } from "./items" 17 | 18 | export const Navigation = ({ count }: { count: number }) => { 19 | const pathname = usePathname() 20 | const { user } = useUser() 21 | 22 | const routes = [ 23 | { 24 | label: "Stream", 25 | href: `/u/${user?.username}`, 26 | icon: Fullscreen, 27 | 28 | 29 | }, 30 | { 31 | label: "Keys", 32 | href: `/u/${user?.username}/keys`, 33 | icon: KeyRound, 34 | }, 35 | { 36 | label: "Jemaw", 37 | href: `/u/${user?.username}/jemaw`, 38 | icon: Users, 39 | }, 40 | { 41 | label: "1 time Messages", 42 | href: `/u/${user?.username}/messages`, 43 | icon: MessageSquareIcon, 44 | isNewMsg: count 45 | }, 46 | { 47 | label: "Payments & Analytics", 48 | href: `/u/${user?.username}/payments`, 49 | icon: DollarSign, 50 | }, 51 | { 52 | label: "Chat Setting", 53 | href: `/u/${user?.username}/chat`, 54 | icon: MessageSquareDashed, 55 | }, 56 | { 57 | label: "Account Setting", 58 | href: `/u/${user?.username}/setting`, 59 | icon: Settings, 60 | }, 61 | ] 62 | 63 | if (!user?.username) { 64 | return ( 65 |
    66 | {[...Array(5)].map((_, i) => ( 67 | 68 | ))} 69 |
70 | ) 71 | } 72 | 73 | return ( 74 |
    75 | {routes.map((route) => ( 76 | 84 | ))} 85 |
86 | ) 87 | } 88 | -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/_components/asides/toogle.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ArrowLeftFromLine, ArrowRightFromLine } from "lucide-react"; 4 | 5 | import { Hint } from "@/components/hint"; 6 | import { Button } from "@/components/ui/button"; 7 | import { useCreatorSidebar } from "@/store/use-creator-sidebar"; 8 | 9 | export const Toggle = () => { 10 | const { 11 | collapsed, 12 | onExpand, 13 | onCollapse, 14 | } = useCreatorSidebar((state) => state); 15 | 16 | const label = collapsed ? "Expand" : "Collapse"; 17 | 18 | return ( 19 | <> 20 | {collapsed && ( 21 |
22 | 23 | 30 | 31 |
32 | )} 33 | {!collapsed && ( 34 |
35 |

36 | Dashboard 37 |

38 | 39 | 46 | 47 |
48 | )} 49 | 50 | ); 51 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/_components/asides/wrapper.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { cn } from "@/lib/utils"; 4 | import { useCreatorSidebar } from "@/store/use-creator-sidebar"; 5 | 6 | interface WrapperProps { 7 | children: React.ReactNode; 8 | }; 9 | 10 | export const Wrapper = ({ 11 | children, 12 | }: WrapperProps) => { 13 | const { collapsed } = useCreatorSidebar((state) => state); 14 | 15 | return ( 16 | 22 | ); 23 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/_components/container.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useEffect } from "react"; 4 | import { useMediaQuery } from "usehooks-ts"; 5 | 6 | import { cn } from "@/lib/utils"; 7 | import { useCreatorSidebar } from "@/store/use-creator-sidebar"; 8 | 9 | interface ContainerProps { 10 | children: React.ReactNode; 11 | }; 12 | 13 | export const Container = ({ 14 | children, 15 | }: ContainerProps) => { 16 | const { 17 | collapsed, 18 | onCollapse, 19 | onExpand, 20 | } = useCreatorSidebar((state) => state); 21 | const matches = useMediaQuery(`(max-width: 1024px)`); 22 | 23 | useEffect(() => { 24 | if (matches) { 25 | onCollapse(); 26 | } else { 27 | onExpand(); 28 | } 29 | }, [matches, onCollapse, onExpand]); 30 | 31 | return ( 32 |
36 | {children} 37 |
38 | ); 39 | }; 40 | -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/_components/navs/act.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { HomeIcon } from "lucide-react"; 3 | import { currentUser } from "@clerk/nextjs"; 4 | import { Button } from "@/components/ui/button"; 5 | 6 | import UserNav from "@/components/user-nav"; 7 | 8 | export const Actions = async () => { 9 | const user = await currentUser() 10 | 11 | return ( 12 |
13 | 24 | {/* */} 27 | 28 |
29 | ); 30 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/_components/navs/index.tsx: -------------------------------------------------------------------------------- 1 | import { Logo } from "./logo"; 2 | import { Actions } from "./act"; 3 | 4 | export const Navbar = () => { 5 | return ( 6 | 10 | ); 11 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/_components/navs/logo.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import Image from "next/image"; 3 | import { Poppins } from "next/font/google"; 4 | 5 | import { cn } from "@/lib/utils"; 6 | 7 | export const Logo = () => { 8 | return ( 9 | 10 |
11 |
12 | {/* Gamehub */} 18 |
19 |
23 |

24 | ChaCha 25 |

26 |

27 | Cook Something 28 |

29 |
30 |
31 | 32 | ); 33 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/chat/_components/toogly-chat.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { toast } from "sonner"; 4 | import { useTransition } from "react"; 5 | 6 | import { Switch } from "@/components/ui/switch"; 7 | import { updateStream } from "@/actions/stream"; 8 | import { Skeleton } from "@/components/ui/skeleton"; 9 | 10 | type FieldTypes = "isChatEnabled" | "isChatDelayed" | "isChatFollowersOnly"; 11 | 12 | interface ToggleCardProps { 13 | label: string; 14 | value: boolean; 15 | field: FieldTypes; 16 | }; 17 | 18 | export const ToggleCard = ({ 19 | label, 20 | value = false, 21 | field, 22 | }: ToggleCardProps) => { 23 | const [isPending, startTransition] = useTransition(); 24 | 25 | const onChange = () => { 26 | startTransition(() => { 27 | updateStream({ [field]: !value }) 28 | .then(() => toast.success("Chat settings updated!")) 29 | .catch(() => toast.error("Something went wrong")); 30 | }); 31 | }; 32 | 33 | return ( 34 |
35 |
36 |

37 | {label} 38 |

39 |
40 | 45 | {value ? "On" : "Off"} 46 | 47 |
48 |
49 |
50 | ); 51 | }; 52 | 53 | export const ToggleCardSkeleton = () => { 54 | return ( 55 | 56 | ); 57 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/chat/loading.tsx: -------------------------------------------------------------------------------- 1 | import { Skeleton } from "@/components/ui/skeleton"; 2 | 3 | import { ToggleCardSkeleton } from "./_components/toogly-chat"; 4 | 5 | const ChatLoading = () => { 6 | return ( 7 |
8 | 9 |
10 | 11 | 12 | 13 |
14 |
15 | ); 16 | }; 17 | 18 | export default ChatLoading; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/chat/page.tsx: -------------------------------------------------------------------------------- 1 | import { getSelf } from "@/lib/valid-user" 2 | import { getStreamByUserId } from "@/lib/stream"; 3 | 4 | import { ToggleCard } from "./_components/toogly-chat"; 5 | import { PageHeader, PageHeaderDescription, PageHeaderHeading } from "@/components/page-header"; 6 | 7 | const ChatPage = async () => { 8 | const self = await getSelf(); 9 | 10 | const stream = await getStreamByUserId(self.id); 11 | if (!stream) { 12 | throw new Error("Stream not found"); 13 | } 14 | 15 | return ( 16 |
17 |
18 | 23 | Chat Setting 24 | 25 | Manage your chat settings 26 | 27 | 28 |
29 |
30 | 35 | 40 | 45 |
46 |
47 | ); 48 | }; 49 | 50 | export default ChatPage; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/followers/page.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | getFollowerLists, 3 | isFollowingUser, 4 | meFollowing, 5 | } from "@/lib/follow-user" 6 | import { getUserByUsername } from "@/lib/user-helper" 7 | import { getSelf } from "@/lib/valid-user" 8 | import FollowerAvater from "@/components/followr-avatar" 9 | import { PageHeader, PageHeaderDescription, PageHeaderHeading } from "@/components/page-header" 10 | 11 | interface FollowerPageProps { 12 | params: { 13 | username: string 14 | } 15 | } 16 | 17 | const FollowerPage = async ({ params }: FollowerPageProps) => { 18 | // console.log("THe props : ", prosp) 19 | const self = await getSelf() 20 | const user = await getUserByUsername(params.username) 21 | 22 | const followers = await getFollowerLists(user?.id) 23 | const isFollowing = await meFollowing(user?.id) 24 | 25 | 26 | 27 | return ( 28 |
29 | 30 | 35 | Followers 36 | 37 | Manage your followers 38 | 39 | 40 |
41 | {followers.map((follow) => { 42 | return ( 43 | 50 | // 56 | ) 57 | })} 58 |
59 |
60 | ) 61 | } 62 | 63 | export default FollowerPage 64 | -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/jemaw/_components/columns.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { ArrowUpDown } from "lucide-react"; 4 | import { ColumnDef } from "@tanstack/react-table"; 5 | 6 | import { Button } from "@/components/ui/button"; 7 | import { UserAvatar } from "@/components/user-avatar"; 8 | 9 | import { UnblockButton } from "./unblock-btn"; 10 | 11 | export type BlockedUser = { 12 | id: string; 13 | userId: string; 14 | imageUrl: string; 15 | username: string; 16 | createdAt: string; 17 | } 18 | 19 | export const columns: ColumnDef[] = [ 20 | { 21 | accessorKey: "username", 22 | header: ({ column }) => ( 23 | 30 | ), 31 | cell: ({ row }) => ( 32 |
33 | 37 | {row.original.username} 38 |
39 | ) 40 | }, 41 | { 42 | accessorKey: "createdAt", 43 | header: ({ column }) => ( 44 | 51 | ), 52 | }, 53 | { 54 | id: "actions", 55 | cell: ({ row }) => 56 | }, 57 | ] -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/jemaw/_components/unblock-btn.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { toast } from "sonner"; 4 | import { useTransition } from "react"; 5 | 6 | import { onUnblock } from "@/actions/block"; 7 | import { Button } from "@/components/ui/button"; 8 | 9 | interface UnblockButtonProps { 10 | userId: string; 11 | }; 12 | 13 | export const UnblockButton = ({ 14 | userId, 15 | }: UnblockButtonProps) => { 16 | const [isPending, startTransition] = useTransition(); 17 | 18 | const onClick = () => { 19 | startTransition(() => { 20 | onUnblock(userId) 21 | .then((result) => toast.success(`User ${result.blocked.username} unblocked`)) 22 | .catch(() => toast.error("Something went wrong")) 23 | }); 24 | }; 25 | 26 | return ( 27 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/jemaw/page.tsx: -------------------------------------------------------------------------------- 1 | import {format } from "date-fns"; 2 | 3 | import { getBlockedUsers } from "@/lib/blocks"; 4 | 5 | import { DataTable } from "./_components/data-table"; 6 | import { columns } from "./_components/columns"; 7 | import { PageHeader ,PageHeaderDescription , PageHeaderHeading} from "@/components/page-header"; 8 | 9 | const CommunityPage = async () => { 10 | const blockedUsers = await getBlockedUsers(); 11 | 12 | const formattedData = blockedUsers.map((block) => ({ 13 | ...block, 14 | userId: block.blocked.id, 15 | imageUrl: block.blocked.imageUrl, 16 | username: block.blocked.username, 17 | createdAt: format(new Date(block.blocked.createdAt), "dd/MM/yyyy"), 18 | })); 19 | 20 | return ( 21 |
22 |
23 | 28 | Jemaw Setting 29 | 30 | Manage Jemaw here 31 | 32 | 33 |
34 | 35 |
36 | ); 37 | } 38 | 39 | export default CommunityPage; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/keys/_components/copy-btn.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | import { CheckCheck, Copy } from "lucide-react"; 5 | 6 | import { Button } from "@/components/ui/button"; 7 | 8 | interface CopyButtonProps { 9 | value?: string; 10 | }; 11 | 12 | export const CopyButton = ({ 13 | value, 14 | }: CopyButtonProps) => { 15 | const [isCopied, setIsCopied] = useState(false); 16 | 17 | const onCopy = () => { 18 | if (!value) return; 19 | 20 | setIsCopied(true); 21 | navigator.clipboard.writeText(value); 22 | setTimeout(() => { 23 | setIsCopied(false); 24 | }, 1000); 25 | }; 26 | 27 | const Icon = isCopied ? CheckCheck : Copy; 28 | 29 | return ( 30 | 38 | ); 39 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/keys/_components/stream-key-gen-card.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState } from "react"; 4 | 5 | import { Input } from "@/components/ui/input"; 6 | import { Button } from "@/components/ui/button"; 7 | 8 | import { CopyButton } from "./copy-btn"; 9 | 10 | interface KeyCardProps { 11 | value: string | null; 12 | }; 13 | 14 | export const KeyCard = ({ 15 | value, 16 | }: KeyCardProps) => { 17 | const [show, setShow] = useState(false); 18 | 19 | return ( 20 |
21 |
22 |

23 | Stream Key 24 |

25 |
26 |
27 | 33 | 34 |
35 | 42 |
43 |
44 |
45 | ); 46 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/keys/_components/url-feild.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "@/components/ui/input"; 2 | 3 | import { CopyButton } from "./copy-btn"; 4 | 5 | interface UrlCardProps { 6 | value: string | null; 7 | }; 8 | 9 | export const UrlCard = ({ 10 | value, 11 | }: UrlCardProps) => { 12 | return ( 13 |
14 |
15 |

16 | Server URL 17 |

18 |
19 |
20 | 25 | 28 |
29 |
30 |
31 |
32 | ); 33 | }; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/keys/page.tsx: -------------------------------------------------------------------------------- 1 | import { getSelf } from "@/lib/valid-user"; 2 | import { getStreamByUserId } from "@/lib/stream"; 3 | 4 | import { UrlCard } from "./_components/url-feild"; 5 | import { KeyCard } from "./_components/stream-key-gen-card"; 6 | import { ConnectModal } from "./_components/dailog-key"; 7 | import { PageHeader, PageHeaderDescription, PageHeaderHeading } from "@/components/page-header"; 8 | 9 | const KeysPage = async () => { 10 | const self = await getSelf(); 11 | const stream = await getStreamByUserId(self.id); 12 | 13 | if (!stream) { 14 | throw new Error("Stream not found"); 15 | } 16 | 17 | return ( 18 |
19 |
20 | 25 | Connecting Key 26 | 27 | Create a connecting key to get started 28 | 29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 | ) 38 | }; 39 | 40 | export default KeysPage; -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/layout.tsx: -------------------------------------------------------------------------------- 1 | import { redirect } from "next/navigation" 2 | 3 | import { getFollowerLists } from "@/lib/follow-user" 4 | import { getUserByUsername } from "@/lib/user-helper" 5 | import { getSelf } from "@/lib/valid-user" 6 | 7 | import { Sidebar } from "./_components/asides" 8 | import { Container } from "./_components/container" 9 | import { Navbar } from "./_components/navs" 10 | 11 | interface CreatorLayoutProps { 12 | params: { username: string } 13 | children: React.ReactNode 14 | } 15 | 16 | const CreatorLayout = async ({ params, children }: CreatorLayoutProps) => { 17 | const self = await getUserByUsername(params.username) 18 | const self2 = await getSelf() 19 | 20 | if (!self) { 21 | redirect("/") 22 | } 23 | 24 | return ( 25 | <> 26 | 27 |
28 | 29 | {children} 30 |
31 | 32 | ) 33 | } 34 | 35 | export default CreatorLayout 36 | -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/messages/_components/delete-msg.tsx: -------------------------------------------------------------------------------- 1 | 'use client' 2 | 3 | 4 | import { onDelete } from "@/actions/sendmsg" 5 | import { Loader, Trash } from "lucide-react" 6 | import { useTransition } from "react" 7 | import { toast } from "sonner" 8 | 9 | const DeleteMsgPage = ({ id }: { id: string }) => { 10 | const [isPending, startTransition] = useTransition() 11 | const handleDelete = () => { 12 | startTransition(() => { 13 | onDelete(id).then(() => toast.success("The meesage is deleted")).catch((err) => toast.error(JSON.stringify(err))) 14 | 15 | }) 16 | 17 | 18 | } 19 | return ( 20 |
21 | {isPending ? : } 22 | 23 | 24 |
25 | ) 26 | } 27 | export default DeleteMsgPage -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/messages/_components/mssage-box.tsx: -------------------------------------------------------------------------------- 1 | import { getUserByUsername } from "@/lib/user-helper" 2 | import { sendError } from "next/dist/server/api-utils" 3 | 4 | type MessageBoxProps = { 5 | msgid: string, 6 | senderName: string, 7 | senderId: string , 8 | read:boolean | null, 9 | msg: string, 10 | 11 | 12 | 13 | } 14 | 15 | const MessageBox = async ({msgid , senderName , senderId , read , msg}: MessageBoxProps) => { 16 | const user = await getUserByUsername(senderName) 17 | 18 | return ( 19 |
20 |
21 | 22 |
23 |
24 |

25 | {msg} 26 |

27 |
28 | Avatar 39 |
40 |

{senderName}

41 |

Acme Corporation

42 |
43 |
44 |
45 | 46 |
47 |
48 |
49 | ) 50 | } 51 | 52 | 53 | export default MessageBox -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/messages/_components/nark-as.tsx: -------------------------------------------------------------------------------- 1 | 2 | 'use client' 3 | 4 | import { toast } from "sonner" 5 | import { 6 | Select, 7 | SelectContent, 8 | SelectGroup, 9 | SelectItem, 10 | SelectLabel, 11 | SelectTrigger, 12 | SelectValue, 13 | } from "@/components/ui/select" 14 | import { useTransform } from "framer-motion" 15 | import { useState, useTransition } from "react" 16 | import { onReadMessage } from "@/actions/sendmsg" 17 | 18 | type MarkAsProps = { 19 | mmsgid: string, 20 | read: boolean 21 | } 22 | 23 | const MarkAs = ({ mmsgid, read }: MarkAsProps) => { 24 | const translateRead = read ? '1' : '0' 25 | const [readType, setReadType] = useState(translateRead) 26 | const [isPending, startTransition] = useTransition() 27 | const types = ['Mark As Read', 'Mark As Unread'] 28 | const handleValueChange = (value: string) => { 29 | startTransition(() => { 30 | let readState = value === '0' ? false : true 31 | setReadType(value) 32 | // console.log('The readstate is:', readState, value) 33 | onReadMessage(mmsgid, readState).then((data) => toast.success(data ? 'You have marked as a read' : "You have marked as unread ")).catch(() => toast.error("Failed changing the status")) 34 | }) 35 | 36 | } 37 | 38 | return ( 39 |
40 | 55 | 56 | 57 |
58 | ) 59 | } 60 | 61 | export default MarkAs -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/messages/page.tsx: -------------------------------------------------------------------------------- 1 | import { getSelf } from "@/lib/valid-user" 2 | import { getAllMessages } from "@/lib/msg" 3 | import { MessageShow } from "@/components/message-show" 4 | import { PageHeader, PageHeaderHeading, PageHeaderDescription } from "@/components/page-header" 5 | import Link from 'next/link' 6 | import { Separator } from "@/components/ui/separator" 7 | 8 | export const metadata = { 9 | title: "One time messages", 10 | description: "Messages from public dumped here" 11 | } 12 | 13 | const Messages = async () => { 14 | const self = await getSelf() 15 | const messages = await getAllMessages(self.id) 16 | return ( 17 |
18 | 23 | Messages 24 | 25 | Explore your private dm from Chacha Jema 26 | 27 | 28 | {!messages.length && ( 29 |
30 |

No Messages :)

31 |

32 | You can hit up on streams to make your self as bold as possible 33 | 34 |

35 | 36 |
37 | )} 38 |
39 | 40 | {messages.map((r) => { 41 | 42 | return () 43 | })} 44 |
45 | 46 |
47 | ) 48 | } 49 | 50 | export default Messages -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/payments/_components/analytics.tsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Kinfe123/chacha/980e15044f77299c47dfb4acb4695329d8ac503a/src/app/(dashboard)/u/[username]/payments/_components/analytics.tsx -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/payments/_components/calander.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { CalendarIcon } from "lucide-react" 5 | import { addDays, format } from "date-fns" 6 | import { DateRange } from "react-day-picker" 7 | 8 | import { cn } from "@/lib/utils" 9 | import { Button } from "@/components/ui/button" 10 | import { Calendar } from "@/components/ui/calendar" 11 | import { 12 | Popover, 13 | PopoverContent, 14 | PopoverTrigger, 15 | } from "@/components/ui/popover" 16 | 17 | export function CalendarDateRangePicker({ 18 | className, 19 | }: React.HTMLAttributes) { 20 | const [date, setDate] = React.useState({ 21 | from: new Date(2023, 0, 20), 22 | to: addDays(new Date(2023, 0, 20), 20), 23 | }) 24 | 25 | return ( 26 |
27 | 28 | 29 | 51 | 52 | 53 | 61 | 62 | 63 |
64 | ) 65 | } -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/payments/_components/overview.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { Bar, BarChart, ResponsiveContainer, XAxis, YAxis } from "recharts" 4 | 5 | const data = [ 6 | { 7 | name: "Jan", 8 | total: Math.floor(Math.random() * 5000) + 1000, 9 | }, 10 | { 11 | name: "Feb", 12 | total: Math.floor(Math.random() * 5000) + 1000, 13 | }, 14 | { 15 | name: "Mar", 16 | total: Math.floor(Math.random() * 5000) + 1000, 17 | }, 18 | { 19 | name: "Apr", 20 | total: Math.floor(Math.random() * 5000) + 1000, 21 | }, 22 | { 23 | name: "May", 24 | total: Math.floor(Math.random() * 5000) + 1000, 25 | }, 26 | { 27 | name: "Jun", 28 | total: Math.floor(Math.random() * 5000) + 1000, 29 | }, 30 | { 31 | name: "Jul", 32 | total: Math.floor(Math.random() * 5000) + 1000, 33 | }, 34 | { 35 | name: "Aug", 36 | total: Math.floor(Math.random() * 5000) + 1000, 37 | }, 38 | { 39 | name: "Sep", 40 | total: Math.floor(Math.random() * 5000) + 1000, 41 | }, 42 | { 43 | name: "Oct", 44 | total: Math.floor(Math.random() * 5000) + 1000, 45 | }, 46 | { 47 | name: "Nov", 48 | total: Math.floor(Math.random() * 5000) + 1000, 49 | }, 50 | { 51 | name: "Dec", 52 | total: Math.floor(Math.random() * 5000) + 1000, 53 | }, 54 | ] 55 | 56 | export function Overview() { 57 | return ( 58 | 59 | 60 | 67 | `$${value}`} 73 | /> 74 | 80 | 81 | 82 | ) 83 | } -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/payments/_components/search.tsx: -------------------------------------------------------------------------------- 1 | import { Input } from "@/components/ui/input" 2 | 3 | export function Search() { 4 | return ( 5 |
6 | 11 |
12 | ) 13 | } 14 | -------------------------------------------------------------------------------- /src/app/(dashboard)/u/[username]/setting/page.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next" 2 | 3 | import { UserProfile } from "@/components/auth/user-profile" 4 | import { 5 | PageHeader, 6 | PageHeaderDescription, 7 | PageHeaderHeading, 8 | } from "@/components/page-header" 9 | import { Shell } from "@/components/shell" 10 | 11 | export const metadata: Metadata = { 12 | // metadataBase: process.env.NEXT_PUBLIC_APP_URL ?? "", 13 | title: "Account Setting", 14 | description: "Manage your account settings", 15 | } 16 | 17 | export default function AccountPage() { 18 | return ( 19 | 20 | 25 | Account Setting 26 | 27 | Manage your account settings 28 | 29 | 30 |
35 | 36 |
37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/app/(web)/(home)/_components/custome-text.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { motion } from "framer-motion" 4 | 5 | import { textContainer, textVariant2 } from "@/lib/utils" 6 | 7 | export const TypingText = ({ 8 | title, 9 | textStyles, 10 | }: { 11 | title: string 12 | textStyles: string 13 | }) => ( 14 | 18 | {Array.from(title).map((letter, index) => ( 19 | 20 | {letter === " " ? "\u00A0" : letter} 21 | 22 | ))} 23 | 24 | ) 25 | 26 | export const TitleText = ({ 27 | title, 28 | textStyles, 29 | }: { 30 | title: React.ReactElement 31 | textStyles?: string 32 | }) => ( 33 | 39 | {title} 40 | 41 | ) 42 | -------------------------------------------------------------------------------- /src/app/(web)/(home)/_components/for-free.tsx: -------------------------------------------------------------------------------- 1 | import Shiny from "./shiny-btn" 2 | import styles from "./styles" 3 | 4 | const ForFree = () => { 5 | return ( 6 | 7 |
8 |
9 |

10 | Enjoy for{" "} 11 | Unlimited 12 |

13 | 14 |

Start cooking your contents at easiest.

15 | 16 |
17 |
18 | ) 19 | } 20 | 21 | export default ForFree 22 | -------------------------------------------------------------------------------- /src/app/(web)/(home)/_components/forms/form.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | 3 | 'use client' 4 | import styles from './forms.module.css' 5 | import { cn } from "@/lib/utils" 6 | import { submitMsg } from '@/actions/forms' 7 | import { useFormState, useFormStatus } from 'react-dom' 8 | import { Check, Loader } from 'lucide-react' 9 | 10 | 11 | 12 | 13 | 14 | export default function Form() { 15 | 16 | const [state, formAction] = useFormState(submitMsg, null) 17 | 18 | return ( 19 | <> 20 |
21 |

Wanna Collab , Hit us!

22 |
23 |
24 | 25 | 26 |
27 |
28 | 29 |