Compare commits
No commits in common. "feature/api" and "main" have entirely different histories.
feature/ap
...
main
@ -1,75 +0,0 @@
|
|||||||
# API-Endpunkte
|
|
||||||
|
|
||||||
Diese Endpunkte decken die Kernfunktionen von OnlyPrompt ab.
|
|
||||||
|
|
||||||
## Auth
|
|
||||||
|
|
||||||
### `POST /api/v1/auth/register`
|
|
||||||
- Erstellt einen neuen Benutzer.
|
|
||||||
|
|
||||||
### `POST /api/v1/auth/login`
|
|
||||||
- Loggt einen Benutzer ein.
|
|
||||||
|
|
||||||
### `GET /api/v1/auth/me`
|
|
||||||
- Gibt den aktuell eingeloggten Benutzer zurueck.
|
|
||||||
|
|
||||||
## User / Profile
|
|
||||||
|
|
||||||
### `GET /api/v1/users/{id}`
|
|
||||||
- Ruft das oeffentliche Benutzerprofil anhand der ID ab.
|
|
||||||
|
|
||||||
### `GET /api/v1/profile/me`
|
|
||||||
- Ruft das eigene Profil des eingeloggten Benutzers ab.
|
|
||||||
|
|
||||||
### `PUT /api/v1/profile/me`
|
|
||||||
- Bearbeitet das eigene Profil des eingeloggten Benutzers.
|
|
||||||
|
|
||||||
## Prompts
|
|
||||||
|
|
||||||
### `GET /api/v1/prompts`
|
|
||||||
- Gibt alle veroeffentlichten Prompts zurueck.
|
|
||||||
|
|
||||||
### `GET /api/v1/prompts/{id}`
|
|
||||||
- Ruft einen einzelnen Prompt anhand der ID ab.
|
|
||||||
|
|
||||||
### `POST /api/v1/prompts`
|
|
||||||
- Erstellt einen neuen Prompt.
|
|
||||||
|
|
||||||
### `PUT /api/v1/prompts/{id}`
|
|
||||||
- Bearbeitet einen bestehenden Prompt.
|
|
||||||
|
|
||||||
### `DELETE /api/v1/prompts/{id}`
|
|
||||||
- Loescht einen Prompt.
|
|
||||||
|
|
||||||
## Filter fuer Marketplace
|
|
||||||
|
|
||||||
### `GET /api/v1/prompts?category=coding`
|
|
||||||
- Filtert Prompts nach Kategorie.
|
|
||||||
|
|
||||||
### `GET /api/v1/prompts?search=python`
|
|
||||||
- Sucht Prompts ueber einen Suchbegriff.
|
|
||||||
|
|
||||||
### `GET /api/v1/prompts?creatorId=5`
|
|
||||||
- Filtert Prompts nach einem bestimmten Creator.
|
|
||||||
|
|
||||||
## Kategorien
|
|
||||||
|
|
||||||
### `GET /api/v1/categories`
|
|
||||||
- Gibt alle verfuegbaren Kategorien zurueck.
|
|
||||||
|
|
||||||
## Kaeufe
|
|
||||||
|
|
||||||
### `POST /api/v1/purchases`
|
|
||||||
- Erstellt einen Kauf fuer einen Prompt.
|
|
||||||
|
|
||||||
### `GET /api/v1/purchases/me`
|
|
||||||
- Gibt alle eigenen Kaeufe des eingeloggten Benutzers zurueck.
|
|
||||||
|
|
||||||
## Reviews
|
|
||||||
|
|
||||||
### `GET /api/v1/prompts/{id}/reviews`
|
|
||||||
- Ruft alle Bewertungen fuer einen Prompt ab.
|
|
||||||
|
|
||||||
### `POST /api/v1/prompts/{id}/reviews`
|
|
||||||
- Erstellt eine neue Bewertung fuer einen Prompt.
|
|
||||||
|
|
||||||
@ -1,78 +0,0 @@
|
|||||||
# Kernmodell
|
|
||||||
|
|
||||||
Dieses Kernmodell beschreibt die wichtigsten Klassen, die OnlyPrompt fuer Login, Profile, Marketplace und Prompt-Kaeufe benoetigt.
|
|
||||||
|
|
||||||
## Klassen
|
|
||||||
|
|
||||||
### User
|
|
||||||
- `id: int`
|
|
||||||
- `username: string`
|
|
||||||
- `email: string`
|
|
||||||
- `passwordHash: string`
|
|
||||||
- `role: string`
|
|
||||||
- `createdAt: datetime`
|
|
||||||
|
|
||||||
### Profile
|
|
||||||
- `id: int`
|
|
||||||
- `userId: int`
|
|
||||||
- `displayName: string`
|
|
||||||
- `bio: string`
|
|
||||||
- `avatarUrl: string`
|
|
||||||
- `specialties: string`
|
|
||||||
|
|
||||||
### Prompt
|
|
||||||
- `id: int`
|
|
||||||
- `creatorId: int`
|
|
||||||
- `categoryId: int`
|
|
||||||
- `title: string`
|
|
||||||
- `description: string`
|
|
||||||
- `content: text`
|
|
||||||
- `price: decimal`
|
|
||||||
- `thumbnailUrl: string`
|
|
||||||
- `ratingAverage: float`
|
|
||||||
- `reviewCount: int`
|
|
||||||
- `status: string`
|
|
||||||
- `createdAt: datetime`
|
|
||||||
- `updatedAt: datetime`
|
|
||||||
|
|
||||||
### Category
|
|
||||||
- `id: int`
|
|
||||||
- `name: string`
|
|
||||||
- `slug: string`
|
|
||||||
|
|
||||||
### Purchase
|
|
||||||
- `id: int`
|
|
||||||
- `buyerId: int`
|
|
||||||
- `promptId: int`
|
|
||||||
- `pricePaid: decimal`
|
|
||||||
- `purchasedAt: datetime`
|
|
||||||
|
|
||||||
### Review
|
|
||||||
- `id: int`
|
|
||||||
- `promptId: int`
|
|
||||||
- `userId: int`
|
|
||||||
- `rating: int`
|
|
||||||
- `comment: string`
|
|
||||||
- `createdAt: datetime`
|
|
||||||
|
|
||||||
## Beziehungen
|
|
||||||
|
|
||||||
- Ein `User` hat genau ein `Profile`.
|
|
||||||
- Ein `User` kann viele `Prompts` erstellen.
|
|
||||||
- Ein `Prompt` gehoert zu genau einer `Category`.
|
|
||||||
- Ein `User` kann viele `Prompts` ueber `Purchase` kaufen.
|
|
||||||
- Ein `User` kann viele `Reviews` schreiben.
|
|
||||||
- Ein `Prompt` kann viele `Reviews` erhalten.
|
|
||||||
|
|
||||||
## UML-Kurzform
|
|
||||||
|
|
||||||
```text
|
|
||||||
User 1 --- 1 Profile
|
|
||||||
User 1 --- * Prompt
|
|
||||||
Category 1 --- * Prompt
|
|
||||||
User 1 --- * Purchase
|
|
||||||
Prompt 1 --- * Purchase
|
|
||||||
User 1 --- * Review
|
|
||||||
Prompt 1 --- * Review
|
|
||||||
```
|
|
||||||
|
|
||||||
@ -1 +0,0 @@
|
|||||||
|
|
||||||
346
backend/main.py
@ -1,346 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from fastapi import FastAPI, HTTPException, Query, Response, status
|
|
||||||
|
|
||||||
from .models import ChatMessage, Favorite, Follow, Prompt, Rating, User
|
|
||||||
from .schemas import (
|
|
||||||
AuthResponse,
|
|
||||||
ChatMessageCreateRequest,
|
|
||||||
ChatMessageResponse,
|
|
||||||
CreatorDetailResponse,
|
|
||||||
FavoriteCreateRequest,
|
|
||||||
FavoriteResponse,
|
|
||||||
FollowResponse,
|
|
||||||
LoginRequest,
|
|
||||||
ProfileUpdateRequest,
|
|
||||||
PromptCreateRequest,
|
|
||||||
PromptResponse,
|
|
||||||
PromptUpdateRequest,
|
|
||||||
RatingCreateRequest,
|
|
||||||
RatingResponse,
|
|
||||||
RegisterRequest,
|
|
||||||
UserResponse,
|
|
||||||
)
|
|
||||||
from .store import store
|
|
||||||
|
|
||||||
app = FastAPI(title="OnlyPrompt API", version="1.0.0")
|
|
||||||
|
|
||||||
|
|
||||||
def get_current_user() -> User:
|
|
||||||
user = store.users.get(store.current_user_id)
|
|
||||||
if user is None:
|
|
||||||
raise HTTPException(status_code=404, detail="Current user not found.")
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/auth/register", response_model=AuthResponse, status_code=status.HTTP_201_CREATED)
|
|
||||||
def register(payload: RegisterRequest) -> AuthResponse:
|
|
||||||
if any(user.email == payload.email for user in store.users.values()):
|
|
||||||
raise HTTPException(status_code=400, detail="Email already exists.")
|
|
||||||
if any(user.username == payload.username for user in store.users.values()):
|
|
||||||
raise HTTPException(status_code=400, detail="Username already exists.")
|
|
||||||
|
|
||||||
now = datetime.utcnow()
|
|
||||||
user_id = store.next_id("user")
|
|
||||||
user = User(
|
|
||||||
id=user_id,
|
|
||||||
email=payload.email,
|
|
||||||
password_hash=payload.password,
|
|
||||||
full_name=payload.full_name,
|
|
||||||
username=payload.username,
|
|
||||||
bio="",
|
|
||||||
location="",
|
|
||||||
avatar_url="",
|
|
||||||
role=payload.role,
|
|
||||||
is_verified=False,
|
|
||||||
created_at=now,
|
|
||||||
)
|
|
||||||
store.users[user_id] = user
|
|
||||||
store.current_user_id = user_id
|
|
||||||
return AuthResponse(message="User created successfully.", user=UserResponse.model_validate(user))
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/auth/login", response_model=AuthResponse)
|
|
||||||
def login(payload: LoginRequest) -> AuthResponse:
|
|
||||||
user = next((item for item in store.users.values() if item.email == payload.email), None)
|
|
||||||
if user is None or user.password_hash != payload.password:
|
|
||||||
raise HTTPException(status_code=401, detail="Invalid email or password.")
|
|
||||||
|
|
||||||
store.current_user_id = user.id
|
|
||||||
return AuthResponse(message="Login successful.", user=UserResponse.model_validate(user))
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/prompts", response_model=list[PromptResponse])
|
|
||||||
def list_prompts(
|
|
||||||
category: str | None = Query(default=None),
|
|
||||||
search: str | None = Query(default=None),
|
|
||||||
) -> list[PromptResponse]:
|
|
||||||
prompts = list(store.prompts.values())
|
|
||||||
|
|
||||||
if category:
|
|
||||||
prompts = [prompt for prompt in prompts if prompt.category.lower() == category.lower()]
|
|
||||||
|
|
||||||
if search:
|
|
||||||
term = search.lower()
|
|
||||||
prompts = [
|
|
||||||
prompt
|
|
||||||
for prompt in prompts
|
|
||||||
if term in prompt.title.lower() or term in prompt.description.lower()
|
|
||||||
]
|
|
||||||
|
|
||||||
return [PromptResponse.model_validate(prompt) for prompt in prompts]
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/prompts/{prompt_id}", response_model=PromptResponse)
|
|
||||||
def get_prompt(prompt_id: int) -> PromptResponse:
|
|
||||||
prompt = store.prompts.get(prompt_id)
|
|
||||||
if prompt is None:
|
|
||||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
|
||||||
return PromptResponse.model_validate(prompt)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/prompts", response_model=PromptResponse, status_code=status.HTTP_201_CREATED)
|
|
||||||
def create_prompt(payload: PromptCreateRequest) -> PromptResponse:
|
|
||||||
creator = store.users.get(payload.creator_id)
|
|
||||||
if creator is None or creator.role != "creator":
|
|
||||||
raise HTTPException(status_code=404, detail="Creator not found.")
|
|
||||||
|
|
||||||
prompt = Prompt(
|
|
||||||
id=store.next_id("prompt"),
|
|
||||||
title=payload.title,
|
|
||||||
description=payload.description,
|
|
||||||
content=payload.content,
|
|
||||||
image_url=payload.image_url,
|
|
||||||
category=payload.category,
|
|
||||||
price=payload.price,
|
|
||||||
creator_id=payload.creator_id,
|
|
||||||
created_at=datetime.utcnow(),
|
|
||||||
)
|
|
||||||
store.prompts[prompt.id] = prompt
|
|
||||||
return PromptResponse.model_validate(prompt)
|
|
||||||
|
|
||||||
|
|
||||||
@app.put("/api/prompts/{prompt_id}", response_model=PromptResponse)
|
|
||||||
def update_prompt(prompt_id: int, payload: PromptUpdateRequest) -> PromptResponse:
|
|
||||||
prompt = store.prompts.get(prompt_id)
|
|
||||||
if prompt is None:
|
|
||||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
|
||||||
|
|
||||||
updates = payload.model_dump(exclude_unset=True)
|
|
||||||
for field, value in updates.items():
|
|
||||||
setattr(prompt, field, value)
|
|
||||||
return PromptResponse.model_validate(prompt)
|
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/api/prompts/{prompt_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
def delete_prompt(prompt_id: int) -> Response:
|
|
||||||
if prompt_id not in store.prompts:
|
|
||||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
|
||||||
del store.prompts[prompt_id]
|
|
||||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/creators", response_model=list[UserResponse])
|
|
||||||
def list_creators(sort: str | None = Query(default=None)) -> list[UserResponse]:
|
|
||||||
creators = [user for user in store.users.values() if user.role == "creator"]
|
|
||||||
|
|
||||||
if sort == "new":
|
|
||||||
creators.sort(key=lambda item: item.created_at, reverse=True)
|
|
||||||
elif sort == "popular":
|
|
||||||
creators.sort(
|
|
||||||
key=lambda item: sum(1 for follow in store.follows.values() if follow.creator_id == item.id),
|
|
||||||
reverse=True,
|
|
||||||
)
|
|
||||||
elif sort == "top_rated":
|
|
||||||
def creator_rating(user: User) -> float:
|
|
||||||
creator_prompt_ids = [prompt.id for prompt in store.prompts.values() if prompt.creator_id == user.id]
|
|
||||||
ratings = [rating.score for rating in store.ratings.values() if rating.prompt_id in creator_prompt_ids]
|
|
||||||
return sum(ratings) / len(ratings) if ratings else 0
|
|
||||||
|
|
||||||
creators.sort(key=creator_rating, reverse=True)
|
|
||||||
|
|
||||||
return [UserResponse.model_validate(creator) for creator in creators]
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/creators/{creator_id}", response_model=CreatorDetailResponse)
|
|
||||||
def get_creator(creator_id: int) -> CreatorDetailResponse:
|
|
||||||
creator = store.users.get(creator_id)
|
|
||||||
if creator is None or creator.role != "creator":
|
|
||||||
raise HTTPException(status_code=404, detail="Creator not found.")
|
|
||||||
|
|
||||||
prompts = [prompt for prompt in store.prompts.values() if prompt.creator_id == creator_id]
|
|
||||||
return CreatorDetailResponse(
|
|
||||||
creator=UserResponse.model_validate(creator),
|
|
||||||
prompts=[PromptResponse.model_validate(prompt) for prompt in prompts],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/prompts/{prompt_id}/ratings", response_model=list[RatingResponse])
|
|
||||||
def list_ratings(prompt_id: int) -> list[RatingResponse]:
|
|
||||||
if prompt_id not in store.prompts:
|
|
||||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
|
||||||
ratings = [rating for rating in store.ratings.values() if rating.prompt_id == prompt_id]
|
|
||||||
return [RatingResponse.model_validate(rating) for rating in ratings]
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/prompts/{prompt_id}/ratings", response_model=RatingResponse, status_code=status.HTTP_201_CREATED)
|
|
||||||
def create_rating(prompt_id: int, payload: RatingCreateRequest) -> RatingResponse:
|
|
||||||
if prompt_id not in store.prompts:
|
|
||||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
|
||||||
if payload.user_id not in store.users:
|
|
||||||
raise HTTPException(status_code=404, detail="User not found.")
|
|
||||||
|
|
||||||
rating = Rating(
|
|
||||||
id=store.next_id("rating"),
|
|
||||||
prompt_id=prompt_id,
|
|
||||||
user_id=payload.user_id,
|
|
||||||
score=payload.score,
|
|
||||||
comment=payload.comment,
|
|
||||||
created_at=datetime.utcnow(),
|
|
||||||
)
|
|
||||||
store.ratings[rating.id] = rating
|
|
||||||
return RatingResponse.model_validate(rating)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/favorites", response_model=list[FavoriteResponse])
|
|
||||||
def list_favorites() -> list[FavoriteResponse]:
|
|
||||||
current_user = get_current_user()
|
|
||||||
favorites = [item for item in store.favorites.values() if item.user_id == current_user.id]
|
|
||||||
return [FavoriteResponse.model_validate(item) for item in favorites]
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/favorites", response_model=FavoriteResponse, status_code=status.HTTP_201_CREATED)
|
|
||||||
def create_favorite(payload: FavoriteCreateRequest) -> FavoriteResponse:
|
|
||||||
current_user = get_current_user()
|
|
||||||
if payload.prompt_id not in store.prompts:
|
|
||||||
raise HTTPException(status_code=404, detail="Prompt not found.")
|
|
||||||
|
|
||||||
existing = next(
|
|
||||||
(
|
|
||||||
item
|
|
||||||
for item in store.favorites.values()
|
|
||||||
if item.user_id == current_user.id and item.prompt_id == payload.prompt_id
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if existing is not None:
|
|
||||||
return FavoriteResponse.model_validate(existing)
|
|
||||||
|
|
||||||
favorite = Favorite(
|
|
||||||
id=store.next_id("favorite"),
|
|
||||||
user_id=current_user.id,
|
|
||||||
prompt_id=payload.prompt_id,
|
|
||||||
created_at=datetime.utcnow(),
|
|
||||||
)
|
|
||||||
store.favorites[favorite.id] = favorite
|
|
||||||
return FavoriteResponse.model_validate(favorite)
|
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/api/favorites/{prompt_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
def delete_favorite(prompt_id: int) -> Response:
|
|
||||||
current_user = get_current_user()
|
|
||||||
favorite_id = next(
|
|
||||||
(
|
|
||||||
item.id
|
|
||||||
for item in store.favorites.values()
|
|
||||||
if item.user_id == current_user.id and item.prompt_id == prompt_id
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if favorite_id is None:
|
|
||||||
raise HTTPException(status_code=404, detail="Favorite not found.")
|
|
||||||
|
|
||||||
del store.favorites[favorite_id]
|
|
||||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/follows/{creator_id}", response_model=FollowResponse, status_code=status.HTTP_201_CREATED)
|
|
||||||
def create_follow(creator_id: int) -> FollowResponse:
|
|
||||||
current_user = get_current_user()
|
|
||||||
creator = store.users.get(creator_id)
|
|
||||||
if creator is None or creator.role != "creator":
|
|
||||||
raise HTTPException(status_code=404, detail="Creator not found.")
|
|
||||||
|
|
||||||
existing = next(
|
|
||||||
(
|
|
||||||
item
|
|
||||||
for item in store.follows.values()
|
|
||||||
if item.follower_id == current_user.id and item.creator_id == creator_id
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if existing is not None:
|
|
||||||
return FollowResponse.model_validate(existing)
|
|
||||||
|
|
||||||
follow = Follow(
|
|
||||||
id=store.next_id("follow"),
|
|
||||||
follower_id=current_user.id,
|
|
||||||
creator_id=creator_id,
|
|
||||||
created_at=datetime.utcnow(),
|
|
||||||
)
|
|
||||||
store.follows[follow.id] = follow
|
|
||||||
return FollowResponse.model_validate(follow)
|
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/api/follows/{creator_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
def delete_follow(creator_id: int) -> Response:
|
|
||||||
current_user = get_current_user()
|
|
||||||
follow_id = next(
|
|
||||||
(
|
|
||||||
item.id
|
|
||||||
for item in store.follows.values()
|
|
||||||
if item.follower_id == current_user.id and item.creator_id == creator_id
|
|
||||||
),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
if follow_id is None:
|
|
||||||
raise HTTPException(status_code=404, detail="Follow not found.")
|
|
||||||
|
|
||||||
del store.follows[follow_id]
|
|
||||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/profile", response_model=UserResponse)
|
|
||||||
def get_profile() -> UserResponse:
|
|
||||||
return UserResponse.model_validate(get_current_user())
|
|
||||||
|
|
||||||
|
|
||||||
@app.put("/api/profile", response_model=UserResponse)
|
|
||||||
def update_profile(payload: ProfileUpdateRequest) -> UserResponse:
|
|
||||||
user = get_current_user()
|
|
||||||
updates = payload.model_dump(exclude_unset=True)
|
|
||||||
for field, value in updates.items():
|
|
||||||
setattr(user, field, value)
|
|
||||||
return UserResponse.model_validate(user)
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/chats/{user_id}", response_model=list[ChatMessageResponse])
|
|
||||||
def get_chat(user_id: int) -> list[ChatMessageResponse]:
|
|
||||||
current_user = get_current_user()
|
|
||||||
if user_id not in store.users:
|
|
||||||
raise HTTPException(status_code=404, detail="User not found.")
|
|
||||||
|
|
||||||
messages = [
|
|
||||||
message
|
|
||||||
for message in store.chat_messages.values()
|
|
||||||
if {message.sender_id, message.receiver_id} == {current_user.id, user_id}
|
|
||||||
]
|
|
||||||
messages.sort(key=lambda item: item.created_at)
|
|
||||||
return [ChatMessageResponse.model_validate(message) for message in messages]
|
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/chats", response_model=ChatMessageResponse, status_code=status.HTTP_201_CREATED)
|
|
||||||
def create_chat_message(payload: ChatMessageCreateRequest) -> ChatMessageResponse:
|
|
||||||
current_user = get_current_user()
|
|
||||||
if payload.receiver_id not in store.users:
|
|
||||||
raise HTTPException(status_code=404, detail="Receiver not found.")
|
|
||||||
|
|
||||||
message = ChatMessage(
|
|
||||||
id=store.next_id("chat_message"),
|
|
||||||
sender_id=current_user.id,
|
|
||||||
receiver_id=payload.receiver_id,
|
|
||||||
content=payload.content,
|
|
||||||
created_at=datetime.utcnow(),
|
|
||||||
)
|
|
||||||
store.chat_messages[message.id] = message
|
|
||||||
return ChatMessageResponse.model_validate(message)
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
from dataclasses import dataclass
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class User:
|
|
||||||
id: int
|
|
||||||
email: str
|
|
||||||
password_hash: str
|
|
||||||
full_name: str
|
|
||||||
username: str
|
|
||||||
bio: str
|
|
||||||
location: str
|
|
||||||
avatar_url: str
|
|
||||||
role: str
|
|
||||||
is_verified: bool
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Prompt:
|
|
||||||
id: int
|
|
||||||
title: str
|
|
||||||
description: str
|
|
||||||
content: str
|
|
||||||
image_url: str
|
|
||||||
category: str
|
|
||||||
price: float
|
|
||||||
creator_id: int
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Rating:
|
|
||||||
id: int
|
|
||||||
prompt_id: int
|
|
||||||
user_id: int
|
|
||||||
score: int
|
|
||||||
comment: str
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Favorite:
|
|
||||||
id: int
|
|
||||||
user_id: int
|
|
||||||
prompt_id: int
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Follow:
|
|
||||||
id: int
|
|
||||||
follower_id: int
|
|
||||||
creator_id: int
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class ChatMessage:
|
|
||||||
id: int
|
|
||||||
sender_id: int
|
|
||||||
receiver_id: int
|
|
||||||
content: str
|
|
||||||
created_at: datetime
|
|
||||||
@ -1,137 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, EmailStr, Field
|
|
||||||
|
|
||||||
|
|
||||||
class UserResponse(BaseModel):
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
|
|
||||||
id: int
|
|
||||||
email: EmailStr
|
|
||||||
full_name: str
|
|
||||||
username: str
|
|
||||||
bio: str
|
|
||||||
location: str
|
|
||||||
avatar_url: str
|
|
||||||
role: str
|
|
||||||
is_verified: bool
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
class RegisterRequest(BaseModel):
|
|
||||||
email: EmailStr
|
|
||||||
password: str = Field(min_length=6)
|
|
||||||
full_name: str = Field(min_length=2, max_length=100)
|
|
||||||
username: str = Field(min_length=3, max_length=50)
|
|
||||||
role: str = Field(default="user")
|
|
||||||
|
|
||||||
|
|
||||||
class LoginRequest(BaseModel):
|
|
||||||
email: EmailStr
|
|
||||||
password: str = Field(min_length=6)
|
|
||||||
|
|
||||||
|
|
||||||
class AuthResponse(BaseModel):
|
|
||||||
message: str
|
|
||||||
user: UserResponse
|
|
||||||
|
|
||||||
|
|
||||||
class ProfileUpdateRequest(BaseModel):
|
|
||||||
full_name: Optional[str] = Field(default=None, min_length=2, max_length=100)
|
|
||||||
bio: Optional[str] = Field(default=None, max_length=500)
|
|
||||||
location: Optional[str] = Field(default=None, max_length=120)
|
|
||||||
avatar_url: Optional[str] = Field(default=None, max_length=255)
|
|
||||||
is_verified: Optional[bool] = None
|
|
||||||
|
|
||||||
|
|
||||||
class PromptCreateRequest(BaseModel):
|
|
||||||
title: str = Field(min_length=3, max_length=120)
|
|
||||||
description: str = Field(min_length=10, max_length=500)
|
|
||||||
content: str = Field(min_length=10)
|
|
||||||
image_url: str = Field(max_length=255)
|
|
||||||
category: str = Field(min_length=2, max_length=50)
|
|
||||||
price: float = Field(ge=0)
|
|
||||||
creator_id: int
|
|
||||||
|
|
||||||
|
|
||||||
class PromptUpdateRequest(BaseModel):
|
|
||||||
title: Optional[str] = Field(default=None, min_length=3, max_length=120)
|
|
||||||
description: Optional[str] = Field(default=None, min_length=10, max_length=500)
|
|
||||||
content: Optional[str] = Field(default=None, min_length=10)
|
|
||||||
image_url: Optional[str] = Field(default=None, max_length=255)
|
|
||||||
category: Optional[str] = Field(default=None, min_length=2, max_length=50)
|
|
||||||
price: Optional[float] = Field(default=None, ge=0)
|
|
||||||
|
|
||||||
|
|
||||||
class PromptResponse(BaseModel):
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
|
|
||||||
id: int
|
|
||||||
title: str
|
|
||||||
description: str
|
|
||||||
content: str
|
|
||||||
image_url: str
|
|
||||||
category: str
|
|
||||||
price: float
|
|
||||||
creator_id: int
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
class CreatorDetailResponse(BaseModel):
|
|
||||||
creator: UserResponse
|
|
||||||
prompts: list[PromptResponse]
|
|
||||||
|
|
||||||
|
|
||||||
class RatingCreateRequest(BaseModel):
|
|
||||||
user_id: int
|
|
||||||
score: int = Field(ge=1, le=5)
|
|
||||||
comment: str = Field(default="", max_length=500)
|
|
||||||
|
|
||||||
|
|
||||||
class RatingResponse(BaseModel):
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
|
|
||||||
id: int
|
|
||||||
prompt_id: int
|
|
||||||
user_id: int
|
|
||||||
score: int
|
|
||||||
comment: str
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
class FavoriteCreateRequest(BaseModel):
|
|
||||||
prompt_id: int
|
|
||||||
|
|
||||||
|
|
||||||
class FavoriteResponse(BaseModel):
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
|
|
||||||
id: int
|
|
||||||
user_id: int
|
|
||||||
prompt_id: int
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
class FollowResponse(BaseModel):
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
|
|
||||||
id: int
|
|
||||||
follower_id: int
|
|
||||||
creator_id: int
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
|
|
||||||
class ChatMessageCreateRequest(BaseModel):
|
|
||||||
receiver_id: int
|
|
||||||
content: str = Field(min_length=1, max_length=2000)
|
|
||||||
|
|
||||||
|
|
||||||
class ChatMessageResponse(BaseModel):
|
|
||||||
model_config = ConfigDict(from_attributes=True)
|
|
||||||
|
|
||||||
id: int
|
|
||||||
sender_id: int
|
|
||||||
receiver_id: int
|
|
||||||
content: str
|
|
||||||
created_at: datetime
|
|
||||||
103
backend/store.py
@ -1,103 +0,0 @@
|
|||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from .models import ChatMessage, Favorite, Follow, Prompt, Rating, User
|
|
||||||
|
|
||||||
|
|
||||||
class InMemoryStore:
|
|
||||||
def __init__(self) -> None:
|
|
||||||
now = datetime.utcnow()
|
|
||||||
|
|
||||||
self.users = {
|
|
||||||
1: User(
|
|
||||||
id=1,
|
|
||||||
email="jane@example.com",
|
|
||||||
password_hash="demo-password",
|
|
||||||
full_name="Jane Doe",
|
|
||||||
username="janedoe",
|
|
||||||
bio="AI creator focused on writing and coding prompts.",
|
|
||||||
location="Zurich",
|
|
||||||
avatar_url="/images/content/creator1.png",
|
|
||||||
role="creator",
|
|
||||||
is_verified=True,
|
|
||||||
created_at=now,
|
|
||||||
),
|
|
||||||
2: User(
|
|
||||||
id=2,
|
|
||||||
email="max@example.com",
|
|
||||||
password_hash="demo-password",
|
|
||||||
full_name="Max Muster",
|
|
||||||
username="maxm",
|
|
||||||
bio="Prompt buyer and tester.",
|
|
||||||
location="Chur",
|
|
||||||
avatar_url="/images/content/creator2.png",
|
|
||||||
role="user",
|
|
||||||
is_verified=False,
|
|
||||||
created_at=now,
|
|
||||||
),
|
|
||||||
}
|
|
||||||
self.prompts = {
|
|
||||||
1: Prompt(
|
|
||||||
id=1,
|
|
||||||
title="Python Code Assistant",
|
|
||||||
description="Efficiently debug and write Python code with AI assistance.",
|
|
||||||
content="You are an expert Python assistant...",
|
|
||||||
image_url="/images/content/prompt2.png",
|
|
||||||
category="Coding",
|
|
||||||
price=19.99,
|
|
||||||
creator_id=1,
|
|
||||||
created_at=now,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
self.ratings = {
|
|
||||||
1: Rating(
|
|
||||||
id=1,
|
|
||||||
prompt_id=1,
|
|
||||||
user_id=2,
|
|
||||||
score=5,
|
|
||||||
comment="Very useful starter prompt.",
|
|
||||||
created_at=now,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
self.favorites = {
|
|
||||||
1: Favorite(
|
|
||||||
id=1,
|
|
||||||
user_id=2,
|
|
||||||
prompt_id=1,
|
|
||||||
created_at=now,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
self.follows = {
|
|
||||||
1: Follow(
|
|
||||||
id=1,
|
|
||||||
follower_id=2,
|
|
||||||
creator_id=1,
|
|
||||||
created_at=now,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
self.chat_messages = {
|
|
||||||
1: ChatMessage(
|
|
||||||
id=1,
|
|
||||||
sender_id=2,
|
|
||||||
receiver_id=1,
|
|
||||||
content="Hi, I have a question about your prompt.",
|
|
||||||
created_at=now,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
self.current_user_id = 2
|
|
||||||
self.next_ids = {
|
|
||||||
"user": 3,
|
|
||||||
"prompt": 2,
|
|
||||||
"rating": 2,
|
|
||||||
"favorite": 2,
|
|
||||||
"follow": 2,
|
|
||||||
"chat_message": 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
def next_id(self, key: str) -> int:
|
|
||||||
value = self.next_ids[key]
|
|
||||||
self.next_ids[key] += 1
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
|
||||||
store = InMemoryStore()
|
|
||||||
19
css/base.css
@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
This file contains global base styles and resets
|
|
||||||
--> ensures consistent spacing, font usage, and box sizing
|
|
||||||
across all elements in the application
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Reset default browser styles */
|
|
||||||
* {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font-family: system-ui, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Global body styling */
|
|
||||||
body {
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
170
css/creators.css
@ -1,170 +0,0 @@
|
|||||||
/* Creators page - Discover creators, filter buttons, creator cards */
|
|
||||||
|
|
||||||
/* Full width layout */
|
|
||||||
.layout > div[style*="flex:1"] {
|
|
||||||
margin: 0 !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.creators-main {
|
|
||||||
background: transparent !important;
|
|
||||||
padding: 20px 32px !important;
|
|
||||||
margin: 0 auto !important;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header */
|
|
||||||
.creators-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
.creators-header h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.creators-header p {
|
|
||||||
color: #64748b;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Filter buttons */
|
|
||||||
.filter-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
|
||||||
.filter-btn {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 20px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #64748b;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 30px;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
.filter-btn.active {
|
|
||||||
background: var(--gradient);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.filter-btn:hover:not(.active) {
|
|
||||||
background: #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Creators grid */
|
|
||||||
.creators-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Creator card */
|
|
||||||
.creator-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 18px;
|
|
||||||
box-shadow: 0 2px 8px rgba(59,130,246,0.06);
|
|
||||||
padding: 20px;
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
|
||||||
}
|
|
||||||
.creator-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 20px rgba(59,130,246,0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.creator-avatar {
|
|
||||||
width: 70px;
|
|
||||||
height: 70px;
|
|
||||||
border-radius: 50%;
|
|
||||||
object-fit: cover;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.creator-info {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
.creator-name {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
.creator-handle {
|
|
||||||
color: #64748b;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.creator-bio {
|
|
||||||
color: #334155;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
.creator-stats {
|
|
||||||
display: flex;
|
|
||||||
gap: 16px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
.creator-stats i {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
.follow-btn {
|
|
||||||
background: var(--gradient);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 6px 16px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
.follow-btn:hover {
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.creators-main {
|
|
||||||
padding: 16px !important;
|
|
||||||
}
|
|
||||||
.filter-buttons {
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.filter-btn {
|
|
||||||
padding: 6px 14px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.creators-main {
|
|
||||||
padding: 12px !important;
|
|
||||||
}
|
|
||||||
.creator-card {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.creator-stats {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
.follow-btn {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
161
css/login.css
@ -1,161 +0,0 @@
|
|||||||
/*
|
|
||||||
File contains the styles for the login page
|
|
||||||
--> defines the layout of the login screen
|
|
||||||
*/
|
|
||||||
|
|
||||||
.login-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex; /* enables flexbox layout for centering */
|
|
||||||
justify-content: center; /* horizontally centers the card */
|
|
||||||
align-items: center; /* vertically centers the card */
|
|
||||||
padding: 24px; /* space inside the page edges */
|
|
||||||
/* Layered background: two soft radial gradients for color accents, then the main background color */
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgba(59, 130, 246, 0.12), transparent 35%),
|
|
||||||
radial-gradient(circle at bottom right, rgba(236, 72, 153, 0.10), transparent 30%),
|
|
||||||
var(--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main login card container */
|
|
||||||
.login-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 430px; /* prevents the card from getting too wide on large screens */
|
|
||||||
background: var(--card); /* uses card color from variables.css */
|
|
||||||
border-radius: 24px; /* rounded corners */
|
|
||||||
box-shadow: var(--shadow); /* soft shadow for card elevation */
|
|
||||||
padding: 40px 32px; /* inner spacing for content */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Logo area above the form */
|
|
||||||
.login-logo-wrapper {
|
|
||||||
display: flex; /* centers logo horizontally */
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden; /* hides any part of the logo that overflows the container */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Full logo styling */
|
|
||||||
.login-logo {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 220px; /* logo never exceeds this width */
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
object-fit: contain; /* keeps logo aspect ratio, prevents stretching */
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-subtitle {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-bottom: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form layout */
|
|
||||||
.login-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column; /* stack form fields vertically */
|
|
||||||
gap: 18px; /* vertical space between form groups */
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column; /* label above input */
|
|
||||||
gap: 8px; /* space between label and input */
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 14px 16px; /* vertical and horizontal padding for input */
|
|
||||||
border: 1px solid #dbe2ea; /* subtle border */
|
|
||||||
border-radius: 14px; /* rounded input corners */
|
|
||||||
background: #ffffff;
|
|
||||||
font-size: 1rem;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Highlight input when focused */
|
|
||||||
.form-group input:focus {
|
|
||||||
outline: none; /* removes default browser outline */
|
|
||||||
border-color: #7c3aed; /* purple border on focus */
|
|
||||||
box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.10); /* soft glow for focus state */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Password field with button inside the same row */
|
|
||||||
.password-wrapper {
|
|
||||||
display: flex; /* input and button in one row */
|
|
||||||
align-items: center; /* vertically center input and button */
|
|
||||||
gap: 10px; /* space between input and show/hide button */
|
|
||||||
}
|
|
||||||
|
|
||||||
.password-wrapper input {
|
|
||||||
flex: 1; /* input takes all available space, button stays compact */
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-password {
|
|
||||||
border: none;
|
|
||||||
background: transparent; /* no background for button */
|
|
||||||
color: #64748b;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer; /* pointer cursor for better UX */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main login action button */
|
|
||||||
.login-button {
|
|
||||||
margin-top: 4px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 14px 18px;
|
|
||||||
background: var(--gradient); /* uses a gradient background from variables */
|
|
||||||
color: white;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.22); /* blue shadow for button depth */
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button:hover {
|
|
||||||
opacity: 0.95; /* slight fade on hover for feedback */
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-text {
|
|
||||||
margin-top: 24px;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-muted);
|
|
||||||
/* used for the 'Don't have an account?' and link below the form */
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-text a {
|
|
||||||
color: #2563eb;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 600;
|
|
||||||
/* blue link for sign up/login */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Smaller spacing and sizing for narrow screens */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
/* Responsive adjustments for small screens (mobile) */
|
|
||||||
.login-card {
|
|
||||||
padding: 28px 20px; /* less padding on mobile */
|
|
||||||
border-radius: 20px; /* slightly less rounded */
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
|
||||||
font-size: 1.7rem; /* smaller title on mobile */
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-logo {
|
|
||||||
max-width: 170px; /* smaller logo on mobile */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,189 +0,0 @@
|
|||||||
/* Marketplace Page - Prompt cards, filter buttons, full width layout */
|
|
||||||
|
|
||||||
/* Full width layout */
|
|
||||||
.layout > div[style*="flex:1"] {
|
|
||||||
margin: 0 !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.marketplace-main {
|
|
||||||
background: transparent !important;
|
|
||||||
padding: 20px 32px !important;
|
|
||||||
margin: 0 auto !important;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header centering */
|
|
||||||
.marketplace-header {
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 24px;
|
|
||||||
}
|
|
||||||
.marketplace-header h1 {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
.marketplace-header p {
|
|
||||||
color: #64748b;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Filter buttons - centered */
|
|
||||||
.filter-buttons {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 12px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
border-bottom: 1px solid #e5e7eb;
|
|
||||||
padding-bottom: 16px;
|
|
||||||
}
|
|
||||||
.filter-btn {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
padding: 8px 20px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #64748b;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: 30px;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
.filter-btn.active {
|
|
||||||
background: var(--gradient);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.filter-btn:hover:not(.active) {
|
|
||||||
background: #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prompts grid */
|
|
||||||
.prompts-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
||||||
gap: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prompt card */
|
|
||||||
.prompt-card {
|
|
||||||
background: #fff;
|
|
||||||
border-radius: 18px;
|
|
||||||
box-shadow: 0 2px 8px rgba(59,130,246,0.06);
|
|
||||||
overflow: hidden;
|
|
||||||
transition: transform 0.2s, box-shadow 0.2s;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.prompt-card:hover {
|
|
||||||
transform: translateY(-2px);
|
|
||||||
box-shadow: 0 8px 20px rgba(59,130,246,0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-img {
|
|
||||||
width: 100%;
|
|
||||||
height: 160px;
|
|
||||||
object-fit: cover;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-info {
|
|
||||||
padding: 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-title {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-author {
|
|
||||||
color: #64748b;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-description {
|
|
||||||
color: #334155;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
margin: 0;
|
|
||||||
display: -webkit-box;
|
|
||||||
-webkit-line-clamp: 2;
|
|
||||||
-webkit-box-orient: vertical;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-rating {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: #f59e0b;
|
|
||||||
}
|
|
||||||
.prompt-rating span:first-child i {
|
|
||||||
color: #f59e0b;
|
|
||||||
}
|
|
||||||
.prompt-rating span:last-child {
|
|
||||||
color: #64748b;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-price {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #3b82f6;
|
|
||||||
margin: 8px 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.prompt-actions {
|
|
||||||
display: flex;
|
|
||||||
gap: 12px;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
.buy-btn, .details-btn {
|
|
||||||
flex: 1;
|
|
||||||
border: none;
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
}
|
|
||||||
.buy-btn {
|
|
||||||
background: var(--gradient);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
.details-btn {
|
|
||||||
background: #f1f5f9;
|
|
||||||
color: #334155;
|
|
||||||
}
|
|
||||||
.buy-btn:hover, .details-btn:hover {
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.marketplace-main {
|
|
||||||
padding: 16px !important;
|
|
||||||
}
|
|
||||||
.filter-buttons {
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
.filter-btn {
|
|
||||||
padding: 6px 14px;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.marketplace-main {
|
|
||||||
padding: 12px !important;
|
|
||||||
}
|
|
||||||
.prompt-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,94 +0,0 @@
|
|||||||
/* Profile Page - Full width layout, darker share button, responsive grid */
|
|
||||||
|
|
||||||
/* Force main content container to full width, remove centering and max-width */
|
|
||||||
.layout > div[style*="flex:1"] {
|
|
||||||
margin: 0 !important;
|
|
||||||
max-width: 100% !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Inner spacing for the profile card */
|
|
||||||
.profile-main {
|
|
||||||
background: transparent !important;
|
|
||||||
border-radius: 0 !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
padding: 20px 32px !important;
|
|
||||||
margin: 0 auto !important;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1600px; /* Limits content on very large screens, but still wide */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make prompts grid use more columns on large screens */
|
|
||||||
.profile-main section:last-child {
|
|
||||||
display: grid !important;
|
|
||||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)) !important;
|
|
||||||
gap: 24px !important;
|
|
||||||
width: 100% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Share button: darker background and text */
|
|
||||||
.profile-header button:last-child {
|
|
||||||
background: #cbd5e1 !important; /* darker gray */
|
|
||||||
color: #1e293b !important;
|
|
||||||
box-shadow: none !important;
|
|
||||||
border: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Buttons keep rounded corners */
|
|
||||||
.login-button {
|
|
||||||
border-radius: 14px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prompt cards: rounded corners */
|
|
||||||
.profile-main section > div {
|
|
||||||
border-radius: 18px !important;
|
|
||||||
box-shadow: 0 2px 8px rgba(59,130,246,0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Prompt images: rounded corners */
|
|
||||||
.profile-main section img {
|
|
||||||
border-radius: 12px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Avatar remains round */
|
|
||||||
.profile-avatar {
|
|
||||||
border-radius: 50% !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* All outer containers stay square */
|
|
||||||
.layout,
|
|
||||||
.profile-main,
|
|
||||||
.profile-header,
|
|
||||||
.profile-tabs,
|
|
||||||
nav {
|
|
||||||
border-radius: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive: tablets */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.profile-main {
|
|
||||||
padding: 16px !important;
|
|
||||||
}
|
|
||||||
.profile-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start !important;
|
|
||||||
}
|
|
||||||
.profile-header > div:last-child {
|
|
||||||
flex-direction: row;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive: mobile */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.profile-main {
|
|
||||||
padding: 12px !important;
|
|
||||||
}
|
|
||||||
.profile-header > div:last-child {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
.profile-main section:last-child {
|
|
||||||
grid-template-columns: 1fr !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
169
css/sidebar.css
@ -1,169 +0,0 @@
|
|||||||
/*
|
|
||||||
Sidebar styles for OnlyPrompt
|
|
||||||
- modern soft card look
|
|
||||||
- responsive: full sidebar on desktop, icon-only on smaller screens
|
|
||||||
*/
|
|
||||||
|
|
||||||
.sidebar-shell {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: #ffffff;
|
|
||||||
border-right: 1px solid #eef2f7;
|
|
||||||
padding: 24px 16px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Logo */
|
|
||||||
.sidebar-logo {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
min-height: 72px;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
padding: 6px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logo-full {
|
|
||||||
max-width: 170px;
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logo-icon {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Navigation */
|
|
||||||
.sidebar {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar ul {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar li {
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar a {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 14px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #475569;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 12px 16px;
|
|
||||||
transition: background 0.2s ease, color 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar a:hover {
|
|
||||||
background: #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar i {
|
|
||||||
font-size: 1.3rem;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-blue {
|
|
||||||
color: #3b82f6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-purple {
|
|
||||||
color: #8b5cf6 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-pink {
|
|
||||||
color: #ec4899 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Active item */
|
|
||||||
.sidebar a.active {
|
|
||||||
background: #eef2ff;
|
|
||||||
color: #2563eb;
|
|
||||||
border-left: 3px solid #3b82f6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Bottom logout area */
|
|
||||||
.sidebar-bottom {
|
|
||||||
margin-top: 16px;
|
|
||||||
padding-top: 16px;
|
|
||||||
border-top: 1px solid #eef2f7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logout {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 12px 16px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: #64748b;
|
|
||||||
font-weight: 600;
|
|
||||||
transition: background 0.2s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logout:hover {
|
|
||||||
background: #f1f5f9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-left {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logout i {
|
|
||||||
font-size: 1.2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logout-arrow {
|
|
||||||
color: #cbd5e1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive: icon-only sidebar */
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.sidebar-shell {
|
|
||||||
padding-left: 12px;
|
|
||||||
padding-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logo-full {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-logo-icon {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar .nav-text,
|
|
||||||
.sidebar-logout .nav-text,
|
|
||||||
.logout-arrow {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar a,
|
|
||||||
.sidebar-logout {
|
|
||||||
justify-content: center;
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar a.active {
|
|
||||||
border-left: none;
|
|
||||||
border-right: 3px solid #3b82f6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 700px) {
|
|
||||||
.sidebar a,
|
|
||||||
.sidebar-logout {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
169
css/signup.css
@ -1,169 +0,0 @@
|
|||||||
/*
|
|
||||||
File contains the styles for the signup page
|
|
||||||
--> defines the layout of the signup screen
|
|
||||||
*/
|
|
||||||
|
|
||||||
.login-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
padding: 24px;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at top left, rgba(59, 130, 246, 0.12), transparent 35%),
|
|
||||||
radial-gradient(circle at bottom right, rgba(236, 72, 153, 0.10), transparent 30%),
|
|
||||||
var(--bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main signup card container */
|
|
||||||
.login-card {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 430px;
|
|
||||||
background: var(--card); /*variable.css*/
|
|
||||||
border-radius: 24px;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
padding: 40px 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Logo area above the form */
|
|
||||||
.login-logo-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Full logo styling */
|
|
||||||
.login-logo {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 220px;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
object-fit: contain;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-subtitle {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-muted);
|
|
||||||
margin-bottom: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Form layout */
|
|
||||||
.login-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 14px 16px;
|
|
||||||
border: 1px solid #dbe2ea;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: #ffffff;
|
|
||||||
font-size: 1rem;
|
|
||||||
color: var(--text);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Highlight input when focused */
|
|
||||||
.form-group input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: #7c3aed;
|
|
||||||
box-shadow: 0 0 0 4px rgba(124, 58, 237, 0.10);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Password field with button inside the same row */
|
|
||||||
.password-wrapper {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.password-wrapper input {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-password {
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
color: #64748b;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-terms {
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
margin: 18px 0 0 0;
|
|
||||||
}
|
|
||||||
.signup-terms a {
|
|
||||||
color: #2563eb;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Main login action button */
|
|
||||||
.login-button {
|
|
||||||
margin-top: 4px;
|
|
||||||
border: none;
|
|
||||||
border-radius: 14px;
|
|
||||||
padding: 14px 18px;
|
|
||||||
background: var(--gradient);
|
|
||||||
color: white;
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 700;
|
|
||||||
cursor: pointer;
|
|
||||||
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.22);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-button:hover {
|
|
||||||
opacity: 0.95;
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-text {
|
|
||||||
margin-top: 24px;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--text-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup-text a {
|
|
||||||
color: #2563eb;
|
|
||||||
text-decoration: none;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Smaller spacing and sizing for narrow screens */
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.login-card {
|
|
||||||
padding: 28px 20px;
|
|
||||||
border-radius: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
|
||||||
font-size: 1.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-logo {
|
|
||||||
max-width: 170px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,92 +0,0 @@
|
|||||||
/*
|
|
||||||
Topbar styles for OnlyPrompt
|
|
||||||
- clean, modern, full-width
|
|
||||||
- search bar centered (expands on full screen), profile avatar always on the right
|
|
||||||
- ONLY search bar and avatar have rounded corners
|
|
||||||
*/
|
|
||||||
|
|
||||||
.topbar-shell {
|
|
||||||
width: 100%;
|
|
||||||
background: #ffffff;
|
|
||||||
border-bottom: 1px solid #eef2f7;
|
|
||||||
padding: 16px 32px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-search {
|
|
||||||
flex: 1; /* Takes all available space */
|
|
||||||
max-width: none; /* No upper limit, expands freely */
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
background: #f8fafc;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
padding: 10px 20px;
|
|
||||||
border-radius: 14px; /* Rounded like login inputs */
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-search i {
|
|
||||||
color: #94a3b8;
|
|
||||||
font-size: 1.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-search input {
|
|
||||||
width: 100%;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
background: transparent;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
color: #334155;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-search input::placeholder {
|
|
||||||
color: #94a3b8;
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-avatar-btn {
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.topbar-avatar {
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
object-fit: cover;
|
|
||||||
border: 1px solid #e2e8f0;
|
|
||||||
border-radius: 50%; /* Avatar round */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Responsive adjustments */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.topbar-shell {
|
|
||||||
padding: 12px 20px;
|
|
||||||
}
|
|
||||||
.topbar-search {
|
|
||||||
padding: 8px 16px;
|
|
||||||
}
|
|
||||||
.topbar-search i {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
.topbar-avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.topbar-shell {
|
|
||||||
padding: 10px 16px;
|
|
||||||
}
|
|
||||||
.topbar-search {
|
|
||||||
padding: 6px 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
This file contains global design variables such as colors,
|
|
||||||
gradients, spacing, and shadows
|
|
||||||
--> these variables ensure a consistent design accross the whole application
|
|
||||||
*/
|
|
||||||
|
|
||||||
:root {
|
|
||||||
/* Main gradient used for buttons and highlights */
|
|
||||||
--gradient: linear-gradient(90deg, #7c3aed, #3b82f6, #ec4899);
|
|
||||||
|
|
||||||
/* Background color of the application */
|
|
||||||
--bg: #f8fafc;
|
|
||||||
|
|
||||||
/* Default text color */
|
|
||||||
--text: #0f172a;
|
|
||||||
|
|
||||||
/* Container background */
|
|
||||||
--card: #ffffff;
|
|
||||||
|
|
||||||
/* Border radius for rounded elements */
|
|
||||||
--radius: 16px;
|
|
||||||
|
|
||||||
/* Standard shadow for cards and components */
|
|
||||||
--shadow: 0 10px 30px rgba(0,0,0,0.08);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,158 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>OnlyPrompt - Discover Creators</title>
|
|
||||||
<link rel="stylesheet" href="../css/variables.css">
|
|
||||||
<link rel="stylesheet" href="../css/base.css">
|
|
||||||
<link rel="stylesheet" href="../css/sidebar.css">
|
|
||||||
<link rel="stylesheet" href="../css/login.css">
|
|
||||||
<link rel="stylesheet" href="../css/topbar.css">
|
|
||||||
<link rel="stylesheet" href="../css/creators.css">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="layout" style="display: flex; min-height: 100vh; background: var(--bg);">
|
|
||||||
|
|
||||||
<div id="sidebar-container"></div>
|
|
||||||
|
|
||||||
<div style="flex:1; margin:40px auto; max-width:950px;">
|
|
||||||
|
|
||||||
<div id="topbar-container"></div>
|
|
||||||
|
|
||||||
<main class="creators-main">
|
|
||||||
|
|
||||||
<!-- Header / Titel -->
|
|
||||||
<div class="creators-header">
|
|
||||||
<h1>Discover Creators</h1>
|
|
||||||
<p>Follow your favorite prompt artists and get inspired.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Filter Buttons -->
|
|
||||||
<div class="filter-buttons">
|
|
||||||
<button class="filter-btn active">Popular</button>
|
|
||||||
<button class="filter-btn">Rising</button>
|
|
||||||
<button class="filter-btn">New</button>
|
|
||||||
<button class="filter-btn">Top Rated</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Creators Grid -->
|
|
||||||
<div class="creators-grid">
|
|
||||||
|
|
||||||
<!-- Creator Card 1 -->
|
|
||||||
<div class="creator-card">
|
|
||||||
<img src="../images/content/creator1.png" alt="Sarah Jenkins" class="creator-avatar">
|
|
||||||
<div class="creator-info">
|
|
||||||
<h3 class="creator-name">Sarah Jenkins</h3>
|
|
||||||
<div class="creator-handle">@sarahj</div>
|
|
||||||
<p class="creator-bio">AI Explorer | Prompt Curator | Exploring creativity through generative AI.</p>
|
|
||||||
<div class="creator-stats">
|
|
||||||
<span><i class="bi bi-puzzle"></i> 42 prompts</span>
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.9</span>
|
|
||||||
</div>
|
|
||||||
<button class="follow-btn">Follow</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Creator Card 2 -->
|
|
||||||
<div class="creator-card">
|
|
||||||
<img src="../images/content/creator2.png" alt="Alex Chen" class="creator-avatar">
|
|
||||||
<div class="creator-info">
|
|
||||||
<h3 class="creator-name">Alex Chen</h3>
|
|
||||||
<div class="creator-handle">@alexchen</div>
|
|
||||||
<p class="creator-bio">Digital artist & prompt engineer. Creating surreal landscapes.</p>
|
|
||||||
<div class="creator-stats">
|
|
||||||
<span><i class="bi bi-puzzle"></i> 87 prompts</span>
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.8</span>
|
|
||||||
</div>
|
|
||||||
<button class="follow-btn">Follow</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Creator Card 3 -->
|
|
||||||
<div class="creator-card">
|
|
||||||
<img src="../images/content/creator3.png" alt="Mia Wong" class="creator-avatar">
|
|
||||||
<div class="creator-info">
|
|
||||||
<h3 class="creator-name">Mia Wong</h3>
|
|
||||||
<div class="creator-handle">@miawong</div>
|
|
||||||
<p class="creator-bio">Midjourney master | UI/UX prompts | Design systems.</p>
|
|
||||||
<div class="creator-stats">
|
|
||||||
<span><i class="bi bi-puzzle"></i> 124 prompts</span>
|
|
||||||
<span><i class="bi bi-star-fill"></i> 5.0</span>
|
|
||||||
</div>
|
|
||||||
<button class="follow-btn">Follow</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Creator Card 4 -->
|
|
||||||
<div class="creator-card">
|
|
||||||
<img src="../images/content/creator4.png" alt="Tom Rivera" class="creator-avatar">
|
|
||||||
<div class="creator-info">
|
|
||||||
<h3 class="creator-name">Tom Rivera</h3>
|
|
||||||
<div class="creator-handle">@tomrivera</div>
|
|
||||||
<p class="creator-bio">3D artist | Character design | Sci-fi & fantasy prompts.</p>
|
|
||||||
<div class="creator-stats">
|
|
||||||
<span><i class="bi bi-puzzle"></i> 33 prompts</span>
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.7</span>
|
|
||||||
</div>
|
|
||||||
<button class="follow-btn">Follow</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Creator Card 5 -->
|
|
||||||
<div class="creator-card">
|
|
||||||
<img src="../images/content/creator5.png" alt="Emma Watson" class="creator-avatar">
|
|
||||||
<div class="creator-info">
|
|
||||||
<h3 class="creator-name">Emma Watson</h3>
|
|
||||||
<div class="creator-handle">@emmawatson</div>
|
|
||||||
<p class="creator-bio">Watercolor & pet portraits | Whimsical art prompts.</p>
|
|
||||||
<div class="creator-stats">
|
|
||||||
<span><i class="bi bi-puzzle"></i> 56 prompts</span>
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.9</span>
|
|
||||||
</div>
|
|
||||||
<button class="follow-btn">Follow</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Creator Card 6 -->
|
|
||||||
<div class="creator-card">
|
|
||||||
<img src="../images/content/creator6.png" alt="Liam O'Brien" class="creator-avatar">
|
|
||||||
<div class="creator-info">
|
|
||||||
<h3 class="creator-name">Liam O'Brien</h3>
|
|
||||||
<div class="creator-handle">@liamob</div>
|
|
||||||
<p class="creator-bio">Minimalist logo designer | Brand identity prompts.</p>
|
|
||||||
<div class="creator-stats">
|
|
||||||
<span><i class="bi bi-puzzle"></i> 28 prompts</span>
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.6</span>
|
|
||||||
</div>
|
|
||||||
<button class="follow-btn">Follow</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
fetch('../html/sidebar.html')
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(data => {
|
|
||||||
document.getElementById('sidebar-container').innerHTML = data;
|
|
||||||
// Remove 'active' from all sidebar links
|
|
||||||
document.querySelectorAll('#sidebar-container .sidebar a').forEach(link => {
|
|
||||||
link.classList.remove('active');
|
|
||||||
});
|
|
||||||
// Set 'active' on the third link (Community)
|
|
||||||
const thirdLink = document.querySelectorAll('#sidebar-container .sidebar li a')[2];
|
|
||||||
if (thirdLink) thirdLink.classList.add('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
fetch('../html/topbar.html')
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(data => document.getElementById('topbar-container').innerHTML = data);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,75 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<!--Info about page but not visible-->
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<!-- For responsive design: adapts width for different devices -->
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<!-- Title shown in browser tab -->
|
|
||||||
<title>OnlyPrompt - Login</title>
|
|
||||||
|
|
||||||
<!-- CSS files for variables, base styles, and login page -->
|
|
||||||
<link rel="stylesheet" href="../css/variables.css">
|
|
||||||
<link rel="stylesheet" href="../css/base.css">
|
|
||||||
<link rel="stylesheet" href="../css/login.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<!-- Main container for the login page (CSS layout) -->
|
|
||||||
<main class="login-page">
|
|
||||||
<!-- White login card -->
|
|
||||||
<section class="login-card">
|
|
||||||
<!-- Logo container -->
|
|
||||||
<div class="login-logo-wrapper">
|
|
||||||
<img src="../images/logo_full.png" alt="OnlyPrompt Logo" class="login-logo">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1 class="login-title">Sign In</h1>
|
|
||||||
<p class="login-subtitle">Welcome back! Enter your details below.</p>
|
|
||||||
|
|
||||||
<!-- Login form, id is used for JavaScript validation -->
|
|
||||||
<form id="loginForm" class="login-form">
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">Email Address</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
placeholder="yourname@email.com"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password">Password</label>
|
|
||||||
<!-- Password field with button to show/hide password -->
|
|
||||||
<div class="password-wrapper">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
placeholder="Enter your password"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<button type="button" id="togglePassword" class="toggle-password">
|
|
||||||
Show <!-- Click to show/hide password -->
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="login-button">Log In</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p class="signup-text">
|
|
||||||
Don't have an account?
|
|
||||||
<a href="#">Sign Up</a>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Script for login logic and form validation -->
|
|
||||||
<script src="../js/login.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,185 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>OnlyPrompt - Marketplace</title>
|
|
||||||
<link rel="stylesheet" href="../css/variables.css">
|
|
||||||
<link rel="stylesheet" href="../css/base.css">
|
|
||||||
<link rel="stylesheet" href="../css/sidebar.css">
|
|
||||||
<link rel="stylesheet" href="../css/login.css">
|
|
||||||
<link rel="stylesheet" href="../css/topbar.css">
|
|
||||||
<link rel="stylesheet" href="../css/marketplace.css">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="layout" style="display: flex; min-height: 100vh; background: var(--bg);">
|
|
||||||
|
|
||||||
<div id="sidebar-container"></div>
|
|
||||||
|
|
||||||
<div style="flex:1; margin:40px auto; max-width:950px;">
|
|
||||||
|
|
||||||
<div id="topbar-container"></div>
|
|
||||||
|
|
||||||
<main class="marketplace-main">
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<div class="marketplace-header">
|
|
||||||
<h1>Marketplace</h1>
|
|
||||||
<p>Browse and discover high-quality AI prompts</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Filter Buttons -->
|
|
||||||
<div class="filter-buttons">
|
|
||||||
<button class="filter-btn active">All</button>
|
|
||||||
<button class="filter-btn">Writing</button>
|
|
||||||
<button class="filter-btn">Coding</button>
|
|
||||||
<button class="filter-btn">Art</button>
|
|
||||||
<button class="filter-btn">Marketing</button>
|
|
||||||
<button class="filter-btn">Video</button>
|
|
||||||
<button class="filter-btn">Data</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Prompts Grid -->
|
|
||||||
<div class="prompts-grid">
|
|
||||||
|
|
||||||
<!-- Prompt Card 1 -->
|
|
||||||
<div class="prompt-card">
|
|
||||||
<img src="../images/content/prompt1.jpg" alt="Creative Blog Post Generator" class="prompt-img">
|
|
||||||
<div class="prompt-info">
|
|
||||||
<h3 class="prompt-title">Creative Blog Post Generator</h3>
|
|
||||||
<div class="prompt-author">@JaneDoe</div>
|
|
||||||
<p class="prompt-description">Generate engaging blog posts in minutes to captivate your audience.</p>
|
|
||||||
<div class="prompt-rating">
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.8</span>
|
|
||||||
<span>(124 reviews)</span>
|
|
||||||
</div>
|
|
||||||
<div class="prompt-price">$19.99</div>
|
|
||||||
<div class="prompt-actions">
|
|
||||||
<button class="buy-btn">Buy Now</button>
|
|
||||||
<button class="details-btn">View Details</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Prompt Card 2 -->
|
|
||||||
<div class="prompt-card">
|
|
||||||
<img src="../images/content/prompt2.png" alt="Python Code Assistant" class="prompt-img">
|
|
||||||
<div class="prompt-info">
|
|
||||||
<h3 class="prompt-title">Python Code Assistant</h3>
|
|
||||||
<div class="prompt-author">@JaneDoe</div>
|
|
||||||
<p class="prompt-description">Efficiently debug and write Python code with AI assistance.</p>
|
|
||||||
<div class="prompt-rating">
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.7</span>
|
|
||||||
<span>(98 reviews)</span>
|
|
||||||
</div>
|
|
||||||
<div class="prompt-price">$19.99</div>
|
|
||||||
<div class="prompt-actions">
|
|
||||||
<button class="buy-btn">Buy Now</button>
|
|
||||||
<button class="details-btn">View Details</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Prompt Card 3 -->
|
|
||||||
<div class="prompt-card">
|
|
||||||
<img src="../images/content/prompt3.jpg" alt="Digital Art Style Guide" class="prompt-img">
|
|
||||||
<div class="prompt-info">
|
|
||||||
<h3 class="prompt-title">Digital Art Style Guide</h3>
|
|
||||||
<div class="prompt-author">@JaneDoe</div>
|
|
||||||
<p class="prompt-description">Create stunning digital art styles with this comprehensive guide.</p>
|
|
||||||
<div class="prompt-rating">
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.8</span>
|
|
||||||
<span>(156 reviews)</span>
|
|
||||||
</div>
|
|
||||||
<div class="prompt-price">$19.99</div>
|
|
||||||
<div class="prompt-actions">
|
|
||||||
<button class="buy-btn">Buy Now</button>
|
|
||||||
<button class="details-btn">View Details</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Prompt Card 4 -->
|
|
||||||
<div class="prompt-card">
|
|
||||||
<img src="../images/content/prompt4.jpg" alt="Marketing Copywriter" class="prompt-img">
|
|
||||||
<div class="prompt-info">
|
|
||||||
<h3 class="prompt-title">Marketing Copywriter</h3>
|
|
||||||
<div class="prompt-author">@JaneDoe</div>
|
|
||||||
<p class="prompt-description">Generate engaging marketing copy in minutes.</p>
|
|
||||||
<div class="prompt-rating">
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.8</span>
|
|
||||||
<span>(112 reviews)</span>
|
|
||||||
</div>
|
|
||||||
<div class="prompt-price">$19.99</div>
|
|
||||||
<div class="prompt-actions">
|
|
||||||
<button class="buy-btn">Buy Now</button>
|
|
||||||
<button class="details-btn">View Details</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Prompt Card 5 -->
|
|
||||||
<div class="prompt-card">
|
|
||||||
<img src="../images/content/prompt5.jpg" alt="Midjourney Image Prompts" class="prompt-img">
|
|
||||||
<div class="prompt-info">
|
|
||||||
<h3 class="prompt-title">Midjourney Image Prompts</h3>
|
|
||||||
<div class="prompt-author">@JaneDoe</div>
|
|
||||||
<p class="prompt-description">Curated prompts for stunning Midjourney images.</p>
|
|
||||||
<div class="prompt-rating">
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.7</span>
|
|
||||||
<span>(203 reviews)</span>
|
|
||||||
</div>
|
|
||||||
<div class="prompt-price">$19.99</div>
|
|
||||||
<div class="prompt-actions">
|
|
||||||
<button class="buy-btn">Buy Now</button>
|
|
||||||
<button class="details-btn">View Details</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Prompt Card 6 -->
|
|
||||||
<div class="prompt-card">
|
|
||||||
<img src="../images/content/prompt6.jpg" alt="UI Design Assistant" class="prompt-img">
|
|
||||||
<div class="prompt-info">
|
|
||||||
<h3 class="prompt-title">UI Design Assistant</h3>
|
|
||||||
<div class="prompt-author">@JaneDoe</div>
|
|
||||||
<p class="prompt-description">Generate UI components and design systems with AI.</p>
|
|
||||||
<div class="prompt-rating">
|
|
||||||
<span><i class="bi bi-star-fill"></i> 4.9</span>
|
|
||||||
<span>(87 reviews)</span>
|
|
||||||
</div>
|
|
||||||
<div class="prompt-price">$19.99</div>
|
|
||||||
<div class="prompt-actions">
|
|
||||||
<button class="buy-btn">Buy Now</button>
|
|
||||||
<button class="details-btn">View Details</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
fetch('../html/sidebar.html')
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(data => {
|
|
||||||
document.getElementById('sidebar-container').innerHTML = data;
|
|
||||||
// Remove 'active' from all sidebar links
|
|
||||||
document.querySelectorAll('#sidebar-container .sidebar a').forEach(link => {
|
|
||||||
link.classList.remove('active');
|
|
||||||
});
|
|
||||||
// Set 'active' on the second link (Marketplace) - index 1
|
|
||||||
const secondLink = document.querySelectorAll('#sidebar-container .sidebar li a')[1];
|
|
||||||
if (secondLink) secondLink.classList.add('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
fetch('../html/topbar.html')
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(data => document.getElementById('topbar-container').innerHTML = data);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,108 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>OnlyPrompt - Profile</title>
|
|
||||||
<link rel="stylesheet" href="../css/variables.css">
|
|
||||||
<link rel="stylesheet" href="../css/base.css">
|
|
||||||
<link rel="stylesheet" href="../css/sidebar.css">
|
|
||||||
<link rel="stylesheet" href="../css/login.css">
|
|
||||||
<link rel="stylesheet" href="../css/topbar.css">
|
|
||||||
<link rel="stylesheet" href="../css/profile.css">
|
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="layout" style="display: flex; min-height: 100vh; background: var(--bg);">
|
|
||||||
|
|
||||||
<div id="sidebar-container"></div>
|
|
||||||
|
|
||||||
<div style="flex:1; margin:40px auto; max-width:950px;">
|
|
||||||
|
|
||||||
<div id="topbar-container"></div>
|
|
||||||
|
|
||||||
<main class="login-card profile-main" style="background:#fff;border-radius:18px;box-shadow:0 2px 8px rgba(59,130,246,0.06);padding:24px;">
|
|
||||||
|
|
||||||
<section class="profile-header" style="display:flex;align-items:center;gap:32px;border-bottom:1px solid #e5e7eb;padding-bottom:24px;">
|
|
||||||
|
|
||||||
<img src="../images/content/cat.png" class="profile-avatar" style="width:110px;height:110px;border-radius:50%;object-fit:cover;">
|
|
||||||
|
|
||||||
<div class="profile-info" style="flex:1;">
|
|
||||||
<h1 style="font-size:2rem;font-weight:700;margin-bottom:4px;">Sunny the Cat</h1>
|
|
||||||
<div style="color:#64748b;margin-bottom:8px;">
|
|
||||||
@sunny_the_cat <i class="bi bi-patch-check-fill" style="color:#3b82f6;"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-bottom:8px;">
|
|
||||||
🐾 Cat prompt creator | Nap enthusiast | AI Cat Content Curator<br>
|
|
||||||
Chasing lasers and building purrfect prompts.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="color:#64748b;">Cat City, Dreamland 🐈</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="display:flex;flex-direction:column;gap:10px;">
|
|
||||||
<button class="login-button">Edit Profile</button>
|
|
||||||
<button class="login-button" style="background:#f3f4f6;color:#111;box-shadow:none;">Share Profile</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<nav style="display:flex;gap:24px;border-bottom:2px solid #e5e7eb;margin:32px 0 18px 0;">
|
|
||||||
<a href="#" style="padding:10px 0;color:#3b82f6;font-weight:600;border-bottom:2px solid #3b82f6;">My Prompts (2)</a>
|
|
||||||
<a href="#" style="padding:10px 0;color:#64748b;">Favorites (15)</a>
|
|
||||||
<a href="#" style="padding:10px 0;color:#64748b;">Saved (7)</a>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<section style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:24px;">
|
|
||||||
|
|
||||||
<div style="background:#fff;border-radius:18px;box-shadow:0 2px 8px rgba(59,130,246,0.06);padding:18px;display:flex;gap:16px;">
|
|
||||||
<img src="../images/content/post1.png" style="width:60px;height:60px;border-radius:12px;">
|
|
||||||
<div>
|
|
||||||
<div style="font-weight:700;">Galactic Catventure</div>
|
|
||||||
<div style="color:#64748b;margin-bottom:6px;">A cosmic journey of a curious cat exploring the stars.</div>
|
|
||||||
<div style="display:flex;gap:16px;color:#64748b;">
|
|
||||||
<span><i class="bi bi-heart"></i> 128</span>
|
|
||||||
<span><i class="bi bi-chat"></i> 15</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="background:#fff;border-radius:18px;box-shadow:0 2px 8px rgba(59,130,246,0.06);padding:18px;display:flex;gap:16px;">
|
|
||||||
<img src="../images/content/post2.png" style="width:60px;height:60px;border-radius:12px;">
|
|
||||||
<div>
|
|
||||||
<div style="font-weight:700;">Minimalist Cat Logo</div>
|
|
||||||
<div style="color:#64748b;margin-bottom:6px;">Sleek logo design for feline fans.</div>
|
|
||||||
<div style="display:flex;gap:16px;color:#64748b;">
|
|
||||||
<span><i class="bi bi-heart"></i> 54</span>
|
|
||||||
<span><i class="bi bi-chat"></i> 6</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</section>
|
|
||||||
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
fetch('../html/sidebar.html')
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(data => {
|
|
||||||
document.getElementById('sidebar-container').innerHTML = data;
|
|
||||||
// Remove 'active' from all sidebar links
|
|
||||||
document.querySelectorAll('#sidebar-container .sidebar a').forEach(link => {
|
|
||||||
link.classList.remove('active');
|
|
||||||
});
|
|
||||||
// Then set 'active' only on the My Profile link
|
|
||||||
const profileLink = document.querySelector('#sidebar-container a[href="profile.html"]');
|
|
||||||
if (profileLink) profileLink.classList.add('active');
|
|
||||||
});
|
|
||||||
|
|
||||||
fetch('../html/topbar.html')
|
|
||||||
.then(r => r.text())
|
|
||||||
.then(data => document.getElementById('topbar-container').innerHTML = data);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,71 +0,0 @@
|
|||||||
<!--
|
|
||||||
Reusable sidebar for OnlyPrompt
|
|
||||||
- Load this file dynamically into pages
|
|
||||||
- Add class="active" to the current page link
|
|
||||||
-->
|
|
||||||
|
|
||||||
<div class="sidebar-shell">
|
|
||||||
<!-- Logo -->
|
|
||||||
<div class="sidebar-logo">
|
|
||||||
<img src="../images/logo_full.png" alt="OnlyPrompt Logo" class="sidebar-logo-full">
|
|
||||||
<img src="../images/logo_icon.png" alt="OnlyPrompt Icon" class="sidebar-logo-icon">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Navigation -->
|
|
||||||
<nav class="sidebar">
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<a href="dashboard.html" class="active">
|
|
||||||
<i class="bi bi-house icon-blue"></i>
|
|
||||||
<span class="nav-text">Dashboard</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a href="marketplace.html">
|
|
||||||
<i class="bi bi-shop icon-purple"></i>
|
|
||||||
<span class="nav-text">Marketplace</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a href="community.html">
|
|
||||||
<i class="bi bi-people icon-pink"></i>
|
|
||||||
<span class="nav-text">Community</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a href="chats.html">
|
|
||||||
<i class="bi bi-chat-dots icon-blue"></i>
|
|
||||||
<span class="nav-text">Chats</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a href="settings.html">
|
|
||||||
<i class="bi bi-gear icon-purple"></i>
|
|
||||||
<span class="nav-text">Settings</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li>
|
|
||||||
<a href="profile.html">
|
|
||||||
<i class="bi bi-person icon-pink"></i>
|
|
||||||
<span class="nav-text">My Profile</span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
<!-- Logout bottom -->
|
|
||||||
<div class="sidebar-bottom">
|
|
||||||
<a href="login.html" class="sidebar-logout">
|
|
||||||
<div class="logout-left">
|
|
||||||
<i class="bi bi-box-arrow-right"></i>
|
|
||||||
<span class="nav-text">Logout</span>
|
|
||||||
</div>
|
|
||||||
<i class="bi bi-chevron-right logout-arrow"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
105
html/signup.html
@ -1,105 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
|
|
||||||
<!--Info about page but not visible-->
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<!-- For responsive design: adapts width for different devices -->
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<!-- Title shown in browser tab -->
|
|
||||||
<title>OnlyPrompt - Login</title>
|
|
||||||
|
|
||||||
<!-- CSS files for variables, base styles, and login page -->
|
|
||||||
<link rel="stylesheet" href="../css/variables.css">
|
|
||||||
<link rel="stylesheet" href="../css/base.css">
|
|
||||||
<link rel="stylesheet" href="../css/signup.css">
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<!-- Main container for the login page (CSS layout) -->
|
|
||||||
<main class="login-page">
|
|
||||||
<!-- White login card -->
|
|
||||||
<section class="login-card">
|
|
||||||
<!-- Logo container -->
|
|
||||||
<div class="login-logo-wrapper">
|
|
||||||
<img src="../images/logo_full.png" alt="OnlyPrompt Logo" class="login-logo">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1 class="login-title">Sign Up</h1>
|
|
||||||
<p class="login-subtitle">Create your account to get started.</p>
|
|
||||||
|
|
||||||
<!-- Login form, id is used for JavaScript validation -->
|
|
||||||
<form id="loginForm" class="login-form">
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">Email Address</label>
|
|
||||||
<input
|
|
||||||
type="email"
|
|
||||||
id="email"
|
|
||||||
name="email"
|
|
||||||
placeholder="yourname@email.com"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="password">Password</label>
|
|
||||||
<!-- Password field with button to show/hide password -->
|
|
||||||
<div class="password-wrapper">
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="password"
|
|
||||||
name="password"
|
|
||||||
placeholder="Enter your password"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<button type="button" id="togglePassword" class="toggle-password">
|
|
||||||
Show <!-- Click to show/hide password -->
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">Full Name</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="fullName"
|
|
||||||
name="fullName"
|
|
||||||
placeholder="Enter your full name"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">Username</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="username"
|
|
||||||
name="username"
|
|
||||||
placeholder="Enter your username"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p class="signup-terms">
|
|
||||||
By signing up, you agree to our
|
|
||||||
<a href="#">Terms</a>,
|
|
||||||
<a href="#">Privacy Policy</a> and
|
|
||||||
<a href="#">Cookies Policy</a>.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
<button type="submit" class="login-button">Sign Up</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<p class="signup-text">
|
|
||||||
Have an account?
|
|
||||||
<a href="#">Log In</a>
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Script for login logic and form validation -->
|
|
||||||
<script src="../js/login.js"></script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
<!--
|
|
||||||
Reusable topbar for OnlyPrompt
|
|
||||||
- Search in the middle
|
|
||||||
- small action icons
|
|
||||||
- profile avatar on the right
|
|
||||||
-->
|
|
||||||
|
|
||||||
<header class="topbar-shell">
|
|
||||||
<div class="topbar-search">
|
|
||||||
<i class="bi bi-search"></i>
|
|
||||||
<input type="text" placeholder="Search">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Profile avatar on the right (must be changed with backend) -->
|
|
||||||
<button class="topbar-avatar-btn" aria-label="Profile">
|
|
||||||
<img src="../images/content/cat.png" alt="Profile Picture" class="topbar-avatar">
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
Before Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 579 KiB |
|
Before Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 868 KiB |
|
Before Width: | Height: | Size: 189 KiB |
|
Before Width: | Height: | Size: 1.8 MiB |
|
Before Width: | Height: | Size: 106 KiB |
|
Before Width: | Height: | Size: 43 KiB |
|
Before Width: | Height: | Size: 63 KiB |
@ -1,4 +0,0 @@
|
|||||||
fastapi
|
|
||||||
uvicorn
|
|
||||||
pydantic
|
|
||||||
email-validator
|
|
||||||