new-term-radar
신규 AI 용어를 매일 추적, X·Hugging Face·GitHub 등에서 모델·제품·레포 이름을 빠르게 수집
수천 명의 검증된 퍼블리셔가 모인 마켓플레이스를 기반으로 한 링크 구축 서비스
Link building services built around a marketplace of thousands of vetted publishers. Order guest posts, niche edits, digital PR and SEO content with real metrics and upfr
마케팅TypeScript · PLpgSQLGitHub @omniaagencyuk-code온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기README 전체를 불러오는 중입니다. 아래는 저장된 발췌입니다.
A modern SaaS-style link building marketplace where SEO agencies, brands and
marketers browse vetted websites and buy guest posts, niche edits and digital
PR placements.
Live domain: pressparrot.com (not yet connected - see Next steps).
Built with Next.js (App Router), TypeScript and Tailwind CSS. The whole
application runs locally on mock data with no external accounts required.
Open.README 전문 보기 ↗A modern SaaS-style link building marketplace where SEO agencies, brands and marketers browse vetted websites and buy guest posts, niche edits and digital PR placements.
Live domain: pressparrot.com (not yet connected - see Next steps).
Built with Next.js (App Router), TypeScript and Tailwind CSS. The whole application runs locally on mock data with no external accounts required.
npm install
cp .env.example .env.local # optional - defaults work as-is
npm run dev
Open http://localhost:3000.
| Command | What it does |
|---|---|
npm run dev | Start the dev server |
npm run build | Production build (also type checks) |
npm run start | Serve the production build |
npm run lint | ESLint |
npm run typecheck | tsc --noEmit |
| URL | Access | Page |
|---|---|---|
/ | Public | Homepage - the main SEO and conversion landing page |
/link-building | Public | Link building service page |
/guest-posts | Public | Guest posts service page |
/niche-edits | Public | Niche edits service page |
/content-writing | Public | Content writing service page, with pricing |
/digital-pr | Public | Digital PR service page |
/link-building-agencies | Public | For SEO agencies |
/marketplace | Both | Gateway when signed out, the marketplace when signed in |
/how-it-works | Public | Product explainer and vetting criteria |
/pricing | Public | Plans and typical placement prices |
/resources | Public | Guides, benchmarks and contact |
/login, /signup | Public | Authentication screens |
/websites | Account | Redirects to /marketplace |
/websites/[slug] | Account | Website listing detail and order builder |
/dashboard | Account | Customer dashboard |
/dashboard/content | Account | Content orders |
/dashboard/content/new | Account | Content brief and basket |
/dashboard/content/[id] | Account | Content order detail, messages and revisions |
/dashboard/orders | Account | Placement orders and current draft |
/dashboard/saved | Account | Saved websites |
/dashboard/billing | Account | Invoices and plan |
/dashboard/account | Account | Profile and session controls |
/admin/login | Public | Admin sign-in |
/admin | Admin | Admin dashboard |
/admin/websites | Admin | Website database with row actions |
/admin/websites/new | Admin | Website editor (create) |
/admin/websites/[id] | Admin | Website editor (edit) |
/admin/websites/import | Admin | Bulk CSV import |
/admin/orders | Admin | Placement order management |
/admin/content-orders | Admin | Content order queue |
/admin/content-orders/[id] | Admin | Assign writer, change status, deliver content |
/admin/users | Admin | Accounts |
/admin/categories | Admin | Niches |
/admin/settings | Admin | Brand, marketplace defaults and content pricing |
/sitemap.xml, /robots.txt | Public | Generated by src/app/sitemap.ts and robots.ts |
Copy, links and images on the public pages are editable from the admin. The model is content, not layout: a page declares the fields an editor may change, and the design around them stays as built.
/admin/pages lists what can be edited; /admin/pages/[slug] edits it.src/lib/cms/pages/ as the shipped copy. A page always
renders correctly against an empty database, and any field can be reset back
to the original - individually or for the whole page.src/lib/cms/markdown.tsx. That renderer
produces React nodes rather than an HTML string, so no dangerouslySetInnerHTML
sits in the path and markup cannot be injected through content.javascript: URL.Editable today: the homepage and the five service pages. To add another, write
a module under src/lib/cms/pages/ and register it in
src/lib/cms/registry.ts.
Posts live at /resources, with category filtering and individual pages at
/resources/[slug].
/admin/blog lists every post including drafts; create, edit and delete from
there.public/images/posts/; direct upload arrives with Supabase storage.The publisher inventory is a benefit of holding an account, not public content.
proxy.ts gates /websites, /websites/:path* and /dashboard/:path*
before a page renders, and each page re-checks with
requireCustomerSession() because server actions have their own endpoints./marketplace is deliberately public: signed out it renders a gateway that
explains the marketplace and sells the signup; signed in it renders the
marketplace itself.websiteService.getPublicPreview() returns rows through
src/lib/services/marketplace-preview.ts, which drops the domain, slug and
id and bands the metrics. A public page has no way to obtain a real domain,
so nothing leaks into the HTML, the RSC payload or the structured data.robots: noindex and are rendered per request rather
than pre-rendered, and the sitemap contains no listing URLs.Sessions are signed HMAC cookies (pp_session), the same construction as the
admin session. Set AUTH_SESSION_SECRET in production; it falls back to
ADMIN_SESSION_SECRET, and outside production to a fixed development value.
In production with neither set, sign-in fails closed with a clear message.
Customers can order content on its own, with no placement attached.
/content-writing./dashboard/content/new - brief an article, Add to Order,
brief the next, then place the whole order. Quantities are supported, so
"3 x 1,000 words" is one line./dashboard/content and /dashboard/content/[id], with the
original brief, messages, deliveries and revision requests./admin/content-orders - assign a writer, change status, set a price,
add internal notes, deliver a draft or final article./admin/settings (fixed per length, per word, or
both). It ships empty: until a price is set, the public page and the
order form show "on request" rather than a number or a zero.Statuses: Brief Received, Writing, Editing, Ready for Review, Revision Requested, Complete.
src/
app/
(marketing)/ Public site: home, marketplace, listings, content pages
(auth)/ Login and signup, split-screen layout
dashboard/ Customer area
admin/ Internal admin area: login/ plus the (protected) group
robots.ts, sitemap.ts, icon.svg
proxy.ts Gates every /admin route (Next 16's middleware)
components/
ui/ Primitives: button, input, select, table, sheet, ...
layout/ Header, footer, logo, container, page hero
marketplace/ Filters, table, cards, pagination, toolbar
website/ Listing detail sections and the order card
home/ Homepage sections: hero, marketplace preview, trust
metrics, trusted by, feature strip, how it works,
niche grid, final CTA
dashboard/ Dashboard shell, widgets and the order editor
support/ Always-on live chat launcher
shared/ Badges, metrics, FAQ, foliage, handwritten notes
admin/ Admin tables and website editor
admin/import/ CSV import wizard components
shared/ Badges, metrics, FAQ
lib/
config/ brand.ts (single source of brand truth), navigation.ts,
support.ts (live chat), marketing.ts (headline stats),
social-proof.ts (logos and testimonials),
how-it-works.ts (the four steps)
types/ Website, Category, Country, User, Order, Settings, ...
data/ Mock seed data (72 websites, orders, users, categories)
services/ Repository layer - the only place data is fetched
auth/ Admin session signing and access checks
import/ CSV parsing, field mapping, normalisation, validation
hooks/ Marketplace filter state, localStorage
providers/ Auth, favourites and order draft contexts
utils/ Formatting, labels, class merging
supabase/
migrations/ Proposed SQL schema (not required to run the app)
README.md How to create the project and apply the schema
Everything brand-specific lives in src/lib/config/brand.ts: name, domain,
tagline, logo paths, favicon, colours, support and sales email, company
registration details, social links, currency, locale and the browser-storage
namespace. No component hardcodes the brand name, so a rename is a one-file
change.
Identity. Dark navy and off-white carry the interface; parrot green is the accent; coral is the secondary, used sparingly (the beak in the mark, the "Most popular" pricing badge). The personality lives in the brand layer - the mark, the odd line of copy - while product surfaces stay literal and calm. Saved websites are "Saved websites", not "the perch".
Colour tokens are also declared in src/app/globals.css under @theme so
Tailwind can generate utilities from them. If you change a brand colour, update
both places. The parrot mark is drawn inline in
src/components/layout/logo.tsx (exported as ParrotMark) and duplicated as
src/app/icon.svg for the favicon; swap both for an <Image> when a bespoke
asset exists.
Placeholders to fill in before launch: company registration number, VAT
number, trading address and phone are empty in the config and are hidden from
the footer while blank. The X and LinkedIn URLs assume the pressparrot
handle on each.
/admin is gated server-side, not in the browser:
src/proxy.ts runs before any admin route renders and redirects anyone
without a valid session cookie to /admin/login. (Next.js 16 renamed the
middleware convention to proxy; behaviour is unchanged.)requireAdminSession(), because server
actions have their own endpoints and are reachable without rendering a page.Sign-in checks the address against an allowlist and the password against a
shared secret, then sets an HMAC-signed, httpOnly cookie scoped to /admin
that lasts 12 hours. Both checks failing produce the same message, so the form
cannot be used to discover who the admins are.
Three server-only environment variables are required:
| Variable | Purpose |
|---|---|
ADMIN_EMAILS | Comma separated addresses allowed to sign in |
ADMIN_PASSWORD | Shared password for the sign-in form |
ADMIN_SESSION_SECRET | Signs the session cookie (openssl rand -base64 32) |
Never prefix these with NEXT_PUBLIC_, which would ship them to the browser.
If any is missing the admin area locks itself rather than opening up, so a
misconfigured deployment fails closed.
Removing an address from ADMIN_EMAILS revokes access on that person's next
request, without waiting for their session to expire. Changing
ADMIN_SESSION_SECRET signs everyone out immediately.
This is a stopgap. A shared password is not per-person identity: it cannot
be rotated for one individual, and it leaves no audit trail of who changed
what. When Supabase Auth is connected, give each admin their own credentials
and keep ADMIN_EMAILS as the profiles.role check.
Browsing and buying are deliberately separate:
/dashboard/orders) - the only place details are entered:
target URL (required), anchor text, preferred landing page, notes for the
publisher, and an optional article attachment. Edits save to the draft as
you type and survive a reload. Switching service reprices that line.The article attachment records the file's name and size only. The upload itself needs storage, so wire it to Supabase Storage when the backend lands.
Two drop-in slots, both resolved at build time so a missing file falls back to a drawn version rather than rendering broken:
| File | Where it appears |
|---|---|
public/images/press-parrot-logo.svg | Header, footer, dashboard and admin logos |
public/images/press-parrot-hero.webp | The homepage mascot |
public/images/README.md has the full spec for both. The logo lookup lives in
next.config.ts rather than the component, because the logo renders inside
client components that cannot read the filesystem; the mascot lookup is in
src/components/home/parrot-hero.tsx.
src/lib/config/social-proof.ts holds customer logos and testimonials, and
ships empty on purpose. While the lists are empty the homepage shows
clearly-marked placeholders rather than implying customers or quotes that do
not exist. Add real entries only with the customer's permission.
A launcher sits bottom-right on every page. src/lib/config/support.ts
chooses what it does:
NEXT_PUBLIC_CHAT_PROVIDER | Behaviour |
|---|---|
built-in (default) | In-house panel; the message opens a prefilled email, tagged with the page the visitor was on |
crisp | Loads Crisp, using NEXT_PUBLIC_CRISP_WEBSITE_ID |
tawk | Loads Tawk.to, using NEXT_PUBLIC_TAWK_PROPERTY_ID and NEXT_PUBLIC_TAWK_WIDGET_ID |
off | Hides the launcher |
The built-in panel is a stopgap, not real live chat: it guarantees there is always a way to reach you, but nobody is watching a queue. Open a Crisp or Tawk account and flip the variable to get genuine live conversations, with no code change.
/admin/websites/import, reached from the Import CSV button beside Add
website. A five step flow: upload, map columns, review, import, results.
DR,
Traffic, RD, Domain URL, Category, Location and many more, listed
in src/lib/import/fields.ts). Anything unmapped is ignored, and a
destination can only be claimed once.45K and 1.2M as
numbers, strips currency symbols, accepts yes/no/true/false/1/0/y/n, and
splits multi-value cells on commas, semicolons or pipes.example.com, www.example.com and
https://example.com/page count as one. The default is to skip existing
websites; choosing update writes only the columns the CSV actually supplied,
so empty cells never blank a stored value.websiteService.bulkUpsert() in batches of 200,
with progress shown and a confirmation above 100 rows. That method is the
single place to swap in a Supabase batched upsert.Only the domain column is required. Download the template from step one for
the full set of supported columns.
Components never import mock data directly. They call the service layer:
import { websiteService } from '@/lib/services';
const websites = await websiteService.getAll();
const website = await websiteService.getBySlug('casinoguru-co-uk');
const results = await websiteService.search({ niches: ['finance'], sort: 'dr-desc' });
await websiteService.create({ domain: 'example.co.uk' });
await websiteService.update(id, { status: 'active' });
Every method is async and returns plain typed objects, so the mock
implementation can be replaced with Supabase queries without touching the UI.
src/lib/services/data-source.ts holds the mock / supabase switch.
The marketplace filter, sort and pagination logic lives in
src/lib/services/query-engine.ts as pure functions. The server uses it inside
websiteService.search(); the client uses the same functions for instant
filtering, so results are guaranteed to match.
src/app/(auth)/actions.ts.writerName is a plain field on a content order. The
data model is ready for a writers table; there is no writer portal./link-building-agencies./pricing and
/how-it-works are still hardcoded.git init # already initialised in this project
git add .
git commit -m "Press Parrot marketplace MVP"
git remote add origin [email protected]:<your-org>/<your-repo>.git
git branch -M main
git push -u origin main
See supabase/README.md for the full walkthrough:
create the project, run the three migration files, add the keys to
.env.local, import the website inventory, then switch each service over to a
Supabase implementation and replace the mock auth calls.
In Vercel, Add New → Project and import the GitHub repository.
Framework preset: Next.js. No build command or output directory changes are needed.
Add environment variables for Production, Preview and Development:
| Variable | Example | Required |
|---|---|---|
NEXT_PUBLIC_SITE_URL | https://pressparrot.com | yes |
NEXT_PUBLIC_DATA_SOURCE | mock or supabase | yes |
NEXT_PUBLIC_ENABLE_MOCK_AUTH | false in production | yes |
ADMIN_EMAILS | [email protected],them@... | yes |
ADMIN_PASSWORD | a long random password | yes |
ADMIN_SESSION_SECRET | openssl rand -base64 32 | yes |
NEXT_PUBLIC_CHAT_PROVIDER | built-in, crisp, tawk or off | no |
NEXT_PUBLIC_SUPABASE_URL | https://<ref>.supabase.co | with Supabase |
NEXT_PUBLIC_SUPABASE_ANON_KEY | eyJhb... | with Supabase |
SUPABASE_SERVICE_ROLE_KEY | eyJhb... (server only, never exposed) | with Supabase |
Deploy, then add pressparrot.com under Project → Settings → Domains
with "Redirect apex domains to www" unticked: the bare domain is
canonical for this brand. Add www.pressparrot.com as well and set it to
redirect to the apex, so the two hosts never both serve the same pages.
Vercel shows the exact DNS records to add at the registrar. Copy the values
it displays rather than any written down here; its recommended targets
change over time.
NEXT_PUBLIC_SITE_URL must match the canonical host exactly
(https://pressparrot.com, no www, no trailing slash), otherwise the
canonical tags and point at a host that redirects.
aria-sort on sortable columns.prefers-reduced-motion is respected.sitemap.xml